/* App shell — sidebar nav, header, screen routing, tweaks */
const { useState, useCallback, useEffect, useRef } = React;

/* ── Content language (內文 EN ↔ 中文) ──
   A lightweight global toggle shared by the editor-only Notes + Discussion
   pages. Persisted in localStorage; changes broadcast via a window event so
   every mounted page re-renders. Only switches AUTHORED prose — live tweet
   text and weekly LLM captions stay in their original language. */
window.__contentLang = window.__contentLang || (function () {
  try { return localStorage.getItem('xw-content-lang') || 'en'; } catch (e) { return 'en'; }
})();
function setContentLang(l) {
  window.__contentLang = l;
  try { localStorage.setItem('xw-content-lang', l); } catch (e) {}
  window.dispatchEvent(new Event('xw-content-lang-change'));
}
function useContentLang() {
  var [lang, setLang] = useState(window.__contentLang || 'en');
  useEffect(function () {
    function onChange() { setLang(window.__contentLang || 'en'); }
    window.addEventListener('xw-content-lang-change', onChange);
    return function () { window.removeEventListener('xw-content-lang-change', onChange); };
  }, []);
  return lang;
}
/* Segmented EN | 中 toggle, matches the scatter toolbar pill style. */
function LangToggle() {
  var lang = useContentLang();
  return (
    <div style={{ display: 'inline-flex', background: 'var(--bg-card)', borderRadius: 10, border: '1px solid var(--border-default)', overflow: 'hidden', flexShrink: 0 }}>
      {[{ v: 'en', l: 'EN' }, { v: 'zh', l: '中' }].map(function (o) {
        var active = lang === o.v;
        return <button key={o.v} onClick={function () { setContentLang(o.v); }}
          title={o.v === 'zh' ? '切換內文為中文' : 'Switch content to English'}
          style={{ padding: '4px 12px', border: 'none', fontSize: 12, fontWeight: active ? 600 : 400, background: active ? 'var(--bg-elevated)' : 'transparent', color: active ? 'var(--text-primary)' : 'var(--text-tertiary)', cursor: 'pointer', fontFamily: 'var(--sans)', transition: 'all .12s' }}>{o.l}</button>;
      })}
    </div>
  );
}
window.useContentLang = useContentLang;
window.LangToggle = LangToggle;

/* ── Hash routing ──
   Each screen (and its sub-view) gets its own URL so visitors can deep-link,
   bookmark, and use browser back/forward as prev/next. Format: #screen/sub,
   e.g. #overview, #activity/timeline, #directory/methodology. */
var SUBVIEWS = {
  directory: { valid: ['list', 'compare', 'scatter', 'diet', 'methodology'], def: 'list' },
  activity: { valid: ['timeline', 'topics', 'frequency', 'heatmap'], def: 'timeline' },
  attention: { valid: ['follows'], def: '' },
};

function isScreenAllowed(s) {
  if (s === 'overview' || s === 'directory' || s === 'network' || s === 'attention' || s === 'activity') return true;
  if ((s === 'editor' || s === 'discussion') && window.IS_EDITOR_MODE) return true;
  return false;
}

function parseHash() {
  var raw = (location.hash || '').replace(/^#\/?/, '');
  var parts = raw.split('/').filter(Boolean);
  return { screen: parts[0] || '', sub: parts[1] || '' };
}

function subFor(screen, dirView, actView, attnView) {
  if (screen === 'directory') return dirView;
  if (screen === 'activity') return actView;
  if (screen === 'attention') return attnView;
  return '';
}

/* ── SVG Icons ── */
function IconOverview() { return <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><rect x="2" y="2" width="6" height="6" rx="1"/><rect x="10" y="2" width="6" height="3" rx="1"/><rect x="10" y="7" width="6" height="7" rx="1"/><rect x="2" y="10" width="6" height="4" rx="1"/></svg>; }
function IconDirectory() { return <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><line x1="3" y1="4.5" x2="15" y2="4.5"/><line x1="3" y1="9" x2="15" y2="9"/><line x1="3" y1="13.5" x2="15" y2="13.5"/><circle cx="5" cy="4.5" r="1" fill="currentColor" stroke="none"/><circle cx="5" cy="9" r="1" fill="currentColor" stroke="none"/><circle cx="5" cy="13.5" r="1" fill="currentColor" stroke="none"/></svg>; }
function IconNetwork() { return <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5"><circle cx="9" cy="4" r="2"/><circle cx="4" cy="14" r="2"/><circle cx="14" cy="14" r="2"/><line x1="9" y1="6" x2="5" y2="12"/><line x1="9" y1="6" x2="13" y2="12"/><line x1="6" y1="14" x2="12" y2="14"/></svg>; }
function IconAttention() { return <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5"><circle cx="9" cy="9" r="3"/><path d="M3 9c2-3 10-3 12 0"/><path d="M5 9c1.5-1.8 6.5-1.8 8 0"/></svg>; }
function IconDigest() { return <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"><rect x="3" y="2" width="12" height="14" rx="1.5"/><line x1="6" y1="6" x2="12" y2="6"/><line x1="6" y1="9" x2="12" y2="9"/><line x1="6" y1="12" x2="9" y2="12"/></svg>; }
function IconActivity() { return <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><polyline points="2,13 5,7 8,10 11,4 14,9 16,6"/></svg>; }
function IconNotes() { return <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 4.5 L3 14 a1 1 0 0 0 1 1 L14 15.5 a1 1 0 0 0 1-1 L15 5 a1 1 0 0 0 -1 -1 L8 4 L7 2.5 L4 2.5 a1 1 0 0 0 -1 1 z"/><line x1="6" y1="9" x2="12" y2="9"/><line x1="6" y1="12" x2="11" y2="12"/></svg>; }
function IconDiscussion() { return <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 4 L13 4 a1.5 1.5 0 0 1 1.5 1.5 L14.5 10.5 a1.5 1.5 0 0 1 -1.5 1.5 L7 12 L4 14.5 L4 12 a1.5 1.5 0 0 1 -1.5 -1.5 L2.5 5.5 a1.5 1.5 0 0 1 1.5 -1.5 z"/><line x1="6" y1="7" x2="11" y2="7"/><line x1="6" y1="9.5" x2="9" y2="9.5"/></svg>; }

/* ── Nav Item ── */
function NavItem({ icon, label, active, disabled, onClick }) {
  var [hov, setHov] = useState(false);
  return (
    <div onClick={disabled ? undefined : onClick}
      onMouseEnter={function () { setHov(true); }} onMouseLeave={function () { setHov(false); }}
      style={{
        display: 'flex', alignItems: 'center', gap: 10,
        padding: '10px 16px', borderRadius: 10, marginBottom: 2,
        cursor: disabled ? 'default' : 'pointer',
        background: active ? 'var(--bg-elevated)' : hov && !disabled ? 'var(--bg-hover)' : 'transparent',
        color: disabled ? 'var(--text-disabled)' : active ? 'var(--text-primary)' : 'var(--text-secondary)',
        transition: 'all .12s', fontSize: 13, fontWeight: active ? 600 : 400,
        opacity: disabled ? 0.5 : 1,
        position: 'relative',
      }}>
      {active && <div style={{ position: 'absolute', left: 6, top: '50%', transform: 'translateY(-50%)', width: 3, height: 16, borderRadius: 2, background: 'var(--accent)' }}></div>}
      {icon}
      <span>{label}</span>
      {disabled && <span style={{ fontSize: 10, color: 'var(--text-disabled)', marginLeft: 'auto', fontFamily: 'var(--mono)' }}>Soon</span>}
    </div>
  );
}

/* ── Header Sub-Tab ── */
function HeaderTab({ label, active, onClick }) {
  var [hov, setHov] = useState(false);
  return (
    <button onClick={onClick} onMouseEnter={function () { setHov(true); }} onMouseLeave={function () { setHov(false); }}
      style={{ padding: '5px 14px', borderRadius: 10, border: 'none', fontSize: 12, fontWeight: active ? 600 : 400, color: active ? 'var(--text-primary)' : 'var(--text-tertiary)', background: active ? 'var(--bg-elevated)' : hov ? 'var(--bg-hover)' : 'transparent', cursor: 'pointer', fontFamily: 'var(--sans)', transition: 'all .12s' }}>{label}</button>
  );
}

/* ── Tweak Defaults ── */
var TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "density": "comfortable",
  "accentScheme": "emerald-amber-rose",
  "showExternalHighlight": true,
  "emptyBioStyle": "dimmed"
}/*EDITMODE-END*/;

/* ── Error boundary ──
   The frontend is transformed in-browser by @babel/standalone, so a runtime
   throw inside any screen would otherwise blank the whole panel ("black
   screen") with no hint of what failed. This catches it, shows the error +
   stack, and — because we key it by route in App — clears itself when the
   user navigates to another tab. */
class ScreenErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { error: null }; }
  static getDerivedStateFromError(error) { return { error: error }; }
  componentDidCatch(error, info) {
    if (window.console) console.error('[ScreenErrorBoundary]', error, info && info.componentStack);
  }
  render() {
    if (!this.state.error) return this.props.children;
    var e = this.state.error;
    return (
      <div style={{ height: '100%', overflowY: 'auto', padding: 32, background: 'var(--bg-base)', fontFamily: 'var(--mono)' }}>
        <div style={{ color: 'var(--rose)', fontSize: 14, fontWeight: 600, marginBottom: 10 }}>
          This view hit a runtime error.
        </div>
        <div style={{ color: 'var(--text-secondary)', fontSize: 12, marginBottom: 14 }}>
          The rest of the app still works — switch tabs in the sidebar, or reload. Details below.
        </div>
        <pre style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', fontSize: 11, lineHeight: 1.5, color: 'var(--text-tertiary)', background: 'var(--bg-card)', border: '1px solid var(--border-subtle)', borderRadius: 8, padding: '12px 14px', margin: 0 }}>
          {(e && e.message ? e.message : String(e)) + (e && e.stack ? '\n\n' + e.stack : '')}
        </pre>
      </div>
    );
  }
}

/* Renders a children-producing function as a *descendant* of the boundary above.
   Why: an error boundary cannot catch an "Element type is invalid" thrown when
   one of its OWN direct children is undefined (e.g. a view whose <script> 404'd) —
   that fiber is created during the boundary's own reconciliation, which a
   boundary can't catch, so the throw escapes to <App> and blacks out the page.
   Building the element tree inside SafeRender moves that creation one level down,
   so the boundary catches it and shows the inline error panel instead. */
function SafeRender(props) { return props.render(); }

/* ══════════ APP ══════════ */
function App() {
  /* A hash in the URL (#activity/timeline) wins — that's an explicit deep-link.
     Otherwise fall back to the editor jump (?editor=1 this page-load) or overview. */
  var fromHash = parseHash();
  var initialScreen = (function () {
    if (isScreenAllowed(fromHash.screen)) return fromHash.screen;
    if (!window.IS_EDITOR_MODE) return 'overview';
    try {
      if (new URLSearchParams(location.search).get('editor') === '1') return 'editor';
    } catch (e) { /* old browser without URLSearchParams — fall through */ }
    return 'overview';
  })();
  var initialSub = function (key) {
    var cfg = SUBVIEWS[key];
    return (fromHash.screen === key && cfg.valid.indexOf(fromHash.sub) >= 0) ? fromHash.sub : cfg.def;
  };
  var [screen, setScreen] = useState(initialScreen);
  var [dirFilter, setDirFilter] = useState(null);
  /* Filter for Activity > Timeline, set when the user clicks a chip on the
     Topics tab. Cleared when navigating away from activity. */
  var [tlTopicFilter, setTlTopicFilter] = useState(null);
  var [tlHandleFilter, setTlHandleFilter] = useState(null);
  var [dirView, setDirView] = useState(initialSub('directory'));
  var [actView, setActView] = useState(initialSub('activity'));
  var [attnView, setAttnView] = useState(initialSub('attention'));

  /* Write state → URL hash. On the very first run, replace (don't push) so we
     don't trap the user behind a phantom history entry on landing. */
  var firstSync = useRef(true);
  useEffect(function () {
    var sub = subFor(screen, dirView, actView, attnView);
    var hash = '#' + screen + (sub ? '/' + sub : '');
    if (location.hash === hash) { firstSync.current = false; return; }
    if (firstSync.current && history.replaceState) {
      history.replaceState(null, '', hash);
      firstSync.current = false;
    } else {
      location.hash = hash;
    }
  }, [screen, dirView, actView, attnView]);

  /* Read URL hash → state, for browser back/forward and pasted links. Setting
     state to the same value is a no-op, so this can't loop with the writer. */
  useEffect(function () {
    function onHash() {
      var h = parseHash();
      var s = isScreenAllowed(h.screen) ? h.screen : 'overview';
      setScreen(s);
      if (SUBVIEWS[s]) {
        var sub = SUBVIEWS[s].valid.indexOf(h.sub) >= 0 ? h.sub : SUBVIEWS[s].def;
        if (s === 'directory') setDirView(sub);
        if (s === 'activity') setActView(sub);
        if (s === 'attention') setAttnView(sub);
      }
    }
    window.addEventListener('hashchange', onHash);
    return function () { window.removeEventListener('hashchange', onHash); };
  }, []);
  var [methPerson, setMethPerson] = useState(null);
  var [t, setTweak] = useTweaks(TWEAK_DEFAULTS);

  /* ── Mobile sidebar drawer ──
     On narrow viewports (≤768px) the sidebar is hidden off-canvas and opened
     via a hamburger in the header; on wide viewports it stays always-visible.
     `isMobile` is driven by matchMedia so inline styles can react to the
     breakpoint without a CSS class system. */
  var [isMobile, setIsMobile] = useState(function () {
    return typeof window !== 'undefined' && window.matchMedia
      ? window.matchMedia('(max-width: 768px)').matches : false;
  });
  var [navOpen, setNavOpen] = useState(false);
  useEffect(function () {
    if (!window.matchMedia) return;
    var mq = window.matchMedia('(max-width: 768px)');
    function onChange(e) { setIsMobile(e.matches); if (!e.matches) setNavOpen(false); }
    if (mq.addEventListener) mq.addEventListener('change', onChange);
    else mq.addListener(onChange);
    return function () {
      if (mq.removeEventListener) mq.removeEventListener('change', onChange);
      else mq.removeListener(onChange);
    };
  }, []);

  var navigateTo = useCallback(function (target, opts) {
    setNavOpen(false);
    setScreen(target);
    if (opts && opts.filter) setDirFilter(opts.filter);
    else setDirFilter(null);
    if (target === 'directory' && opts && opts.view) setDirView(opts.view);
    if (target === 'activity' && opts && opts.view) setActView(opts.view);
    /* Cross-tab filter handoff: Topics tab passes {topic} or {handle}, we set
       activity > timeline as the destination and stash the filter for it to
       pick up. */
    if (target === 'activity') {
      setTlTopicFilter(opts && opts.topic ? opts.topic : null);
      setTlHandleFilter(opts && opts.handle ? opts.handle : null);
    }
  }, []);

  /* Density mapping */
  var rowH = t.density === 'compact' ? 44 : t.density === 'spacious' ? 60 : 52;

  /* Format last updated */
  var lastUp = new Date(window.COHORT.lastUpdated);
  var fmtDate = lastUp.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', timeZone: 'Asia/Taipei' });
  var fmtTime = lastUp.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', timeZone: 'Asia/Taipei', timeZoneName: 'short' });

  return (
    <div style={{ display: 'flex', height: '100vh', background: 'var(--bg-base)', color: 'var(--text-primary)', fontFamily: 'var(--sans)', overflow: 'hidden' }}>

      {/* ── Backdrop (mobile only, when drawer open) ── */}
      {isMobile && navOpen && (
        <div onClick={function () { setNavOpen(false); }}
          style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.6)', zIndex: 90 }}></div>
      )}

      {/* ── Sidebar ──
           Desktop: a normal flex column, always visible.
           Mobile: a fixed off-canvas drawer slid in/out via transform. */}
      <div style={isMobile
        ? { width: 248, maxWidth: '82vw', background: 'var(--bg-surface)', borderRight: '1px solid var(--border-subtle)', display: 'flex', flexDirection: 'column', padding: '20px 16px', position: 'fixed', top: 0, left: 0, bottom: 0, zIndex: 100, transform: navOpen ? 'translateX(0)' : 'translateX(-100%)', transition: 'transform .22s ease', boxShadow: navOpen ? '2px 0 24px rgba(0,0,0,0.5)' : 'none' }
        : { width: 224, flexShrink: 0, background: 'var(--bg-surface)', borderRight: '1px solid var(--border-subtle)', display: 'flex', flexDirection: 'column', padding: '20px 16px' }}>
        {/* ─ Logo area — xAI-inspired geometric X.
             Switched from the old solid double-slab to a precision twin-bar mark:
             tall (4:5 aspect), sharp angular tips, a tiny clear gap where the
             two slabs cross. The right slab is half-tone (0.55 opacity) to
             create the layered, "structural" feel xAI uses, instead of a
             flat mass. Wordmark sits on a tightened track for the same
             precision-minimalist read. */}
        <div style={{ padding: '8px 14px 28px', display: 'flex', alignItems: 'center', gap: 9 }}>
          <svg viewBox="0 0 22 28" aria-label="xWatchlist" role="img"
            style={{ width: 18, height: 23, display: 'block', flexShrink: 0 }}>
            <defs>
              <clipPath id="xw-mid-gap">
                {/* Carves a thin diagonal sliver in the middle so the two slabs
                    *don't* visually merge into a solid mass. */}
                <polygon points="9.5,11.5 12.5,11.5 12.5,16.5 9.5,16.5" />
              </clipPath>
              <mask id="xw-mid-gap-mask">
                <rect x="0" y="0" width="22" height="28" fill="white" />
                <rect x="9.5" y="12.4" width="3" height="3.2" fill="black" transform="rotate(-12 11 14)" />
              </mask>
            </defs>
            {/* Back slab (top-left → bottom-right) — full white */}
            <path d="M1 1 L7 1 L21 27 L15 27 Z" fill="var(--text-primary)" mask="url(#xw-mid-gap-mask)" />
            {/* Front slab (top-right → bottom-left) — half-tone for layered depth */}
            <path d="M15 1 L21 1 L7 27 L1 27 Z" fill="var(--text-primary)" opacity="0.55" mask="url(#xw-mid-gap-mask)" />
          </svg>
          <span style={{
            fontSize: 17, fontWeight: 600,
            color: 'var(--text-primary)',
            letterSpacing: '-0.035em',
            lineHeight: 1,
            fontFamily: 'var(--sans)',
          }}>
            Watchlist
          </span>
        </div>

        <nav>
          <NavItem icon={<IconOverview />} label="Overview" active={screen === 'overview'} onClick={function () { navigateTo('overview'); }} />
          <NavItem icon={<IconDirectory />} label="Directory" active={screen === 'directory'} onClick={function () { navigateTo('directory'); }} />
          <NavItem icon={<IconNetwork />} label="Network" active={screen === 'network'} onClick={function () { navigateTo('network'); }} />
          <NavItem icon={<IconAttention />} label="Attention" active={screen === 'attention'} onClick={function () { navigateTo('attention'); }} />
          <NavItem icon={<IconActivity />} label="Activity" active={screen === 'activity'} onClick={function () { navigateTo('activity'); }} />
          <NavItem icon={<IconDigest />} label="Digest" disabled />
          {window.IS_EDITOR_MODE && (
            <NavItem icon={<IconNotes />} label="Notes" active={screen === 'editor'} onClick={function () { navigateTo('editor'); }} />
          )}
          {window.IS_EDITOR_MODE && (
            <NavItem icon={<IconDiscussion />} label="Discussion" active={screen === 'discussion'} onClick={function () { navigateTo('discussion'); }} />
          )}
        </nav>

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

        {/* Cohort info */}
        <div style={{ padding: '14px 16px', borderTop: '1px solid var(--border-subtle)', marginTop: 8 }}>
          <div style={{ fontSize: 10, color: 'var(--text-tertiary)', marginBottom: 6, letterSpacing: '0.08em', fontWeight: 600 }}>ACTIVE COHORT</div>
          <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text-primary)' }}>{window.COHORT.name}</div>
          <div style={{ fontSize: 11, color: 'var(--text-tertiary)', fontFamily: 'var(--mono)', fontFeatureSettings: "'tnum' 1", marginTop: 4 }}>{window.COHORT.total} handles</div>
          {window.IS_EDITOR_MODE && (
            <div title="Editor mode is on. Visit ?editor=0 to hide author-only commentary."
              style={{ marginTop: 10, display: 'inline-flex', alignItems: 'center', gap: 5, padding: '2px 7px', borderRadius: 4, background: 'var(--accent-muted)', border: '1px solid rgba(249, 115, 22,0.25)', fontSize: 9, fontFamily: 'var(--mono)', color: 'var(--accent)', letterSpacing: '0.06em', textTransform: 'uppercase', fontWeight: 600 }}>
              <span style={{ width: 4, height: 4, borderRadius: '50%', background: 'var(--accent)' }}></span>
              editor
            </div>
          )}
        </div>
      </div>

      {/* ── Main ── */}
      <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
        {/* Header */}
        <div style={{ padding: isMobile ? '14px 16px' : '14px 32px', borderBottom: '1px solid var(--border-subtle)', display: 'flex', alignItems: 'center', gap: isMobile ? 10 : 16, flexShrink: 0, background: 'var(--bg-surface)' }}>
          {isMobile && (
            <button onClick={function () { setNavOpen(true); }} aria-label="Open menu"
              style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: 34, height: 34, flexShrink: 0, borderRadius: 8, border: '1px solid var(--border-subtle)', background: 'var(--bg-elevated)', color: 'var(--text-primary)', cursor: 'pointer', padding: 0 }}>
              <svg width="18" height="18" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round"><line x1="3" y1="5" x2="15" y2="5"/><line x1="3" y1="9" x2="15" y2="9"/><line x1="3" y1="13" x2="15" y2="13"/></svg>
            </button>
          )}
          <h1 style={{ fontSize: 16, fontWeight: 600, margin: 0, color: 'var(--text-primary)', letterSpacing: '-0.01em' }}>
            {screen === 'overview' ? 'Cohort Overview'
              : screen === 'network'  ? 'Pedigree Map'
              : screen === 'attention' ? 'Shared Attention'
              : screen === 'activity' ? 'Activity'
              : screen === 'editor'   ? 'Editor\'s Notes'
              : screen === 'discussion' ? 'Discussion'
              : 'Handle Directory'}
          </h1>
          {screen === 'directory' && (
            <div style={{ display: 'flex', gap: 2 }}>
              <HeaderTab label="List" active={dirView === 'list'} onClick={function () { setDirView('list'); }} />
              <HeaderTab label="Comparison" active={dirView === 'compare'} onClick={function () { setDirView('compare'); }} />
              <HeaderTab label="Scatter" active={dirView === 'scatter'} onClick={function () { setDirView('scatter'); }} />
              <HeaderTab label="Info Diet" active={dirView === 'diet'} onClick={function () { setDirView('diet'); }} />
              <HeaderTab label="Methodology" active={dirView === 'methodology'} onClick={function () { setDirView('methodology'); }} />
            </div>
          )}
          {screen === 'activity' && (
            <div style={{ display: 'flex', gap: 2 }}>
              <HeaderTab label="Timeline" active={actView === 'timeline'} onClick={function () { setActView('timeline'); }} />
              <HeaderTab label="Topics" active={actView === 'topics'} onClick={function () { setActView('topics'); }} />
              <HeaderTab label="Frequency" active={actView === 'frequency'} onClick={function () { setActView('frequency'); }} />
              <HeaderTab label="Heatmap" active={actView === 'heatmap'} onClick={function () { setActView('heatmap'); }} />
            </div>
          )}
          {screen === 'attention' && (
            <div style={{ display: 'flex', gap: 2 }}>
              <HeaderTab label="Overview" active={attnView !== 'follows'} onClick={function () { setAttnView(''); }} />
              <HeaderTab label="Follows list" active={attnView === 'follows'} onClick={function () { setAttnView('follows'); }} />
            </div>
          )}
          <div style={{ flex: 1 }}></div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <div style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--accent)', flexShrink: 0 }}></div>
            <span style={{ fontSize: 12, color: 'var(--text-tertiary)', fontFamily: 'var(--mono)', fontFeatureSettings: "'tnum' 1" }}>Last sync {fmtDate} {fmtTime}</span>
          </div>
        </div>

        {/* Screen content */}
        <div style={{ flex: 1, overflow: 'hidden' }}>
          <ScreenErrorBoundary key={screen + '/' + subFor(screen, dirView, actView, attnView)}>
          <SafeRender render={function () { return (
          <React.Fragment>
          {screen === 'overview' && <div style={{ height: '100%', overflowY: 'auto' }}><OverviewScreen onNavigate={navigateTo} /></div>}
          {screen === 'directory' && dirView === 'list' && <DirectoryScreen initialFilter={dirFilter} tweaks={t} />}
          {screen === 'directory' && dirView === 'compare' && <ComparisonView />}
          {screen === 'directory' && dirView === 'scatter' && <ScatterView />}
          {screen === 'directory' && dirView === 'diet' && <DietScatterView />}
          {screen === 'directory' && dirView === 'methodology' && <MethodologyView onOpenPerson={function (h) { setMethPerson(h); }} />}
          {methPerson && <PersonDrawer person={window.COHORT.members.find(function (m) { return m.handle === methPerson; })} onClose={function () { setMethPerson(null); }} />}
          {screen === 'network' && <PedigreeScreen />}
          {screen === 'attention' && attnView !== 'follows' && <AttentionView onOpenFollows={function () { setAttnView('follows'); }} />}
          {screen === 'attention' && attnView === 'follows' && <AttentionFollowsView onBack={function () { setAttnView(''); }} />}
          {screen === 'activity' && actView === 'timeline' && <TimelineView initialTopic={tlTopicFilter} initialHandle={tlHandleFilter} />}
          {screen === 'activity' && actView === 'topics' && <TopicsView onJumpToTimeline={function (opts) { setActView('timeline'); setTlTopicFilter(opts.topic || null); setTlHandleFilter(opts.handle || null); }} />}
          {screen === 'activity' && actView === 'frequency' && <FrequencyView onJumpToHandle={function (h) { setActView('timeline'); setTlHandleFilter(h); setTlTopicFilter(null); }} />}
          {screen === 'activity' && actView === 'heatmap' && <HeatmapView onJumpToHandle={function (h) { setActView('timeline'); setTlHandleFilter(h); setTlTopicFilter(null); }} />}
          {screen === 'editor' && window.IS_EDITOR_MODE && <EditorNotesPage />}
          {screen === 'discussion' && window.IS_EDITOR_MODE && <DiscussionPage />}
          </React.Fragment>
          ); }} />
          </ScreenErrorBoundary>
        </div>
      </div>

      {/* ── Tweaks ── */}
      <TweaksPanel>
        <TweakSection label="Display" />
        <TweakRadio label="Row Density" value={t.density} options={['compact', 'comfortable', 'spacious']} onChange={function (v) { setTweak('density', v); }} />
        <TweakToggle label="Highlight External Targets" value={t.showExternalHighlight} onChange={function (v) { setTweak('showExternalHighlight', v); }} />
        <TweakRadio label="Empty Bio Style" value={t.emptyBioStyle} options={['dimmed', 'normal']} onChange={function (v) { setTweak('emptyBioStyle', v); }} />
      </TweaksPanel>
    </div>
  );
}

window.App = App;
