// viewbot.info — main app v2

const { useState: useStateA, useEffect: useEffectA, useMemo: useMemoA, useRef: useRefA } = React;

// ─────────── SIDEBAR (discord-style) ───────────
function Sidebar({ onPickStreamer }) {
  const [active, setActive] = useStateA('feed');
  const sections = [
    { id: 'feed',         glyph: '▸', label: 'Live feed',       count: '166', section: 'INVESTIGATION' },
    { id: 'methodology',  glyph: '◇', label: 'The 8 scores',    count: '',    section: '' },
    { id: 'rigidity',     glyph: '═', label: 'Audience rigidity', count: '', section: '' },
    { id: 'context',      glyph: '▤', label: 'Context detachment', count: '', section: '' },
    { id: 'peers',        glyph: '∿', label: 'Peer co-movement',count: '',    section: '' },
    { id: 'dossiers',     glyph: '☰', label: 'All dossiers',    count: '162', section: '' },

    { id: 'brands',        glyph: '◷', label: 'Overview',        count: '',    section: 'FOR BRANDS' },
    { id: 'brands-roster', glyph: '☴', label: 'Active roster',   count: '28',  section: '' },
    { id: 'brands-audit',  glyph: '⌖', label: 'Post-campaign audit', count: 'NEW', section: '' },
    { id: 'brands-dir',    glyph: '✓', label: 'Vetted directory',count: '46',  section: '' },
    { id: 'brands-bulk',   glyph: '⊞', label: 'Bulk screen',     count: '',    section: '' },

    { id: 'creators',     glyph: '↥', label: 'Self-scan',       count: 'NEW', section: 'FOR CREATORS' },
    { id: 'verified',     glyph: '✓', label: 'Verified badge',  count: '',    section: '' },
    { id: 'appeals',      glyph: '⇋', label: 'Appeals',         count: '',    section: '' },
  ];

  // sample live channels for the sidebar
  const liveSample = LIVE_FEED.slice(0, 6);

  return (
    <aside className="sidebar">
      <div className="sb-brand">
        <span className="brand-mark"></span>
        viewbot<span className="tld">.info</span>
      </div>

      {sections.map((s, i) => {
        const prev = sections[i-1];
        const showHead = s.section && (!prev || prev.section !== s.section);
        return (
          <React.Fragment key={s.id}>
            {showHead && <div className="sb-sect">{s.section}</div>}
            <a className={'sb-link ' + (active === s.id ? 'active' : '')}
               href={'#' + s.id}
               onClick={() => setActive(s.id)}>
              <span className="glyph">{s.glyph}</span>
              <span>{s.label}</span>
              {s.count && <span className="count">{s.count}</span>}
            </a>
          </React.Fragment>
        );
      })}

      <div className="sb-sect">FLAGGED · LIVE NOW</div>
      {liveSample.map(s => (
        <div key={s.user} className="sb-streamer" onClick={() => onPickStreamer(s)}>
          <Avatar name={s.user} palette={avatarColor(s.user.length*3)} size={24}/>
          <div style={{ minWidth: 0 }}>
            <div className="name">{s.user}</div>
            <div className="meta" style={{ textAlign: 'left' }}>
              <Plat p={s.plat} size={10}/> {s.cat}
            </div>
          </div>
          <span className="meta">
            <span className="live-dot"></span>
            {(s.viewers/1000).toFixed(1)}k
          </span>
        </div>
      ))}

      <div style={{ flex: 1 }}/>
      <div className="sb-sect">SYSTEM</div>
      <div className="sb-link" style={{ pointerEvents: 'none' }}>
        <span className="glyph">●</span>
        <span style={{ color: 'var(--green)' }}>model v3.2.1</span>
        <span className="count">live</span>
      </div>
    </aside>
  );
}

// ─────────── TOPBAR ───────────
function Topbar() {
  return (
    <header className="topbar">
      <div className="topbar-inner">
        <div className="search">
          <span style={{ color: 'var(--green)' }}>$</span>
          <input placeholder="scan a username · paste a stream URL · @brand for waitlist"/>
          <span className="hotkey">⌘ K</span>
        </div>
        <div className="topbar-actions">
          <span className="chip" style={{ color: 'var(--green)' }}>
            <span style={{ width: 6, height: 6, borderRadius: '50%', background: 'var(--green)', display: 'inline-block' }}></span>
            8,402 channels live · poll 30s
          </span>
          <button className="btn">SIGN IN</button>
          <button className="btn green">▸ SCAN A STREAM</button>
        </div>
      </div>
    </header>
  );
}

// ─────────── TICKER ───────────
function Ticker() {
  const items = [...TICKER_ITEMS, ...TICKER_ITEMS];
  return (
    <div className="ticker">
      <div className="ticker-track">
        {items.map((it, i) => (
          <span key={i} className={'ticker-item ' + (it.k || '')}>
            <span>{it.l}</span>
            <span className="v">{it.v}</span>
          </span>
        ))}
      </div>
    </div>
  );
}

// ─────────── HERO ───────────
function Hero() {
  return (
    <section className="hero">
      <div className="hero-grid">
        <div>
          <span className="eyebrow">SYS / FORENSIC TELEMETRY · v3.2.1</span>
          <h1>
            Real audiences <span className="accent">breathe</span>.<br/>
            <span className="dim">Fake ones sit still.</span>
          </h1>
          <p className="hero-sub">
            viewbot.info models eight independent signals — viewer velocity, audience rigidity,
            category and game elasticity, engagement coupling, rank targeting, historical deviation,
            and pattern repetition — and asks one question.
            &nbsp;Does this audience behave like it belongs to the rest of the ecosystem?
          </p>
          <div className="hero-actions">
            <button className="btn solid">▸ SCAN A STREAM</button>
            <a className="btn purple" href="#dossiers">BROWSE THE 80 CAUGHT →</a>
            <a className="btn" href="#methodology">READ THE METHODOLOGY</a>
          </div>
        </div>

        <div className="hero-vis">
          <div className="row-between">
            <span className="card-title"><span className="dot red"></span>RIGIDITY · 14 STREAMS</span>
            <span className="chip">σ²(REAL) / σ²(FAKE) = 105x</span>
          </div>
          <div className="hv-chart"><HeroSpark/></div>
          <div className="hero-vis-stats">
            <div className="cell">
              <span className="lbl">REAL · σ²</span>
              <span className="val green">0.42</span>
            </div>
            <div className="cell">
              <span className="lbl">FAKE · σ²</span>
              <span className="val red">0.004</span>
            </div>
            <div className="cell">
              <span className="lbl">DETACHMENT</span>
              <span className="val purple">105x</span>
            </div>
          </div>
        </div>
      </div>

      {/* hero stats — full width below */}
      <div className="statgrid" style={{ marginTop: 48 }}>
        <div className="statcell">
          <span className="label">STREAMS AUDITED · ALL TIME</span>
          <span className="num numfont">162</span>
          <span className="delta">+14 THIS WEEK</span>
        </div>
        <div className="statcell red">
          <span className="label">CONFIRMED BOTTED</span>
          <span className="num numfont">80</span>
          <span className="delta up">+5 THIS WEEK</span>
        </div>
        <div className="statcell green">
          <span className="label">CLEARED AS LEGIT</span>
          <span className="num numfont">46</span>
          <span className="delta down">VERIFIED CREATORS</span>
        </div>
        <div className="statcell yellow">
          <span className="label">UNDER REVIEW</span>
          <span className="num numfont">34</span>
          <span className="delta">SECOND-PASS PENDING</span>
        </div>
      </div>
    </section>
  );
}

// ─────────── LIVE FEED — top stream tiles + table ───────────
function StreamTiles({ rows, onPick }) {
  return (
    <div className="stream-tile-row">
      {rows.slice(0, 4).map(s => (
        <div key={s.user} className="stream-tile" onClick={() => onPick(s)}>
          <div className="thumb">
            <span className="live-pill live-tag">LIVE</span>
            <span className="view-tag">{(s.viewers/1000).toFixed(1)}k</span>
            <span className="risk-tag"><RiskPill score={s.score}/></span>
          </div>
          <div className="body">
            <Avatar name={s.user} palette={avatarColor(s.user.length*3)} size={36} ringStatus="botted"/>
            <div>
              <div className="name">{s.user}</div>
              <div className="sub"><Plat p={s.plat} size={12}/> &nbsp;{s.cat}</div>
            </div>
          </div>
        </div>
      ))}
    </div>
  );
}

function ScanPanel() {
  const [plat, setPlat] = useStateA('twitch');
  const [windw, setWindw] = useStateA('30m');
  const [user, setUser] = useStateA('');
  const [scanning, setScanning] = useStateA(false);
  const [ready, setReady] = useStateA(false);
  const [hits, setHits] = useStateA([]);

  const runScan = () => {
    if (!user) return;
    setScanning(true); setReady(false); setHits([]);
    const r = rng(user.length * 7);
    const dots = Array.from({ length: 8 }, () => ({ x: 14 + r()*72, y: 14 + r()*72, lit: false }));
    setHits(dots);
    let i = 0;
    const id = setInterval(() => {
      i++;
      if (i > dots.length) { clearInterval(id); setScanning(false); setReady(true); return; }
      setHits(prev => prev.map((d, j) => j === i - 1 ? { ...d, lit: true } : d));
    }, 280);
  };

  return (
    <div className="card scan-panel">
      <div className="card-head" style={{ padding: 0, paddingBottom: 14, marginBottom: 14, borderBottom: 'none' }}>
        <span className="card-title"><span className="dot"></span>STREAM_SCAN</span>
        <span className="card-aux">QUICK LAUNCH</span>
      </div>

      <Radar scanning={scanning} ready={ready} hits={hits}/>

      <div className="seg" style={{ width: '100%', justifyContent: 'space-between', marginTop: 14 }}>
        {['twitch','kick','rumble'].map(p => (
          <button key={p} className={plat === p ? 'active' : ''} onClick={() => setPlat(p)}
            style={{ flex: 1, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 6 }}>
            <Plat p={p} size={12}/> {p.toUpperCase()}
          </button>
        ))}
      </div>

      <div className="field">
        <span className="prompt">$</span>
        <input
          value={user}
          onChange={(e) => setUser(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && runScan()}
          placeholder="enter username."
        />
      </div>

      <div className="time-seg">
        {['15m','30m','45m','1h'].map(w => (
          <button key={w} className={windw === w ? 'active' : ''} onClick={() => setWindw(w)}>
            {w}
          </button>
        ))}
      </div>

      <button className={'btn-primary ' + (user ? 'go' : '')} onClick={runScan}>
        {user ? `▸ SCAN ${user.toUpperCase()}` : '[ ENTER A USERNAME TO SCAN ]'}
      </button>
      <div className="muted-3" style={{ fontSize: 11, letterSpacing: '0.08em', textTransform: 'uppercase', textAlign: 'center', marginTop: 10 }}>
        FREE ACCOUNT REQUIRED TO SCAN A STREAM
      </div>
    </div>
  );
}

function LiveFeedTable({ onPickStreamer }) {
  const [sort, setSort] = useStateA('viewers');
  const [showAll, setShowAll] = useStateA(false);

  const sorted = useMemoA(() => {
    const arr = [...LIVE_FEED];
    arr.sort((a, b) => sort === 'viewers' ? b.viewers - a.viewers :
                       sort === 'chatters' ? b.chatters - a.chatters :
                       sort === 'eng' ? a.eng - b.eng :
                       sort === 'score' ? a.score - b.score :
                       a.rank - b.rank);
    return arr.map((s, i) => ({ ...s, rank: i + 1 }));
  }, [sort]);

  const rows = showAll ? sorted : sorted.slice(0, 10);

  return (
    <div className="card">
      <div className="card-head">
        <span className="card-title"><span className="dot red"></span>LIVE_BOTTED.FEED</span>
        <span className="card-aux"><span style={{color:'var(--red)'}}>●</span> 166 FLAGGED</span>
      </div>
      <div style={{ overflowX: 'auto' }}>
        <table className="tbl">
          <thead>
            <tr>
              <th>#</th>
              <th>Streamer</th>
              <th>Category</th>
              <th className="num" onClick={() => setSort('viewers')} style={{ cursor: 'pointer' }}>
                Viewers {sort === 'viewers' ? '↓' : ''}
              </th>
              <th className="num" onClick={() => setSort('chatters')} style={{ cursor: 'pointer' }}>
                Chatters {sort === 'chatters' ? '↓' : ''}
              </th>
              <th className="num" onClick={() => setSort('eng')} style={{ cursor: 'pointer' }}>
                Eng% {sort === 'eng' ? '↓' : ''}
              </th>
              <th className="num" onClick={() => setSort('score')} style={{ cursor: 'pointer' }}>
                Risk {sort === 'score' ? '↓' : ''}
              </th>
            </tr>
          </thead>
          <tbody>
            {rows.map(s => (
              <tr key={s.user} onClick={() => onPickStreamer(s)}>
                <td className="idx">{String(s.rank).padStart(2, '0')}</td>
                <td>
                  <span className="streamer-cell">
                    <Plat p={s.plat} size={14}/>
                    <span>{s.user}</span>
                  </span>
                </td>
                <td className="muted-2">{s.cat}</td>
                <td className="num">{fmt(s.viewers)}</td>
                <td className="num">{fmt(s.chatters)}</td>
                <td className={'num score ' + (engClass(s.eng) === 'low' ? 'low' : engClass(s.eng) === 'mid' ? 'mid' : 'high')}>{s.eng}%</td>
                <td className="num"><RiskPill score={s.score}/></td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      <div className="card-foot" style={{ justifyContent: 'center' }}>
        <button className="muted-2" onClick={() => setShowAll(v => !v)} style={{ letterSpacing: '0.08em' }}>
          [ {showAll ? 'COLLAPSE' : 'SHOW ALL 166'} ]
        </button>
      </div>
      <div className="card-foot">
        <span>THRESHOLD · &lt;10% ENG · ≥100 VIEWERS · OR INORGANIC SCORE &lt; 50</span>
        <span>#{String(sorted.length).padStart(4,'0')} · POLL 30s</span>
      </div>
    </div>
  );
}

function LiveFeedSection({ onPickStreamer }) {
  return (
    <section className="section" id="feed">
      <div className="section-inner">
        <div className="section-head">
          <span className="eyebrow">REAL-TIME FEED</span>
          <h2>Streams currently failing the model.</h2>
          <p>Live channels with active-chatter engagement below 10% on at least 100 concurrent viewers — or an inorganic-traffic score under 50. Sampled every 30 seconds across Twitch, Kick, and Rumble.</p>
        </div>

        <StreamTiles rows={LIVE_FEED} onPick={onPickStreamer}/>

        <div className="scan-grid" style={{ marginTop: 18 }}>
          <ScanPanel/>
          <LiveFeedTable onPickStreamer={onPickStreamer}/>
        </div>
      </div>
    </section>
  );
}

// ─────────── 8-SCORE MODEL ───────────
function ScoreModel() {
  return (
    <section className="section" id="methodology">
      <div className="section-inner">
        <div className="section-head">
          <span className="eyebrow purple">METHODOLOGY · v3.2.1</span>
          <h2>Eight scores. <span className="dim">One inorganic-traffic verdict.</span></h2>
          <p>
            One ratio is not a case. Eight independent signals modeled together, weighted by historical labels and reviewer feedback, are. Each score is 0–100. Below 35 fails. Above 65 clears. The middle band gets a second pass.
          </p>
        </div>

        <div className="scores">
          {SCORE_DEFS.map((s, i) => (
            <div className={'score-card ' + (i % 2 === 1 ? 'purple' : '')} key={s.key}>
              <span className="idx">{s.idx} · {s.short}</span>
              <span className="name">{s.name}</span>
              <span className="desc">{s.desc}</span>
              <div className="viz"><ScoreViz kind={s.viz}/></div>
              <div className="var-bar"><div style={{ width: (60 + i*4) + '%' }}/></div>
              <div className="meta">
                <span>WEIGHT</span>
                <span className="v">{[18, 16, 14, 14, 12, 10, 8, 8][i]}%</span>
              </div>
            </div>
          ))}
        </div>

        {/* the population scatter — keep as overview */}
        <div className="card" style={{ marginTop: 32 }}>
          <div className="card-head">
            <span className="card-title"><span className="dot"></span>POPULATION_PLOT · 6,418 CHANNELS · LIVE NOW</span>
            <span className="card-aux">↓ LOWER-LEFT QUADRANT · LIKELY SYNTHETIC</span>
          </div>
          <div style={{ padding: '20px 24px 18px' }}>
            <div className="viz-scroll"><ViewerChatterPlot highlight={LIVE_FEED[1]}/></div>
          </div>
          <div className="card-foot">
            <span style={{ display: 'inline-flex', gap: 16 }}>
              <span style={{ color: 'var(--green)' }}>● 4,206 LEGIT</span>
              <span style={{ color: 'var(--red)' }}>● 1,402 BOTTED</span>
              <span style={{ color: 'var(--yellow)' }}>● 810 UNDER REVIEW</span>
            </span>
            <span>X · LOG VIEWERS · Y · LOG CHATTERS</span>
          </div>
        </div>
      </div>
    </section>
  );
}

// ─────────── AUDIENCE RIGIDITY ───────────
function RigiditySection() {
  return (
    <section className="section" id="rigidity">
      <div className="section-inner">
        <div className="section-head">
          <span className="eyebrow red">SIGNAL 02 · THE FLOOR</span>
          <h2>The artificial floor problem.</h2>
          <p>
            Real viewer counts are noisy. They drift, dip, and respond. A botted channel often shows
            something different — a hard guaranteed minimum that snaps back across every stream regardless
            of game, time, length, or category health. It looks like a guarantee, because it is one.
          </p>
        </div>

        <div className="card">
          <div className="card-head">
            <span className="card-title"><span className="dot red"></span>RIGIDITY_MAP / case-002</span>
            <span className="card-aux">σ² = 0.004 · FLOOR 4,920 · CEILING 5,090</span>
          </div>
          <div style={{ padding: '20px 24px 24px' }}>
            <div className="viz-scroll"><AudienceRigidity name="case-002"/></div>
          </div>
          <div className="card-foot">
            <span style={{ display: 'inline-flex', gap: 16 }}>
              <span style={{ color: 'var(--red)' }}>● case-002 · 14 streams · 1.7% variance</span>
              <span style={{ color: 'var(--text-3)' }}>● peer creator · 14 streams · 62.4% variance</span>
            </span>
            <span>RIGIDITY_SCORE · 12 / 100 · FAIL</span>
          </div>
        </div>

        <div className="viz-grid-2" style={{ marginTop: 18 }}>
          <div className="card">
            <div className="card-head">
              <span className="card-title"><span className="dot"></span>WHAT REAL AUDIENCES DO</span>
            </div>
            <div style={{ padding: 22 }}>
              <p className="muted" style={{ fontSize: 14, lineHeight: 1.65, margin: 0 }}>
                Across fourteen comparable streams, a healthy channel will move 40–70% in concurrent viewers depending on game, hour, category traffic, and content. Even strong creators with loyal cores fluctuate — their main game beats Just Chatting beats a sponsored title beats a 3am test stream. The audience doesn't follow them equally everywhere.
              </p>
              <div style={{ display: 'flex', gap: 10, marginTop: 16, flexWrap: 'wrap' }}>
                <span className="chip" style={{ color: 'var(--green)' }}>variance ≥ 0.25</span>
                <span className="chip" style={{ color: 'var(--green)' }}>game-switch Δ ≥ 30%</span>
                <span className="chip" style={{ color: 'var(--green)' }}>day-part Δ ≥ 20%</span>
              </div>
            </div>
          </div>

          <div className="card" style={{ borderColor: 'var(--red-line)', background: 'linear-gradient(180deg, rgba(255,90,95,0.05), var(--raised))' }}>
            <div className="card-head">
              <span className="card-title"><span className="dot red"></span>WHAT THIS CHANNEL DOES</span>
            </div>
            <div style={{ padding: 22 }}>
              <p className="muted" style={{ fontSize: 14, lineHeight: 1.65, margin: 0 }}>
                Same band. Every stream. Across five different games and a peak-vs-3am gap that should have eaten 80% of the audience. CCV returns to ~5,000 within four minutes of going live, holds within a 170-viewer band, and collapses to zero the moment the stream ends. There is no human audience that behaves like this.
              </p>
              <div style={{ display: 'flex', gap: 10, marginTop: 16, flexWrap: 'wrap' }}>
                <span className="chip" style={{ color: 'var(--red)' }}>variance 0.004</span>
                <span className="chip" style={{ color: 'var(--red)' }}>game-switch Δ 0.4%</span>
                <span className="chip" style={{ color: 'var(--red)' }}>day-part Δ 1.1%</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

// ─────────── CONTEXT DETACHMENT ───────────
function ContextSection() {
  return (
    <section className="section" id="context">
      <div className="section-inner">
        <div className="section-head">
          <span className="eyebrow purple">SIGNAL 03 · CONTEXT DETACHMENT</span>
          <h2>
            Same viewers.<br/>
            <span className="purple">Different world.</span>
          </h2>
          <p>
            The most defensible test we run. We change everything around the channel — game, hour, category health, event load, schedule — and watch the audience react. Real channels move with their context. Detached channels behave like the rest of the ecosystem isn't even there.
          </p>
        </div>

        <div className="card">
          <div className="card-head">
            <span className="card-title"><span className="dot purple"></span>CONTEXT_MATRIX / case-002 · 7-DAY WINDOW</span>
            <span className="card-aux">DETACHMENT SCORE · 8 / 100 · FAIL</span>
          </div>
          <div style={{ padding: '20px 24px 22px' }}>
            <ContextMatrix/>
          </div>
          <div className="card-foot">
            <span>EXPECTED Δ from category baseline · ACTUAL Δ from this channel</span>
            <span>6 / 6 CONTEXTS · FLOOR HELD</span>
          </div>
        </div>
      </div>
    </section>
  );
}

// ─────────── PEER CO-MOVEMENT ───────────
function PeersSection() {
  return (
    <section className="section" id="peers">
      <div className="section-inner">
        <div className="section-head">
          <span className="eyebrow">SIGNAL 03b · PEER ELASTICITY</span>
          <h2>The category breathes. <span className="dim">This channel doesn't.</span></h2>
          <p>
            Same time slot, same category, same language. We line a channel up against four similar creators and the total category traffic. When the tide rolls in, peers respond. When the tide goes out, peers respond. A real audience can't help reacting to its own world. A bought one usually doesn't bother.
          </p>
        </div>

        <div className="card">
          <div className="card-head">
            <span className="card-title"><span className="dot purple"></span>PEER_COMOVEMENT / Slots & Casino · 24H</span>
            <span className="card-aux">CORR(TARGET, CATEGORY) = 0.04 · CORR(PEER MEDIAN, CATEGORY) = 0.71</span>
          </div>
          <div style={{ padding: '18px 24px 18px' }}>
            <div className="viz-scroll"><PeerComovement/></div>
          </div>
          <div className="card-foot">
            <span style={{ display: 'inline-flex', gap: 16 }}>
              <span style={{ color: 'var(--purple)' }}>● category total</span>
              <span style={{ color: 'var(--text-2)' }}>● peer creators (n=4)</span>
              <span style={{ color: 'var(--red)' }}>● target channel</span>
            </span>
            <span>ELASTICITY_SCORE · 9 / 100 · FAIL</span>
          </div>
        </div>
      </div>
    </section>
  );
}

// ─────────── DOSSIER GRID ───────────
function DossierGrid({ onPickStreamer }) {
  const [tab, setTab] = useStateA('phase1');
  const [filter, setFilter] = useStateA('all');

  const filteredAll = useMemoA(() => DOSSIERS, []);
  const filtered = useMemoA(() => filter === 'all' ? filteredAll : filteredAll.filter(d => d.status === filter), [filter, filteredAll]);

  const counts = useMemoA(() => {
    const c = { botted: 0, legit: 0, review: 0, reformed: 0 };
    filteredAll.forEach(d => { c[d.status] = (c[d.status] || 0) + 1; });
    return c;
  }, [filteredAll]);

  return (
    <section className="section" id="dossiers">
      <div className="section-inner">
        <div className="section-head">
          <span className="eyebrow red">THE LIST · UPDATED 30S AGO</span>
          <h2>The streamers caught faking it.</h2>
          <p>An ongoing public investigation into view botting, chat botting, and synthetic engagement across the major streaming platforms. Every name below has been audited against the eight-score model. Click any to open the full dossier with evidence.</p>
        </div>

        <div className="statgrid" style={{ marginBottom: 24 }}>
          <div className="statcell"><span className="label">TOTAL AUDITED</span><span className="num numfont">162</span></div>
          <div className="statcell red"><span className="label">BOTTED</span><span className="num numfont">80</span></div>
          <div className="statcell green"><span className="label">CLEARED AS LEGIT</span><span className="num numfont">46</span></div>
          <div className="statcell yellow"><span className="label">UNDER REVIEW</span><span className="num numfont">34</span></div>
        </div>

        <div className="card flush">
          <div className="card-head">
            <span className="card-title"><span className="dot red"></span>STREAMER_DOSSIERS.DB</span>
            <span className="card-aux">{filteredAll.length} FILES · UPDATED 2m AGO</span>
          </div>

          <div className="tabs" style={{ padding: '0 18px', borderBottom: '1px solid var(--border)' }}>
            {[
              { id: 'phase1', label: 'PHASE 1' },
              { id: 'phase2', label: 'PHASE 2' },
              { id: 'phase3', label: 'PHASE 3' },
              { id: 'verified', label: 'VERIFIED PARTNERS' },
              { id: 'pushing', label: 'PUSHING FOR VERIFIED' },
            ].map(t => (
              <button key={t.id} className={tab === t.id ? 'active' : ''} onClick={() => setTab(t.id)}>{t.label}</button>
            ))}
          </div>

          <div style={{ padding: '14px 18px', borderBottom: '1px solid var(--border)', display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap' }}>
            <span className="label">FILTER:</span>
            <div className="seg">
              {[
                { id: 'all',      label: 'ALL'      },
                { id: 'botted',   label: 'BOTTED'   },
                { id: 'legit',    label: 'LEGIT'    },
                { id: 'review',   label: 'REVIEW'   },
                { id: 'reformed', label: 'REFORMED' },
              ].map(f => (
                <button key={f.id} className={filter === f.id ? 'active' : ''} onClick={() => setFilter(f.id)}>
                  {f.label}
                </button>
              ))}
            </div>
          </div>

          <div className="dossiers">
            {filtered.map(d => (
              <div className="dossier" key={d.name} onClick={() => onPickStreamer(d)}>
                <Avatar name={d.name} palette={d.avatar} size={32} ringStatus={d.status}/>
                <div className="info">
                  <span className="name">{d.name}</span>
                  <StatusBadge status={d.status}/>
                </div>
              </div>
            ))}
          </div>

          <div className="card-foot">
            <span>SHOWING {filtered.length} OF {filteredAll.length}</span>
            <span>
              {counts.botted} BOTTED · {counts.legit} LEGIT · {counts.review} UNDER REVIEW · {counts.reformed} REFORMED
            </span>
          </div>
        </div>
      </div>
    </section>
  );
}

// ─────────── FOR BRANDS ─────────── (now defined in brands.jsx)

// ─────────── FOOTER ───────────
function Footer() {
  return (
    <footer>
      <div className="foot-inner">
        <div className="foot-grid">
          <div>
            <div className="sb-brand" style={{ padding: 0, marginBottom: 12, borderBottom: 0 }}>
              <span className="brand-mark"></span>
              viewbot<span className="tld">.info</span>
            </div>
            <p className="muted" style={{ fontSize: 13, lineHeight: 1.6, maxWidth: 320 }}>
              Independent forensic telemetry for live-streaming platforms. No platform partnerships. No creator self-reports. Funded by the brand-safety waitlist — not by the streamers we audit.
            </p>
          </div>
          <div className="foot-col">
            <h5>INVESTIGATION</h5>
            <a href="#feed">Live feed</a>
            <a href="#methodology">The 8 scores</a>
            <a href="#rigidity">Audience rigidity</a>
            <a href="#context">Context detachment</a>
            <a href="#dossiers">All dossiers</a>
            <a href="#">Submit a tip</a>
          </div>
          <div className="foot-col">
            <h5>CREATORS</h5>
            <a href="#creators">Self-scan</a>
            <a href="#">Verified badge</a>
            <a href="#">Appeals</a>
            <a href="#">Help center</a>
          </div>
          <div className="foot-col">
            <h5>BRANDS</h5>
            <a href="#brands">Operations dashboard</a>
            <a href="#brands">Post-campaign audit</a>
            <a href="#brands">Vetted directory</a>
            <a href="#brands">Bulk screen</a>
            <a href="#">API docs</a>
          </div>
          <div className="foot-col">
            <h5>LEGAL</h5>
            <a href="#">Appeals</a>
            <a href="#">Terms</a>
            <a href="#">Privacy</a>
            <a href="#">Contact</a>
          </div>
        </div>
        <div className="foot-bar">
          <span>© 2026 VIEWBOT.INFO · MODEL v3.2.1-FORENSIC · NO PLATFORM PARTNERSHIP</span>
          <span>BUILT TO BE WRONG ABOUT FEWER THINGS THAN THE PLATFORMS</span>
        </div>
      </div>
    </footer>
  );
}

// ─────────── APP ───────────
function App() {
  const [picked, setPicked] = useStateA(null);

  const handlePickStreamer = (s) => {
    if (s.avatar && s.scores) { setPicked(s); }
    else if (s.avatar)        { setPicked(s); }
    else {
      setPicked({
        name: s.user,
        plat: s.plat,
        cat: s.cat,
        viewers: s.viewers,
        eng: s.eng,
        score: s.score || 22,
        status: 'botted',
        avatar: avatarColor(s.user.length * 3),
      });
    }
  };

  return (
    <div className="app-shell">
      <Sidebar onPickStreamer={handlePickStreamer}/>
      <div className="main">
        <Topbar/>
        <Ticker/>
        <Hero/>
        <LiveFeedSection onPickStreamer={handlePickStreamer}/>
        <ScoreModel/>
        <RigiditySection/>
        <ContextSection/>
        <PeersSection/>
        <DossierGrid onPickStreamer={handlePickStreamer}/>
        <ForCreators/>
        <ForBrands onPickStreamer={handlePickStreamer}/>
        <Footer/>
      </div>
      {picked && <Dossier streamer={picked} onClose={() => setPicked(null)}/>}
    </div>
  );
}

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