/* ── The Method · Deep-dive sections: VISDI · The Ledger · CLV ── Content sourced from Minerva_VISDI_Ledger_CLV_Explained.pptx. Consumes shared globals: Eyebrow, Icon. Exposes window.MethodDeepDive. */ const VISDI_SKY = "#3FB1E5"; const LEDGER_GREEN = "#069348"; const CLV_GOLD = "#8E86C8"; /* ---------- small shared building blocks ---------- */ function DDHead({ eyebrow, accent, title, lead, mb = 52, leadMaxWidth = 680 }) { return (
{eyebrow}

{title}

{lead &&

{lead}

}
); } function SubHead({ accent, kicker, children, noRule, center }) { return (

{kicker}

{!noRule && } {children}
); } /* a small feature card: icon + title + description */ function FeatureCard({ icon, accent, title, desc, tall }) { return (

{title}

{desc}

); } /* Expandable pillar card - collapsed shows icon + title (+ optional subtitle); the chevron button expands to reveal the body copy and optional deliverables. */ function PillarCard({ icon, title, tag, body, deliver, accent = VISDI_SKY, titleSize = 25, image, titleNoWrap }) { const [open, setOpen] = React.useState(false); const innerRef = React.useRef(null); const [h, setH] = React.useState(0); const toggle = () => { if (innerRef.current) setH(innerRef.current.scrollHeight); setOpen((o) => !o); }; return (

{body}

{deliver &&
Deliverables

{deliver}

}
); } /* Build an explicit grid track list ("208px 208px 208px") rather than the repeat(N, …) shorthand — responsive.css has a generic [style*="...repeat(4"] safety-net rule that would otherwise override our fixed square tracks. */ function squareTracks(size, fallback) { if (!size) return fallback; return Array(size.cols).fill(`${size.w}px`).join(" "); } /* detect hover-capable (desktop) vs touch (mobile): desktop reveals body on hover/focus, touch expands it on tap */ function useCanHover() { const [canHover, setCanHover] = React.useState(true); React.useEffect(() => { if (!window.matchMedia) return; const mq = window.matchMedia("(hover: hover) and (pointer: fine)"); const update = () => setCanHover(mq.matches); update(); mq.addEventListener ? mq.addEventListener("change", update) : mq.addListener(update); return () => {mq.removeEventListener ? mq.removeEventListener("change", update) : mq.removeListener(update);}; }, []); return canHover; } /* equalize heights of matching elements inside a container, re-running on resize — keeps the two collapsed pillar headers the same size even when one subtitle wraps to an extra line at narrow widths */ function useEqualHeights(ref, selector) { React.useLayoutEffect(() => { const root = ref.current; if (!root) return; const apply = () => { const els = [...root.querySelectorAll(selector)]; if (els.length < 2) return; els.forEach((el) => {el.style.minHeight = "";}); let max = 0; els.forEach((el) => {max = Math.max(max, el.offsetHeight);}); els.forEach((el) => {el.style.minHeight = max + "px";}); }; apply(); const timers = [120, 400, 900].map((ms) => setTimeout(apply, ms)); window.addEventListener("resize", apply); if (document.fonts && document.fonts.ready) document.fonts.ready.then(apply); const imgs = [...root.querySelectorAll("img")]; imgs.forEach((im) => {if (!im.complete) im.addEventListener("load", apply);}); return () => {window.removeEventListener("resize", apply);timers.forEach(clearTimeout);imgs.forEach((im) => im.removeEventListener("load", apply));}; }, []); } /* Find the smallest SQUARE side that fully contains the tallest card's *expanded* content, then lock every card to that fixed size. The box is therefore big enough that revealing the body never resizes it, yet as small as possible so there's minimal empty space once the text is showing. Re-runs on resize / font / image load. Returns the side in px (null until first measure → cards fall back to aspect-ratio). colOptions: candidate column counts, largest first; picks the largest whose square would be a sensible width, else the smallest (which then drops to a single column). */ function useSnugSquare(ref, colOptions = [2, 1]) { const [size, setSize] = React.useState(null); React.useLayoutEffect(() => { const root = ref.current; if (!root) return; const measureAt = (w) => { const cards = [...root.querySelectorAll("button")]; let max = 0; cards.forEach((c) => { const gw = c.querySelector(".oc-reveal"); const img = c.querySelector(".oc-img"); const sv = { w: c.style.width, mw: c.style.maxWidth, h: c.style.height, ar: c.style.aspectRatio, jc: c.style.justifyContent, rows: gw.style.gridTemplateRows, imgD: img && img.style.display }; c.style.aspectRatio = "auto"; c.style.height = "auto"; c.style.width = w + "px"; c.style.maxWidth = w + "px"; c.style.justifyContent = "flex-start"; // closed state: image shown, reveal collapsed gw.style.gridTemplateRows = "0fr"; if (img) img.style.display = ""; const closedH = c.scrollHeight; // open state: image hidden, reveal expanded (text replaces image) gw.style.gridTemplateRows = "1fr"; if (img) img.style.display = "none"; const openH = c.scrollHeight; max = Math.max(max, closedH, openH); c.style.width = sv.w;c.style.maxWidth = sv.mw;c.style.height = sv.h; c.style.aspectRatio = sv.ar;c.style.justifyContent = sv.jc;gw.style.gridTemplateRows = sv.rows; if (img) img.style.display = sv.imgD || ""; }); return max; }; const apply = () => { // available width is the COLUMN's width (parent), not the grid's own shrink-to-fit // width — using the grid itself would feed its locked track size back in. const avail = (root.parentElement || root).clientWidth; const gap = 14; // pick the largest candidate column count whose per-column width is still sensible let cols = colOptions[colOptions.length - 1]; let perCol = Math.floor((avail - gap * (cols - 1)) / cols); for (const opt of colOptions) { const pc = Math.floor((avail - gap * (opt - 1)) / opt); if (pc >= 168) {cols = opt;perCol = pc;break;} } const cap = Math.max(160, Math.min(300, perCol)); // smallest square side S in [160, cap] whose width holds the expanded content within S let chosen = null; for (let s = 160; s <= cap; s += 6) { if (measureAt(s) <= s) {chosen = { w: s, h: s, cols };break;} } // no square fits within the column width → keep width at cap and grow height to // the content (snug portrait) so text never clips and the box still never resizes if (!chosen) {chosen = { w: cap, h: Math.ceil(measureAt(cap)), cols };} setSize((prev) => prev && prev.w === chosen.w && prev.h === chosen.h && prev.cols === chosen.cols ? prev : chosen); }; apply(); const timers = [120, 400, 900].map((ms) => setTimeout(apply, ms)); window.addEventListener("resize", apply); if (document.fonts && document.fonts.ready) document.fonts.ready.then(apply); return () => {window.removeEventListener("resize", apply);timers.forEach(clearTimeout);}; }, []); return size; } /* Outcome card: collapsed shows icon + title only. Desktop reveals the body on hover/focus; touch toggles it on tap. */ function OutcomeCard({ icon, accent, title, desc, canHover, size, image, imageMaxH = 78, stacked }) { const [open, setOpen] = React.useState(false); const show = () => setOpen(true); const hide = () => setOpen(false); const toggle = () => setOpen((o) => !o); const interaction = canHover ? { onMouseEnter: show, onMouseLeave: hide, onFocus: show, onBlur: hide } : { onClick: toggle }; return ( ); } /* ================= SECTION 1 · VISDI ================= */ function VisdiDeepDive() { const canHover = useCanHover(); const pillarsRef = React.useRef(null); useEqualHeights(pillarsRef, "button"); const outcomesRef = React.useRef(null); const sqSize = useSnugSquare(outcomesRef); const visCaps = [ { icon: "git-merge", title: "Process Maps", desc: "UML-standard flow diagrams integrating intuitive structure, custom visuals, and pre-defined language for any process, system, or workflow." }, { icon: "eye", title: "Technical Illustrations", desc: "Accurate scientific visuals depicting biological and physical processes, components, and conditions - fast to produce and easy to update." }, { icon: "package", title: "3D Visualizations", desc: "Isometric and dynamic 3D representations of physical, chemical, and biological systems - from cellular interactions to facility-level flows." }, { icon: "network", title: "Visuals for ML & AI", desc: "Labeled visual datasets designed for immediate human comprehension and direct use in machine learning and AI model training." }, { icon: "layout-grid", title: "Infographics", desc: "Combinations of text, graphics, and images that communicate complex relationships in an engaging, understandable format for any audience." }, { icon: "activity", title: "KPI Dashboards", desc: "Continuously updated visual dashboards that surface operational and quality performance data - structured to support the right decisions at the right time." }]; const digOutcomes = [ { icon: "zap", title: "Turn data into actionable intelligence", image: "uploads/Pareto Chart.png", desc: "Structure and visualize complex operational data to accelerate investigations, identify risks earlier, and support faster compliant decisions." }, { icon: "activity", title: "Accelerate innovation with analytics", image: "uploads/KPI.png", desc: "Apply ML and AI-assisted evaluation to identify patterns, improve process understanding, and strengthen decision-making with traceability." }, { icon: "network", title: "Connect your operational ecosystem", image: "uploads/Operational ecosystem v3.png", imageMaxH: 132, desc: "Integrate MES, ERP, EBR, QMS, laboratory, and process systems into connected digital environments with improved visibility and execution." }, { icon: "users", title: "Enable cross-functional execution", image: "uploads/Cross functional team v2.png", desc: "Create shared visual-digital environments that reduce ambiguity and align multidisciplinary teams around a common operational understanding." }]; const digDeliverables = [ ["Electronic Batch Records (EBR)", "Process knowledge structured for MES, ERP, and manufacturing environments."], ["Digital QMS", "Quality management systems digitalized using industry platforms and low-code solutions."], ["Custom Knowledge Databases", "Low-code and enterprise apps that centralize product, process, quality, and regulatory info."], ["Predictive Digital Models", "Computational simulations that reduce uncertainty before physical implementation."]]; return (
{/* Two pillars - expandable */}
{[ { icon: "eye", title: "Visualization", image: "uploads/Visualizations v4-82f99534.png", tag: "See, understand, and accelerate informed decision making.", body: "Minerva creates visual representations of complex physical and biological processes - helping researchers, engineers, quality, and regulatory teams quickly understand conditions, risks, and improvement opportunities.", deliver: "Process maps · technical illustrations · 3D models · infographics · KPI dashboards · Visuals for ML & AI." }, { icon: "database", title: "Digitalization", image: "uploads/Digitalization.png", tag: "Transform fragmented operational data into trusted, executable knowledge.", body: "Minerva converts manufacturing, quality, technical, and operational data into structured visual-digital knowledge environments - connecting people, systems, and processes into governed digital environments that improve decisions and compliant execution.", deliver: "Digital QMS · EBR integration · Custom Databases · AI-ready data structures · Predictive Digital Models." }]. map((p) => )}
{/* Digitalization outcomes — centered header + body on top, four cards in a row below */}

Minerva connects people, systems, processes, and operational knowledge into governed digital environments.

{digOutcomes.map((c) => )}
); } /* ================= SECTION 2 · THE LEDGER ================= */ function LedgerDeepDive() { const canHover = useCanHover(); const rolesRef = React.useRef(null); const roleSize = useSnugSquare(rolesRef, [3, 1]); const jobs = [ { icon: "scale", title: "Governance", desc: "Enforces who can create, verify, and approve at each risk level. Defines accountability at every gate." }, { icon: "message-circle", title: "Communication", desc: "Provides the client-facing view of the knowledge layers- what exists, what changed, what was rejected and why." }, { icon: "git-merge", title: "Traceability", desc: "Keeps the full history of every knowledge packet from the moment it passed the trust gate. Nothing is erased. Everything is linked." }]; const roles = [ { icon: "eye", title: "Observer", desc: "Present during the activity. Confirms the action happened as described. Used for lower-risk routine tasks." }, { icon: "shield-check", title: "Verifier", desc: "Reviews the model's output and confirms it is accurate before the packet is accepted into the Ledger." }, { icon: "badge", title: "Approver", desc: "Authorizes the packet for use. Signs off at the risk level required - regulatory submissions require the most senior approval." }]; return (
{/* Three jobs */}
{jobs.map((c) => )}
{/* Governance structure — narrative left, role cards stacked right */}

Not every action needs the same review. The Ledger grades each activity to its risk and routes it to the right validation layer. A generic log records events; the Ledger records events and enforces who may approve them, and at what level of scrutiny.

Result: the Ledger produces the validation evidence an auditor expects to find - not as a retrospective effort, but as a natural product of how work is done.

Human-in-command roles

{roles.map((c) => )}
); } /* ================= SECTION 3 · CLV ================= */ /* Lifecycle phase card: collapsed shows phase number + title only; clicking the box expands it to reveal the lead line and bullet detail. Uses the grid-template-rows 0fr→1fr reveal (no measurement, no CSS transition that the scroll-animation engine could wedge). */ function PhaseCard({ n, title, bullets, lead, accent }) { const [open, setOpen] = React.useState(false); const innerRef = React.useRef(null); const [h, setH] = React.useState(0); const toggle = () => { if (innerRef.current) setH(innerRef.current.scrollHeight); setOpen((o) => !o); }; // Reveal lives OUTSIDE the button (sibling), same structure as PillarCard — this is // what lets the max-height transition animate smoothly here; nesting it inside the // button leaves the transition wedged by the page's scroll-animation engine. return (
{lead &&

{lead}

}
    {bullets.map((b, bi) =>
  • {b}
  • )}
); } /* Lifecycle loop diagram — the five ring boxes ARE the expandable phase cards: each carries a chevron and reveals its lead + bullets in place (accordion, one open at a time). The open box raises its z-index and overlays as a popover so the ring layout stays intact. This replaces the separate horizontal phase-card row. */ function RingPhaseBox({ p, open, onToggle, accent }) { const innerRef = React.useRef(null); const [h, setH] = React.useState(0); const handle = () => { if (innerRef.current) setH(innerRef.current.scrollHeight); onToggle(); }; return (
{p.lead &&

{p.lead}

}
    {p.bullets.map((b, bi) =>
  • {b}
  • )}
); } function SeqPhaseBox({ p, n, accent, open, onToggle }) { const innerRef = React.useRef(null); const [h, setH] = React.useState(0); const handle = () => { if (innerRef.current) setH(innerRef.current.scrollHeight); onToggle(); }; return (
{p.lead &&

{p.lead}

}
    {p.bullets.map((b, bi) =>
  • {b}
  • )}
); } function LifecycleLoop() { const [openIdx, setOpenIdx] = React.useState(-1); // box centers as % of container W/H (ring around the centered panel) // five boxes spaced evenly (72° apart) on one circle, so the connecting // arrows can be true arcs of that same circle. Each box also carries the // phase detail (lead + bullets) revealed on chevron toggle. const phases = [ { label: "Define", sub: "Visualize + Structure", x: 50, y: 10.7, lead: "Map the context layer:", bullets: ["What components are needed (skills, tools, documents, MD files).", "Define what each should do.", "What language standards apply."] }, { label: "Lock", sub: "Verify \u00b7 Trust Gate", x: 76.2, y: 37.9, bullets: ["Version and approve every artifact.", "Assign each component a locked state.", "Production may only use locked, approved context - no ad-hoc changes."] }, { label: "Apply", sub: "Digitalize + Execute", x: 66.2, y: 81.8, bullets: ["The live system runs on locked context only.", "Every interaction is traceable to a specific version of every context component in use."] }, { label: "Monitor", sub: "Automatic flagging", x: 33.8, y: 81.8, bullets: ["Continuously watch for drift, ambiguity, and stale content.", "Any context that no longer matches the process it describes triggers a versioned update."] }, { label: "Review", sub: "Feed back · Learning Gate", x: 23.9, y: 37.9, bullets: ["Formal review loops back to Define.", "New process knowledge, new risk findings, or model behavior changes can all trigger a new cycle."] }]; // Cycle arrows are arcs of ONE circle (center 500,350, radius R) in a // 1000×700 space. Each arc runs between two boxes with a wide angular gap at // each box (±27°) so both endpoints — including the arrowhead — sit clear of // the boxes (boxes are centered on the circle and extend past R at the corners). return (
{/* sequential row: Define → Lock → Apply → Monitor → Review */}
{phases.map((p, i) =>
setOpenIdx(openIdx === i ? -1 : i)} />
{i < phases.length - 1 && }
)}
{/* dashed loop-back: Review feeds back to Define */}