// Right widget panel
function WidgetPanel({ widgets, active, setActive, view, setView, onClose, accent, codeAnalysisIds }) {
  const idx = Math.min(active, widgets.length - 1);
  const w = widgets[idx] || widgets[0];
  const supportsGraph = codeAnalysisIds && codeAnalysisIds.has(w.id);
  const heatLabel = (window.HEAT_SOURCE_LABEL && window.HEAT_SOURCE_LABEL[w.id]) || "this analysis";

  // Sub-state for the graph: which node, if any, is in the detail pane.
  const [selectedNode, setSelectedNode] = useState(null);
  // Reset node selection when switching widget or leaving graph view.
  useEffect(() => { setSelectedNode(null); }, [w.id, view]);

  return (
    <aside className="w-full h-full border-l border-[#e8e3d8] bg-[#faf8f3] flex flex-col">
      {/* Header */}
      <div className="flex items-center gap-2 px-4 h-12 border-b border-[#e8e3d8] bg-white">
        <div className="text-[13.5px] font-medium text-[#0e0e10]">{w.title}</div>
        <div className="text-[11.5px] text-[#9aa0a6]">·</div>
        <div className="text-[11.5px] text-[#9aa0a6] truncate">{w.subtitle}</div>
        <div className="flex-1" />
        <div className="inline-flex items-center bg-[#f4f1ea] rounded-md p-0.5 mr-1">
          <button onClick={() => setView("widget")} title="Widget view"
            className={`px-1.5 py-1 rounded ${view === "widget" ? "bg-white shadow-sm text-[#0e0e10]" : "text-[#6b6b6b]"}`}>
            <Icon name="eye" size={13} />
          </button>
          <button onClick={() => setView("data")} title="Data view"
            className={`px-1.5 py-1 rounded ${view === "data" ? "bg-white shadow-sm text-[#0e0e10]" : "text-[#6b6b6b]"}`}>
            <Icon name="json" size={13} />
          </button>
          <button
            onClick={() => supportsGraph && setView("graph")}
            disabled={!supportsGraph}
            title={supportsGraph ? `Component graph — Heat: ${heatLabel}` : "Component graph available for code-analysis widgets only."}
            className={`px-1.5 py-1 rounded ${view === "graph" ? "bg-white shadow-sm text-[#0e0e10]" : supportsGraph ? "text-[#6b6b6b]" : "text-[#cbc3ad] cursor-not-allowed"}`}>
            <Icon name="hierarchy" size={13} />
          </button>
        </div>
        <button onClick={onClose} className="p-1.5 rounded-md hover:bg-black/[0.04] text-[#5a5a5a]" title="Collapse panel"><Icon name="chevron-right" size={15} /></button>
      </div>

      {/* Body */}
      <div className="flex-1 min-h-0 overflow-y-auto">
        {view === "graph"
          ? (selectedNode
              ? <NodeDetailView nodeId={selectedNode} widgetId={w.id} onBack={() => setSelectedNode(null)} />
              : <GraphView widgetId={w.id} onSelectNode={setSelectedNode} />)
          : view === "data" ? <DataView id={w.id} /> : <WidgetBody id={w.id} />}
      </div>

      {/* Bottom navigator */}
      {widgets.length > 1 && view !== "graph" && (
        <div className="mt-auto shrink-0 border-t border-[#e8e3d8] bg-white px-3 py-2 flex items-center justify-center gap-3">
          <button onClick={() => setActive(Math.max(0, idx - 1))} disabled={idx === 0}
            className="p-1.5 rounded-md text-[#5a5a5a] hover:bg-black/[0.04] disabled:opacity-30"><Icon name="chevron-left" size={14} /></button>
          <div className="text-[11.5px] text-[#6b6b6b] tabular-nums">{idx + 1} / {widgets.length}</div>
          <button onClick={() => setActive(Math.min(widgets.length - 1, idx + 1))} disabled={idx === widgets.length - 1}
            className="p-1.5 rounded-md text-[#5a5a5a] hover:bg-black/[0.04] disabled:opacity-30"><Icon name="chevron-right" size={14} /></button>
        </div>
      )}
    </aside>
  );
}

function WidgetBody({ id }) {
  if (id === 10) return <RiskAnalyzer />;
  if (id === 30) return <StoryEstimation />;
  if (id === 31) return <StoryUpdate />;
  if (id === 50) return <SprintPulse />;
  return null;
}

function StatDial({ label, value, max, valueLabel, color }) {
  const r = 30; const c = 2 * Math.PI * r;
  const off = c - (value / max) * c;
  return (
    <div className="rounded-lg border border-[#ece7da] bg-[#faf8f3] p-3 flex flex-col items-center text-center">
      <div className="text-[10.5px] uppercase tracking-[0.06em] font-semibold text-[#9aa0a6]">{label}</div>
      <div className="relative w-[72px] h-[72px] my-2">
        <svg width="72" height="72" viewBox="0 0 72 72">
          <circle cx="36" cy="36" r={r} fill="none" stroke="#eee8d6" strokeWidth="6" />
          <circle cx="36" cy="36" r={r} fill="none" stroke={color} strokeWidth="6" strokeLinecap="round"
            strokeDasharray={c} strokeDashoffset={off} transform="rotate(-90 36 36)" />
        </svg>
        <div className="absolute inset-0 flex items-center justify-center text-[14px] font-semibold text-[#0e0e10] tabular-nums">
          {max === 5 ? `${value}/5` : value}
        </div>
      </div>
      <div className="text-[12.5px] font-semibold text-[#0e0e10]">{valueLabel}</div>
    </div>
  );
}

function StoryEstimation() {
  const components = [
    { id: "c1", name: "src/composer/Dropzone.tsx", touch: "modify", risk: "high" },
    { id: "c2", name: "src/chat/AttachmentRail.tsx", touch: "new", risk: "medium" },
    { id: "c3", name: "src/api/upload/", touch: "new", risk: "medium" },
    { id: "c4", name: "src/state/contextGraph.ts", touch: "modify", risk: "low" },
  ];
  const risks = [
    { id: "r1", title: "Overlap with TEK-345 on Dropzone.tsx", severity: "high" },
    { id: "r2", title: "Virus-scan UX undefined (AC-9)", severity: "medium" },
    { id: "r3", title: "Mobile picker behavior pending product decision (AC-5)", severity: "medium" },
  ];
  const experts = [
    { id: "e1", name: "Felix H.", area: "Composer & paste handling" },
    { id: "e2", name: "Anita R.", area: "Virtualization & layout" },
    { id: "e3", name: "Lukas B.", area: "State & storage" },
  ];
  const features = [
    "Drag-drop attachments onto composer",
    "Inline file thumbnails in chat",
    "Per-file size + type guardrails",
    "Virus scan + quarantine surface",
    "Mobile camera-roll upload",
  ];
  return (
    <div className="p-5">
      <div className="rounded-xl border border-[#ece7da] bg-white p-5">
        <div className="text-[14px] font-semibold text-[#0e0e10] mb-1.5">Estimation</div>
        <p className="text-[12.5px] leading-[1.6] text-[#3a3a3a]">
          Story <span className="font-mono text-[12px] px-1 py-[1px] bg-[#f4f1ea] rounded">TEK-140</span> sits at <strong>Moderate complexity</strong>. Two new components plus the
          Dropzone collision with TEK-345 are the main contributors. Suggest sequencing after TEK-345 ships.
        </p>
        <div className="mt-4 grid grid-cols-2 gap-3">
          <StatDial label="Complexity" value={3} max={5} valueLabel="Medium" color="#c2410c" />
          <StatDial label="Risk" value={62} max={100} valueLabel="62 / 100" color="#d05848" />
        </div>
      </div>

      <div className="mt-5 mb-2 text-[13px] font-semibold text-[#0e0e10]">Related components</div>
      <div className="flex flex-col gap-1.5">
        {components.map(c => {
          const sev = c.risk === "high" ? { color: "#a83228", bg: "#fdecea" } : c.risk === "medium" ? { color: "#a05a00", bg: "#fef0d9" } : { color: "#1f7a3a", bg: "#e8f6ec" };
          return (
            <div key={c.id} className="rounded-lg border border-[#ece7da] bg-white p-2.5 flex items-center gap-2.5">
              <div className="w-7 h-7 rounded-md bg-[#f4f1ea] text-[#10b981] flex items-center justify-center"><Icon name="code" size={13} /></div>
              <div className="flex-1 min-w-0">
                <div className="font-mono text-[12px] text-[#0e0e10] truncate">{c.name}</div>
                <div className="text-[11px] text-[#9aa0a6]">{c.touch === "new" ? "New file" : "Modified"}</div>
              </div>
              <div className="px-1.5 py-[1px] rounded-full text-[10.5px] font-medium" style={{ background: sev.bg, color: sev.color }}>{c.risk}</div>
            </div>
          );
        })}
      </div>

      <div className="mt-5 mb-2 text-[13px] font-semibold text-[#0e0e10]">Related risks</div>
      <div className="flex flex-col gap-1.5">
        {risks.map(r => {
          const sev = r.severity === "high" ? { color: "#a83228", bg: "#fdecea" } : r.severity === "medium" ? { color: "#a05a00", bg: "#fef0d9" } : { color: "#1f7a3a", bg: "#e8f6ec" };
          return (
            <div key={r.id} className="rounded-lg border border-[#ece7da] bg-white p-2.5 flex items-center gap-2.5">
              <div className="px-1.5 py-[1px] rounded-full text-[10.5px] font-medium shrink-0" style={{ background: sev.bg, color: sev.color }}>{r.severity}</div>
              <div className="flex-1 min-w-0 text-[12.5px] text-[#1a1a1a]">{r.title}</div>
            </div>
          );
        })}
      </div>

      <div className="mt-5 mb-2 text-[13px] font-semibold text-[#0e0e10]">Best experts</div>
      <div className="flex flex-col gap-1.5">
        {experts.map(e => (
          <div key={e.id} className="rounded-lg border border-[#ece7da] bg-white p-2.5 flex items-center gap-2.5">
            <div className="w-7 h-7 rounded-full bg-gradient-to-br from-[#3b5cf6] to-[#ec4899] text-white flex items-center justify-center text-[10px] font-semibold">{e.name.split(/\s+/).map(w => w[0]).join("")}</div>
            <div className="flex-1 min-w-0">
              <div className="text-[12.5px] font-medium text-[#0e0e10]">{e.name}</div>
              <div className="text-[11px] text-[#6b6b6b]">{e.area}</div>
            </div>
            <button className="px-2 py-1 rounded text-[11.5px] text-[var(--accent)] hover:bg-[var(--accent)]/10">Ping</button>
          </div>
        ))}
      </div>

      <div className="mt-5 mb-2 text-[13px] font-semibold text-[#0e0e10]">Key features</div>
      <div className="flex flex-col gap-1.5">
        {features.map(f => (
          <div key={f} className="rounded-lg border border-[#ece7da] bg-white p-2.5 flex items-center gap-2.5 text-[12.5px] text-[#1a1a1a]">
            <Icon name="check" size={13} className="text-[#1f7a3a]" />
            {f}
          </div>
        ))}
      </div>
    </div>
  );
}

function StoryUpdate() {
  const sections = [
    {
      id: "ac", title: "Acceptance criteria", icon: "check",
      desc: "Tighten language and resolve the three open ACs.",
      proposals: [
        "AC-7 — Set file-size ceiling to 50 MB (4 customer tickets supporting).",
        "AC-9 — Add 'pending scan' state copy + 24h auto-expire.",
        "AC-5 — Lock mobile picker to camera roll for v1; defer files.app to follow-up story.",
      ],
    },
    {
      id: "impl", title: "Implementation details", icon: "code",
      desc: "Document the technical decisions surfaced in chat.",
      proposals: [
        "Add upload-path diagram from Engineering Wiki § Attachments.",
        "Note Dropzone overlap with TEK-345 + suggested sequencing.",
        "Pin feature flag name: `attachments_v1`.",
      ],
    },
    {
      id: "files", title: "Related files", icon: "source",
      desc: "Link the code surfaces this story touches.",
      proposals: [
        "src/composer/Dropzone.tsx",
        "src/chat/AttachmentRail.tsx (new)",
        "src/api/upload/* (new)",
      ],
    },
    {
      id: "feat", title: "Feature definition", icon: "wand",
      desc: "Sharpen the user-facing description.",
      proposals: [
        "Replace generic 'attach files' with explicit drag-drop + paste-from-clipboard wording.",
        "Add mobile constraints to the user story.",
      ],
    },
  ];
  return (
    <div className="p-5 pb-3 flex flex-col gap-3">
      <div className="rounded-xl border border-[#ece7da] bg-white p-4">
        <div className="flex items-center gap-2">
          <Icon name="edit" size={13} className="text-[var(--accent)]" />
          <div className="text-[13px] font-semibold text-[#0e0e10]">Improve TEK-140</div>
          <div className="flex-1" />
          <span className="text-[10.5px] font-semibold tracking-wider uppercase px-1.5 py-[1px] rounded bg-[#fef0d9] text-[#a05a00]">12 proposed edits</span>
        </div>
        <p className="text-[12px] text-[#3a3a3a] leading-relaxed mt-1.5">
          Pulled from the virtual chat above. Each section lets you accept proposed edits and write them back to Jira in one click.
        </p>
      </div>

      {sections.map(s => (
        <div key={s.id} className="rounded-xl border border-[#ece7da] bg-white p-4">
          <div className="flex items-center gap-2">
            <Icon name={s.icon} size={13} className="text-[var(--accent)]" />
            <div className="text-[13px] font-semibold text-[#0e0e10]">{s.title}</div>
            <div className="flex-1" />
            <button className="p-1 rounded text-[#9aa0a6] hover:bg-black/[0.05]"><Icon name="edit-line" size={13} /></button>
          </div>
          <p className="text-[11.5px] text-[#6b6b6b] mt-1">{s.desc}</p>
          <div className="mt-2 flex flex-col gap-1.5">
            {s.proposals.map((p, i) => (
              <label key={i} className="flex items-start gap-2 p-2 rounded-md border border-[#ece7da] bg-[#faf8f3] hover:bg-white cursor-pointer">
                <input type="checkbox" defaultChecked className="mt-[3px] accent-[var(--accent)]" />
                <span className="flex-1 text-[12.5px] text-[#1a1a1a] leading-relaxed">{p}</span>
              </label>
            ))}
          </div>
        </div>
      ))}

      <button className="mt-1 w-full py-2.5 rounded-md bg-[var(--accent)] text-white text-[13px] font-medium hover:brightness-110">
        Update story
      </button>
      <div className="text-[11px] text-[#9aa0a6] text-center -mt-1">Will write back to Jira and post a comment on TEK-140.</div>
    </div>
  );
}

function RiskAnalyzer() {
  const risks = [
    {
      id: "r1", severity: "high", score: 84, title: "Race condition between streaming chunks and optimistic user message",
      area: "src/chat/useChatStream.ts", lines: "L42–L78", likelihood: "Likely", impact: "High",
      desc: "Stream subscription opens before the optimistic insert resolves. On poor network, chunks may target a stale message id and silently drop.",
      mitigation: "Defer stream subscription until the optimistic id is committed; introduce a pending-buffer keyed by clientMsgId and replay on bind.",
      owner: "felix.h", effort: "M",
    },
    {
      id: "r2", severity: "high", score: 76, title: "Virtualization breaks scroll-to-bottom on tool-call expansion",
      area: "src/chat/MessageList.tsx", lines: "L120–L165", likelihood: "Possible", impact: "High",
      desc: "Variable-height tool cards aren't measured before next render. Auto-scroll lands above the latest message ~12% of the time in repro.",
      mitigation: "Use ResizeObserver per row; gate auto-scroll on a 1-frame post-measure RAF. Add an at-bottom sentinel.",
      owner: "anita.r", effort: "S",
    },
    {
      id: "r3", severity: "medium", score: 64, title: "Pinned-context store migration has no backfill",
      area: "src/state/contextGraph.ts", lines: "—", likelihood: "Likely", impact: "Medium",
      desc: "Schema changes contextChips[] → contextGraph. Existing users would lose pins on first load.",
      mitigation: "Write one-shot migrator on hydrate; emit telemetry per migrated user; add rollback flag.",
      owner: "lukas.b", effort: "M",
    },
    {
      id: "r4", severity: "medium", score: 52, title: "Composer drag-drop swallows clipboard paste on Safari 17",
      area: "src/composer/Dropzone.tsx", lines: "L22–L40", likelihood: "Possible", impact: "Medium",
      desc: "File handler intercepts paste events at window scope rather than dropzone scope.",
      mitigation: "Scope listener to dropzone ref; whitelist text/* to fall through to native composer.",
      owner: "felix.h", effort: "S",
    },
    {
      id: "r5", severity: "low", score: 31, title: "Bundle size +18KB from new markdown renderer",
      area: "package.json", lines: "—", likelihood: "Certain", impact: "Low",
      desc: "react-markdown + remark-gfm doubled the chat bundle. No code-split.",
      mitigation: "Lazy-load renderer behind first assistant message; pre-warm on chat open.",
      owner: "anita.r", effort: "S",
    },
  ];
  const overall = 68;
  return (
    <div className="p-5">
      {/* Score card */}
      <div className="rounded-xl border border-[#ece7da] bg-white p-5">
        <div className="flex items-start gap-5">
          <div className="flex-1">
            <div className="text-[14px] font-semibold text-[#0e0e10] mb-1.5">Implementation Risk</div>
            <p className="text-[12.5px] leading-[1.6] text-[#3a3a3a]">
              Branch <span className="font-mono text-[12px] px-1 py-[1px] bg-[#f4f1ea] rounded">features/new-chat-frontend</span> diverges 38 commits from <span className="font-mono text-[12px] px-1 py-[1px] bg-[#f4f1ea] rounded">main</span>. Risk is concentrated in two adjacent surfaces: streaming state and message virtualization. Migration of the pinned-context schema is the highest-blast-radius item if shipped without backfill.
            </p>
            <div className="mt-3 flex items-center gap-3 text-[11.5px]">
              <span className="text-[#6b6b6b]">5 risks</span>
              <span className="text-[#d05848]">2 high</span>
              <span className="text-[#c2410c]">2 medium</span>
              <span className="text-[#1f7a3a]">1 low</span>
            </div>
          </div>
          <RiskRing score={overall} />
        </div>
      </div>

      <div className="mt-5 mb-2 text-[13px] font-semibold text-[#0e0e10]">Top risks</div>
      <div className="flex flex-col gap-2.5">
        {risks.map(r => <RiskRow key={r.id} {...r} />)}
      </div>

      <div className="mt-4 rounded-lg border border-dashed border-[#d4cdba] bg-white px-3 py-2.5 flex items-center gap-2">
        <Icon name="info" size={13} className="text-[#9aa0a6]" />
        <span className="text-[11.5px] text-[#6b6b6b]">Mitigations are estimated against the current branch HEAD. Re-run after each push.</span>
      </div>
    </div>
  );
}

function RiskRing({ score }) {
  const r = 28; const c = 2 * Math.PI * r;
  const off = c - (score / 100) * c;
  const color = score >= 70 ? "#d05848" : score >= 40 ? "#c2410c" : "#1f8a5b";
  return (
    <div className="relative w-[78px] h-[78px] shrink-0">
      <svg width="78" height="78" viewBox="0 0 78 78">
        <circle cx="39" cy="39" r={r} fill="none" stroke="#eee8d6" strokeWidth="6" />
        <circle cx="39" cy="39" r={r} fill="none" stroke={color} strokeWidth="6" strokeLinecap="round"
          strokeDasharray={c} strokeDashoffset={off} transform="rotate(-90 39 39)" />
      </svg>
      <div className="absolute inset-0 flex flex-col items-center justify-center">
        <div className="text-[18px] font-semibold text-[#0e0e10] leading-none">{score}</div>
        <div className="text-[10px] text-[#9aa0a6] mt-0.5">Risk</div>
      </div>
    </div>
  );
}

function RiskRow({ severity, score, title, area, lines, likelihood, impact, desc, mitigation, owner, effort }) {
  const sev = {
    high: { bg: "#fdecea", color: "#a83228", label: "High" },
    medium: { bg: "#fef0d9", color: "#a05a00", label: "Medium" },
    low: { bg: "#e8f6ec", color: "#1f7a3a", label: "Low" },
  }[severity];
  return (
    <div className="rounded-lg border border-[#ece7da] bg-white p-3">
      <div className="flex items-start gap-2">
        <div className="px-2 py-[1px] rounded-full text-[10.5px] font-medium shrink-0" style={{ background: sev.bg, color: sev.color }}>{sev.label}</div>
        <div className="text-[13px] font-semibold text-[#0e0e10] leading-snug flex-1">{title}</div>
        <div className="text-[11px] text-[#9aa0a6] tabular-nums shrink-0">{score}/100</div>
      </div>
      <div className="mt-1.5 flex items-center gap-2 flex-wrap text-[11px] text-[#6b6b6b]">
        <span className="font-mono text-[11px] text-[#3a3a3a]">{area}</span>
        {lines !== "—" && <span className="text-[#9aa0a6]">·</span>}
        {lines !== "—" && <span className="font-mono text-[11px] text-[#9aa0a6]">{lines}</span>}
        <span className="text-[#9aa0a6]">·</span>
        <span>Likelihood: <span className="text-[#1a1a1a]">{likelihood}</span></span>
        <span className="text-[#9aa0a6]">·</span>
        <span>Impact: <span className="text-[#1a1a1a]">{impact}</span></span>
      </div>
      <div className="mt-2 text-[12px] text-[#3a3a3a] leading-relaxed">{desc}</div>
      <div className="mt-2 rounded-md bg-[#faf7f0] border border-[#f0ebdd] px-2.5 py-2">
        <div className="text-[10.5px] uppercase tracking-wider text-[#9aa0a6] mb-0.5">Mitigation</div>
        <div className="text-[12px] text-[#1a1a1a] leading-relaxed">{mitigation}</div>
      </div>
      <div className="mt-2 flex items-center gap-2 text-[11px] text-[#6b6b6b]">
        <span className="inline-flex items-center gap-1"><Icon name="user" size={11} /> {owner}</span>
        <span className="text-[#9aa0a6]">·</span>
        <span>Effort: <span className="text-[#1a1a1a]">{effort}</span></span>
        <div className="flex-1" />
        <button className="px-2 py-0.5 rounded text-[11px] text-[var(--accent)] hover:bg-[var(--accent)]/10">Create ticket</button>
      </div>
    </div>
  );
}

function DataView({ id }) {
  const data = {
    10: { story: "TEK-345", branch: "features/new-chat-frontend", base: "main", overall_risk: 68, top: [{ id: "r1", severity: "high", area: "src/chat/useChatStream.ts" }, { id: "r2", severity: "high", area: "src/chat/MessageList.tsx" }, { id: "r3", severity: "medium", area: "src/state/contextGraph.ts" }, { id: "r4", severity: "medium", area: "src/composer/Dropzone.tsx" }, { id: "r5", severity: "low", area: "package.json" }] },
    50: { sprint: "Sprint 42", day: 6, length: 10, committed: 55, done: 34, goal: "Attachments shippable behind a flag", lanes: [{ key: "TEK-140", state: "in_review", points: 8 }, { key: "TEK-152", state: "in_progress", points: 13 }, { key: "TEK-166", state: "blocked", points: 5 }, { key: "TEK-178", state: "done", points: 13 }] },
  }[id];
  return (
    <pre className="p-4 font-mono text-[11.5px] text-[#1a1a1a] whitespace-pre-wrap leading-relaxed">{JSON.stringify(data ?? { note: "No structured data view for this widget." }, null, 2)}</pre>
  );
}

function SprintPulse() {
  const total = 55, done = 34, day = 6, length = 10;
  const ideal = Math.round(total * (1 - day / length));
  const remaining = total - done;
  const pct = Math.round((done / total) * 100);
  const lanes = [
    { id: "TEK-140", title: "Allow attachments", points: 8, state: "In review", color: "#a05a00", bg: "#fef0d9" },
    { id: "TEK-152", title: "Multi-tenant roles", points: 13, state: "In progress", color: "#1c4f9c", bg: "#dceefc" },
    { id: "TEK-166", title: "Inline comments", points: 5, state: "Blocked", color: "#a83228", bg: "#fdecea" },
    { id: "TEK-178", title: "Audit log redesign", points: 13, state: "Done", color: "#1f7a3a", bg: "#e8f6ec" },
  ];
  const burndown = [55, 50, 47, 44, 40, 34, remaining];
  const max = 55;
  return (
    <div className="p-5">
      <div className="rounded-xl border border-[#ece7da] bg-white p-5">
        <div className="flex items-start gap-5">
          <div className="flex-1">
            <div className="text-[14px] font-semibold text-[#0e0e10] mb-1.5">Sprint 42 · Pulse</div>
            <p className="text-[12.5px] leading-[1.6] text-[#3a3a3a]">
              Day {day} of {length}. <strong>{done} of {total} points</strong> done or in review — slightly ahead of the ideal burndown line. Goal: <em>attachments shippable behind a flag</em>.
            </p>
            <div className="mt-3 flex items-center gap-3 text-[11.5px]">
              <span className="text-[#1f7a3a]">{done} done</span>
              <span className="text-[#a05a00]">{remaining} remaining</span>
              <span className="text-[#9aa0a6]">ideal ≈ {ideal}</span>
            </div>
          </div>
          <StatDial label="Progress" value={pct} max={100} valueLabel={`${pct}%`} color="#1f8a5b" />
        </div>
        {/* burndown */}
        <div className="mt-4 flex items-end gap-1.5 h-20">
          {burndown.map((v, i) => (
            <div key={i} className="flex-1 flex flex-col items-center justify-end h-full">
              <div className="w-full rounded-t" style={{ height: `${(v / max) * 100}%`, background: i === burndown.length - 1 ? "var(--accent)" : "#d8d2c1" }} />
            </div>
          ))}
        </div>
        <div className="text-[10.5px] text-[#9aa0a6] mt-1 text-center">Points remaining · day 0 → today</div>
      </div>

      <div className="mt-5 mb-2 text-[13px] font-semibold text-[#0e0e10]">Scope</div>
      <div className="flex flex-col gap-1.5">
        {lanes.map(l => (
          <div key={l.id} className="rounded-lg border border-[#ece7da] bg-white p-2.5 flex items-center gap-2.5">
            <div className="font-mono text-[11.5px] text-[#6b6b6b] shrink-0">{l.id}</div>
            <div className="flex-1 min-w-0 text-[12.5px] text-[#1a1a1a] truncate">{l.title}</div>
            <div className="text-[11px] text-[#9aa0a6] tabular-nums shrink-0">{l.points} pts</div>
            <div className="px-1.5 py-[1px] rounded-full text-[10.5px] font-medium shrink-0" style={{ background: l.bg, color: l.color }}>{l.state}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

// ---------- TWEAKS ----------
function TweaksFloater({ tweaks, setTweaks, onClose }) {
  const accents = ["#1f3df6", "#8b5cf6", "#ec4899", "#0e7c66", "#0e0e10"];
  return (
    <div className="fixed bottom-4 right-4 z-50 w-[280px] bg-white border border-[#ece7da] rounded-xl shadow-2xl">
      <div className="flex items-center px-3 py-2 border-b border-[#ece7da]">
        <div className="text-[12.5px] font-semibold">Tweaks</div>
        <div className="flex-1" />
        <button onClick={onClose} className="p-1 rounded hover:bg-black/[0.05]"><Icon name="x" size={13} /></button>
      </div>
      <div className="p-3 flex flex-col gap-3">
        <div>
          <div className="text-[11px] uppercase tracking-wider text-[#9aa0a6] mb-1.5">Accent</div>
          <div className="flex gap-2">
            {accents.map(a => (
              <button key={a} onClick={() => setTweaks(t => ({ ...t, accent: a }))}
                className={`w-7 h-7 rounded-full border-2 ${tweaks.accent === a ? "border-[#0e0e10]" : "border-transparent"}`}
                style={{ background: a }} />
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

// ---------- RESIZE HANDLE ----------
function ResizeHandle({ onResize }) {
  const startRef = useRef(null);
  const onDown = (e) => {
    startRef.current = e.clientX;
    const move = (ev) => {
      const dx = ev.clientX - startRef.current;
      startRef.current = ev.clientX;
      onResize(dx);
    };
    const up = () => {
      window.removeEventListener("mousemove", move);
      window.removeEventListener("mouseup", up);
      document.body.style.cursor = "";
      document.body.style.userSelect = "";
    };
    document.body.style.cursor = "col-resize";
    document.body.style.userSelect = "none";
    window.addEventListener("mousemove", move);
    window.addEventListener("mouseup", up);
  };
  return (
    <div onMouseDown={onDown}
      className="group relative w-[6px] shrink-0 cursor-col-resize bg-[#e8e3d8] hover:bg-[var(--accent)]/40 active:bg-[var(--accent)]/60 transition-colors flex items-center justify-center">
      <div className="w-[2px] h-7 rounded-full bg-[#b8b09a] group-hover:bg-white" />
    </div>
  );
}

// ---------- COLLAPSED WIDGET RAIL ----------
function CollapsedWidgetRail({ onExpand, widgets, active, setActive }) {
  return (
    <div className="w-[44px] shrink-0 border-l border-[#e8e3d8] bg-[#faf8f3] flex flex-col items-center py-2 gap-1">
      <button onClick={onExpand} title="Expand widget panel"
        className="w-8 h-8 flex items-center justify-center rounded-md text-[#5a5a5a] hover:bg-black/[0.05] hover:text-[#0e0e10]">
        <Icon name="chevron-left" size={16} />
      </button>
    </div>
  );
}

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