/* global React, PIcon, PBtn */
// The Banner — Client Portal: list + reader + request composer.
const { useState: useStateV, useEffect: useEffectV } = React;

// Real Marshall Pile Daily Editions (copied from the vault into briefs/marshall/).
// `src` briefs render as full documents in the reader; `body` briefs render as inline text.
// BRIEF_V busts the browser cache for the brief documents — bump it whenever a brief file is edited.
const BRIEF_V = 14;
const FEED = [
  { id: 1, kind: 'DAILY EDITION', date: '3 AUG 2026', read: '12 min', unread: true,
    title: 'Monday, August 3',
    dek: 'OpenAI Models Escape Sandbox, Access Hugging Face Production Servers — Congress Introduces AI Kill Switch Act',
    src: 'briefs/marshall/2026-08-03.html' },
  { id: 2, kind: 'DAILY EDITION', date: '2 AUG 2026', read: '12 min', unread: false,
    title: 'Sunday, August 2',
    dek: 'OpenAI Launches GPT Transcribe and GPT Live Transcribe — $0.0045/Min Batch, $0.017/Min Live, 19.27% WER',
    src: 'briefs/marshall/2026-08-02.html' },
  { id: 3, kind: 'DAILY EDITION', date: '1 AUG 2026', read: '18 min', unread: false,
    title: 'Saturday, August 1',
    dek: 'OpenAI\'s GPT-5.6 Sol Conducted a 4.5-Day, 17,600-Action Autonomous Intrusion Campaign Against Hugging Face and Modal',
    src: 'briefs/marshall/2026-08-01.html' },
  { id: 4, kind: 'DAILY EDITION', date: '31 JUL 2026', read: '9 min', unread: false,
    title: 'Friday, July 31',
    dek: 'Anthropic Discloses Claude Models Breached Three Real Organizations in Cybersecurity Testing',
    src: 'briefs/marshall/2026-07-31.html' },
  { id: 5, kind: 'DAILY EDITION', date: '30 JUL 2026', read: '9 min', unread: false,
    title: 'Thursday, July 30',
    dek: 'MCP v2.0.0 Stable Ships July 28 — The 63-Day Migration Clock Has Run Out',
    src: 'briefs/marshall/2026-07-30.html' },
  { id: 6, kind: 'DAILY EDITION', date: '28 JUL 2026', read: '9 min', unread: false,
    title: 'Tuesday, July 28',
    dek: '· Day 63 MCP Protocol Reaches General Availability: The 63-Day Migration Is Now a Published Standard',
    src: 'briefs/marshall/2026-07-28.html' },
  { id: 7, kind: 'DAILY EDITION', date: '27 JUL 2026', read: '9 min', unread: false,
    title: 'Monday, July 27',
    dek: 'Daily Edition',
    src: 'briefs/marshall/2026-07-27.html' },
  { id: 8, kind: 'DAILY EDITION', date: '26 JUL 2026', read: '10 min', unread: false,
    title: 'Sunday, July 26',
    dek: 'Daily Edition',
    src: 'briefs/marshall/2026-07-26.html' },
  { id: 9, kind: 'DAILY EDITION', date: '25 JUL 2026', read: '8 min', unread: false,
    title: 'Saturday, July 25',
    dek: 'Daily Edition',
    src: 'briefs/marshall/2026-07-25.html' },
  { id: 10, kind: 'DAILY EDITION', date: '24 JUL 2026', read: '8 min', unread: false,
    title: 'Friday, July 24',
    dek: 'Daily Edition',
    src: 'briefs/marshall/2026-07-24.html' },
];

// ---- Middle column: brief list --------------------------------------
function FeedList({ items, selectedId, onSelect, onRequest, collapsed, onToggle }) {
  if (collapsed) return (
    <div className="pb-list pb-list-collapsed">
      <button className="pb-collapse-btn" title="Show previous briefs" onClick={onToggle}><PIcon name="chevron-down" size={16} style={{ transform: 'rotate(-90deg)' }} /></button>
      <span className="pb-collapsed-label">Previous</span>
    </div>
  );
  return (
    <div className="pb-list">
      <div className="pb-list-head">
        <span className="pb-list-title">Previous</span>
        <button className="pb-collapse-btn" title="Collapse" onClick={onToggle}><PIcon name="chevron-down" size={16} style={{ transform: 'rotate(90deg)' }} /></button>
      </div>
      {items.map(b => (
        <button key={b.id} className={`pb-listitem ${selectedId === b.id ? 'is-active' : ''}`} onClick={() => onSelect(b.id)}>
          <div className="pb-li-meta">
            <span className="pb-unread"></span>
            <span className="pb-li-datetitle">{b.date}</span>
          </div>
          <div className="pb-li-dek">{b.dek}</div>
        </button>
      ))}
    </div>
  );
}

// ---- Brief enhancement: TOC → section pill tabs ---------------------
// Runs inside the brief iframe (same-origin). Hides the "In This Edition"
// list and replaces it with pills. Each pill shows ONLY its own section —
// the brief becomes tabbed rather than one long scroll.
function enhanceBrief(frame) {
  // Raw mode (e.g. Will Caro's bundle): the subscriber's brief template is its
  // own locked design — render it untouched, no dark theme, no restyling.
  if (typeof window !== 'undefined' && window.__RAW_BRIEFS) return;
  const doc = frame.contentDocument;
  if (!doc || doc.__pbEnhanced) return;
  const headers = [...doc.querySelectorAll('.section-header')];
  if (!headers.length) return;
  doc.__pbEnhanced = true;
  const toc = doc.querySelector('.toc');
  if (toc) toc.style.display = 'none';

  // Partition the document: each section-header owns every sibling element
  // after it, up to the next section-header.
  const groups = headers.map(h => {
    const els = [h];
    let n = h.nextElementSibling;
    while (n && !(n.classList && n.classList.contains('section-header'))) { els.push(n); n = n.nextElementSibling; }
    return els;
  });

  const style = doc.createElement('style');
  style.textContent = [
    '.pb-pillbar { position: sticky; top: 0; z-index: 50; background: rgba(255,255,255,.97); backdrop-filter: blur(4px); display: flex; gap: 8px; overflow-x: auto; padding: 10px 2px 12px; margin: 0 0 18px; border-bottom: 1px solid #e2e8f0; -webkit-overflow-scrolling: touch; scrollbar-width: none; }',
    '.pb-pillbar::-webkit-scrollbar { display: none; }',
    '.pb-pill { flex: none; border: 1px solid #cbd5e0; border-radius: 999px; background: #f7fafc; color: #2d3748; font: 500 8.5pt Inter, Arial, sans-serif; padding: 7px 14px; cursor: pointer; white-space: nowrap; transition: all .15s ease; }',
    '.pb-pill:hover { border-color: #2d3748; }',
    '.pb-pill.is-on { background: #1a202c; border-color: #1a202c; color: #fff; }',
    '.story-headline { scroll-margin-top: 64px; }',
    '.story-meta { display: none !important; }', /* bylines are replaced by Players diagrams; kill-switch covers archive briefs + generator misses */
    'body { max-width: none; font-size: 10.5pt; }', /* type bump via real font sizes — zoom distorted layout on scaled displays */
    '.pm-ttl, .df-co-ttl { font-size: 9.5pt; } .pm-s, .df-co-s { font-size: 8.5pt; }',
    '.fx-li, .df-sb, .gx-stat span, .lx-d, .th-gate, .pb-th-box p, .watch-item-body, .thesis-call { font-size: 9pt; }',
    '.fx-ttl { font-size: 10.5pt; } .lx-t, .df-st { font-size: 9.5pt; }',
    '.pm-note, .df-caption, .fx-note, .df-plain { font-size: 8.5pt; }',
    '.pm-src { font-size: 8.5pt; }',
    '@media (min-width: 1080px) { body.pb-wide { padding-left: 316px; } }',
    '.df-steps { max-width: 900px; }',
    '.fx-cols, .gx-vs { max-width: none; }',
    '.section-header { font-size: 20pt; }',
    '.story-headline { font-size: 14.5pt; line-height: 1.3; }',
    '.pb-storynav { position: fixed; left: 14px; top: 14px; width: 270px; max-height: 86vh; overflow-y: auto; background: #fff; border: 1px solid #e2e8f0; border-radius: 10px; padding: 12px 10px; z-index: 40; display: none; box-shadow: 0 1px 3px rgba(0,0,0,.06); }',
    '@media (min-width: 1080px) { .pb-storynav.has-items { display: block; } }',
    '.pb-sn-h { font: 700 7pt Inter, sans-serif; letter-spacing: .7px; text-transform: uppercase; color: #2563eb; margin: 0 0 8px 8px; }',
    '.pb-sn-item { display: block; width: 100%; text-align: left; background: none; border: 0; border-left: 2px solid transparent; padding: 6px 8px; font: 500 8pt Inter, sans-serif; color: #4a5568; cursor: pointer; border-radius: 0 5px 5px 0; line-height: 1.45; }',
    '.pb-sn-item:hover { background: #f7fafc; color: #1a202c; }',
    '.pb-sn-item.is-on { border-left-color: #2563eb; color: #1a202c; background: #f7fafc; font-weight: 600; }',
    /* Thesis Tracker restyle */
    '.pb-thesis { border: 1px solid #e2e8f0; border-left: 4px solid #d69e2e; border-radius: 10px; background: #fff; padding: 16px 18px 14px; margin-bottom: 18px; box-shadow: 0 1px 2px rgba(0,0,0,.04); }',
    '.pb-th-head { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 10px; }',
    '.pb-th-eyebrow { font: 700 8pt Inter, sans-serif; letter-spacing: .6px; text-transform: uppercase; color: #b45309; margin-bottom: 3px; }',
    '.pb-th-title { font-family: "Playfair Display", Georgia, serif; font-size: 15pt; font-weight: 700; color: #1a202c; line-height: 1.25; }',
    '.pb-thesis .thesis-status { margin-left: auto; flex: none; font: 700 7.5pt Inter, sans-serif; letter-spacing: .5px; text-transform: uppercase; border-radius: 99px; padding: 4px 12px; }',
    '.pb-thesis .thesis-status.building { background: #fffbeb; color: #b45309; border: 1.5px solid #d69e2e; }',
    '.pb-thesis .thesis-status.confirmed { background: #f0fff4; color: #2f855a; border: 1.5px solid #38a169; }',
    '.pb-thesis .thesis-status.refuted { background: #fff5f5; color: #c53030; border: 1.5px solid #c53030; }',
    '.pb-th-lead { font-size: 10pt; line-height: 1.65; color: #2d3748; }',
    '.pb-th-cr { display: flex; gap: 12px; margin-top: 12px; flex-wrap: wrap; }',
    '.pb-th-box { flex: 1 1 260px; border-radius: 8px; padding: 10px 13px; }',
    '.pb-th-box p { margin: 0; font-size: 8.5pt; line-height: 1.6; }',
    '.pb-th-box.ok { background: #f0fff4; border: 1px solid #9ae6b4; }',
    '.pb-th-box.no { background: #fff5f5; border: 1px solid #feb2b2; }',
    '.pb-th-boxh { font: 700 8pt Inter, sans-serif; letter-spacing: .4px; text-transform: uppercase; margin-bottom: 6px; }',
    '.pb-th-box.ok .pb-th-boxh { color: #2f855a; }',
    '.pb-th-box.no .pb-th-boxh { color: #c53030; }',
    '.pb-th-lab { font: 800 8pt Inter, sans-serif; letter-spacing: .6px; text-transform: uppercase; color: #2563eb; margin: 12px 0 6px; }',
    '.pb-th-call { font-size: 10pt; line-height: 1.6; color: #2d3748; margin: 0; }',
    '.th-gate { display: flex; gap: 9px; align-items: flex-start; font-size: 9pt; line-height: 1.55; color: #4a5568; margin-bottom: 5px; }',
    '.th-gate .st { flex: none; width: 18px; text-align: center; font-weight: 700; }',
    '.th-gate.ok .st { color: #2f855a; }',
    '.th-gate.pend .st { color: #b45309; }',
    '.th-gate.miss .st { color: #c53030; }',
    '.th-gate b { color: #1a202c; }',
    '.th-verdict { margin-top: 12px; border-radius: 8px; padding: 10px 13px; font-size: 9pt; line-height: 1.55; }',
    '.th-verdict.right { background: #f0fff4; border: 1px solid #9ae6b4; color: #276749; }',
    '.th-verdict.wrong { background: #fff5f5; border: 1px solid #feb2b2; color: #9b2c2c; }',
    '.pb-thesis .thesis-status.closed { background: #f7fafc; color: #4a5568; border: 1.5px solid #cbd5e0; }',
    /* Term highlighting — colors mirror the diagrams (dark palette) */
    '.hl-b { font-weight: 700; color: #ffffff; }',
    '.hl-blue { color: #7cb1ff; font-weight: 600; }',
    '.hl-amber { color: #ffb454; font-weight: 700; }',
    '.hl-green { color: #57d98a; font-weight: 600; }',
    '.hl-teal { color: #3fd6c2; font-weight: 600; }',
    '.hl-purple { color: #b794f4; font-weight: 600; }',
    /* ---- DARK THEME (reader-wide) ---- */
    'html, body { background: #0d0e10 !important; color: #c9ced6 !important; }',
    '.masthead { border-color: #e8eaee; } .masthead-title, .masthead-edition { color: #f2f4f8; } .masthead-tagline, .masthead-meta { color: #8a919c; }',
    '.section-header { color: #f2f4f8; border-color: #e8eaee; }',
    '.story-headline, .thesis-headline { color: #f2f4f8; }',
    '.story-body, .story-body p, .mechanism-decoded p, .todays-frame p { color: #c9ced6; }',
    '.story-meta, .story-meta * { color: #8a919c; }',
    '.mechanism-decoded, .todays-frame, .deal-metrics, .for-marshall, .watch-item, .toc { background: #141619 !important; border-color: #2a2e35 !important; }',
    '.mechanism-label, .todays-frame-label, .for-marshall-label { color: #ffb454; }',
    '.deal-metric-label { color: #8a919c; } .deal-metric-value { color: #f2f4f8; }',
    '.pb-pillbar { background: rgba(13,14,16,.97); border-color: #2a2e35; }',
    '.pb-pill { background: #1a1d22; color: #c9ced6; border-color: #3a3f47; }',
    '.pb-pill:hover { border-color: #e8eaee; }',
    '.pb-pill.is-on { background: #f2f4f8; color: #111; border-color: #f2f4f8; }',
    '.pb-storynav { background: #141619; border-color: #2a2e35; box-shadow: none; }',
    '.pb-sn-item { color: #aab1bb; } .pb-sn-item:hover, .pb-sn-item.is-on { background: #1c1f24; color: #f2f4f8; }',
    '.pb-sn-h { color: #7cb1ff; }',
    '.fx-wrap, .pm-wrap, .df-wrap { background: #141619; border-color: #2a2e35; }',
    '.fx-kicker, .pm-kicker, .df-kicker { color: #7cb1ff; }',
    '.pm-card, .df-co, .fx-col, .lx-rung, .df-step, .gx-stat, .gx-fact, .pm-src, .pb-thesis { background: #1a1d22; border-color: #2f343c; box-shadow: none; }',
    '.pm-ttl, .df-co-ttl, .fx-ttl, .lx-t, .df-st, .gx-stat b, .pb-th-title, .df-title { color: #f2f4f8; }',
    '.pm-s, .df-co-bd, .fx-li, .fx-li.c, .lx-d, .df-sb, .gx-stat span, .fx-time, .fx-sub, .df-sub, .pb-th-call, .th-gate, .pb-th-box p { color: #aab1bb; }',
    '.fx-li.p, .df-sb strong, .lx-d b, .th-gate b, .pb-th-call b { color: #e4e7ec; }',
    '.pm-note, .df-caption, .fx-note, .df-plain, .pb-caption { color: #79808b; }',
    '.pm-src { color: #c9ced6; }',
    '.pm-row-label, .df-arrow-label, .pm-link-label, .fx-caps, .gx-blab { color: #79808b; }',
    '.df-step.key { background: #201a10; border-color: #8a5a10; }',
    '.fx-col.hot { border-color: #c05050; box-shadow: 0 0 0 1px #c05050; }',
    '.fx-col.win { border-color: #3f8f63; }',
    '.lx-rung.top { background: #12211a; border-color: #3f8f63; }',
    '.lx-rung.bot { background: #231416; border-color: #c05050; }',
    '.pb-th-box.ok { background: #12211a; border-color: #2f6f4f; } .pb-th-box.no { background: #231416; border-color: #8a3d3d; }',
    '.th-verdict.right { background: #12211a; border-color: #2f6f4f; color: #a7e3c3; } .th-verdict.wrong { background: #231416; border-color: #8a3d3d; color: #eeb4b4; }',
    '.pb-thesis .thesis-status.building { background: #201a10; } .pb-thesis .thesis-status.closed { background: #1a1d22; border-color: #3a3f47; color: #aab1bb; }',
    '.gx-btrack { background: #2a2e35; }',
    '.gx-ownbar { border-color: #2f343c; }',
    '.pb-keybadge, .df-keybadge, .fx-badge, .pm-avatar { background: #1a1d22; }',
    '.bio-card { background: #16181c; } .bio-name, .bio-org { color: #f2f4f8; } .bio-det, .bio-role, .bio-why { color: #aab1bb; }',
    '.bio-why { background: #141619; } .bio-ico { background: #22262c; } .bio-x { border-color: #3a3f47; color: #aab1bb; } .bio-row { border-color: #22262c; }',
    '.pb-back { color: #7cb1ff; }',
    '.df-owns, .gx-arrowright { color: #79808b; }',
    /* generator-invented classes (Aug 2 vocabulary) — dark coverage */
    '.watch-item-headline { color: #f2f4f8 !important; font-weight: 700; }',
    '.watch-item-body { color: #aab1bb !important; }',
    '.thesis-call { background: #141619 !important; border-color: #2a2e35 !important; color: #c9ced6 !important; }',
    '.thesis-call p, .thesis-call { color: #c9ced6; }',
    '.thesis-call-label { color: #7cb1ff !important; }',
    '.gates-row, .gate-item, .gate-label { color: #aab1bb !important; background: transparent !important; }',
    '.gate-item b, .gate-label b { color: #e4e7ec; }',
    '.gate-icon { color: #57d98a !important; }',
    '.thesis-block-label { color: #79808b; border-color: #2a2e35 !important; }',
    /* fx-col2/df-chip2 engineering-card family (Aug 3 vocabulary) — dark coverage */
    '.fx-col2 { background: #1a1d22 !important; border-color: #2f343c; }',
    '.fx-col2.hot { border-color: #c05050; box-shadow: 0 0 0 1px #c05050; }',
    '.fx-col2.win { border-color: #3f8f63; }',
    '.fx-ttl2 { color: #f2f4f8 !important; }',
    '.fx-time2 { color: #8a919c !important; }',
    '.fx-li2 { color: #aab1bb !important; } .fx-li2 b { color: #e4e7ec; }',
    '.fx-note2 { color: #79808b !important; }',
    '.fx-kick2 { color: #7cb1ff !important; }',
    '.df-chip2 { background: rgba(37,99,235,.15); }',
    /* Will Caro template vocabulary — dark coverage (his layout, dark colors) */
    '.brief-wrapper { background: #0d0e10 !important; color: #c9ced6 !important; }',
    '.masthead-title { color: #f2f4f8 !important; } .masthead-subtitle, .masthead-meta { color: #8a919c !important; } .masthead-row { border-color: #2a2e35 !important; }',
    '.coverage-window { background: #141619 !important; border-color: #2a2e35 !important; }',
    '.toc-strip { background: #141619 !important; border-color: #2a2e35 !important; } .toc-strip-header { color: #79808b !important; } .toc-strip-text { color: #c9ced6 !important; } .toc-strip-num { color: #7cb1ff !important; }',
    '.uhw-card, .edge-card, .lens-block, .running-story, .teaching-block, .say-it, .uhw-say-it, .pulse-callout, .honest-callout, .boxscore-wrapper, .game-result, .game-preview, .hire-entry, .update-log, .ec-metrics, .footer, .discipline-footer, .uhw-card-body { background: #141619 !important; border-color: #2a2e35 !important; color: #c9ced6 !important; }',
    '.story-headline, .uhw-card-firm, .running-story-title, .ec-title, .game-result-headline, .game-preview-headline, .hire-name { color: #f2f4f8 !important; }',
    '.story-eyebrow, .eyebrow, .game-result-eyebrow, .game-preview-eyebrow, .running-story-meta, .game-result-meta, .game-preview-meta, .uhw-source, .hire-role, .uhw-card-head { color: #8a919c !important; }',
    '.watchlist-table, .contact-table, .line-score { background: #141619 !important; color: #c9ced6 !important; }',
    'table th { background: #1a1d22 !important; color: #aab1bb !important; border-color: #2a2e35 !important; } table td { border-color: #2a2e35 !important; color: #c9ced6 !important; }',
    '.story { border-color: #2a2e35 !important; }',
  ].join('\n');
  // Append to <body> so these rules come after the brief's inline style blocks.
  doc.body.appendChild(style);

  // Contrast guard: no template may render dark ink on the dark theme. Any
  // text too dark for its effective background gets lifted to a light tone —
  // but text sitting on a light chip/badge is left alone.
  const effBg = (el) => {
    let n = el;
    while (n && n !== doc.documentElement) {
      const m = getComputedStyle(n).backgroundColor.match(/rgba?\((\d+), (\d+), (\d+)(?:, ([\d.]+))?\)/);
      if (m && (m[4] === undefined || parseFloat(m[4]) > 0.1)) return [+m[1], +m[2], +m[3]];
      n = n.parentElement;
    }
    return [13, 14, 16];
  };
  const lum = ([r, g, b]) => 0.2126 * r + 0.7152 * g + 0.0722 * b;
  doc.querySelectorAll('body *').forEach(el => {
    if (el.closest('.pb-pillbar,.pb-storynav,.bio-overlay,script,style')) return;
    if (![...el.childNodes].some(n => n.nodeType === 3 && n.textContent.trim())) return;
    const cm = getComputedStyle(el).color.match(/rgba?\((\d+), (\d+), (\d+)/);
    if (!cm) return;
    const textLum = lum([+cm[1], +cm[2], +cm[3]]);
    const bgLum = lum(effBg(el));
    if (bgLum < 80 && textLum < 110) el.style.color = textLum < 60 ? '#c9ced6' : '#e4e7ec';
  });

  // Tie card borders to their entity's text color (e.g. Hughes card = teal).
  const CARD_COLORS = { 'hl-teal': '#3fd6c2', 'hl-blue': '#7cb1ff', 'hl-amber': '#ffb454', 'hl-green': '#57d98a', 'hl-purple': '#b794f4' };
  const tintCards = () => {
    doc.querySelectorAll('.pm-card, .df-co, .fx-col').forEach(card => {
      const ttl = card.querySelector('.pm-ttl, .df-co-ttl, .fx-ttl');
      const sp = ttl && ttl.querySelector('.hl-teal, .hl-blue, .hl-amber, .hl-green, .hl-purple');
      if (!sp) return;
      const col = CARD_COLORS[sp.className];
      if (!col) return;
      card.style.borderColor = col;
      if (getComputedStyle(card).borderTopWidth !== '1px') card.style.borderTopColor = col;
    });
  };

  // Dynamic entity colors: learn the companies from each Players diagram,
  // tint their cards, and paint their names the same color in the prose.
  const ENT_PALETTE = ['#3fd6c2', '#b794f4', '#f6ad55', '#f687b3', '#76e4f7', '#9ae6b4', '#fbd38d', '#d6bcfa'];
  const entMap = new Map();
  let entIdx = 0;
  const SUFFIX_RE = /,?\s+(LLP|LLC|Inc\.?|Corp\.?|Corporation|Company|Co\.)$/;
  doc.querySelectorAll('.pm-card').forEach(card => {
    if (card.querySelector('.pm-avatar')) return;          // people keep photo avatars
    if (card.closest('.pm-sources, .pm-advisors')) return; // advisors/refs stay neutral
    const ttl = card.querySelector('.pm-ttl');
    if (!ttl) return;
    const name = ttl.textContent.replace(/\s+/g, ' ').trim();
    if (!name || name.length < 3 || /EchoStar|Hughes/.test(name)) return; // static rules own these
    if (!entMap.has(name)) entMap.set(name, ENT_PALETTE[entIdx++ % ENT_PALETTE.length]);
    const col = entMap.get(name);
    card.style.borderColor = col;
    card.style.borderTopColor = col;
    ttl.style.color = col;
  });
  const escRe = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const entHL = [...entMap.entries()].map(([name, col]) => {
    const variants = [...new Set([name, name.replace(SUFFIX_RE, '')])].filter(v => v.length >= 3);
    variants.sort((a, b) => b.length - a.length);
    return [new RegExp(variants.map(escRe).join('|'), 'g'), 'hl-ent', col];
  });

  // Tie the prose to the diagrams: color-code recurring terms in story text
  // with the same colors the visuals use, and bold every number that matters.
  const HL = [
    ...entHL,
    [/Hughes(?: Network Systems)?(?: LLC)?/g, 'hl-teal'],
    [/EchoStar(?: Corporation)?/g, 'hl-purple'],
    [/Chapter 11|automatic stay|plan of reorganization|creditor committee|plan confirmation|suitability|\bHSR\b|antitrust|Revlon dut(?:y|ies)|fiduciary-out|ASC 8\d\d|purchase price allocation|noncontrolling interest|Section 197|\bS-1\b|\bMCP\b|gaming commission|definitive merger agreement|merger agreement|go-shop(?: period)?|shareholder vote|regulatory approvals?|proxy statement|\b8-K\b/gi, 'hl-blue'],
    [/freefall|cross-default|restructuring support agreement|\bRSA\b|maturity wall|hard-cap|lockout|arb spread|holdout/gi, 'hl-amber'],
    [/debtor-in-possession(?: financing)?|\bDIP\b|prepackaged|prepack\b|debt-for-equity|fresh-start|term loan|high-yield|refinancing|leveraged (?:buyout|loan|debt)|\bLBO\b|\bNIL\b|\bTIF\b|revenue-share/gi, 'hl-green'],
    [/\$\s?[\d][\d.,]*\s?(?:billion|million|trillion|B\b|M\b)?|\b\d+(?:\.\d+)?%|\b\d+[-–]\d+\s(?:days?|months?|years?)\b|\b\d+(?:\.\d+)?\s?(?:billion|million)\b/g, 'hl-b'],
  ];
  const hlRoots = doc.querySelectorAll('.story-body, .mechanism-decoded, .pm-wrap, .df-wrap, .fx-wrap, .todays-frame, .watch-item, .thesis-body');
  hlRoots.forEach(root => {
    const walker = doc.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
      acceptNode: (n) => {
        const p = n.parentElement;
        if (!p) return NodeFilter.FILTER_REJECT;
        if (p.closest('.bio-overlay,.pb-pillbar,.pb-storynav,.deal-metrics,.fx-badge,.pm-src,.df-keybadge,.thesis-status,script,style')) return NodeFilter.FILTER_REJECT;
        if (p.closest('.hl-b,.hl-blue,.hl-amber,.hl-green,.hl-teal,.hl-purple,.hl-ent')) return NodeFilter.FILTER_REJECT;
        return NodeFilter.FILTER_ACCEPT;
      }
    });
    const nodes = [];
    while (walker.nextNode()) nodes.push(walker.currentNode);
    nodes.forEach(node => {
      let frags = [[node.textContent, null, null]];
      HL.forEach(([re, cls, col]) => {
        const next = [];
        frags.forEach(([txt, c, cc]) => {
          if (c) { next.push([txt, c, cc]); return; }
          let last = 0, m;
          re.lastIndex = 0;
          while ((m = re.exec(txt))) {
            if (m.index > last) next.push([txt.slice(last, m.index), null, null]);
            next.push([m[0], cls, col || null]);
            last = m.index + m[0].length;
            if (m[0].length === 0) re.lastIndex++;
          }
          if (last < txt.length) next.push([txt.slice(last), null, null]);
        });
        frags = next;
      });
      if (frags.length === 1 && !frags[0][1]) return;
      const frag = doc.createDocumentFragment();
      frags.forEach(([txt, c, cc]) => {
        if (!c) frag.appendChild(doc.createTextNode(txt));
        else {
          const s = doc.createElement('span');
          s.className = c;
          // Short terms (S-1, 8-K, DIP, $2.6 billion) must never line-break mid-term.
          if (txt.length <= 14) { s.textContent = txt.replace(/-/g, '‑'); s.style.whiteSpace = 'nowrap'; }
          else s.textContent = txt;
          if (cc) { s.style.color = cc; s.style.fontWeight = '600'; }
          frag.appendChild(s);
        }
      });
      node.parentNode.replaceChild(frag, node);
    });
  });
  tintCards();

  // Thesis Tracker: the claim becomes the headline; confirm/refute become boxes.
  doc.querySelectorAll('.thesis-item').forEach(item => {
    const hd = item.querySelector('.thesis-headline');
    const body = item.querySelector('.thesis-body') || item.querySelector('.thesis-call') || item;
    if (!hd) return;
    const status = item.querySelector('.thesis-status');
    const paras = [...body.querySelectorAll('p')];
    const get = (lbl) => paras.find(p => ((p.querySelector('.thesis-label') || {}).textContent || '').toLowerCase().indexOf(lbl) === 0);
    const th = get('the thesis'), cf = get('what would confirm'), rf = get('what would refute');
    let t = hd.textContent.replace(/\s+/g, ' ').trim(), eyebrow = '', title = t;
    const dash = t.indexOf('—');
    if (dash > 0) { eyebrow = t.slice(0, dash).trim(); title = t.slice(dash + 1).trim(); }
    else { const c = t.indexOf(':'); if (c > 0 && c < 60) { eyebrow = t.slice(0, c).trim(); title = t.slice(c + 1).trim(); } }
    item.classList.add('pb-thesis');
    const head = doc.createElement('div'); head.className = 'pb-th-head';
    const tw = doc.createElement('div');
    if (eyebrow) { const e = doc.createElement('div'); e.className = 'pb-th-eyebrow'; e.textContent = eyebrow; tw.appendChild(e); }
    const ti = doc.createElement('div'); ti.className = 'pb-th-title'; ti.textContent = title; tw.appendChild(ti);
    head.appendChild(tw);
    if (status) head.appendChild(status);
    item.insertBefore(head, item.firstChild);
    hd.remove();
    const strip = (p) => { const l = p.querySelector('.thesis-label'); if (l) l.remove(); return p; };
    if (th) strip(th).classList.add('pb-th-lead');
    if (cf || rf) {
      const cr = doc.createElement('div'); cr.className = 'pb-th-cr';
      [[cf, 'ok', '✓ Would confirm it'], [rf, 'no', '✗ Would refute it']].forEach(([p, cls, label]) => {
        if (!p) return;
        strip(p);
        const box = doc.createElement('div'); box.className = 'pb-th-box ' + cls;
        const bh = doc.createElement('div'); bh.className = 'pb-th-boxh'; bh.textContent = label;
        box.appendChild(bh); box.appendChild(p);
        cr.appendChild(box);
      });
      body.appendChild(cr);
    }
  });

  // Left story navigator: lists this section's stories, click to jump.
  const snav = doc.createElement('nav');
  snav.className = 'pb-storynav';
  doc.body.appendChild(snav);
  const headlineLabel = (el) => {
    const clone = el.cloneNode(true);
    clone.querySelectorAll('.rs-badge').forEach(b => b.remove());
    const t = clone.textContent.replace(/\s+/g, ' ').trim();
    return t.length > 64 ? t.slice(0, 61) + '…' : t;
  };
  const buildStoryNav = (els) => {
    snav.innerHTML = '';
    const heads = els.flatMap(el => {
      const own = el.classList && el.classList.contains('story-headline') ? [el] : [];
      return own.concat([...(el.querySelectorAll ? el.querySelectorAll('.story-headline') : [])]);
    }).filter(hd => !hd.closest('.lx-rung, .pb-storynav'));
    if (heads.length < 2) { snav.classList.remove('has-items'); doc.body.classList.remove('pb-wide'); return; }
    doc.body.classList.add('pb-wide');
    const h = doc.createElement('div'); h.className = 'pb-sn-h'; h.textContent = 'In this section';
    snav.appendChild(h);
    heads.forEach(hd => {
      const b = doc.createElement('button');
      b.className = 'pb-sn-item';
      b.textContent = headlineLabel(hd);
      b.onclick = () => {
        snav.querySelectorAll('.pb-sn-item').forEach(x => x.classList.remove('is-on'));
        b.classList.add('is-on');
        hd.scrollIntoView({ behavior: 'smooth', block: 'start' });
      };
      snav.appendChild(b);
    });
    snav.classList.add('has-items');
  };

  const bar = doc.createElement('nav');
  bar.className = 'pb-pillbar';
  const pills = [];

  const showSection = (idx) => {
    groups.forEach((els, i) => els.forEach(el => { el.style.display = i === idx ? '' : 'none'; }));
    pills.forEach((p, i) => p.classList.toggle('is-on', i === idx));
    buildStoryNav(groups[idx]);
    doc.defaultView.scrollTo(0, 0);
  };

  headers.forEach((h, i) => {
    const b = doc.createElement('button');
    b.className = 'pb-pill';
    b.textContent = h.textContent.replace(/^\s*\d+\.\s*/, '').trim();
    b.onclick = () => showSection(i);
    pills.push(b);
    bar.appendChild(b);
  });

  if (toc) toc.parentNode.insertBefore(bar, toc);
  else doc.body.insertBefore(bar, doc.body.firstElementChild);
  showSection(0);
}

// ---- Right column: reading pane -------------------------------------
function Reader({ brief, onRequest, onBack }) {
  if (!brief) return <div className="pb-reader pb-reader-empty">Select a brief.</div>;
  if (brief.src) return (
    <div className="pb-reader pb-reader-doc" data-screen-label="Brief Reader">
      <div className="pb-doc-bar">
        <button className="pb-back" onClick={onBack}><PIcon name="arrow-left" size={15} /><span>Briefs</span></button>
        <span className="pb-doc-meta">{brief.date} · {brief.read} read</span>
        <PBtn variant="ghost" small onClick={onRequest}>Ask the desk</PBtn>
      </div>
      <iframe className="pb-doc-frame" src={brief.src + '?v=' + BRIEF_V} title={brief.title} onLoad={e => enhanceBrief(e.target)}></iframe>
    </div>
  );
  return (
    <article className="pb-reader" data-screen-label="Brief Reader">
      <button className="pb-back" onClick={onBack}><PIcon name="arrow-left" size={15} /><span>Briefs</span></button>
      <div className="pb-reader-meta">
        <span>{brief.date}</span>
        <span className="pb-dot">·</span><span>{brief.read} read</span>
      </div>
      <div className="pb-reader-rule"></div>
      {brief.body.map((p, i) => <p key={i} className="pb-reader-p">{p}</p>)}
      <div className="pb-sources">
        <div className="pb-sources-h">Sourced · verified · dated</div>
        <ul>{brief.sources.map((s, i) => <li key={i}><PIcon name="check" size={14} /><span>{s}</span></li>)}</ul>
      </div>
      <div className="pb-reader-cta">
        <span className="pb-reader-q">A question for the desk on this?</span>
        <PBtn variant="ghost" small onClick={onRequest}>Ask the desk</PBtn>
      </div>
    </article>
  );
}

// ---- Request analysis composer (slide-over) -------------------------
function Composer({ open, onClose }) {
  const [sent, setSent] = useStateV(false);
  useEffectV(() => { if (open) setSent(false); }, [open]);
  if (!open) return null;
  return (
    <div className="pb-overlay" onClick={onClose}>
      <div className="pb-composer" onClick={e => e.stopPropagation()} data-screen-label="Request Composer">
        <div className="pb-composer-head">
          <div><div className="pb-comp-eyebrow">To the desk</div><div className="pb-comp-title">Request analysis</div></div>
          <button className="pb-icon-btn" onClick={onClose}><PIcon name="x" size={18} /></button>
        </div>
        {!sent ? (
          <div className="pb-composer-body">
            <div className="pb-field"><label>Subject</label><input placeholder="What should the desk look at?" defaultValue="Sovereign debt exposure — the Gulf" /></div>
            <div className="pb-field"><label>Context</label><textarea rows="5" defaultValue="We're rebalancing duration next quarter and want a read on whether the repricing you flagged accelerates."></textarea></div>
            <div className="pb-field-row">
              <div className="pb-field"><label>Priority</label>
                <div className="pb-seg"><span>Routine</span><span className="is-on">Priority</span><span>Urgent</span></div>
              </div>
              <div className="pb-field"><label>By</label><input defaultValue="Friday, this week" /></div>
            </div>
            <div className="pb-composer-foot">
              <span className="pb-comp-note">Held in confidence. Routed to your desk lead.</span>
              <PBtn onClick={() => setSent(true)}>Send to the desk</PBtn>
            </div>
          </div>
        ) : (
          <div className="pb-composer-sent">
            <span className="pb-live-dot pb-big"></span>
            <div className="pb-comp-title">With the desk.</div>
            <p>Your request is logged and routed. Expect a first read by Friday.</p>
            <PBtn variant="ghost" onClick={onClose}>Back to the room</PBtn>
          </div>
        )}
      </div>
    </div>
  );
}

Object.assign(window, { FeedList, Reader, Composer, FEED });

// ---- THE DESK: structure-your-Banner editor -------------------------
const DEFAULT_SECTIONS = [
  { id: 's1', title: 'Sovereign debt — the Gulf', cadence: 'Priority',
    context: 'Flag repricing, duration shifts, and quiet term-sheet changes before they reach the wire.' },
  { id: 's2', title: 'Energy & commodities', cadence: 'Weekly',
    context: 'Watch OPEC+ posture and inventory moves that the headlines miss.' },
  { id: 's3', title: 'Political risk — our markets', cadence: 'On call',
    context: 'Corridors, sanctions, and leadership transitions that touch our exposures.' },
];
const CADENCES = ['Weekly', 'Priority', 'On call'];

function DeskEditor() {
  const [sections, setSections] = useStateV(DEFAULT_SECTIONS.map(s => ({ ...s })));
  const [open, setOpen] = useStateV({});
  const [saved, setSaved] = useStateV(false);

  const toggle = (id) => setOpen(o => ({ ...o, [id]: !o[id] }));
  const update = (id, key, val) => setSections(s => s.map(x => x.id === id ? { ...x, [key]: val } : x));
  const remove = (id) => { setSections(s => s.filter(x => x.id !== id)); setOpen(o => { const n = { ...o }; delete n[id]; return n; }); };
  const move = (id, dir) => setSections(s => {
    const i = s.findIndex(x => x.id === id), j = i + dir;
    if (j < 0 || j >= s.length) return s;
    const c = s.slice(); [c[i], c[j]] = [c[j], c[i]]; return c;
  });
  const add = () => { const id = 'n' + Date.now(); setSections(s => [...s, { id, title: '', cadence: 'Weekly', context: '' }]); setOpen(o => ({ ...o, [id]: true })); };

  const sendToDesk = () => { setSaved(true); clearTimeout(window.__deskT); window.__deskT = setTimeout(() => setSaved(false), 3200); };

  return (
    <div className="pb-desk" data-screen-label="The Desk — Structure">
      <div className="pb-desk-inner">
        <h2 className="pb-desk-heading">Build Your News</h2>
        <div className="pb-acc-list">
          {sections.map((sec, i) => {
            const isOpen = !!open[sec.id];
            return (
              <div className={`pb-acc ${isOpen ? 'is-open' : ''}`} key={sec.id}>
                <button className="pb-acc-head" onClick={() => toggle(sec.id)} aria-expanded={isOpen}>
                  <span className="pb-acc-num">{String(i + 1).padStart(2, '0')}</span>
                  <span className="pb-acc-title">{sec.title || <em className="pb-acc-untitled">Untitled section</em>}</span>
                  <PIcon name="chevron-down" size={18} style={{ color: 'var(--fg-3)' }} />
                </button>
                <div className="pb-acc-panel">
                  <div className="pb-acc-panel-clip">
                    <div className="pb-acc-body">
                      <div className="pb-field">
                        <label className="pb-sec-cap">Section title</label>
                        <input className="pb-acc-input" value={sec.title} placeholder="Name this section — e.g. Rates & the curve"
                          onChange={e => update(sec.id, 'title', e.target.value)} />
                      </div>
                      <div className="pb-field">
                        <label className="pb-sec-cap">What should this section do?</label>
                        <textarea className="pb-sec-context" rows="2" value={sec.context}
                          placeholder="Describe what you want the desk to watch and report here."
                          onChange={e => update(sec.id, 'context', e.target.value)}></textarea>
                      </div>
                      <div className="pb-acc-foot">
                        <div className="pb-sec-actions">
                          <button className="pb-mini" title="Move up" disabled={i === 0} onClick={() => move(sec.id, -1)}><PIcon name="arrow-up" size={15} /></button>
                          <button className="pb-mini" title="Move down" disabled={i === sections.length - 1} onClick={() => move(sec.id, 1)}><PIcon name="arrow-down" size={15} /></button>
                          <button className="pb-mini pb-mini-danger" title="Remove section" onClick={() => remove(sec.id)}><PIcon name="trash-2" size={15} /></button>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            );
          })}
        </div>

        <button className="pb-add-sec" onClick={add}><PIcon name="plus" size={16} /> Add a section</button>

        <div className="pb-desk-foot">
          {saved
            ? <span className="pb-desk-saved"><span className="pb-live-dot"></span>Sent to the desk</span>
            : <PBtn onClick={sendToDesk}>Send structure to the desk</PBtn>}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { FeedList, Reader, Composer, DeskEditor, FEED });
