// STUDIO — Builder v2: drag-and-drop composition canvas with 12 new features
const { useState: useStBl, useRef: useRefBl } = React;
const TspBl = window.ArbiterTokens;

// ── Static data (outside component — no re-creation on render) ────────────────

const BL_CATS = [
  { id: 'matter',    label: 'Matter',    icon: '⊟', color: '#7C3AED' },
  { id: 'research',  label: 'Research',  icon: '◈', color: '#0891B2' },
  { id: 'discovery', label: 'Discovery', icon: '⟐', color: '#EA580C' },
  { id: 'calendar',  label: 'Calendar',  icon: '▦', color: '#65A30D' },
  { id: 'billing',   label: 'Billing',   icon: '⊟', color: '#B45309' },
  { id: 'exhibits',  label: 'Exhibits',  icon: '◈', color: '#C9A84C' },
  { id: 'people',    label: 'People',    icon: '◎', color: '#1D4ED8' },
  { id: 'recent',    label: 'Recent',    icon: '◎', color: '#64748B' },
  { id: 'templates', label: 'Templates', icon: '◆', color: '#C9A84C' },
];

const BL_ITEMS = {
  matter: [
    { id: 'mat-1', label: 'Case Caption Block',           sub: 'SDNY · #4421 · Meridian v. Coastal' },
    { id: 'mat-2', label: 'Counsel Signature Block',       sub: 'M. Kirkland + associates' },
    { id: 'mat-3', label: 'Certificate of Service',        sub: 'Fed. R. Civ. P. 5' },
    { id: 'mat-4', label: 'Certificate of Conference',     sub: 'Local Rule 37.2 (SDNY)' },
    { id: 'mat-5', label: 'Proposed Order',                sub: 'Draft · awaiting judicial signature' },
  ],
  research: [
    { id: 'res-1', label: 'Bell Atl. Corp. v. Twombly',   sub: '550 U.S. 544 (2007) · plausibility std.' },
    { id: 'res-2', label: 'Ashcroft v. Iqbal',            sub: '556 U.S. 662 (2009) · pleading standard' },
    { id: 'res-3', label: 'Fed. R. Civ. P. 26(b)(1)',     sub: 'Proportionality standard' },
    { id: 'res-4', label: 'Fed. R. Civ. P. 37(a)',        sub: 'Motion to compel — statutory basis' },
    { id: 'res-5', label: 'Local Rule 37.2 (SDNY)',        sub: 'Pre-motion conference requirement' },
    { id: 'res-6', label: 'Oppenheimer Fund v. Sanders',   sub: '437 U.S. 340 (1978)' },
  ],
  discovery: [
    { id: 'disc-1', label: 'PROD-003 Production Summary', sub: '1,847 docs · ESI collection' },
    { id: 'disc-2', label: 'Custodian List',               sub: '6 custodians identified' },
    { id: 'disc-3', label: 'Meet-and-Confer Log',          sub: 'Feb 14 – Apr 2, 2026 · 3 sessions' },
    { id: 'disc-4', label: 'Privilege Log Summary',        sub: '47 documents withheld as privileged' },
    { id: 'disc-5', label: 'TAR Methodology Statement',    sub: 'Seed set · 87% recall verified' },
    { id: 'disc-6', label: 'Deficiency Letter — Mar 18',   sub: 'Coastal non-compliance notice' },
  ],
  calendar: [
    { id: 'cal-1', label: 'Filing Deadline — Apr 25',     sub: 'Motion to Compel · SDNY' },
    { id: 'cal-2', label: 'Discovery Close — Jun 30',     sub: 'Per scheduling order' },
    { id: 'cal-3', label: 'Scheduling Order Reference',    sub: 'Hon. J. Torres · Nov 12, 2025' },
    { id: 'cal-4', label: 'Meet & Confer Timeline',        sub: 'Mar 5, 18 · Apr 2, 2026' },
  ],
  billing: [
    { id: 'bil-1', label: 'Attorney Fees to Date',         sub: '$142,800 · Matter #4421' },
    { id: 'bil-2', label: 'Hourly Rate Disclosure',        sub: 'Partner / Associate / Paralegal rates' },
    { id: 'bil-3', label: 'Budget Status',                 sub: '$48,200 of $191,000 remaining' },
  ],
  exhibits: [
    { id: 'exh-1', label: 'EXH-047 — Coastal Email',      sub: 'Oct 14, 2025 · Pelican Bay reference' },
    { id: 'exh-2', label: 'EXH-048 — Valuation Report',   sub: 'Third-party appraisal · Dec 2025' },
    { id: 'exh-3', label: 'EXH-049 — Supplier Contract',  sub: 'Jan 3, 2024 · produced in PROD-003' },
    { id: 'exh-4', label: 'Exhibit Index',                 sub: 'EXH-001 through EXH-049' },
  ],
  people: [
    { id: 'ppl-1', label: 'M. Kirkland — Partner',         sub: 'Lead counsel · Meridian Oil Corp.' },
    { id: 'ppl-2', label: 'S. Chen — Associate',           sub: 'Discovery & research lead' },
    { id: 'ppl-3', label: 'Dr. T. Nguyen — Expert',        sub: 'Petroleum valuation · retained' },
    { id: 'ppl-4', label: 'Coastal Counsel',               sub: 'Opposing counsel · 3 attorneys' },
    { id: 'ppl-5', label: 'Hon. J. Torres',                sub: 'SDNY · Commercial Division' },
  ],
};

// Feature 1 — Document Templates
const BL_TEMPLATES = [
  {
    id: 'motion-compel',
    name: 'Motion to Compel',
    icon: '▶',
    desc: '8 blocks · Full discovery motion',
    blocks: [
      { id: 'mat-1', catId: 'matter',    color: '#7C3AED', catIcon: '⊟', label: 'Case Caption Block',           sub: 'SDNY · #4421', status: 'ready' },
      { id: 'mat-4', catId: 'matter',    color: '#7C3AED', catIcon: '⊟', label: 'Certificate of Conference',     sub: 'Local Rule 37.2', status: 'draft' },
      { id: 'res-4', catId: 'research',  color: '#0891B2', catIcon: '◈', label: 'Fed. R. Civ. P. 37(a)',        sub: 'Statutory basis', status: 'ready' },
      { id: 'disc-3',catId: 'discovery', color: '#EA580C', catIcon: '⟐', label: 'Meet-and-Confer Log',          sub: 'Feb 14 – Apr 2', status: 'ready' },
      { id: 'disc-1',catId: 'discovery', color: '#EA580C', catIcon: '⟐', label: 'PROD-003 Production Summary',  sub: '1,847 docs', status: 'review' },
      { id: 'res-1', catId: 'research',  color: '#0891B2', catIcon: '◈', label: 'Bell Atl. Corp. v. Twombly',   sub: '550 U.S. 544', status: 'draft' },
      { id: 'cal-1', catId: 'calendar',  color: '#65A30D', catIcon: '▦', label: 'Filing Deadline — Apr 25',     sub: 'Motion to Compel', status: 'ready' },
      { id: 'mat-2', catId: 'matter',    color: '#7C3AED', catIcon: '⊟', label: 'Counsel Signature Block',      sub: 'M. Kirkland', status: 'draft' },
    ],
  },
  {
    id: 'brief-shell',
    name: 'Appellate Brief Shell',
    icon: '◈',
    desc: '6 blocks · Standard brief framework',
    blocks: [
      { id: 'mat-1', catId: 'matter',   color: '#7C3AED', catIcon: '⊟', label: 'Case Caption Block',          sub: 'SDNY · #4421', status: 'draft' },
      { id: 'res-3', catId: 'research', color: '#0891B2', catIcon: '◈', label: 'Fed. R. Civ. P. 26(b)(1)',    sub: 'Proportionality', status: 'draft' },
      { id: 'res-2', catId: 'research', color: '#0891B2', catIcon: '◈', label: 'Ashcroft v. Iqbal',           sub: '556 U.S. 662', status: 'draft' },
      { id: 'exh-4', catId: 'exhibits', color: '#C9A84C', catIcon: '◈', label: 'Exhibit Index',               sub: 'EXH-001 – EXH-049', status: 'draft' },
      { id: 'mat-3', catId: 'matter',   color: '#7C3AED', catIcon: '⊟', label: 'Certificate of Service',     sub: 'Fed. R. Civ. P. 5', status: 'draft' },
      { id: 'mat-2', catId: 'matter',   color: '#7C3AED', catIcon: '⊟', label: 'Counsel Signature Block',    sub: 'M. Kirkland', status: 'draft' },
    ],
  },
  {
    id: 'discovery-pkg',
    name: 'Discovery Package',
    icon: '⟐',
    desc: '7 blocks · Production + privilege + TAR',
    blocks: [
      { id: 'disc-1',catId: 'discovery', color: '#EA580C', catIcon: '⟐', label: 'PROD-003 Production Summary', sub: '1,847 docs', status: 'ready' },
      { id: 'disc-2',catId: 'discovery', color: '#EA580C', catIcon: '⟐', label: 'Custodian List',              sub: '6 custodians', status: 'ready' },
      { id: 'disc-5',catId: 'discovery', color: '#EA580C', catIcon: '⟐', label: 'TAR Methodology Statement',  sub: '87% recall', status: 'draft' },
      { id: 'disc-4',catId: 'discovery', color: '#EA580C', catIcon: '⟐', label: 'Privilege Log Summary',       sub: '47 docs', status: 'review' },
      { id: 'disc-3',catId: 'discovery', color: '#EA580C', catIcon: '⟐', label: 'Meet-and-Confer Log',        sub: 'Feb 14 – Apr 2', status: 'ready' },
      { id: 'ppl-1', catId: 'people',    color: '#1D4ED8', catIcon: '◎', label: 'M. Kirkland — Partner',       sub: 'Lead counsel', status: 'ready' },
      { id: 'cal-2', catId: 'calendar',  color: '#65A30D', catIcon: '▦', label: 'Discovery Close — Jun 30',   sub: 'Per scheduling order', status: 'ready' },
    ],
  },
  {
    id: 'expert-pkg',
    name: 'Expert Witness Package',
    icon: '◎',
    desc: '5 blocks · Expert + valuation + exhibits',
    blocks: [
      { id: 'ppl-3', catId: 'people',   color: '#1D4ED8', catIcon: '◎', label: 'Dr. T. Nguyen — Expert',      sub: 'Petroleum valuation', status: 'draft' },
      { id: 'exh-2', catId: 'exhibits', color: '#C9A84C', catIcon: '◈', label: 'EXH-048 — Valuation Report', sub: 'Dec 2025', status: 'ready' },
      { id: 'bil-1', catId: 'billing',  color: '#B45309', catIcon: '⊟', label: 'Attorney Fees to Date',       sub: '$142,800', status: 'ready' },
      { id: 'res-6', catId: 'research', color: '#0891B2', catIcon: '◈', label: 'Oppenheimer Fund v. Sanders', sub: '437 U.S. 340', status: 'draft' },
      { id: 'mat-3', catId: 'matter',   color: '#7C3AED', catIcon: '⊟', label: 'Certificate of Service',     sub: 'Fed. R. Civ. P. 5', status: 'draft' },
    ],
  },
];

// Feature 2 — Block status configuration
const BL_STATUS = {
  draft:  { label: 'Draft',  background: 'transparent',          color: '#64748B', borderColor: '#64748B44' },
  ready:  { label: 'Ready',  background: 'rgba(5,150,105,0.08)', color: '#059669', borderColor: 'rgba(5,150,105,0.25)' },
  review: { label: 'Review', background: 'rgba(180,83,9,0.08)',  color: '#B45309', borderColor: 'rgba(180,83,9,0.25)' },
};

function statusBadgeStyle(status) {
  const cfg = BL_STATUS[status] || BL_STATUS.draft;
  return { fontSize: '8px', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.06em', padding: '1px 5px', borderRadius: '3px', background: cfg.background, color: cfg.color, border: `1px solid ${cfg.borderColor}`, flexShrink: 0 };
}

// ── Component ──────────────────────────────────────────────────────────────────
function StudioBuilder() {
  const st = window.__st;

  // ── Core state ──────────────────────────────────────────────────────────────
  const [activeCat,  setActiveCat]  = useStBl('matter');
  const [searchQ,    setSearchQ]    = useStBl('');
  const [blocks,     setBlocks]     = useStBl([]);
  const [selIdx,     setSelIdx]     = useStBl(null);
  const [slotIdx,    setSlotIdx]    = useStBl(null);
  const [emptyOver,  setEmptyOver]  = useStBl(false);
  const [exported,   setExported]   = useStBl(false);

  // Feature state
  const [compact,    setCompact]    = useStBl(false);        // Feature 6 — Compact view
  const [numbered,   setNumbered]   = useStBl(false);        // Feature 7 — Outline numbering
  const [canvasQ,    setCanvasQ]    = useStBl('');            // Feature 8 — Canvas filter
  const [history,    setHistory]    = useStBl([]);            // Feature 9 — Undo
  const [multiMode,  setMultiMode]  = useStBl(false);        // Feature 10 — Multi-select mode
  const [multiSel,   setMultiSel]   = useStBl(new Set());    // Feature 10 — Selected bk ids
  const [recent,     setRecent]     = useStBl([]);            // Feature 5 — Recently used
  const [saved,      setSaved]      = useStBl([]);            // Feature 12 — Saved compositions
  const [saveName,   setSaveName]   = useStBl('');            // Feature 12 — Save name input
  const [saveOpen,   setSaveOpen]   = useStBl(false);        // Feature 12 — Save panel open
  const [editLbl,    setEditLbl]    = useStBl(false);        // Feature 4 — Label editing

  const drag = useRefBl(null);

  // ── Derived ─────────────────────────────────────────────────────────────────
  const catMeta    = BL_CATS.find(c => c.id === activeCat) || BL_CATS[0];
  const rawItems   = activeCat === 'recent' ? recent : activeCat === 'templates' ? [] : (BL_ITEMS[activeCat] || []);
  const palette    = searchQ
    ? rawItems.filter(it => it.label.toLowerCase().includes(searchQ.toLowerCase()) || it.sub.toLowerCase().includes(searchQ.toLowerCase()))
    : rawItems;
  const selBlock   = selIdx !== null ? blocks[selIdx] : null;
  const cntBlocks  = blocks.filter(b => !b.isDivider && !b.isPlaceholder);
  const isFiltered = canvasQ.length > 0;

  function blockMatches(b) {
    if (!isFiltered) return true;
    const q = canvasQ.toLowerCase();
    return b.label.toLowerCase().includes(q) || (b.sub && b.sub.toLowerCase().includes(q));
  }

  // Build outline numbers: resets at each divider
  let _n = 0;
  const blockNums = blocks.map(b => {
    if (b.isDivider) { _n = 0; return null; }
    _n++;
    return _n;
  });

  // ── History (Feature 9 — Undo) ───────────────────────────────────────────────
  function snap() {
    const snapshot = blocks;
    setHistory(prev => [...prev.slice(-19), snapshot]);
  }

  function undo() {
    if (!history.length) return;
    const last = history[history.length - 1];
    setHistory(prev => prev.slice(0, -1));
    setBlocks(last);
    setSelIdx(null);
    setMultiSel(new Set());
  }

  // ── Drag ─────────────────────────────────────────────────────────────────────
  function onPaletteDragStart(e, item) {
    drag.current = { from: 'palette', item, catId: activeCat, color: catMeta.color, catIcon: catMeta.icon };
    e.dataTransfer.setData('text/plain', 'dnd');
    e.dataTransfer.effectAllowed = 'copy';
  }

  function onBlockDragStart(e, i) {
    e.stopPropagation();
    drag.current = { from: 'canvas', idx: i };
    e.dataTransfer.setData('text/plain', 'dnd');
    e.dataTransfer.effectAllowed = 'move';
  }

  function makeBlock(src) {
    return { bk: Date.now() + Math.random(), ...src.item, catId: src.catId, color: src.color, catIcon: src.catIcon, note: '', status: 'draft' };
  }

  function addToRecent(src) {
    setRecent(prev => {
      const filtered = prev.filter(r => r.id !== src.item.id);
      return [{ ...src.item, catId: src.catId, color: src.color, catIcon: src.catIcon }, ...filtered].slice(0, 8);
    });
  }

  function dropAtSlot(e, toIdx) {
    e.preventDefault();
    e.stopPropagation();
    setSlotIdx(null);
    setEmptyOver(false);
    const src = drag.current;
    if (!src) return;
    snap();
    if (src.from === 'palette') {
      addToRecent(src);
      const nb = makeBlock(src);
      setBlocks(prev => { const c = [...prev]; c.splice(toIdx, 0, nb); return c; });
      setSelIdx(toIdx);
    } else {
      const fi = src.idx;
      if (fi === toIdx || fi === toIdx - 1) { drag.current = null; return; }
      setBlocks(prev => {
        const c = [...prev];
        const [moved] = c.splice(fi, 1);
        c.splice(toIdx > fi ? toIdx - 1 : toIdx, 0, moved);
        return c;
      });
      setSelIdx(toIdx > fi ? toIdx - 1 : toIdx);
    }
    drag.current = null;
  }

  function dropOnEmpty(e) {
    e.preventDefault();
    setEmptyOver(false);
    const src = drag.current;
    if (!src || src.from !== 'palette') { drag.current = null; return; }
    snap();
    addToRecent(src);
    setBlocks([makeBlock(src)]);
    setSelIdx(0);
    drag.current = null;
  }

  // ── Block mutations ──────────────────────────────────────────────────────────
  function removeBlock(i) {
    snap();
    setBlocks(prev => prev.filter((_, idx) => idx !== i));
    if (selIdx === i) setSelIdx(null);
    else if (selIdx !== null && selIdx > i) setSelIdx(selIdx - 1);
  }

  function nudge(i, dir) {
    const j = i + dir;
    if (j < 0 || j >= blocks.length) return;
    snap();
    setBlocks(prev => { const c = [...prev]; [c[i], c[j]] = [c[j], c[i]]; return c; });
    setSelIdx(j);
  }

  // Feature 3 — Duplicate block
  function duplicate(i) {
    snap();
    const copy = { ...blocks[i], bk: Date.now() + Math.random() };
    setBlocks(prev => { const c = [...prev]; c.splice(i + 1, 0, copy); return c; });
    setSelIdx(i + 1);
  }

  function insertDivider() {
    snap();
    const at = selIdx !== null ? selIdx + 1 : blocks.length;
    setBlocks(prev => { const c = [...prev]; c.splice(at, 0, { bk: Date.now(), isDivider: true, label: 'Section', note: '' }); return c; });
    setSelIdx(null);
  }

  // Feature 11 — Placeholder block
  function addPlaceholder() {
    snap();
    const at = selIdx !== null ? selIdx + 1 : blocks.length;
    const ph = { bk: Date.now(), isPlaceholder: true, label: 'Placeholder Section', sub: 'TBD — content to be determined', catId: 'tbd', color: '#94A3B8', catIcon: '⊡', note: '', status: 'draft' };
    setBlocks(prev => { const c = [...prev]; c.splice(at, 0, ph); return c; });
    setSelIdx(at);
  }

  function patchBlock(i, patch) {
    setBlocks(prev => prev.map((b, idx) => idx === i ? { ...b, ...patch } : b));
  }

  // Feature 10 — Multi-select
  function toggleSel(bk) {
    setMultiSel(prev => { const s = new Set(prev); if (s.has(bk)) s.delete(bk); else s.add(bk); return s; });
  }

  function bulkDelete() {
    snap();
    setBlocks(prev => prev.filter(b => !multiSel.has(b.bk)));
    setMultiSel(new Set());
    setSelIdx(null);
  }

  // Feature 1 — Templates
  function loadTemplate(tmpl) {
    snap();
    const newBlocks = tmpl.blocks.map(b => ({ ...b, bk: Date.now() + Math.random(), note: '' }));
    if (blocks.length > 0) {
      setBlocks(prev => [...prev, { bk: Date.now(), isDivider: true, label: tmpl.name, note: '' }, ...newBlocks]);
    } else {
      setBlocks(newBlocks);
    }
    setSelIdx(null);
  }

  // Feature 12 — Saved compositions
  function saveComposition() {
    if (!saveName.trim() || !blocks.length) return;
    const entry = { id: Date.now(), name: saveName.trim(), blocks: [...blocks], ts: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) };
    setSaved(prev => [entry, ...prev].slice(0, 5));
    setSaveName('');
    setSaveOpen(false);
  }

  function loadSaved(comp) {
    snap();
    setBlocks(comp.blocks.map(b => ({ ...b, bk: Date.now() + Math.random() })));
    setSelIdx(null);
    setMultiSel(new Set());
  }

  function doExport() {
    if (!cntBlocks.length) return;
    setExported(true);
    setTimeout(() => setExported(false), 2500);
  }

  // ── Render helpers ───────────────────────────────────────────────────────────
  const ph   = { fontSize: '9px', fontWeight: 700, color: TspBl.color.text.tertiary, textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: '5px' };
  const pnl  = { ...st.card, marginBottom: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' };
  const sep  = `1px solid ${TspBl.color.border.light}`;
  const ibtn = { background: 'none', border: 'none', cursor: 'pointer', color: TspBl.color.text.tertiary, fontSize: '10px', padding: '1px 2px', lineHeight: 1, fontFamily: TspBl.font.family };

  function slotSty(active) {
    return { height: active ? '34px' : '6px', borderRadius: '4px', marginBottom: '2px', background: active ? st.goldBg : 'transparent', border: `2px dashed ${active ? st.gold : 'transparent'}`, transition: 'all 0.12s', display: 'flex', alignItems: 'center', justifyContent: 'center' };
  }

  function blkSty(blk, selected) {
    const dim = isFiltered && !blockMatches(blk);
    return {
      display: 'flex', alignItems: 'stretch',
      borderRadius: compact ? '5px' : '7px', marginBottom: '2px',
      background: TspBl.color.bg.card,
      border: `1.5px solid ${selected ? blk.color + '66' : TspBl.color.border.light}`,
      boxShadow: selected ? `0 0 0 2px ${blk.color}18` : 'none',
      cursor: 'pointer', overflow: 'hidden', transition: 'all 0.12s', userSelect: 'none',
      opacity: dim ? 0.25 : 1,
    };
  }

  // ── Render ───────────────────────────────────────────────────────────────────
  return (
    <div style={{ display: 'grid', gridTemplateColumns: '220px 1fr 230px', gap: '14px', minHeight: '560px' }}>

      {/* ═══ LEFT: Source Palette ═══════════════════════════════════════════ */}
      <div style={pnl}>
        <div style={{ padding: '10px 12px 8px', borderBottom: sep, flexShrink: 0 }}>
          <div style={{ fontSize: '11px', fontWeight: 700, color: TspBl.color.text.primary, marginBottom: '7px' }}>Source Palette</div>
          <input
            value={searchQ}
            onChange={e => setSearchQ(e.target.value)}
            placeholder="Search all items…"
            style={{ width: '100%', padding: '5px 8px', fontSize: '11px', borderRadius: '5px', border: `1px solid ${TspBl.color.border.light}`, background: TspBl.color.bg.secondary, color: TspBl.color.text.primary, fontFamily: TspBl.font.family, outline: 'none', boxSizing: 'border-box' }}
          />
        </div>

        {/* Category pills */}
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '3px', padding: '8px 10px', borderBottom: sep, flexShrink: 0 }}>
          {BL_CATS.map(cat => (
            <button key={cat.id}
              onClick={() => { setActiveCat(cat.id); setSearchQ(''); }}
              style={{ padding: '3px 7px', fontSize: '10px', fontWeight: activeCat === cat.id ? 700 : 400, borderRadius: '4px', border: `1px solid ${activeCat === cat.id ? cat.color + '55' : TspBl.color.border.light}`, background: activeCat === cat.id ? cat.color + '12' : 'transparent', color: activeCat === cat.id ? cat.color : TspBl.color.text.tertiary, cursor: 'pointer', fontFamily: TspBl.font.family, transition: 'all 0.1s' }}
            >{cat.label}</button>
          ))}
        </div>

        {/* Palette body */}
        <div style={{ flex: 1, overflowY: 'auto', padding: '7px 8px' }}>

          {/* Feature 1 — Templates view */}
          {activeCat === 'templates' && BL_TEMPLATES.map(tmpl => (
            <div key={tmpl.id} style={{ padding: '9px 10px', borderRadius: '6px', marginBottom: '6px', background: st.goldBg, border: `1px solid ${st.goldBorder}` }}>
              <div style={{ display: 'flex', alignItems: 'flex-start', gap: '6px', marginBottom: '6px' }}>
                <span style={{ fontSize: '14px', color: st.gold, flexShrink: 0, marginTop: '1px' }}>{tmpl.icon}</span>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: '11px', fontWeight: 700, color: TspBl.color.text.primary, lineHeight: 1.3 }}>{tmpl.name}</div>
                  <div style={{ fontSize: '9px', color: TspBl.color.text.tertiary }}>{tmpl.desc}</div>
                </div>
              </div>
              <button onClick={() => loadTemplate(tmpl)} style={{ ...st.btnPrimary, width: '100%', fontSize: '10px', padding: '4px 8px' }}>
                Load{blocks.length > 0 ? ' (append)' : ''}
              </button>
            </div>
          ))}

          {/* Feature 5 — Recent: empty state */}
          {activeCat === 'recent' && recent.length === 0 && (
            <div style={{ textAlign: 'center', padding: '24px 10px', color: TspBl.color.text.tertiary, fontSize: '11px', lineHeight: 1.8 }}>
              Items you drag to the canvas<br/>will appear here.
            </div>
          )}

          {/* Normal & Recent draggable items */}
          {activeCat !== 'templates' && palette.map(item => (
            <div
              key={item.id}
              draggable
              onDragStart={e => onPaletteDragStart(e, item)}
              style={{ display: 'flex', alignItems: 'flex-start', gap: '8px', padding: '7px 9px', borderRadius: '6px', marginBottom: '3px', background: TspBl.color.bg.secondary, border: `1px solid ${TspBl.color.border.light}`, cursor: 'grab', userSelect: 'none', transition: 'border-color 0.1s, background 0.1s' }}
              onMouseEnter={e => { e.currentTarget.style.borderColor = catMeta.color + '55'; e.currentTarget.style.background = catMeta.color + '08'; }}
              onMouseLeave={e => { e.currentTarget.style.borderColor = TspBl.color.border.light; e.currentTarget.style.background = TspBl.color.bg.secondary; }}
            >
              <span style={{ fontSize: '12px', color: catMeta.color, flexShrink: 0, marginTop: '1px' }}>{catMeta.icon}</span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: '11px', fontWeight: 500, color: TspBl.color.text.primary, lineHeight: 1.3 }}>{item.label}</div>
                <div style={{ fontSize: '9px', color: TspBl.color.text.tertiary, marginTop: '1px' }}>{item.sub}</div>
              </div>
              <span style={{ color: TspBl.color.text.tertiary, fontSize: '10px', flexShrink: 0, marginTop: '2px' }}>⠿</span>
            </div>
          ))}

          {activeCat !== 'templates' && activeCat !== 'recent' && palette.length === 0 && (
            <div role="status" style={{ textAlign: 'center', padding: '16px 10px', color: TspBl.color.text.tertiary, fontSize: '11px' }}>No matches</div>
          )}
        </div>

        <div style={{ padding: '8px 12px', borderTop: sep, flexShrink: 0 }}>
          <div style={{ fontSize: '9px', color: TspBl.color.text.tertiary, textAlign: 'center', lineHeight: 1.6 }}>Drag items onto the canvas to compose your document</div>
        </div>
      </div>

      {/* ═══ CENTER: Composition Canvas ══════════════════════════════════════ */}
      <div style={{ display: 'flex', flexDirection: 'column', minWidth: 0 }}>

        {/* Toolbar */}
        <div style={{ ...st.card, marginBottom: '10px', padding: '7px 10px', display: 'flex', alignItems: 'center', gap: '5px', flexWrap: 'wrap', flexShrink: 0 }}>
          <span style={{ fontSize: '11px', fontWeight: 700, color: TspBl.color.text.primary, whiteSpace: 'nowrap' }}>◆ Canvas</span>
          <span style={{ fontSize: '10px', color: TspBl.color.text.tertiary, fontFamily: TspBl.font.mono }}>{cntBlocks.length}b</span>

          <div style={{ width: '1px', height: '14px', background: TspBl.color.border.light, margin: '0 2px' }} />

          {/* Insert */}
          <button onClick={addPlaceholder} title="Add TBD placeholder" style={{ ...st.btnGhost, fontSize: '10px', padding: '2px 7px' }}>⊡ TBD</button>
          <button onClick={insertDivider} title="Insert section break" style={{ ...st.btnGhost, fontSize: '10px', padding: '2px 7px' }}>÷ Break</button>

          <div style={{ width: '1px', height: '14px', background: TspBl.color.border.light, margin: '0 2px' }} />

          {/* Feature 9 — Undo */}
          <button onClick={undo} disabled={!history.length} title={`Undo (${history.length} available)`}
            style={{ ...st.btnGhost, fontSize: '10px', padding: '2px 7px', opacity: history.length ? 1 : 0.35 }}>
            {history.length > 0 && <span style={{ marginLeft: '2px', fontSize: '9px', color: st.cobalt, fontFamily: TspBl.font.mono }}>{history.length}</span>}
          </button>

          <div style={{ width: '1px', height: '14px', background: TspBl.color.border.light, margin: '0 2px' }} />

          {/* Feature 8 — Canvas filter */}
          <input
            value={canvasQ}
            onChange={e => setCanvasQ(e.target.value)}
            placeholder="Filter blocks…"
            style={{ width: '108px', padding: '3px 7px', fontSize: '10px', borderRadius: '4px', border: `1px solid ${canvasQ ? st.gold : TspBl.color.border.light}`, background: TspBl.color.bg.secondary, color: TspBl.color.text.primary, fontFamily: TspBl.font.family, outline: 'none' }}
          />

          <div style={{ width: '1px', height: '14px', background: TspBl.color.border.light, margin: '0 2px' }} />

          {/* Feature 6 — Compact, Feature 7 — Numbering, Feature 10 — Multi */}
          {[
            [compact,   () => setCompact(v => !v),   '≡',  'Compact view',    st.gold,    st.goldBg],
            [numbered,  () => setNumbered(v => !v),  '1.', 'Outline numbers', st.gold,    st.goldBg],
            [multiMode, () => { setMultiMode(v => !v); setMultiSel(new Set()); setSelIdx(null); }, 'ok', 'Multi-select', st.cobalt, 'rgba(29,78,216,0.08)'],
          ].map(([active, toggle, icon, title, activeColor, activeBg]) => (
            <button key={icon} onClick={toggle} title={title}
              style={{ ...st.btnGhost, fontSize: '10px', padding: '2px 7px', background: active ? activeBg : 'transparent', color: active ? activeColor : TspBl.color.text.tertiary, border: `1px solid ${active ? activeColor + '55' : TspBl.color.border.light}`, transition: 'all 0.1s' }}
            >{icon}</button>
          ))}

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

          <button onClick={() => { snap(); setBlocks([]); setSelIdx(null); setMultiSel(new Set()); }} style={{ ...st.btnGhost, fontSize: '10px', padding: '2px 7px', color: TspBl.color.text.tertiary }}>Clear</button>
          <button onClick={doExport} style={{ ...st.btnPrimary, fontSize: '10px', padding: '4px 10px', ...(exported ? { background: st.emerald } : {}), transition: 'background 0.3s' }}>
            {exported ? 'ok Sent' : 'Send to Editor →'}
          </button>
        </div>

        {/* Feature 10 — Multi-select action bar */}
        {multiMode && multiSel.size > 0 && (
          <div style={{ padding: '6px 10px', marginBottom: '8px', background: 'rgba(29,78,216,0.06)', border: `1px solid ${st.cobalt}33`, borderRadius: TspBl.radius.lg, display: 'flex', alignItems: 'center', gap: '8px', flexShrink: 0 }}>
            <span style={{ fontSize: '11px', fontWeight: 600, color: st.cobalt }}>{multiSel.size} block{multiSel.size !== 1 ? 's' : ''} selected</span>
            <button onClick={() => setMultiSel(new Set(blocks.filter(b => !b.isDivider).map(b => b.bk)))} style={{ ...st.btnGhost, fontSize: '10px', color: st.cobalt }}>Select all</button>
            <div style={{ flex: 1 }} />
            <button onClick={() => setMultiSel(new Set())} style={{ ...st.btnGhost, fontSize: '10px' }}>Clear</button>
            <button onClick={bulkDelete} style={{ ...st.btnGhost, fontSize: '10px', color: st.crimson }}>x Delete {multiSel.size}</button>
          </div>
        )}

        {/* Drop canvas */}
        <div
          style={{ flex: 1, overflowY: 'auto', borderRadius: TspBl.radius.lg, border: `2px dashed ${emptyOver ? st.gold : TspBl.color.border.light}`, background: emptyOver ? st.goldBg : TspBl.color.bg.secondary, transition: 'all 0.15s', padding: blocks.length === 0 ? '0' : '10px 10px 6px', minHeight: '440px' }}
          onDragOver={e => { e.preventDefault(); if (blocks.length === 0) setEmptyOver(true); }}
          onDragLeave={() => setEmptyOver(false)}
          onDrop={blocks.length === 0 ? dropOnEmpty : undefined}
        >
          {/* Empty state */}
          {blocks.length === 0 && (
            <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', minHeight: '440px', gap: '12px', pointerEvents: 'none' }}>
              <div style={{ fontSize: '40px', opacity: 0.18, lineHeight: 1 }}>◆</div>
              <div style={{ fontSize: '14px', fontWeight: 600, color: TspBl.color.text.secondary }}>Drop items here to start composing</div>
              <div style={{ fontSize: '11px', color: TspBl.color.text.tertiary }}>Drag from the palette — or load a Template</div>
            </div>
          )}

          {/* Block list */}
          {blocks.map((blk, i) => {
            const isSel   = multiMode ? multiSel.has(blk.bk) : selIdx === i;
            const num     = blockNums[i];
            const matches = !blk.isDivider && blockMatches(blk);

            return (
              <div key={blk.bk}>
                {/* Drop slot before */}
                <div
                  style={slotSty(slotIdx === i)}
                  onDragOver={e => { e.preventDefault(); e.stopPropagation(); setSlotIdx(i); }}
                  onDragLeave={() => setSlotIdx(null)}
                  onDrop={e => dropAtSlot(e, i)}
                >
                  {slotIdx === i && <span style={{ fontSize: '10px', color: st.gold, fontWeight: 600 }}>Drop here</span>}
                </div>

                {/* ── Divider ── */}
                {blk.isDivider ? (
                  <div
                    draggable
                    onDragStart={e => onBlockDragStart(e, i)}
                    onClick={() => !multiMode && setSelIdx(selIdx === i ? null : i)}
                    style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '5px 10px', borderRadius: '4px', marginBottom: '2px', border: `1px dashed ${TspBl.color.border.light}`, background: !multiMode && selIdx === i ? st.goldBg : 'transparent', cursor: 'pointer', transition: 'background 0.1s' }}
                  >
                    <div style={{ flex: 1, height: '1px', background: TspBl.color.border.light }} />
                    <span style={{ fontSize: '9px', fontWeight: 700, color: TspBl.color.text.tertiary, textTransform: 'uppercase', letterSpacing: '0.08em', flexShrink: 0 }}>{blk.label}</span>
                    <div style={{ flex: 1, height: '1px', background: TspBl.color.border.light }} />
                    <button onClick={e => { e.stopPropagation(); removeBlock(i); }} style={{ ...ibtn, fontSize: '11px' }}><Icons.X size={11}/></button>
                  </div>
                ) : (
                  /* ── Content / Placeholder block ── */
                  <div
                    draggable={!multiMode}
                    onDragStart={e => !multiMode && onBlockDragStart(e, i)}
                    onClick={() => multiMode ? toggleSel(blk.bk) : setSelIdx(selIdx === i ? null : i)}
                    style={blkSty(blk, isSel)}
                  >
                    {/* Feature 10 — Checkbox (multi-select mode) */}
                    {multiMode && (
                      <div style={{ display: 'flex', alignItems: 'center', padding: '0 8px', background: isSel ? st.cobalt + '12' : 'transparent', flexShrink: 0 }}>
                        <div style={{ width: '14px', height: '14px', borderRadius: '3px', border: `2px solid ${isSel ? st.cobalt : TspBl.color.border.light}`, background: isSel ? st.cobalt : 'transparent', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                          {isSel && <span style={{ fontSize: '9px', color: '#fff', fontWeight: 700, lineHeight: 1 }}><Icons.Check size={11}/></span>}
                        </div>
                      </div>
                    )}

                    {/* Color strip */}
                    <div style={{ width: '4px', background: blk.isPlaceholder ? '#94A3B8' : blk.color, flexShrink: 0 }} />

                    {/* Feature 6 — Compact body */}
                    {compact ? (
                      <div style={{ flex: 1, padding: '0 10px', display: 'flex', alignItems: 'center', gap: '7px', minWidth: 0, height: '34px' }}>
                        {numbered && num !== null && <span style={{ fontSize: '9px', fontWeight: 700, color: TspBl.color.text.tertiary, fontFamily: TspBl.font.mono, flexShrink: 0 }}>{num}.</span>}
                        <span style={{ fontSize: '10px', color: blk.isPlaceholder ? '#94A3B8' : blk.color, flexShrink: 0 }}>{blk.catIcon}</span>
                        <span style={{ fontSize: '11px', fontWeight: 500, color: blk.isPlaceholder ? TspBl.color.text.tertiary : TspBl.color.text.primary, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontStyle: blk.isPlaceholder ? 'italic' : 'normal' }}>{blk.label}</span>
                        {blk.status && !blk.isPlaceholder && <span style={statusBadgeStyle(blk.status)}>{BL_STATUS[blk.status].label}</span>}
                      </div>
                    ) : (
                      /* Full body */
                      <div style={{ flex: 1, padding: '8px 10px', minWidth: 0 }}>
                        <div style={{ display: 'flex', alignItems: 'center', gap: '5px', marginBottom: '2px' }}>
                          {numbered && num !== null && <span style={{ fontSize: '9px', fontWeight: 700, color: TspBl.color.text.tertiary, fontFamily: TspBl.font.mono, flexShrink: 0 }}>{num}.</span>}
                          <span style={{ fontSize: '10px', color: blk.isPlaceholder ? '#94A3B8' : blk.color }}>{blk.catIcon}</span>
                          <span style={{ fontSize: '9px', fontWeight: 700, color: blk.isPlaceholder ? '#94A3B8' : blk.color, textTransform: 'uppercase', letterSpacing: '0.07em', flex: 1 }}>{blk.catId}</span>
                          {blk.status && !blk.isPlaceholder && <span style={statusBadgeStyle(blk.status)}>{BL_STATUS[blk.status].label}</span>}
                        </div>
                        <div style={{ fontSize: '12px', fontWeight: 600, color: blk.isPlaceholder ? TspBl.color.text.tertiary : TspBl.color.text.primary, lineHeight: 1.3, marginBottom: '1px', fontStyle: blk.isPlaceholder ? 'italic' : 'normal' }}>{blk.label}</div>
                        {!blk.isPlaceholder && <div style={{ fontSize: '10px', color: TspBl.color.text.tertiary }}>{blk.sub}</div>}
                        {blk.note ? <div style={{ fontSize: '10px', color: TspBl.color.text.secondary, marginTop: '4px', paddingTop: '4px', borderTop: `1px solid ${TspBl.color.border.light}`, fontStyle: 'italic' }}>{blk.note}</div> : null}
                      </div>
                    )}

                    {/* Block controls (hidden in multi-select mode) */}
                    {!multiMode && (
                      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '1px', padding: '4px 6px', flexShrink: 0 }}>
                        <button onClick={e => { e.stopPropagation(); nudge(i, -1); }} style={ibtn}>▲</button>
                        <span style={{ color: TspBl.color.text.tertiary, fontSize: '12px', cursor: compact ? 'pointer' : 'grab', lineHeight: 1 }}>⠿</span>
                        <button onClick={e => { e.stopPropagation(); nudge(i, 1); }} style={ibtn}>▼</button>
                        {/* Feature 3 — Duplicate */}
                        <button onClick={e => { e.stopPropagation(); duplicate(i); }} style={{ ...ibtn, fontSize: '11px', marginTop: '2px' }} title="Duplicate">⧉</button>
                        <button onClick={e => { e.stopPropagation(); removeBlock(i); }} style={{ ...ibtn, color: st.crimson + '99', fontSize: '11px' }}><Icons.X size={11}/></button>
                      </div>
                    )}
                  </div>
                )}
              </div>
            );
          })}

          {/* Final drop slot */}
          {blocks.length > 0 && (
            <div
              style={slotSty(slotIdx === blocks.length)}
              onDragOver={e => { e.preventDefault(); e.stopPropagation(); setSlotIdx(blocks.length); }}
              onDragLeave={() => setSlotIdx(null)}
              onDrop={e => dropAtSlot(e, blocks.length)}
            >
              {slotIdx === blocks.length && <span style={{ fontSize: '10px', color: st.gold, fontWeight: 600 }}>Drop here</span>}
            </div>
          )}
        </div>
      </div>

      {/* ═══ RIGHT: Inspector ════════════════════════════════════════════════ */}
      <div style={pnl}>

        {/* Feature 10 — Multi-select inspector */}
        {multiMode && multiSel.size > 0 ? (
          <div style={{ padding: '12px 12px' }}>
            <div style={{ fontSize: '12px', fontWeight: 700, color: st.cobalt, marginBottom: '5px' }}>{multiSel.size} block{multiSel.size !== 1 ? 's' : ''} selected</div>
            <div style={{ fontSize: '10px', color: TspBl.color.text.tertiary, marginBottom: '12px', lineHeight: 1.6 }}>Use the action bar above to bulk-delete, or deselect to return to single-block inspection.</div>
            <button onClick={bulkDelete} style={{ ...st.btnGhost, width: '100%', fontSize: '11px', color: st.crimson, marginBottom: '6px' }}>x Delete All Selected</button>
            <button onClick={() => setMultiSel(new Set())} style={{ ...st.btnSecondary, width: '100%', fontSize: '11px' }}>Deselect All</button>
          </div>

        ) : selBlock && !selBlock.isDivider ? (
          /* ── Block inspector ── */
          <>
            <div style={{ padding: '10px 12px', borderBottom: sep, flexShrink: 0 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: '5px', marginBottom: '5px' }}>
                <div style={{ width: '7px', height: '7px', borderRadius: '50%', background: selBlock.isPlaceholder ? '#94A3B8' : selBlock.color }} />
                <span style={{ fontSize: '9px', fontWeight: 700, color: selBlock.isPlaceholder ? '#94A3B8' : selBlock.color, textTransform: 'uppercase', letterSpacing: '0.08em' }}>{selBlock.catId}</span>
              </div>
              {/* Feature 4 — Custom label editor */}
              <div style={{ fontSize: '9px', fontWeight: 700, color: TspBl.color.text.tertiary, textTransform: 'uppercase', letterSpacing: '0.07em', marginBottom: '3px' }}>Label</div>
              {editLbl ? (
                <input
                  autoFocus
                  value={selBlock.label}
                  onChange={e => patchBlock(selIdx, { label: e.target.value })}
                  onBlur={() => setEditLbl(false)}
                  onKeyDown={e => { if (e.key === 'Enter' || e.key === 'Escape') setEditLbl(false); }}
                  style={{ width: '100%', padding: '4px 7px', fontSize: '12px', fontWeight: 700, borderRadius: '4px', border: `1px solid ${st.gold}`, background: st.goldBg, color: TspBl.color.text.primary, fontFamily: TspBl.font.family, outline: 'none', boxSizing: 'border-box' }}
                />
              ) : (
                <div
                  onClick={() => setEditLbl(true)}
                  title="Click to rename"
                  style={{ fontSize: '12px', fontWeight: 700, color: TspBl.color.text.primary, lineHeight: 1.35, marginBottom: '2px', cursor: 'text', padding: '2px 4px', borderRadius: '3px', border: '1px dashed transparent', transition: 'border-color 0.12s' }}
                  onMouseEnter={e => { e.currentTarget.style.borderColor = TspBl.color.border.light; }}
                  onMouseLeave={e => { e.currentTarget.style.borderColor = 'transparent'; }}
                >
                  {selBlock.label} <span style={{ fontSize: '9px', color: TspBl.color.text.tertiary }}></span>
                </div>
              )}
              {!selBlock.isPlaceholder && <div style={{ fontSize: '10px', color: TspBl.color.text.tertiary }}>{selBlock.sub}</div>}
            </div>

            {/* Feature 2 — Status selector */}
            {!selBlock.isPlaceholder && (
              <div style={{ padding: '9px 12px', borderBottom: sep, flexShrink: 0 }}>
                <div style={ph}>Status</div>
                <div style={{ display: 'flex', gap: '4px' }}>
                  {Object.entries(BL_STATUS).map(([key, cfg]) => (
                    <button key={key}
                      onClick={() => patchBlock(selIdx, { status: key })}
                      style={{ flex: 1, padding: '4px 2px', fontSize: '9px', fontWeight: 700, borderRadius: '4px', border: `1px solid ${selBlock.status === key ? cfg.color + '55' : TspBl.color.border.light}`, background: selBlock.status === key ? cfg.background || TspBl.color.bg.secondary : 'transparent', color: selBlock.status === key ? cfg.color : TspBl.color.text.tertiary, cursor: 'pointer', fontFamily: TspBl.font.family, textTransform: 'uppercase', letterSpacing: '0.05em', transition: 'all 0.1s' }}
                    >{cfg.label}</button>
                  ))}
                </div>
              </div>
            )}

            {/* Editorial note */}
            <div style={{ padding: '9px 12px', borderBottom: sep, flexShrink: 0 }}>
              <div style={ph}>Editorial Note</div>
              <textarea
                value={selBlock.note}
                onChange={e => patchBlock(selIdx, { note: e.target.value })}
                placeholder="Add a note for this block…"
                style={{ width: '100%', minHeight: '56px', padding: '6px 8px', fontSize: '11px', borderRadius: '5px', border: `1px solid ${TspBl.color.border.light}`, background: TspBl.color.bg.secondary, color: TspBl.color.text.primary, fontFamily: TspBl.font.family, resize: 'vertical', outline: 'none', boxSizing: 'border-box' }}
              />
            </div>

            {/* Reorder */}
            <div style={{ padding: '9px 12px', borderBottom: sep, flexShrink: 0 }}>
              <div style={ph}>Position</div>
              <div style={{ display: 'flex', gap: '4px' }}>
                <button onClick={() => nudge(selIdx, -1)} disabled={selIdx === 0} style={{ ...st.btnSecondary, flex: 1, fontSize: '10px', opacity: selIdx === 0 ? 0.4 : 1 }}>▲ Up</button>
                <button onClick={() => nudge(selIdx, 1)} disabled={selIdx >= blocks.length - 1} style={{ ...st.btnSecondary, flex: 1, fontSize: '10px', opacity: selIdx >= blocks.length - 1 ? 0.4 : 1 }}>▼ Down</button>
              </div>
            </div>

            {/* Feature 3 — Duplicate + remove */}
            <div style={{ padding: '9px 12px', flexShrink: 0 }}>
              <button onClick={() => duplicate(selIdx)} style={{ ...st.btnSecondary, width: '100%', fontSize: '11px', marginBottom: '5px' }}>⧉ Duplicate Block</button>
              <button onClick={() => removeBlock(selIdx)} style={{ ...st.btnGhost, width: '100%', fontSize: '11px', color: st.crimson }}>x Remove Block</button>
            </div>
          </>

        ) : (
          /* ── Outline + stats + saved compositions ── */
          <>
            <div style={{ padding: '10px 12px', borderBottom: sep, flexShrink: 0 }}>
              <div style={{ fontSize: '11px', fontWeight: 700, color: TspBl.color.text.primary, marginBottom: '2px' }}>Document Outline</div>
              <div style={{ fontSize: '10px', color: TspBl.color.text.tertiary }}>Click any block to inspect it</div>
            </div>

            {/* Stats */}
            {blocks.length > 0 && (
              <div style={{ padding: '9px 12px', borderBottom: sep, flexShrink: 0 }}>
                <div style={ph}>Composition</div>
                <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '4px', marginBottom: '8px' }}>
                  {[['Blocks', cntBlocks.length, st.gold], ['Dividers', blocks.filter(b => b.isDivider).length, TspBl.color.text.secondary], ['TBD', blocks.filter(b => b.isPlaceholder).length, '#94A3B8']].map(([k, v, c]) => (
                    <div key={k} style={{ padding: '4px', borderRadius: '5px', background: TspBl.color.bg.secondary, textAlign: 'center' }}>
                      <div style={{ fontSize: '16px', fontWeight: 700, color: c, fontFamily: TspBl.font.mono, lineHeight: 1 }}>{v}</div>
                      <div style={{ fontSize: '8px', color: TspBl.color.text.tertiary, marginTop: '1px' }}>{k}</div>
                    </div>
                  ))}
                </div>
                {/* Feature 2 — Status breakdown */}
                {Object.entries(BL_STATUS).map(([key, cfg]) => {
                  const n = cntBlocks.filter(b => b.status === key).length;
                  if (!n) return null;
                  return (
                    <div key={key} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '3px' }}>
                      <span style={{ fontSize: '9px', fontWeight: 700, color: cfg.color, textTransform: 'uppercase', letterSpacing: '0.06em' }}>{cfg.label}</span>
                      <span style={{ fontSize: '11px', fontWeight: 700, color: cfg.color, fontFamily: TspBl.font.mono }}>{n}</span>
                    </div>
                  );
                })}
              </div>
            )}

            {/* Outline list */}
            <div style={{ flex: 1, overflowY: 'auto', padding: '6px 10px' }}>
              {blocks.length === 0 ? (
                <div style={{ textAlign: 'center', padding: '24px 10px', color: TspBl.color.text.tertiary, fontSize: '11px', lineHeight: 1.8 }}>
                  Drop items from the palette<br/>to build your outline.
                </div>
              ) : blocks.map((blk, i) => (
                blk.isDivider ? (
                  <div key={blk.bk} style={{ display: 'flex', alignItems: 'center', gap: '4px', marginBottom: '3px', opacity: 0.45 }}>
                    <div style={{ flex: 1, height: '1px', background: TspBl.color.border.light }} />
                    <span style={{ fontSize: '8px', color: TspBl.color.text.tertiary, textTransform: 'uppercase', letterSpacing: '0.07em' }}>{blk.label}</span>
                    <div style={{ flex: 1, height: '1px', background: TspBl.color.border.light }} />
                  </div>
                ) : (
                  <div key={blk.bk}
                    onClick={() => setSelIdx(i)}
                    style={{ display: 'flex', alignItems: 'center', gap: '5px', padding: '3px 5px', borderRadius: '4px', marginBottom: '2px', cursor: 'pointer', background: selIdx === i ? (blk.isPlaceholder ? '#94A3B820' : blk.color + '12') : 'transparent', transition: 'background 0.1s' }}
                  >
                    {numbered && blockNums[i] !== null && <span style={{ fontSize: '8px', color: TspBl.color.text.tertiary, fontFamily: TspBl.font.mono, flexShrink: 0 }}>{blockNums[i]}.</span>}
                    <span style={{ fontSize: '10px', color: blk.isPlaceholder ? '#94A3B8' : blk.color, flexShrink: 0 }}>{blk.catIcon}</span>
                    <span style={{ fontSize: '10px', color: blk.isPlaceholder ? TspBl.color.text.tertiary : TspBl.color.text.secondary, flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontStyle: blk.isPlaceholder ? 'italic' : 'normal' }}>{blk.label}</span>
                    {blk.status && !blk.isPlaceholder && <div style={{ width: '6px', height: '6px', borderRadius: '50%', background: BL_STATUS[blk.status].color, flexShrink: 0 }} />}
                  </div>
                )
              ))}
            </div>

            {/* Feature 12 — Saved compositions */}
            <div style={{ borderTop: sep, flexShrink: 0, padding: '9px 12px' }}>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '6px' }}>
                <div style={ph}>Saved Compositions</div>
                <button
                  onClick={() => setSaveOpen(v => !v)}
                  disabled={!blocks.length}
                  style={{ ...st.btnGhost, fontSize: '9px', padding: '1px 6px', color: blocks.length ? st.gold : TspBl.color.text.tertiary }}
                >+ Save</button>
              </div>

              {saveOpen && (
                <div style={{ display: 'flex', gap: '4px', marginBottom: '7px' }}>
                  <input
                    autoFocus
                    value={saveName}
                    onChange={e => setSaveName(e.target.value)}
                    onKeyDown={e => { if (e.key === 'Enter') saveComposition(); if (e.key === 'Escape') { setSaveOpen(false); setSaveName(''); } }}
                    placeholder="Name this composition…"
                    style={{ flex: 1, padding: '4px 7px', fontSize: '10px', borderRadius: '4px', border: `1px solid ${st.gold}`, background: st.goldBg, color: TspBl.color.text.primary, fontFamily: TspBl.font.family, outline: 'none' }}
                  />
                  <button onClick={saveComposition} disabled={!saveName.trim()} style={{ ...st.btnPrimary, fontSize: '10px', padding: '3px 8px', opacity: saveName.trim() ? 1 : 0.5 }}>Save</button>
                </div>
              )}

              {saved.length === 0 ? (
                <div style={{ fontSize: '10px', color: TspBl.color.text.tertiary, textAlign: 'center', padding: '6px 0' }}>No saved compositions yet</div>
              ) : saved.map(comp => (
                <div key={comp.id} style={{ display: 'flex', alignItems: 'center', gap: '5px', padding: '5px 7px', borderRadius: '5px', marginBottom: '3px', background: TspBl.color.bg.secondary, border: `1px solid ${TspBl.color.border.light}` }}>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: '10px', fontWeight: 600, color: TspBl.color.text.primary, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{comp.name}</div>
                    <div style={{ fontSize: '9px', color: TspBl.color.text.tertiary }}>{comp.blocks.filter(b => !b.isDivider).length} blocks · {comp.ts}</div>
                  </div>
                  <button onClick={() => loadSaved(comp)} style={{ ...st.btnGhost, fontSize: '9px', padding: '1px 5px', color: st.gold, flexShrink: 0 }}>Load</button>
                  <button onClick={() => setSaved(prev => prev.filter(c => c.id !== comp.id))} style={{ ...ibtn, fontSize: '11px', flexShrink: 0 }}><Icons.X size={11}/></button>
                </div>
              ))}
            </div>
          </>
        )}
      </div>
    </div>
  );
}

window.StudioBuilder = StudioBuilder;
