// ═══════════════════════════════════════════════════════════════════
// WORLD MODEL — the ontology as a PROPERTY GRAPH. The datapoint register
// is properties; this layer names the subjects those properties are about
// (13 nodes) and the edges between them. Tiers are demoted to property
// domains. Structural rules: roles live on edges, attributed relationships
// are reified (Holding, Valuation), a vocab value is a node iff an edge
// targets it (Jurisdiction, Chain).
// ═══════════════════════════════════════════════════════════════════
(function () {
  const { useState } = React;
  const S = window.ONTOLOGY;

  const kgMono = (s, extra) => <span style={{ fontFamily: "var(--mono)", fontSize: "8.5px", letterSpacing: "0.1em", textTransform: "uppercase", color: "var(--ink-4)", ...extra }}>{s}</span>;

  // shared ontology visual language (window.OKit) — palette, category order,
  // marks and legend declared once in OntologyKit.jsx and reused everywhere.
  const OK = window.OKit;
  const { CAT_COLOR, CAT_ORDER } = OK;
  // edge kind → stroke style
  const EDGE_STYLE = {
    spine:      { stroke: "var(--ink-3)", w: 1.6, dash: "", lbl: "var(--accent-lo)" },
    reified:    { stroke: "var(--accent)", w: 1.4, dash: "5 3", lbl: "var(--accent-lo)" },
    ref:        { stroke: "var(--ink-3)", w: 1.3, dash: "3 3", lbl: "var(--ink-4)" },
    actor:      { stroke: "var(--ink-4)", w: 1.2, dash: "", lbl: "var(--ink-4)" },
    provenance: { stroke: "var(--ink-4)", w: 1.1, dash: "1 4", lbl: "var(--ink-4)" },
    struct:     { stroke: "var(--ink-4)", w: 1.2, dash: "4 3", lbl: "var(--ink-4)" },
  };

  // ── fixed layout — the 5-node deal ring with actor / ref / reified
  // satellites and the provenance layer at the bottom ───────────────
  const POS = {
    Company:      { x: 300, y: 110, w: 150, h: 58, shape: "rect", cat: "core" },
    Project:      { x: 620, y: 110, w: 150, h: 58, shape: "rect", cat: "core" },
    Offering:     { x: 872, y: 300, w: 150, h: 58, shape: "rect", cat: "core" },
    Security:     { x: 620, y: 492, w: 150, h: 58, shape: "rect", cat: "core" },
    Asset:        { x: 312, y: 492, w: 150, h: 58, shape: "rect", cat: "core" },
    Document:     { x: 470, y: 638, w: 156, h: 52, shape: "rect", cat: "content" },
    Party:        { x: 92,  y: 286, w: 124, h: 46, shape: "rect", cat: "core" },
    Jurisdiction: { x: 112, y: 612, w: 132, h: 46, shape: "rect", cat: "ref" },
    Chain:        { x: 712, y: 612, w: 120, h: 46, shape: "rect", cat: "ref" },
    "Broker-Dealer": { x: 906, y: 150, w: 150, h: 56, shape: "rect", cat: "core" },
    Holding:      { x: 198, y: 196, w: 96,  h: 58, shape: "diamond", cat: "reified" },
    Valuation:    { x: 168, y: 492, w: 96,  h: 58, shape: "diamond", cat: "reified" },
    Exemption:    { x: 906, y: 462, w: 96,  h: 58, shape: "diamond", cat: "reified" },
  };
  // border-clip: from a node centre toward a target, return the border point
  function clip(id, tx, ty) {
    const n = POS[id]; const dx = tx - n.x, dy = ty - n.y;
    const k = n.shape === "diamond" ? 0.62 : 0.5;
    const hw = n.w * k, hh = n.h * k;
    const sx = dx ? hw / Math.abs(dx) : Infinity, sy = dy ? hh / Math.abs(dy) : Infinity;
    const s = Math.min(sx, sy);
    return { x: n.x + dx * s, y: n.y + dy * s };
  }
  // edges drawn prominently (the full set lives in the edge table)
  const DRAW = [
    { from: "Company",  to: "Project",  kind: "spine", label: "runs" },
    { from: "Project",  to: "Offering", kind: "spine", label: "raises via" },
    { from: "Offering", to: "Security", kind: "spine", label: "sells" },
    { from: "Security", to: "Asset",    kind: "spine", label: "wraps" },
    { from: "Asset",    to: "Company",  kind: "spine", label: "owned by" },
    { from: "Asset",    to: "Project",  kind: "spine", label: "dev. under", faint: true },
    { from: "Valuation", to: "Asset",   kind: "reified", label: "appraises" },
    { from: "Company",  to: "Jurisdiction", kind: "ref", label: "incorp. in" },
    { from: "Asset",    to: "Jurisdiction", kind: "ref", label: "located in" },
    { from: "Security", to: "Chain",    kind: "ref", label: "deployed on" },
    { from: "Offering", to: "Broker-Dealer", kind: "actor", label: "distributed via" },
    { from: "Offering", to: "Exemption", kind: "reified", label: "offered under" },
    { from: "Exemption", to: "Jurisdiction", kind: "ref", label: "scoped to", faint: true },
  ];
  const PROV = ["Company", "Project", "Asset", "Security", "Offering"]; // Document evidences →

  function GraphEdge({ from, to, kind, label, faint }) {
    const st = EDGE_STYLE[kind];
    const a = POS[from], b = POS[to];
    const p1 = clip(from, b.x, b.y), p2 = clip(to, a.x, a.y);
    const mx = (p1.x + p2.x) / 2, my = (p1.y + p2.y) / 2;
    return (
      <g opacity={faint ? 0.5 : 1}>
        <line x1={p1.x} y1={p1.y} x2={p2.x} y2={p2.y} stroke={st.stroke} strokeWidth={st.w} strokeDasharray={st.dash} markerEnd="url(#wm-arrow)"></line>
        {label && <text x={mx} y={my - 4} textAnchor="middle" style={{ fontFamily: "var(--mono)", fontSize: "9.5px", letterSpacing: "0.02em" }} fill={st.lbl}>{label}</text>}
      </g>
    );
  }

  function GraphNode({ id, active, setActive }) {
    const n = POS[id], s = S.subjMeta(id), on = active === id;
    const col = CAT_COLOR[n.cat];
    const count = (s.owns && s.owns.length) ? S.subjCount(s) : 0;
    const sub = n.cat === "reified" ? "reified edge" : n.cat === "ref" ? "ref node" : count ? count + " props" : "actor · role on edge";
    const x0 = n.x - n.w / 2, y0 = n.y - n.h / 2;
    const fill = on ? "var(--accent-bg)" : "var(--bg)";
    const stroke = on ? "var(--accent)" : col;
    const sw = on ? 2 : (n.cat === "ref" ? 1 : 1.4);
    const dash = n.cat === "ref" && !on ? "3 2" : "";
    return (
      <g onClick={() => setActive(on ? null : id)} style={{ cursor: "pointer" }}>
        {n.shape === "diamond"
          ? <polygon points={`${n.x},${y0} ${n.x + n.w / 2},${n.y} ${n.x},${y0 + n.h} ${n.x - n.w / 2},${n.y}`} fill={fill} stroke={stroke} strokeWidth={sw}></polygon>
          : <rect x={x0} y={y0} width={n.w} height={n.h} rx="9" fill={fill} stroke={stroke} strokeWidth={sw} strokeDasharray={dash}></rect>}
        <text x={n.x} y={n.y - 4} textAnchor="middle" style={{ fontFamily: "var(--ui-display)", fontWeight: 700, fontSize: n.shape === "diamond" ? "12.5px" : "15px" }} fill={on ? "var(--accent-lo)" : "var(--ink)"}>
          <tspan style={{ fontFamily: "var(--mono)", fontWeight: 400 }} fill={on ? "var(--accent)" : col}>{s.glyph} </tspan>{id}
        </text>
        <text x={n.x} y={n.y + 12} textAnchor="middle" style={{ fontFamily: "var(--mono)", fontSize: "8px", letterSpacing: "0.08em", textTransform: "uppercase" }} fill="var(--ink-4)">{sub}</text>
      </g>
    );
  }

  function PropertyGraph({ active, setActive }) {
    const d = POS.Document;
    return (
      <svg viewBox="0 0 1000 700" style={{ width: "100%", maxWidth: 1000, height: "auto", display: "block", margin: "0 auto" }}>
        <defs>
          <marker id="wm-arrow" markerWidth="9" markerHeight="9" refX="7.5" refY="4" orient="auto">
            <path d="M0,0 L8,4 L0,8 Z" fill="var(--ink-3)"></path>
          </marker>
        </defs>

        {/* provenance layer — Document evidences every core subject */}
        {PROV.map((t) => {
          const p1 = clip("Document", POS[t].x, POS[t].y), p2 = clip(t, d.x, d.y);
          return <line key={t} x1={p1.x} y1={p1.y} x2={p2.x} y2={p2.y} stroke="var(--ink-4)" strokeWidth="1" strokeDasharray="1 4" opacity="0.6"></line>;
        })}
        <text x={d.x + 96} y={d.y - 2} style={{ fontFamily: "var(--mono)", fontSize: "9px", letterSpacing: "0.04em" }} fill="var(--ink-4)">evidences ↗ all</text>

        {/* Party ⇄ Company through the Holding (reified ownership position) */}
        {(() => {
          const ph = clip("Party", POS.Holding.x, POS.Holding.y), hp = clip("Holding", POS.Party.x, POS.Party.y);
          const hc = clip("Holding", POS.Company.x, POS.Company.y), ch = clip("Company", POS.Holding.x, POS.Holding.y);
          return (
            <g>
              <line x1={ph.x} y1={ph.y} x2={hp.x} y2={hp.y} stroke="var(--ink-4)" strokeWidth="1.2"></line>
              <line x1={hc.x} y1={hc.y} x2={ch.x} y2={ch.y} stroke="var(--ink-4)" strokeWidth="1.2" markerEnd="url(#wm-arrow)"></line>
              <text x={(hc.x + ch.x) / 2 + 6} y={(hc.y + ch.y) / 2 - 3} style={{ fontFamily: "var(--mono)", fontSize: "9px" }} fill="var(--ink-4)">holds</text>
            </g>
          );
        })()}

        {/* Company self-edge — controls (SPV) */}
        {(() => { const c = POS.Company; return (
          <g>
            <path d={`M${c.x - 40},${c.y - c.h / 2} C${c.x - 70},${c.y - c.h / 2 - 38} ${c.x + 10},${c.y - c.h / 2 - 40} ${c.x},${c.y - c.h / 2 - 1}`} fill="none" stroke="var(--ink-4)" strokeWidth="1.2" strokeDasharray="4 3" markerEnd="url(#wm-arrow)"></path>
            <text x={c.x - 34} y={c.y - c.h / 2 - 30} textAnchor="middle" style={{ fontFamily: "var(--mono)", fontSize: "8.5px" }} fill="var(--ink-4)">controls (SPV)</text>
          </g>
        ); })()}

        {/* prominent edges */}
        {DRAW.map((e, i) => <GraphEdge key={i} {...e}></GraphEdge>)}

        {/* nodes on top */}
        {Object.keys(POS).map((id) => <GraphNode key={id} id={id} active={active} setActive={setActive}></GraphNode>)}
      </svg>
    );
  }

  // ── subjects, grouped by category (CAT_ORDER from window.OKit) ─────

  function SubjectCard({ s, active, setActive, reference, cov }) {
    const on = active === s.id;
    const col = CAT_COLOR[s.cat];
    const groups = S.subjGroups(s);
    const count = s.owns.length ? S.subjCount(s) : 0;
    const sc = (!reference && s.owns.length) ? S.subjCoverage(s, cov) : null;
    const out = S.EDGES.filter((e) => e.from === s.id);
    const disc = Object.entries(S.DISCRIMINATOR_VOCAB).filter(([, d]) => d.target === s.id);
    return (
      <div onClick={() => setActive(on ? null : s.id)} style={{ cursor: "pointer", border: "1px solid " + (on ? "var(--accent)" : "var(--rule-hard)"), borderLeft: "3px solid " + (on ? "var(--accent)" : col), borderRadius: "var(--r-3)", background: on ? "var(--accent-bg)" : "var(--bg)", padding: "13px 14px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 9, marginBottom: 7 }}>
          <span style={{ width: 28, height: 28, borderRadius: 7, background: col, color: "#fff", display: "grid", placeItems: "center", fontFamily: "var(--mono)", fontSize: 14, flex: "0 0 auto" }}>{s.glyph}</span>
          <div style={{ flex: 1 }}>
            <div style={{ fontFamily: "var(--ui-display)", fontWeight: 700, fontSize: "var(--fs-base)", color: "var(--ink)", lineHeight: 1.1 }}>{s.id}</div>
            {kgMono(S.SUBJ_CATS[s.cat].label, { marginTop: 2, display: "block" })}
          </div>
          {s.owns.length > 0 && <span style={{ fontFamily: "var(--mono)", fontSize: "9px", letterSpacing: "0.04em", color: "var(--accent-lo)", textAlign: "right", flex: "0 0 auto" }}>{count}<br></br><span style={{ color: "var(--ink-4)" }}>props</span></span>}
        </div>
        <p style={{ margin: "0 0 9px", fontSize: "var(--fs-xs)", color: "var(--ink-3)", lineHeight: 1.5 }}>{s.def}</p>
        {groups.length > 0 && <>
          {kgMono("properties from", { display: "block", marginBottom: 5, color: "var(--ink-4)" })}
          <div style={{ display: "flex", flexWrap: "wrap", gap: 4, marginBottom: sc ? 9 : 0 }}>
            {groups.map((g) => (
              <span key={g.tier + g.group.group} style={{ display: "inline-flex", alignItems: "center", gap: 5, fontFamily: "var(--mono)", fontSize: "8.5px", letterSpacing: "0.01em", color: "var(--ink-2)", border: "1px solid var(--rule)", borderRadius: "var(--r-1)", padding: "2px 7px" }}>
                <span style={{ color: "var(--ink-4)" }}>{S.tierMeta(g.tier).glyph}</span> {g.group.group} <span style={{ color: "var(--ink-4)" }}>{g.group.codes.length}</span>
              </span>
            ))}
          </div>
        </>}
        {disc.length > 0 && (
          <div style={{ marginBottom: sc ? 9 : 0 }}>
            {kgMono("variant selected by", { display: "block", marginBottom: 5, color: "var(--ink-4)" })}
            <div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
              {disc.map(([name, d]) => (
                <span key={name} style={{ display: "inline-flex", alignItems: "center", gap: 5, fontFamily: "var(--mono)", fontSize: "8.5px", color: "var(--accent-lo)", border: "1px solid var(--accent-bd)", background: "var(--accent-bg)", borderRadius: "var(--r-1)", padding: "2px 7px" }}>
                  {name} <span style={{ color: "var(--ink-4)" }}>· {d.code}</span>
                </span>
              ))}
            </div>
          </div>
        )}
        {(!groups.length && s.refs) && <>
          {kgMono("referenced via", { display: "block", marginBottom: 5, color: "var(--ink-4)" })}
          <div style={{ fontFamily: "var(--mono)", fontSize: "9px", color: "var(--ink-3)", lineHeight: 1.6, marginBottom: 4 }}>{s.refs}</div>
        </>}
        {sc && sc.total > 0 && (
          <div style={{ display: "flex", alignItems: "center", gap: 7 }}>
            <span style={{ flex: 1, height: 4, borderRadius: 2, overflow: "hidden", background: "var(--bg-3)", display: "inline-flex" }}>
              <span style={{ width: (sc.verified / sc.total * 100) + "%", background: "var(--ok)" }}></span>
              <span style={{ width: (sc.captured / sc.total * 100) + "%", background: "var(--accent)" }}></span>
            </span>
            <span style={{ fontFamily: "var(--mono)", fontSize: "9px", color: "var(--ink-4)" }}>{sc.verified + sc.captured}/{sc.total}</span>
          </div>
        )}
        {(on && out.length > 0) && (
          <div style={{ display: "flex", flexWrap: "wrap", gap: 4, marginTop: 9, paddingTop: 9, borderTop: "1px solid var(--rule)" }}>
            {out.map((e, i) => (
              <span key={i} style={{ display: "inline-flex", alignItems: "center", gap: 4, fontFamily: "var(--mono)", fontSize: "8px", letterSpacing: "0.02em", color: "var(--ink-3)" }}>
                {e.label} <span style={{ color: "var(--ink-4)" }}>→</span> <span style={{ color: "var(--accent-lo)" }}>{e.to === "*" ? "any" : (S.subjMeta(e.to).glyph + " " + e.to)}</span>
              </span>
            ))}
          </div>
        )}
      </div>
    );
  }

  function KnowledgeGraph({ proj, tenant, go, focus, reference }) {
    const [active, setActive] = useState(null);
    const cov = S.coverage(proj.id, proj.kbFill || 0, reference ? null : (window.AssetClass ? window.AssetClass.of(proj) : null));
    const focusVocab = focus && focus.vocab;
    const vbyName = {}; S.VOCAB_LIST.forEach((v) => (vbyName[v.name] = v));

    return (
      <div>
        <window.PageHead eyebrow={reference ? "Platform · Canonical reference" : tenant.name + " · " + proj.name} title={reference ? "Ontology Reference · World Model" : "Knowledge Graph"}
          sub="The register is properties; the ontology is the subjects those properties are about. Thirteen typed nodes and the edges that bind them — roles ride on edges, attributed relationships are reified, and a vocabulary value becomes a node only when an edge points at it."
          right={
            <div style={{ textAlign: "right" }}>
              {kgMono(S.SUBJECTS.length + " subjects · " + S.EDGES.length + " edges", { display: "block", marginBottom: 4, color: "var(--accent-lo)" })}
              {kgMono(S.meta.total + " properties · " + S.VOCAB_LIST.length + " vocabularies")}
            </div>
          }>
        </window.PageHead>

        {/* property graph */}
        <window.SectionHead sub="the shape of the graph — five core subjects in the deal ring, actors and references as satellites, the provenance layer beneath · click a node to inspect it">Property Graph</window.SectionHead>
        <div style={{ border: "1px solid var(--rule-hard)", borderRadius: "var(--r-3)", background: "var(--bg)", padding: "16px 14px 6px", marginBottom: 8 }}>
          <PropertyGraph active={active} setActive={setActive}></PropertyGraph>
          <OK.CategoryLegend style={{ padding: "6px 0 10px" }}></OK.CategoryLegend>
        </div>

        {/* subjects */}
        <window.SectionHead sub={S.SUBJECTS.length + " typed subjects · most core subjects own a property bundle from the register; an actor like Party anchors the deal with its roles on the edges; reified edges and reference nodes earn their place structurally"}>Subjects</window.SectionHead>
        {CAT_ORDER.map((c) => {
          const subs = S.SUBJECTS.filter((s) => s.cat === c);
          return (
            <div key={c} style={{ marginBottom: 14 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}>
                <OK.CatMark cat={c}></OK.CatMark>
                {kgMono(S.SUBJ_CATS[c].label + " — " + S.SUBJ_CATS[c].note, { color: "var(--ink-3)" })}
              </div>
              <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(290px, 1fr))", gap: 10 }}>
                {subs.map((s) => <SubjectCard key={s.id} s={s} active={active} setActive={setActive} reference={reference} cov={cov}></SubjectCard>)}
              </div>
            </div>
          );
        })}

        {/* the shape — edge table */}
        <window.SectionHead sub={S.EDGES.length + " directed edges · the relationships that give the property graph its shape · register basis where one field resolves the link"}>The Shape · Edges</window.SectionHead>
        <div style={{ border: "1px solid var(--rule-hard)", borderRadius: "var(--r-3)", overflow: "hidden", marginBottom: 22 }}>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1.3fr 1fr 0.9fr", gap: 1, background: "var(--rule)" }}>
            {["Subject", "Edge", "Target", "Basis"].map((h) => (
              <div key={h} style={{ background: "var(--bg-2)", padding: "8px 12px" }}><window.MonoLabel>{h}</window.MonoLabel></div>
            ))}
            {S.EDGES.map((e, i) => {
              const st = EDGE_STYLE[e.kind];
              return (
                <React.Fragment key={i}>
                  <div style={{ background: "var(--bg)", padding: "8px 12px", display: "flex", alignItems: "center", gap: 6, fontFamily: "var(--mono)", fontSize: "10.5px", fontWeight: 600, color: "var(--ink-2)" }}><span style={{ color: CAT_COLOR[S.subjMeta(e.from).cat] }}>{S.subjMeta(e.from).glyph}</span> {e.from}</div>
                  <div style={{ background: "var(--bg)", padding: "8px 12px", display: "flex", alignItems: "center", gap: 7 }}>
                    <span style={{ width: 18, height: 0, borderTop: st.w + "px " + (st.dash ? "dashed" : "solid") + " " + st.stroke, flex: "0 0 auto" }}></span>
                    <span style={{ fontFamily: "var(--mono)", fontSize: "10px", color: "var(--ink-2)" }}>{e.label}</span>
                    {e.via && <span style={{ fontFamily: "var(--mono)", fontSize: "8px", color: "var(--accent-lo)", border: "1px solid var(--accent-bd)", borderRadius: "var(--r-1)", padding: "0 4px" }}>via {e.via}</span>}
                  </div>
                  <div style={{ background: "var(--bg)", padding: "8px 12px", fontFamily: "var(--mono)", fontSize: "10.5px", fontWeight: 600, color: "var(--ink-2)" }}>{e.to === "*" ? <span style={{ color: "var(--ink-4)" }}>any subject</span> : <><span style={{ color: CAT_COLOR[S.subjMeta(e.to).cat] }}>{S.subjMeta(e.to).glyph}</span> {e.to}</>}</div>
                  <div style={{ background: "var(--bg)", padding: "8px 12px", fontFamily: "var(--mono)", fontSize: "9px", color: "var(--ink-4)" }}>{e.basis || "—"}</div>
                </React.Fragment>
              );
            })}
          </div>
        </div>

        {/* controlled vocabularies — classified by structural role */}
        <window.SectionHead sub={"the value-domain layer · " + S.VOCAB_LIST.length + " vocabularies in three roles — only discriminators and dimensions carry structural weight; the rest validate a single field"}>Controlled Vocabularies</window.SectionHead>

        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}>
          <span style={{ width: 9, height: 9, transform: "rotate(45deg)", background: "var(--accent)", flex: "0 0 auto" }}></span>
          {kgMono("Discriminator — selects a subject's variant · it is schema, not data", { color: "var(--ink-3)" })}
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))", gap: 10, marginBottom: 16 }}>
          {Object.keys(S.DISCRIMINATOR_VOCAB).map((name) => {
            const d = S.DISCRIMINATOR_VOCAB[name];
            return (
              <div key={name} style={{ border: "1px solid var(--accent-bd)", borderRadius: "var(--r-2)", background: "var(--bg)", padding: "11px 13px" }}>
                <div style={{ display: "flex", alignItems: "center", gap: 7, marginBottom: 6 }}>
                  <span style={{ fontFamily: "var(--mono)", fontSize: "11px", fontWeight: 600, color: "var(--ink)" }}>{name}</span>
                  <span style={{ fontFamily: "var(--mono)", fontSize: "8.5px", color: "var(--ink-4)" }}>{d.code}</span>
                  <span style={{ marginLeft: "auto", display: "inline-flex", alignItems: "center", gap: 5, fontFamily: "var(--mono)", fontSize: "9px", color: "var(--accent-lo)" }}>→ {d.label} of <span>{S.subjMeta(d.target).glyph} {d.target}</span></span>
                </div>
                <p style={{ margin: 0, fontSize: "var(--fs-xs)", color: "var(--ink-3)", lineHeight: 1.45 }}>{d.note}</p>
                {name === "ASSET_CLASS" && (
                  <React.Fragment>
                  <div style={{ display: "flex", flexWrap: "wrap", gap: 4, marginTop: 8 }}>
                    {S.assetVariants().map((v) => (
                      <span key={v.group} style={{ display: "inline-flex", alignItems: "center", gap: 5, fontFamily: "var(--mono)", fontSize: "8px", color: "var(--ink-2)", border: "1px solid var(--rule)", borderRadius: "var(--r-1)", padding: "2px 6px" }}>
                        {v.label} <span style={{ color: "var(--ink-4)" }}>{v.codes.length}</span>
                      </span>
                    ))}
                  </div>
                  {go && <button onClick={() => go("classes")} style={{ marginTop: 8, border: "1px solid var(--accent-bd)", background: "var(--accent-bg)", color: "var(--accent-lo)", borderRadius: "var(--r-1)", padding: "4px 9px", cursor: "pointer", fontFamily: "var(--mono)", fontSize: "8px", fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase" }}>this is the specification axis · Asset Classes ↗</button>}
                  </React.Fragment>
                )}
              </div>
            );
          })}
        </div>

        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}>
          <span style={{ width: 9, height: 9, borderRadius: 2, background: "var(--ink)", flex: "0 0 auto" }}></span>
          {kgMono("Dimension — shared join axis / external standard · the graph's sliceable pivots", { color: "var(--ink-3)" })}
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(232px, 1fr))", gap: 10, marginBottom: 16 }}>
          {Object.keys(S.DIMENSION_VOCAB).map((name) => {
            const d = S.DIMENSION_VOCAB[name], v = vbyName[name] || { codes: [], tiers: [] };
            return (
              <div key={name} style={{ border: "1px solid var(--rule-hard)", borderRadius: "var(--r-2)", background: "var(--bg)", padding: "11px 13px" }}>
                <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, marginBottom: 6 }}>
                  <span style={{ fontFamily: "var(--mono)", fontSize: "11px", fontWeight: 600, color: "var(--ink)" }}>{name}</span>
                  {d.node
                    ? <span style={{ fontFamily: "var(--mono)", fontSize: "8px", color: "var(--accent-lo)", border: "1px solid var(--accent-bd)", borderRadius: "var(--r-1)", padding: "1px 5px" }}>→ {d.node} node</span>
                    : <span style={{ fontFamily: "var(--mono)", fontSize: "9px", color: "var(--ink-4)" }}>{v.codes.length}×</span>}
                </div>
                <p style={{ margin: 0, fontSize: "var(--fs-xs)", color: "var(--ink-4)", lineHeight: 1.45 }}>{d.note}</p>
              </div>
            );
          })}
        </div>

        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 8 }}>
          <span style={{ width: 9, height: 9, borderRadius: 2, border: "1.5px solid var(--ink-4)", flex: "0 0 auto" }}></span>
          {kgMono("Enum — local field validation · the long tail, no structural weight", { color: "var(--ink-3)" })}
        </div>
        <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
          {S.VOCAB_LIST.filter((v) => v.role === "enum").map((v) => {
            const hot = focusVocab === v.name;
            return (
              <span key={v.name} style={{ display: "inline-flex", alignItems: "center", gap: 5, fontFamily: "var(--mono)", fontSize: "9px", color: hot ? "var(--accent-lo)" : "var(--ink-3)", border: "1px solid " + (hot ? "var(--accent)" : "var(--rule)"), background: hot ? "var(--accent-bg)" : "var(--bg)", borderRadius: "var(--r-1)", padding: "3px 8px" }}>
                {v.name} <span style={{ color: "var(--ink-4)" }}>{v.codes.length}</span>
              </span>
            );
          })}
        </div>

        <p style={{ margin: "14px 0 0", fontFamily: "var(--mono)", fontSize: "9px", letterSpacing: "0.06em", color: "var(--ink-4)", lineHeight: 1.7 }}>
          Derived from {S.meta.title} · {S.meta.version}. Tiers (Issuer · Project · Asset · Offering) are now property domains, not nodes; the per-property schema lives on the Knowledge Base.
        </p>
      </div>
    );
  }

  window.KnowledgeGraph = KnowledgeGraph;
})();
