/* Common Works — Feature Lab (/lab)
   Internal sandbox: 15 candidate visual/interactive directions,
   each independently toggleable from the side panel. Nothing here
   is wired into the live site — it's a preview surface only. */
const { useState, useEffect, useRef } = React;

const REDUCE_MOTION = !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);

/* ---------------- shared canvas raf loop ---------------- */
function useCanvasLoop(draw) {
  const ref = useRef(null);
  useEffect(() => {
    const canvas = ref.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    function resize() {
      const rect = canvas.getBoundingClientRect();
      canvas.width = rect.width;
      canvas.height = rect.height;
    }
    resize();
    window.addEventListener("resize", resize);
    let raf, running = true;
    const t0 = performance.now();
    function frame(now) {
      if (!running) return;
      draw(ctx, canvas.width, canvas.height, (now - t0) / 1000);
      raf = requestAnimationFrame(frame);
    }
    if (REDUCE_MOTION) draw(ctx, canvas.width, canvas.height, 0);
    else raf = requestAnimationFrame(frame);
    return () => { running = false; if (raf) cancelAnimationFrame(raf); window.removeEventListener("resize", resize); };
  }, []);
  return ref;
}

/* ============================================================
   A — FINANCE-NATIVE
   ============================================================ */

/* 1. Live EBITDA waterfall */
function buildWaterfall() {
  const raw = [
    { label: "Base EBITDA", delta: 22, kind: "base" },
    { label: "Revenue Growth", delta: 4, kind: "add" },
    { label: "Margin Expansion", delta: 3, kind: "add" },
    { label: "AI Automation", delta: 5, kind: "add" },
    { label: "Multiple Expansion", delta: 2.5, kind: "add" },
  ];
  let running = 0;
  const bars = raw.map((r) => { const start = running; running += r.delta; return { ...r, start, end: running }; });
  bars.push({ label: "Exit EBITDA", delta: running, kind: "total", start: 0, end: running });
  return bars;
}
const WATERFALL_BARS = buildWaterfall();

function WaterfallViz() {
  const [hover, setHover] = useState(null);
  const max = WATERFALL_BARS[WATERFALL_BARS.length - 1].end;
  const chartH = 200, chartW = 560, barW = 76;
  const gap = (chartW - barW * WATERFALL_BARS.length) / (WATERFALL_BARS.length - 1);
  const scale = (chartH - 30) / max;
  const active = hover != null ? WATERFALL_BARS[hover] : null;
  return (
    <div className="lab-waterfall">
      <div className="lab-wf-readout kicker">
        {active
          ? active.kind === "total" ? `Exit EBITDA: $${active.end.toFixed(1)}M`
          : active.kind === "base" ? `Base EBITDA: $${active.end.toFixed(1)}M`
          : `${active.label}: +$${active.delta.toFixed(1)}M → $${active.end.toFixed(1)}M running`
          : "Hover a segment"}
      </div>
      <svg viewBox={`0 0 ${chartW} ${chartH}`} className="lab-wf-svg">
        <line x1="0" y1={chartH - 20} x2={chartW} y2={chartH - 20} className="lab-wf-base" />
        {WATERFALL_BARS.map((b, i) => {
          const x = i * (barW + gap);
          const y = chartH - 20 - b.end * scale;
          const h = Math.max((b.end - b.start) * scale, 2);
          return (
            <g key={i} onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(null)}>
              <rect x={x} y={y} width={barW} height={h} className={"lab-wf-bar " + b.kind + (hover === i ? " on" : "")} />
              <text x={x + barW / 2} y={chartH - 4} textAnchor="middle" className="lab-wf-label">{b.label}</text>
            </g>
          );
        })}
      </svg>
    </div>
  );
}

/* 2. ROI / value-creation calculator */
function RoiCalculator() {
  const [ebitda, setEbitda] = useState(20);
  const [hold, setHold] = useState(4);
  const [uplift, setUplift] = useState(8);
  const multiple = 8.5;
  const current = ebitda * multiple;
  const projected = ebitda * (1 + (uplift / 100) * hold) * multiple;
  const delta = projected - current;
  const max = Math.max(current, projected, 1) * 1.15;
  return (
    <div className="lab-roi">
      <div className="lab-roi-inputs">
        <label>Target EBITDA ($M)
          <input type="number" value={ebitda} min="1" onChange={(e) => setEbitda(+e.target.value || 0)} />
        </label>
        <label>Hold Period (yrs)
          <input type="number" value={hold} min="1" max="10" onChange={(e) => setHold(+e.target.value || 0)} />
        </label>
        <label>Annual AI-Driven Margin Uplift (%)
          <input type="number" value={uplift} min="0" max="30" onChange={(e) => setUplift(+e.target.value || 0)} />
        </label>
      </div>
      <div className="lab-roi-chart">
        <div className="lab-roi-bar-row">
          <span className="kicker">Entry Value</span>
          <div className="lab-roi-track"><div className="lab-roi-bar base" style={{ width: (current / max * 100) + "%" }} /></div>
          <span className="lab-roi-val">${current.toFixed(0)}M</span>
        </div>
        <div className="lab-roi-bar-row">
          <span className="kicker">Projected Exit Value</span>
          <div className="lab-roi-track"><div className="lab-roi-bar proj" style={{ width: (projected / max * 100) + "%" }} /></div>
          <span className="lab-roi-val">${projected.toFixed(0)}M</span>
        </div>
      </div>
      <p className="lab-roi-note">Illustrative only, at a {multiple}x exit multiple — +${delta.toFixed(0)}M from AI-driven value creation over {hold} years. Not investment advice.</p>
    </div>
  );
}

/* 3. Underwriting model terminal */
const TERMINAL_ROWS = [
  { k: "REV_GROWTH_YOY", v: 14.2, unit: "%" },
  { k: "GROSS_MARGIN", v: 61.8, unit: "%" },
  { k: "EBITDA_MARGIN", v: 24.3, unit: "%" },
  { k: "AUTOMATION_INDEX", v: 0.72, unit: "" },
  { k: "AI_READINESS_SCORE", v: 6.4, unit: "/10" },
];
function ModelTerminal() {
  const [rows, setRows] = useState(TERMINAL_ROWS);
  useEffect(() => {
    if (REDUCE_MOTION) return;
    const id = setInterval(() => {
      setRows((prev) => prev.map((r) => ({ ...r, v: +(r.v + (Math.random() - 0.5) * (r.unit === "%" ? 0.3 : 0.02)).toFixed(2) })));
    }, 1400);
    return () => clearInterval(id);
  }, []);
  return (
    <div className="lab-terminal">
      <div className="lab-terminal-bar">
        <span className="dot r" /><span className="dot y" /><span className="dot g" />
        <span className="lab-terminal-title">underwriting_model.live</span>
      </div>
      <div className="lab-terminal-body">
        {rows.map((r) => (
          <div className="lab-terminal-row" key={r.k}>
            <span className="lab-terminal-k">{r.k}</span>
            <span className="lab-terminal-v">{r.v}{r.unit}</span>
          </div>
        ))}
        <div className="lab-terminal-row lab-terminal-cursor">
          <span className="lab-terminal-k">&gt;</span><span className="lab-terminal-caret">_</span>
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   B — SYSTEMS & ENGINEERING
   ============================================================ */

/* 4. Capability graph */
const CAP_NODES = [
  { id: "dil", label: "Diligence", x: 80, y: 60, core: true },
  { id: "plan", label: "Blueprint", x: 220, y: 30, core: true },
  { id: "deploy", label: "Deploy", x: 220, y: 110, core: true },
  { id: "co1", label: "Portfolio Co A", x: 360, y: 20 },
  { id: "co2", label: "Portfolio Co B", x: 360, y: 70 },
  { id: "data", label: "Data Infra", x: 360, y: 120 },
  { id: "mlops", label: "MLOps", x: 120, y: 140 },
];
const CAP_EDGES = [["dil", "plan"], ["plan", "deploy"], ["deploy", "co1"], ["deploy", "co2"], ["deploy", "data"], ["dil", "mlops"], ["plan", "mlops"]];

function CapabilityGraph() {
  const [hover, setHover] = useState(null);
  const [t, setT] = useState(0);
  useEffect(() => {
    if (REDUCE_MOTION) return;
    let raf, running = true;
    const start = performance.now();
    function frame(now) { if (!running) return; setT((now - start) / 1000); raf = requestAnimationFrame(frame); }
    raf = requestAnimationFrame(frame);
    return () => { running = false; cancelAnimationFrame(raf); };
  }, []);
  const nodeMap = Object.fromEntries(CAP_NODES.map((n, i) => [n.id, {
    ...n,
    x: n.x + (REDUCE_MOTION ? 0 : Math.sin(t * 0.6 + i) * 3),
    y: n.y + (REDUCE_MOTION ? 0 : Math.cos(t * 0.5 + i) * 3),
  }]));
  return (
    <svg viewBox="0 0 420 160" className="lab-cap-svg">
      {CAP_EDGES.map(([a, b], i) => {
        const A = nodeMap[a], B = nodeMap[b];
        const on = hover && (hover === a || hover === b);
        return <line key={i} x1={A.x} y1={A.y} x2={B.x} y2={B.y} className={"lab-cap-edge" + (on ? " on" : "")} />;
      })}
      {CAP_NODES.map((n) => {
        const N = nodeMap[n.id];
        return (
          <g key={n.id} onMouseEnter={() => setHover(n.id)} onMouseLeave={() => setHover(null)}>
            <circle cx={N.x} cy={N.y} r={n.core ? 7 : 5} className={"lab-cap-node" + (n.core ? " core" : "") + (hover === n.id ? " on" : "")} />
            <text x={N.x} y={N.y - 12} textAnchor="middle">{n.label}</text>
          </g>
        );
      })}
    </svg>
  );
}

/* 5. Self-building architecture diagram */
const ARCH_NODES = [
  { id: "ingest", label: "Data Ingestion", x: 20 },
  { id: "feature", label: "Feature Store", x: 170 },
  { id: "model", label: "Model Layer", x: 320 },
  { id: "orchestration", label: "Orchestration", x: 470 },
];
function ArchitectureDiagram() {
  const [step, setStep] = useState(0);
  useEffect(() => {
    if (REDUCE_MOTION) { setStep(ARCH_NODES.length); return; }
    const timers = ARCH_NODES.map((_, i) => setTimeout(() => setStep(i + 1), 260 * i + 200));
    return () => timers.forEach(clearTimeout);
  }, []);
  return (
    <svg viewBox="0 0 560 100" className="lab-arch-svg">
      {ARCH_NODES.slice(0, -1).map((n, i) => (
        <line key={n.id} pathLength="1" x1={n.x + 90} y1="40" x2={ARCH_NODES[i + 1].x} y2="40"
          className={"lab-arch-line" + (step > i + 1 ? " on" : "")} />
      ))}
      {ARCH_NODES.map((n, i) => (
        <g key={n.id} className={"lab-arch-node" + (step > i ? " on" : "")}>
          <rect x={n.x} y="16" width="90" height="48" rx="2" />
          <text x={n.x + 45} y="44" textAnchor="middle">{n.label}</text>
        </g>
      ))}
    </svg>
  );
}

/* 6. Scroll-scrubbed pipeline */
const PIPE_STAGES = ["Raw Signal", "Investment Thesis", "Value Creation Plan", "Deployed System"];
function ScrollPipeline() {
  const containerRef = useRef(null);
  const [progress, setProgress] = useState(0);
  useEffect(() => {
    function onScroll() {
      const el = containerRef.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const total = rect.height - window.innerHeight;
      if (total <= 0) return;
      setProgress(Math.min(1, Math.max(0, -rect.top / total)));
    }
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  const stageIdx = Math.min(PIPE_STAGES.length - 1, Math.floor(progress * PIPE_STAGES.length));
  return (
    <div className="lab-scrollpipe" ref={containerRef} style={{ height: "220vh" }}>
      <div className="lab-scrollpipe-sticky">
        <svg viewBox="0 0 600 120" className="lab-pipe-svg">
          <line x1="90" y1="60" x2="510" y2="60" className="lab-pipe-track" />
          <line x1="90" y1="60" x2={90 + progress * 420} y2="60" className="lab-pipe-fill" />
          {PIPE_STAGES.map((s, i) => {
            const x = 90 + (i / (PIPE_STAGES.length - 1)) * 420;
            return (
              <g key={s}>
                <circle cx={x} cy="60" r="7" className={"lab-pipe-node" + (i <= stageIdx ? " on" : "")} />
                <text x={x} y="92" textAnchor="middle">{s}</text>
              </g>
            );
          })}
        </svg>
        <p className="lab-pipe-caption kicker">Scroll to advance — stage {stageIdx + 1} of {PIPE_STAGES.length}: <b>{PIPE_STAGES[stageIdx]}</b></p>
      </div>
    </div>
  );
}

/* ============================================================
   C — INTERACTIVE METAPHORS
   ============================================================ */

/* 7. Before / after operating model slider */
function BeforeAfterSlider() {
  const ref = useRef(null);
  const [pct, setPct] = useState(50);
  const dragging = useRef(false);
  function setFromClientX(clientX) {
    const rect = ref.current.getBoundingClientRect();
    setPct(Math.max(0, Math.min(100, ((clientX - rect.left) / rect.width) * 100)));
  }
  function onDown(e) { dragging.current = true; setFromClientX(e.touches ? e.touches[0].clientX : e.clientX); }
  function onMove(e) { if (!dragging.current) return; setFromClientX(e.touches ? e.touches[0].clientX : e.clientX); }
  function onUp() { dragging.current = false; }
  useEffect(() => {
    window.addEventListener("mousemove", onMove);
    window.addEventListener("mouseup", onUp);
    window.addEventListener("touchmove", onMove);
    window.addEventListener("touchend", onUp);
    return () => {
      window.removeEventListener("mousemove", onMove);
      window.removeEventListener("mouseup", onUp);
      window.removeEventListener("touchmove", onMove);
      window.removeEventListener("touchend", onUp);
    };
  }, []);
  return (
    <div className="lab-ba-wrap" ref={ref} onMouseDown={onDown} onTouchStart={onDown}>
      <div className="lab-ba-layer lab-ba-after">
        <div className="lab-ba-tag">After — Common Works</div>
        <div className="lab-ba-boxes">
          {["Data", "Model", "Automation", "Insight"].map((b) => <div key={b} className="lab-ba-node on">{b}</div>)}
        </div>
      </div>
      <div className="lab-ba-layer lab-ba-before" style={{ clipPath: `inset(0 ${100 - pct}% 0 0)` }}>
        <div className="lab-ba-tag">Before</div>
        <div className="lab-ba-boxes">
          {["Manual Reports", "Spreadsheets", "Tribal Knowledge", "Delay"].map((b) => <div key={b} className="lab-ba-node">{b}</div>)}
        </div>
      </div>
      <div className="lab-ba-handle" style={{ left: pct + "%" }}>
        <span className="lab-ba-grip" />
      </div>
    </div>
  );
}

/* 8. Underwriting lens */
const LENS_SIZE = 190, LENS_RADIUS = LENS_SIZE / 2, LENS_ZOOM = 1.9;
function UnderwritingLens() {
  const ref = useRef(null);
  const [pos, setPos] = useState(null);
  function onMove(e) {
    const rect = ref.current.getBoundingClientRect();
    setPos({ x: e.clientX - rect.left, y: e.clientY - rect.top, w: rect.width });
  }
  const rows = [
    { label: "Revenue Growth", base: "+4.2%", detail: "vs. 2.1% cohort avg" },
    { label: "Gross Margin", base: "61.8%", detail: "+340bps YoY" },
    { label: "Automation Coverage", base: "32%", detail: "18 processes identified" },
    { label: "Data Maturity", base: "Tier 2", detail: "clean warehouse, no MLOps" },
    { label: "AI Readiness Score", base: "6.4/10", detail: "top-quartile potential" },
  ];
  return (
    <div className="lab-lens-wrap" ref={ref} onMouseMove={onMove} onMouseLeave={() => setPos(null)}>
      <div className="lab-lens-base">
        {rows.map((r) => <div className="lab-lens-row" key={r.label}><span>{r.label}</span><span>{r.base}</span></div>)}
      </div>
      {pos && (
        <div className="lab-lens" style={{ left: pos.x, top: pos.y, width: LENS_SIZE, height: LENS_SIZE, marginLeft: -LENS_RADIUS, marginTop: -LENS_RADIUS }}>
          <div className="lab-lens-detail" style={{
            width: pos.w,
            transform: `translate(${LENS_RADIUS - pos.x * LENS_ZOOM}px, ${LENS_RADIUS - pos.y * LENS_ZOOM}px) scale(${LENS_ZOOM})`,
          }}>
            {rows.map((r) => (
              <div className="lab-lens-row" key={r.label}>
                <span>{r.label}</span><span>{r.base} <i>{r.detail}</i></span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

/* 9. Coverage network map */
const COVERAGE_NODES = [
  { id: "industrials", label: "Industrials", x: 60, y: 40 },
  { id: "healthcare", label: "Healthcare", x: 220, y: 20 },
  { id: "financial", label: "Financial Services", x: 340, y: 60 },
  { id: "consumer", label: "Consumer", x: 300, y: 160 },
  { id: "data", label: "Data & Infra", x: 120, y: 160 },
];
const COV_HUB = { x: 200, y: 100 };
function CoverageMap() {
  const [hover, setHover] = useState(null);
  return (
    <svg viewBox="0 0 400 200" className="lab-coverage">
      {COVERAGE_NODES.map((n) => (
        <path key={n.id}
          d={`M${COV_HUB.x},${COV_HUB.y} Q${(COV_HUB.x + n.x) / 2},${(COV_HUB.y + n.y) / 2 - 30} ${n.x},${n.y}`}
          className={"cov-arc" + (hover === n.id ? " on" : "")} />
      ))}
      <circle cx={COV_HUB.x} cy={COV_HUB.y} r="7" className="cov-hub" />
      {COVERAGE_NODES.map((n) => (
        <g key={n.id} onMouseEnter={() => setHover(n.id)} onMouseLeave={() => setHover(null)}>
          <circle cx={n.x} cy={n.y} r="5" className={"cov-node" + (hover === n.id ? " on" : "")} />
          <text x={n.x} y={n.y - 12} textAnchor="middle">{n.label}</text>
        </g>
      ))}
    </svg>
  );
}

/* ============================================================
   D — MOTION & TYPE
   ============================================================ */

/* 10. Kinetic headline decode */
const DECODE_FINAL = "We turn AI into underwritable value creation.";
const DECODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ#%&$@!?/*+=";
function DecodeHeadline() {
  const [text, setText] = useState(REDUCE_MOTION ? DECODE_FINAL : "");
  useEffect(() => {
    if (REDUCE_MOTION) return;
    let frame = 0;
    const totalFrames = 28;
    const id = setInterval(() => {
      frame++;
      const revealCount = Math.floor((frame / totalFrames) * DECODE_FINAL.length);
      let out = "";
      for (let i = 0; i < DECODE_FINAL.length; i++) {
        if (DECODE_FINAL[i] === " ") { out += " "; continue; }
        out += i < revealCount ? DECODE_FINAL[i] : DECODE_CHARS[Math.floor(Math.random() * DECODE_CHARS.length)];
      }
      setText(out);
      if (frame >= totalFrames) { setText(DECODE_FINAL); clearInterval(id); }
    }, 45);
    return () => clearInterval(id);
  }, []);
  return <h3 className="lab-decode display">{text}</h3>;
}

/* 26. Binary decode headline — left-aligned, resolves from 0/1s, and
   re-scrambles the characters under the cursor back into 0/1s on hover.
   Each glyph's box is width-locked to its real character so swapping in
   a "0" or "1" never shifts the surrounding text. */
const BIN_DECODE_TEXT = "We turn AI into underwritable value creation.";
function randBit() { return Math.random() < 0.5 ? "0" : "1"; }
function BinaryDecodeHeadline() {
  const chars = BIN_DECODE_TEXT.split("");
  const spanRefs = useRef([]);
  const measuredRef = useRef(false);
  const [widths, setWidths] = useState(null);
  const [revealed, setRevealed] = useState(chars.length);
  const [, setTick] = useState(0);
  const [hoverSet, setHoverSet] = useState(() => new Set());

  // Measure each real glyph's width before the decode starts, so the
  // width-locked boxes reserve space for the actual letters.
  React.useLayoutEffect(() => {
    if (measuredRef.current) return;
    measuredRef.current = true;
    setWidths(spanRefs.current.map((el) => (el ? el.getBoundingClientRect().width : 0)));
    if (!REDUCE_MOTION) setRevealed(0);
  }, []);

  // decode-in on load
  useEffect(() => {
    if (widths === null || REDUCE_MOTION) return;
    let f = 0; const total = chars.length;
    const id = setInterval(() => {
      f++;
      setRevealed(Math.min(total, Math.round(f * (total / 30))));
      if (f >= 30) clearInterval(id);
    }, 45);
    return () => clearInterval(id);
  }, [widths]);

  // reroll the visible binary glyphs so they flicker
  useEffect(() => {
    if (REDUCE_MOTION) return;
    const id = setInterval(() => setTick((t) => (t + 1) % 1e6), 90);
    return () => clearInterval(id);
  }, []);

  function onMove(e) {
    if (REDUCE_MOTION) return;
    const next = new Set();
    for (let i = 0; i < spanRefs.current.length; i++) {
      const el = spanRefs.current[i];
      if (!el || chars[i] === " ") continue;
      const r = el.getBoundingClientRect();
      const dx = e.clientX - (r.left + r.width / 2);
      const dy = e.clientY - (r.top + r.height / 2);
      if (Math.hypot(dx, dy) < 48) next.add(i);
    }
    setHoverSet(next);
  }
  function onLeave() { setHoverSet(new Set()); }

  return (
    <h3 className="lab-bindecode display" onMouseMove={onMove} onMouseLeave={onLeave}>
      {chars.map((ch, i) => {
        const locked = widths ? { width: widths[i] + "px" } : null;
        if (ch === " ") {
          return <span key={i} ref={(el) => (spanRefs.current[i] = el)} className="lab-bin-sp" style={locked}>{" "}</span>;
        }
        const asBinary = hoverSet.has(i) || i >= revealed;
        return (
          <span key={i} ref={(el) => (spanRefs.current[i] = el)}
                className={"lab-bin-char" + (asBinary ? " on" : "")} style={locked}>
            {asBinary ? randBit() : ch}
          </span>
        );
      })}
    </h3>
  );
}

/* 11. Living case-study cards */
const SPARK_CARDS = [
  { id: 1, label: "AI Diligence", metric: "+18% conviction score", d: "M0,32 L10,30 L20,26 L30,28 L40,18 L50,20 L60,10 L70,14 L80,4" },
  { id: 2, label: "Value Creation Plan", metric: "14 initiatives sequenced", d: "M0,20 L10,24 L20,16 L30,22 L40,12 L50,16 L60,6 L70,10 L80,2" },
  { id: 3, label: "Embedded Build Pods", metric: "6-week time to first ship", d: "M0,30 L10,28 L20,30 L30,20 L40,22 L50,12 L60,14 L70,6 L80,8" },
];
function SparklineCards() {
  return (
    <div className="lab-spark-grid">
      {SPARK_CARDS.map((c) => (
        <div className="lab-spark-card" key={c.id}>
          <div className="lab-spark-label">{c.label}</div>
          <svg viewBox="0 0 80 36" className="lab-spark-svg"><path d={c.d} pathLength="1" /></svg>
          <div className="lab-spark-metric">{c.metric}</div>
        </div>
      ))}
    </div>
  );
}

/* 12. 3D parallax deliverables stack */
const STACK_LAYERS = [
  { label: "Thesis Document", z: 0 },
  { label: "Value Creation Plan", z: 40 },
  { label: "Deployed System", z: 80 },
];
function DeliverablesStack() {
  const wrapRef = useRef(null);
  const [rot, setRot] = useState({ x: 0, y: 0 });
  function onMove(e) {
    const rect = wrapRef.current.getBoundingClientRect();
    const px = (e.clientX - rect.left) / rect.width - 0.5;
    const py = (e.clientY - rect.top) / rect.height - 0.5;
    setRot({ x: py * -10, y: px * 14 });
  }
  return (
    <div className="lab-stack-wrap" ref={wrapRef} onMouseMove={onMove} onMouseLeave={() => setRot({ x: 0, y: 0 })}>
      <div className="lab-stack" style={{ transform: `rotateX(${rot.x}deg) rotateY(${rot.y}deg)` }}>
        {STACK_LAYERS.map((l, i) => (
          <div key={i} className="lab-stack-card" style={{ transform: `translateZ(${l.z}px) translateY(${-i * 14}px)` }}>
            <span className="kicker">{String(i + 1).padStart(2, "0")}</span>
            <h4>{l.label}</h4>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ============================================================
   E — AMBIENT
   ============================================================ */

/* 13. Ambient flow field */
function drawAmbientFlow(ctx, w, h, t) {
  ctx.clearRect(0, 0, w, h);
  const bands = 5;
  for (let b = 0; b < bands; b++) {
    const yBase = (h / (bands + 1)) * (b + 1);
    ctx.beginPath();
    for (let x = 0; x <= w; x += 8) {
      const y = yBase + Math.sin(x * 0.008 + t * 0.4 + b * 1.3) * 18 * Math.sin(t * 0.15 + b);
      x === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
    }
    ctx.strokeStyle = `rgba(27,58,47,${0.05 + b * 0.015})`;
    ctx.lineWidth = 1.5;
    ctx.stroke();
  }
}
function AmbientFlowField() {
  const ref = useCanvasLoop(drawAmbientFlow);
  return <div className="lab-canvas-wrap ambient"><canvas ref={ref} /></div>;
}

/* 14. Signal-field particles */
function ParticleFieldViz() {
  const stateRef = useRef(null);
  const draw = (ctx, w, h) => {
    if (!stateRef.current || stateRef.current.w !== w || stateRef.current.h !== h) {
      const pts = Array.from({ length: 42 }, () => ({
        x: Math.random() * w, y: Math.random() * h,
        vx: (Math.random() - 0.5) * 0.4, vy: (Math.random() - 0.5) * 0.4,
      }));
      stateRef.current = { pts, w, h };
    }
    const { pts } = stateRef.current;
    ctx.clearRect(0, 0, w, h);
    for (const p of pts) {
      p.x += p.vx; p.y += p.vy;
      if (p.x < 0) p.x = w; if (p.x > w) p.x = 0;
      if (p.y < 0) p.y = h; if (p.y > h) p.y = 0;
    }
    ctx.lineWidth = 1;
    for (let i = 0; i < pts.length; i++) {
      for (let j = i + 1; j < pts.length; j++) {
        const dx = pts[i].x - pts[j].x, dy = pts[i].y - pts[j].y;
        const d = Math.hypot(dx, dy);
        if (d < 120) {
          ctx.strokeStyle = `rgba(27,58,47,${(1 - d / 120) * 0.35})`;
          ctx.beginPath(); ctx.moveTo(pts[i].x, pts[i].y); ctx.lineTo(pts[j].x, pts[j].y); ctx.stroke();
        }
      }
    }
    for (const p of pts) {
      ctx.fillStyle = "rgba(27,58,47,0.55)";
      ctx.beginPath(); ctx.arc(p.x, p.y, 2, 0, Math.PI * 2); ctx.fill();
    }
  };
  const ref = useCanvasLoop(draw);
  return <div className="lab-canvas-wrap"><canvas ref={ref} /></div>;
}

/* 15. Market signal seismograph */
function drawSeismograph(ctx, w, h, t) {
  ctx.clearRect(0, 0, w, h);
  const midY = h / 2;
  ctx.strokeStyle = "rgba(22,20,15,0.14)"; ctx.lineWidth = 1;
  ctx.beginPath(); ctx.moveTo(0, midY); ctx.lineTo(w, midY); ctx.stroke();
  const speed = 60;
  ctx.beginPath();
  for (let x = 0; x <= w; x++) {
    const phase = (x + t * speed) * 0.02;
    const y = midY + Math.sin(phase) * 10 + Math.sin(phase * 2.7) * 6 + (Math.sin(phase * 0.13) > 0.85 ? -20 : 0);
    x === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
  }
  ctx.strokeStyle = "#1B3A2F"; ctx.lineWidth = 1.75;
  ctx.stroke();
}
function SeismographStrip() {
  const ref = useCanvasLoop(drawSeismograph);
  return <div className="lab-canvas-wrap seismo"><canvas ref={ref} /></div>;
}

/* ============================================================
   F — INCREMENTAL POLISH (the original 10 lighter-touch ideas)
   ============================================================ */

/* 16. Blueprint grid wash */
function GridWashDemo() {
  return (
    <div className="lab-gridwash">
      <div className="lab-gridwash-grid" />
      <div className="lab-gridwash-content">
        <span className="kicker" style={{ color: "#8FB5A6" }}>How we work</span>
        <h3>One firm across the investment lifecycle.</h3>
      </div>
    </div>
  );
}

/* 17. Tonal gradient depth */
function GradientDepthDemo() {
  return (
    <div className="lab-graddepth-grid">
      <div className="lab-graddepth-panel flat">
        <span className="kicker" style={{ color: "#8FB5A6" }}>Flat</span>
        <h4>Current forest band</h4>
      </div>
      <div className="lab-graddepth-panel grad">
        <span className="kicker" style={{ color: "#8FB5A6" }}>Gradient</span>
        <h4>Tonal depth variant</h4>
      </div>
    </div>
  );
}

/* 18. Count-up metrics */
const COUNTUP_STATS = [
  { label: "EBITDA Initiatives Sequenced", target: 14, suffix: "" },
  { label: "Time to First Ship", target: 6, suffix: " wks" },
  { label: "Portfolio Coverage", target: 82, suffix: "%" },
];
function CountUpMetrics() {
  const [vals, setVals] = useState(COUNTUP_STATS.map(() => 0));
  useEffect(() => {
    if (REDUCE_MOTION) { setVals(COUNTUP_STATS.map((s) => s.target)); return; }
    const duration = 1100;
    const start = performance.now();
    let raf;
    function frame(now) {
      const p = Math.min(1, (now - start) / duration);
      const eased = 1 - Math.pow(1 - p, 3);
      setVals(COUNTUP_STATS.map((s) => Math.round(s.target * eased)));
      if (p < 1) raf = requestAnimationFrame(frame);
    }
    raf = requestAnimationFrame(frame);
    return () => cancelAnimationFrame(raf);
  }, []);
  return (
    <div className="lab-countup">
      {COUNTUP_STATS.map((s, i) => (
        <div className="lab-countup-item" key={s.label}>
          <div className="lab-countup-num">{vals[i]}{s.suffix}</div>
          <div className="lab-countup-label kicker">{s.label}</div>
        </div>
      ))}
    </div>
  );
}

/* 19. Gradient-clip headline word */
function GradientHeadline() {
  return (
    <h3 className="lab-gradheadline display">
      We turn AI into <span className="lab-gradheadline-accent">underwritable value creation</span>.
    </h3>
  );
}

/* 20. Live signal path in the Motif */
function SignalPathMotif() {
  return (
    <svg viewBox="0 0 300 160" className="lab-signalmotif">
      <line x1="30" y1="30" x2="30" y2="130" className="sm-spine" />
      <line x1="30" y1="40" x2="260" y2="40" className="sm-branch" />
      <line x1="30" y1="85" x2="260" y2="85" className="sm-branch" />
      <line x1="30" y1="130" x2="260" y2="130" className="sm-branch" />
      <circle cx="30" cy="40" r="5" className="sm-node on" />
      <circle cx="30" cy="85" r="5" className="sm-node" />
      <circle cx="30" cy="130" r="5" className="sm-node" />
      {!REDUCE_MOTION && (
        <circle r="4" className="sm-runner">
          <animateMotion dur="3.2s" repeatCount="indefinite" path="M30,40 L260,40" />
        </circle>
      )}
      <text x="44" y="36">Underwrite</text>
      <text x="44" y="81">Plan</text>
      <text x="44" y="126">Deploy</text>
    </svg>
  );
}

/* 21. Magnetic / glow button */
function MagneticButton() {
  const ref = useRef(null);
  const [t, setT] = useState({ x: 0, y: 0 });
  function onMove(e) {
    const rect = ref.current.getBoundingClientRect();
    const x = e.clientX - (rect.left + rect.width / 2);
    const y = e.clientY - (rect.top + rect.height / 2);
    setT({ x: x * 0.25, y: y * 0.25 });
  }
  return (
    <div className="lab-magnetic-wrap">
      <button ref={ref} className="btn btn-primary lab-magnetic-btn"
        style={{ transform: `translate(${t.x}px, ${t.y}px)` }}
        onMouseMove={onMove} onMouseLeave={() => setT({ x: 0, y: 0 })}>
        Talk to us <Arrow />
      </button>
    </div>
  );
}

/* 22. Bento grid offers */
const BENTO_TILES = [
  { title: "AI Diligence", span: "wide", body: "Pre-deal opportunity and risk, sized for the IC." },
  { title: "Value Creation Plan", span: "tall", body: "Initiatives sequenced to the 100-day plan." },
  { title: "Embedded Build Pods", span: "", body: "Forward-deployed engineers inside the portfolio company." },
  { title: "Board AI Readiness", span: "", body: "What's real, what's hype, framed in EBITDA terms." },
];
function BentoGridDemo() {
  return (
    <div className="lab-bento">
      {BENTO_TILES.map((t) => (
        <div className={"lab-bento-tile " + t.span} key={t.title}>
          <h4>{t.title}</h4>
          <p>{t.body}</p>
        </div>
      ))}
    </div>
  );
}

/* 23. Cursor-reactive gradient field */
function CursorGlowHero() {
  const ref = useRef(null);
  const [pos, setPos] = useState({ x: 50, y: 30 });
  function onMove(e) {
    const rect = ref.current.getBoundingClientRect();
    setPos({ x: ((e.clientX - rect.left) / rect.width) * 100, y: ((e.clientY - rect.top) / rect.height) * 100 });
  }
  return (
    <div className="lab-cursorglow" ref={ref} onMouseMove={onMove}
      style={{ backgroundImage: `radial-gradient(480px circle at ${pos.x}% ${pos.y}%, rgba(143,181,166,.35), transparent 60%)` }}>
      <span className="eyebrow" style={{ color: "#8FB5A6" }}>How we work</span>
      <h3>Move your cursor across this panel.</h3>
    </div>
  );
}

/* 24. Scroll-scrubbed horizontal rail */
const RAIL_STAGES = [
  { k: "01", label: "Diligence", body: "Size AI-driven opportunity and risk before signing." },
  { k: "02", label: "Value Creation", body: "Sequence initiatives to the 100-day plan." },
  { k: "03", label: "Implementation", body: "Deploy engineers and ship inside the portfolio company." },
];
function HorizontalRail() {
  const containerRef = useRef(null);
  const [progress, setProgress] = useState(0);
  useEffect(() => {
    function onScroll() {
      const el = containerRef.current;
      if (!el) return;
      const rect = el.getBoundingClientRect();
      const total = rect.height - window.innerHeight;
      if (total <= 0) return;
      setProgress(Math.min(1, Math.max(0, -rect.top / total)));
    }
    window.addEventListener("scroll", onScroll, { passive: true });
    onScroll();
    return () => window.removeEventListener("scroll", onScroll);
  }, []);
  const shift = progress * (RAIL_STAGES.length - 1) * (100 / RAIL_STAGES.length);
  return (
    <div className="lab-hrail" ref={containerRef} style={{ height: "220vh" }}>
      <div className="lab-hrail-sticky">
        <div className="lab-hrail-track" style={{ transform: `translateX(-${shift}%)` }}>
          {RAIL_STAGES.map((s) => (
            <div className="lab-hrail-card" key={s.k}>
              <span className="kicker">{s.k}</span>
              <h4>{s.label}</h4>
              <p>{s.body}</p>
            </div>
          ))}
        </div>
        <p className="lab-pipe-caption kicker">Scroll to advance the rail</p>
      </div>
    </div>
  );
}

/* 25. Dark / lab mode toggle */
function LabModeToggle() {
  const [dark, setDark] = useState(false);
  return (
    <div className={"lab-modetoggle" + (dark ? " dark" : "")}>
      <div className="lab-modetoggle-grid" />
      <div className="lab-modetoggle-content">
        <span className="kicker">{dark ? "Lab Mode" : "Standard"}</span>
        <h3>Common Works</h3>
        <p>A technical operating firm for private equity.</p>
      </div>
      <button className="lab-modetoggle-btn" onClick={() => setDark((d) => !d)}>
        Switch to {dark ? "standard" : "lab mode"}
      </button>
    </div>
  );
}

/* ============================================================
   PANEL + PAGE ASSEMBLY
   ============================================================ */
const LAB_GROUPS = [
  { id: "finance", label: "Finance-native" },
  { id: "systems", label: "Systems & Engineering" },
  { id: "interaction", label: "Interactive Metaphors" },
  { id: "motion", label: "Motion & Type" },
  { id: "ambient", label: "Ambient" },
  { id: "polish", label: "Incremental Polish" },
];

const LAB_FEATURES = [
  { n: 1, id: "waterfall", group: "finance", title: "Live EBITDA Waterfall", blurb: "Interactive value-creation bridge — hover a segment to see its contribution.", Comp: WaterfallViz },
  { n: 2, id: "roi", group: "finance", title: "ROI / Value-Creation Calculator", blurb: "Functional inputs recompute an illustrative exit-value chart live.", Comp: RoiCalculator },
  { n: 3, id: "terminal", group: "finance", title: "Underwriting Model Terminal", blurb: "A ticking, live-feeling model readout in a terminal chrome.", Comp: ModelTerminal },
  { n: 4, id: "capgraph", group: "systems", title: "Capability Graph", blurb: "Force-graph-style diagram of firm capability flowing into portfolio companies.", Comp: CapabilityGraph },
  { n: 5, id: "archdiagram", group: "systems", title: "Self-Building Architecture Diagram", blurb: "A system diagram that draws itself in on reveal.", Comp: ArchitectureDiagram },
  { n: 6, id: "scrollpipe", group: "systems", title: "Scroll-Scrubbed Pipeline", blurb: "Scroll to advance a diligence → deployment sequence.", Comp: ScrollPipeline },
  { n: 7, id: "beforeafter", group: "interaction", title: "Before / After Operating Model", blurb: "Drag to reveal the automated state of a portfolio company's operations.", Comp: BeforeAfterSlider },
  { n: 8, id: "lens", group: "interaction", title: "Underwriting Lens", blurb: "Move your cursor to inspect model assumptions in detail.", Comp: UnderwritingLens },
  { n: 9, id: "coverage", group: "interaction", title: "Coverage Network Map", blurb: "Abstract map of sector coverage radiating from a central hub.", Comp: CoverageMap },
  { n: 10, id: "decode", group: "motion", title: "Kinetic Headline Decode", blurb: "Headline resolves from scrambled characters on load.", Comp: DecodeHeadline },
  { n: 11, id: "sparklines", group: "motion", title: "Living Case-Study Cards", blurb: "Hover a card to draw in its value-creation sparkline.", Comp: SparklineCards },
  { n: 12, id: "stack3d", group: "motion", title: "3D Parallax Deliverables Stack", blurb: "Cursor-tilted stack of thesis → blueprint → deployed layers.", Comp: DeliverablesStack },
  { n: 13, id: "ambientflow", group: "ambient", title: "Ambient Flow Field", blurb: "Slow, shader-like breathing backdrop in forest tones.", Comp: AmbientFlowField },
  { n: 14, id: "particles", group: "ambient", title: "Signal-Field Particles", blurb: "A drifting constellation of connected nodes.", Comp: ParticleFieldViz },
  { n: 15, id: "seismograph", group: "ambient", title: "Market Signal Seismograph", blurb: "A continuously scrolling market-signal line.", Comp: SeismographStrip },
  { n: 16, id: "gridwash", group: "polish", title: "Blueprint Grid Wash", blurb: "Faint graph-paper grid, masked, behind a forest band.", Comp: GridWashDemo },
  { n: 17, id: "gradientdepth", group: "polish", title: "Tonal Gradient Depth", blurb: "Flat forest fill vs. a radial tonal gradient, side by side.", Comp: GradientDepthDemo },
  { n: 18, id: "countup", group: "polish", title: "Count-Up Metrics", blurb: "Stat numbers animate up on reveal instead of appearing static.", Comp: CountUpMetrics },
  { n: 19, id: "gradientheadline", group: "polish", title: "Gradient-Clip Headline Word", blurb: "A single accent phrase rendered with a tonal gradient clip.", Comp: GradientHeadline },
  { n: 20, id: "signalpath", group: "polish", title: "Live Signal Path in the Motif", blurb: "The thesis → plan → deploy diagram with a traveling signal.", Comp: SignalPathMotif },
  { n: 21, id: "magneticbtn", group: "polish", title: "Magnetic / Glow Button", blurb: "A button that leans toward the cursor and glows on hover.", Comp: MagneticButton },
  { n: 22, id: "bentogrid", group: "polish", title: "Bento Grid Offers", blurb: "Asymmetric tile grid in place of the uniform 3-up cards.", Comp: BentoGridDemo },
  { n: 23, id: "cursorglow", group: "polish", title: "Cursor-Reactive Gradient Field", blurb: "A soft radial glow that follows the cursor across a forest panel.", Comp: CursorGlowHero },
  { n: 24, id: "horizontalrail", group: "polish", title: "Scroll-Scrubbed Horizontal Rail", blurb: "Scroll to slide through the arc as a horizontal rail instead of a grid.", Comp: HorizontalRail },
  { n: 25, id: "labmode", group: "polish", title: "Dark / Lab Mode Toggle", blurb: "Flip a panel between the standard look and a dark 'lab mode'.", Comp: LabModeToggle },
  { n: 26, id: "bindecode", group: "motion", title: "Binary Decode Headline", blurb: "Left-aligned headline resolves from 0/1s; hovering re-scrambles the characters under the cursor back into binary, with locked spacing.", Comp: BinaryDecodeHeadline },
];

const LAB_STORAGE_KEY = "cw-lab-toggles";
function loadToggles() {
  try { const raw = localStorage.getItem(LAB_STORAGE_KEY); if (raw) return JSON.parse(raw); } catch (e) {}
  return {};
}

function LabTogglePanel({ toggles, setToggles, open, setOpen }) {
  function toggle(id) { setToggles((prev) => ({ ...prev, [id]: !prev[id] })); }
  function setAll(v) { setToggles(Object.fromEntries(LAB_FEATURES.map((f) => [f.id, v]))); }
  const onCount = LAB_FEATURES.filter((f) => toggles[f.id]).length;
  return (
    <div className={"lab-panel" + (open ? " open" : "")}>
      <button className="lab-panel-tab" onClick={() => setOpen((o) => !o)}>{open ? "Close" : "Lab"}</button>
      <div className="lab-panel-in">
        <div className="lab-panel-head">
          <span className="kicker">Feature Lab — {onCount}/{LAB_FEATURES.length} on</span>
          <div className="lab-panel-allbtns">
            <button onClick={() => setAll(true)}>All on</button>
            <button onClick={() => setAll(false)}>All off</button>
          </div>
        </div>
        {LAB_GROUPS.map((g) => (
          <div className="lab-panel-group" key={g.id}>
            <div className="lab-panel-group-label">{g.label}</div>
            {LAB_FEATURES.filter((f) => f.group === g.id).map((f) => (
              <label className="lab-panel-item" key={f.id}>
                <input type="checkbox" checked={!!toggles[f.id]} onChange={() => toggle(f.id)} />
                <span className="lab-panel-item-n">{f.n}</span>
                <span>{f.title}</span>
              </label>
            ))}
          </div>
        ))}
      </div>
    </div>
  );
}

function LabSection({ feature, on }) {
  const { n, title, blurb, Comp, group } = feature;
  return (
    <section className="lab-section" id={"lab-" + feature.id}>
      <div className="wrap-wide">
        <div className="lab-section-head">
          <span className="tag">{LAB_GROUPS.find((g) => g.id === group).label}</span>
          <h3><span className="lab-section-n">{n}.</span> {title}</h3>
          <p className="muted">{blurb}</p>
        </div>
        {on
          ? <div className="lab-stage on"><Comp /></div>
          : <div className="lab-stage off"><span className="kicker">Toggled off — enable in the panel to preview</span></div>}
      </div>
    </section>
  );
}

function Lab() {
  const [toggles, setToggles] = useState(loadToggles);
  const [panelOpen, setPanelOpen] = useState(true);
  useEffect(() => {
    try { localStorage.setItem(LAB_STORAGE_KEY, JSON.stringify(toggles)); } catch (e) {}
  }, [toggles]);
  return (
    <div className="lab" data-screen-label="Feature Lab">
      <div className="lab-topbar">
        <a className="brand" href="/" onClick={(e) => { e.preventDefault(); window.__go("home"); }}>
          <BrandMark /><span className="wordmark">Common <b>Works</b></span>
        </a>
        <a className="lab-exit tlink" href="/" onClick={(e) => { e.preventDefault(); window.__go("home"); }}>Exit lab <Arrow /></a>
      </div>
      <div className="lab-intro wrap-wide">
        <div className="eyebrow">Internal preview</div>
        <h1 className="display">Feature Lab</h1>
        <p className="lead">Twenty-six candidate visual and interactive directions, each independently toggleable. Nothing here ships until it's chosen.</p>
        <div className="lab-concepts">
          <span className="kicker lab-concepts-label">Full concept homepages — bigger swings, each a complete alternate direction</span>
          <div className="lab-concepts-links">
            {[
              { path: "/lab/manifesto", id: "concept-manifesto", n: "01", label: "Manifesto", blurb: "The partner letter, engraved: one thesis staged across full-bleed movements, set like a financial instrument." },
              { path: "/lab/systems", id: "concept-systems", n: "02", label: "Systems", blurb: "The evidence file: IC-grade documents, an engagement protocol, and the architecture we install." },
              { path: "/lab/os", id: "concept-os", n: "03", label: "Operating System", blurb: "The engagement console: an interactive value bridge, a week-gridded plan, and a delivery ledger." },
            ].map((c) => (
              <a key={c.id} className="lab-concept-card" href={c.path} onClick={(e) => { e.preventDefault(); window.__go(c.id); }}>
                <span className="lab-concept-n kicker">{c.n}</span>
                <h3>{c.label}</h3>
                <p>{c.blurb}</p>
                <span className="tlink">View <Arrow /></span>
              </a>
            ))}
          </div>
        </div>
      </div>
      {LAB_FEATURES.map((f) => <LabSection key={f.id} feature={f} on={!!toggles[f.id]} />)}
      <LabTogglePanel toggles={toggles} setToggles={setToggles} open={panelOpen} setOpen={setPanelOpen} />
    </div>
  );
}

Object.assign(window, { Lab });
