// Discovery Analysis — report dependency map · APP COMPONENT
// Consumes globals from discovery-dep-map-data.jsx.

const { useState, useMemo, useEffect } = React;
const {
  NODES, EDGES, POS, HORIZ_PAIRS, PHASES, SUB_DIVIDERS,
  CLT, CDK, KIND_LABEL, APP_KINDS,
  ORIGIN_LABEL, STAGE_LABEL, STATUS_LABEL,
  COLOR_LEGEND, ORIGIN_LEGEND,
  PARENTS, CHILDREN, EDGE_TYPE, traverse,
} = window;

// ════════════════════════════════════════════════════════════════════════════
// Small UI helpers
// ════════════════════════════════════════════════════════════════════════════

function ApprovedBadge({ col, size = 14 }) {
  const r = size / 2;
  return (
    <g>
      <circle cx={r} cy={r} r={r} fill={col}/>
      <path d={`M${r*0.5} ${r} L${r*0.9} ${r*1.4} L${r*1.55} ${r*0.6}`}
        fill="none" stroke="white" strokeWidth={size*0.18}
        strokeLinecap="round" strokeLinejoin="round"/>
    </g>
  );
}

function Section({ label, color, children, style }) {
  return (
    <div style={{ ...(style || {}) }}>
      <div style={{
        fontSize: 9, fontWeight: 600, letterSpacing: "0.08em",
        textTransform: "uppercase", color, marginBottom: 4,
      }}>{label}</div>
      {children}
    </div>
  );
}

function Pill({ children, fill, border, text, onClick, dashed, small }) {
  return (
    <span onClick={onClick} style={{
      fontSize: small ? 10 : 11,
      padding: small ? "1px 7px" : "2px 9px",
      borderRadius: 20,
      background: fill,
      border: `${dashed ? "1px dashed" : "1px solid"} ${border}`,
      color: text,
      cursor: onClick ? "pointer" : "default",
      whiteSpace: "nowrap",
      display: "inline-flex",
      alignItems: "center",
      gap: 4,
    }}>{children}</span>
  );
}

function MetaBadge({ label, value, col, dark }) {
  return (
    <span style={{
      display: "inline-flex", alignItems: "center", gap: 5,
      fontSize: 10, padding: "2px 8px", borderRadius: 4,
      background: col.s + (dark ? "22" : "12"),
      border: `1px solid ${col.s}33`,
      color: col.t, whiteSpace: "nowrap",
    }}>
      <span style={{
        fontSize: 8.5, fontWeight: 600, letterSpacing: "0.08em",
        textTransform: "uppercase", opacity: 0.7, color: col.u,
      }}>{label}</span>
      <span style={{ fontWeight: 500 }}>{value}</span>
    </span>
  );
}

function ParentPills({ ids, edgeKind, label, sel, C, setSel, color }) {
  if (!ids.length) return null;
  return (
    <Section label={label} color={color}>
      <div style={{ display: "flex", flexWrap: "wrap", gap: 5 }}>
        {ids.map(p => {
          const node = NODES[p];
          const pc = C[node.col];
          return (
            <Pill key={p} fill={pc.f} border={pc.s} text={pc.t}
              dashed={edgeKind === "ctx"}
              onClick={() => setSel(p)}>
              {node.label}
            </Pill>
          );
        })}
      </div>
    </Section>
  );
}

// ════════════════════════════════════════════════════════════════════════════
// App
// ════════════════════════════════════════════════════════════════════════════

function App() {
  const [sel, setSel] = useState(null);
  const [hov, setHov] = useState(null);
  const [dir, setDir] = useState("up");         // "up" | "down"
  const [primOnly, setPrimOnly] = useState(false);
  const [dark, setDark] = useState(() =>
    typeof window !== "undefined" ? window.matchMedia("(prefers-color-scheme: dark)").matches : false
  );

  useEffect(() => {
    const mq = window.matchMedia("(prefers-color-scheme: dark)");
    const fn = e => setDark(e.matches);
    mq.addEventListener("change", fn);
    return () => mq.removeEventListener("change", fn);
  }, []);

  const C = dark ? CDK : CLT;

  // Trace set
  const trace = useMemo(() => sel ? traverse(sel, dir) : null, [sel, dir]);
  const active = useMemo(() => trace ? new Set([...trace, sel]) : null, [trace, sel]);
  const activeE = useMemo(() => {
    if (!active) return null;
    return new Set(EDGES.filter(([s, t]) =>
      active.has(s) && active.has(t)
    ).map(([s, t]) => `${s}-${t}`));
  }, [active]);

  // Selection-derived
  const selNode  = sel ? NODES[sel] : null;
  const selCol   = selNode ? C[selNode.col] : null;
  const parents  = sel ? (PARENTS[sel]  || []) : [];
  const children = sel ? (CHILDREN[sel] || []) : [];
  const groupedParents = useMemo(() => {
    const groups = { primary: [], ctx: [] };
    parents.forEach(p => {
      const t = EDGE_TYPE[`${p}-${sel}`] || "ctx";
      (groups[t] || groups.ctx).push(p);
    });
    return groups;
  }, [parents, sel]);

  // Theme tokens
  const ink      = dark ? "#ECECEA" : "#1c1c1a";
  const muted    = dark ? "#888780" : "#7a7975";
  const dimF     = dark ? "#1c1c1b" : "#EDECEA";
  const dimS     = dark ? "#36352f" : "#D8D6D0";
  const dimT     = dark ? "#3a3a37" : "#C8C6BF";
  const edgeDef  = dark ? "#484845" : "#C8C6BF";
  const sepClr   = dark ? "#2a2a27" : "#E8E6E0";
  const phaseLbl = dark ? "#5a5853" : "#A8A69C";
  const phaseBg  = (i) => i % 2
    ? (dark ? "#1c1c1b" : "#F6F4ED")
    : (dark ? "#161614" : "#FAFAF6");

  return (
    <div style={{ padding: 16, fontFamily: "inherit", color: ink }}>

      {/* ─── Title ────────────────────────────────────────────────── */}
      <div style={{ marginBottom: 14 }}>
        <div style={{
          fontSize: 18, fontWeight: 600, letterSpacing: "-0.01em",
          marginBottom: 4, color: ink,
        }}>
          Discovery Analysis · Report Dependency Map
        </div>
        <div style={{ fontSize: 12, color: muted, lineHeight: 1.5, maxWidth: 760 }}>
          From source data through workshop artifacts to the final executive brief — how the eight Discovery reports depend on each other.
        </div>
      </div>

      {/* How to read this map — visual legend, placed up top for first-time readers */}
      <HowToRead C={C} dark={dark} muted={muted} sepClr={sepClr}/>

      {/* ─── Detail panel ────────────────────────────────────────── */}
      <div style={{
        borderRadius: 10, marginBottom: 12, padding: "14px 16px",
        border: `1px solid ${sel ? selCol.s + "55" : (dark ? "#2a2a27" : "#E8E6E0")}`,
        background: sel ? selCol.f + (dark ? "30" : "20") : "transparent",
        transition: "border-color 0.2s, background 0.2s",
        minHeight: 90,
      }}>
        {sel ? (
          <DetailPanel
            id={sel} node={selNode} col={selCol} C={C} dark={dark}
            trace={trace} dir={dir}
            groupedParents={groupedParents}
            childrenIds={children}
            setSel={setSel}
          />
        ) : (
          <EmptyState muted={muted} dir={dir}/>
        )}
      </div>

      {/* ─── Toolbar ──────────────────────────────────────────────── */}
      <div style={{
        display: "flex", gap: 14, alignItems: "center", flexWrap: "wrap",
        fontSize: 11, color: muted, marginBottom: 10,
      }}>
        <div style={{ display: "flex", gap: 4, alignItems: "center" }}>
          <span style={{ marginRight: 4 }}>Trace:</span>
          {[
            { v: "up",   label: "upstream"   },
            { v: "down", label: "downstream" },
          ].map(o => (
            <button key={o.v}
              onClick={() => setDir(o.v)}
              style={{
                fontSize: 11, padding: "3px 9px", borderRadius: 20,
                border: `1px solid ${dir === o.v ? (selCol?.s || muted) : (dark ? "#36352f" : "#D8D6D0")}`,
                background: dir === o.v ? (selCol?.f || (dark ? "#1c1c1b" : "#fff")) : "transparent",
                color: dir === o.v ? (selCol?.t || ink) : muted,
                cursor: "pointer", fontFamily: "inherit",
              }}>{o.label}</button>
          ))}
        </div>

        <label style={{ display: "flex", gap: 5, alignItems: "center", cursor: "pointer" }}>
          <input type="checkbox" checked={primOnly}
            onChange={e => setPrimOnly(e.target.checked)}
            style={{ accentColor: "#534AB7" }}/>
          <span>primary edges only</span>
        </label>

        {sel && (
          <button onClick={() => setSel(null)} style={{
            marginLeft: "auto", fontSize: 11, padding: "3px 9px",
            borderRadius: 20, border: `1px solid ${dark ? "#36352f" : "#D8D6D0"}`,
            background: "transparent", color: muted, cursor: "pointer", fontFamily: "inherit",
          }}>clear selection ✕</button>
        )}
      </div>

      {/* ─── SVG diagram ──────────────────────────────────────────── */}
      <svg width="100%" viewBox="0 0 720 830" style={{ display: "block" }}
        aria-label="Discovery analysis report dependency map">

        <defs>
          <marker id="ar" viewBox="0 0 10 10" refX="8" refY="5"
            markerWidth="5" markerHeight="5" orient="auto-start-reverse">
            <path d="M2 1L8 5L2 9" fill="none" stroke="context-stroke"
              strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
          </marker>
        </defs>

        {/* Phase swimlane backgrounds */}
        {PHASES.map((p, i) => (
          <rect key={p.label} x={0} y={p.y0} width={720} height={p.y1 - p.y0}
            fill={phaseBg(i)} opacity={0.55}/>
        ))}

        {/* Phase strip — rotated labels on left edge */}
        {PHASES.map((p) => {
          const cy = (p.y0 + p.y1) / 2;
          return (
            <g key={p.label}>
              <line x1={48} y1={p.y0 + 6} x2={48} y2={p.y1 - 6}
                stroke={phaseLbl} strokeWidth={1} opacity={0.5}/>
              <text x={28} y={cy} textAnchor="middle" dominantBaseline="central"
                transform={`rotate(-90 28 ${cy})`}
                fontSize={9.5} fontWeight={600} letterSpacing="0.14em"
                fill={phaseLbl} style={{ fontFamily: "inherit" }}>
                {p.label}
              </text>
            </g>
          );
        })}

        {/* Tier dividers */}
        {SUB_DIVIDERS.map(y => (
          <line key={y} x1={60} y1={y} x2={700} y2={y}
            stroke={sepClr} strokeWidth={1} strokeDasharray="2 6"/>
        ))}

        {/* Edges */}
        {EDGES.map(([s, t, type]) => {
          if (primOnly && type !== "primary") return null;

          const sn = POS[s], tn = POS[t], key = `${s}-${t}`;
          const isHoriz = HORIZ_PAIRS.has(key);
          const isAct = activeE ? activeE.has(key) : false;
          const col = C[NODES[s].col];

          let d, x1, y1, x2, y2;
          if (isHoriz) {
            x1 = sn.x + sn.w; y1 = sn.y + sn.h / 2;
            x2 = tn.x;        y2 = tn.y + tn.h / 2;
            d  = `M${x1} ${y1} L${x2} ${y2}`;
          } else {
            // Spread incoming attachment points along K's and J's top edge
            if (t === "K" && type === "primary") {
              const order = ["D","E","F","G","H","I","J"];
              const idx = order.indexOf(s);
              const step = (tn.w - 60) / (order.length - 1);
              x2 = tn.x + 30 + step * idx; y2 = tn.y;
            } else if (t === "J") {
              const incoming = ["D","G","H","I"];
              const idx = incoming.indexOf(s);
              if (idx >= 0) {
                const step = (tn.w - 40) / (incoming.length - 1);
                x2 = tn.x + 20 + step * idx;
              } else { x2 = tn.cx; }
              y2 = tn.y;
            } else if (t === "I") {
              const incoming = ["D","F","G","H","W1"];
              const idx = incoming.indexOf(s);
              if (idx >= 0) {
                const step = (tn.w - 40) / (incoming.length - 1);
                x2 = tn.x + 20 + step * idx;
              } else { x2 = tn.cx; }
              y2 = tn.y;
            } else {
              x2 = tn.cx; y2 = tn.y;
            }
            x1 = sn.cx; y1 = sn.y + sn.h;
            const ym = (y1 + y2) / 2;
            d = `M${x1} ${y1} C${x1} ${ym} ${x2} ${ym} ${x2} ${y2}`;
          }

          let strokeWidth, dash, op;
          if (type === "primary") {
            strokeWidth = isAct ? 2.2 : 1.4;
            dash = undefined;
            op   = activeE ? (isAct ? 0.92 : 0.05) : 0.55;
          } else { // ctx
            strokeWidth = isAct ? 1.5 : 0.9;
            dash = "3 4";
            op   = activeE ? (isAct ? 0.7 : 0.05) : 0.25;
          }

          return (
            <path key={key} d={d}
              fill="none"
              stroke={isAct ? col.h : edgeDef}
              strokeWidth={strokeWidth}
              strokeDasharray={dash}
              opacity={op}
              markerEnd="url(#ar)"/>
          );
        })}

        {/* Nodes */}
        {Object.entries(POS).map(([id, pos]) => {
          const n = NODES[id], col = C[n.col];
          const isAct = !active || active.has(id);
          const isSel = id === sel;
          const isHov = id === hov;
          const isWorkshop = n.kind === "workshopRound";
          const rx = isWorkshop ? 22 : 8;
          const dash = isWorkshop ? "5 3" : undefined;
          return (
            <g key={id} style={{ cursor: "pointer" }}
              onClick={() => setSel(prev => prev === id ? null : id)}
              onMouseEnter={() => setHov(id)}
              onMouseLeave={() => setHov(null)}>

              <rect x={pos.x} y={pos.y} width={pos.w} height={pos.h} rx={rx}
                fill={isAct ? col.f : dimF}
                stroke={isAct ? col.s : dimS}
                strokeDasharray={dash}
                strokeWidth={isSel ? 2 : isHov && isAct ? 1.4 : (isWorkshop ? 1 : 0.7)}
                opacity={isAct ? 1 : 0.32}/>

              <text x={pos.cx} y={pos.y + pos.h / 2 - 9}
                textAnchor="middle" dominantBaseline="central"
                fontSize={12.5} fontWeight={500}
                fill={isAct ? col.t : dimT}
                style={{ fontFamily: "inherit", userSelect: "none" }}>
                {n.label}
              </text>
              <text x={pos.cx} y={pos.y + pos.h / 2 + 10}
                textAnchor="middle" dominantBaseline="central"
                fontSize={10.5} fill={isAct ? col.u : dimT}
                style={{ fontFamily: "inherit", userSelect: "none" }}>
                {n.sub}
              </text>

              {/* Facilitator-approved badge */}
              {n.approved && isAct && (
                <g transform={`translate(${pos.x + pos.w - 22}, ${pos.y + 6})`}
                  style={{ pointerEvents: "none" }}>
                  <ApprovedBadge col={col.s} size={14}/>
                </g>
              )}
            </g>
          );
        })}
      </svg>

      {/* ─── Origin reference (visual legend is at top via HowToRead) ─── */}
      <div style={{
        paddingTop: 12, marginTop: 4,
        borderTop: `1px dashed ${sepClr}`,
        fontSize: 10.5, color: muted,
        display: "flex", gap: 14, flexWrap: "wrap", alignItems: "center",
      }}>
        <span style={{
          fontSize: 9, fontWeight: 600, letterSpacing: "0.08em",
          textTransform: "uppercase", opacity: 0.7,
        }}>Origin (dot color on full diagram)</span>
        {ORIGIN_LEGEND.map(({ v, label, light, dark: dCol }) => (
          <span key={v} style={{ display: "flex", alignItems: "center", gap: 5 }}>
            <span style={{
              width: 8, height: 8, borderRadius: 8,
              background: dark ? dCol : light,
            }}/>
            {label}
          </span>
        ))}
      </div>
    </div>
  );
}

// ════════════════════════════════════════════════════════════════════════════
// Sub-components
// ════════════════════════════════════════════════════════════════════════════

// Top-of-page visual legend: actual mini-shape previews so a first-time reader
// can map shape + color → meaning before they start clicking around.
function HowToRead({ C, dark, muted, sepClr }) {
  const ink2 = dark ? "#C4C2BC" : "#3a3937";

  // Mini node sample — same geometry/styling rules as nodes on the diagram itself.
  const NodeSample = ({ col, kind, headline, sub, approved }) => {
    const palette = C[col];
    const isWorkshop = kind === "workshop";
    return (
      <div style={{ display: "flex", flexDirection: "column", alignItems: "flex-start", gap: 6, maxWidth: 150 }}>
        <svg width="140" height="38" viewBox="0 0 140 38" style={{ display: "block" }}>
          <rect x={1} y={1} width={138} height={36}
            rx={isWorkshop ? 18 : 6}
            fill={palette.f}
            stroke={palette.s}
            strokeDasharray={isWorkshop ? "5 3" : undefined}
            strokeWidth={1}/>
          <text x={70} y={19} textAnchor="middle" dominantBaseline="central"
            fontSize={11} fontWeight={500} fill={palette.t}
            style={{ fontFamily: "inherit" }}>{headline}</text>
          {approved && (
            <g transform="translate(120, 5)">
              <circle cx={5} cy={5} r={5} fill={palette.s}/>
              <path d="M2.6 5 L4.5 7 L7.4 3" fill="none" stroke="white"
                strokeWidth={1.4} strokeLinecap="round" strokeLinejoin="round"/>
            </g>
          )}
        </svg>
        <span style={{ fontSize: 10.5, color: ink2, lineHeight: 1.35 }}>{sub}</span>
      </div>
    );
  };

  return (
    <div style={{
      border: `1px dashed ${sepClr}`,
      borderRadius: 10,
      padding: "12px 14px 10px",
      marginBottom: 14,
      background: dark ? "#1a1a18" : "#FBFAF6",
    }}>
      <div style={{
        fontSize: 9.5, fontWeight: 700, letterSpacing: "0.1em",
        textTransform: "uppercase", color: muted, marginBottom: 10,
      }}>How to read this map</div>

      {/* Node sample row */}
      <div style={{
        display: "flex", flexWrap: "wrap", gap: 18,
        alignItems: "flex-start", marginBottom: 12,
      }}>
        <NodeSample col="teal"   kind="app"
          headline="Data input"
          sub="Source data fed into the app."/>
        <NodeSample col="purple" kind="app"
          headline="Input report"
          sub="App-drafted from source data; facilitator-reviewed."/>
        <NodeSample col="amber"  kind="workshop"
          headline="Workshop round"
          sub="In-person activity. Outside the app unless artifacts are captured."/>
        <NodeSample col="coral"  kind="app" approved
          headline="Analytic report"
          sub="App-drafted from workshop artifacts. ✓ = facilitator-approved."/>
        <NodeSample col="gray"   kind="app"
          headline="Final report"
          sub="Assembled executive brief; exported."/>
      </div>

      {/* Shape + edge rules */}
      <div style={{
        display: "flex", flexWrap: "wrap", gap: 18,
        alignItems: "center",
        paddingTop: 10,
        borderTop: `1px dashed ${sepClr}`,
        fontSize: 11, color: ink2,
      }}>
        <span style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <svg width="22" height="12" viewBox="0 0 22 12">
            <rect x={1} y={1} width={20} height={10} rx={2} fill="none" stroke={ink2} strokeWidth={1}/>
          </svg>
          <span><strong style={{ fontWeight: 600 }}>Solid rectangle</strong> — lives inside the app</span>
        </span>
        <span style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <svg width="32" height="12" viewBox="0 0 32 12">
            <rect x={1} y={1} width={30} height={10} rx={5} fill="none"
              stroke={ink2} strokeWidth={1} strokeDasharray="5 3"/>
          </svg>
          <span><strong style={{ fontWeight: 600 }}>Dashed pill</strong> — happens in person, outside the app</span>
        </span>
        <span style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <svg width="38" height="10" viewBox="0 0 38 10">
            <line x1="0" y1="5" x2="30" y2="5" stroke={ink2} strokeWidth={1.8}/>
            <path d="M30 2 L35 5 L30 8" fill="none" stroke={ink2} strokeWidth={1.8}
              strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
          <span><strong style={{ fontWeight: 600 }}>Solid arrow</strong> — primary input (direct trigger)</span>
        </span>
        <span style={{ display: "flex", alignItems: "center", gap: 6 }}>
          <svg width="38" height="10" viewBox="0 0 38 10">
            <line x1="0" y1="5" x2="30" y2="5" stroke={ink2} strokeWidth={1.3}
              strokeDasharray="3 3"/>
            <path d="M30 2 L35 5 L30 8" fill="none" stroke={ink2} strokeWidth={1.3}
              strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
          <span><strong style={{ fontWeight: 600 }}>Dashed arrow</strong> — contextual reference</span>
        </span>
        <span style={{ marginLeft: "auto", fontSize: 11, color: muted }}>
          Click any node to trace its full upstream chain.
        </span>
      </div>
    </div>
  );
}

function EdgeKey({ label, color, kind }) {
  const dash = kind === "primary" ? undefined : "2 3";
  const w = kind === "primary" ? 2 : 1.2;
  return (
    <span style={{ display: "flex", alignItems: "center", gap: 5 }}>
      <svg width="26" height="8">
        <line x1="0" y1="4" x2="26" y2="4" stroke={color}
          strokeWidth={w} strokeDasharray={dash}/>
      </svg>
      {label}
    </span>
  );
}

function EmptyState({ muted, dir }) {
  return (
    <div style={{ fontSize: 12, color: muted, lineHeight: 1.55 }}>
      <div style={{ marginBottom: 6 }}>
        <strong style={{ color: muted, fontWeight: 600 }}>Click any node</strong>
        {" "}to inspect — origin, stage, status, what it generates, and a {dir === "up" ? "trace of every upstream input" : "trace of everything it feeds"}.
      </div>
      <div style={{ fontSize: 11 }}>
        Use the toolbar below to switch trace direction or hide contextual edges.
      </div>
    </div>
  );
}

function DetailPanel({ id, node, col, C, dark, trace, dir, groupedParents, childrenIds, setSel }) {
  return (
    <React.Fragment>
      {/* Title row */}
      <div style={{ display: "flex", alignItems: "baseline", gap: 8, marginBottom: 7, flexWrap: "wrap" }}>
        <span style={{ fontSize: 14, fontWeight: 600, color: col.t, lineHeight: 1.3 }}>
          {node.full}
        </span>
        {node.approved && (
          <span style={{ fontSize: 10, color: col.u, fontWeight: 600 }}>
            ✓ facilitator-approved
          </span>
        )}
        {trace && trace.size > 0 && (
          <span style={{
            marginLeft: "auto", fontSize: 10, padding: "1px 8px", borderRadius: 20,
            background: col.s + "22", color: col.u, whiteSpace: "nowrap",
          }}>
            {trace.size} {dir === "up" ? "upstream" : "downstream"}
          </span>
        )}
      </div>

      {/* Metadata badges */}
      <div style={{ display: "flex", gap: 6, marginBottom: 10, flexWrap: "wrap" }}>
        <MetaBadge label="Origin" value={ORIGIN_LABEL[node.origin] || "—"}  col={col} dark={dark}/>
        <MetaBadge label="Stage"  value={STAGE_LABEL[node.stage]   || "—"}  col={col} dark={dark}/>
        <MetaBadge label="Status" value={STATUS_LABEL[node.status] || "—"}  col={col} dark={dark}/>
        <MetaBadge label="Type"   value={KIND_LABEL[node.kind]     || "—"}  col={col} dark={dark}/>
      </div>

      {/* Summary */}
      {node.summary && (
        <div style={{ fontSize: 12, color: col.t, fontWeight: 500, marginBottom: 6, lineHeight: 1.5 }}>
          {node.summary}
        </div>
      )}

      {/* Description */}
      <div style={{ fontSize: 12, color: col.t, lineHeight: 1.6, marginBottom: 12, opacity: 0.92 }}>
        {node.desc}
      </div>

      {/* 2-column body */}
      <div style={{
        display: "grid",
        gridTemplateColumns: (node.outputFormat || node.whatHappens) && node.keyFields ? "1fr 1.1fr" : "1fr",
        gap: 16, marginBottom: 12, alignItems: "start",
      }}>
        {/* Left: key fields */}
        {node.keyFields && (
          <Section label="Key fields" color={col.u}>
            <ul style={{ margin: 0, padding: 0, listStyle: "none" }}>
              {node.keyFields.map((kf, i) => (
                <li key={i} style={{
                  fontSize: 11, color: col.t, lineHeight: 1.55,
                  paddingLeft: 12, position: "relative", marginBottom: 2,
                }}>
                  <span style={{
                    position: "absolute", left: 0, top: 7,
                    width: 4, height: 4, borderRadius: 4,
                    background: col.u, opacity: 0.6,
                  }}/>
                  {kf}
                </li>
              ))}
            </ul>
          </Section>
        )}

        {/* Right: output format OR what happens (workshop) */}
        {node.outputFormat ? (
          <Section label="Output format" color={col.u}>
            <FormatRow k="Visual"    v={node.outputFormat.visual}    col={col}/>
            <FormatRow k="Narrative" v={node.outputFormat.narrative} col={col}/>
            <FormatRow k="Export"    v={node.outputFormat.export}    col={col}/>
          </Section>
        ) : (
          node.whatHappens && (
            <Section label="What happens" color={col.u}>
              <div style={{ fontSize: 11, color: col.t, lineHeight: 1.55 }}>
                {node.whatHappens}
              </div>
            </Section>
          )
        )}
      </div>

      {/* Generates */}
      {node.generates && (
        <div style={{ display: "flex", gap: 6, alignItems: "center", marginBottom: 10, flexWrap: "wrap" }}>
          <span style={{
            fontSize: 9, fontWeight: 600, letterSpacing: "0.08em",
            textTransform: "uppercase", color: col.u,
          }}>
            generates
          </span>
          <span style={{
            fontSize: 11, padding: "2px 9px", borderRadius: 4,
            background: col.s + "22", color: col.t, border: `1px solid ${col.s}44`,
          }}>
            {node.generates}
          </span>
        </div>
      )}

      {/* Fed by + Feeds into */}
      <div style={{ display: "grid", gap: 8 }}>
        <ParentPills ids={groupedParents.primary} edgeKind="primary"
          label="Fed by · primary input"
          sel={id} C={C} setSel={setSel} color={col.u}/>
        <ParentPills ids={groupedParents.ctx} edgeKind="ctx"
          label="Fed by · contextual reference"
          sel={id} C={C} setSel={setSel} color={col.u}/>
        {childrenIds.length > 0 && (
          <Section label="Feeds into" color={col.u}>
            <div style={{ display: "flex", flexWrap: "wrap", gap: 5 }}>
              {childrenIds.map(ch => {
                const n2 = NODES[ch];
                const pc = C[n2.col];
                return (
                  <Pill key={ch} fill={pc.f} border={pc.s} text={pc.t}
                    onClick={() => setSel(ch)}>
                    {n2.label}
                  </Pill>
                );
              })}
            </div>
          </Section>
        )}
      </div>
    </React.Fragment>
  );
}

function FormatRow({ k, v, col }) {
  return (
    <div style={{ fontSize: 11, color: col.t, lineHeight: 1.55, display: "flex", gap: 6, marginBottom: 2 }}>
      <span style={{ flex: "0 0 56px", color: col.u, fontWeight: 500 }}>{k}</span>
      <span style={{ flex: 1 }}>{v}</span>
    </div>
  );
}

// ════════════════════════════════════════════════════════════════════════════

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
