/* paideia.jsx — Paideia case study page */

const { useState: useStatePA, useEffect: useEffectPA, useMemo: useMemoPA, useRef: useRefPA } = React;

/* ------------------------------------------------------------------
   PaTierMark — B&W pattern square that replaces the emoji tier system
   (🔥🔥/🔥/🟡/⚪). Pattern, never colour, in keeping with the
   Lombardi aesthetic used elsewhere on the site.

   tier 0 — no HW                  → dashed outline, empty
   tier 1 — 1 HW occurrence        → open square (stroke only)
   tier 2 — 2 HW occurrences       → diagonal hatch fill
   tier 3 — 3+ HW occurrences      → solid fill
------------------------------------------------------------------ */
function PaTierMark({ tier, size = 14 }) {
  const stroke = "var(--fg)";
  const sw = 1.1;
  const r = size / 2 - 1;
  const id = `pa-hatch-${size}`;
  return (
    <svg width={size} height={size} viewBox={`${-size/2} ${-size/2} ${size} ${size}`} style={{ display: "inline-block", verticalAlign: "middle", flexShrink: 0 }}>
      <defs>
        <pattern id={id} patternUnits="userSpaceOnUse" width="3" height="3" patternTransform="rotate(45)">
          <line x1="0" y1="0" x2="0" y2="3" stroke={stroke} strokeWidth="0.9" />
        </pattern>
      </defs>
      {tier === 0 && (
        <rect x={-r} y={-r} width={r*2} height={r*2} fill="var(--bg)" stroke={stroke} strokeWidth={sw} strokeDasharray="2 2" />
      )}
      {tier === 1 && (
        <rect x={-r} y={-r} width={r*2} height={r*2} fill="var(--bg)" stroke={stroke} strokeWidth={sw} />
      )}
      {tier === 2 && (
        <g>
          <rect x={-r} y={-r} width={r*2} height={r*2} fill={`url(#${id})`} stroke={stroke} strokeWidth={sw} />
        </g>
      )}
      {tier >= 3 && (
        <rect x={-r} y={-r} width={r*2} height={r*2} fill={stroke} stroke={stroke} strokeWidth={sw} />
      )}
    </svg>
  );
}

const PA_TIER_GLOSS = [
  { label: "no HW",   exam: "low",       desc: "Not seen in homework. Likely background or foundation only." },
  { label: "1 HW",    exam: "moderate",  desc: "Touched once. Could appear; not central." },
  { label: "2 HW",    exam: "high",      desc: "Repeated emphasis. Strong candidate for an exam item." },
  { label: "3+ HW",   exam: "very high", desc: "Heavy density. Treat as guaranteed exam material." },
];

/* ------------------------------------------------------------------
   Demo A — Formation cycle (headline)
   Six stages around a hex; click to inspect the artifact each stage
   writes to disk. "Advance →" walks through them sequentially and
   ticks the exam D-counter down.
------------------------------------------------------------------ */
const PA_STAGES = [
  {
    id: "ingest",
    cmd: "/paideia:ingest",
    title: "Ingest",
    blurb: "Pull every PDF, slide deck, and lecture transcript in materials/, normalize to markdown, drop into converted/.",
    out: "converted/**/*.md",
    artifact:
`# converted/lec03-spinwaves.md

## §1 Magnon dispersion
The spin-wave dispersion in a 1D ferromagnet is
ω(k) = 2zJS(1 − cos ka).
> Lecturer emphasized: "this is the only form
> we will exploit on the exam."

## §2 Holstein–Primakoff
S⁺ = √(2S − a†a) · a
S⁻ = a† · √(2S − a†a)
S_z = S − a†a

## §3 Worked example (HW#2 P3)
For a square lattice (z=4, J=1, S=½):
ω(k) = 4(1 − ½(cos kx + cos ky))   ← derive in HW#2`,
  },
  {
    id: "analyze",
    cmd: "/paideia:analyze",
    title: "Analyze",
    blurb: "Read every converted file. Cluster topics. Cross-reference HW frequency. Score each topic for its likely exam weight.",
    out: "course-index/{summary,patterns,coverage}.md",
    artifact:
`# course-index/patterns.md

## High-density topics (3+ HW)
- Holstein–Primakoff bosonization        [HW#1, HW#2, HW#5]
- Linear spin-wave theory                [HW#2, HW#3, HW#5]
- Ferromagnet vs antiferromagnet gap     [HW#1, HW#3, HW#5, HW#6]

## Repeated emphasis (2 HW)
- Magnon–magnon scattering vertices      [HW#3, HW#4]
- Bogoliubov rotation for AFM            [HW#4, HW#6]

## Touched once (1 HW)
- Skyrmion winding number                [HW#4]

## Foundation only (no HW)
- Heisenberg model derivation
- SU(2) algebra recap`,
  },
  {
    id: "drill",
    cmd: "/paideia:drill",
    title: "Drill",
    blurb: "Generate practice quizzes from the high-density topics. Mix HW twins (same form) and chains (build on each other).",
    out: "quizzes/*.md",
    artifact:
`# quizzes/q07-hp-bosonization.md

## Q1 — twin of HW#2 P3
Square lattice (z=4, J=2, S=1).
Compute ω(k) using Holstein–Primakoff to
linear order. Expand around k = (0,0).

## Q2 — chain
Using the result from Q1, find the magnon
dispersion if a uniaxial anisotropy KS_z²
is added. State the gap.

## Q3 — derivation
Show that, for a ferromagnet, the linear
spin-wave Hamiltonian diagonalizes by Fourier
transform alone. Why does the AFM not?`,
  },
  {
    id: "grade",
    cmd: "/paideia:grade",
    title: "Grade",
    blurb: "Compare student answers against the source. Log every error with its topic, type, and a one-line diagnosis.",
    out: "errors/log.md",
    artifact:
`# errors/log.md

| ts                | qid       | topic                   | type        | dx                              |
|-------------------|-----------|-------------------------|-------------|---------------------------------|
| 2026-04-21 14:02  | q07.q1    | HP bosonization         | algebra     | dropped √(2S) prefactor         |
| 2026-04-21 14:18  | q07.q2    | anisotropy gap          | concept     | gap-opens-at-k=0 not stated     |
| 2026-04-22 09:41  | q08.q3    | AFM Bogoliubov          | sign        | u² − v² = 1 written as u + v=1  |
| 2026-04-23 22:10  | q09.q1    | magnon scattering       | bookkeeping | missed (1/N) factor in vertex   |`,
  },
  {
    id: "weakmap",
    cmd: "/paideia:weakmap",
    title: "Weakmap",
    blurb: "Aggregate the error log against the topic graph. Surface the seams — where errors cluster, where review must concentrate.",
    out: "weakmap/weakmap_<ts>.md",
    artifact:
`# weakmap/weakmap_2026-04-23.md

## Severity ranking
1. AFM Bogoliubov rotation     ████████░░  8 errors / 12 attempts
2. Magnon vertex bookkeeping   █████░░░░░  5 / 9
3. HP √(2S) prefactor          ███░░░░░░░  3 / 11
4. Anisotropy gap statement    ██░░░░░░░░  2 / 6

## Recommended drill order
1. AFM Bogoliubov  (highest density × highest error rate)
2. Magnon vertices
3. HP prefactor (mechanical; one careful pass)`,
  },
  {
    id: "cheatsheet",
    cmd: "/paideia:cheatsheet",
    title: "Cheatsheet",
    blurb: "Compress the weakmap and the high-density topics into a single page. The page tightens with each cycle.",
    out: "cheatsheet/final.md",
    artifact:
`# cheatsheet/final.md       (rev. 04, D-3)

## Forms to know cold
ω_FM(k)  = 2zJS(1 − cos ka)
ω_AFM(k) = 2zJS · √(1 − γ_k²),   γ_k = (1/z)Σ cos k·δ
HP:        S⁺ = √(2S−a†a)·a,   S_z = S − a†a

## Bogoliubov (AFM)
α_k = u_k a_k − v_k b†_{-k}
u² − v² = 1     ← LIVE TRIPWIRE (HW#4, HW#6, weakmap #1)

## Vertices (3-magnon, 4-magnon)
Always carry (1/N) per internal sum. ← weakmap #2

## Anisotropy
KS_z²  ⇒  gap = 2KS at k=0`,
  },
];

function PaideiaCycle() {
  useLang();
  const [active, setActive] = useStatePA("ingest");
  const [advancedThrough, setAdvancedThrough] = useStatePA(0); // 0..6
  const [dCount, setDCount] = useStatePA(12);

  const stage = PA_STAGES.find(s => s.id === active);
  const stageIndex = PA_STAGES.findIndex(s => s.id === active);

  const advance = () => {
    const next = Math.min(advancedThrough + 1, PA_STAGES.length);
    setAdvancedThrough(next);
    setActive(PA_STAGES[Math.min(next - 1, PA_STAGES.length - 1)].id);
    if (next > advancedThrough && dCount > 0) setDCount(dCount - 2);
  };

  const reset = () => {
    setAdvancedThrough(0);
    setDCount(12);
    setActive("ingest");
  };

  // hexagonal layout — 6 stages around a center
  const W = 760, H = 460;
  const R = Math.min(W, H) * 0.36;
  const stagePos = (i) => {
    const a = (-Math.PI / 2) + (i / 6) * Math.PI * 2;
    return { x: Math.cos(a) * R, y: Math.sin(a) * R };
  };

  return (
    <div style={{ border: "1px solid var(--line)", padding: 28, marginBottom: 100, background: "var(--bg)" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 20, flexWrap: "wrap", gap: 12 }}>
        <div>
          <div className="micro" style={{ marginBottom: 6 }}>{t("Fig. 01 · Formation cycle")}</div>
          <div className="display-md" style={{ fontSize: 28 }}>{t("Six stages, six artifacts, one descending counter.")}</div>
        </div>
        <div className="micro" style={{ color: "var(--fg-muted)" }}>{t("ingest · analyze · drill · grade · weakmap · cheatsheet")}</div>
      </div>

      <div style={{
        display: "grid",
        gridTemplateColumns: "minmax(0, 1.2fr) minmax(0, 1fr)",
        gap: 18,
        marginBottom: 18,
        alignItems: "start",
      }} className="pa-cycle-grid">

        {/* Hex graph — alignSelf:start + aspectRatio so it never stretches
            vertically (which would blow up the width past the column). */}
        <div style={{ position: "relative", aspectRatio: `${W} / ${H}`, border: "1px solid var(--line-soft)", background: "var(--bg)", minWidth: 0, width: "100%", alignSelf: "start", overflow: "hidden" }}>
          <svg viewBox={`${-W/2} ${-H/2} ${W} ${H}`} style={{ width: "100%", height: "100%", display: "block" }}>
            {/* center */}
            <circle r={36} fill="var(--bg)" stroke="var(--fg)" strokeWidth={1.2} />
            <text textAnchor="middle" y={-4} fontFamily="var(--font-mono)" fontSize={9} letterSpacing="0.18em" fill="var(--fg-muted)" style={{ textTransform: "uppercase" }}>
              {t("course")}
            </text>
            <text textAnchor="middle" y={12} fontFamily="var(--font-display)" fontSize={16} fontStyle="italic" fill="var(--fg)">
              D−{dCount}
            </text>

            {/* edges from center to stages */}
            {PA_STAGES.map((s, i) => {
              const p = stagePos(i);
              const reached = i < advancedThrough;
              const isActive = s.id === active;
              return (
                <line key={`e${i}`} x1={0} y1={0} x2={p.x} y2={p.y}
                      stroke="var(--fg)"
                      strokeWidth={isActive ? 1.3 : 0.7}
                      strokeDasharray={reached ? "" : "2 3"}
                      opacity={isActive ? 0.9 : 0.45} />
              );
            })}

            {/* edges between consecutive stages — the cycle */}
            {PA_STAGES.map((s, i) => {
              const p1 = stagePos(i);
              const p2 = stagePos((i + 1) % 6);
              const reached = i + 1 < advancedThrough;
              return (
                <line key={`r${i}`} x1={p1.x} y1={p1.y} x2={p2.x} y2={p2.y}
                      stroke="var(--fg)"
                      strokeWidth={0.8}
                      strokeDasharray={reached ? "" : "1 4"}
                      opacity={reached ? 0.5 : 0.2} />
              );
            })}

            {/* stage nodes */}
            {PA_STAGES.map((s, i) => {
              const p = stagePos(i);
              const isActive = s.id === active;
              const reached = i < advancedThrough;
              return (
                <g key={s.id} transform={`translate(${p.x} ${p.y})`}
                   style={{ cursor: "pointer" }}
                   onClick={() => setActive(s.id)}>
                  <circle r={28} fill="transparent" />
                  <rect x={-22} y={-22} width={44} height={44}
                        fill={reached ? "var(--fg)" : "var(--bg)"}
                        stroke="var(--fg)" strokeWidth={isActive ? 1.6 : 1} />
                  <text textAnchor="middle" y={-2}
                        fontFamily="var(--font-mono)" fontSize={9} letterSpacing="0.16em"
                        fill={reached ? "var(--bg)" : "var(--fg)"}
                        style={{ textTransform: "uppercase" }}>
                    {String(i + 1).padStart(2, "0")}
                  </text>
                  <text textAnchor="middle" y={11}
                        fontFamily="var(--font-display)" fontSize={11} fontStyle="italic"
                        fill={reached ? "var(--bg)" : "var(--fg)"}>
                    {t(s.title.toLowerCase())}
                  </text>
                  {isActive && (
                    <rect x={-26} y={-26} width={52} height={52}
                          fill="none" stroke="var(--fg)" strokeWidth={0.6} strokeDasharray="1 2" />
                  )}
                </g>
              );
            })}
          </svg>
        </div>

        {/* Inspector panel — minWidth: 0 so wide <pre> lines can scroll
            inside instead of forcing the grid column to stretch. */}
        <div style={{
          border: "1px solid var(--line)",
          padding: 18,
          display: "flex",
          flexDirection: "column",
          minHeight: 0,
          minWidth: 0,
          overflow: "hidden",
          background: "var(--bg)",
        }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 8 }}>
            <div className="micro">
              <span style={{ color: "var(--fg-muted)" }}>{String(stageIndex + 1).padStart(2, "0")} · </span>
              <span>{t("Stage")}</span>
            </div>
            <div className="micro" style={{ color: "var(--fg-muted)" }}>{stage.cmd}</div>
          </div>
          <div style={{ fontFamily: "var(--font-display)", fontStyle: "italic", fontSize: 24, marginBottom: 10 }}>
            {t(stage.title)}
          </div>
          <div className="body-sm" style={{ color: "var(--fg-muted)", lineHeight: 1.55, marginBottom: 12 }}>
            {t(stage.blurb)}
          </div>
          <div className="micro" style={{ color: "var(--fg-muted)", marginBottom: 6 }}>{t("Writes to")}</div>
          <code style={{ fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--fg)", background: "var(--line-soft)", padding: "4px 8px", display: "inline-block", marginBottom: 14, alignSelf: "flex-start" }}>
            {stage.out}
          </code>

          <div className="micro" style={{ color: "var(--fg-muted)", marginBottom: 6 }}>{t("Artifact preview")}</div>
          <pre style={{
            fontFamily: "var(--font-mono)",
            fontSize: 10.5,
            lineHeight: 1.5,
            background: "var(--bg)",
            border: "1px solid var(--line-soft)",
            padding: 12,
            margin: 0,
            overflow: "auto",
            flex: 1,
            maxHeight: 260,
            maxWidth: "100%",
            minWidth: 0,
            width: "100%",
            color: "var(--fg)",
            whiteSpace: "pre",
            boxSizing: "border-box",
          }}>
            {stage.artifact}
          </pre>
        </div>
      </div>

      {/* controls */}
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 8 }}>
        <div className="micro" style={{ color: "var(--fg-muted)" }}>
          {advancedThrough === 0 && t("Idle. Press Advance to walk through one cycle. Each step writes a real artifact and ticks the exam counter.")}
          {advancedThrough > 0 && advancedThrough < PA_STAGES.length && t("Cycle in progress · {n} / {total} stages complete · D−{d} until exam").replace("{n}", advancedThrough).replace("{total}", PA_STAGES.length).replace("{d}", dCount)}
          {advancedThrough >= PA_STAGES.length && t("Cycle complete. Cheatsheet committed. Ready for the next pass — or the exam.")}
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          {advancedThrough > 0 && (
            <button type="button" onClick={reset} data-cursor="link" data-cursor-label={t("Reset")} className="micro"
                    style={{ padding: "10px 16px", border: "1px solid var(--line)", background: "var(--bg)", color: "var(--fg)", cursor: "pointer" }}>
              {t("Reset")}
            </button>
          )}
          <button type="button" onClick={advance} disabled={advancedThrough >= PA_STAGES.length}
                  data-cursor="link" data-cursor-label={t("Advance")} className="micro"
                  style={{ padding: "10px 18px", border: "1px solid var(--fg)", background: "var(--fg)", color: "var(--bg)", cursor: advancedThrough >= PA_STAGES.length ? "default" : "pointer", opacity: advancedThrough >= PA_STAGES.length ? 0.4 : 1 }}>
            {advancedThrough >= PA_STAGES.length ? t("Cycle done") : t("Advance →")}
          </button>
        </div>
      </div>

      <style>{`
        @media (max-width: 880px) { .pa-cycle-grid { grid-template-columns: 1fr !important; } }
      `}</style>
    </div>
  );
}

/* ------------------------------------------------------------------
   Demo B — HW-density heatmap
   8 × 6 grid of topics from a synthetic graduate solid-state syllabus.
   Pattern markers replace the emoji tier system. Sort by chapter or
   by exam-tier.
------------------------------------------------------------------ */
const PA_TOPICS = [
  { ch: 1, title: "Reciprocal lattice",        hw: 2 },
  { ch: 1, title: "Brillouin zones",           hw: 3 },
  { ch: 2, title: "Structure factor",          hw: 3 },
  { ch: 2, title: "Debye–Waller",              hw: 0 },
  { ch: 3, title: "Linear chain dispersion",   hw: 2 },
  { ch: 3, title: "Density of states",         hw: 3 },
  { ch: 4, title: "Tight-binding bands",       hw: 4 },
  { ch: 4, title: "Wannier functions",         hw: 0 },
  { ch: 5, title: "Carrier statistics",        hw: 3 },
  { ch: 5, title: "p–n junction",              hw: 1 },
  { ch: 6, title: "Holstein–Primakoff",        hw: 3 },
  { ch: 6, title: "AFM Bogoliubov rotation",   hw: 4 },
  { ch: 7, title: "BCS gap equation",          hw: 2 },
  { ch: 7, title: "Cooper instability",        hw: 1 },
  { ch: 8, title: "Berry phase",               hw: 3 },
  { ch: 8, title: "Chern number",              hw: 2 },
  { ch: 9, title: "Boltzmann equation",        hw: 2 },
  { ch: 9, title: "Kubo formula",              hw: 0 },
  { ch:10, title: "Second quantization",       hw: 2 },
  { ch:10, title: "RPA",                       hw: 0 },
  { ch:11, title: "Quantum Hall plateaus",     hw: 2 },
  { ch:11, title: "DMRG / numerics",           hw: 0 },
  { ch:12, title: "Magnonics",                 hw: 2 },
  { ch:12, title: "MR sensors",                hw: 0 },
];

const tierOf = (hw) => (hw >= 3 ? 3 : hw === 2 ? 2 : hw === 1 ? 1 : 0);

function PaideiaHeatmap() {
  useLang();
  const [sort, setSort] = useStatePA("chapter"); // chapter | tier
  const [selected, setSelected] = useStatePA(null); // index into sorted, or null
  const [popupPos, setPopupPos] = useStatePA(null); // {left, top, w} relative to grid wrapper
  const isOpen = selected !== null;

  const gridRef = useRefPA(null);
  const cellRefs = useRefPA([]);

  const sorted = useMemoPA(() => {
    const arr = [...PA_TOPICS];
    if (sort === "tier") arr.sort((a, b) => b.hw - a.hw || a.ch - b.ch);
    else arr.sort((a, b) => a.ch - b.ch);
    return arr;
  }, [sort]);

  // close on Escape; reset selection when sort changes (indices shift)
  useEffectPA(() => { setSelected(null); }, [sort]);
  useEffectPA(() => {
    if (!isOpen) return;
    const onKey = (e) => { if (e.key === "Escape") setSelected(null); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [isOpen]);

  // Position the popup beside the clicked cell, inside the grid wrapper.
  // Re-measure on selection change and on window resize.
  useEffectPA(() => {
    if (selected === null) return;
    const measure = () => {
      const grid = gridRef.current;
      const cell = cellRefs.current[selected];
      if (!grid || !cell) return;
      const gridR = grid.getBoundingClientRect();
      const cellR = cell.getBoundingClientRect();
      const popupW = Math.min(300, gridR.width - 24);
      const popupH = 220; // estimate for clamping
      const gap = 12;
      const cellRight  = cellR.right  - gridR.left;
      const cellLeft   = cellR.left   - gridR.left;
      const cellTop    = cellR.top    - gridR.top;

      let left;
      if (cellRight + gap + popupW <= gridR.width) {
        left = cellRight + gap;
      } else if (cellLeft - gap - popupW >= 0) {
        left = cellLeft - gap - popupW;
      } else {
        left = Math.max(0, Math.min(gridR.width - popupW, cellLeft));
      }
      let top = cellTop;
      if (top + popupH > gridR.height) top = Math.max(0, gridR.height - popupH);

      setPopupPos({ left, top, w: popupW });
    };
    measure();
    window.addEventListener("resize", measure);
    return () => window.removeEventListener("resize", measure);
  }, [selected, sort]);

  const activeTopic = isOpen ? sorted[selected] : null;
  const activeTier  = activeTopic ? tierOf(activeTopic.hw) : 0;
  const activeGloss = activeTopic ? PA_TIER_GLOSS[activeTier] : null;

  return (
    <div style={{ border: "1px solid var(--line)", padding: 28, marginBottom: 100, background: "var(--bg)" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 20, flexWrap: "wrap", gap: 12 }}>
        <div>
          <div className="micro" style={{ marginBottom: 6 }}>{t("Fig. 02 · HW-density heatmap")}</div>
          <div className="display-md" style={{ fontSize: 28 }}>{t("Where the homework piled up — and where the exam will follow.")}</div>
        </div>
        <div style={{ display: "flex", gap: 8 }}>
          <button type="button" onClick={() => setSort("chapter")} data-cursor="link" data-cursor-label={t("Sort")}
                  className="micro"
                  style={{
                    padding: "8px 14px",
                    border: "1px solid var(--line)",
                    background: sort === "chapter" ? "var(--fg)" : "var(--bg)",
                    color: sort === "chapter" ? "var(--bg)" : "var(--fg)",
                    cursor: "pointer",
                  }}>
            {t("By chapter")}
          </button>
          <button type="button" onClick={() => setSort("tier")} data-cursor="link" data-cursor-label={t("Sort")}
                  className="micro"
                  style={{
                    padding: "8px 14px",
                    border: "1px solid var(--line)",
                    background: sort === "tier" ? "var(--fg)" : "var(--bg)",
                    color: sort === "tier" ? "var(--bg)" : "var(--fg)",
                    cursor: "pointer",
                  }}>
            {t("By exam-tier ↓")}
          </button>
        </div>
      </div>

      {/* legend */}
      <div style={{ display: "flex", gap: 24, marginBottom: 18, flexWrap: "wrap" }}>
        {PA_TIER_GLOSS.map((g, i) => (
          <div key={i} style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <PaTierMark tier={i} size={14} />
            <span className="micro">{t(g.label)}</span>
            <span className="micro" style={{ color: "var(--fg-muted)" }}>· {t("exam")} {t(g.exam)}</span>
          </div>
        ))}
      </div>

      {/* grid wrapper — holds grid, local dim layer, and popup. */}
      <div ref={gridRef} style={{ position: "relative" }}>
        <div style={{
          display: "grid",
          gridTemplateColumns: "repeat(6, 1fr)",
          gap: 6,
        }} className="pa-heat-grid">
          {sorted.map((tp, i) => {
            const tier = tierOf(tp.hw);
            const isActive = selected === i;
            return (
              <div key={i}
                   ref={(el) => { cellRefs.current[i] = el; }}
                   onClick={(e) => { e.stopPropagation(); setSelected(isActive ? null : i); }}
                   data-cursor="link" data-cursor-label={t("Inspect")}
                   style={{
                     border: `1px solid ${isActive ? "var(--fg)" : "var(--line)"}`,
                     padding: 12,
                     minHeight: 90,
                     display: "flex",
                     flexDirection: "column",
                     justifyContent: "space-between",
                     gap: 10,
                     cursor: "pointer",
                     background: "var(--bg)",
                     position: "relative",
                     zIndex: isActive ? 70 : "auto",
                     transform: isActive ? "translateY(-2px)" : "translateY(0)",
                     boxShadow: isActive ? "0 18px 36px -16px rgba(0,0,0,0.22)" : "none",
                     transition: "transform 280ms var(--easing-default), box-shadow 280ms var(--easing-default), border-color 200ms var(--easing-default), background 200ms var(--easing-default)",
                   }}
                   className="pa-heat-cell">
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 6 }}>
                  <span className="micro" style={{ color: "var(--fg-muted)" }}>ch{tp.ch}</span>
                  <PaTierMark tier={tier} size={12} />
                </div>
                <div style={{ fontFamily: "var(--font-display)", fontStyle: "italic", fontSize: 13, lineHeight: 1.25, color: "var(--fg)" }}>
                  {t(tp.title)}
                </div>
                <div className="micro" style={{ color: "var(--fg-muted)" }}>
                  {tp.hw} {tp.hw === 1 ? t("HW") : t("HW") + (tp.hw === 0 ? "" : "s")}
                </div>
              </div>
            );
          })}
        </div>

        {/* Local dim layer — sits over the grid only. Click closes. */}
        <div
          onClick={() => setSelected(null)}
          aria-hidden="true"
          style={{
            position: "absolute",
            inset: 0,
            background: "rgba(245, 244, 240, 0.78)",
            opacity: isOpen ? 1 : 0,
            pointerEvents: isOpen ? "auto" : "none",
            transition: "opacity 320ms var(--easing-default)",
            zIndex: 60,
          }} />

        {/* Side popup — anchored next to the clicked cell, inside the grid area. */}
        <div
          role="dialog"
          aria-hidden={!isOpen}
          onClick={(e) => e.stopPropagation()}
          style={{
            position: "absolute",
            left: popupPos ? popupPos.left : 0,
            top:  popupPos ? popupPos.top  : 0,
            width: popupPos ? popupPos.w   : 300,
            background: "var(--bg)",
            border: "1px solid var(--fg)",
            padding: 18,
            opacity: isOpen && popupPos ? 1 : 0,
            transform: `translateX(${isOpen ? 0 : 8}px)`,
            pointerEvents: isOpen ? "auto" : "none",
            transition: "opacity 280ms var(--easing-default), transform 280ms var(--easing-default), left 280ms var(--easing-default), top 280ms var(--easing-default)",
            boxShadow: "0 18px 40px -16px rgba(0,0,0,0.22)",
            zIndex: 80,
          }}
          className="pa-heat-popup">
          {activeTopic && (
            <>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 12 }}>
                <div className="micro" style={{ color: "var(--fg-muted)" }}>
                  ch{activeTopic.ch} · #{String(selected + 1).padStart(2, "0")}
                </div>
                <button type="button" onClick={() => setSelected(null)}
                        data-cursor="link" data-cursor-label={t("Close")}
                        style={{
                          border: "1px solid var(--line)",
                          background: "var(--bg)",
                          color: "var(--fg)",
                          width: 22, height: 22,
                          lineHeight: 1, fontSize: 13,
                          cursor: "pointer",
                          padding: 0,
                        }}>×</button>
              </div>
              <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 10 }}>
                <PaTierMark tier={activeTier} size={16} />
                <span className="micro">{t(activeGloss.label)}</span>
                <span className="micro" style={{ color: "var(--fg-muted)" }}>· {t("exam")} {t(activeGloss.exam)}</span>
              </div>
              <div style={{ fontFamily: "var(--font-display)", fontStyle: "italic", fontSize: 22, lineHeight: 1.2, marginBottom: 8 }}>
                {t(activeTopic.title)}
              </div>
              <div className="micro" style={{ color: "var(--fg-muted)", marginBottom: 12 }}>
                {activeTopic.hw} {activeTopic.hw === 1 ? t("HW") : t("HW") + (activeTopic.hw === 0 ? "" : "s")}
              </div>
              <div className="body-sm" style={{ color: "var(--fg)", lineHeight: 1.55, fontSize: 13 }}>
                {t(activeGloss.desc)}
              </div>
            </>
          )}
        </div>
      </div>

      {/* status line */}
      <div className="micro" style={{ marginTop: 18, color: "var(--fg-muted)" }}>
        {t("Click any cell to inspect. Click outside or press Esc to dismiss.")}
      </div>

      <div className="micro" style={{ marginTop: 16, color: "var(--fg-muted)", display: "flex", justifyContent: "space-between", flexWrap: "wrap", gap: 8 }}>
        <span>{t("24 topics across 12 chapters · pattern, never colour. Pattern density = HW count = exam probability.")}</span>
        <span>{t("Generated by /paideia:analyze")}</span>
      </div>

      <style>{`
        @media (max-width: 1000px) { .pa-heat-grid { grid-template-columns: repeat(4, 1fr) !important; } }
        @media (max-width: 600px)  { .pa-heat-grid { grid-template-columns: repeat(2, 1fr) !important; } }
      `}</style>
    </div>
  );
}

/* ------------------------------------------------------------------
   Demo C — Terminal walkthrough
   File tree (left) + terminal (center) + markdown preview (right).
   Preset command buttons replay a recorded session.
------------------------------------------------------------------ */
const PA_TREE = [
  { path: "course/", depth: 0, kind: "dir" },
  { path: "  .course-meta",         depth: 1, kind: "file" },
  { path: "  CLAUDE.md",            depth: 1, kind: "file" },
  { path: "  materials/",           depth: 1, kind: "dir" },
  { path: "    lec01.pdf",          depth: 2, kind: "file" },
  { path: "    lec02.pdf",          depth: 2, kind: "file" },
  { path: "    hw1.pdf",            depth: 2, kind: "file" },
  { path: "  converted/",           depth: 1, kind: "dir", writeStage: 0 },
  { path: "    lec01.md",           depth: 2, kind: "file", writeStage: 0 },
  { path: "    lec02.md",           depth: 2, kind: "file", writeStage: 0 },
  { path: "    hw1.md",             depth: 2, kind: "file", writeStage: 0 },
  { path: "  course-index/",        depth: 1, kind: "dir", writeStage: 1 },
  { path: "    summary.md",         depth: 2, kind: "file", writeStage: 1 },
  { path: "    patterns.md",        depth: 2, kind: "file", writeStage: 1, openStage: 1 },
  { path: "    coverage.md",        depth: 2, kind: "file", writeStage: 1 },
  { path: "  quizzes/",             depth: 1, kind: "dir", writeStage: 2 },
  { path: "    q07-hp.md",          depth: 2, kind: "file", writeStage: 2, openStage: 2 },
  { path: "  errors/",              depth: 1, kind: "dir", writeStage: 3 },
  { path: "    log.md",             depth: 2, kind: "file", writeStage: 3 },
  { path: "  weakmap/",             depth: 1, kind: "dir", writeStage: 4 },
  { path: "    weakmap_2026-04-23.md", depth: 2, kind: "file", writeStage: 4, openStage: 4 },
  { path: "  cheatsheet/",          depth: 1, kind: "dir", writeStage: 5 },
  { path: "    final.md",           depth: 2, kind: "file", writeStage: 5, openStage: 5 },
];

const PA_TERMINAL = [
  { stage: -1, lines: [
    { kind: "prompt", text: "$ cd ~/courses/solid-state-2026" },
    { kind: "out",    text: "(course/ — 3 PDFs in materials/, nothing else)" },
  ]},
  { stage: 0, lines: [
    { kind: "prompt", text: "$ /paideia:ingest" },
    { kind: "out",    text: "→ scanning materials/ (3 files)" },
    { kind: "out",    text: "→ converting lec01.pdf → converted/lec01.md" },
    { kind: "out",    text: "→ converting lec02.pdf → converted/lec02.md" },
    { kind: "out",    text: "→ converting hw1.pdf  → converted/hw1.md" },
    { kind: "ok",     text: "✓ ingest complete · 3 files written" },
  ]},
  { stage: 1, lines: [
    { kind: "prompt", text: "$ /paideia:analyze" },
    { kind: "out",    text: "→ clustering topics across converted/" },
    { kind: "out",    text: "→ scoring HW frequency" },
    { kind: "out",    text: "→ writing course-index/{summary,patterns,coverage}.md" },
    { kind: "ok",     text: "✓ analyze complete · 48 topics indexed, 8 high-density" },
  ]},
  { stage: 2, lines: [
    { kind: "prompt", text: "$ /paideia:drill --topic hp-bosonization" },
    { kind: "out",    text: "→ generating twin of HW#2 P3" },
    { kind: "out",    text: "→ generating chain (anisotropy gap)" },
    { kind: "out",    text: "→ writing quizzes/q07-hp.md" },
    { kind: "ok",     text: "✓ drill complete" },
  ]},
  { stage: 3, lines: [
    { kind: "prompt", text: "$ /paideia:grade quizzes/q07-hp.md answers/q07.md" },
    { kind: "out",    text: "→ Q1: ✗ algebra — dropped √(2S) prefactor" },
    { kind: "out",    text: "→ Q2: ✗ concept — gap not stated at k=0" },
    { kind: "out",    text: "→ Q3: ✓" },
    { kind: "ok",     text: "✓ grade complete · 2 errors logged" },
  ]},
  { stage: 4, lines: [
    { kind: "prompt", text: "$ /paideia:weakmap" },
    { kind: "out",    text: "→ aggregating errors/log.md against course-index/" },
    { kind: "out",    text: "→ ranking severity = density × error-rate" },
    { kind: "out",    text: "→ writing weakmap/weakmap_2026-04-23.md" },
    { kind: "ok",     text: "✓ weakmap committed · severity #1: AFM Bogoliubov rotation" },
  ]},
  { stage: 5, lines: [
    { kind: "prompt", text: "$ /paideia:cheatsheet --rev 04" },
    { kind: "out",    text: "→ compressing weakmap + high-density forms" },
    { kind: "out",    text: "→ writing cheatsheet/final.md (rev 04)" },
    { kind: "ok",     text: "✓ cheatsheet rev 04 · D−3 until exam" },
  ]},
];

const PA_PREVIEWS = {
  null: { path: "—", body: "Click a file in the tree, or run a stage to auto-open the artifact it writes." },
  1: PA_STAGES[1].artifact ? { path: "course-index/patterns.md", body: PA_STAGES[1].artifact } : null,
  2: { path: "quizzes/q07-hp.md", body: PA_STAGES[2].artifact },
  4: { path: "weakmap/weakmap_2026-04-23.md", body: PA_STAGES[4].artifact },
  5: { path: "cheatsheet/final.md", body: PA_STAGES[5].artifact },
};

function PaideiaTerminal() {
  useLang();
  const [stage, setStage] = useStatePA(-1); // -1 idle, 0..5 = PA_STAGES index
  const [openIdx, setOpenIdx] = useStatePA(null); // numeric stage of the previewed file
  const termRef = useRefPA(null);

  // auto-scroll terminal
  useEffectPA(() => {
    if (termRef.current) termRef.current.scrollTop = termRef.current.scrollHeight;
  }, [stage]);

  const runStage = (s) => {
    setStage(s);
    // auto-open artifact if there is one
    const auto = PA_TREE.find(n => n.openStage === s);
    if (auto) setOpenIdx(s);
  };

  const reset = () => { setStage(-1); setOpenIdx(null); };

  // build visible terminal lines: every entry with stage <= current
  const visibleBlocks = PA_TERMINAL.filter(b => b.stage <= stage);

  // visible files: hide files written in stages we have not reached
  const visibleTree = PA_TREE.filter(n => n.writeStage == null || n.writeStage <= stage);

  const previewed = openIdx == null ? null : PA_PREVIEWS[openIdx];

  return (
    <div style={{ border: "1px solid var(--line)", padding: 28, marginBottom: 100, background: "var(--bg)" }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 20, flexWrap: "wrap", gap: 12 }}>
        <div>
          <div className="micro" style={{ marginBottom: 6 }}>{t("Fig. 03 · Terminal walkthrough")}</div>
          <div className="display-md" style={{ fontSize: 28 }}>{t("A course folder, six commands, the artifacts on disk.")}</div>
        </div>
        <div className="micro" style={{ color: "var(--fg-muted)" }}>{t("zsh · paideia plugin · Claude Code")}</div>
      </div>

      <div style={{ display: "flex", gap: 8, flexWrap: "wrap", marginBottom: 16 }}>
        {PA_STAGES.map((s, i) => (
          <button type="button" key={s.id} onClick={() => runStage(i)}
                  data-cursor="link" data-cursor-label={t("Run")}
                  className="micro"
                  style={{
                    padding: "8px 14px",
                    border: "1px solid var(--line)",
                    background: stage === i ? "var(--fg)" : "var(--bg)",
                    color: stage === i ? "var(--bg)" : "var(--fg)",
                    cursor: "pointer",
                  }}>
            {String(i + 1).padStart(2, "0")} · {s.cmd}
          </button>
        ))}
        {stage >= 0 && (
          <button type="button" onClick={reset} data-cursor="link" data-cursor-label={t("Reset")} className="micro"
                  style={{ padding: "8px 14px", border: "1px solid var(--line-soft)", background: "var(--bg)", color: "var(--fg-muted)", cursor: "pointer", marginLeft: "auto" }}>
            {t("Reset")}
          </button>
        )}
      </div>

      <div style={{
        display: "grid",
        gridTemplateColumns: "220px 1.4fr 1fr",
        gap: 0,
        border: "1px solid var(--line)",
        minHeight: 420,
      }} className="pa-term-grid">

        {/* file tree */}
        <div style={{ borderRight: "1px solid var(--line)", padding: 14, overflow: "auto", maxHeight: 480 }}>
          <div className="micro" style={{ color: "var(--fg-muted)", marginBottom: 8 }}>{t("course/")}</div>
          {visibleTree.map((n, i) => {
            const isWritten = n.writeStage != null && n.writeStage <= stage;
            const justWritten = n.writeStage === stage;
            const canOpen = n.kind === "file" && n.openStage != null;
            return (
              <div key={i}
                   onClick={() => canOpen && setOpenIdx(n.openStage)}
                   data-cursor={canOpen ? "link" : undefined}
                   data-cursor-label={canOpen ? t("Open") : undefined}
                   style={{
                     fontFamily: "var(--font-mono)",
                     fontSize: 11,
                     paddingLeft: n.depth * 12,
                     paddingTop: 2,
                     paddingBottom: 2,
                     color: justWritten ? "var(--fg)" : isWritten ? "var(--fg)" : "var(--fg-muted)",
                     cursor: canOpen ? "pointer" : "default",
                     fontWeight: justWritten ? 500 : 400,
                     background: openIdx === n.openStage && canOpen ? "var(--line-soft)" : "transparent",
                   }}>
                {n.kind === "dir" ? "▸ " : "  "}{n.path.trimStart()}
                {justWritten && <span style={{ color: "var(--fg-muted)", marginLeft: 8 }}>· {t("new")}</span>}
              </div>
            );
          })}
        </div>

        {/* terminal */}
        <div ref={termRef} style={{
          borderRight: "1px solid var(--line)",
          padding: 14,
          fontFamily: "var(--font-mono)",
          fontSize: 11.5,
          lineHeight: 1.6,
          overflow: "auto",
          maxHeight: 480,
          background: "var(--bg)",
        }}>
          {visibleBlocks.map((b, bi) => (
            <div key={bi} style={{ marginBottom: 10 }}>
              {b.lines.map((ln, li) => (
                <div key={li} style={{
                  color: ln.kind === "prompt" ? "var(--fg)" : ln.kind === "ok" ? "var(--fg)" : "var(--fg-muted)",
                  fontWeight: ln.kind === "prompt" ? 500 : 400,
                }}>
                  {ln.text}
                </div>
              ))}
            </div>
          ))}
          {stage >= 0 && stage < PA_STAGES.length - 1 && (
            <div style={{ color: "var(--fg-muted)", marginTop: 6 }}>$ <span style={{ background: "var(--fg)", color: "var(--bg)", padding: "0 4px" }}>_</span></div>
          )}
        </div>

        {/* preview */}
        <div style={{ padding: 14, overflow: "auto", maxHeight: 480 }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 8 }}>
            <div className="micro">{t("Preview")}</div>
            {previewed && <code style={{ fontFamily: "var(--font-mono)", fontSize: 10, color: "var(--fg-muted)" }}>{previewed.path}</code>}
          </div>
          {previewed ? (
            <pre style={{
              fontFamily: "var(--font-mono)",
              fontSize: 10.5,
              lineHeight: 1.55,
              margin: 0,
              whiteSpace: "pre-wrap",
              color: "var(--fg)",
            }}>
              {previewed.body}
            </pre>
          ) : (
            <div className="body-sm" style={{ color: "var(--fg-muted)", fontStyle: "italic", lineHeight: 1.5 }}>
              {t("Run a stage above, or click a file in the tree to open its contents.")}
            </div>
          )}
        </div>
      </div>

      <div className="micro" style={{ marginTop: 16, color: "var(--fg-muted)", display: "flex", justifyContent: "space-between", flexWrap: "wrap", gap: 8 }}>
        <span>{t("Every artifact is a real markdown file on disk. The plugin's job is to write them, version them, and bring them back to your attention.")}</span>
        <span>{t("Plugin · /paideia · 14 commands")}</span>
      </div>

      <style>{`
        @media (max-width: 980px) { .pa-term-grid { grid-template-columns: 1fr !important; } }
      `}</style>
    </div>
  );
}

/* ------------------------------------------------------------------ */

function PaideiaPage() {
  useLang();
  const { go } = useRouter();

  return (
    <div className="page" style={{ paddingTop: 140 }}>
      <div className="page-eyebrow">
        <button type="button" onClick={() => go("projects")} data-cursor="link" data-cursor-label={t("Back")} className="micro" style={{ display: "flex", alignItems: "center", gap: 8 }}>
          {t("← Index 03 / Projects")}
        </button>
        <span style={{ width: 24, height: 1, background: "var(--fg)" }} />
        <span className="micro micro-fg">{t("Case study · Paideia")}</span>
      </div>

      <div className="responsive-stack" style={{ display: "grid", gridTemplateColumns: "1.5fr 1fr", gap: 64, marginBottom: 100, alignItems: "end" }}>
        <h1 className="display-xxl" style={{ fontFamily: "var(--font-kr)", fontWeight: 600, letterSpacing: "-0.03em" }}>
          Paideia<span style={{ color: "var(--fg-muted)", fontWeight: 400 }}> · Παιδεία</span>
        </h1>
        <div>
          <div className="micro" style={{ marginBottom: 12 }}>{t("2026 — present · Solo · Open source")}</div>
          <p className="body-lg" style={{ color: "var(--fg-muted)" }}>
            {t("A Claude Code plugin that turns a course folder into a self-formatting study system. Fourteen commands, six artifacts, one descending counter to the exam.")}
          </p>
        </div>
      </div>

      <div className="four-col" style={{ display: "grid", gridTemplateColumns: "repeat(4, 1fr)", gap: 32, marginBottom: 100 }}>
        {[
          { l: "Problem", v: "A graduate course is a stream of PDFs, slides, and hand-written homework, and a fixed exam date. Most of the work is bookkeeping the bookkeeper does not want to do." },
          { l: "Approach", v: "Treat the course folder as the system of record. Each plugin command takes the previous artifact as input and writes the next one — markdown all the way down, no app, no UI." },
          { l: "Result", v: "Used through one full semester. Cheatsheet revisions converged after four cycles. Time spent on exam prep dropped by half; coverage of high-density topics went up." },
          { l: "Stack", v: "Claude Code plugin · markdown artifacts · slash commands · zsh. Designed to be readable by a future maintainer who is just you, two semesters later." },
        ].map((s, i) => (
          <div key={i}>
            <div className="micro" style={{ marginBottom: 12 }}>{String(i + 1).padStart(2, "0")} · {t(s.l)}</div>
            <p className="body" style={{ color: "var(--fg-muted)" }}>{t(s.v)}</p>
          </div>
        ))}
      </div>

      <PaideiaCycle />

      <div className="responsive-stack" style={{ display: "grid", gridTemplateColumns: "1fr 2fr", gap: 64, marginBottom: 60 }}>
        <div className="micro">{t("§ 01 — On density")}</div>
        <div>
          <p className="body-lg" style={{ marginBottom: 20 }}>
            {t("The exam is not a test of the syllabus. It is a test of the homework, projected onto the syllabus. Topics that the lecturer drilled in problem sets show up; topics they only mentioned, mostly do not.")}
          </p>
          <p className="body-lg" style={{ color: "var(--fg-muted)" }}>
            {t("Paideia treats homework count as the simplest, most honest signal of exam probability. The pattern below is just that count, made visible.")}
          </p>
        </div>
      </div>

      <PaideiaHeatmap />

      <div className="responsive-stack" style={{ display: "grid", gridTemplateColumns: "1fr 2fr", gap: 64, marginBottom: 60 }}>
        <div className="micro">{t("§ 02 — Why a folder, not an app")}</div>
        <div>
          <p className="body-lg" style={{ marginBottom: 20 }}>
            {t("Apps decide the shape of the work for you. Folders do not. Paideia is a plugin precisely because it has to compose with the rest of how a graduate student already works — Obsidian, git, a terminal, a stack of PDFs.")}
          </p>
          <p className="body-lg" style={{ color: "var(--fg-muted)" }}>
            {t("Each command writes a markdown file you can open in any editor, diff in git, copy onto an exam-ready iPad, or send to a peer. The plugin is the choreography; the artifacts are the work.")}
          </p>
        </div>
      </div>

      <PaideiaTerminal />

      <div className="responsive-stack" style={{ display: "grid", gridTemplateColumns: "1fr 2fr", gap: 64, marginBottom: 100 }}>
        <div className="micro">{t("§ 03 — Παιδεία")}</div>
        <div>
          <p className="body-lg" style={{ marginBottom: 20 }}>
            {t("Paideia is the Greek word for the formation of a person — not training, not certification, but the slow shaping that lets a citizen meet their city.")}
          </p>
          <p className="body-lg" style={{ color: "var(--fg-muted)" }}>
            {t("This is a small tool that takes the name seriously: it does not promise to teach you, only to keep the formation legible while you do the work yourself.")}
          </p>
        </div>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 24, marginBottom: 80 }}>
        <button type="button" onClick={() => go("projects")} data-cursor="link" data-cursor-label={t("Back")} style={{ padding: "32px", border: "1px solid var(--line)", textAlign: "left" }}>
          <div className="micro" style={{ marginBottom: 8, color: "var(--fg-muted)" }}>{t("← Previous")}</div>
          <div className="display-md">{t("Index of all projects")}</div>
        </button>
        <button type="button" onClick={() => go("nodeprompt")} data-cursor="link" data-cursor-label={t("Next")} style={{ padding: "32px", border: "1px solid var(--line)", textAlign: "right", background: "var(--fg)", color: "var(--bg)" }}>
          <div className="micro" style={{ marginBottom: 8, color: "rgba(255,255,255,0.5)" }}>{t("Next →")}</div>
          <div className="display-md">{t("NodePrompt")}</div>
        </button>
      </div>
    </div>
  );
}

Object.assign(window, { PaideiaPage, PaideiaCycle, PaideiaHeatmap, PaideiaTerminal, PaTierMark });
