// "For Me" — private owner-only dashboards.
//
// Tickers are stored in the `live_investments` Supabase table and managed
// from the UI (+ ADD / × remove buttons on each tab). DEFAULT_INVESTMENTS
// seeds the table on first login; all edits then persist in the database.
//
// Apply the migration before using the CRUD UI:
//   supabase db push          ← pushes supabase/migrations/ to the project
// Or paste supabase/migrations/20260602000000_live_investments.sql directly
// into the Supabase dashboard → SQL editor and run it.

const DEFAULT_INVESTMENTS = [
  { ticker: 'PAYO',   tvSymbol: 'NASDAQ:PAYO',     exchange: 'NASDAQ',        name: 'Payoneer',     query: 'Payoneer',     color: '#FF5500' },
  { ticker: 'IIIV',   tvSymbol: 'NASDAQ:IIIV',     exchange: 'NASDAQ',        name: 'i3 Verticals', query: 'i3 Verticals', color: '#0E7C3A' },
  { ticker: 'ASMDEE', tvSymbol: 'OMXSTO:ASMDEE_B', exchange: 'OMX STOCKHOLM', name: 'Asmodee',      query: 'Asmodee',      color: '#7A2D8E' },
];

// Client-side cache duration for ranked news (limits Edge Function invocations)
const NEWS_CACHE_MS = 24 * 60 * 60 * 1000; // 24 hours

const PRESET_COLORS = [
  '#FF5500', '#0E7C3A', '#7A2D8E', '#1A4FB5',
  '#C9A24A', '#E63946', '#2196F3', '#795548',
];

// Best-effort Bloomberg ticker from a TradingView symbol (e.g. "OMXSTO:ASMDEE_B"
// → "ASMDEE SS Equity"). Bloomberg's country/exchange suffixes aren't public-API
// derivable, so this is only a suggested default the owner can correct.
const BBG_EXCH = {
  NASDAQ: 'US', NYSE: 'US', AMEX: 'US', BATS: 'US', OTC: 'US', ARCA: 'US',
  OMXSTO: 'SS', OMXHEX: 'FH', OMXCOP: 'DC', LSE: 'LN', LSIN: 'LN',
  XETR: 'GR', FWB: 'GR', FRA: 'GR', TRADEGATE: 'GR',
  EURONEXT: 'FP', EPA: 'FP', AMS: 'NA', EBR: 'BB', BME: 'SM', BMAD: 'SM',
  BIT: 'IM', MIL: 'IM', SWX: 'SW', SIX: 'SW', VIE: 'AV',
  TSX: 'CN', TSXV: 'CN', HKEX: 'HK', SEHK: 'HK', TSE: 'JP', JPX: 'JP',
  ASX: 'AU', SGX: 'SP', KRX: 'KS', NSE: 'IN', BSE: 'IN', SAU: 'AB',
};
const suggestBloomberg = (tvSymbol) => {
  if (!tvSymbol || tvSymbol.indexOf(':') < 0) return '';
  const [exch, rawSym] = tvSymbol.split(':');
  const suffix = BBG_EXCH[exch.toUpperCase()];
  if (!suffix) return '';
  // Strip share-class suffixes TradingView appends (ASMDEE_B → ASMDEE); Bloomberg
  // usually carries the class differently or not at all.
  const sym = rawSym.replace(/[._][A-Z]$/i, '').replace(/[^A-Za-z0-9]/g, '');
  return `${sym} ${suffix} Equity`;
};
const parsePeers = (s) => (s || '').split(/[\n,]+/).map((x) => x.trim()).filter(Boolean);

// Collapse state persisted in localStorage so fold/unfold preferences stick.
const useCollapsed = (key, def = false) => {
  const k = 'forme.collapse.' + key;
  const [c, setC] = React.useState(() => { try { const v = localStorage.getItem(k); return v == null ? def : v === '1'; } catch { return def; } });
  const toggle = () => setC((p) => { const n = !p; try { localStorage.setItem(k, n ? '1' : '0'); } catch {} return n; });
  return [c, toggle];
};

// Subtle grey fold/unfold chevron. Points down when open, right when collapsed.
const Chevron = ({ collapsed, onClick, size = 11 }) => (
  <button onClick={onClick} title={collapsed ? 'Expand' : 'Collapse'}
    style={{ background: 'none', border: 'none', cursor: 'pointer', color: tokens.inkMute, padding: '2px 6px', fontSize: size + 'px', lineHeight: 1, fontFamily: fontMono, display: 'inline-flex', alignItems: 'center', transition: 'transform 0.15s ease', transform: collapsed ? 'rotate(-90deg)' : 'none' }}>▾</button>
);

// Lazy-load the vis-network UMD bundle (exposes window.vis) once.
const ensureVis = (ok, fail) => {
  if (window.vis && window.vis.Network) return ok();
  let s = document.getElementById('vis-network-js');
  if (!s) {
    s = document.createElement('script');
    s.id = 'vis-network-js';
    s.src = 'https://unpkg.com/vis-network@9.1.9/standalone/umd/vis-network.min.js';
    s.onload = () => ok();
    s.onerror = () => fail && fail();
    document.body.appendChild(s);
  } else if (window.vis && window.vis.Network) { ok(); }
  else { s.addEventListener('load', ok, { once: true }); s.addEventListener('error', () => fail && fail(), { once: true }); }
};



// ─────────────────────────────────────────────────────────────────────────────
// TradingView chart — loads tv.js once, re-initialises on ticker change
// ─────────────────────────────────────────────────────────────────────────────
const TradingViewChart = ({ investment, height }) => {
  const h = height || 460;
  const containerRef = React.useRef(null);

  React.useEffect(() => {
    const container = containerRef.current;
    if (!container) return;
    container.innerHTML = '';

    const divId = 'tv_' + investment.ticker + '_' + Date.now();
    const el = document.createElement('div');
    el.id = divId;
    container.appendChild(el);

    const init = () => {
      if (!window.TradingView) return;
      new window.TradingView.widget({
        container_id: divId,
        symbol: investment.tvSymbol || (investment.exchange + ':' + investment.ticker),
        interval: 'D',
        width: '100%',
        height: h,
        theme: 'light',
        style: '1',
        locale: 'en',
        toolbar_bg: '#F4F1EA',
        enable_publishing: false,
        save_image: false,
        allow_symbol_change: false,
      });
    };

    const existingScript = document.getElementById('tv-script');
    if (window.TradingView) {
      init();
    } else if (!existingScript) {
      const s = document.createElement('script');
      s.id = 'tv-script';
      s.src = 'https://s3.tradingview.com/tv.js';
      s.async = true;
      s.onload = init;
      document.head.appendChild(s);
    } else {
      existingScript.addEventListener('load', init, { once: true });
    }

    return () => { container.innerHTML = ''; };
  }, [investment.tvSymbol, investment.ticker, investment.exchange]);

  return <div ref={containerRef} style={{ minHeight: h + 'px' }} />;
};

// ─────────────────────────────────────────────────────────────────────────────
// News Intelligence feed — calls the Supabase Edge Function, falls back to links
// ─────────────────────────────────────────────────────────────────────────────
const SIGNAL = {
  high:   { label: 'HIGH SIGNAL', bg: '#0E0E0C', color: '#F4F1EA' },
  medium: { label: 'NOTABLE',     bg: '#EDE9DF', color: '#0E0E0C' },
};
const level = (score) => score >= 7 ? 'high' : 'medium';

const NewsIntelFeed = ({ investment }) => {
  const ticker = investment.ticker;
  const cacheKey = 'newsintel:' + ticker;
  const [articles, setArticles]     = React.useState([]);
  const [status, setStatus]         = React.useState('idle');
  const [ts, setTs]                 = React.useState(null);
  const [cached, setCached]         = React.useState(false);
  const [rankingOff, setRankingOff] = React.useState(false);

  const load = React.useCallback(async (force) => {
    const client = window._supabaseClient;
    if (!client) { setStatus('unconfigured'); return; }

    if (!force) {
      try {
        const hit = JSON.parse(localStorage.getItem(cacheKey) || 'null');
        if (hit && Date.now() - hit.ts < NEWS_CACHE_MS) {
          setArticles(hit.articles || []);
          setRankingOff(!!hit.noApiKey);
          setTs(new Date(hit.ts));
          setCached(true);
          setStatus('ready');
          return;
        }
      } catch { /* ignore corrupt cache */ }
    }

    setStatus('loading');
    try {
      const { data, error } = await client.functions.invoke('investment-intel', {
        body: { tickers: [ticker], queries: { [ticker]: investment.query || ticker } },
      });
      if (error) throw new Error(error.message || 'Edge function error');
      const arts = data.articles || [];
      setArticles(arts);
      setRankingOff(!!data.noApiKey);
      setTs(new Date());
      setCached(false);
      setStatus('ready');
      try {
        localStorage.setItem(cacheKey, JSON.stringify({ articles: arts, noApiKey: !!data.noApiKey, ts: Date.now() }));
      } catch { /* storage quota */ }
    } catch (e) {
      console.error('NewsIntel:', e);
      setStatus('error');
    }
  }, [ticker, investment.query, cacheKey]);

  React.useEffect(() => { load(false); }, [load]);

  const fmtDate = (unix) =>
    new Date(unix * 1000).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });

  const header = (
    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
      <span style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.18em', color: tokens.inkMute }}>NEWS INTELLIGENCE</span>
      <div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
        {rankingOff && (
          <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.red, letterSpacing: '0.1em' }}>NO AI KEY</span>
        )}
        {cached && (
          <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, letterSpacing: '0.1em' }} title="Cached — click ↻ to refresh">CACHED</span>
        )}
        {ts && (
          <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute }}>
            {ts.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} {ts.toLocaleTimeString()}
          </span>
        )}
        <span onClick={() => load(true)} style={{ cursor: 'pointer', fontSize: '14px', color: tokens.ink }} title="Refresh now">↻</span>
      </div>
    </div>
  );

  if (status === 'unconfigured') return (
    <div>
      {header}
      <div style={{ fontSize: '12px', color: tokens.inkSoft, lineHeight: 1.6, marginBottom: '12px' }}>
        Deploy the <strong>investment-intel</strong> Edge Function — see <strong>INVESTMENT-INTEL-SETUP.md</strong>.
      </div>
      <a href={`https://finance.yahoo.com/quote/${ticker}/news`} target="_blank" rel="noopener noreferrer"
        style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.ink, borderBottom: `1px solid ${tokens.inkLine}` }}>
        {ticker} NEWS ON YAHOO FINANCE ↗
      </a>
    </div>
  );

  if (status === 'loading') return (
    <div>
      {header}
      <div style={{ paddingTop: '32px', textAlign: 'center', fontFamily: fontMono, fontSize: '11px', letterSpacing: '0.14em', color: tokens.inkMute }}>
        FETCHING &amp; RANKING…
      </div>
    </div>
  );

  if (status === 'error') return (
    <div>
      {header}
      <div style={{ fontSize: '13px', color: tokens.red, marginBottom: '12px' }}>
        Could not reach edge function.{' '}
        <span style={{ cursor: 'pointer', borderBottom: '1px solid currentColor' }} onClick={() => load(true)}>Retry</span>
      </div>
      <a href={`https://finance.yahoo.com/quote/${ticker}/news`} target="_blank" rel="noopener noreferrer"
        style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.ink, borderBottom: `1px solid ${tokens.inkLine}` }}>
        {ticker} NEWS ON YAHOO FINANCE ↗
      </a>
    </div>
  );

  const visible = articles.filter(a => a.score == null || a.score >= 5);

  return (
    <div style={{ height: '100%' }}>
      {header}
      <div style={{ overflowY: 'auto', maxHeight: '420px' }}>
        {visible.length === 0 && (
          <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, letterSpacing: '0.1em' }}>NO ARTICLES FOUND</div>
        )}
        {visible.map((a, i) => {
          const lv = a.score != null ? level(a.score) : 'medium';
          const sig = SIGNAL[lv];
          return (
            <a key={a.uuid || i} href={a.link} target="_blank" rel="noopener noreferrer"
              style={{ display: 'block', padding: '14px 0', borderBottom: `1px solid ${tokens.inkLineSoft}`, textDecoration: 'none', color: tokens.ink }}>
              <div style={{ display: 'flex', gap: '6px', alignItems: 'center', marginBottom: '7px', flexWrap: 'wrap' }}>
                <span style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.12em', padding: '2px 6px', background: sig.bg, color: sig.color }}>
                  {sig.label}
                </span>
                {a.score != null && (
                  <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute }}>{a.score}/10</span>
                )}
              </div>
              <div style={{ fontSize: '13px', lineHeight: 1.45, letterSpacing: '-0.01em', marginBottom: '5px' }}>{a.title}</div>
              <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.06em', color: tokens.inkMute }}>
                {a.publisher}{a.providerPublishTime ? ' · ' + fmtDate(a.providerPublishTime) : ''}
              </div>
              {a.reason && (
                <div style={{ marginTop: '5px', fontSize: '12px', color: tokens.inkSoft, lineHeight: 1.4, fontStyle: 'italic' }}>{a.reason}</div>
              )}
            </a>
          );
        })}
      </div>
    </div>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// Talent Signal — job-postings intelligence
//
// Reads the last snapshot from `job_snapshots` on mount (no tokens spent). The
// "Refresh" button calls the `job-intel` Edge Function, which scrapes the
// company's ATS (Greenhouse/Lever/Ashby/Workday), recomputes hiring stats and
// velocity, and asks Claude for an owner-focused briefing — so tokens are only
// spent on an explicit click. Set each ticker's `jobs_source` in Supabase
// (see JOBS-INTEL-SETUP.md).
// ─────────────────────────────────────────────────────────────────────────────

// Minimal markdown → JSX for the Claude briefing (## headers, - bullets, **bold**).
const renderBrief = (text) => {
  const bold = (s) => s.split(/(\*\*[^*]+\*\*)/g).map((part, i) =>
    part.startsWith('**') && part.endsWith('**')
      ? <strong key={i} style={{ fontWeight: 600 }}>{part.slice(2, -2)}</strong>
      : part
  );
  const blocks = [];
  let bullets = [];
  const flush = () => {
    if (bullets.length) {
      blocks.push(
        <ul key={'ul' + blocks.length} style={{ margin: '6px 0 12px', paddingLeft: '18px' }}>
          {bullets.map((b, i) => <li key={i} style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.55, marginBottom: '4px' }}>{bold(b)}</li>)}
        </ul>
      );
      bullets = [];
    }
  };
  (text || '').split('\n').forEach((raw, i) => {
    const line = raw.trim();
    if (!line) { flush(); return; }
    if (line.startsWith('## ')) {
      flush();
      blocks.push(<div key={'h' + i} style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.inkMute, margin: '16px 0 8px' }}>{line.slice(3).toUpperCase()}</div>);
    } else if (line.startsWith('- ') || line.startsWith('* ')) {
      bullets.push(line.slice(2));
    } else {
      flush();
      blocks.push(<p key={'p' + i} style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.6, margin: '0 0 10px' }}>{bold(line)}</p>);
    }
  });
  flush();
  return blocks;
};

const JobsPanel = ({ investment }) => {
  const isMobile = useIsMobile();
  const ticker = investment.ticker;
  const accent = investment.color || tokens.ink;
  const [collapsed, toggleCollapsed] = useCollapsed('jobs');

  const [status, setStatus] = React.useState('init'); // init|empty|loading|ready|error
  const [snap, setSnap]     = React.useState(null);
  const [errMsg, setErrMsg] = React.useState('');
  const [deptFilter, setDeptFilter] = React.useState(null);
  const [urlInput, setUrlInput] = React.useState('');
  const [showSource, setShowSource] = React.useState(false);

  // Load the last persisted snapshot (no tokens spent). Detection happens on Refresh.
  React.useEffect(() => {
    let cancelled = false;
    setSnap(null); setDeptFilter(null); setStatus('init');
    const client = window._supabaseClient;
    if (!client) { setStatus('empty'); return; }
    (async () => {
      const { data } = await client.from('job_snapshots').select('data, fetched_at').eq('ticker', ticker).maybeSingle();
      if (cancelled) return;
      if (data && data.data) { setSnap(data.data); setStatus('ready'); }
      else setStatus('empty');
    })();
    return () => { cancelled = true; };
  }, [ticker]);

  const refresh = React.useCallback(async (careersUrlArg) => {
    const client = window._supabaseClient;
    if (!client) return;
    if (!roleCanResearch()) { setErrMsg(DEMO_BLOCK_MSG); setStatus('error'); return; }
    // A freshly pasted URL forces re-detection from that URL, ignoring any
    // previously-saved (possibly wrong) jobs_source.
    const explicit = !!(careersUrlArg && careersUrlArg.trim());
    const careersUrl = (careersUrlArg || investment.careersUrl || '').trim();
    setStatus('loading'); setErrMsg('');
    try {
      const { data, error } = await client.functions.invoke('job-intel', {
        body: {
          ticker,
          source: explicit ? undefined : (investment.jobsSource || undefined),
          company: investment.name || ticker,
          careersUrl: careersUrl || undefined,
        },
      });
      // Surface the function's own error body (it returns 200 with {error});
      // fall back to reading a non-2xx response body if needed.
      let payload = data;
      if (error) {
        try { payload = await error.context.json(); } catch { /* keep generic */ }
        if (!payload || !payload.error) throw new Error(error.message || 'Edge function error');
      }
      if (payload && payload.error) { setErrMsg(payload.error); setStatus('error'); return; }

      setSnap(payload); setStatus('ready'); setDeptFilter(null); setUrlInput(''); setShowSource(false);

      // Persist what we learned so future refreshes are direct
      const { data: { user } } = await client.auth.getUser();
      if (user) {
        const patch = {};
        if (payload.detected_source && (explicit || !investment.jobsSource)) {
          patch.jobs_source = payload.detected_source;
          investment.jobsSource = payload.detected_source;
        }
        if (careersUrlArg && careersUrlArg.trim() && careersUrlArg.trim() !== investment.careersUrl) {
          patch.careers_url = careersUrlArg.trim();
          investment.careersUrl = careersUrlArg.trim();
        }
        if (Object.keys(patch).length) {
          await client.from('live_investments').update(patch).eq('ticker', ticker);
        }
      }
    } catch (e) {
      console.error('JobIntel:', e);
      setErrMsg(e.message || 'Refresh failed.');
      setStatus('error');
    }
  }, [ticker, investment.jobsSource, investment.careersUrl, investment.name]);

  const fetchedStr = snap && snap.fetched_at
    ? new Date(snap.fetched_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + ' ' +
      new Date(snap.fetched_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
    : null;

  const header = (
    <div style={{ padding: '12px 20px', borderBottom: collapsed ? 'none' : `1px solid ${tokens.inkLineSoft}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
        <Chevron collapsed={collapsed} onClick={toggleCollapsed} />
        <span style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.18em', color: tokens.inkMute }}>
          TALENT SIGNAL · {ticker} HIRING
        </span>
      </div>
      <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
        {snap && snap.noApiKey && (
          <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.red, letterSpacing: '0.1em' }}>NO AI KEY</span>
        )}
        {fetchedStr && (
          <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute }}>{fetchedStr}</span>
        )}
        {snap && (
          <span onClick={() => setShowSource(s => !s)}
            style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.1em', color: tokens.inkMute, cursor: 'pointer', borderBottom: `1px solid ${tokens.inkLine}` }}
            title="Change the job-board source">
            SOURCE
          </span>
        )}
        <button onClick={() => refresh()} disabled={status === 'loading'}
          style={{
            fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.1em', padding: '6px 14px',
            background: status === 'loading' ? 'none' : tokens.ink, color: status === 'loading' ? tokens.inkMute : tokens.paper,
            border: `1px solid ${status === 'loading' ? tokens.inkLine : tokens.ink}`,
            cursor: status === 'loading' ? 'default' : 'pointer',
          }}>
          {status === 'loading' ? 'WORKING…' : (snap ? '↻ REFRESH' : 'DETECT & PULL')}
        </button>
      </div>
    </div>
  );

  const shell = (body) => (
    <div style={{ border: `1px solid ${tokens.inkLine}`, background: tokens.paper, marginTop: '16px' }}>
      {header}
      {!collapsed && body}
    </div>
  );

  // Inline box to point a ticker at its careers / job-board URL when name-detection misses
  const careersBox = (
    <div style={{ display: 'flex', gap: '8px', maxWidth: '560px', margin: '0 auto' }}>
      <input
        value={urlInput}
        onChange={e => setUrlInput(e.target.value)}
        onKeyDown={e => { if (e.key === 'Enter' && urlInput.trim()) refresh(urlInput); }}
        placeholder="https://careers.company.com  ·  or the Greenhouse/Workday/Lever URL"
        style={{ flex: 1, padding: '8px 10px', border: `1px solid ${tokens.inkLine}`, background: '#FDFBF7', fontFamily: 'inherit', fontSize: '12px', color: tokens.ink, outline: 'none' }} />
      <button onClick={() => urlInput.trim() && refresh(urlInput)}
        style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.1em', padding: '8px 14px', background: tokens.ink, color: tokens.paper, border: 'none', cursor: urlInput.trim() ? 'pointer' : 'default', opacity: urlInput.trim() ? 1 : 0.5 }}>
        DETECT
      </button>
    </div>
  );

  if (status === 'init') return shell(
    <div style={{ padding: '20px', fontFamily: fontMono, fontSize: '11px', letterSpacing: '0.12em', color: tokens.inkMute }}>LOADING…</div>
  );

  if (status === 'empty') return shell(
    <div style={{ padding: '24px 20px', textAlign: 'center' }}>
      <div style={{ fontSize: '13px', color: tokens.inkSoft, marginBottom: '14px', lineHeight: 1.55 }}>
        No snapshot yet for {ticker}. Click <strong>Detect &amp; Pull</strong> — it auto-finds {investment.name || ticker}'s
        job board and runs the analysis. If it can't find it by name, paste the careers / job-board URL:
      </div>
      {careersBox}
    </div>
  );

  if (status === 'loading' && !snap) return shell(
    <div style={{ padding: '40px 20px', textAlign: 'center', fontFamily: fontMono, fontSize: '11px', letterSpacing: '0.14em', color: tokens.inkMute }}>
      DETECTING, PULLING &amp; ANALYZING…
    </div>
  );

  if (status === 'error' && !snap) return shell(
    <div style={{ padding: '20px 20px 24px' }}>
      <div style={{ fontSize: '13px', color: tokens.red, lineHeight: 1.5, marginBottom: '16px', textAlign: 'center' }}>{errMsg}</div>
      {careersBox}
    </div>
  );

  // ── Ready (or refreshing over existing data) ──
  const stats = (snap && snap.stats) || {};
  const postings = (snap && snap.postings) || [];
  const depts = Object.entries(stats.by_department || {});
  const maxDept = depts.reduce((m, [, n]) => Math.max(m, n), 0) || 1;
  const filtered = deptFilter ? postings.filter(p => (p.department || 'Unspecified') === deptFilter) : postings;

  const deltaChip = stats.delta != null && stats.delta !== 0 && (
    <span style={{ fontFamily: fontMono, fontSize: '11px', color: stats.delta > 0 ? '#0E7C3A' : tokens.red, marginLeft: '10px' }}>
      {stats.delta > 0 ? '▲ +' : '▼ '}{stats.delta} vs last
    </span>
  );

  return shell(
    <div>
      {errMsg && status === 'error' && (
        <div style={{ padding: '8px 20px', fontSize: '12px', color: tokens.red, borderBottom: `1px solid ${tokens.inkLineSoft}` }}>{errMsg}</div>
      )}
      {showSource && (
        <div style={{ padding: '16px 20px', borderBottom: `1px solid ${tokens.inkLineSoft}`, background: '#FBF9F4' }}>
          <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.12em', color: tokens.inkMute, marginBottom: '8px' }}>
            CURRENT SOURCE: <span style={{ color: tokens.ink }}>{(snap && snap.source) || investment.jobsSource || '—'}</span>
            {' · '}wrong company? paste the correct careers / job-board URL to replace it:
          </div>
          {careersBox}
        </div>
      )}
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1.4fr' }}>

        {/* Left: stats + department mix */}
        <div style={{ padding: '20px', borderRight: isMobile ? 'none' : `1px solid ${tokens.inkLine}`, borderBottom: isMobile ? `1px solid ${tokens.inkLine}` : 'none' }}>
          <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.inkMute, marginBottom: '6px' }}>OPEN ROLES</div>
          <div style={{ display: 'flex', alignItems: 'baseline' }}>
            <span style={{ fontSize: '40px', lineHeight: 1, letterSpacing: '-0.03em', color: accent }}>{stats.total ?? 0}</span>
            {deltaChip}
          </div>
          {stats.recent_30d != null && stats.recent_30d > 0 && (
            <div style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.inkMute, marginTop: '8px' }}>{stats.recent_30d} posted in last 30 days</div>
          )}

          <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.inkMute, margin: '22px 0 10px' }}>BY FUNCTION</div>
          {depts.slice(0, 8).map(([d, n]) => {
            const active = deptFilter === d;
            return (
              <div key={d} onClick={() => setDeptFilter(active ? null : d)}
                style={{ cursor: 'pointer', marginBottom: '8px', opacity: deptFilter && !active ? 0.45 : 1 }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '11px', color: tokens.ink, marginBottom: '3px' }}>
                  <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', paddingRight: '8px' }}>{d}</span>
                  <span style={{ fontFamily: fontMono, color: tokens.inkMute }}>{n}</span>
                </div>
                <div style={{ height: '3px', background: tokens.inkLineSoft }}>
                  <div style={{ height: '100%', width: `${(n / maxDept) * 100}%`, background: accent }} />
                </div>
              </div>
            );
          })}
          {deptFilter && (
            <div onClick={() => setDeptFilter(null)} style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.1em', color: tokens.inkMute, cursor: 'pointer', marginTop: '4px' }}>
              ✕ CLEAR FILTER
            </div>
          )}
        </div>

        {/* Right: Claude briefing */}
        <div style={{ padding: '20px' }}>
          <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.inkMute, marginBottom: '4px' }}>OWNER BRIEFING · CLAUDE</div>
          {snap && snap.analysis
            ? <div>{renderBrief(snap.analysis)}</div>
            : <div style={{ fontSize: '12px', color: tokens.inkMute, lineHeight: 1.6, paddingTop: '6px' }}>
                {snap && snap.noApiKey
                  ? 'Set the ANTHROPIC_API_KEY secret on the job-intel function to generate the briefing.'
                  : 'No briefing generated. Refresh to analyze.'}
              </div>}
        </div>
      </div>

      {/* Postings list */}
      <div style={{ borderTop: `1px solid ${tokens.inkLine}`, padding: '14px 20px 4px' }}>
        <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.inkMute, marginBottom: '4px' }}>
          {deptFilter ? `${deptFilter.toUpperCase()} · ${filtered.length}` : `ALL POSTINGS · ${filtered.length}`}
        </div>
      </div>
      <div style={{ maxHeight: '360px', overflowY: 'auto', padding: '0 20px 16px' }}>
        {filtered.length === 0 && (
          <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, letterSpacing: '0.1em', padding: '12px 0' }}>NO POSTINGS</div>
        )}
        {filtered.map((p, i) => (
          <a key={p.id || i} href={p.url} target="_blank" rel="noopener noreferrer"
            style={{ display: 'flex', justifyContent: 'space-between', gap: '16px', alignItems: 'baseline', padding: '11px 0', borderBottom: `1px solid ${tokens.inkLineSoft}`, textDecoration: 'none', color: tokens.ink }}>
            <div style={{ minWidth: 0 }}>
              <div style={{ fontSize: '13px', lineHeight: 1.35 }}>{p.title}</div>
              <div style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.inkMute, marginTop: '3px', letterSpacing: '0.04em' }}>
                {[p.department, p.location].filter(Boolean).join(' · ')}
              </div>
            </div>
            <span style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.inkMute, whiteSpace: 'nowrap' }}>
              {p.updated_at ? new Date(p.updated_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) : '↗'}
            </span>
          </a>
        ))}
      </div>
    </div>
  );
};


// ─────────────────────────────────────────────────────────────────────────────
// Add Ticker form
// ─────────────────────────────────────────────────────────────────────────────
const AddTickerForm = ({ onAdd, onCancel }) => {
  const isMobile = useIsMobile();
  const [form, setForm]     = React.useState({ ticker: '', name: '', tvSymbol: '', query: '', careersUrl: '', jobsSource: '', bloombergTicker: '', peers: '', color: PRESET_COLORS[3] });
  const [saving, setSaving] = React.useState(false);
  const [err, setErr]       = React.useState('');

  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
  // Auto-suggest the Bloomberg ticker when the TradingView symbol is set and the
  // Bloomberg field hasn't been hand-edited.
  const setTv = (v) => setForm(f => ({ ...f, tvSymbol: v, bloombergTicker: (!f.bloombergTicker || f.bloombergTicker === suggestBloomberg(f.tvSymbol)) ? suggestBloomberg(v) : f.bloombergTicker }));

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!form.ticker.trim() || !form.name.trim() || !form.tvSymbol.trim()) {
      setErr('Ticker, name, and TradingView symbol are required.');
      return;
    }
    setSaving(true);
    setErr('');
    const error = await onAdd({
      ticker:     form.ticker.trim().toUpperCase(),
      name:       form.name.trim(),
      tvSymbol:   form.tvSymbol.trim(),
      exchange:   form.tvSymbol.trim().split(':')[0],
      query:      form.query.trim() || form.name.trim(),
      careersUrl: form.careersUrl.trim(),
      jobsSource: form.jobsSource.trim(),
      bloombergTicker: form.bloombergTicker.trim(),
      peers:      parsePeers(form.peers),
      color:      form.color,
    });
    if (error) { setErr(error.message || 'Failed to add ticker.'); setSaving(false); }
  };

  const lbl = {
    fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.12em',
    color: tokens.inkMute, display: 'block', marginBottom: '5px',
  };
  const inp = {
    width: '100%', padding: '8px 10px', border: `1px solid ${tokens.inkLine}`,
    background: '#FDFBF7', fontFamily: 'inherit', fontSize: '13px',
    color: tokens.ink, boxSizing: 'border-box', outline: 'none',
  };

  return (
    <form onSubmit={handleSubmit}
      style={{ border: `1px solid ${tokens.inkLine}`, background: tokens.paper, padding: '20px', marginBottom: '20px' }}>
      <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.16em', color: tokens.inkMute, marginBottom: '16px' }}>
        ADD TICKER
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '110px 1fr 1fr', gap: '12px', marginBottom: '12px' }}>
        <div>
          <label style={lbl}>TICKER *</label>
          <input style={inp} placeholder="AAPL" value={form.ticker}
            onChange={e => set('ticker', e.target.value.toUpperCase())} autoFocus maxLength={12} />
        </div>
        <div>
          <label style={lbl}>COMPANY NAME *</label>
          <input style={inp} placeholder="Apple Inc." value={form.name}
            onChange={e => set('name', e.target.value)} />
        </div>
        <div>
          <label style={lbl}>TRADINGVIEW SYMBOL *</label>
          <input style={inp} placeholder="NASDAQ:AAPL" value={form.tvSymbol}
            onChange={e => setTv(e.target.value)} />
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: '12px', marginBottom: '12px' }}>
        <div>
          <label style={lbl}>BLOOMBERG TICKER <span style={{ opacity: 0.55 }}>(for Bloomberg pull &amp; comps)</span></label>
          <input style={inp} placeholder="PAYO US Equity" value={form.bloombergTicker}
            onChange={e => set('bloombergTicker', e.target.value)} />
          {suggestBloomberg(form.tvSymbol) && form.bloombergTicker !== suggestBloomberg(form.tvSymbol) && (
            <div onClick={() => set('bloombergTicker', suggestBloomberg(form.tvSymbol))}
              style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, marginTop: '4px', cursor: 'pointer' }}>
              ↳ suggested: <span style={{ color: tokens.ink, borderBottom: `1px solid ${tokens.inkLine}` }}>{suggestBloomberg(form.tvSymbol)}</span>
            </div>
          )}
        </div>
        <div>
          <label style={lbl}>PEERS <span style={{ opacity: 0.55 }}>(comma-separated Bloomberg tickers, for comps)</span></label>
          <input style={inp} placeholder="GPN US Equity, FOUR US Equity, FIS US Equity" value={form.peers}
            onChange={e => set('peers', e.target.value)} />
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: '12px', marginBottom: '12px' }}>
        <div>
          <label style={lbl}>YAHOO SEARCH TERM <span style={{ opacity: 0.55 }}>(optional — defaults to name)</span></label>
          <input style={inp} placeholder="e.g. Payoneer, i3 Verticals" value={form.query}
            onChange={e => set('query', e.target.value)} />
        </div>
        <div>
          <label style={lbl}>CAREERS URL <span style={{ opacity: 0.55 }}>(optional — helps auto-detect jobs)</span></label>
          <input style={inp} placeholder="https://careers.company.com" value={form.careersUrl}
            onChange={e => set('careersUrl', e.target.value)} />
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr auto', gap: '12px', alignItems: 'end', marginBottom: '16px' }}>
        <div />
        <div>
          <label style={lbl}>COLOR</label>
          <div style={{ display: 'flex', gap: '6px', alignItems: 'center', paddingTop: '2px' }}>
            {PRESET_COLORS.map(c => (
              <div key={c} onClick={() => set('color', c)} title={c}
                style={{
                  width: '22px', height: '22px', borderRadius: '50%', background: c,
                  cursor: 'pointer', flexShrink: 0,
                  boxShadow: form.color === c
                    ? `0 0 0 2px ${tokens.paper}, 0 0 0 3.5px ${c}`
                    : 'none',
                }} />
            ))}
            <input type="color" value={form.color} onChange={e => set('color', e.target.value)}
              title="Custom colour"
              style={{ width: '28px', height: '22px', border: `1px solid ${tokens.inkLine}`, padding: '1px', cursor: 'pointer', background: 'none' }} />
          </div>
        </div>
      </div>

      {err && <div style={{ color: tokens.red, fontSize: '12px', marginBottom: '12px' }}>{err}</div>}

      <div style={{ display: 'flex', gap: '8px' }}>
        <button type="submit" disabled={saving}
          style={{
            fontFamily: fontMono, fontSize: '11px', letterSpacing: '0.1em',
            padding: '9px 22px', background: tokens.ink, color: tokens.paper,
            border: 'none', cursor: saving ? 'default' : 'pointer', opacity: saving ? 0.6 : 1,
          }}>
          {saving ? 'ADDING…' : 'ADD TICKER'}
        </button>
        <button type="button" onClick={onCancel}
          style={{
            fontFamily: fontMono, fontSize: '11px', letterSpacing: '0.1em',
            padding: '9px 16px', background: 'none', border: `1px solid ${tokens.inkLine}`,
            cursor: 'pointer', color: tokens.inkMute,
          }}>
          CANCEL
        </button>
      </div>
    </form>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// Per-ticker settings — edit Bloomberg ticker, peers, TradingView symbol, etc.
// for an EXISTING holding, straight from the page (no SQL editor).
// ─────────────────────────────────────────────────────────────────────────────
const TickerSettings = ({ inv, onSave, onClose }) => {
  const isMobile = useIsMobile();
  const [f, setF] = React.useState({
    name: inv.name || '', tvSymbol: inv.tvSymbol || '', query: inv.query || '',
    careersUrl: inv.careersUrl || '', bloombergTicker: inv.bloombergTicker || '',
    peers: (inv.peers || []).join(', '), color: inv.color || PRESET_COLORS[0],
  });
  const [saving, setSaving] = React.useState(false);
  const [err, setErr] = React.useState('');
  const set = (k, v) => setF(p => ({ ...p, [k]: v }));
  const lbl = { fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.12em', color: tokens.inkMute, display: 'block', marginBottom: '5px' };
  const inp = { width: '100%', padding: '8px 10px', border: `1px solid ${tokens.inkLine}`, background: '#FDFBF7', fontFamily: 'inherit', fontSize: '13px', color: tokens.ink, boxSizing: 'border-box', outline: 'none' };

  const submit = async () => {
    setSaving(true); setErr('');
    const e = await onSave({
      name: f.name.trim() || inv.name,
      tv_symbol: f.tvSymbol.trim(),
      exchange: f.tvSymbol.trim().split(':')[0],
      query: f.query.trim() || f.name.trim() || inv.name,
      careers_url: f.careersUrl.trim() || null,
      bloomberg_ticker: f.bloombergTicker.trim() || null,
      peers: parsePeers(f.peers),
      color: f.color,
    });
    if (e) { setErr(e.message || 'Save failed.'); setSaving(false); } else onClose();
  };

  return (
    <div style={{ border: `1px solid ${tokens.inkLine}`, background: tokens.paper, padding: '20px', marginTop: '16px', marginBottom: '20px' }}>
      <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.16em', color: tokens.inkMute, marginBottom: '16px' }}>
        SETTINGS · {inv.ticker}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: '12px', marginBottom: '12px' }}>
        <div>
          <label style={lbl}>COMPANY NAME</label>
          <input style={inp} value={f.name} onChange={e => set('name', e.target.value)} />
        </div>
        <div>
          <label style={lbl}>TRADINGVIEW SYMBOL</label>
          <input style={inp} value={f.tvSymbol} onChange={e => set('tvSymbol', e.target.value)} placeholder="OMXSTO:ASMDEE_B" />
        </div>
        <div>
          <label style={lbl}>BLOOMBERG TICKER</label>
          <input style={inp} value={f.bloombergTicker} onChange={e => set('bloombergTicker', e.target.value)} placeholder="ASMDEE SS Equity" />
          {suggestBloomberg(f.tvSymbol) && f.bloombergTicker !== suggestBloomberg(f.tvSymbol) && (
            <div onClick={() => set('bloombergTicker', suggestBloomberg(f.tvSymbol))}
              style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, marginTop: '4px', cursor: 'pointer' }}>
              ↳ suggested: <span style={{ color: tokens.ink, borderBottom: `1px solid ${tokens.inkLine}` }}>{suggestBloomberg(f.tvSymbol)}</span>
            </div>
          )}
        </div>
        <div>
          <label style={lbl}>CAREERS URL <span style={{ opacity: 0.55 }}>(optional)</span></label>
          <input style={inp} value={f.careersUrl} onChange={e => set('careersUrl', e.target.value)} placeholder="https://careers.company.com" />
        </div>
        <div style={{ gridColumn: isMobile ? 'auto' : 'span 2' }}>
          <label style={lbl}>PEERS <span style={{ opacity: 0.55 }}>(comma-separated Bloomberg tickers — drives the comps panel)</span></label>
          <input style={inp} value={f.peers} onChange={e => set('peers', e.target.value)} placeholder="GPN US Equity, FOUR US Equity, FIS US Equity, FI US Equity" />
        </div>
      </div>

      <div style={{ marginBottom: '14px' }}>
        <label style={lbl}>COLOR</label>
        <div style={{ display: 'flex', gap: '6px', alignItems: 'center' }}>
          {PRESET_COLORS.map(c => (
            <div key={c} onClick={() => set('color', c)} title={c}
              style={{ width: '22px', height: '22px', borderRadius: '50%', background: c, cursor: 'pointer', flexShrink: 0, boxShadow: f.color === c ? `0 0 0 2px ${tokens.paper}, 0 0 0 3.5px ${c}` : 'none' }} />
          ))}
          <input type="color" value={f.color} onChange={e => set('color', e.target.value)} style={{ width: '28px', height: '22px', border: `1px solid ${tokens.inkLine}`, padding: '1px', cursor: 'pointer', background: 'none' }} />
        </div>
      </div>

      {err && <div style={{ color: tokens.red, fontSize: '12px', marginBottom: '12px' }}>{err}</div>}

      <div style={{ display: 'flex', gap: '8px' }}>
        <button onClick={submit} disabled={saving}
          style={{ fontFamily: fontMono, fontSize: '11px', letterSpacing: '0.1em', padding: '9px 22px', background: tokens.ink, color: tokens.paper, border: 'none', cursor: saving ? 'default' : 'pointer', opacity: saving ? 0.6 : 1 }}>
          {saving ? 'SAVING…' : 'SAVE'}
        </button>
        <button onClick={onClose}
          style={{ fontFamily: fontMono, fontSize: '11px', letterSpacing: '0.1em', padding: '9px 16px', background: 'none', border: `1px solid ${tokens.inkLine}`, cursor: 'pointer', color: tokens.inkMute }}>
          CANCEL
        </button>
        <span style={{ flexGrow: 1 }} />
        <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, alignSelf: 'center', letterSpacing: '0.04em' }}>
          THEN ⟳ RUN PULL (comps) · saved to live_investments
        </span>
      </div>
    </div>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// Bloomberg Financial Summary panel
// Reads from `bloomberg_snapshots` table (populated by scripts/bloomberg_push.py).
// Shows EV build-up waterfall + IS financials (LTM | FY1E | FY2E).
// ─────────────────────────────────────────────────────────────────────────────
// Local helper (scripts/bloomberg_runner.py) that lets the button trigger the
// Bloomberg pull on the owner's machine. Browsers permit https→localhost fetches.
const BLOOMBERG_RUNNER = 'http://127.0.0.1:8765';

const BloombergPanel = ({ investment }) => {
  const isMobile = useIsMobile();
  const [collapsed, toggleCollapsed] = useCollapsed('bloomberg');
  const [snap, setSnap]   = React.useState(null);
  const [status, setStatus] = React.useState('loading');
  const [runState, setRunState] = React.useState('idle'); // idle|running|ok|err
  const [runMsg, setRunMsg]     = React.useState('');

  const loadSnap = React.useCallback((quiet) => {
    const client = window._supabaseClient;
    if (!client) { setStatus('unconfigured'); return; }
    if (!quiet) { setStatus('loading'); setSnap(null); }
    client
      .from('bloomberg_snapshots')
      .select('bloomberg_ticker, fetched_at, data')
      .eq('ticker', investment.ticker)
      .single()
      .then(({ data, error }) => {
        if (error || !data) { if (!quiet) setStatus('empty'); return; }
        setSnap(data);
        setStatus('ready');
      });
  }, [investment.ticker]);

  React.useEffect(() => { loadSnap(false); }, [loadSnap]);

  const runPull = React.useCallback(async () => {
    setRunState('running');
    setRunMsg('Running bloomberg_push.py on your machine…');
    try {
      const res = await fetch(BLOOMBERG_RUNNER + '/run', { method: 'POST' });
      const j = await res.json().catch(() => ({}));
      if (j.ok) {
        setRunState('ok');
        setRunMsg('Pull complete — refreshing data.');
        loadSnap(true);
        // Tell sibling panels (Comps, Value-Gap) that the snapshot changed.
        window.dispatchEvent(new CustomEvent('bbg-snapshot-updated', { detail: investment.ticker }));
        setTimeout(() => { setRunState('idle'); setRunMsg(''); }, 5000);
      } else {
        setRunState('err');
        setRunMsg(j.error || 'Pull failed — is the Bloomberg Terminal open?');
      }
    } catch (e) {
      setRunState('err');
      setRunMsg('Local runner not reachable. Start it on your computer: python scripts/bloomberg_runner.py');
    }
  }, [loadSnap, investment.ticker]);

  if (status === 'unconfigured') return null;

  const panelHeader = (right) => (
    <React.Fragment>
      <div style={{ padding: '12px 20px', borderBottom: collapsed ? 'none' : `1px solid ${tokens.inkLineSoft}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '12px' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
          <Chevron collapsed={collapsed} onClick={toggleCollapsed} />
          <span style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.18em', color: tokens.inkMute }}>
            BLOOMBERG · FINANCIAL SUMMARY
          </span>
        </div>
        <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
          {right && <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute }}>{right}</span>}
          <button onClick={runPull} disabled={runState === 'running'}
            title="Run the Bloomberg pull on your computer (needs the local runner + Terminal open)"
            style={{
              fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.1em', padding: '6px 12px',
              background: runState === 'running' ? 'none' : tokens.ink,
              color: runState === 'running' ? tokens.inkMute : tokens.paper,
              border: `1px solid ${runState === 'running' ? tokens.inkLine : tokens.ink}`,
              cursor: runState === 'running' ? 'default' : 'pointer', whiteSpace: 'nowrap',
            }}>
            {runState === 'running' ? 'RUNNING…' : '⟳ RUN PULL'}
          </button>
        </div>
      </div>
      {!collapsed && runMsg && (
        <div style={{ padding: '7px 20px', fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.03em', lineHeight: 1.5, color: runState === 'err' ? tokens.red : tokens.inkSoft, borderBottom: `1px solid ${tokens.inkLineSoft}` }}>
          {runMsg}
        </div>
      )}
    </React.Fragment>
  );

  const shell = (header, body) => (
    <div style={{ border: `1px solid ${tokens.inkLine}`, background: tokens.paper, marginTop: '16px' }}>
      {header}
      {!collapsed && body}
    </div>
  );

  if (status === 'loading') return shell(
    panelHeader(''),
    <div style={{ padding: '20px', fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, letterSpacing: '0.1em' }}>LOADING…</div>
  );

  if (status === 'empty') return shell(
    panelHeader(''),
    <div style={{ padding: '20px' }}>
      <div style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.6, marginBottom: '10px' }}>
        No Bloomberg data for {investment.ticker} yet.
      </div>
      <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, lineHeight: 1.8 }}>
        Start the helper once (Terminal must be open): <span style={{ color: tokens.ink }}>python scripts/bloomberg_runner.py</span><br />
        then click <span style={{ color: tokens.ink }}>⟳ RUN PULL</span> above — or run <span style={{ color: tokens.ink }}>python scripts/bloomberg_push.py</span> directly.
      </div>
    </div>
  );

  // ── Data + format helpers ──
  const d   = snap.data;
  const ccy = d.reporting_currency || d.currency || 'USD';
  const sym = ({ USD: '$', EUR: '€', GBP: '£' })[ccy] || (ccy + ' ');

  const fmtM = (v) => {
    if (v == null) return '—';
    const abs = Math.abs(v);
    const neg = v < 0;
    const s = abs >= 1000 ? `${sym}${(abs / 1000).toFixed(1)}B` : `${sym}${Math.round(abs)}M`;
    return neg ? `(${s})` : s;
  };
  const fmtX   = (v) => v == null ? '—' : `${v.toFixed(1)}x`;
  const fmtPct = (n, dn) => (!n || !dn) ? '—' : `${(n / dn * 100).toFixed(1)}%`;
  const fmtEPS    = (v) => v == null ? '—' : `${sym}${Math.abs(v).toFixed(2)}`;
  const fmtGrowthVal = (pct) => {
    if (pct == null) return '—';
    return (pct >= 0 ? '+' : '') + pct.toFixed(1) + '%';
  };

  const ev      = d.ev_buildup    || {};
  const periods = d.periods       || [];
  const ml      = d.multiples_ltm || {};
  const mf      = d.multiples_fwd || [];

  // Multiples columns: LTM actual + each forward estimate year
  const mulCols = [ml, ...mf];

  // Shared cell styles
  const hdCellStyle = { fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.inkMute, paddingBottom: '7px', borderBottom: `1px solid ${tokens.inkLine}`, whiteSpace: 'nowrap' };
  const hdNum = (est) => ({ ...hdCellStyle, textAlign: 'right', color: est ? tokens.ink : tokens.inkMute });
  const numCellStyle = (v) => ({ fontFamily: fontMono, fontSize: '12px', color: v === '—' ? tokens.inkMute : tokens.ink, textAlign: 'right', padding: '7px 0', borderBottom: `1px solid ${tokens.inkLineSoft}`, whiteSpace: 'nowrap' });
  const lblCellStyle = (muted) => ({ fontSize: '12px', color: muted ? tokens.inkSoft : tokens.ink, padding: '7px 0', borderBottom: `1px solid ${tokens.inkLineSoft}` });

  const evRows = [
    { label: 'Market Cap',    value: fmtM(ev.market_cap), indent: false, bold: false },
    { label: '+ Net Debt',    value: fmtM(ev.net_debt),   indent: true,  bold: false },
    { label: '+ Minorities',  value: fmtM(ev.minorities), indent: true,  bold: false },
    { label: '+ Preferred',   value: fmtM(ev.preferred),  indent: true,  bold: false },
    { label: 'Enterprise Value', value: fmtM(ev.ev),      indent: false, bold: true  },
  ];

  const mulMetrics = [
    { label: 'EV / EBITDA',  key: 'ev_ebitda'  },
    { label: 'EV / Revenue', key: 'ev_revenue' },
    { label: 'P / E',        key: 'pe'         },
  ];

  // Income-statement rows defined as accessors over each period column
  const isMetrics = [
    { label: 'Revenue',       muted: false, get: p => fmtM(p.revenue) },
    // LTM overlaps the last reported fiscal year, so a "YoY" there is 0%/noise — blank it.
    { label: '↳ YoY growth',  muted: true,  get: p => p.kind === 'ltm' ? '—' : fmtGrowthVal(p.rev_growth) },
    { label: 'EBITDA',        muted: false, get: p => fmtM(p.ebitda) },
    { label: 'EBITDA margin', muted: true,  get: p => fmtPct(p.ebitda, p.revenue) },
    { label: 'EBIT',          muted: true,  get: p => fmtM(p.ebit) },
    { label: 'Net Income',    muted: false, get: p => fmtM(p.net_income) },
    { label: 'EPS (diluted)', muted: false, get: p => fmtEPS(p.eps) },
    { label: 'CFO',           muted: false, get: p => fmtM(p.cfo) },
    { label: 'FCF',           muted: false, get: p => fmtM(p.fcf) },
  ];

  const fye = d.fiscal_year_end;   // e.g. "12/31" — fiscal periods are FY-based, not calendarized
  const isHeader = isMobile
    ? `IS · ${ccy} M${fye ? ` · FYE ${fye}` : ''}`
    : `INCOME STATEMENT · ${ccy} M` + (fye ? ` · FYE ${fye}` : '');

  const fetchedAt = new Date(snap.fetched_at);
  const updatedStr = fetchedAt.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + ' ' +
    fetchedAt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });

  const isCols = `minmax(${isMobile ? '88px' : '120px'}, 1.4fr) repeat(${periods.length}, minmax(54px, 1fr))`;

  return shell(
    panelHeader(`${snap.bloomberg_ticker} · Updated ${updatedStr}`),
    <div>

      {/* ── Top: EV build-up + multiples side by side (stacked on mobile) ── */}
      <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', borderBottom: `1px solid ${tokens.inkLine}` }}>

        {/* EV waterfall */}
        <div style={{ padding: '20px', borderRight: isMobile ? 'none' : `1px solid ${tokens.inkLine}`, borderBottom: isMobile ? `1px solid ${tokens.inkLine}` : 'none' }}>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: '0 12px' }}>
            <div style={{ ...hdCellStyle }}>EV BUILD-UP</div>
            <div style={{ ...hdCellStyle, textAlign: 'right' }}>{ccy} M</div>
            {evRows.map(({ label, value, indent, bold }) => (
              <React.Fragment key={label}>
                <div style={{ fontSize: bold ? '13px' : '12px', fontWeight: bold ? 500 : 400, color: bold ? tokens.ink : tokens.inkSoft, padding: '7px 0', paddingLeft: indent ? '12px' : 0, borderBottom: bold ? `1px solid ${tokens.inkLine}` : `1px solid ${tokens.inkLineSoft}` }}>
                  {label}
                </div>
                <div style={{ fontFamily: fontMono, fontSize: bold ? '13px' : '12px', fontWeight: bold ? 500 : 400, color: value === '—' ? tokens.inkMute : tokens.ink, textAlign: 'right', padding: '7px 0', borderBottom: bold ? `1px solid ${tokens.inkLine}` : `1px solid ${tokens.inkLineSoft}` }}>
                  {value}
                </div>
              </React.Fragment>
            ))}
          </div>
        </div>

        {/* Multiples */}
        <div style={{ padding: '20px' }}>
          <div style={{ display: 'grid', gridTemplateColumns: `1fr repeat(${mulCols.length}, minmax(48px, auto))`, gap: '0 10px' }}>
            <div style={{ ...hdCellStyle }}>VALUATION</div>
            {mulCols.map((c, i) => (
              <div key={i} style={hdNum(i > 0)}>{c.label || (i === 0 ? 'LTM' : '—')}</div>
            ))}
            {mulMetrics.map(({ label, key }) => (
              <React.Fragment key={label}>
                <div style={lblCellStyle(true)}>{label}</div>
                {mulCols.map((c, i) => {
                  const v = fmtX(c[key]);
                  return <div key={i} style={numCellStyle(v)}>{v}</div>;
                })}
              </React.Fragment>
            ))}
          </div>
        </div>
      </div>

      {/* ── Bottom: full-width income statement (CY24A · CY25A · LTM · CY26E · CY27E) ── */}
      <div style={{ padding: '20px' }}>
        {/* Meta line sits above the scroll area so the long label never overlaps columns */}
        <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.inkMute, marginBottom: '10px' }}>{isHeader}</div>
        <div style={{ overflowX: 'auto', width: '100%', maxWidth: '100%', WebkitOverflowScrolling: 'touch' }}>
          <div style={{ display: 'grid', gridTemplateColumns: isCols, gap: '0 14px', minWidth: isMobile ? '520px' : 'auto' }}>
            <div style={{ ...hdCellStyle }} />
            {periods.map((p, i) => (
              <div key={i} style={hdNum(p.kind === 'estimate')}>{p.label}</div>
            ))}
            {isMetrics.map(({ label, get, muted }) => (
              <React.Fragment key={label}>
                <div style={lblCellStyle(muted)}>{label}</div>
                {periods.map((p, i) => {
                  const v = get(p);
                  return <div key={i} style={numCellStyle(v)}>{v}</div>;
                })}
              </React.Fragment>
            ))}
          </div>
        </div>
      </div>

    </div>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// Peer Comps panel — benchmarks the holding vs its configured peer set on
// operating metrics (2 historical + 3 forward). Reads bloomberg_snapshots.data
// .comps, populated by scripts/bloomberg_push.py from the live_investments.peers
// list. Pure read; same RUN PULL button on the Bloomberg panel refreshes it.
// ─────────────────────────────────────────────────────────────────────────────
const _median = (arr) => {
  const v = arr.filter((x) => x != null).sort((a, b) => a - b);
  if (!v.length) return null;
  const m = Math.floor(v.length / 2);
  return v.length % 2 ? v[m] : (v[m - 1] + v[m]) / 2;
};

const CompsPanel = ({ investment }) => {
  const isMobile = useIsMobile();
  const [collapsed, toggleCollapsed] = useCollapsed('comps');
  const [comps, setComps] = React.useState(undefined); // undefined=loading
  const [updated, setUpdated] = React.useState('');

  const loadComps = React.useCallback(() => {
    const client = window._supabaseClient;
    if (!client) { setComps(null); return; }
    client.from('bloomberg_snapshots').select('data, fetched_at').eq('ticker', investment.ticker).maybeSingle()
      .then(({ data }) => {
        if (!data) { setComps(null); return; }
        setUpdated(data.fetched_at ? new Date(data.fetched_at).toLocaleDateString() : '');
        setComps((data.data && data.data.comps) || null);
      });
  }, [investment.ticker]);

  React.useEffect(() => {
    loadComps();
    // Re-read when the Bloomberg panel completes a pull for this ticker.
    const onUpd = (e) => { if (!e.detail || e.detail === investment.ticker) loadComps(); };
    window.addEventListener('bbg-snapshot-updated', onUpd);
    return () => window.removeEventListener('bbg-snapshot-updated', onUpd);
  }, [loadComps, investment.ticker]);

  const header = (right) => (
    <div style={{ padding: '12px 20px', borderBottom: collapsed ? 'none' : `1px solid ${tokens.inkLineSoft}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '12px' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
        <Chevron collapsed={collapsed} onClick={toggleCollapsed} />
        <span style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.18em', color: tokens.inkMute }}>BLOOMBERG · PEER COMPS</span>
      </div>
      {right && <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute }}>{right}</span>}
    </div>
  );
  const shell = (body, right) => (
    <div style={{ border: `1px solid ${tokens.inkLine}`, background: tokens.paper, marginTop: '16px' }}>
      {header(right)}{!collapsed && body}
    </div>
  );

  if (comps === undefined) return null; // stay quiet until the snapshot resolves
  if (!comps || comps.configured === false) {
    // Distinguish "no peers configured" from "peers saved but the snapshot has
    // no comps yet" (pull not run since saving, or the machine running the pull
    // has an outdated bloomberg_push.py without comps support).
    const peersSaved = (investment.peers || []).length;
    return shell(
      <div style={{ padding: '20px' }}>
        {peersSaved ? (
          <React.Fragment>
            <div style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.6, marginBottom: '10px' }}>
              {peersSaved} peer{peersSaved === 1 ? '' : 's'} saved for {investment.ticker} — the last Bloomberg snapshot doesn't include comps yet.
            </div>
            <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, lineHeight: 1.8 }}>
              Click <span style={{ color: tokens.ink }}>⟳ RUN PULL</span> on the Bloomberg panel above.<br />
              Still seeing this after a successful pull? The machine running the pull has an older script — run <span style={{ color: tokens.ink }}>git pull origin main</span> there first (comps were added to <span style={{ color: tokens.ink }}>scripts/bloomberg_push.py</span> recently).
            </div>
          </React.Fragment>
        ) : (
          <React.Fragment>
            <div style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.6, marginBottom: '10px' }}>
              No peer set configured for {investment.ticker}.
            </div>
            <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, lineHeight: 1.8 }}>
              Use <span style={{ color: tokens.ink }}>⚙ EDIT</span> above to add ~5 Bloomberg peer tickers
              (e.g. <span style={{ color: tokens.ink }}>GPN US Equity, FOUR US Equity, FIS US Equity</span>), then ⟳ RUN PULL on the Bloomberg panel.
            </div>
          </React.Fragment>
        )}
      </div>
    );
  }

  const cols = comps.columns || [];
  const rows = comps.rows || [];
  const peers = rows.filter((r) => !r.is_self);

  const colHead = (
    <>
      <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.1em', color: tokens.inkMute, padding: '6px 10px' }}>COMPANY</div>
      {cols.map((c) => (
        <div key={c.label} style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.06em', color: tokens.inkMute, padding: '6px 10px', textAlign: 'right', fontStyle: c.kind === 'estimate' ? 'italic' : 'normal' }}>{c.label}</div>
      ))}
    </>
  );

  // One metric block: a small matrix of companies × periods, self row highlighted,
  // plus a peer-median row to read the holding against the set at a glance.
  const MetricTable = ({ title, field, fmt, tone }) => {
    const gridCols = `1.5fr repeat(${cols.length}, 1fr)`;
    const cell = (v, self) => (
      <div style={{ fontFamily: fontMono, fontSize: '11px', padding: '7px 10px', textAlign: 'right',
        color: v == null ? tokens.inkMute : (tone ? (v >= 0 ? tokens.green : tokens.red) : tokens.ink),
        fontWeight: self ? 600 : 400 }}>{fmt(v)}</div>
    );
    return (
      <div style={{ marginBottom: '18px' }}>
        <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '6px' }}>{title}</div>
        <div style={{ minWidth: isMobile ? '520px' : 'auto' }}>
          <div style={{ display: 'grid', gridTemplateColumns: gridCols, borderBottom: `1px solid ${tokens.inkLineSoft}` }}>{colHead}</div>
          {rows.map((r) => (
            <div key={r.bb} style={{ display: 'grid', gridTemplateColumns: gridCols, alignItems: 'center',
              background: r.is_self ? tokens.bgAlt : 'transparent',
              borderLeft: `3px solid ${r.is_self ? investment.color : 'transparent'}`,
              borderBottom: `1px solid ${tokens.inkLineSoft}` }}>
              <div style={{ fontSize: '12px', padding: '7px 10px', letterSpacing: '-0.01em', fontWeight: r.is_self ? 600 : 400, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.name}</div>
              {cols.map((c) => cell((r[field] || {})[c.label], r.is_self))}
            </div>
          ))}
          {peers.length > 0 && (
            <div style={{ display: 'grid', gridTemplateColumns: gridCols, alignItems: 'center', borderTop: `1px solid ${tokens.inkLine}` }}>
              <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.08em', color: tokens.inkMute, padding: '7px 10px' }}>PEER MEDIAN</div>
              {cols.map((c) => cell(_median(peers.map((r) => (r[field] || {})[c.label])), false))}
            </div>
          )}
        </div>
      </div>
    );
  };

  const pct  = (v) => v == null ? '—' : (v >= 0 ? '+' : '') + v.toFixed(0) + '%';
  const mgn  = (v) => v == null ? '—' : v.toFixed(0) + '%';

  // Forward-multiple matrix (estimate columns only).
  const estCols = cols.filter((c) => c.kind === 'estimate');
  const MultTable = ({ title, key2 }) => {
    const gridCols = `1.5fr repeat(${estCols.length}, 1fr)`;
    return (
      <div style={{ marginBottom: '18px' }}>
        <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '6px' }}>{title}</div>
        <div style={{ minWidth: isMobile ? '440px' : 'auto' }}>
          <div style={{ display: 'grid', gridTemplateColumns: gridCols, borderBottom: `1px solid ${tokens.inkLineSoft}` }}>
            <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.1em', color: tokens.inkMute, padding: '6px 10px' }}>COMPANY</div>
            {estCols.map((c) => <div key={c.label} style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, padding: '6px 10px', textAlign: 'right', fontStyle: 'italic' }}>{c.label}</div>)}
          </div>
          {rows.map((r) => (
            <div key={r.bb} style={{ display: 'grid', gridTemplateColumns: gridCols, alignItems: 'center', background: r.is_self ? tokens.bgAlt : 'transparent', borderLeft: `3px solid ${r.is_self ? investment.color : 'transparent'}`, borderBottom: `1px solid ${tokens.inkLineSoft}` }}>
              <div style={{ fontSize: '12px', padding: '7px 10px', fontWeight: r.is_self ? 600 : 400, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.name}</div>
              {estCols.map((c) => {
                const v = ((r.fwd_multiples || {})[c.label] || {})[key2];
                return <div key={c.label} style={{ fontFamily: fontMono, fontSize: '11px', padding: '7px 10px', textAlign: 'right', color: v == null ? tokens.inkMute : tokens.ink, fontWeight: r.is_self ? 600 : 400 }}>{v == null ? '—' : v.toFixed(1) + 'x'}</div>;
              })}
            </div>
          ))}
        </div>
      </div>
    );
  };

  return shell(
    <div style={{ padding: '18px 20px', overflowX: isMobile ? 'auto' : 'visible' }}>
      <MetricTable title="REVENUE GROWTH (YoY)"  field="rev_growth"    fmt={pct} tone />
      <MetricTable title="EBITDA GROWTH (YoY)"   field="ebitda_growth" fmt={pct} tone />
      <MetricTable title="FCF GROWTH (YoY)"      field="fcf_growth"    fmt={pct} tone />
      <MetricTable title="EBITDA MARGIN"         field="ebitda_margin" fmt={mgn} />
      <MultTable title="EV / EBITDA (FWD)" key2="ev_ebitda" />
      <MultTable title="P / E (FWD)"       key2="pe" />
      <div style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, letterSpacing: '0.04em', lineHeight: 1.6, marginTop: '4px' }}>
        SELF = {investment.ticker} (highlighted). FCF consensus is unavailable, so forward FCF growth is blank. Source: Bloomberg · {updated || '—'}.
      </div>
    </div>,
    `${peers.length} PEERS · ${updated || '—'}`
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// People panel — management & board roster + psychographic profiles. Reads
// people_snapshots; the RESEARCH button runs the `people-intel` Edge Function
// (Claude + web search). Ownership/comp (item 3) + network (item 4) layer in here.
// ─────────────────────────────────────────────────────────────────────────────
const _confBand = (c) => c == null ? ['—', tokens.inkMute] : c >= 0.7 ? ['High', tokens.green] : c >= 0.4 ? ['Moderate', tokens.ochre || '#C9A24A'] : ['Low', tokens.red];

const ProfileCard = ({ p, accent }) => {
  const [open, setOpen] = React.useState(false);
  const conf = typeof p.confidence === 'number' ? p.confidence : null;
  const [band, bandColor] = _confBand(conf);
  const chips = (label, arr) => (arr && arr.length) ? (
    <div style={{ marginTop: '8px' }}>
      <span style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.12em', color: tokens.inkMute }}>{label}</span>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px', marginTop: '5px' }}>
        {arr.map((m, i) => <span key={i} style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.04em', color: tokens.ink, border: `1px solid ${tokens.inkLine}`, borderRadius: '999px', padding: '3px 9px' }}>{m}</span>)}
      </div>
    </div>
  ) : null;
  const line = (label, v) => v ? (
    <div style={{ marginTop: '8px' }}>
      <span style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.12em', color: tokens.inkMute }}>{label}</span>
      <div style={{ fontSize: '13px', color: tokens.ink, marginTop: '2px', lineHeight: 1.45 }}>{v}</div>
    </div>
  ) : null;

  return (
    <div style={{ border: `1px solid ${tokens.inkLine}`, borderLeft: `3px solid ${accent}`, background: tokens.bg, padding: '16px 18px', borderRadius: '2px' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: '12px', flexWrap: 'wrap' }}>
        <div>
          <span style={{ fontSize: '16px', letterSpacing: '-0.02em', fontWeight: 600 }}>{p.name}</span>
          <span style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.08em', color: tokens.inkMute, marginLeft: '10px', textTransform: 'uppercase' }}>{p.role}</span>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
          <span style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.1em', color: bandColor }}>CONF {band}{conf != null ? ` ${Math.round(conf * 100)}%` : ''}</span>
          <div style={{ width: '54px', height: '5px', background: tokens.inkLineSoft, borderRadius: '3px', overflow: 'hidden' }}>
            <div style={{ width: `${Math.round((conf || 0) * 100)}%`, height: '100%', background: bandColor }} />
          </div>
        </div>
      </div>
      {p.summary && <div style={{ fontSize: '13.5px', lineHeight: 1.55, color: tokens.inkSoft, marginTop: '10px' }}>{p.summary}</div>}
      {chips('MOTIVATIONS', p.motivations)}
      {line('DECISION STYLE', p.decision_style)}
      {line('RISK POSTURE', p.risk_posture)}
      {line('COMMUNICATION', p.communication_style)}
      {p.activist_read && (
        <div style={{ marginTop: '12px', padding: '10px 12px', background: tokens.bgAlt, borderLeft: `2px solid ${accent}` }}>
          <span style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.12em', color: tokens.inkMute }}>HOW TO ENGAGE</span>
          <div style={{ fontSize: '13px', color: tokens.ink, marginTop: '3px', lineHeight: 1.5 }}>{p.activist_read}</div>
        </div>
      )}
      {p.sources && p.sources.length > 0 && (
        <div style={{ marginTop: '10px' }}>
          <button onClick={() => setOpen(o => !o)} style={{ background: 'none', border: 'none', cursor: 'pointer', fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.08em', color: tokens.inkMute, padding: 0 }}>
            {open ? '▾' : '▸'} {p.sources.length} SOURCE{p.sources.length === 1 ? '' : 'S'}
          </button>
          {open && (
            <div style={{ marginTop: '8px', display: 'flex', flexDirection: 'column', gap: '5px' }}>
              {p.sources.map((s, i) => (
                <a key={i} href={s.url} target="_blank" rel="noopener noreferrer" style={{ fontSize: '12px', color: tokens.ink, textDecoration: 'none', display: 'flex', gap: '8px', alignItems: 'baseline' }}>
                  <span style={{ fontFamily: fontMono, fontSize: '8px', letterSpacing: '0.1em', color: tokens.inkMute, textTransform: 'uppercase', minWidth: '54px' }}>{s.kind || 'link'}</span>
                  <span style={{ borderBottom: `1px solid ${tokens.inkLine}`, lineHeight: 1.4 }}>{s.title || s.url} ↗</span>
                </a>
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  );
};

const PeoplePanel = ({ investment }) => {
  const isMobile = useIsMobile();
  const ticker = investment.ticker;
  const accent = investment.color || tokens.ink;
  const [collapsed, toggleCollapsed] = useCollapsed('people');
  const [data, setData] = React.useState(undefined); // undefined=loading cache
  const [run, setRun] = React.useState('idle');       // idle|running|error
  const [err, setErr] = React.useState('');

  React.useEffect(() => {
    const client = window._supabaseClient;
    if (!client) { setData(null); return; }
    const load = () => client.from('people_snapshots').select('data').eq('ticker', ticker).maybeSingle()
      .then(({ data: row }) => setData((row && row.data) || null));
    load();
    const onUpd = (e) => { if (!e.detail || e.detail === ticker) load(); };
    window.addEventListener('people-snapshot-updated', onUpd);
    return () => window.removeEventListener('people-snapshot-updated', onUpd);
  }, [ticker]);

  const research = React.useCallback(async () => {
    const client = window._supabaseClient;
    if (!client) return;
    setRun('running'); setErr('');
    try {
      const payload = await invokePeopleIntel(ticker, investment.name || ticker, 'people');
      setData(payload); setRun('idle');
    } catch (e) { setErr(e.message || 'Research failed'); setRun('error'); }
  }, [ticker, investment.name]);

  const header = (
    <div style={{ padding: '12px 20px', borderBottom: collapsed ? 'none' : `1px solid ${tokens.inkLineSoft}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '12px' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
        <Chevron collapsed={collapsed} onClick={toggleCollapsed} />
        <span style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.18em', color: tokens.inkMute }}>MANAGEMENT &amp; BOARD INTEL</span>
      </div>
      <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
        {data && data.fetched_at && <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute }}>{new Date(data.fetched_at).toLocaleDateString()}</span>}
        <button onClick={research} disabled={run === 'running'} title="Run Claude web research (uses tokens)"
          style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.1em', padding: '6px 12px', background: run === 'running' ? 'none' : tokens.ink, color: run === 'running' ? tokens.inkMute : tokens.paper, border: `1px solid ${run === 'running' ? tokens.inkLine : tokens.ink}`, cursor: run === 'running' ? 'default' : 'pointer', whiteSpace: 'nowrap' }}>
          {run === 'running' ? 'RESEARCHING…' : '⟳ RESEARCH'}
        </button>
      </div>
    </div>
  );
  const shell = (body) => (
    <div style={{ border: `1px solid ${tokens.inkLine}`, background: tokens.paper, marginTop: '16px' }}>{header}{!collapsed && body}</div>
  );

  if (data === undefined) return null;
  if (run === 'running' && (!data || !data.profiles)) return shell(
    <div style={{ padding: '20px', fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, letterSpacing: '0.06em', lineHeight: 1.7 }}>
      Researching management & board from public filings and sources — this can take a minute or two.
    </div>
  );
  if (!data || (!(data.profiles || []).length && !(data.roster || []).length)) return shell(
    <div style={{ padding: '20px' }}>
      <div style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.6, marginBottom: '8px' }}>No management/board intel for {ticker} yet.</div>
      {err && <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.red, lineHeight: 1.6, marginBottom: '8px' }}>{err}</div>}
      <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, lineHeight: 1.8 }}>
        Click <span style={{ color: tokens.ink }}>⟳ RESEARCH</span> to build the roster + psychographic profiles (needs the <span style={{ color: tokens.ink }}>people-intel</span> function deployed).
      </div>
    </div>
  );

  const profiles = data.profiles || [];
  const roster = data.roster || [];
  const mgmt = roster.filter((r) => r.type === 'management');
  const board = roster.filter((r) => r.type === 'board');

  const rosterCol = (title, list) => (
    <div>
      <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.inkMute, marginBottom: '10px' }}>{title} · {list.length}</div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
        {list.map((m, i) => (
          <div key={i} style={{ borderTop: `1px solid ${tokens.inkLineSoft}`, paddingTop: '8px' }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', gap: '8px', alignItems: 'baseline' }}>
              <span style={{ fontSize: '13.5px', letterSpacing: '-0.01em', fontWeight: 500 }}>{m.name}</span>
              {(m.is_chair || m.is_lead_independent) && (
                <span style={{ fontFamily: fontMono, fontSize: '8px', letterSpacing: '0.1em', color: accent, border: `1px solid ${accent}`, borderRadius: '999px', padding: '2px 7px', whiteSpace: 'nowrap' }}>
                  {m.is_lead_independent ? 'LEAD IND' : 'CHAIR'}
                </span>
              )}
            </div>
            <div style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.inkMute, letterSpacing: '0.04em', margin: '2px 0 4px' }}>{(m.role || '').toUpperCase()}</div>
            {m.bio && <div style={{ fontSize: '12.5px', color: tokens.inkSoft, lineHeight: 1.5 }}>{m.bio} {m.source && <a href={m.source} target="_blank" rel="noopener noreferrer" style={{ color: tokens.inkMute, fontFamily: fontMono, fontSize: '9px' }}>↗</a>}</div>}
          </div>
        ))}
      </div>
    </div>
  );

  return shell(
    <div style={{ padding: '18px 20px' }}>
      {err && <div style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.red, marginBottom: '12px' }}>{err}</div>}
      {profiles.length > 0 && (
        <div style={{ marginBottom: '24px' }}>
          <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '10px' }}>PSYCHOGRAPHIC PROFILES</div>
          <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: '12px' }}>
            {profiles.map((p, i) => <ProfileCard key={i} p={p} accent={accent} />)}
          </div>
          <div style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, letterSpacing: '0.04em', lineHeight: 1.6, marginTop: '10px' }}>
            Synthesized from public sources by Claude · confidence is evidence-calibrated · verify before acting.
          </div>
        </div>
      )}
      {roster.length > 0 && (
        <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: '28px', borderTop: `1px solid ${tokens.inkLine}`, paddingTop: '18px' }}>
          {rosterCol('MANAGEMENT', mgmt)}
          {rosterCol('BOARD', board)}
        </div>
      )}
    </div>
  );
};

// Shared invoker for the people-intel Edge Function (modes: people | governance | holders).
// The function does its research in the BACKGROUND (it would otherwise exceed the
// Edge wall-clock) and answers { started: true } immediately; we then poll
// people_snapshots until this mode's timestamp advances — or its error appears.
const invokePeopleIntel = async (ticker, company, mode) => {
  if (!roleCanResearch()) throw new Error(DEMO_BLOCK_MSG);
  const client = window._supabaseClient;
  const since = new Date().toISOString();
  const { data, error } = await client.functions.invoke('people-intel', { body: { ticker, company, mode } });
  let payload = data;
  if (error) { try { payload = await error.context.json(); } catch {} if (!payload || !payload.error) throw new Error(error.message || 'Edge function error'); }
  if (payload && payload.error) throw new Error(payload.error);
  if (!payload || !payload.started) {
    // Older deploy: result returned inline — still notify sibling panels.
    window.dispatchEvent(new CustomEvent('people-snapshot-updated', { detail: ticker }));
    return payload;
  }

  const stampKey = mode === 'people' ? 'fetched_at' : `${mode}_fetched_at`;
  const errKey = `${mode}_error`, errAtKey = `${mode}_error_at`;
  const deadline = Date.now() + 6 * 60 * 1000;
  while (Date.now() < deadline) {
    await new Promise((r) => setTimeout(r, 6000));
    const { data: row } = await client.from('people_snapshots').select('data').eq('ticker', ticker).maybeSingle();
    const d = row && row.data;
    if (!d) continue;
    if (d[errKey] && d[errAtKey] && d[errAtKey] >= since) throw new Error(d[errKey]);
    if (d[stampKey] && d[stampKey] >= since) {
      // Tell every panel reading people_snapshots (network, people, holders) to re-read.
      window.dispatchEvent(new CustomEvent('people-snapshot-updated', { detail: ticker }));
      return d;
    }
  }
  throw new Error('Research timed out (6 min) — it may still complete in the background; reload in a minute.');
};

// Panel chrome shared by the intel panels below.
const IntelShell = ({ title, right, onAction, actionLabel, running, collapseKey, children }) => {
  const [collapsed, toggle] = useCollapsed(collapseKey || title);
  return (
    <div style={{ border: `1px solid ${tokens.inkLine}`, background: tokens.paper, marginTop: '16px' }}>
      <div style={{ padding: '12px 20px', borderBottom: collapsed ? 'none' : `1px solid ${tokens.inkLineSoft}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '12px' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: '4px', minWidth: 0 }}>
          <Chevron collapsed={collapsed} onClick={toggle} />
          <span style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.18em', color: tokens.inkMute, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{title}</span>
        </div>
        <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
          {right && <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute }}>{right}</span>}
          {onAction && (
            <button onClick={onAction} disabled={running}
              style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.1em', padding: '6px 12px', background: running ? 'none' : tokens.ink, color: running ? tokens.inkMute : tokens.paper, border: `1px solid ${running ? tokens.inkLine : tokens.ink}`, cursor: running ? 'default' : 'pointer', whiteSpace: 'nowrap' }}>
              {running ? 'RUNNING…' : actionLabel}
            </button>
          )}
        </div>
      </div>
      {!collapsed && children}
    </div>
  );
};

// Money formatter for the governance tables ($1.2M / $840K).
const _fmtUsd = (v) => {
  if (v == null) return '—';
  const a = Math.abs(v);
  if (a >= 1e9) return `$${(v / 1e9).toFixed(1)}B`;
  if (a >= 1e6) return `$${(v / 1e6).toFixed(1)}M`;
  if (a >= 1e3) return `$${Math.round(v / 1e3)}K`;
  return `$${Math.round(v)}`;
};

// ─────────────────────────────────────────────────────────────────────────────
// Value-Creation Gap panel — quantifies the activist upside mechanically from
// the comps data: close the EBITDA-margin gap to peer median, re-rate to the
// peer-median forward multiple, or both. Pure client-side math; illustrative.
// ─────────────────────────────────────────────────────────────────────────────
const ValueGapPanel = ({ investment }) => {
  const isMobile = useIsMobile();
  const [snap, setSnap] = React.useState(undefined);

  React.useEffect(() => {
    const client = window._supabaseClient;
    if (!client) { setSnap(null); return; }
    const load = () => client.from('bloomberg_snapshots').select('data').eq('ticker', investment.ticker).maybeSingle()
      .then(({ data }) => setSnap((data && data.data) || null));
    load();
    const onUpd = (e) => { if (!e.detail || e.detail === investment.ticker) load(); };
    window.addEventListener('bbg-snapshot-updated', onUpd);
    return () => window.removeEventListener('bbg-snapshot-updated', onUpd);
  }, [investment.ticker]);

  if (snap === undefined || !snap) return null;
  const comps = snap.comps;
  if (!comps || comps.configured === false || !(comps.rows || []).length) return null;

  const estCols = (comps.columns || []).filter((c) => c.kind === 'estimate');
  if (!estCols.length) return null;
  const lab = estCols[0].label; // first forward year, e.g. FY26E

  const self = comps.rows.find((r) => r.is_self) || {};
  const peers = comps.rows.filter((r) => !r.is_self);
  const estPeriod = (snap.periods || []).find((p) => p.kind === 'estimate' && p.label === lab) || (snap.periods || []).find((p) => p.kind === 'estimate') || {};
  const ev = (snap.ev_buildup || {}).ev;
  const rev = estPeriod.revenue, ebitda = estPeriod.ebitda;
  if (ev == null || rev == null || ebitda == null) return null;

  const selfMult = ev / ebitda;
  const peerMult = _median(peers.map((r) => ((r.fwd_multiples || {})[lab] || {}).ev_ebitda));
  const selfMargin = ebitda / rev * 100;
  const peerMargin = _median(peers.map((r) => (r.ebitda_margin || {})[lab]));
  const selfGrowth = (self.rev_growth || {})[lab];
  const peerGrowth = _median(peers.map((r) => (r.rev_growth || {})[lab]));

  const uplift = peerMargin != null ? Math.max(0, (peerMargin - selfMargin) / 100 * rev) : 0;
  const ccy = snap.reporting_currency || snap.currency || 'USD';
  const sym = ({ USD: '$', EUR: '€', GBP: '£' })[ccy] || (ccy + ' ');
  const fmtM = (v) => v == null ? '—' : (Math.abs(v) >= 1000 ? `${sym}${(v / 1000).toFixed(1)}B` : `${sym}${Math.round(v)}M`);

  const levers = [
    { name: `Close margin gap to peer median (${peerMargin != null ? peerMargin.toFixed(0) : '—'}% vs ${selfMargin.toFixed(0)}%)`, value: uplift * selfMult,
      note: `+${fmtM(uplift)} EBITDA × ${selfMult.toFixed(1)}x current multiple` },
    { name: `Re-rate to peer-median multiple (${peerMult != null ? peerMult.toFixed(1) : '—'}x vs ${selfMult.toFixed(1)}x)`, value: peerMult != null ? ebitda * (peerMult - selfMult) : null,
      note: `${fmtM(ebitda)} EBITDA × ${(peerMult != null ? (peerMult - selfMult) : 0).toFixed(1)}x re-rating` },
    { name: 'Both (peer margin × peer multiple)', value: peerMult != null && peerMargin != null ? (ebitda + uplift) * peerMult - ev : null,
      note: `${fmtM(ebitda + uplift)} EBITDA × ${peerMult != null ? peerMult.toFixed(1) : '—'}x − current EV` },
  ];
  const maxAbs = Math.max(1, ...levers.map((l) => Math.abs(l.value || 0)));

  return (
    <IntelShell collapseKey="valuegap" title="VALUE-CREATION GAP · ILLUSTRATIVE" right={`${lab} BASIS · EV ${fmtM(ev)}`}>
      <div style={{ padding: '18px 20px' }}>
        {levers.map((l, i) => {
          const pct = l.value != null ? l.value / ev * 100 : null;
          const pos = (l.value || 0) >= 0;
          return (
            <div key={i} style={{ marginBottom: '14px' }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', gap: '12px', alignItems: 'baseline', flexWrap: 'wrap' }}>
                <span style={{ fontSize: '13px', letterSpacing: '-0.005em' }}>{l.name}</span>
                <span style={{ fontFamily: fontMono, fontSize: '12px', color: pos ? tokens.green : tokens.red, whiteSpace: 'nowrap' }}>
                  {l.value == null ? '—' : `${pos ? '+' : ''}${fmtM(l.value)} (${pos ? '+' : ''}${pct.toFixed(0)}% of EV)`}
                </span>
              </div>
              <div style={{ height: '7px', background: tokens.inkLineSoft, borderRadius: '4px', overflow: 'hidden', marginTop: '5px' }}>
                <div style={{ width: `${Math.min(100, Math.abs(l.value || 0) / maxAbs * 100)}%`, height: '100%', background: pos ? tokens.green : tokens.red, opacity: 0.75 }} />
              </div>
              <div style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, marginTop: '3px', letterSpacing: '0.04em' }}>{l.note.toUpperCase()}</div>
            </div>
          );
        })}
        <div style={{ borderTop: `1px solid ${tokens.inkLineSoft}`, paddingTop: '10px', display: 'flex', gap: isMobile ? '16px' : '32px', flexWrap: 'wrap', fontFamily: fontMono, fontSize: '10px', color: tokens.inkSoft, letterSpacing: '0.04em' }}>
          <span>REV GROWTH {lab}: SELF {selfGrowth != null ? `${selfGrowth >= 0 ? '+' : ''}${selfGrowth.toFixed(0)}%` : '—'} · PEER MED {peerGrowth != null ? `${peerGrowth >= 0 ? '+' : ''}${peerGrowth.toFixed(0)}%` : '—'}</span>
          <span style={{ color: tokens.inkMute }}>MECHANICAL PEER-CONVERGENCE MATH — NOT A PRICE TARGET</span>
        </div>
      </div>
    </IntelShell>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// Ownership & Compensation panel (item 3) — standardized across companies.
// Reads people_snapshots.{ownership,comp}; research = people-intel mode 'governance'.
// ─────────────────────────────────────────────────────────────────────────────
const OwnershipCompPanel = ({ investment }) => {
  const isMobile = useIsMobile();
  const ticker = investment.ticker;
  const [data, setData] = React.useState(undefined);
  const [run, setRun] = React.useState(false);
  const [err, setErr] = React.useState('');

  const load = React.useCallback(() => {
    const client = window._supabaseClient;
    if (!client) { setData(null); return; }
    client.from('people_snapshots').select('data').eq('ticker', ticker).maybeSingle()
      .then(({ data: row }) => setData((row && row.data) || null));
  }, [ticker]);
  React.useEffect(() => { load(); }, [load]);

  const research = async () => {
    setRun(true); setErr('');
    try { setData(await invokePeopleIntel(ticker, investment.name || ticker, 'governance')); }
    catch (e) { setErr(e.message); }
    finally { setRun(false); }
  };

  if (data === undefined) return null;
  const ownership = (data && data.ownership) || [];
  const comp = (data && data.comp) || [];
  const sayOnPay = comp.length ? comp.find((c) => c.say_on_pay_support_pct != null) : null;

  const chips = (arr, fmt) => (arr || []).map((m, i) => (
    <span key={i} style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.02em', color: tokens.ink, border: `1px solid ${tokens.inkLine}`, borderRadius: '999px', padding: '2px 7px', whiteSpace: 'nowrap' }}>{fmt(m)}</span>
  ));

  const body = (!ownership.length && !comp.length) ? (
    <div style={{ padding: '20px' }}>
      <div style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.6, marginBottom: '8px' }}>No ownership/comp data for {ticker} yet.</div>
      {err && <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.red, marginBottom: '8px' }}>{err}</div>}
      <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, lineHeight: 1.8 }}>
        Click <span style={{ color: tokens.ink }}>⟳ RESEARCH PROXY</span> — Claude reads the latest DEF 14A and standardizes ownership + STIP/LTIP structure.
      </div>
    </div>
  ) : (
    <div style={{ padding: '18px 20px' }}>
      {err && <div style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.red, marginBottom: '12px' }}>{err}</div>}

      {ownership.length > 0 && (
        <div style={{ marginBottom: '24px' }}>
          <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '8px' }}>INSIDER OWNERSHIP</div>
          {[...ownership].sort((a, b) => (b.pct || 0) - (a.pct || 0)).map((o, i) => (
            <div key={i} style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr 70px' : '220px 1fr 90px 90px', gap: '10px', alignItems: 'center', padding: '7px 0', borderBottom: `1px solid ${tokens.inkLineSoft}` }}>
              <div>
                <span style={{ fontSize: '13px', fontWeight: 500 }}>{o.name}</span>
                <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, marginLeft: '8px', textTransform: 'uppercase' }}>{o.role}</span>
              </div>
              {!isMobile && (
                <div style={{ height: '6px', background: tokens.inkLineSoft, borderRadius: '3px', overflow: 'hidden' }}>
                  <div style={{ width: `${Math.min(100, (o.pct || 0) / Math.max(0.01, ownership[0].pct || 1) * 100)}%`, height: '100%', background: investment.color || tokens.ink, opacity: 0.8 }} />
                </div>
              )}
              <div style={{ fontFamily: fontMono, fontSize: '11px', textAlign: 'right' }}>{o.pct != null ? `${o.pct}%` : '—'}</div>
              {!isMobile && <div style={{ fontFamily: fontMono, fontSize: '11px', textAlign: 'right', color: tokens.inkSoft }}>{_fmtUsd(o.value_usd)}</div>}
            </div>
          ))}
        </div>
      )}

      {comp.length > 0 && (
        <div>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '8px', flexWrap: 'wrap', gap: '8px' }}>
            <span style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink }}>COMPENSATION STRUCTURE · {comp[0].fiscal_year || ''}</span>
            {sayOnPay && <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute }}>SAY-ON-PAY {sayOnPay.say_on_pay_support_pct}% SUPPORT</span>}
          </div>
          <div style={{ overflowX: 'auto' }}>
            <div style={{ minWidth: '760px' }}>
              <div style={{ display: 'grid', gridTemplateColumns: '180px 80px 90px 1.3fr 90px 1fr 1.3fr 80px', gap: '8px', borderBottom: `1px solid ${tokens.inkLine}`, padding: '6px 0' }}>
                {['NEO', 'SALARY', 'STIP TGT', 'STIP METRICS', 'LTIP TGT', 'LTIP MIX', 'LTIP METRICS', 'TOTAL'].map((h) => (
                  <span key={h} style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.08em', color: tokens.inkMute }}>{h}</span>
                ))}
              </div>
              {comp.map((c, i) => (
                <div key={i} style={{ display: 'grid', gridTemplateColumns: '180px 80px 90px 1.3fr 90px 1fr 1.3fr 80px', gap: '8px', padding: '9px 0', borderBottom: `1px solid ${tokens.inkLineSoft}`, alignItems: 'start' }}>
                  <div>
                    <div style={{ fontSize: '12.5px', fontWeight: 500, lineHeight: 1.3 }}>{c.name}</div>
                    <div style={{ fontFamily: fontMono, fontSize: '8px', color: tokens.inkMute, textTransform: 'uppercase' }}>{c.role}</div>
                  </div>
                  <span style={{ fontFamily: fontMono, fontSize: '11px' }}>{_fmtUsd(c.salary)}</span>
                  <span style={{ fontFamily: fontMono, fontSize: '11px' }}>{c.stip_target_pct_of_salary != null ? `${c.stip_target_pct_of_salary}%${c.stip_max_pct ? ` / ${c.stip_max_pct}%` : ''}` : '—'}</span>
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px' }}>{chips(c.stip_metrics, (m) => `${m.metric} ${m.weight_pct != null ? m.weight_pct + '%' : ''}`)}</div>
                  <span style={{ fontFamily: fontMono, fontSize: '11px' }}>{_fmtUsd(c.ltip_target_value)}</span>
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px' }}>{chips(c.ltip_mix, (m) => `${m.vehicle} ${m.weight_pct != null ? m.weight_pct + '%' : ''}`)}</div>
                  <div style={{ display: 'flex', flexWrap: 'wrap', gap: '4px' }}>{chips(c.ltip_metrics, (m) => `${m.metric} ${m.weight_pct != null ? m.weight_pct + '%' : ''}${m.period ? ' · ' + m.period : ''}`)}</div>
                  <span style={{ fontFamily: fontMono, fontSize: '11px', fontWeight: 600 }}>{_fmtUsd(c.total_comp)}</span>
                </div>
              ))}
            </div>
          </div>
          <div style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, letterSpacing: '0.04em', marginTop: '8px', lineHeight: 1.6 }}>
            STIP TGT = target bonus as % of salary (/ max where disclosed). Parsed from the DEF 14A by Claude — verify against the filing before relying on it.
          </div>
        </div>
      )}
    </div>
  );

  return (
    <IntelShell collapseKey="ownership" title="OWNERSHIP & COMPENSATION" right={data && data.governance_as_of ? `PROXY ${data.governance_as_of}` : ''}
      onAction={research} actionLabel="⟳ RESEARCH PROXY" running={run}>
      {body}
    </IntelShell>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// Network map panel (item 4) — interactive vis-network graph of board/management
// overlaps (co-boards, shared employers/schools) from people_snapshots.network,
// with your own relationship tags layered on top (person_tags). Pan/zoom/drag/
// click are native; physics layout spreads labels so they don't collide.
// ─────────────────────────────────────────────────────────────────────────────
const EDGE_COLORS = { board_seat: '#9A938A', employer: '#7A9BB8', education: '#A88BC2', co_board: '#C9622E', shared_employer: '#7A9BB8', shared_education: '#A88BC2' };

// vis-network renderer. Builds DataSets from the graph, fits to view, and emits
// node selection up to the panel. Tag highlights (gold ring) update in place.
const VisNetwork = ({ net, accent, ticker, tags, onSelect }) => {
  const ref = React.useRef(null);
  const netRef = React.useRef(null);
  const nodesRef = React.useRef(null);
  const [failed, setFailed] = React.useState(false);

  React.useEffect(() => {
    let cancelled = false;
    setFailed(false);
    ensureVis(() => {
      if (cancelled || !ref.current || !window.vis) return;
      const personIds = new Set((net.nodes || []).filter((n) => n.type === 'person').map((n) => n.id));
      const nodeArr = [{ id: '__HUB__', label: ticker, shape: 'dot', size: 26, color: { background: accent, border: accent }, font: { color: '#FBFAF6', size: 15, face: 'JetBrains Mono' }, fixed: { x: false, y: false } }];
      const seen = new Set(['__HUB__']);
      (net.nodes || []).forEach((n) => {
        if (seen.has(n.id)) return; seen.add(n.id);
        const person = n.type === 'person';
        const tagged = !!tags[n.id];
        nodeArr.push({
          id: n.id, label: n.label || n.id,
          shape: person ? 'dot' : 'diamond',
          size: person ? 14 : 9,
          color: { background: '#FBFAF6', border: tagged ? '#C9A24A' : (person ? tokens.ink : '#9A938A') },
          borderWidth: tagged ? 3 : 1.5,
          font: { size: person ? 14 : 11, color: person ? tokens.ink : '#7A736A', face: 'Inter' },
          title: (n.label || n.id) + (n.role ? ' — ' + n.role : ''),
        });
      });
      const edgeArr = [];
      personIds.forEach((id) => edgeArr.push({ from: '__HUB__', to: id, color: { color: '#D8D2C6', opacity: 0.6 }, width: 1 }));
      (net.edges || []).forEach((e) => {
        const pp = personIds.has(e.source) && personIds.has(e.target);
        edgeArr.push({ from: e.source, to: e.target, color: { color: EDGE_COLORS[e.kind] || '#B9B2A6' }, dashes: pp, width: pp ? 2.5 : 1, title: `${e.source} ↔ ${e.target}${e.detail ? ' — ' + e.detail : ''} (${e.kind})` });
      });
      try {
        nodesRef.current = new window.vis.DataSet(nodeArr);
        const edges = new window.vis.DataSet(edgeArr);
        netRef.current = new window.vis.Network(ref.current, { nodes: nodesRef.current, edges }, {
          physics: { solver: 'barnesHut', barnesHut: { gravitationalConstant: -9000, centralGravity: 0.25, springLength: 130, springConstant: 0.035, avoidOverlap: 0.5 }, stabilization: { iterations: 220 } },
          interaction: { hover: true, tooltipDelay: 120, dragNodes: true, zoomView: true, dragView: true },
          nodes: { borderWidth: 1.5, shadow: false },
          edges: { smooth: { enabled: true, type: 'continuous' } },
        });
        netRef.current.on('selectNode', (p) => { if (p.nodes && p.nodes.length) onSelect(p.nodes[0] === '__HUB__' ? null : p.nodes[0]); });
        netRef.current.on('deselectNode', () => onSelect(null));
        // Freeze the layout once it has settled — otherwise the physics sim keeps
        // nudging nodes and the whole graph slowly drifts/rotates forever.
        // Nodes remain individually draggable with physics off.
        netRef.current.once('stabilizationIterationsDone', () => {
          if (!netRef.current) return;
          netRef.current.setOptions({ physics: { enabled: false } });
          netRef.current.fit({ animation: { duration: 300 } });
        });
      } catch (e) { setFailed(true); }
    }, () => { if (!cancelled) setFailed(true); });
    return () => { cancelled = true; if (netRef.current) { netRef.current.destroy(); netRef.current = null; } };
  }, [net, ticker, accent]);

  // Reflect tag changes (gold ring) without rebuilding the whole graph.
  React.useEffect(() => {
    const ds = nodesRef.current;
    if (!ds) return;
    const ups = [];
    ds.forEach((nd) => {
      if (nd.id === '__HUB__' || nd.shape !== 'dot') return;
      const tagged = !!tags[nd.id];
      ups.push({ id: nd.id, borderWidth: tagged ? 3 : 1.5, color: { background: '#FBFAF6', border: tagged ? '#C9A24A' : tokens.ink } });
    });
    if (ups.length) ds.update(ups);
  }, [tags]);

  if (failed) {
    return (
      <div style={{ padding: '20px', fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, lineHeight: 1.7 }}>
        Couldn't load the graph library. Check your connection and reopen this panel.
      </div>
    );
  }
  return <div ref={ref} style={{ width: '100%', height: '560px', background: tokens.bg, border: `1px solid ${tokens.inkLineSoft}` }} />;
};

const NetworkPanel = ({ investment }) => {
  const isMobile = useIsMobile();
  const ticker = investment.ticker;
  const accent = investment.color || '#C9A24A';
  const [net, setNet] = React.useState(undefined);
  const [tags, setTags] = React.useState({});      // person -> {relationship, note, id}
  const [selected, setSelected] = React.useState(null);

  React.useEffect(() => {
    const client = window._supabaseClient;
    if (!client) { setNet(null); return; }
    const loadNet = () => client.from('people_snapshots').select('data').eq('ticker', ticker).maybeSingle()
      .then(({ data: row }) => setNet((row && row.data && row.data.network) || null));
    loadNet();
    client.from('person_tags').select('id, person, relationship, note').eq('ticker', ticker)
      .then(({ data: rows }) => {
        const m = {};
        (rows || []).forEach((r) => { m[r.person] = r; });
        setTags(m);
      });
    // Re-read when a research run (e.g. RESEARCH PROXY) updates the snapshot.
    const onUpd = (e) => { if (!e.detail || e.detail === ticker) loadNet(); };
    window.addEventListener('people-snapshot-updated', onUpd);
    return () => window.removeEventListener('people-snapshot-updated', onUpd);
  }, [ticker]);

  const toggleTag = async (person) => {
    const client = window._supabaseClient;
    if (!client) return;
    if (tags[person]) {
      await client.from('person_tags').delete().eq('id', tags[person].id);
      setTags((t) => { const n = { ...t }; delete n[person]; return n; });
    } else {
      const { data: row } = await client.from('person_tags').insert({ ticker, person, relationship: 'know' }).select().single();
      if (row) setTags((t) => ({ ...t, [person]: row }));
    }
  };

  if (net === undefined) return null;
  const hasGraph = net && (net.nodes || []).length > 0;

  const body = !hasGraph ? (
    <div style={{ padding: '20px' }}>
      <div style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.6, marginBottom: '8px' }}>No network data for {ticker} yet.</div>
      <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, lineHeight: 1.8 }}>
        The graph is built by the <span style={{ color: tokens.ink }}>⟳ RESEARCH PROXY</span> run on the Ownership &amp; Compensation panel above (same research pass extracts the network).
      </div>
    </div>
  ) : (
    <div style={{ padding: '12px 20px 18px' }}>
      <div style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, letterSpacing: '0.06em', marginBottom: '8px' }}>
        DRAG TO MOVE · SCROLL TO ZOOM · CLICK A PERSON TO INSPECT &amp; TAG
      </div>
      <VisNetwork net={net} accent={accent} ticker={ticker} tags={tags} onSelect={setSelected} />

      {/* selected-person detail + tagging */}
      {selected && (() => {
        const n = (net.nodes || []).find((x) => x.id === selected) || { id: selected };
        const myEdges = (net.edges || []).filter((e) => e.source === selected || e.target === selected);
        return (
          <div style={{ border: `1px solid ${tokens.inkLine}`, borderLeft: `3px solid ${accent}`, padding: '12px 14px', marginTop: '8px', background: tokens.bg }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '12px', flexWrap: 'wrap' }}>
              <div>
                <span style={{ fontSize: '14px', fontWeight: 600 }}>{n.label || n.id}</span>
                {n.role && <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, marginLeft: '8px', textTransform: 'uppercase' }}>{n.role}</span>}
              </div>
              <button onClick={() => toggleTag(selected)}
                style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.08em', padding: '5px 11px', cursor: 'pointer', background: tags[selected] ? '#C9A24A' : 'none', color: tags[selected] ? tokens.ink : tokens.inkSoft, border: `1px solid ${tags[selected] ? '#C9A24A' : tokens.inkLine}` }}>
                {tags[selected] ? '★ I KNOW THEM' : '☆ TAG: I KNOW THEM'}
              </button>
            </div>
            {myEdges.length > 0 && (
              <div style={{ marginTop: '8px', display: 'flex', flexDirection: 'column', gap: '3px' }}>
                {myEdges.map((e, i) => (
                  <div key={i} style={{ fontSize: '12px', color: tokens.inkSoft }}>
                    <span style={{ display: 'inline-block', width: '8px', height: '8px', background: EDGE_COLORS[e.kind] || '#B9B2A6', borderRadius: '2px', marginRight: '7px' }} />
                    {e.source === selected ? e.target : e.source}{e.detail ? ` — ${e.detail}` : ''} <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute }}>({e.kind})</span>
                  </div>
                ))}
              </div>
            )}
          </div>
        );
      })()}

      <div style={{ display: 'flex', gap: isMobile ? '12px' : '20px', flexWrap: 'wrap', marginTop: '10px', fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, letterSpacing: '0.06em' }}>
        <span>● PERSON</span><span>◆ EXTERNAL ORG</span>
        <span style={{ color: EDGE_COLORS.co_board }}>┄ CO-BOARD</span>
        <span style={{ color: EDGE_COLORS.employer }}>— SHARED EMPLOYER</span>
        <span style={{ color: EDGE_COLORS.education }}>— SHARED SCHOOL</span>
        <span style={{ color: '#C9A24A' }}>○ TAGGED (I KNOW THEM)</span>
      </div>
    </div>
  );

  return (
    <IntelShell collapseKey="network" title="NETWORK MAP · BOARD & MANAGEMENT OVERLAPS" right={Object.keys(tags).length ? `${Object.keys(tags).length} TAGGED` : ''}>
      {body}
    </IntelShell>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// Shareholder-base panel — passive/active mix, activists, 13F changes.
// Reads people_snapshots.holders; research = people-intel mode 'holders'.
// ─────────────────────────────────────────────────────────────────────────────
const HOLDER_COLORS = { passive: '#9A938A', active: '#1A4FB5', activist: '#C9622E', insider: '#1F6F5C', other: '#B9B2A6' };

const HoldersPanel = ({ investment }) => {
  const ticker = investment.ticker;
  const [data, setData] = React.useState(undefined);
  const [run, setRun] = React.useState(false);
  const [err, setErr] = React.useState('');

  React.useEffect(() => {
    const client = window._supabaseClient;
    if (!client) { setData(null); return; }
    const load = () => client.from('people_snapshots').select('data').eq('ticker', ticker).maybeSingle()
      .then(({ data: row }) => setData((row && row.data) || null));
    load();
    const onUpd = (e) => { if (!e.detail || e.detail === ticker) load(); };
    window.addEventListener('people-snapshot-updated', onUpd);
    return () => window.removeEventListener('people-snapshot-updated', onUpd);
  }, [ticker]);

  const research = async () => {
    setRun(true); setErr('');
    try { setData(await invokePeopleIntel(ticker, investment.name || ticker, 'holders')); }
    catch (e) { setErr(e.message); }
    finally { setRun(false); }
  };

  if (data === undefined) return null;
  const holders = (data && data.holders) || [];
  const sum = (data && data.holders_summary) || null;
  const maxPct = Math.max(0.1, ...holders.map((h) => h.pct || 0));

  const body = !holders.length ? (
    <div style={{ padding: '20px' }}>
      <div style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.6, marginBottom: '8px' }}>No shareholder-base data for {ticker} yet.</div>
      {err && <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.red, marginBottom: '8px' }}>{err}</div>}
      <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, lineHeight: 1.8 }}>
        Click <span style={{ color: tokens.ink }}>⟳ MAP HOLDERS</span> — Claude researches 13F data + the proxy to map who decides a vote.
      </div>
    </div>
  ) : (
    <div style={{ padding: '18px 20px' }}>
      {err && <div style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.red, marginBottom: '12px' }}>{err}</div>}
      {sum && (
        <div style={{ display: 'flex', gap: '24px', flexWrap: 'wrap', marginBottom: '14px', borderBottom: `1px solid ${tokens.inkLineSoft}`, paddingBottom: '12px' }}>
          {[['PASSIVE', sum.passive_pct], ['ACTIVE', sum.active_pct], ['INSIDER', sum.insider_pct], ['TOP-10', sum.top10_pct]].map(([k, v]) => (
            <div key={k}>
              <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.12em', color: tokens.inkMute }}>{k}</div>
              <div style={{ fontSize: '20px', fontFamily: fontDisplay, letterSpacing: '-0.02em' }}>{v != null ? `${v}%` : '—'}</div>
            </div>
          ))}
        </div>
      )}
      {[...holders].sort((a, b) => (b.pct || 0) - (a.pct || 0)).map((h, i) => (
        <div key={i} style={{ display: 'grid', gridTemplateColumns: '1.4fr 70px 1fr 64px', gap: '10px', alignItems: 'center', padding: '6px 0', borderBottom: `1px solid ${tokens.inkLineSoft}` }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: '8px', minWidth: 0 }}>
            <span style={{ width: '8px', height: '8px', borderRadius: '2px', background: HOLDER_COLORS[h.type] || HOLDER_COLORS.other, flexShrink: 0 }} title={h.type} />
            <span style={{ fontSize: '12.5px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }} title={h.note || ''}>{h.name}</span>
          </div>
          <span style={{ fontFamily: fontMono, fontSize: '11px', textAlign: 'right' }}>{h.pct != null ? `${h.pct}%` : '—'}</span>
          <div style={{ height: '6px', background: tokens.inkLineSoft, borderRadius: '3px', overflow: 'hidden' }}>
            <div style={{ width: `${Math.min(100, (h.pct || 0) / maxPct * 100)}%`, height: '100%', background: HOLDER_COLORS[h.type] || HOLDER_COLORS.other }} />
          </div>
          <span style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.06em', textAlign: 'right', color: h.change === 'added' || h.change === 'new' ? tokens.green : h.change === 'trimmed' || h.change === 'exited' ? tokens.red : tokens.inkMute }}>
            {(h.change || '—').toUpperCase()}
          </span>
        </div>
      ))}
      {sum && sum.commentary && (
        <div style={{ marginTop: '12px', padding: '10px 12px', background: tokens.bgAlt, fontSize: '13px', lineHeight: 1.55, color: tokens.inkSoft }}>{sum.commentary}</div>
      )}
      <div style={{ display: 'flex', gap: '16px', flexWrap: 'wrap', marginTop: '10px', fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, letterSpacing: '0.06em' }}>
        {Object.entries(HOLDER_COLORS).map(([k, c]) => <span key={k}><span style={{ display: 'inline-block', width: '8px', height: '8px', background: c, borderRadius: '2px', marginRight: '5px' }} />{k.toUpperCase()}</span>)}
        <span>· 13F-BASED APPROXIMATIONS</span>
      </div>
    </div>
  );

  return (
    <IntelShell collapseKey="holders" title="SHAREHOLDER BASE" right={data && data.holders_as_of ? `AS OF ${data.holders_as_of}` : ''}
      onAction={research} actionLabel="⟳ MAP HOLDERS" running={run}>
      {body}
    </IntelShell>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// Site-watch panel — alt-data from the company's own website. Watches configured
// pages (leadership, pricing, product/news), diffs on refresh, Claude summarizes
// investor-relevant changes. Reads site_snapshots; refresh = `site-watch` function.
// ─────────────────────────────────────────────────────────────────────────────
const SiteWatchPanel = ({ investment }) => {
  const ticker = investment.ticker;
  const [urls, setUrls] = React.useState([]);
  const [snap, setSnap] = React.useState(undefined);
  const [report, setReport] = React.useState(null);
  const [run, setRun] = React.useState(false);
  const [err, setErr] = React.useState('');
  const [input, setInput] = React.useState('');

  React.useEffect(() => {
    const client = window._supabaseClient;
    if (!client) { setSnap(null); return; }
    client.from('live_investments').select('watch_urls').eq('ticker', ticker).limit(1).maybeSingle()
      .then(({ data: row }) => setUrls((row && row.watch_urls) || []));
    client.from('site_snapshots').select('data').eq('ticker', ticker).maybeSingle()
      .then(({ data: row }) => setSnap((row && row.data) || null));
  }, [ticker]);

  const addUrl = async () => {
    if (!roleCanManageTickers()) { setErr(DEMO_BLOCK_MSG); return; }
    const u = input.trim();
    if (!u || !/^https?:\/\//i.test(u)) { setErr('Paste a full URL (https://…)'); return; }
    const client = window._supabaseClient;
    const next = Array.from(new Set([...urls, u]));
    const { data: { user } } = await client.auth.getUser();
    await client.from('live_investments').update({ watch_urls: next }).eq('ticker', ticker);
    setUrls(next); setInput(''); setErr('');
  };

  const removeUrl = async (u) => {
    if (!roleCanManageTickers()) { setErr(DEMO_BLOCK_MSG); return; }
    const client = window._supabaseClient;
    const next = urls.filter((x) => x !== u);
    const { data: { user } } = await client.auth.getUser();
    await client.from('live_investments').update({ watch_urls: next }).eq('ticker', ticker);
    setUrls(next);
  };

  const refresh = async () => {
    const client = window._supabaseClient;
    if (!roleCanResearch()) { setErr(DEMO_BLOCK_MSG); return; }
    setRun(true); setErr('');
    try {
      const { data, error } = await client.functions.invoke('site-watch', { body: { ticker, company: investment.name || ticker } });
      let payload = data;
      if (error) { try { payload = await error.context.json(); } catch {} if (!payload || !payload.error) throw new Error(error.message || 'Edge function error'); }
      if (payload && payload.error) { setErr(payload.error); return; }
      setReport(payload.report || []);
      setSnap((s) => ({ ...(s || {}), history: payload.history || (s && s.history) || [], fetched_at: payload.fetched_at }));
    } catch (e) { setErr(e.message); }
    finally { setRun(false); }
  };

  if (snap === undefined) return null;
  const history = (snap && snap.history) || [];
  const statusFor = (u) => (report || []).find((r) => r.url === u);

  return (
    <IntelShell collapseKey="sitewatch" title="SITE WATCH · COMPANY WEB ALT-DATA" right={snap && snap.fetched_at ? new Date(snap.fetched_at).toLocaleDateString() : ''}
      onAction={urls.length ? refresh : null} actionLabel="⟳ CHECK PAGES" running={run}>
      <div style={{ padding: '16px 20px' }}>
        {err && <div style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.red, marginBottom: '10px' }}>{err}</div>}

        {/* watched urls */}
        {urls.length === 0 ? (
          <div style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.6, marginBottom: '12px' }}>
            Watch the company's own pages for change — leadership (exec departures), pricing (packaging moves), product/news (launch cadence). Add a page below.
          </div>
        ) : (
          <div style={{ marginBottom: '12px' }}>
            {urls.map((u) => {
              const st = statusFor(u);
              const page = snap && snap.pages && snap.pages[u];
              const lastChange = page && page.last_change;
              return (
                <div key={u} style={{ padding: '7px 0', borderBottom: `1px solid ${tokens.inkLineSoft}` }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', gap: '10px', alignItems: 'baseline' }}>
                    <a href={u} target="_blank" rel="noopener noreferrer" style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.ink, textDecoration: 'none', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: '70%' }}>{u.replace(/^https?:\/\//, '')}</a>
                    <div style={{ display: 'flex', gap: '10px', alignItems: 'center', flexShrink: 0 }}>
                      {st && <span style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.08em', color: st.status === 'changed' ? tokens.green : st.status === 'error' ? tokens.red : tokens.inkMute }}>{st.status.toUpperCase()}</span>}
                      {roleCanManageTickers() && <button onClick={() => removeUrl(u)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: tokens.inkMute, fontSize: '11px', padding: 0 }}>✕</button>}
                    </div>
                  </div>
                  {(st && st.status === 'changed' && st.summary) ? (
                    <div style={{ fontSize: '12.5px', color: tokens.ink, lineHeight: 1.5, marginTop: '3px' }}>{st.summary} <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute }}>SIG {st.significance}/5</span></div>
                  ) : lastChange ? (
                    <div style={{ fontSize: '12px', color: tokens.inkSoft, lineHeight: 1.5, marginTop: '3px' }}>Last change {new Date(lastChange.at).toLocaleDateString()}: {lastChange.summary}</div>
                  ) : null}
                </div>
              );
            })}
          </div>
        )}

        {/* add url — owner + live demo only */}
        {roleCanManageTickers() && (
        <div style={{ display: 'flex', gap: '8px', marginBottom: history.length ? '16px' : 0 }}>
          <input value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && addUrl()}
            placeholder="https://company.com/leadership — or /pricing, /news…"
            style={{ flexGrow: 1, padding: '8px 11px', border: `1px solid ${tokens.inkLine}`, background: tokens.bg, fontSize: '12px', fontFamily: fontMono }} />
          <button onClick={addUrl} style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.08em', padding: '8px 14px', background: 'none', border: `1px solid ${tokens.ink}`, cursor: 'pointer' }}>＋ WATCH</button>
        </div>
        )}

        {/* change history */}
        {history.length > 0 && (
          <div>
            <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.inkMute, marginBottom: '6px' }}>CHANGE LOG</div>
            {history.slice(0, 8).map((h, i) => (
              <div key={i} style={{ display: 'flex', gap: '10px', padding: '4px 0', fontSize: '12px', color: tokens.inkSoft, alignItems: 'baseline' }}>
                <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, flexShrink: 0 }}>{new Date(h.at).toLocaleDateString()}</span>
                <span style={{ lineHeight: 1.45 }}>{h.summary}</span>
              </div>
            ))}
          </div>
        )}
      </div>
    </IntelShell>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// 02 · MARKET SIGNALS — weekly comp-sheet screening (MS EU Software & Services).
// Upload (or email-forward) the workbook → screens-intel parses it, runs the
// valuation regressions, scores the Finch Bay screen, and drafts top-3 ideas.
// ─────────────────────────────────────────────────────────────────────────────
const TAB_COLORS = { SW: '#0E0E0C', SVS: '#1A4FB5' };
const _pctf = (v, dp = 0) => v == null ? '—' : (v * 100).toFixed(dp) + '%';
const _xf = (v) => v == null ? '—' : v.toFixed(1) + 'x';

// SVG scatter with fitted line. Points carry {t, tab, x, y}; logY means y = ln(multiple).
const ScreenScatter = ({ reg, selected, onSelect }) => {
  const W = 760, H = 420, P = { l: 54, r: 16, t: 14, b: 38 };
  const pts = reg.points || [];
  if (!pts.length) return null;
  const xs = pts.map((p) => p.x), ys = pts.map((p) => p.y);
  const xmin = Math.min(...xs), xmax = Math.max(...xs), ymin = Math.min(...ys), ymax = Math.max(...ys);
  const xpad = (xmax - xmin) * 0.05 || 0.01, ypad = (ymax - ymin) * 0.06 || 0.1;
  const X = (x) => P.l + (x - xmin + xpad) / (xmax - xmin + 2 * xpad) * (W - P.l - P.r);
  const Y = (y) => H - P.b - (y - ymin + ypad) / (ymax - ymin + 2 * ypad) * (H - P.t - P.b);

  // axis ticks
  const xticks = [];
  for (let i = 0; i <= 4; i++) xticks.push(xmin + (xmax - xmin) * i / 4);
  let yticks;
  if (reg.logY) {
    yticks = [0.25, 0.5, 1, 2, 4, 8, 16, 32].filter((m) => Math.log(m) >= ymin - 0.05 && Math.log(m) <= ymax + 0.05)
      .map((m) => ({ pos: Math.log(m), lab: m + 'x' }));
  } else {
    yticks = [];
    for (let i = 0; i <= 4; i++) { const v = ymin + (ymax - ymin) * i / 4; yticks.push({ pos: v, lab: v.toFixed(0) + 'x' }); }
  }

  return (
    <svg viewBox={`0 0 ${W} ${H}`} style={{ width: '100%', height: 'auto', display: 'block', background: tokens.bg, border: `1px solid ${tokens.inkLineSoft}` }}>
      {yticks.map((t, i) => (
        <g key={'y' + i}>
          <line x1={P.l} x2={W - P.r} y1={Y(t.pos)} y2={Y(t.pos)} stroke={tokens.inkLineSoft} strokeWidth="1" />
          <text x={P.l - 8} y={Y(t.pos) + 3} textAnchor="end" fontSize="10" fill="#9A938A" fontFamily="JetBrains Mono, monospace">{t.lab}</text>
        </g>
      ))}
      {xticks.map((t, i) => (
        <text key={'x' + i} x={X(t)} y={H - P.b + 18} textAnchor="middle" fontSize="10" fill="#9A938A" fontFamily="JetBrains Mono, monospace">{_pctf(t)}</text>
      ))}
      {/* fitted line */}
      <line x1={X(xmin)} y1={Y(reg.a + reg.b * xmin)} x2={X(xmax)} y2={Y(reg.a + reg.b * xmax)} stroke="#C9622E" strokeWidth="2" strokeDasharray="6 4" opacity="0.85" />
      {/* points */}
      {pts.map((p, i) => {
        const sel = selected === p.t;
        return (
          <circle key={i} cx={X(p.x)} cy={Y(p.y)} r={sel ? 7 : 4}
            fill={sel ? '#C9A24A' : TAB_COLORS[p.tab] || tokens.ink}
            opacity={sel ? 1 : 0.55} stroke={sel ? tokens.ink : 'none'} strokeWidth="1.5"
            style={{ cursor: 'pointer' }} onClick={() => onSelect(sel ? null : p.t)}>
            <title>{p.t}</title>
          </circle>
        );
      })}
      <text x={W / 2} y={H - 6} textAnchor="middle" fontSize="10" fill="#7A736A" fontFamily="JetBrains Mono, monospace">{reg.xLabel.toUpperCase()}</text>
      <text x={14} y={H / 2} transform={`rotate(-90 14 ${H / 2})`} textAnchor="middle" fontSize="10" fill="#7A736A" fontFamily="JetBrains Mono, monospace">{reg.yLabel.toUpperCase()}</text>
    </svg>
  );
};

const IdeaCard = ({ idea, rank }) => {
  const [open, setOpen] = React.useState(rank === 0);
  return (
    <div style={{ border: `1px solid ${tokens.inkLine}`, borderTop: `3px solid #C9622E`, background: tokens.bg, padding: '16px 18px' }}>
      <div onClick={() => setOpen((o) => !o)} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', cursor: 'pointer', gap: '10px' }}>
        <div>
          <span style={{ fontFamily: fontMono, fontSize: '11px', color: '#C9622E', marginRight: '10px' }}>#{rank + 1}</span>
          <span style={{ fontSize: '17px', fontWeight: 600, letterSpacing: '-0.02em' }}>{idea.name}</span>
          <span style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.inkMute, marginLeft: '10px' }}>{idea.ticker}</span>
        </div>
        <span style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute }}>{open ? '▾' : '▸'}</span>
      </div>
      {open && (
        <div style={{ marginTop: '12px' }}>
          <div style={{ fontSize: '13.5px', lineHeight: 1.6, color: tokens.ink }}>{idea.why}</div>
          {idea.narrative && idea.narrative.length > 0 && (
            <div style={{ marginTop: '14px' }}>
              <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.inkMute, marginBottom: '6px' }}>MARKET NARRATIVE</div>
              {idea.narrative.map((n, i) => <div key={i} style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.55, paddingLeft: '14px', textIndent: '-14px' }}>· {n}</div>)}
            </div>
          )}
          {idea.questions && idea.questions.length > 0 && (
            <div style={{ marginTop: '14px' }}>
              <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.inkMute, marginBottom: '6px' }}>KEY QUESTIONS TO VALIDATE</div>
              {idea.questions.map((q, i) => (
                <div key={i} style={{ display: 'grid', gridTemplateColumns: '22px 1fr', gap: '6px', fontSize: '13px', color: tokens.ink, lineHeight: 1.55, padding: '3px 0' }}>
                  <span style={{ fontFamily: fontMono, fontSize: '10px', color: '#C9622E' }}>{String(i + 1).padStart(2, '0')}</span>
                  <span>{q}</span>
                </div>
              ))}
            </div>
          )}
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginTop: '14px' }}>
            <div>
              <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.green, marginBottom: '6px' }}>PROS</div>
              {(idea.pros || []).map((p, i) => <div key={i} style={{ fontSize: '12.5px', color: tokens.inkSoft, lineHeight: 1.5, padding: '2px 0' }}>+ {p}</div>)}
            </div>
            <div>
              <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.red, marginBottom: '6px' }}>CONS</div>
              {(idea.cons || []).map((c, i) => <div key={i} style={{ fontSize: '12.5px', color: tokens.inkSoft, lineHeight: 1.5, padding: '2px 0' }}>− {c}</div>)}
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

const ScreensPanel = () => {
  const isMobile = useIsMobile();
  const [snap, setSnap] = React.useState(undefined);
  const [regId, setRegId] = React.useState('ro40_lnevs');
  const [selected, setSelected] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const fileRef = React.useRef(null);
  const pollRef = React.useRef(null);

  const load = React.useCallback(async () => {
    const client = window._supabaseClient;
    if (!client) { setSnap(null); return null; }
    const { data: row } = await client.from('market_screens').select('data, fetched_at').eq('source', 'ms_eu_software').maybeSingle();
    const d = (row && row.data) || null;
    setSnap(d);
    return d;
  }, []);
  React.useEffect(() => { load(); }, [load]);

  // While ideas are still generating in the background, keep checking quietly.
  React.useEffect(() => {
    if (snap && snap.ideas_status === 'pending' && !pollRef.current) {
      let tries = 0;
      pollRef.current = setInterval(async () => {
        const d = await load();
        if (!d || d.ideas_status !== 'pending' || ++tries > 24) { clearInterval(pollRef.current); pollRef.current = null; }
      }, 8000);
    }
    return () => { if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; } };
  }, [snap && snap.ideas_status, load]);

  const upload = async (file) => {
    if (!file) return;
    if (!roleCanResearch()) { setErr(DEMO_BLOCK_MSG); return; }
    setBusy(true); setErr('');
    try {
      const buf = await file.arrayBuffer();
      let bin = ''; const u8 = new Uint8Array(buf);
      for (let i = 0; i < u8.length; i += 0x8000) bin += String.fromCharCode.apply(null, u8.subarray(i, i + 0x8000));
      const client = window._supabaseClient;
      const since = new Date().toISOString();
      const { data, error } = await client.functions.invoke('screens-intel', { body: { file_b64: btoa(bin), filename: file.name } });
      let payload = data;
      if (error) { try { payload = await error.context.json(); } catch {} if (!payload || !payload.error) throw new Error(error.message || 'Edge function error'); }
      if (payload && payload.error) throw new Error(payload.error);
      // Poll until the snapshot lands (parse is fast; ideas keep filling after).
      const deadline = Date.now() + 3 * 60 * 1000;
      while (Date.now() < deadline) {
        await new Promise((r) => setTimeout(r, 4000));
        const d = await load();
        if (d && d.fetched_at && d.fetched_at >= since) {
          if (d.error && d.error_at && d.error_at >= since) throw new Error(d.error);
          return;
        }
      }
      throw new Error('Parse timed out — reload in a minute.');
    } catch (e) { setErr(e.message); }
    finally { setBusy(false); if (fileRef.current) fileRef.current.value = ''; }
  };

  if (snap === undefined) return null;

  const regs = (snap && snap.regressions) || [];
  const reg = regs.find((r) => r.id === regId) || regs[0];
  const rows = (snap && snap.rows) || [];
  const byTicker = {}; rows.forEach((r) => { byTicker[r.ticker] = r; });
  const selRow = selected ? byTicker[selected] : null;
  const ranked = rows.filter((r) => r.score != null).sort((a, b) => b.score - a.score).slice(0, 14);
  const ideas = (snap && snap.ideas) || [];

  return (
    <IntelShell collapseKey="screens" title="MS EU SOFTWARE & SERVICES · WEEKLY SCREEN"
      right={snap ? `${snap.n || 0} COMPANIES · ${snap.fetched_at ? new Date(snap.fetched_at).toLocaleDateString() : ''}` : ''}
      onAction={() => fileRef.current && fileRef.current.click()} actionLabel={busy ? null : '⬆ UPLOAD XLSX'} running={busy}>
      <input ref={fileRef} type="file" accept=".xlsx" style={{ display: 'none' }} onChange={(e) => upload(e.target.files[0])} />
      <div style={{ padding: '16px 20px' }}>
        {err && <div style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.red, marginBottom: '10px' }}>{err}</div>}

        {!snap || !rows.length ? (
          <div>
            <div style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.6, marginBottom: '8px' }}>No screen data yet.</div>
            <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, lineHeight: 1.8 }}>
              <span style={{ color: tokens.ink }}>⬆ UPLOAD XLSX</span> with the weekly MS workbook — or just <span style={{ color: tokens.ink }}>forward the Morgan Stanley email</span> (with the attachment) to your ingest address with <span style={{ color: tokens.ink }}>add_to_screens</span> in the subject. Needs the <span style={{ color: tokens.ink }}>screens-intel</span> function deployed.
            </div>
          </div>
        ) : (
          <React.Fragment>
            {/* regression picker */}
            <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', marginBottom: '12px' }}>
              {regs.map((r) => (
                <button key={r.id} onClick={() => setRegId(r.id)}
                  style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.04em', padding: '6px 11px', cursor: 'pointer',
                    background: (reg && reg.id === r.id) ? tokens.ink : 'none', color: (reg && reg.id === r.id) ? tokens.paper : tokens.inkSoft,
                    border: `1px solid ${(reg && reg.id === r.id) ? tokens.ink : tokens.inkLine}` }}>
                  {r.label.split('—')[0].trim()} · R² {r.r2.toFixed(2)}
                </button>
              ))}
            </div>

            {reg && (
              <React.Fragment>
                <div style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, letterSpacing: '0.06em', marginBottom: '6px' }}>
                  {reg.label.toUpperCase()} · N={reg.n} · R²={reg.r2.toFixed(3)} · <span style={{ color: TAB_COLORS.SW }}>● SOFTWARE</span> <span style={{ color: TAB_COLORS.SVS }}>● IT SERVICES</span> · CLICK A DOT
                </div>
                <ScreenScatter reg={reg} selected={selected} onSelect={setSelected} />
              </React.Fragment>
            )}

            {selRow && (
              <div style={{ border: `1px solid ${tokens.inkLine}`, borderLeft: `3px solid #C9A24A`, padding: '10px 14px', marginTop: '8px', background: tokens.bg, display: 'flex', gap: isMobile ? '12px' : '24px', flexWrap: 'wrap', alignItems: 'baseline' }}>
                <span style={{ fontSize: '14px', fontWeight: 600 }}>{selRow.name} <span style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.inkMute }}>{selRow.ticker} · {selRow.section}</span></span>
                {[['MCAP', selRow.mktcap != null ? `€${(selRow.mktcap / 1000).toFixed(1)}B` : '—'], ['G27', _pctf(selRow.g27, 1)], ['EBIT MGN', _pctf(selRow.m27, 1)], ['RO40', _pctf(selRow.ro40, 0)], ['EV/S 27', _xf(selRow.evs27)], ['EV/EBITDA 27', _xf(selRow.evebitda27)], ['FCF/EV', _pctf(selRow.fcfy27, 1)], ['GAP TO FIT', _pctf(selRow.gap, 0)], ['MS', `${selRow.rating || '—'} ${_pctf(selRow.upside, 0)}`]].map(([k, v]) => (
                  <span key={k} style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.inkSoft }}><span style={{ color: tokens.inkMute }}>{k}</span> {v}</span>
                ))}
              </div>
            )}

            {/* screen ranking */}
            <div style={{ marginTop: '20px' }}>
              <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '8px' }}>
                FINCH BAY SCREEN · TOP RANKED <span style={{ color: tokens.inkMute, letterSpacing: '0.04em' }}>(VALUATION GAP × QUALITY × MID-CAP FIT − LEVERAGE)</span>
              </div>
              <div style={{ overflowX: 'auto' }}>
                <div style={{ minWidth: '700px' }}>
                  <div style={{ display: 'grid', gridTemplateColumns: '44px 1.6fr 70px 64px 64px 70px 78px 64px 84px', gap: '8px', borderBottom: `1px solid ${tokens.inkLine}`, padding: '5px 0' }}>
                    {['SCORE', 'COMPANY', 'MCAP', 'G27', 'RO40', 'EV/S 27', 'GAP TO FIT', 'ND/EBITDA', 'MS VIEW'].map((h) => (
                      <span key={h} style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.06em', color: tokens.inkMute }}>{h}</span>
                    ))}
                  </div>
                  {ranked.map((r) => (
                    <div key={r.ticker} onClick={() => setSelected(selected === r.ticker ? null : r.ticker)}
                      style={{ display: 'grid', gridTemplateColumns: '44px 1.6fr 70px 64px 64px 70px 78px 64px 84px', gap: '8px', padding: '7px 0', borderBottom: `1px solid ${tokens.inkLineSoft}`, cursor: 'pointer', background: selected === r.ticker ? tokens.bgAlt : 'transparent', alignItems: 'baseline' }}>
                      <span style={{ fontFamily: fontMono, fontSize: '11px', fontWeight: 600, color: '#C9622E' }}>{r.score}</span>
                      <span style={{ fontSize: '12.5px', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{r.name} <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute }}>{r.ticker}</span></span>
                      <span style={{ fontFamily: fontMono, fontSize: '10.5px' }}>{r.mktcap != null ? `€${(r.mktcap / 1000).toFixed(1)}B` : '—'}</span>
                      <span style={{ fontFamily: fontMono, fontSize: '10.5px' }}>{_pctf(r.g27, 0)}</span>
                      <span style={{ fontFamily: fontMono, fontSize: '10.5px' }}>{_pctf(r.ro40, 0)}</span>
                      <span style={{ fontFamily: fontMono, fontSize: '10.5px' }}>{_xf(r.evs27)}</span>
                      <span style={{ fontFamily: fontMono, fontSize: '10.5px', color: (r.gap || 0) > 0 ? tokens.green : tokens.red }}>{_pctf(r.gap, 0)}</span>
                      <span style={{ fontFamily: fontMono, fontSize: '10.5px' }}>{r.ndebitda != null ? r.ndebitda.toFixed(1) : '—'}</span>
                      <span style={{ fontFamily: fontMono, fontSize: '10.5px', color: tokens.inkSoft }}>{r.rating || '—'} {_pctf(r.upside, 0)}</span>
                    </div>
                  ))}
                </div>
              </div>
            </div>

            {/* idea candidates */}
            <div style={{ marginTop: '24px' }}>
              <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '10px' }}>TOP-3 IDEA CANDIDATES · CLAUDE × FINCH BAY PHILOSOPHY</div>
              {snap.ideas_status === 'pending' ? (
                <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, letterSpacing: '0.06em' }}>GENERATING IDEAS — this fills in within a minute or two…</div>
              ) : snap.ideas_status === 'error' ? (
                <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.red }}>Idea generation failed: {snap.ideas_error}</div>
              ) : ideas.length === 0 ? (
                <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute }}>No ideas in this snapshot.</div>
              ) : (
                <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
                  {ideas.map((idea, i) => <IdeaCard key={i} idea={idea} rank={i} />)}
                </div>
              )}
              <div style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, letterSpacing: '0.04em', lineHeight: 1.6, marginTop: '10px' }}>
                Screen + ideas are a starting point, not research — generated from the comp sheet without web access. Automate by forwarding the MS email (attachment included) to your ingest address with "add_to_screens" in the subject.
              </div>
            </div>
          </React.Fragment>
        )}
      </div>
    </IntelShell>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// Idea Database — the permanent, searchable archive of every idea generated
// across Section 02 (the MS comp screen + the AI Market Signals scan). Reads
// from `screen_ideas`; same card format as the freshly-generated ideas, plus
// provenance, free-text search, a source filter, and an owner workflow
// (★ star · verdict · notes) persisted back to the row.
// ─────────────────────────────────────────────────────────────────────────────
const formeCanWrite = () => (window.siteCanWrite ? window.siteCanWrite('forme') : roleCanResearch());

const GEN_LABELS = { ms_eu_software: 'MS SCREEN', ai_signals: 'AI SIGNALS', manual: 'MANUAL' };
const VERDICTS = [
  { id: 'pursuing', label: 'Pursuing', color: tokens.green },
  { id: 'watching', label: 'Watching', color: tokens.ochre },
  { id: 'passed',   label: 'Passed',   color: tokens.red },
];

const ArchiveIdeaCard = ({ idea, onPatch }) => {
  const [open, setOpen] = React.useState(false);
  const [notesOpen, setNotesOpen] = React.useState(false);
  const [notes, setNotes] = React.useState(idea.notes || '');
  const canWrite = formeCanWrite();
  const m = idea.metrics || {};
  const metricChips = [
    m.mktcap != null && ['MCAP', `€${(m.mktcap / 1000).toFixed(1)}B`],
    m.ro40 != null && ['RO40', _pctf(m.ro40, 0)],
    m.gap != null && ['GAP', _pctf(m.gap, 0)],
    m.evs27 != null && ['EV/S', _xf(m.evs27)],
  ].filter(Boolean);
  const dt = idea.generated_at ? new Date(idea.generated_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) : '';

  const star = () => canWrite && onPatch({ starred: !idea.starred });
  const setVerdict = (v) => canWrite && onPatch({ verdict: idea.verdict === v ? null : v });
  const saveNotes = () => { onPatch({ notes }); setNotesOpen(false); };

  return (
    <div style={{ border: `1px solid ${tokens.inkLine}`, borderTop: `3px solid ${idea.generator === 'ai_signals' ? '#1A4FB5' : '#C9622E'}`, background: tokens.bg, padding: '14px 16px' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', gap: '10px' }}>
        <div onClick={() => setOpen((o) => !o)} style={{ cursor: 'pointer', minWidth: 0, flex: 1 }}>
          <span style={{ fontSize: '15.5px', fontWeight: 600, letterSpacing: '-0.02em' }}>{idea.name}</span>
          {idea.ticker && <span style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.inkMute, marginLeft: '8px' }}>{idea.ticker}</span>}
          <div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap', marginTop: '4px', alignItems: 'center' }}>
            <span style={{ fontFamily: fontMono, fontSize: '8.5px', letterSpacing: '0.1em', padding: '2px 6px', background: tokens.bgAlt, color: tokens.inkMute }}>
              {GEN_LABELS[idea.generator] || idea.generator}
            </span>
            <span style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute }}>{dt}</span>
            {metricChips.map(([k, v]) => (
              <span key={k} style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkSoft }}><span style={{ color: tokens.inkMute }}>{k}</span> {v}</span>
            ))}
          </div>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: '8px', flexShrink: 0 }}>
          <span onClick={star} title={canWrite ? 'Star' : 'Read-only'} style={{ cursor: canWrite ? 'pointer' : 'default', fontSize: '15px', color: idea.starred ? tokens.ochre : tokens.inkLine }}>{idea.starred ? '★' : '☆'}</span>
          <span onClick={() => setOpen((o) => !o)} style={{ cursor: 'pointer', fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute }}>{open ? '▾' : '▸'}</span>
        </div>
      </div>

      {/* Verdict pills */}
      <div style={{ display: 'flex', gap: '6px', marginTop: '8px', flexWrap: 'wrap' }}>
        {VERDICTS.map((v) => {
          const on = idea.verdict === v.id;
          return (
            <span key={v.id} onClick={() => setVerdict(v.id)}
              style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.08em', padding: '3px 9px', cursor: canWrite ? 'pointer' : 'default',
                border: `1px solid ${on ? v.color : tokens.inkLine}`, color: on ? tokens.paper : tokens.inkMute, background: on ? v.color : 'none' }}>
              {v.label.toUpperCase()}
            </span>
          );
        })}
        {canWrite && (
          <span onClick={() => setNotesOpen((n) => !n)} style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.08em', padding: '3px 9px', cursor: 'pointer', border: `1px solid ${tokens.inkLine}`, color: idea.notes ? tokens.ink : tokens.inkMute }}>
            {idea.notes ? '✎ NOTE' : '+ NOTE'}
          </span>
        )}
      </div>

      {notesOpen && (
        <div style={{ marginTop: '8px' }}>
          <textarea value={notes} onChange={(e) => setNotes(e.target.value)} rows={3}
            placeholder="Private notes on this idea…"
            style={{ width: '100%', padding: '8px 10px', border: `1px solid ${tokens.inkLine}`, background: '#FDFBF7', fontFamily: 'inherit', fontSize: '12.5px', color: tokens.ink, boxSizing: 'border-box', resize: 'vertical' }} />
          <div style={{ display: 'flex', gap: '6px', marginTop: '6px' }}>
            <button onClick={saveNotes} style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.08em', padding: '6px 14px', background: tokens.ink, color: tokens.paper, border: 'none', cursor: 'pointer' }}>SAVE</button>
            <button onClick={() => { setNotes(idea.notes || ''); setNotesOpen(false); }} style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.08em', padding: '6px 14px', background: 'none', border: `1px solid ${tokens.inkLine}`, cursor: 'pointer', color: tokens.inkMute }}>CANCEL</button>
          </div>
        </div>
      )}
      {!notesOpen && idea.notes && (
        <div style={{ marginTop: '8px', fontSize: '12.5px', color: tokens.inkSoft, lineHeight: 1.5, fontStyle: 'italic', borderLeft: `2px solid ${tokens.inkLine}`, paddingLeft: '10px' }}>{idea.notes}</div>
      )}

      {open && (
        <div style={{ marginTop: '12px' }}>
          {idea.why && <div style={{ fontSize: '13.5px', lineHeight: 1.6, color: tokens.ink }}>{idea.why}</div>}
          {idea.narrative && idea.narrative.length > 0 && (
            <div style={{ marginTop: '14px' }}>
              <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.inkMute, marginBottom: '6px' }}>MARKET NARRATIVE</div>
              {idea.narrative.map((n, i) => <div key={i} style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.55, paddingLeft: '14px', textIndent: '-14px' }}>· {n}</div>)}
            </div>
          )}
          {idea.questions && idea.questions.length > 0 && (
            <div style={{ marginTop: '14px' }}>
              <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.inkMute, marginBottom: '6px' }}>KEY QUESTIONS TO VALIDATE</div>
              {idea.questions.map((q, i) => (
                <div key={i} style={{ display: 'grid', gridTemplateColumns: '22px 1fr', gap: '6px', fontSize: '13px', color: tokens.ink, lineHeight: 1.55, padding: '3px 0' }}>
                  <span style={{ fontFamily: fontMono, fontSize: '10px', color: '#C9622E' }}>{String(i + 1).padStart(2, '0')}</span>
                  <span>{q}</span>
                </div>
              ))}
            </div>
          )}
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginTop: '14px' }}>
            <div>
              <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.green, marginBottom: '6px' }}>PROS</div>
              {(idea.pros || []).map((p, i) => <div key={i} style={{ fontSize: '12.5px', color: tokens.inkSoft, lineHeight: 1.5, padding: '2px 0' }}>+ {p}</div>)}
            </div>
            <div>
              <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.14em', color: tokens.red, marginBottom: '6px' }}>CONS</div>
              {(idea.cons || []).map((c, i) => <div key={i} style={{ fontSize: '12.5px', color: tokens.inkSoft, lineHeight: 1.5, padding: '2px 0' }}>− {c}</div>)}
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

const IdeaArchivePanel = () => {
  const [rows, setRows] = React.useState(undefined);
  const [q, setQ] = React.useState('');
  const [gen, setGen] = React.useState('all');
  const [starredOnly, setStarredOnly] = React.useState(false);

  const load = React.useCallback(async () => {
    const client = window._supabaseClient;
    if (!client) { setRows(null); return; }
    const { data } = await client.from('screen_ideas')
      .select('id, generator, source_label, ticker, name, why, narrative, questions, pros, cons, score, metrics, tags, starred, verdict, notes, generated_at')
      .order('generated_at', { ascending: false })
      .limit(500);
    setRows(data || []);
  }, []);
  React.useEffect(() => { load(); }, [load]);

  const patch = async (id, p) => {
    setRows((rs) => rs.map((r) => r.id === id ? { ...r, ...p } : r));   // optimistic
    const client = window._supabaseClient;
    if (client) await client.from('screen_ideas').update(p).eq('id', id);
  };

  if (rows === undefined) return null;

  const list = (rows || []).filter((r) => {
    if (gen !== 'all' && r.generator !== gen) return false;
    if (starredOnly && !r.starred) return false;
    if (!q.trim()) return true;
    const hay = [r.name, r.ticker, r.why, (r.narrative || []).join(' '), (r.pros || []).join(' '), (r.cons || []).join(' '), (r.tags || []).join(' ')].join(' ').toLowerCase();
    return q.toLowerCase().split(/\s+/).every((w) => hay.includes(w));
  });

  const counts = { all: (rows || []).length };
  (rows || []).forEach((r) => { counts[r.generator] = (counts[r.generator] || 0) + 1; });

  const genBtns = [['all', 'ALL'], ['ms_eu_software', 'MS SCREEN'], ['ai_signals', 'AI SIGNALS'], ['manual', 'MANUAL']]
    .filter(([id]) => id === 'all' || counts[id]);

  return (
    <IntelShell collapseKey="ideaarchive" title="IDEA DATABASE · SEARCHABLE ARCHIVE"
      right={`${counts.all} IDEAS`} onAction={load} actionLabel="↻ REFRESH">
      <div style={{ padding: '16px 20px' }}>
        {!rows || rows.length === 0 ? (
          <div style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.6 }}>
            No ideas archived yet. Every idea generated by the weekly MS screen and the AI Market Signals scan is saved here permanently — upload a screen or run the AI scan to start the database.
          </div>
        ) : (
          <React.Fragment>
            <div style={{ display: 'flex', gap: '10px', flexWrap: 'wrap', alignItems: 'center', marginBottom: '14px' }}>
              <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search ideas — name, ticker, thesis, tags…"
                style={{ flex: '1 1 280px', padding: '9px 12px', border: `1px solid ${tokens.inkLine}`, background: '#FDFBF7', fontFamily: 'inherit', fontSize: '13px', color: tokens.ink, outline: 'none' }} />
              <div style={{ display: 'flex', gap: '6px', flexWrap: 'wrap' }}>
                {genBtns.map(([id, label]) => (
                  <button key={id} onClick={() => setGen(id)}
                    style={{ fontFamily: fontMono, fontSize: '9.5px', letterSpacing: '0.06em', padding: '6px 10px', cursor: 'pointer',
                      background: gen === id ? tokens.ink : 'none', color: gen === id ? tokens.paper : tokens.inkSoft, border: `1px solid ${gen === id ? tokens.ink : tokens.inkLine}` }}>
                    {label}{id !== 'all' && counts[id] ? ` ${counts[id]}` : ''}
                  </button>
                ))}
                <button onClick={() => setStarredOnly((s) => !s)}
                  style={{ fontFamily: fontMono, fontSize: '9.5px', letterSpacing: '0.06em', padding: '6px 10px', cursor: 'pointer',
                    background: starredOnly ? tokens.ochre : 'none', color: starredOnly ? tokens.paper : tokens.inkSoft, border: `1px solid ${starredOnly ? tokens.ochre : tokens.inkLine}` }}>
                  ★ STARRED
                </button>
              </div>
            </div>
            {list.length === 0 ? (
              <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, letterSpacing: '0.08em', padding: '20px 0' }}>NO MATCHES</div>
            ) : (
              <div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
                {list.map((idea) => <ArchiveIdeaCard key={idea.id} idea={idea} onPatch={(p) => patch(idea.id, p)} />)}
              </div>
            )}
          </React.Fragment>
        )}
      </div>
    </IntelShell>
  );
};

const MarketSignals = () => {
  const isMobile = useIsMobile();
  const [collapsed, toggle] = useCollapsed('section.market');
  return (
    <div style={{ marginBottom: isMobile ? '40px' : '72px' }}>
      <div onClick={toggle} style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: collapsed ? '0' : '8px', cursor: 'pointer', userSelect: 'none' }}>
        <Chevron collapsed={collapsed} onClick={(e) => { e.stopPropagation(); toggle(); }} size={13} />
        <span style={{ fontFamily: fontMono, fontSize: '11px', letterSpacing: '0.18em', color: tokens.inkMute }}>
          02 · MARKET SIGNALS
        </span>
      </div>
      {!collapsed && <React.Fragment>
        <ScreensPanel />
        <IdeaArchivePanel />
      </React.Fragment>}
    </div>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// Live Investments — ticker list loaded from Supabase, falls back to defaults
// ─────────────────────────────────────────────────────────────────────────────
const LiveInvestments = () => {
  const isMobile = useIsMobile();
  const [investments, setInvestments] = React.useState(null); // null = loading
  const [idx, setIdx]       = React.useState(0);
  const [showAdd, setShowAdd]   = React.useState(false);
  const [showSettings, setShowSettings] = React.useState(false);
  const [removing, setRemoving] = React.useState(null);
  const [secCollapsed, toggleSec] = useCollapsed('section.live');

  const loadInvestments = React.useCallback(async (jumpToTicker) => {
    const client = window._supabaseClient;
    if (!client) {
      setInvestments(DEFAULT_INVESTMENTS);
      return DEFAULT_INVESTMENTS;
    }

    const { data, error } = await client
      .from('live_investments')
      .select('ticker, tv_symbol, exchange, name, query, color, sort_order, jobs_source, careers_url, bloomberg_ticker, peers')
      .order('sort_order', { ascending: true })
      .order('created_at', { ascending: true });

    if (error) {
      // Table may not exist yet — show defaults gracefully
      setInvestments(DEFAULT_INVESTMENTS);
      return DEFAULT_INVESTMENTS;
    }

    if (!data || data.length === 0) {
      // First visit — seed the table with defaults
      const { data: { user } } = await client.auth.getUser();
      if (user) {
        await client.from('live_investments').insert(
          DEFAULT_INVESTMENTS.map((inv, i) => ({
            user_id:    user.id,
            ticker:     inv.ticker,
            tv_symbol:  inv.tvSymbol,
            exchange:   inv.exchange,
            name:       inv.name,
            query:      inv.query,
            color:      inv.color,
            sort_order: i,
          }))
        );
      }
      setInvestments(DEFAULT_INVESTMENTS);
      return DEFAULT_INVESTMENTS;
    }

    const list = data.map(d => ({
      ticker:     d.ticker,
      tvSymbol:   d.tv_symbol,
      exchange:   d.exchange,
      name:       d.name,
      query:      d.query,
      color:      d.color,
      jobsSource: d.jobs_source || '',
      careersUrl: d.careers_url || '',
      bloombergTicker: d.bloomberg_ticker || '',
      peers: d.peers || [],
    }));
    setInvestments(list);

    if (jumpToTicker) {
      const jumpIdx = list.findIndex(i => i.ticker === jumpToTicker);
      if (jumpIdx !== -1) setIdx(jumpIdx);
    }

    return list;
  }, []);

  React.useEffect(() => { loadInvestments(); }, [loadInvestments]);

  const addTicker = async (formData) => {
    if (!roleCanManageTickers()) return { message: DEMO_BLOCK_MSG };
    const client = window._supabaseClient;
    if (!client) {
      // No Supabase — just add to local state
      setInvestments(prev => [...(prev || []), formData]);
      setIdx((investments || []).length);
      setShowAdd(false);
      return null;
    }

    const { data: { user } } = await client.auth.getUser();
    if (!user) return { message: 'Not signed in.' };

    const { error } = await client.from('live_investments').insert({
      user_id:     user.id,
      ticker:      formData.ticker,
      tv_symbol:   formData.tvSymbol,
      exchange:    formData.exchange,
      name:        formData.name,
      query:       formData.query,
      color:       formData.color,
      careers_url: formData.careersUrl || null,
      jobs_source: formData.jobsSource || null,
      bloomberg_ticker: formData.bloombergTicker || null,
      peers:       formData.peers && formData.peers.length ? formData.peers : [],
      sort_order:  (investments || []).length,
    });
    if (error) return error;

    setShowAdd(false);
    await loadInvestments(formData.ticker);
    return null;
  };

  const saveSettings = async (ticker, patch) => {
    if (!roleCanManageTickers()) return { message: DEMO_BLOCK_MSG };
    const client = window._supabaseClient;
    if (!client) return { message: 'No backend connected.' };
    const { data: { user } } = await client.auth.getUser();
    if (!user) return { message: 'Not signed in.' };
    const { error } = await client.from('live_investments').update(patch).eq('ticker', ticker);
    if (error) return error;
    await loadInvestments(ticker);
    return null;
  };

  const removeTicker = async (tickerToRemove) => {
    if (!roleCanDelete()) { window.alert(DEMO_BLOCK_MSG); return; }
    if (removing) return;
    setRemoving(tickerToRemove);

    const currentTicker = (investments || [])[idx]?.ticker;
    const client = window._supabaseClient;

    if (client) {
      const { data: { user } } = await client.auth.getUser();
      if (user) {
        await client.from('live_investments')
          .delete()
          .eq('ticker', tickerToRemove);
      }
    }

    const next = (investments || []).filter(i => i.ticker !== tickerToRemove);
    setInvestments(next);
    const newIdx = next.findIndex(i => i.ticker === currentTicker);
    setIdx(Math.max(0, newIdx === -1 ? 0 : newIdx));
    setRemoving(null);
  };

  // ── Loading state ──
  if (investments === null) {
    return (
      <div style={{ marginBottom: isMobile ? '40px' : '72px' }}>
        <div style={{ fontFamily: fontMono, fontSize: '11px', letterSpacing: '0.18em', color: tokens.inkMute, marginBottom: '24px' }}>
          01 · LIVE INVESTMENTS
        </div>
        <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, letterSpacing: '0.1em' }}>LOADING…</div>
      </div>
    );
  }

  const inv = investments[idx] || investments[0];

  return (
    <div style={{ marginBottom: isMobile ? '40px' : '72px' }}>
      <div onClick={toggleSec} style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: secCollapsed ? '0' : '24px', cursor: 'pointer', userSelect: 'none' }}>
        <Chevron collapsed={secCollapsed} onClick={(e) => { e.stopPropagation(); toggleSec(); }} size={13} />
        <span style={{ fontFamily: fontMono, fontSize: '11px', letterSpacing: '0.18em', color: tokens.inkMute }}>
          01 · LIVE INVESTMENTS
        </span>
        {secCollapsed && <span style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.inkMute, opacity: 0.6 }}>· {investments.length} TICKER{investments.length === 1 ? '' : 'S'}</span>}
      </div>

      {!secCollapsed && (<>

      {/* ── Ticker tabs ── */}
      <div style={{
        display: 'flex', alignItems: 'flex-end',
        borderBottom: `1px solid ${tokens.inkLine}`,
        marginBottom: showAdd ? '0' : '24px',
        flexWrap: 'wrap',
      }}>
        {investments.map((inv, i) => (
          <div key={inv.ticker} style={{ display: 'flex', alignItems: 'center', marginBottom: '-1px' }}>
            <button onClick={() => { setIdx(i); setShowAdd(false); }} style={{
              padding: '10px 10px 10px 0', background: 'none', border: 'none', cursor: 'pointer',
              fontFamily: fontMono, fontSize: '12px', letterSpacing: '0.1em',
              borderBottom: i === idx ? `2px solid ${inv.color}` : '2px solid transparent',
              color: i === idx ? tokens.ink : tokens.inkMute,
              fontWeight: i === idx ? 500 : 400,
              display: 'flex', alignItems: 'center', gap: '8px',
            }}>
              <span style={{
                width: '7px', height: '7px', borderRadius: '50%',
                background: inv.color, display: 'inline-block', flexShrink: 0,
              }} />
              {inv.ticker}
              <span style={{ fontSize: '10px', fontWeight: 400, opacity: 0.6 }}>{inv.name}</span>
            </button>
            {/* Remove button (hidden on the view-only demo) */}
            {roleCanDelete() && <button
              onClick={() => removeTicker(inv.ticker)}
              disabled={removing === inv.ticker}
              title={`Remove ${inv.ticker}`}
              style={{
                background: 'none', border: 'none', cursor: 'pointer',
                padding: '0 12px 0 2px', fontSize: '15px', lineHeight: 1,
                color: tokens.inkMute, opacity: 0.4,
                transition: 'opacity 0.15s',
              }}
              onMouseEnter={e => { e.currentTarget.style.opacity = '1'; }}
              onMouseLeave={e => { e.currentTarget.style.opacity = '0.4'; }}
            >×</button>}
          </div>
        ))}

        {/* Settings (edit current ticker) + Add ticker buttons — owner + live demo only */}
        {roleCanManageTickers() && <button
          onClick={() => { setShowSettings(s => !s); setShowAdd(false); }}
          title="Edit the current ticker (Bloomberg ticker, peers, …)"
          style={{
            marginLeft: 'auto', padding: '10px 0 10px 16px',
            background: 'none', border: 'none', cursor: 'pointer',
            fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em',
            color: showSettings ? tokens.ink : tokens.inkMute,
            borderBottom: '2px solid transparent', marginBottom: '-1px',
          }}>
          ⚙ EDIT
        </button>}
        {roleCanManageTickers() && <button
          onClick={() => { setShowAdd(s => !s); setShowSettings(false); }}
          style={{
            padding: '10px 0 10px 16px',
            background: 'none', border: 'none', cursor: 'pointer',
            fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em',
            color: showAdd ? tokens.ink : tokens.inkMute,
            borderBottom: '2px solid transparent', marginBottom: '-1px',
          }}>
          + ADD
        </button>}
      </div>

      {/* ── Add ticker form ── */}
      {showAdd && (
        <div style={{ marginTop: '16px' }}>
          <AddTickerForm onAdd={addTicker} onCancel={() => setShowAdd(false)} />
        </div>
      )}

      {/* ── Edit current ticker ── */}
      {showSettings && inv && (
        <TickerSettings inv={inv} onSave={(patch) => saveSettings(inv.ticker, patch)} onClose={() => setShowSettings(false)} />
      )}

      {/* ── Empty state ── */}
      {investments.length === 0 && !showAdd && (
        <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, letterSpacing: '0.1em', padding: '48px 0' }}>
          NO TICKERS — click + ADD to start tracking
        </div>
      )}

      {/* ── Chart + News ── */}
      {inv && (
        <>
          <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '3fr 1.4fr', gap: '16px', marginBottom: '16px' }}>
            {/* Chart */}
            <div style={{ border: `1px solid ${tokens.inkLine}`, background: tokens.paper }}>
              <div style={{ padding: '14px 20px', borderBottom: `1px solid ${tokens.inkLineSoft}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
                  <span style={{ width: '8px', height: '8px', borderRadius: '50%', background: inv.color, display: 'inline-block' }} />
                  <span style={{ fontSize: '15px', letterSpacing: '-0.02em' }}>{inv.ticker} · {inv.name}</span>
                </div>
                <span style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.1em', color: tokens.inkMute }}>{inv.exchange}</span>
              </div>
              <TradingViewChart investment={inv} height={isMobile ? 300 : 460} />
            </div>

            {/* News */}
            <div style={{ border: `1px solid ${tokens.inkLine}`, background: tokens.paper, padding: '20px' }}>
              <NewsIntelFeed investment={inv} />
            </div>
          </div>

          {/* ── Bloomberg Financial Summary ── */}
          <BloombergPanel investment={inv} key={'bbg-' + inv.ticker} />

          {/* ── Peer Comps — benchmark vs configured peer set ── */}
          <CompsPanel investment={inv} key={'comps-' + inv.ticker} />

          {/* ── Value-Creation Gap — activist upside math off the comps ── */}
          <ValueGapPanel investment={inv} key={'gap-' + inv.ticker} />

          {/* ── Management & Board intel — roster + psychographic profiles ── */}
          <PeoplePanel investment={inv} key={'people-' + inv.ticker} />

          {/* ── Ownership & Compensation — standardized proxy parse ── */}
          <OwnershipCompPanel investment={inv} key={'comp-' + inv.ticker} />

          {/* ── Network map — board/management overlaps + my relationships ── */}
          <NetworkPanel investment={inv} key={'net-' + inv.ticker} />

          {/* ── Shareholder base — passive/active mix, activists, 13F ── */}
          <HoldersPanel investment={inv} key={'hold-' + inv.ticker} />

          {/* ── Talent Signal — job-postings intelligence ── */}
          <JobsPanel investment={inv} key={'jobs-' + inv.ticker} />

          {/* ── Site watch — company-website alt-data diffs ── */}
          <SiteWatchPanel investment={inv} key={'site-' + inv.ticker} />
        </>
      )}

      </>)}
    </div>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// 03 · AI MARKET SIGNALS — live read on the AI economy (token prices, GPU rental
// rates, public-market beneficiaries by stack layer, the week's OpenAI/Anthropic
// releases) plus three philosophy-matched ideas. Powered by the ai-signals Edge
// Function (Claude + web search). Snapshot in `ai_signals`; ideas archived too.
// ─────────────────────────────────────────────────────────────────────────────
const LAYER_COLORS = {
  'Compute': '#0E0E0C', 'Power & Cooling': '#C9622E', 'Networking': '#1A4FB5',
  'Memory': '#7A2D8E', 'Foundry': '#0E7C3A', 'Hyperscaler': '#9A6A1A', 'Application/Software': '#A04A3E',
};
const _usd = (v, dp = 2) => v == null || isNaN(v) ? '—' : '$' + Number(v).toFixed(dp);

// Tiny inline sparkline for a numeric series (e.g. falling $/token over time).
const Sparkline = ({ points, color, height = 56 }) => {
  const vals = (points || []).map(Number).filter((v) => isFinite(v));
  if (vals.length < 2) return null;
  const W = 220, H = height, P = 6;
  const min = Math.min(...vals), max = Math.max(...vals), span = max - min || 1;
  const X = (i) => P + (i / (vals.length - 1)) * (W - 2 * P);
  const Y = (v) => P + (1 - (v - min) / span) * (H - 2 * P);
  const d = vals.map((v, i) => `${i ? 'L' : 'M'}${X(i).toFixed(1)} ${Y(v).toFixed(1)}`).join(' ');
  return (
    <svg viewBox={`0 0 ${W} ${H}`} style={{ width: '100%', height: 'auto', display: 'block' }}>
      <path d={d} fill="none" stroke={color || tokens.ink} strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" />
      <circle cx={X(0)} cy={Y(vals[0])} r="2.5" fill={tokens.inkMute} />
      <circle cx={X(vals.length - 1)} cy={Y(vals[vals.length - 1])} r="3" fill={color || tokens.ink} />
    </svg>
  );
};

const MiniTable = ({ head, rows }) => (
  <div style={{ overflowX: 'auto' }}>
    <table style={{ width: '100%', borderCollapse: 'collapse', minWidth: '440px' }}>
      <thead>
        <tr>{head.map((h, i) => (
          <th key={i} style={{ textAlign: i === 0 ? 'left' : 'right', fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.08em', color: tokens.inkMute, fontWeight: 400, padding: '4px 8px', borderBottom: `1px solid ${tokens.inkLine}` }}>{h}</th>
        ))}</tr>
      </thead>
      <tbody>
        {rows.map((r, i) => (
          <tr key={i}>{r.map((c, j) => (
            <td key={j} style={{ textAlign: j === 0 ? 'left' : 'right', fontFamily: j === 0 ? fontBody : fontMono, fontSize: j === 0 ? '12.5px' : '11.5px', color: j === 0 ? tokens.ink : tokens.inkSoft, padding: '6px 8px', borderBottom: `1px solid ${tokens.inkLineSoft}`, whiteSpace: 'nowrap' }}>{c}</td>
          ))}</tr>
        ))}
      </tbody>
    </table>
  </div>
);

const AISignalsPanel = () => {
  const isMobile = useIsMobile();
  const [snap, setSnap] = React.useState(undefined);
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState('');
  const pollRef = React.useRef(null);

  const load = React.useCallback(async () => {
    const client = window._supabaseClient;
    if (!client) { setSnap(null); return null; }
    const { data } = await client.from('ai_signals').select('data, fetched_at').eq('source', 'ai_market').maybeSingle();
    const d = (data && data.data) || null;
    setSnap(d);
    return d;
  }, []);
  // A run is "fresh" if it started within the last 8 min; an older 'running' row
  // is an orphan (the function died mid-scan) and must not lock the button.
  const isFreshRun = (d) => d && d.status === 'running' && (!d.started_at || Date.now() - new Date(d.started_at).getTime() < 8 * 60 * 1000);

  // On mount: load the snapshot; resume polling only if a fresh scan is running.
  React.useEffect(() => { load().then((d) => { if (isFreshRun(d)) setBusy(true); }); }, [load]);

  // Poll for the result whenever a run is in flight (`busy`). Keying off `busy`
  // (not the snapshot's status) avoids the race where the first poll fires before
  // the function has written its initial "running" row — which previously left
  // the panel stuck on RUNNING forever.
  React.useEffect(() => {
    if (!busy || pollRef.current) return;
    let tries = 0;
    pollRef.current = setInterval(async () => {
      const d = await load();
      const settled = d && (d.status === 'done' || d.status === 'error');
      if (settled || ++tries > 70) {   // ~7 min cap
        clearInterval(pollRef.current); pollRef.current = null; setBusy(false);
      }
    }, 6000);
    return () => { if (pollRef.current) { clearInterval(pollRef.current); pollRef.current = null; } };
  }, [busy, load]);

  const run = async () => {
    if (!roleCanResearch()) { setErr(DEMO_BLOCK_MSG); return; }
    const client = window._supabaseClient;
    if (!client) { setErr('No backend connected.'); return; }
    setBusy(true); setErr('');
    try {
      const { data, error } = await client.functions.invoke('ai-signals', { body: {} });
      let payload = data;
      if (error) { try { payload = await error.context.json(); } catch {} if (!payload || !payload.error) throw new Error(error.message || 'Edge function error'); }
      if (payload && payload.error) throw new Error(payload.error);
      await load(); // the `busy` poller takes over until the scan settles
    } catch (e) { setErr(e.message); setBusy(false); }
  };

  if (snap === undefined) return null;
  const running = busy || isFreshRun(snap);
  const staleRun = snap && snap.status === 'running' && !running;  // orphaned scan
  const ideas = (snap && snap.ideas) || [];
  const tokenRows = (snap && snap.token_prices) || [];
  const gpuRows = (snap && snap.gpu_rental) || [];
  const benef = (snap && snap.beneficiaries) || [];
  const releases = (snap && snap.releases) || [];

  return (
    <IntelShell collapseKey="aisignals" title="AI MARKET SIGNALS · THE AI ECONOMY"
      right={snap && snap.fetched_at ? `AS OF ${new Date(snap.fetched_at).toLocaleDateString()}` : ''}
      onAction={run} actionLabel={running ? null : (snap ? '↻ RE-SCAN' : '⚡ RUN SCAN')} running={running}>
      <div style={{ padding: '16px 20px' }}>
        {err && <div style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.red, marginBottom: '10px' }}>{err}</div>}

        {running ? (
          <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, letterSpacing: '0.08em', padding: '8px 0', lineHeight: 1.7 }}>
            SCANNING THE AI ECONOMY — web searches + analysis run for a couple of minutes…
            <div style={{ marginTop: '6px', opacity: 0.7 }}>this keeps updating automatically; you can leave the page and come back.</div>
          </div>
        ) : !snap ? (
          <div>
            <div style={{ fontSize: '13px', color: tokens.inkSoft, lineHeight: 1.6, marginBottom: '8px' }}>No AI signal scan yet.</div>
            <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, lineHeight: 1.8 }}>
              Click <span style={{ color: tokens.ink }}>⚡ RUN SCAN</span> — the <span style={{ color: tokens.ink }}>ai-signals</span> function uses Claude + web search to pull current token prices, GPU rental rates, the week's OpenAI/Anthropic releases, the public-market beneficiaries, and three philosophy-matched ideas. See <span style={{ color: tokens.ink }}>AI-SIGNALS-SETUP.md</span>.
            </div>
          </div>
        ) : snap.status === 'error' ? (
          <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.red }}>Scan failed: {snap.error}</div>
        ) : staleRun ? (
          <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute, lineHeight: 1.7 }}>
            The previous scan didn't finish (it may have timed out). Click <span style={{ color: tokens.ink }}>↻ RE-SCAN</span> to try again — if it keeps stalling, check the <span style={{ color: tokens.ink }}>ai-signals</span> function logs in the Supabase dashboard.
          </div>
        ) : (
          <React.Fragment>
            <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: '20px' }}>
              {/* Token prices */}
              <div>
                <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '6px' }}>FRONTIER TOKEN PRICES · $/M TOKENS</div>
                <MiniTable head={['MODEL', 'IN', 'OUT']} rows={tokenRows.map((t) => [
                  <span>{t.model}{t.note ? <span style={{ color: tokens.inkMute, fontSize: '10px' }}> · {t.note}</span> : ''}</span>,
                  _usd(t.in_per_mtok), _usd(t.out_per_mtok),
                ])} />
                {snap.token_trend && <div style={{ fontSize: '12px', color: tokens.inkSoft, lineHeight: 1.5, marginTop: '8px', fontStyle: 'italic' }}>{snap.token_trend}</div>}
              </div>
              {/* GPU rental */}
              <div>
                <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '6px' }}>GPU RENTAL · $/HR ON-DEMAND</div>
                <MiniTable head={['ACCELERATOR', 'LOW', 'HIGH']} rows={gpuRows.map((g) => [
                  <span>{g.accelerator}{g.providers ? <span style={{ color: tokens.inkMute, fontSize: '10px' }}> · {g.providers}</span> : ''}</span>,
                  _usd(g.low_per_hr), _usd(g.high_per_hr),
                ])} />
                {snap.gpu_trend && <div style={{ fontSize: '12px', color: tokens.inkSoft, lineHeight: 1.5, marginTop: '8px', fontStyle: 'italic' }}>{snap.gpu_trend}</div>}
              </div>
            </div>

            {/* Beneficiaries by layer */}
            {benef.length > 0 && (
              <div style={{ marginTop: '22px' }}>
                <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '10px' }}>PUBLIC-MARKET BENEFICIARIES · BY STACK LAYER</div>
                {Object.entries(benef.reduce((acc, b) => { (acc[b.layer] = acc[b.layer] || []).push(b); return acc; }, {})).map(([layer, items]) => (
                  <div key={layer} style={{ marginBottom: '12px' }}>
                    <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.1em', color: LAYER_COLORS[layer] || tokens.inkMute, marginBottom: '5px' }}>
                      ● {layer.toUpperCase()}
                    </div>
                    <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
                      {items.map((b, i) => (
                        <span key={i} title={b.thesis}
                          style={{ fontFamily: fontMono, fontSize: '10px', padding: '4px 9px', border: `1px solid ${tokens.inkLine}`, background: tokens.bg, color: tokens.ink, cursor: 'help' }}>
                          {b.ticker ? <strong>{b.ticker}</strong> : b.name}{b.ticker && b.name ? <span style={{ color: tokens.inkMute }}> · {b.name}</span> : ''}
                          {b.mcap_bucket === 'mid' && <span style={{ color: tokens.green, marginLeft: '5px' }}>mid</span>}
                        </span>
                      ))}
                    </div>
                  </div>
                ))}
                <div style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, marginTop: '4px' }}>hover a chip for the one-line thesis · <span style={{ color: tokens.green }}>mid</span> = mid-cap (our sweet spot)</div>
              </div>
            )}

            {/* Releases */}
            {releases.length > 0 && (
              <div style={{ marginTop: '22px' }}>
                <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '10px' }}>THIS WEEK · OPENAI &amp; ANTHROPIC RELEASES</div>
                {releases.map((r, i) => (
                  <div key={i} style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '92px 86px 1fr', gap: '8px', padding: '8px 0', borderBottom: `1px solid ${tokens.inkLineSoft}`, alignItems: 'baseline' }}>
                    <span style={{ fontFamily: fontMono, fontSize: '10px', color: r.vendor === 'Anthropic' ? '#C9622E' : '#0E7C3A' }}>{r.vendor}</span>
                    <span style={{ fontFamily: fontMono, fontSize: '10px', color: tokens.inkMute }}>{r.date}</span>
                    <span style={{ fontSize: '13px', color: tokens.ink, lineHeight: 1.45 }}>
                      <strong>{r.title}</strong> — {r.what}
                      {r.why_it_matters && <span style={{ color: tokens.inkSoft, fontStyle: 'italic' }}> {r.why_it_matters}</span>}
                    </span>
                  </div>
                ))}
              </div>
            )}

            {/* Inference-cost deflation tracker */}
            {snap.cost_deflation && (snap.cost_deflation.series || []).length > 0 && (
              <div style={{ marginTop: '22px' }}>
                <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '8px' }}>
                  INFERENCE-COST DEFLATION · GPT-4-CLASS $/M TOKENS
                  {snap.cost_deflation.annualized_decline_pct != null && (
                    <span style={{ color: tokens.green, marginLeft: '10px' }}>▼ {Math.abs(snap.cost_deflation.annualized_decline_pct)}%/yr</span>
                  )}
                </div>
                <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1.3fr 1fr', gap: '16px', alignItems: 'center' }}>
                  <MiniTable head={['PERIOD', 'IN', 'OUT']} rows={(snap.cost_deflation.series || []).map((s) => [
                    <span>{s.period}{s.note ? <span style={{ color: tokens.inkMute, fontSize: '10px' }}> · {s.note}</span> : ''}</span>,
                    _usd(s.in_per_mtok), _usd(s.out_per_mtok),
                  ])} />
                  <Sparkline points={(snap.cost_deflation.series || []).map((s) => s.out_per_mtok)} color="#0E7C3A" />
                </div>
                {snap.cost_deflation.read_through && <div style={{ fontSize: '12.5px', color: tokens.inkSoft, lineHeight: 1.55, marginTop: '10px', fontStyle: 'italic' }}>{snap.cost_deflation.read_through}</div>}
              </div>
            )}

            {/* Capex vs. depreciation watch */}
            {snap.capex_depreciation && ((snap.capex_depreciation.hyperscalers || []).length > 0 || (snap.capex_depreciation.arms_dealers || []).length > 0) && (
              <div style={{ marginTop: '22px' }}>
                <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '8px' }}>CAPEX vs. DEPRECIATION · USEFUL-LIFE RISK + ARMS-DEALERS</div>
                {(snap.capex_depreciation.hyperscalers || []).length > 0 && (
                  <MiniTable head={['HYPERSCALER', 'AI CAPEX', 'USEFUL LIFE', 'FLAG']} rows={(snap.capex_depreciation.hyperscalers || []).map((h) => {
                    const fc = h.depreciation_flag === 'stretched' ? tokens.red : h.depreciation_flag === 'conservative' ? tokens.green : tokens.inkMute;
                    return [
                      <span>{h.name}{h.ticker ? <span style={{ color: tokens.inkMute, fontSize: '10px' }}> · {h.ticker}</span> : ''}</span>,
                      h.ai_capex_annual || '—',
                      h.useful_life_years ? `${h.useful_life_years}y` : '—',
                      <span style={{ color: fc }}>{(h.depreciation_flag || '—').toUpperCase()}</span>,
                    ];
                  })} />
                )}
                {(snap.capex_depreciation.arms_dealers || []).length > 0 && (
                  <div style={{ marginTop: '12px' }}>
                    <div style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.1em', color: tokens.inkMute, marginBottom: '6px' }}>ARMS-DEALERS · POWER / COOLING / OPTICS / NETWORKING</div>
                    <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px' }}>
                      {(snap.capex_depreciation.arms_dealers || []).map((a, i) => (
                        <span key={i} title={a.thesis}
                          style={{ fontFamily: fontMono, fontSize: '10px', padding: '4px 9px', border: `1px solid ${tokens.inkLine}`, background: tokens.bg, color: tokens.ink, cursor: 'help' }}>
                          {a.ticker ? <strong>{a.ticker}</strong> : a.name}{a.ticker && a.name ? <span style={{ color: tokens.inkMute }}> · {a.name}</span> : ''}
                          {a.layer && <span style={{ color: LAYER_COLORS[a.layer] || tokens.inkMute, marginLeft: '5px' }}>{a.layer}</span>}
                          {a.mcap_bucket === 'mid' && <span style={{ color: tokens.green, marginLeft: '5px' }}>mid</span>}
                        </span>
                      ))}
                    </div>
                  </div>
                )}
                {snap.capex_depreciation.read_through && <div style={{ fontSize: '12.5px', color: tokens.inkSoft, lineHeight: 1.55, marginTop: '10px', fontStyle: 'italic' }}>{snap.capex_depreciation.read_through}</div>}
              </div>
            )}

            {/* Token-demand elasticity */}
            {snap.token_elasticity && (snap.token_elasticity.cases || []).length > 0 && (
              <div style={{ marginTop: '22px' }}>
                <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '8px' }}>TOKEN-DEMAND ELASTICITY · WHERE CHEAPER TOKENS RE-RATE A NAME</div>
                {(snap.token_elasticity.cases || []).map((c, i) => {
                  const ec = c.elasticity_read === 'high' ? tokens.green : c.elasticity_read === 'medium' ? tokens.ochre : tokens.inkMute;
                  return (
                    <div key={i} style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '150px 70px 1fr', gap: '10px', padding: '8px 0', borderBottom: `1px solid ${tokens.inkLineSoft}`, alignItems: 'baseline' }}>
                      <span style={{ fontSize: '13px' }}>{c.ticker ? <strong style={{ fontFamily: fontMono, fontSize: '11px' }}>{c.ticker}</strong> : ''} {c.name}</span>
                      <span style={{ fontFamily: fontMono, fontSize: '9px', letterSpacing: '0.06em', color: ec }}>{(c.elasticity_read || '—').toUpperCase()}</span>
                      <span style={{ fontSize: '12.5px', color: tokens.inkSoft, lineHeight: 1.45 }}>{c.thesis}</span>
                    </div>
                  );
                })}
                {snap.token_elasticity.read_through && <div style={{ fontSize: '12.5px', color: tokens.inkSoft, lineHeight: 1.55, marginTop: '10px', fontStyle: 'italic' }}>{snap.token_elasticity.read_through}</div>}
              </div>
            )}

            {/* Top-3 ideas */}
            <div style={{ marginTop: '24px' }}>
              <div style={{ fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.14em', color: tokens.ink, marginBottom: '10px' }}>TOP-3 AI-ECONOMY IDEAS · CLAUDE × FINCH BAY PHILOSOPHY</div>
              {ideas.length === 0 ? (
                <div style={{ fontFamily: fontMono, fontSize: '11px', color: tokens.inkMute }}>No ideas in this scan.</div>
              ) : (
                <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
                  {ideas.map((idea, i) => <IdeaCard key={i} idea={idea} rank={i} />)}
                </div>
              )}
            </div>

            <div style={{ fontFamily: fontMono, fontSize: '9px', color: tokens.inkMute, letterSpacing: '0.04em', lineHeight: 1.6, marginTop: '14px' }}>
              Live read via web search — verify prices before acting. Ideas are a starting point and are saved to your Idea Database. {snap.as_of ? `Data as-of ${snap.as_of}.` : ''}
            </div>
          </React.Fragment>
        )}
      </div>
    </IntelShell>
  );
};

const AIMarketSignals = () => {
  const isMobile = useIsMobile();
  const [collapsed, toggle] = useCollapsed('section.aimarket');
  return (
    <div style={{ marginBottom: isMobile ? '40px' : '72px' }}>
      <div onClick={toggle} style={{ display: 'flex', alignItems: 'center', gap: '6px', marginBottom: collapsed ? '0' : '8px', cursor: 'pointer', userSelect: 'none' }}>
        <Chevron collapsed={collapsed} onClick={(e) => { e.stopPropagation(); toggle(); }} size={13} />
        <span style={{ fontFamily: fontMono, fontSize: '11px', letterSpacing: '0.18em', color: tokens.inkMute }}>
          03 · AI MARKET SIGNALS
        </span>
      </div>
      {!collapsed && <AISignalsPanel />}
    </div>
  );
};

// ─────────────────────────────────────────────────────────────────────────────
// Page root
// ─────────────────────────────────────────────────────────────────────────────
const ForMe = ({ user }) => (
  <div>
    <Section
      label="PRIVATE · FOR ME"
      title="Live investments."
      sub="Per-ticker market intelligence: AI-ranked news, Bloomberg financials, and talent-signal hiring analysis. Visible only while you're signed in."
    >
      <LiveInvestments />

      <MarketSignals />

      <AIMarketSignals />

      <div style={{ marginTop: '48px', fontFamily: fontMono, fontSize: '10px', letterSpacing: '0.08em', color: tokens.inkMute, lineHeight: 1.8 }}>
        SIGNED IN AS {user && user.email ? user.email.toUpperCase() : 'OWNER'} ·
        USE + ADD / × TO MANAGE TICKERS ·
        SEE <span style={{ color: tokens.ink }}>INVESTMENT-INTEL-SETUP.md</span> &amp; <span style={{ color: tokens.ink }}>JOBS-INTEL-SETUP.md</span> FOR EDGE-FUNCTION SETUP
      </div>
    </Section>
  </div>
);

window.ForMe = ForMe;
