/* coretex.jsx — Coretex bento card + workspace-driven case-study page.

   "Writing gives not memory, but only reminding." — Plato, Phaedrus 275a

   The card sticks to the projects-bento tone: a small DAG with a
   left-to-right time-scrub. The case-study page does the opposite of
   the paideia/emotype template — no 4-col, no Fig-01/02 panels, no
   §01–§03 narrative, no comparison table. Instead the page IS the
   prototype: a draggable graph workspace with a click-to-inspect side
   panel, a play / scrub time slider, and reset. Coretex's product is
   exactly that, and the case study performs it instead of describing
   it. */

const { useState: useStateCT, useEffect: useEffectCT, useMemo: useMemoCT, useRef: useRefCT } = React;

const CORETEX_REPO = "https://github.com/TaewoooPark/Coretex";

const CTX_TYPES = ["IDEA", "RESEARCH", "DRAFT", "FEEDBACK", "DECISION", "ASSET", "FINAL"];

/* The synthetic 13-node decision genealogy. x is the time axis [0, 1];
   y is layout only. The card and the workspace share this data. */
const CTX_NODES = [
  { id: "n0",  type: "IDEA",     x: 0.05, y: 0.50 },
  { id: "n1",  type: "RESEARCH", x: 0.17, y: 0.30, parents: ["n0"] },
  { id: "n2",  type: "RESEARCH", x: 0.17, y: 0.70, parents: ["n0"] },
  { id: "n3",  type: "DRAFT",    x: 0.30, y: 0.20, parents: ["n1"] },
  { id: "n4",  type: "DRAFT",    x: 0.30, y: 0.55, parents: ["n2"], discarded: true },
  { id: "n5",  type: "FEEDBACK", x: 0.44, y: 0.14, parents: ["n3"] },
  { id: "n6",  type: "FEEDBACK", x: 0.44, y: 0.36, parents: ["n3"] },
  { id: "n7",  type: "DRAFT",    x: 0.46, y: 0.72, parents: ["n2"], discarded: true },
  { id: "n8",  type: "DECISION", x: 0.60, y: 0.26, parents: ["n5", "n6"] },
  { id: "n9",  type: "ASSET",    x: 0.74, y: 0.12, parents: ["n8"] },
  { id: "n10", type: "ASSET",    x: 0.74, y: 0.42, parents: ["n8"] },
  { id: "n11", type: "FEEDBACK", x: 0.62, y: 0.80, parents: ["n7"], discarded: true },
  { id: "n12", type: "FINAL",    x: 0.92, y: 0.28, parents: ["n8", "n9", "n10"] },
];

/* Per-node prose + a tiny pinned thread. These are the strings the
   inspector panel shows when the visitor clicks a node — they make the
   genealogy concrete and let the case study "perform" Coretex's claim
   without resorting to a comparison table. */
const CTX_NODE_CONTENT = {
  n0:  { title: "Brief — collaborative doc tooling",
         excerpt: "We keep losing the why. Sketch a node-graph model where every save is a node and the file tree is a projection.",
         thread: [
           { ts: "Apr 2 · 14:22", who: "team", msg: "file trees are lossy" },
           { ts: "Apr 2 · 17:08", who: "self", msg: "Notion handoff bounced three times this week" },
         ] },
  n1:  { title: "Survey — git-as-graph approaches",
         excerpt: "git-log is genealogy; Obsidian backlinks are not. We need typed nodes with AI-extracted edges, not free-form bidirectional links.",
         thread: [
           { ts: "Apr 4 · 10:30", who: "self", msg: "ADR (Architecture Decision Records) is a partial precedent" },
           { ts: "Apr 5 · 15:11", who: "team", msg: "Roam research mode is close but untyped" },
         ] },
  n2:  { title: "Survey — decision-log products",
         excerpt: "ADRs work but stay isolated from drafts; can't time-travel; no place to hang a feedback thread that closed a branch.",
         thread: [
           { ts: "Apr 5 · 09:42", who: "self", msg: "ThoughtWorks ADR post — too thin for what we want" },
         ] },
  n3:  { title: "Draft v1 — file-tree wrapper",
         excerpt: "Re-skin a file tree with type badges. Felt thin during user test; reviewer asked \"so it's just folders?\".",
         thread: [
           { ts: "Apr 7 · 16:00", who: "user", msg: "feels like Notion-lite" },
         ] },
  n4:  { title: "Draft v0 — 21 node types",
         excerpt: "Tried 21 types: spec, hypothesis, vote, retro, … Pruned during design review. Ontology too tall to memorise.",
         thread: [
           { ts: "Apr 10 · 11:18", who: "team", msg: "too many shapes; reviewer can't memorise" },
         ] },
  n5:  { title: "Feedback — KAIST UX review",
         excerpt: "\"The graph needs a default view of progress, not just structure.\" Forced the time-axis into the layout from this point on.",
         thread: [
           { ts: "Apr 14 · 19:30", who: "Hannah", msg: "force time on the x-axis" },
         ] },
  n6:  { title: "Feedback — early-adopter call",
         excerpt: "Two PMs asked for time travel + comment threads pinned to a node, not to the doc. This survived to the decision node.",
         thread: [
           { ts: "Apr 17 · 13:05", who: "Tom",    msg: "comments belong to the work, not the doc" },
           { ts: "Apr 17 · 13:08", who: "Jiyeon", msg: "+1, also let me rewind" },
         ] },
  n7:  { title: "Draft — markdown-first variant (dropped)",
         excerpt: "Pure-markdown export. Lost the live graph; collaborators couldn't see ancestry. Closed quickly.",
         thread: [
           { ts: "Apr 19 · 10:14", who: "Tom", msg: "if there's no graph, why use Coretex?" },
         ] },
  n8:  { title: "Decision — typed DAG over file-tree",
         excerpt: "Lock in: 7 node types, immutable versions, AI extracts edges only. Time travel is a first-class view, not a hidden tab.",
         thread: [
           { ts: "Apr 22 · 09:00", who: "self", msg: "DR-002 signed" },
         ] },
  n9:  { title: "Asset — design-system glyphs",
         excerpt: "○ △ ▭ ◇ ■ ▣ ●. Brutalist B&W, dashed = discarded, × = dead-end leaf.",
         thread: [
           { ts: "Apr 24 · 17:50", who: "self", msg: "Figma file v2 exported" },
         ] },
  n10: { title: "Asset — Next.js scaffold",
         excerpt: "Next.js 16 + Prisma + in-memory mock store for the public demo. No database required to run it locally.",
         thread: [
           { ts: "Apr 25 · 22:14", who: "self", msg: "pushed main, dev server boots cleanly" },
         ] },
  n11: { title: "Feedback — markdown branch (closed)",
         excerpt: "One-line response from Tom closed the discarded branch. The branch stays in the graph as evidence; no Coretex feature can remove it.",
         thread: [] },
  n12: { title: "Final — Coretex visual prototype",
         excerpt: "Public repo, mock store, demo workspace. Every artifact above is reachable from this node — including the two branches that were closed.",
         thread: [
           { ts: "May 3 · 11:00", who: "self", msg: "repo public on github.com/TaewoooPark/Coretex" },
         ] },
};

function ctxAncestors(id) {
  const s = new Set();
  const q = [id];
  while (q.length) {
    const cur = q.shift();
    if (s.has(cur)) continue;
    s.add(cur);
    const n = CTX_NODES.find(x => x.id === cur);
    if (n && n.parents) q.push(...n.parents);
  }
  return s;
}

function CtxNodeGlyph({ node, W, H, size = 12, dim = false, emphasized = false }) {
  const x = node.x * W;
  const y = node.y * H;
  const r = size / 2;
  const sw = emphasized ? 1.8 : 1;
  const op = dim ? 0.32 : 1;
  const stroke = node.discarded ? "var(--fg-muted)" : "var(--fg)";
  const bg = "var(--bg)";
  switch (node.type) {
    case "IDEA":
      return <circle cx={x} cy={y} r={r} fill={bg} stroke={stroke} strokeWidth={sw} opacity={op} />;
    case "RESEARCH":
      return <polygon
                points={`${x},${y - r} ${x - r * 0.92},${y + r * 0.66} ${x + r * 0.92},${y + r * 0.66}`}
                fill={bg} stroke={stroke} strokeWidth={sw} opacity={op} />;
    case "DRAFT":
      return <rect x={x - r} y={y - r} width={size} height={size}
                   fill={bg} stroke={stroke} strokeWidth={sw} opacity={op} />;
    case "FEEDBACK":
      return <polygon
                points={`${x},${y - r} ${x + r},${y} ${x},${y + r} ${x - r},${y}`}
                fill={bg} stroke={stroke} strokeWidth={sw} opacity={op} />;
    case "DECISION":
      return <rect x={x - r} y={y - r} width={size} height={size}
                   fill="var(--fg)" stroke="var(--fg)" strokeWidth={sw} opacity={op} />;
    case "ASSET":
      return (
        <g opacity={op}>
          <rect x={x - r} y={y - r} width={size} height={size}
                fill={bg} stroke={stroke} strokeWidth={sw} />
          <line x1={x - r + 2.4} y1={y} x2={x + r - 2.4} y2={y}
                stroke={stroke} strokeWidth={sw * 0.8} />
        </g>
      );
    case "FINAL":
      return <circle cx={x} cy={y} r={r} fill="var(--fg)" stroke="var(--fg)" strokeWidth={sw} opacity={op} />;
    default:
      return null;
  }
}

/* CoretexGenealogy — shared SVG used by the bento card only. The
   case-study page uses CoretexWorkspace, which has its own renderer
   that supports drag + click + selection. */
function CoretexGenealogy({ tNow, ancestorSet, W = 600, H = 320, size = 12 }) {
  return (
    <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet"
         aria-hidden="true"
         style={{ position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none" }}>
      {CTX_NODES.flatMap(n =>
        (n.parents || []).map(pid => {
          const p = CTX_NODES.find(x => x.id === pid);
          if (!p) return null;
          if (p.x > tNow + 0.02 || n.x > tNow + 0.02) return null;
          const inChain = ancestorSet && ancestorSet.has(n.id) && ancestorSet.has(pid);
          const dim = ancestorSet && !inChain;
          const discarded = n.discarded || p.discarded;
          return (
            <line key={`${pid}-${n.id}`}
                  x1={p.x * W} y1={p.y * H}
                  x2={n.x * W} y2={n.y * H}
                  stroke={discarded ? "var(--fg-muted)" : "var(--fg)"}
                  strokeWidth={inChain ? 1.6 : 0.8}
                  strokeDasharray={discarded ? "3 3" : ""}
                  opacity={dim ? 0.22 : (discarded ? 0.55 : 0.92)} />
          );
        })
      )}
      {CTX_NODES.map(n => {
        if (n.x > tNow + 0.02) return null;
        const inChain = ancestorSet && ancestorSet.has(n.id);
        const dim = ancestorSet && !inChain;
        return (
          <CtxNodeGlyph key={n.id} node={n} W={W} H={H} size={size}
                        dim={dim} emphasized={inChain} />
        );
      })}
      {CTX_NODES.filter(n => n.discarded && !CTX_NODES.some(m => (m.parents || []).includes(n.id))).map(n => {
        if (n.x > tNow + 0.02) return null;
        const dim = ancestorSet && !ancestorSet.has(n.id);
        return (
          <text key={n.id + "-x"}
                x={n.x * W + size * 1.05} y={n.y * H + size * 0.32}
                fontFamily="var(--font-mono)" fontSize={size}
                fill="var(--fg-muted)" opacity={dim ? 0.22 : 0.7}>×</text>
        );
      })}
    </svg>
  );
}

/* CoretexCard — projects bento card (2×2). Idle = auto time-scrub
   left-to-right; hover slows it and pointer X overrides the time. */
function CoretexCard({ onClick }) {
  const ref = useRefCT(null);
  const mouseRef = useRefCT({ x: 0.5, y: 0.5 });
  const hoverRef = useRefCT(false);
  const [tNow, setTNow] = useStateCT(0);
  const [hoverNodeId, setHoverNodeId] = useStateCT(null);

  useEffectCT(() => {
    let raf;
    const start = performance.now();
    const loop = (now) => {
      const elapsed = (now - start) / 1000;
      const cyc = 9;
      const phase = (elapsed % cyc) / cyc;
      const eased = phase < 0.94 ? phase / 0.94 : 1;
      const t = hoverRef.current ? mouseRef.current.x : eased;
      setTNow(t);
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(raf);
  }, []);

  const ancestorSet = useMemoCT(() => hoverNodeId ? ctxAncestors(hoverNodeId) : null, [hoverNodeId]);

  const onEnter = () => { hoverRef.current = true; };
  const onLeave = () => {
    hoverRef.current = false;
    mouseRef.current = { x: 0.5, y: 0.5 };
    setHoverNodeId(null);
  };
  const onMove = (e) => {
    if (!window.__hasHover) return;
    const r = ref.current.getBoundingClientRect();
    const mx = Math.max(0, Math.min(1, (e.clientX - r.left) / r.width));
    const my = Math.max(0, Math.min(1, (e.clientY - r.top) / r.height));
    mouseRef.current = { x: mx, y: my };
    let best = null, bestD = Infinity;
    for (const n of CTX_NODES) {
      if (n.x > mx + 0.08) continue;
      const dx = n.x - mx;
      const dy = n.y - my;
      const d = dx * dx + dy * dy;
      if (d < bestD) { bestD = d; best = n; }
    }
    setHoverNodeId(best && bestD < 0.06 ? best.id : null);
  };

  return (
    <div
      ref={ref}
      onClick={onClick}
      onMouseMove={onMove}
      onMouseEnter={onEnter}
      onMouseLeave={onLeave}
      data-cursor="link" data-cursor-label={t("View Project")}
      style={{
        gridColumn: "span 2", gridRow: "span 2",
        position: "relative", overflow: "hidden",
        border: "1px solid var(--line)", background: "var(--bg)",
        padding: 20,
        display: "flex", flexDirection: "column",
        cursor: "pointer",
      }}>
      <div style={{ position: "absolute", inset: 0, padding: "52px 14px 64px", boxSizing: "border-box" }}>
        <CoretexGenealogy tNow={tNow} ancestorSet={ancestorSet} W={600} H={320} size={11} />
      </div>

      <div style={{ position: "relative", zIndex: 1, display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 8 }}>
        <span className="micro">{t("03e · Prototype")}</span>
        <span className="micro" style={{ color: "var(--fg-muted)" }}>{t("2026 · Closed")}</span>
      </div>

      <div style={{ flex: 1 }} />

      <div style={{ position: "relative", zIndex: 1 }}>
        <div className="display-md" style={{ fontSize: 30, marginBottom: 6, letterSpacing: "-0.01em" }}>CORETEX</div>
        <div className="body-sm" style={{ color: "var(--fg-muted)", marginBottom: 12, maxWidth: "100%" }}>
          {t("Decision genealogy for collaborative documents — drafts, chats, versions, and discarded branches kept walkable.")}
        </div>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: 8, flexWrap: "wrap" }}>
          <span className="micro micro-fg">{t("Next.js · React · TS")}</span>
          <span className="micro" style={{ fontFamily: "var(--font-mono)", color: "var(--fg-muted)" }}>
            t = {tNow.toFixed(2)}
          </span>
        </div>
      </div>
    </div>
  );
}

/* CoretexWorkspace — the case-study page's entire body.

   What it supports:
   - Drag any node to a new position (positions are local React state, so
     reset is trivial). Edges follow.
   - Click a node (mouseup without movement) to open the inspector panel
     on the right. Click the same node or × to close.
   - Click on empty canvas to deselect.
   - Hover a node (or have one selected) to isolate its ancestor lineage.
   - Play / pause + a real <input type="range"> time slider scrubs the
     reveal — when t < 1, late nodes (and edges that touch them) fade out.
   - Reset positions returns nodes to the seed layout in CTX_NODES. */
function CoretexWorkspace() {
  const seedPositions = useMemoCT(() => {
    const m = {};
    CTX_NODES.forEach(n => { m[n.id] = { x: n.x, y: n.y }; });
    return m;
  }, []);
  const [positions, setPositions] = useStateCT(seedPositions);
  const [selected, setSelected] = useStateCT(null);
  const [hoverNodeId, setHoverNodeId] = useStateCT(null);
  const [tNow, setTNow] = useStateCT(1.0);
  const [playing, setPlaying] = useStateCT(false);
  const dragRef = useRefCT(null);
  const svgRef = useRefCT(null);

  useEffectCT(() => {
    if (!playing) return;
    let raf, last = performance.now();
    const tick = (now) => {
      const dt = (now - last) / 1000;
      last = now;
      setTNow(t => {
        const next = t + dt * 0.18;
        if (next >= 1) { setPlaying(false); return 1; }
        return next;
      });
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [playing]);

  const ancestorSet = useMemoCT(() => {
    const id = selected || hoverNodeId;
    return id ? ctxAncestors(id) : null;
  }, [selected, hoverNodeId]);

  const W = 1100, H = 520;

  const screenToNorm = (clientX, clientY) => {
    const r = svgRef.current.getBoundingClientRect();
    return {
      x: (clientX - r.left) / r.width,
      y: (clientY - r.top) / r.height,
    };
  };

  const onNodeDown = (id, e) => {
    e.stopPropagation();
    if (e.button !== undefined && e.button !== 0) return;
    const p = screenToNorm(e.clientX, e.clientY);
    const cur = positions[id];
    dragRef.current = {
      nodeId: id,
      offsetX: cur.x - p.x,
      offsetY: cur.y - p.y,
      moved: false,
    };
  };

  const onSvgMove = (e) => {
    const d = dragRef.current;
    if (!d) return;
    const p = screenToNorm(e.clientX, e.clientY);
    const nx = Math.max(0.03, Math.min(0.97, p.x + d.offsetX));
    const ny = Math.max(0.06, Math.min(0.94, p.y + d.offsetY));
    const cur = positions[d.nodeId];
    if (Math.abs(nx - cur.x) + Math.abs(ny - cur.y) > 0.003) d.moved = true;
    setPositions(prev => ({ ...prev, [d.nodeId]: { x: nx, y: ny } }));
  };

  const onSvgUp = () => {
    const d = dragRef.current;
    if (!d) return;
    if (!d.moved) {
      setSelected(s => s === d.nodeId ? null : d.nodeId);
    }
    dragRef.current = null;
  };

  const onCanvasClick = (e) => {
    // only deselect if we did not just finish a drag
    if (dragRef.current) return;
    if (e.target === svgRef.current || e.target.tagName === "svg") {
      setSelected(null);
    }
  };

  const resetPositions = () => {
    const m = {};
    CTX_NODES.forEach(n => { m[n.id] = { x: n.x, y: n.y }; });
    setPositions(m);
  };

  const selectedNode    = selected ? CTX_NODES.find(n => n.id === selected) : null;
  const selectedContent = selected ? CTX_NODE_CONTENT[selected] : null;

  return (
    <div className="ctx-wrapper" style={{
      marginBottom: 56,
      maxWidth: 1080,
      marginLeft: "auto",
      marginRight: "auto",
    }}>
      <div className="ctx-workspace" style={{
        position: "relative",
        border: "1px solid var(--line)",
        background: "var(--bg)",
        overflow: "hidden",
      }}>
        <div ref={svgRef}
             onMouseMove={onSvgMove}
             onMouseUp={onSvgUp}
             onMouseLeave={() => { if (dragRef.current) onSvgUp(); }}
             onClick={onCanvasClick}
             style={{
               position: "relative",
               width: "100%",
               aspectRatio: "11 / 5.2",
               maxHeight: 440,
               cursor: dragRef.current ? "grabbing" : "default",
               userSelect: "none",
               WebkitUserSelect: "none",
               background: "var(--bg)",
               overflow: "hidden",
             }}>
          <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet"
               style={{ position: "absolute", inset: 0, width: "100%", height: "100%" }}>
            {/* edges */}
            {CTX_NODES.flatMap(n =>
              (n.parents || []).map(pid => {
                const p = positions[pid];
                const c = positions[n.id];
                if (!p || !c) return null;
                if (p.x > tNow + 0.02 || c.x > tNow + 0.02) return null;
                const pn = CTX_NODES.find(x => x.id === pid);
                const inChain = ancestorSet && ancestorSet.has(n.id) && ancestorSet.has(pid);
                const dim = ancestorSet && !inChain;
                const discarded = n.discarded || (pn && pn.discarded);
                return (
                  <line key={`${pid}-${n.id}`}
                        x1={p.x * W} y1={p.y * H}
                        x2={c.x * W} y2={c.y * H}
                        stroke={discarded ? "var(--fg-muted)" : "var(--fg)"}
                        strokeWidth={inChain ? 1.8 : 1.0}
                        strokeDasharray={discarded ? "3 3" : ""}
                        opacity={dim ? 0.22 : (discarded ? 0.55 : 0.9)} />
                );
              })
            )}
            {/* nodes — interactable */}
            {CTX_NODES.map(n => {
              const pos = positions[n.id];
              if (pos.x > tNow + 0.02) return null;
              const inChain = ancestorSet && ancestorSet.has(n.id);
              const dim = ancestorSet && !inChain;
              const isSelected = selected === n.id;
              const isHover = hoverNodeId === n.id;
              const placedNode = { ...n, x: pos.x, y: pos.y };
              return (
                <g key={n.id}
                   onMouseDown={(e) => onNodeDown(n.id, e)}
                   onMouseEnter={() => setHoverNodeId(n.id)}
                   onMouseLeave={() => setHoverNodeId(null)}
                   style={{ cursor: dragRef.current && dragRef.current.nodeId === n.id ? "grabbing" : "grab" }}>
                  {/* invisible hit-area */}
                  <circle cx={pos.x * W} cy={pos.y * H} r="22"
                          fill="transparent" stroke="none" />
                  {isSelected && (
                    <circle cx={pos.x * W} cy={pos.y * H} r="20"
                            fill="none" stroke="var(--fg)" strokeWidth="1" />
                  )}
                  {(isHover && !isSelected) && (
                    <circle cx={pos.x * W} cy={pos.y * H} r="18"
                            fill="none" stroke="var(--fg-muted)" strokeWidth="0.7" strokeDasharray="2 3" />
                  )}
                  <CtxNodeGlyph node={placedNode} W={W} H={H} size={16}
                                dim={dim} emphasized={inChain || isSelected} />
                  {(isHover || isSelected) && (
                    <text x={pos.x * W} y={pos.y * H + 34}
                          textAnchor="middle"
                          fontFamily="var(--font-mono)" fontSize="10"
                          letterSpacing="0.06em"
                          fill="var(--fg)"
                          opacity="0.85"
                          style={{ pointerEvents: "none" }}>
                      {n.type.toLowerCase()} · {n.id}
                    </text>
                  )}
                </g>
              );
            })}
            {/* × terminators on dead-end discarded leaves */}
            {CTX_NODES.filter(n => n.discarded && !CTX_NODES.some(m => (m.parents || []).includes(n.id))).map(n => {
              const pos = positions[n.id];
              if (pos.x > tNow + 0.02) return null;
              const dim = ancestorSet && !ancestorSet.has(n.id);
              return (
                <text key={n.id + "-x"}
                      x={pos.x * W + 22} y={pos.y * H + 5}
                      fontFamily="var(--font-mono)" fontSize="14"
                      fill="var(--fg-muted)" opacity={dim ? 0.22 : 0.7}
                      style={{ pointerEvents: "none" }}>×</text>
              );
            })}
          </svg>
        </div>

      </div>

      {/* Inspector — a dedicated row directly below the canvas, NOT an
          overlay on top of it. The canvas keeps its full bounds; nodes
          on the right side stay clickable; the inspector simply pushes
          the controls/legend rows down. Internally split: left column
          is meta/excerpt/ancestors, right column is pinned thread. */}
      {selectedNode && selectedContent && (
        <div className="ctx-inspector" style={{
          marginTop: 16,
          padding: "22px 24px 20px",
          background: "var(--bg)",
          border: "1px solid var(--line)",
          animation: "ctxInspectorIn 280ms var(--easing-default)",
          display: "grid",
          gridTemplateColumns: "1.3fr 1fr",
          gap: 32,
        }}>
          <div>
            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 12 }}>
              <span className="micro" style={{ fontFamily: "var(--font-mono)", letterSpacing: "0.08em" }}>
                {selectedNode.type} · {selectedNode.id}
              </span>
              <button type="button" onClick={() => setSelected(null)}
                      data-cursor="link" data-cursor-label={t("Close")}
                      className="micro"
                      style={{ background: "transparent", border: 0, color: "var(--fg-muted)", cursor: "pointer", padding: 0, fontFamily: "var(--font-mono)" }}>
                ✕
              </button>
            </div>
            <div className="display-md" style={{ fontSize: 20, lineHeight: 1.25, marginBottom: 12 }}>
              {selectedContent.title}
            </div>
            <div className="body-sm" style={{ color: "var(--fg-muted)", marginBottom: 14, lineHeight: 1.55 }}>
              {selectedContent.excerpt}
            </div>
            {selectedNode.parents && selectedNode.parents.length > 0 && (
              <div style={{ marginBottom: 10 }}>
                <div className="micro" style={{ color: "var(--fg-muted)", marginBottom: 6 }}>
                  {t("Ancestors")}
                </div>
                <div className="micro" style={{ fontFamily: "var(--font-mono)", lineHeight: 1.6 }}>
                  {selectedNode.parents.map(pid => {
                    const p = CTX_NODES.find(x => x.id === pid);
                    return p ? `${p.type.toLowerCase()} · ${p.id}` : pid;
                  }).join("  ←  ")}
                </div>
              </div>
            )}
            {selectedNode.discarded && (
              <div className="micro" style={{ color: "var(--fg-muted)", fontFamily: "var(--font-mono)" }}>
                {t("× branch closed — kept walkable as evidence")}
              </div>
            )}
          </div>
          <div>
            {selectedContent.thread && selectedContent.thread.length > 0 ? (
              <>
                <div className="micro" style={{ color: "var(--fg-muted)", marginBottom: 10 }}>
                  {t("Pinned thread")}
                </div>
                {selectedContent.thread.map((m, i) => (
                  <div key={i} style={{
                    borderLeft: "1px solid var(--line-soft)",
                    paddingLeft: 12, marginBottom: 10,
                  }}>
                    <div className="micro" style={{ color: "var(--fg-muted)", marginBottom: 2, fontFamily: "var(--font-mono)" }}>
                      {m.who} · {m.ts}
                    </div>
                    <div className="body-sm">{m.msg}</div>
                  </div>
                ))}
              </>
            ) : (
              <div className="micro" style={{ color: "var(--fg-muted)", fontStyle: "italic" }}>
                {t("No pinned thread on this node.")}
              </div>
            )}
          </div>
        </div>
      )}

      {/* Controls — play/pause + range slider + reset */}
      <div style={{
        display: "flex", alignItems: "center", gap: 14,
        padding: "16px 0 0", flexWrap: "wrap",
      }}>
        <button type="button"
                onClick={() => { if (tNow >= 1) setTNow(0); setPlaying(p => !p); }}
                data-cursor="link" data-cursor-label={t(playing ? "Pause" : "Play")}
                className="micro"
                style={{
                  background: "var(--bg)", border: "1px solid var(--line)",
                  padding: "8px 14px", cursor: "pointer", color: "var(--fg)",
                  fontFamily: "var(--font-mono)", letterSpacing: "0.06em",
                }}>
          {playing ? "▮▮  pause" : "▶  play"}
        </button>
        <input
          type="range" min={0} max={1000}
          value={Math.round(tNow * 1000)}
          onChange={(e) => { setTNow(Number(e.target.value) / 1000); setPlaying(false); }}
          className="ctx-time-slider"
          style={{ flex: "1 1 220px", minWidth: 180 }}
          aria-label={t("Time scrub")}
        />
        <span className="micro" style={{ fontFamily: "var(--font-mono)", color: "var(--fg-muted)", minWidth: 70 }}>
          t = {tNow.toFixed(2)}
        </span>
        <button type="button" onClick={resetPositions}
                data-cursor="link" data-cursor-label={t("Reset")}
                className="micro"
                style={{
                  background: "var(--bg)", border: "1px solid var(--line-soft)",
                  padding: "8px 14px", cursor: "pointer", color: "var(--fg-muted)",
                  fontFamily: "var(--font-mono)", letterSpacing: "0.06em",
                }}>
          reset positions
        </button>
      </div>

      {/* Legend + interaction hint — replaces the old §01-§03 narrative */}
      <div style={{
        display: "flex", justifyContent: "space-between", alignItems: "center",
        gap: 16, padding: "14px 0 0", flexWrap: "wrap",
      }}>
        <div className="micro" style={{ color: "var(--fg-muted)", letterSpacing: "0.04em" }}>
          ○ idea  ·  △ research  ·  ▭ draft  ·  ◇ feedback  ·  ■ decision  ·  ▣ asset  ·  ● final  ·  ┄┄ × discarded
        </div>
        <div className="micro" style={{ color: "var(--fg-muted)" }}>
          {t("drag a node · click to inspect · slider to scrub time")}
        </div>
      </div>

      <style>{`
        .ctx-time-slider {
          appearance: none;
          -webkit-appearance: none;
          height: 2px;
          background: var(--line);
          outline: none;
          cursor: pointer;
        }
        .ctx-time-slider::-webkit-slider-thumb {
          appearance: none;
          width: 14px; height: 14px;
          background: var(--fg);
          border-radius: 0;
          cursor: pointer;
        }
        .ctx-time-slider::-moz-range-thumb {
          width: 14px; height: 14px;
          background: var(--fg);
          border: 0;
          border-radius: 0;
          cursor: pointer;
        }
        @keyframes ctxInspectorIn {
          from { opacity: 0; transform: translateY(-8px); }
          to   { opacity: 1; transform: translateY(0); }
        }
        @media (max-width: 760px) {
          .ctx-inspector { grid-template-columns: 1fr !important; gap: 22px !important; padding: 18px 16px 16px !important; }
          .ctx-workspace > div {
            aspect-ratio: 4 / 3 !important;
            max-height: none !important;
          }
        }
      `}</style>
    </div>
  );
}

/* CoretexPage — case study. Skips the paideia/emotype 4-col + Fig + §
   + table template. The workspace IS the page; everything that the
   template would have explained is reachable by clicking a node. */
function CoretexPage() {
  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 · Coretex")}</span>
        <span style={{ width: 24, height: 1, background: "var(--fg)" }} />
        <a href={CORETEX_REPO} target="_blank" rel="noopener noreferrer"
           data-cursor="link" data-cursor-label={t("Open on GitHub")}
           className="micro" style={{ color: "var(--fg)", textDecoration: "none", borderBottom: "1px solid var(--line)" }}>
          {t("GitHub ↗ TaewoooPark/Coretex")}
        </a>
      </div>

      {/* Compact hero — one row, no 4-col grid */}
      <div className="responsive-stack" style={{ display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 48, marginBottom: 36, alignItems: "end" }}>
        <h1 className="display-xxl" style={{ fontFamily: "var(--font-kr)", fontWeight: 600, letterSpacing: "-0.03em" }}>
          CORETEX
        </h1>
        <div>
          <div className="micro" style={{ marginBottom: 10 }}>{t("2026 · Solo · Visual prototype · Closed source")}</div>
          <p className="body-lg" style={{ color: "var(--fg-muted)", marginBottom: 0 }}>
            {t("Project work as a directed lineage — assumptions, evidence, edits, arguments, decisions, abandoned alternatives. Drag any node. Click one to open its pinned thread. Scrub time to replay the genealogy.")}
          </p>
        </div>
      </div>

      <CoretexWorkspace />

      {/* Brutalist statement paragraph — the comparison information from the
          README, but rendered as one sentence chain instead of a table. */}
      <div style={{
        borderTop: "1px solid var(--line)",
        padding: "32px 0 8px",
        marginBottom: 48,
      }}>
        <div className="micro" style={{ color: "var(--fg-muted)", marginBottom: 14, letterSpacing: "0.08em" }}>
          {t("What Coretex preserves")}
        </div>
        <div style={{
          fontSize: "clamp(18px, 2.0vw, 22px)",
          lineHeight: 1.55,
          color: "var(--fg)",
          maxWidth: 920,
          fontWeight: 500,
          letterSpacing: "-0.005em",
        }}>
          <span style={{ color: "var(--fg-muted)" }}>{t("Reasoning,")}</span> {t("not artifacts.")}{" "}
          <span style={{ color: "var(--fg-muted)" }}>{t("Branches that were closed,")}</span> {t("not deleted.")}{" "}
          <span style={{ color: "var(--fg-muted)" }}>{t("Immutable versions,")}</span> {t("not overwrite-with-history.")}{" "}
          <span style={{ color: "var(--fg-muted)" }}>{t("Conversation pinned to the node,")}</span> {t("not to the doc.")}{" "}
          <span style={{ color: "var(--fg-muted)" }}>{t("AI that extracts tags, edges, summaries,")}</span> {t("never decisions.")}
        </div>
      </div>

      {/* ※ aside notes — 2×2 grid, left-border only, no boxes; structurally
          different from paideia/emotype's §01–§03 prose layout. */}
      <div className="ctx-notes" style={{
        display: "grid",
        gridTemplateColumns: "repeat(2, 1fr)",
        gap: 36,
        marginBottom: 56,
      }}>
        {[
          { tag: "On context",
            body: "Work is modelled as directed node relationships rather than loose folders. A final output has ancestry. Conversations are evidence. Versions are memory, not overwrite noise." },
          { tag: "On discarded branches",
            body: "Startup pivots, research hypotheses that didn't pan out, design feedback that closed a branch. The dead end is still knowledge; the graph remembers it explicitly so a new teammate can read it." },
          { tag: "On AI",
            body: "AI extracts tags, related nodes, candidate edges, summaries, and explicit decision statements. Never the decisions themselves. The team decides; the model annotates." },
          { tag: "On Plato",
            body: "Phaedrus distinguishes mnēmē — living memory — from hypomnēsis — the external reminder writing leaves behind. The document is the reminder; Coretex builds the path back to the memory that produced it." },
        ].map(({ tag, body }) => (
          <div key={tag} style={{
            borderLeft: "1px solid var(--fg)",
            padding: "4px 0 4px 18px",
          }}>
            <div className="micro" style={{ marginBottom: 8, color: "var(--fg-muted)", letterSpacing: "0.08em" }}>
              ※ {t(tag)}
            </div>
            <p className="body" style={{ color: "var(--fg)", marginBottom: 0, lineHeight: 1.55 }}>
              {t(body)}
            </p>
          </div>
        ))}
        <style>{`
          @media (max-width: 760px) {
            .ctx-notes { grid-template-columns: 1fr !important; gap: 24px !important; }
          }
        `}</style>
      </div>

      {/* Plato pull-quote — italic display, centered, between two thin lines.
          Replaces a §03 paragraph with a single line of force. */}
      <div style={{
        borderTop: "1px solid var(--line)",
        borderBottom: "1px solid var(--line)",
        padding: "40px 0",
        marginBottom: 56,
        textAlign: "center",
      }}>
        <div style={{
          fontFamily: "var(--font-display)",
          fontStyle: "italic",
          fontSize: "clamp(22px, 3.2vw, 32px)",
          letterSpacing: "-0.01em",
          color: "var(--fg)",
          marginBottom: 12,
          lineHeight: 1.25,
        }}>
          {t("\"Writing gives not memory, but only reminding.\"")}
        </div>
        <div className="micro" style={{ color: "var(--fg-muted)", letterSpacing: "0.08em" }}>
          {t("— Plato, Phaedrus 275a")}
        </div>
      </div>

      {/* Single-row "run it" strip — no comparison table */}
      <div className="ctx-cta" style={{
        display: "grid", gridTemplateColumns: "auto 1fr auto",
        gap: 24, alignItems: "center",
        borderTop: "1px solid var(--line)", borderBottom: "1px solid var(--line)",
        padding: "22px 0", marginBottom: 80,
      }}>
        <div>
          <div className="micro" style={{ color: "var(--fg-muted)", marginBottom: 6 }}>{t("Run the demo")}</div>
          <code style={{ fontFamily: "var(--font-mono)", fontSize: 13, color: "var(--fg)" }}>
            git clone …/Coretex && npm i && npm run dev
          </code>
        </div>
        <div className="micro" style={{ color: "var(--fg-muted)", textAlign: "center" }}>
          {t("Next.js 16 · Prisma · in-memory mock store · proprietary license")}
        </div>
        <a href={CORETEX_REPO} target="_blank" rel="noopener noreferrer"
           data-cursor="link" data-cursor-label={t("Open on GitHub")}
           className="micro"
           style={{ color: "var(--fg)", textDecoration: "none", borderBottom: "1px solid var(--fg)", paddingBottom: 2 }}>
          {t("github.com/TaewoooPark/Coretex →")}
        </a>
      </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("emotype")} 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("emotype")}</div>
        </button>
      </div>

      <style>{`
        @media (max-width: 760px) {
          .ctx-cta { grid-template-columns: 1fr !important; text-align: left !important; }
          .ctx-cta > div, .ctx-cta > a { text-align: left !important; }
        }
      `}</style>
    </div>
  );
}

Object.assign(window, {
  CoretexPage, CoretexCard,
  CTX_TYPES, CTX_NODES, CTX_NODE_CONTENT, ctxAncestors,
  CtxNodeGlyph, CoretexGenealogy, CoretexWorkspace,
});
