// parts.jsx — reusable bits: code block w/ copy, diagrams, expandable concept,
// tab bar. Globals attached at the bottom for app.jsx to consume.

const { useState, useEffect, useRef, useCallback } = React;

// ----- CopyButton -------------------------------------------------------------
function CopyButton({ text, label = "copy", accent = "var(--ink)" }) {
  const [state, setState] = useState("idle"); // idle | ok | err
  const t = useRef(null);
  const onClick = useCallback(async () => {
    try {
      await navigator.clipboard.writeText(text);
      setState("ok");
    } catch {
      // Fallback for non-secure contexts (older deployments)
      try {
        const ta = document.createElement("textarea");
        ta.value = text;
        ta.style.position = "fixed";
        ta.style.opacity = "0";
        document.body.appendChild(ta);
        ta.select();
        document.execCommand("copy");
        document.body.removeChild(ta);
        setState("ok");
      } catch {
        setState("err");
      }
    }
    clearTimeout(t.current);
    t.current = setTimeout(() => setState("idle"), 1400);
  }, [text]);
  useEffect(() => () => clearTimeout(t.current), []);
  return (
    <button
      onClick={onClick}
      style={{
        display: "inline-flex", alignItems: "center", gap: 6,
        padding: "5px 10px", borderRadius: 999,
        border: "1px solid var(--rule-strong)", background: "var(--paper)",
        color: state === "ok" ? accent : "var(--ink-soft)",
        fontSize: 11, letterSpacing: "0.04em", textTransform: "uppercase",
        fontWeight: 600, cursor: "pointer", lineHeight: 1,
        transition: "color .15s, border-color .15s, transform .1s",
      }}
      onMouseDown={(e) => (e.currentTarget.style.transform = "scale(0.97)")}
      onMouseUp={(e) => (e.currentTarget.style.transform = "")}
      onMouseLeave={(e) => (e.currentTarget.style.transform = "")}
      aria-label={state === "ok" ? "Copied" : "Copy to clipboard"}
    >
      <span style={{ width: 12, height: 12, display: "inline-block", position: "relative" }}>
        {state === "ok" ? (
          <svg viewBox="0 0 12 12" width="12" height="12"><path d="M2.5 6.5 L5 9 L9.5 3.5" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"/></svg>
        ) : (
          <svg viewBox="0 0 12 12" width="12" height="12"><rect x="3.5" y="3.5" width="6" height="7" rx="1" fill="none" stroke="currentColor" strokeWidth="1.2"/><rect x="2" y="1.5" width="6" height="7" rx="1" fill="none" stroke="currentColor" strokeWidth="1.2"/></svg>
        )}
      </span>
      {state === "ok" ? "copied" : state === "err" ? "failed" : label}
    </button>
  );
}

// ----- CodeBlock --------------------------------------------------------------
function CodeBlock({ code, accent = "var(--ink)", small = false, label }) {
  return (
    <div style={{
      position: "relative", borderRadius: 10, overflow: "hidden",
      background: "#1c1814", color: "#e8dfce",
      border: "1px solid rgba(0,0,0,0.2)",
      boxShadow: "0 1px 0 rgba(255,255,255,0.6) inset",
    }}>
      <div style={{
        display: "flex", alignItems: "center", justifyContent: "space-between",
        padding: "8px 10px 8px 14px",
        borderBottom: "1px solid rgba(255,255,255,0.06)",
        background: "rgba(255,255,255,0.02)",
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <span style={{ width: 7, height: 7, borderRadius: "50%", background: accent, opacity: 0.9 }} />
          <span style={{ fontSize: 10, letterSpacing: "0.1em", textTransform: "uppercase", color: "rgba(232,223,206,0.55)", fontFamily: "Geist Mono, monospace" }}>
            {label || "shell"}
          </span>
        </div>
        <CopyButton text={code} accent={accent} />
      </div>
      <pre style={{
        margin: 0, padding: small ? "12px 14px" : "14px 16px",
        fontFamily: "Geist Mono, monospace", fontSize: small ? 12.5 : 13.5,
        lineHeight: 1.55, whiteSpace: "pre", overflowX: "auto",
      }}>
        <code>{highlightShell(code)}</code>
      </pre>
    </div>
  );
}

// Minimal shell highlighter — keeps prose comments dim, commands bright.
function highlightShell(src) {
  const lines = src.split("\n");
  return lines.map((line, i) => {
    const nl = i < lines.length - 1 ? "\n" : "";
    if (/^\s*#/.test(line)) {
      return <span key={i} style={{ color: "rgba(232,223,206,0.45)", fontStyle: "italic" }}>{line}{nl}</span>;
    }
    // split inline trailing comment
    const m = line.match(/^(.*?)(\s+#\s.*)$/);
    if (m) {
      return (
        <span key={i}>
          <span>{tokens(m[1])}</span>
          <span style={{ color: "rgba(232,223,206,0.45)", fontStyle: "italic" }}>{m[2]}</span>
          {nl}
        </span>
      );
    }
    return <span key={i}>{tokens(line)}{nl}</span>;
  });
}
function tokens(s) {
  // highlight first word as command keyword
  const m = s.match(/^(\s*)(\S+)(.*)$/);
  if (!m) return s;
  return <>
    {m[1]}
    <span style={{ color: "#f7c873", fontWeight: 600 }}>{m[2]}</span>
    <span>{m[3]}</span>
  </>;
}

// ----- LaneBadge --------------------------------------------------------------
function LaneBadge({ n, accent, accentBg, accentInk, size = 38 }) {
  return (
    <div style={{
      width: size, height: size, borderRadius: "50%",
      background: accentBg, color: accentInk,
      display: "grid", placeItems: "center",
      border: `1.5px solid ${accent}`,
      fontWeight: 700, fontSize: size * 0.5,
      fontFamily: "Bricolage Grotesque, sans-serif",
      flex: "0 0 auto",
    }}>{n}</div>
  );
}

// ----- LaneChip (clickable, used in matrix + flowchart) -----------------------
function LaneChip({ lane, onClick }) {
  const L = window.CHEAT.LANES[lane - 1];
  return (
    <button
      onClick={() => onClick(L.id)}
      style={{
        display: "inline-flex", alignItems: "center", gap: 8,
        padding: "5px 10px 5px 5px", borderRadius: 999,
        border: `1.5px solid ${L.accent}`, background: L.accentBg,
        color: L.accentInk, fontWeight: 600, fontSize: 13,
        cursor: "pointer", lineHeight: 1,
        transition: "transform .12s, box-shadow .12s",
      }}
      onMouseEnter={(e) => { e.currentTarget.style.transform = "translateY(-1px)"; e.currentTarget.style.boxShadow = `0 4px 14px -6px ${L.accent}`; }}
      onMouseLeave={(e) => { e.currentTarget.style.transform = ""; e.currentTarget.style.boxShadow = ""; }}
    >
      <span style={{
        width: 22, height: 22, borderRadius: "50%",
        background: L.accent, color: "white",
        display: "grid", placeItems: "center",
        fontWeight: 700, fontSize: 12,
      }}>{L.n}</span>
      Lane {L.n} · {L.short}
      <span style={{ fontSize: 14, marginLeft: 2, opacity: 0.7 }}>→</span>
    </button>
  );
}

// ----- Flowchart (custom SVG, not Mermaid) -----------------------------------
function Flowchart({ goto }) {
  // Pure SVG, no foreignObject — keeps it crisp at any size.
  // Nodes: A (start), B (trivial?), L1, D (parallel?), L2, E (CI/review?), G (done?), L3.
  const W = 560, H = 360;
  const L = window.CHEAT.LANES;
  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: "block" }} role="img" aria-label="Lane decision flowchart">
      <defs>
        <marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
          <path d="M0,0 L10,5 L0,10 z" fill="var(--ink-soft)"/>
        </marker>
      </defs>
      {/* lines */}
      <g stroke="var(--ink-soft)" strokeWidth="1.4" fill="none" markerEnd="url(#arr)">
        <path d="M280,40 L280,70" />
        {/* B -> L1 (yes left) */}
        <path d="M225,115 L100,115 L100,158" />
        {/* B -> D (no down) */}
        <path d="M280,130 L280,160" />
        {/* D -> L2 (yes right) */}
        <path d="M335,195 L460,195 L460,238" />
        {/* D -> E (no down) */}
        <path d="M280,210 L280,240" />
        {/* E -> L2 (yes left up) */}
        <path d="M225,265 L160,265 L160,260 L420,260" />
        {/* E -> L1 (no left far) */}
        <path d="M225,280 L60,280 L60,200 L75,200" />
        {/* L2 -> G */}
        <path d="M460,290 L460,320" />
      </g>
      {/* labels on lines */}
      <g fontFamily="Geist Mono, monospace" fontSize="10" fill="var(--ink-faint)">
        <text x="155" y="108">yes</text>
        <text x="290" y="148">no</text>
        <text x="390" y="188">yes</text>
        <text x="290" y="228">no</text>
        <text x="180" y="258">yes</text>
        <text x="70" y="275">no</text>
      </g>
      {/* nodes */}
      <FNode x={225} y={20} w={110} h={28} label="New task" subtle />
      <FNode x={225} y={70} w={110} h={48} label="Trivial fix?" diamond />
      <FNode x={225} y={160} w={110} h={48} label="Want parallel agent?" diamond />
      <FNode x={225} y={240} w={110} h={48} label="Needs CI / review?" diamond />
      <FNode x={420} y={320} w={80} h={28} label="Done?" diamond compact />
      {/* lane chips as clickable groups */}
      <LaneNode lane={L[0]} cx={100} cy={180} goto={goto} />
      <LaneNode lane={L[1]} cx={460} cy={260} goto={goto} />
      <LaneNode lane={L[2]} cx={460} cy={335} goto={goto} small />
    </svg>
  );
}
function FNode({ x, y, w, h, label, diamond, subtle, compact }) {
  const cx = x + w / 2, cy = y + h / 2;
  return (
    <g>
      {diamond ? (
        <polygon
          points={`${cx},${y} ${x + w},${cy} ${cx},${y + h} ${x},${cy}`}
          fill="var(--paper)" stroke="var(--ink-soft)" strokeWidth="1.2"
        />
      ) : (
        <rect x={x} y={y} width={w} height={h} rx="8"
          fill={subtle ? "var(--cream-deep)" : "var(--paper)"} stroke="var(--ink-soft)" strokeWidth="1.2"/>
      )}
      <text x={cx} y={cy + (compact ? 3 : 4)} textAnchor="middle"
        fontFamily="Geist, sans-serif" fontSize={compact ? 11 : 12}
        fontWeight="600" fill="var(--ink)">{label}</text>
    </g>
  );
}
function LaneNode({ lane, cx, cy, goto, small }) {
  const w = small ? 110 : 130, h = small ? 36 : 44;
  return (
    <g style={{ cursor: "pointer" }} onClick={() => goto(lane.id)}>
      <rect x={cx - w/2} y={cy - h/2} width={w} height={h} rx={h/2}
        fill={lane.accentBg} stroke={lane.accent} strokeWidth="1.5"/>
      <circle cx={cx - w/2 + h/2} cy={cy} r={h/2 - 6} fill={lane.accent}/>
      <text x={cx - w/2 + h/2} y={cy + 4} textAnchor="middle"
        fontFamily="Bricolage Grotesque, sans-serif" fontWeight="700"
        fontSize="14" fill="white">{lane.n}</text>
      <text x={cx + 6} y={cy + 4} textAnchor="middle"
        fontFamily="Geist, sans-serif" fontWeight="600" fontSize="12"
        fill={lane.accentInk}>{lane.short}</text>
    </g>
  );
}

// ----- Lane diagrams ---------------------------------------------------------
function DiagramTimeline() {
  // Lane 1: dev branch with merge points, fix branch born/merged/deleted.
  const W = 560, H = 170;
  const dev = "M40,80 L140,80 L240,80 L340,80 L440,80 L520,80";
  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: "block" }}>
      <text x="20" y="46" fontFamily="Geist Mono, monospace" fontSize="11" fill="var(--ink-faint)">dev</text>
      <text x="20" y="138" fontFamily="Geist Mono, monospace" fontSize="11" fill="var(--l1-ink)">fix/…</text>
      {/* dev line */}
      <path d={dev} stroke="var(--ink-soft)" strokeWidth="2" fill="none"/>
      {[140, 240, 340, 440].map((x,i) => <circle key={i} cx={x} cy={80} r="5" fill="var(--cream)" stroke="var(--ink-soft)" strokeWidth="1.5"/>)}
      <circle cx={520} cy={80} r="5" fill="var(--ink)" />
      {/* fix branch */}
      <path d="M240,80 C 290,80 290,130 340,130 C 390,130 390,80 440,80" stroke="var(--l1)" strokeWidth="2" fill="none"/>
      <circle cx={340} cy={130} r="6" fill="var(--l1)"/>
      <text x={340} y={158} textAnchor="middle" fontFamily="Geist Mono, monospace" fontSize="10" fill="var(--l1-ink)">commit</text>
      <text x={240} y={70} textAnchor="middle" fontFamily="Geist Mono, monospace" fontSize="10" fill="var(--ink-faint)">switch -c</text>
      <text x={440} y={70} textAnchor="middle" fontFamily="Geist Mono, monospace" fontSize="10" fill="var(--ink-faint)">merge · -d</text>
    </svg>
  );
}
function DiagramParallel() {
  // Lane 2: main repo lane + worktrees stacked, arrows back to dev
  const W = 580, H = 220;
  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: "block" }}>
      {/* lanes */}
      {[
        { y: 40, label: "main repo · dev", sub: "live coding", color: "var(--ink)" },
        { y: 100, label: "worktree #1 · feat/A", sub: "Claude agent", color: "var(--l2)" },
        { y: 160, label: "worktree #2 · feat/B", sub: "CI / verify", color: "var(--l2)" },
      ].map((row, i) => (
        <g key={i}>
          <rect x="20" y={row.y - 16} width="320" height="32" rx="16"
            fill={i === 0 ? "var(--cream-deep)" : "var(--l2-bg)"}
            stroke={row.color} strokeWidth="1.4"/>
          <text x="40" y={row.y + 4} fontFamily="Geist Mono, monospace" fontSize="12" fontWeight="600" fill={row.color}>{row.label}</text>
          <text x="200" y={row.y + 4} fontFamily="Geist, sans-serif" fontSize="11" fill="var(--ink-faint)">— {row.sub}</text>
          {/* arrow to merge node */}
          {i !== 0 && (
            <path d={`M340,${row.y} C 400,${row.y} 420,80 470,80`}
              stroke="var(--l2)" strokeWidth="1.6" fill="none" strokeDasharray="3 3"/>
          )}
        </g>
      ))}
      {/* merge target node */}
      <circle cx="470" cy="80" r="18" fill="var(--l2-bg)" stroke="var(--l2)" strokeWidth="2"/>
      <text x="470" y="78" textAnchor="middle" fontFamily="Geist, sans-serif" fontSize="10" fontWeight="700" fill="var(--l2-ink)">PR</text>
      <text x="470" y="91" textAnchor="middle" fontFamily="Geist, sans-serif" fontSize="9" fill="var(--l2-ink)">→ dev</text>
      {/* legend */}
      <text x="20" y="200" fontFamily="Geist, sans-serif" fontSize="11" fill="var(--ink-faint)">All branches converge through PRs into dev.</text>
    </svg>
  );
}
function DiagramReview() {
  // Lane 2 · mode B: main-repo timeline briefly diverts to the agent's branch
  // for in-place verification, then returns to dev once the PR is merged.
  const W = 580, H = 200;
  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: "block" }}>
      <text x="20" y="40" fontFamily="Geist Mono, monospace" fontSize="11" fill="var(--ink-faint)">main repo</text>
      {/* dev line */}
      <path d="M40,80 L530,80" stroke="var(--ink-soft)" strokeWidth="2" fill="none"/>
      {[40, 530].map((x, i) => (
        <circle key={i} cx={x} cy={80} r="5" fill={i === 1 ? "var(--ink)" : "var(--cream)"} stroke="var(--ink-soft)" strokeWidth="1.5"/>
      ))}
      <text x={40} y={68} textAnchor="middle" fontFamily="Geist Mono, monospace" fontSize="10" fill="var(--ink-faint)">dev</text>
      <text x={530} y={68} textAnchor="middle" fontFamily="Geist Mono, monospace" fontSize="10" fill="var(--ink-faint)">dev (synced)</text>
      {/* claude branch detour */}
      <text x="20" y="170" fontFamily="Geist Mono, monospace" fontSize="11" fill="var(--l2-ink)">claude/…</text>
      <path d="M120,80 C 170,80 170,140 230,140 L 380,140 C 440,140 440,80 480,80"
            stroke="var(--l2)" strokeWidth="2" fill="none"/>
      <circle cx={305} cy={140} r="6" fill="var(--l2)"/>
      <text x={305} y={170} textAnchor="middle" fontFamily="Geist Mono, monospace" fontSize="10" fill="var(--l2-ink)">verify on device</text>
      {/* phase labels above the path */}
      <text x={170} y={108} fontFamily="Geist Mono, monospace" fontSize="10" fill="var(--ink-faint)">fetch · checkout</text>
      <text x={420} y={108} textAnchor="middle" fontFamily="Geist Mono, monospace" fontSize="10" fill="var(--ink-faint)">merge PR · sync</text>
    </svg>
  );
}

function DiagramLifecycle() {
  // Lane 3: state machine for a worktree
  const W = 580, H = 220;
  const states = [
    { x: 80, y: 110, label: "active", id: "a" },
    { x: 240, y: 60,  label: "locked", id: "l" },
    { x: 400, y: 110, label: "active", id: "a2" },
    { x: 540, y: 110, label: "gone", id: "g", terminal: true },
    { x: 240, y: 170, label: "orphan", id: "o", warn: true },
  ];
  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: "block" }}>
      <defs>
        <marker id="arr3" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
          <path d="M0,0 L10,5 L0,10 z" fill="var(--l3-ink)"/>
        </marker>
      </defs>
      {/* transitions */}
      <g stroke="var(--l3-ink)" strokeWidth="1.6" fill="none" markerEnd="url(#arr3)">
        {/* add -> active */}
        <path d="M20,110 L60,110" />
        {/* active -> locked */}
        <path d="M100,95 C 140,60 180,50 220,55" />
        {/* locked -> active2 */}
        <path d="M260,75 C 300,95 340,100 380,108" />
        {/* active2 -> gone */}
        <path d="M420,110 L520,110" />
        {/* active -> orphan (rm -rf, warn) */}
        <path d="M85,128 C 110,160 170,175 220,172" stroke="var(--l3)" strokeDasharray="4 3"/>
        {/* orphan -> gone via prune */}
        <path d="M260,172 C 360,172 460,150 525,118" />
      </g>
      <g fontFamily="Geist Mono, monospace" fontSize="10" fill="var(--l3-ink)">
        <text x="22" y="102">add</text>
        <text x="135" y="50">agent lock</text>
        <text x="305" y="73">unlock</text>
        <text x="455" y="100">remove</text>
        <text x="105" y="170" fill="var(--l3)">rm -rf (don't!)</text>
        <text x="320" y="160">prune</text>
      </g>
      {states.map((s) => (
        <g key={s.id}>
          <rect x={s.x - 36} y={s.y - 16} width="72" height="32" rx="16"
            fill={s.warn ? "var(--l3-bg)" : (s.terminal ? "var(--cream-deep)" : "var(--paper)")}
            stroke={s.warn ? "var(--l3)" : "var(--ink-soft)"} strokeDasharray={s.warn ? "4 3" : ""} strokeWidth="1.4"/>
          <text x={s.x} y={s.y + 4} textAnchor="middle" fontFamily="Geist Mono, monospace"
            fontSize="11" fontWeight="600" fill={s.warn ? "var(--l3-ink)" : "var(--ink)"}>{s.label}</text>
        </g>
      ))}
    </svg>
  );
}

// ----- Concept (expandable) --------------------------------------------------
function Concept({ c, open, onToggle }) {
  return (
    <div style={{
      borderTop: "1px solid var(--rule)",
      padding: "0",
    }}>
      <button
        onClick={onToggle}
        style={{
          display: "flex", alignItems: "center", gap: 10,
          width: "100%", textAlign: "left", padding: "12px 0",
          background: "transparent", border: 0, cursor: "pointer",
          color: "var(--ink)", fontFamily: "inherit",
        }}
        aria-expanded={open}
      >
        <span style={{
          width: 18, height: 18, display: "grid", placeItems: "center",
          borderRadius: 4, border: "1px solid var(--rule-strong)",
          fontSize: 12, color: "var(--ink-soft)",
          transition: "transform .2s", transform: open ? "rotate(45deg)" : "none",
        }}>+</span>
        <span style={{ fontWeight: 600, fontSize: 14, lineHeight: 1.3 }}>{c.h}</span>
      </button>
      {open && (
        <div style={{ padding: "0 0 16px 28px", color: "var(--ink-soft)", fontSize: 13.5, lineHeight: 1.6 }}>
          {c.body.map((b, i) => {
            if (b.p) return <p key={i} style={{ margin: "0 0 10px" }}>{b.p}</p>;
            if (b.code) return <div key={i} style={{ margin: "0 0 10px" }}><CodeBlock code={b.code} small label="snippet" /></div>;
            if (b.list) return (
              <ul key={i} style={{ margin: "0 0 10px", paddingLeft: 18 }}>
                {b.list.map((li, j) => <li key={j} style={{ marginBottom: 4 }}>{li}</li>)}
              </ul>
            );
            if (b.table) return (
              <div key={i} style={{ margin: "0 0 10px", overflowX: "auto" }}>
                <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12.5, fontFamily: "Geist Mono, monospace" }}>
                  <thead><tr>{b.table[0].map((th, k) => (
                    <th key={k} style={{ textAlign: "left", padding: "6px 8px", background: "var(--cream-deep)", color: "var(--ink)", fontWeight: 600, borderBottom: "1px solid var(--rule-strong)" }}>{th}</th>
                  ))}</tr></thead>
                  <tbody>{b.table.slice(1).map((row, ri) => (
                    <tr key={ri}>{row.map((td, k) => (
                      <td key={k} style={{ padding: "6px 8px", borderBottom: "1px solid var(--rule)", verticalAlign: "top" }}>{td}</td>
                    ))}</tr>
                  ))}</tbody>
                </table>
              </div>
            );
            return null;
          })}
        </div>
      )}
    </div>
  );
}

// ----- ModeToggle ------------------------------------------------------------
// Bigger, banner-style segmented control used inside a lane card to switch
// between fundamentally different workflows (e.g. manual vs. with Claude Code).
// Visually heavier than Tabs because the choice changes ALL the content below.
function ModeToggle({ modes, value, onChange, accent, accentBg, accentInk }) {
  return (
    <div
      role="radiogroup"
      aria-label="Workflow mode"
      style={{
        display: "grid",
        gridTemplateColumns: `repeat(${modes.length}, minmax(0, 1fr))`,
        gap: 8,
      }}
    >
      {modes.map((m, i) => {
        const on = m.id === value;
        return (
          <button
            key={m.id}
            role="radio"
            aria-checked={on}
            onClick={() => onChange(m.id)}
            style={{
              padding: "12px 14px",
              borderRadius: 12,
              border: `1.5px solid ${on ? accent : "var(--rule-strong)"}`,
              background: on ? accentBg : "var(--cream-deep)",
              color: on ? accentInk : "var(--ink-soft)",
              cursor: "pointer", textAlign: "left",
              fontFamily: "inherit",
              transition: "all .15s",
              boxShadow: on ? `0 6px 18px -10px ${accent}` : "none",
            }}
            onMouseEnter={(e) => { if (!on) e.currentTarget.style.borderColor = accent; }}
            onMouseLeave={(e) => { if (!on) e.currentTarget.style.borderColor = "var(--rule-strong)"; }}
          >
            <div style={{
              display: "flex", alignItems: "center", gap: 8,
              fontFamily: "Geist Mono, monospace", fontSize: 10.5,
              letterSpacing: "0.14em", textTransform: "uppercase",
              opacity: on ? 1 : 0.7, fontWeight: 700,
            }}>
              <span style={{
                width: 18, height: 18, borderRadius: "50%",
                background: on ? accent : "transparent",
                color: on ? "white" : "var(--ink-soft)",
                border: `1.2px solid ${on ? accent : "var(--rule-strong)"}`,
                display: "grid", placeItems: "center",
                fontSize: 11, lineHeight: 1, fontFamily: "Bricolage Grotesque, sans-serif",
              }}>{String.fromCharCode(65 + i)}</span>
              {m.label}
            </div>
            <div style={{ fontWeight: 600, fontSize: 13.5, marginTop: 6, lineHeight: 1.35 }}>
              {m.sublabel}
            </div>
          </button>
        );
      })}
    </div>
  );
}

// ----- Tabs ------------------------------------------------------------------
function Tabs({ tabs, value, onChange, accent }) {
  return (
    <div role="tablist" style={{
      display: "inline-flex", padding: 4, borderRadius: 999,
      background: "var(--cream-deep)", border: "1px solid var(--rule)",
      gap: 2,
    }}>
      {tabs.map((t) => {
        const active = value === t.id;
        return (
          <button
            key={t.id}
            role="tab"
            aria-selected={active}
            onClick={() => onChange(t.id)}
            style={{
              padding: "7px 14px", borderRadius: 999, border: 0,
              cursor: "pointer", fontWeight: 600, fontSize: 13,
              background: active ? "var(--paper)" : "transparent",
              color: active ? accent : "var(--ink-soft)",
              boxShadow: active ? "0 1px 2px rgba(0,0,0,0.06)" : "none",
              fontFamily: "inherit",
              transition: "background .15s, color .15s",
            }}
          >
            {t.label}
          </button>
        );
      })}
    </div>
  );
}

Object.assign(window, {
  CopyButton, CodeBlock, LaneBadge, LaneChip, Flowchart,
  DiagramTimeline, DiagramParallel, DiagramReview, DiagramLifecycle,
  Concept, Tabs, ModeToggle,
});
