// viewbot.info — components v2

const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ───────── platform glyph ─────────
function Plat({ p, size = 18 }) {
  return (
    <span className={'plat ' + p} style={{ width: size, height: size, flex: `0 0 ${size}px` }}>
      {p === 'twitch' ? (
        <svg viewBox="0 0 24 24" width={size*0.66} height={size*0.66} fill="currentColor"><path d="M4 2l-2 4v14h5v3l3-3h4l6-6V2H4zm15 11l-3 3h-4l-3 3v-3H5V4h14v9zm-3-7v6h-2V6h2zm-5 0v6H9V6h2z"/></svg>
      ) : p === 'kick' ? (
        <svg viewBox="0 0 24 24" width={size*0.7} height={size*0.7} fill="currentColor"><path d="M3 3h5v6h2V6h2V3h5v3h-2v3h-2v3h2v3h2v3h-5v-3h-2v-3H8v6H3z"/></svg>
      ) : (
        <svg viewBox="0 0 24 24" width={size*0.7} height={size*0.7} fill="currentColor"><path d="M5 3l14 9-14 9z"/></svg>
      )}
    </span>
  );
}

// ───────── avatar ─────────
function Avatar({ name, palette, size = 32, ringStatus, live }) {
  const [a, b] = palette || ['#1c2224', '#2a3133'];
  const initial = (name || '?').replace(/[^a-z0-9]/gi, '').slice(0, 2).toUpperCase();
  const ringMap = { botted: '#ff5a5f', legit: '#b8ff5c', review: '#ffd84d', reformed: '#5aa3ff' };
  const ring = ringStatus ? ringMap[ringStatus] : null;
  return (
    <span style={{
      width: size, height: size, flex: `0 0 ${size}px`,
      background: `linear-gradient(135deg, ${a}, ${b})`,
      boxShadow: ring ? `0 0 0 1.5px ${ring}, 0 0 0 3px rgba(0,0,0,0.6)` : 'none',
      borderRadius: '50%',
      display: 'grid', placeItems: 'center',
      color: '#0a0d0e', fontWeight: 700,
      fontFamily: 'var(--mono)', fontSize: Math.floor(size*0.34),
      position: 'relative',
    }}>
      {initial}
      {live && (
        <span style={{
          position: 'absolute', bottom: -1, right: -1,
          width: Math.max(8, size*0.28), height: Math.max(8, size*0.28),
          borderRadius: '50%', background: 'var(--red)',
          border: '2px solid var(--raised)',
        }}/>
      )}
    </span>
  );
}

// ───────── badge ─────────
function StatusBadge({ status }) {
  const map = {
    botted:  { cls: 'botted',  txt: '! BOTTED' },
    legit:   { cls: 'legit',   txt: '✓ LEGIT' },
    review:  { cls: 'review',  txt: '? UNDER REVIEW' },
    reformed:{ cls: 'reformed',txt: '↻ REFORMED' },
  }[status];
  if (!map) return null;
  return <span className={'badge ' + map.cls}>{map.txt}</span>;
}

function RiskPill({ score }) {
  const cls = score < 35 ? 'botted' : score < 65 ? 'review' : 'legit';
  const label = score < 35 ? 'HIGH RISK' : score < 65 ? 'WATCH' : 'OK';
  return <span className={'badge ' + cls}>{label} · {score}</span>;
}

// ───────── helpers ─────────
function fmt(n) { return n == null ? '—' : n.toLocaleString('en-US'); }
function pct(n) { return n.toFixed(1) + '%'; }

// ───────── score-card mini visualizations (8) ─────────
function ScoreViz({ kind }) {
  if (kind === 'velocity')   return <VelocityViz/>;
  if (kind === 'rigidity')   return <RigidityViz/>;
  if (kind === 'category')   return <CategoryViz/>;
  if (kind === 'game')       return <GameViz/>;
  if (kind === 'engagement') return <EngagementViz/>;
  if (kind === 'rank')       return <RankViz/>;
  if (kind === 'deviation')  return <DeviationViz/>;
  if (kind === 'repetition') return <RepetitionViz/>;
  return null;
}

// 01 — viewer velocity: organic curve vs sudden step
function VelocityViz() {
  return (
    <svg viewBox="0 0 280 56" preserveAspectRatio="none" width="100%" height="100%">
      <defs>
        <linearGradient id="vstep" x1="0" x2="0" y1="0" y2="1">
          <stop offset="0%" stopColor="var(--red)" stopOpacity="0.35"/>
          <stop offset="100%" stopColor="var(--red)" stopOpacity="0"/>
        </linearGradient>
      </defs>
      {/* organic curve (faint) */}
      <path d="M0,48 C40,46 80,42 120,32 C160,22 200,16 280,10"
        stroke="var(--text-3)" strokeWidth="1" fill="none" strokeDasharray="2 3" opacity="0.5"/>
      {/* synthetic step */}
      <path d="M0,48 L130,46 L132,12 L280,12"
        stroke="var(--red)" strokeWidth="1.6" fill="none"/>
      <path d="M0,48 L130,46 L132,12 L280,12 L280,56 L0,56 Z" fill="url(#vstep)" opacity="0.7"/>
      <line x1="131" x2="131" y1="2" y2="54" stroke="var(--red)" strokeWidth="0.6" strokeDasharray="2 2" opacity="0.7"/>
    </svg>
  );
}

// 02 — rigidity: flat line through wobbling channels
function RigidityViz() {
  const wob = useMemo(() => {
    const r = rng(13);
    return Array.from({length: 56}, (_, i) => 28 + Math.sin(i/2)*10 + (r()-0.5)*8);
  }, []);
  const d = wob.map((y, i) => (i===0?'M':'L') + (i*5)+','+y).join(' ');
  return (
    <svg viewBox="0 0 280 56" preserveAspectRatio="none" width="100%" height="100%">
      <path d={d} stroke="var(--text-3)" strokeWidth="1" fill="none" opacity="0.55"/>
      <line x1="0" x2="280" y1="28" y2="28" stroke="var(--red)" strokeWidth="1.6"/>
      <rect x="0" y="24" width="280" height="8" fill="var(--red)" opacity="0.08"/>
    </svg>
  );
}

// 03 — category: peers fluctuate, target flat
function CategoryViz() {
  return (
    <svg viewBox="0 0 280 56" preserveAspectRatio="none" width="100%" height="100%">
      {/* peer curves */}
      <path d="M0,16 C40,8 70,30 120,22 C170,12 200,40 280,28" stroke="var(--text-3)" strokeWidth="0.8" fill="none" opacity="0.5"/>
      <path d="M0,30 C40,38 70,12 120,28 C170,42 200,18 280,24" stroke="var(--text-3)" strokeWidth="0.8" fill="none" opacity="0.5"/>
      <path d="M0,42 C40,30 70,46 120,38 C170,46 200,30 280,42" stroke="var(--text-3)" strokeWidth="0.8" fill="none" opacity="0.5"/>
      <line x1="0" x2="280" y1="26" y2="26" stroke="var(--red)" strokeWidth="1.6"/>
    </svg>
  );
}

// 04 — game elasticity: bars per game, organic vs floor
function GameViz() {
  // simulates 5 games — heights differ for organic, flat for bot
  const games = [38, 22, 30, 16, 28];
  return (
    <svg viewBox="0 0 280 56" preserveAspectRatio="none" width="100%" height="100%">
      {games.map((h, i) => (
        <rect key={i} x={i*56+8} y={56-h} width={20} height={h} fill="var(--text-3)" opacity="0.55"/>
      ))}
      {games.map((_, i) => (
        <rect key={'b'+i} x={i*56+34} y={20} width={20} height={36} fill="var(--red)" opacity="0.8"/>
      ))}
      <line x1="0" x2="280" y1="20" y2="20" stroke="var(--red)" strokeWidth="0.8" strokeDasharray="2 3" opacity="0.7"/>
    </svg>
  );
}

// 05 — engagement coupling: viewer line vs chatter line
function EngagementViz() {
  const v = useMemo(() => {
    const r = rng(31);
    return Array.from({length: 28}, (_, i) => 36 - i*0.6 + (r()-0.5)*4);
  }, []);
  const c = useMemo(() => Array.from({length: 28}, () => 46 + (Math.random()-0.5)*1.5), []);
  const dV = v.map((y, i) => (i===0?'M':'L') + (i*10)+','+y).join(' ');
  const dC = c.map((y, i) => (i===0?'M':'L') + (i*10)+','+y).join(' ');
  return (
    <svg viewBox="0 0 280 56" preserveAspectRatio="none" width="100%" height="100%">
      <path d={dV} stroke="var(--text)" strokeWidth="1.2" fill="none" opacity="0.85"/>
      <path d={dC} stroke="var(--red)" strokeWidth="1.4" fill="none"/>
    </svg>
  );
}

// 06 — rank targeting: viewer adds spike right at rank threshold
function RankViz() {
  return (
    <svg viewBox="0 0 280 56" preserveAspectRatio="none" width="100%" height="100%">
      <line x1="0" x2="280" y1="28" y2="28" stroke="var(--green)" strokeWidth="0.6" strokeDasharray="2 3" opacity="0.5"/>
      <text x="276" y="24" textAnchor="end" fontFamily="var(--mono)" fontSize="7" fill="var(--green-dim)" letterSpacing="0.5">RANK 6 THRESHOLD</text>
      <path d="M0,40 L40,42 L42,26 L70,28 L72,42 L120,40 L122,26 L160,28 L162,42 L210,40 L212,26 L250,28 L252,42 L280,40"
        stroke="var(--red)" strokeWidth="1.4" fill="none"/>
    </svg>
  );
}

// 07 — historical deviation: today vs personal baseline cloud
function DeviationViz() {
  const cloud = useMemo(() => {
    const r = rng(67);
    return Array.from({length: 40}, () => ({ x: 30 + r()*220, y: 12 + r()*32, s: 1.4 + r()*1.4 }));
  }, []);
  return (
    <svg viewBox="0 0 280 56" preserveAspectRatio="none" width="100%" height="100%">
      <ellipse cx="120" cy="28" rx="100" ry="14" fill="rgba(184,255,92,0.06)" stroke="rgba(184,255,92,0.25)" strokeWidth="0.6" strokeDasharray="2 2"/>
      {cloud.map((p, i) => <circle key={i} cx={p.x} cy={p.y} r={p.s} fill="var(--text-3)" opacity="0.65"/>)}
      <circle cx="246" cy="14" r="3.6" fill="var(--red)"/>
      <circle cx="246" cy="14" r="6.5" fill="none" stroke="var(--red)" strokeWidth="0.8" opacity="0.7"/>
    </svg>
  );
}

// 08 — repetition: same fingerprint across multiple streams
function RepetitionViz() {
  return (
    <svg viewBox="0 0 280 56" preserveAspectRatio="none" width="100%" height="100%">
      {[0,1,2,3,4,5,6].map(i => (
        <g key={i} transform={`translate(${i*40},0)`}>
          <path d="M0,48 L18,46 L20,12 L36,12 L38,48"
            stroke="var(--red)" strokeWidth="1.2" fill="none" opacity={0.4 + i*0.08}/>
        </g>
      ))}
    </svg>
  );
}

// ───────── radar (scan tool) ─────────
function Radar({ scanning, ready, hits }) {
  const angle = useRef(0);
  const [, force] = useState(0);
  useEffect(() => {
    let id;
    const tick = () => { angle.current = (angle.current + 1.6) % 360; force(a => a+1); id = requestAnimationFrame(tick); };
    id = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(id);
  }, []);
  const cx = 50, cy = 50;
  const a = angle.current;
  const sweepX = cx + Math.cos(a*Math.PI/180) * 48;
  const sweepY = cy + Math.sin(a*Math.PI/180) * 48;

  return (
    <div className="radar">
      <svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
        <defs>
          <radialGradient id="gradar">
            <stop offset="0%" stopColor="rgba(184,255,92,0.20)"/>
            <stop offset="80%" stopColor="rgba(184,255,92,0)"/>
          </radialGradient>
          <linearGradient id="gsweep" x1="0" x2="1" y1="0" y2="0" gradientUnits="userSpaceOnUse"
            gradientTransform={`rotate(${a} ${cx} ${cy})`}>
            <stop offset="0%" stopColor="rgba(184,255,92,0.6)"/>
            <stop offset="100%" stopColor="rgba(184,255,92,0)"/>
          </linearGradient>
        </defs>
        <circle cx={cx} cy={cy} r={48} fill="url(#gradar)"/>
        {[12,24,36,48].map(r => (
          <circle key={r} cx={cx} cy={cy} r={r} fill="none"
            stroke="rgba(184,255,92,0.18)" strokeWidth="0.4" strokeDasharray="0.5 1.5"/>
        ))}
        <line x1={2} y1={50} x2={98} y2={50} stroke="rgba(184,255,92,0.10)" strokeWidth="0.3"/>
        <line x1={50} y1={2} x2={50} y2={98} stroke="rgba(184,255,92,0.10)" strokeWidth="0.3"/>
        <path d={`M${cx},${cy} L${sweepX},${sweepY} A48,48 0 0,1 ${cx + Math.cos((a-30)*Math.PI/180)*48},${cy + Math.sin((a-30)*Math.PI/180)*48} Z`}
          fill="url(#gsweep)" opacity="0.85"/>
        {(hits || []).map((h, i) => (
          <g key={i} opacity={h.lit ? 1 : 0.35}>
            <circle cx={h.x} cy={h.y} r={1.2} fill={h.lit ? 'var(--red)' : 'var(--green)'}/>
            {h.lit && <circle cx={h.x} cy={h.y} r={3} fill="none" stroke="var(--red)" strokeWidth="0.4" opacity="0.7"/>}
          </g>
        ))}
        <text x={4} y={9} fill="rgba(184,255,92,0.45)" fontSize="3.2" fontFamily="var(--mono)" letterSpacing="0.5">
          {scanning ? 'SCANNING' : ready ? 'READY' : 'IDLE'}
        </text>
        <text x={92} y={97} fill="rgba(184,255,92,0.45)" fontSize="3.2" fontFamily="var(--mono)" textAnchor="end">
          {Math.floor(a)}°
        </text>
      </svg>
    </div>
  );
}

// ───────── BIG VIZ: audience rigidity (multi-session bars) ─────────
function AudienceRigidity({ name = 'case-002' }) {
  const W = 760, H = 360;
  const padL = 60, padR = 16, padT = 24, padB = 40;
  const sessions = 14;

  // each session is a sparkline of CCV over the stream
  const data = useMemo(() => {
    const r = rng(101);
    return Array.from({ length: sessions }, (_, s) => {
      const points = 24;
      const floor = 4900 + Math.floor(r()*120);
      // tiny noise around floor
      return Array.from({ length: points }, () => floor + (r()-0.5)*180);
    });
  }, []);

  const peer = useMemo(() => {
    // peer creator's sessions — varies wildly
    const r = rng(55);
    return Array.from({ length: sessions }, (_, s) => {
      const peak = 800 + r()*4400;
      const center = 400 + r()*peak;
      const points = 24;
      return Array.from({ length: points }, (_, i) => {
        return Math.max(80, center + Math.sin(i/3)*peak*0.35 + (r()-0.5)*peak*0.5);
      });
    });
  }, []);

  const allValsBot = data.flat();
  const minBot = Math.min(...allValsBot), maxBot = Math.max(...allValsBot);
  const allValsPeer = peer.flat();
  const maxBoth = Math.max(maxBot, ...allValsPeer) * 1.05;

  const cellW = (W - padL - padR) / sessions;
  const sx = (s, i) => padL + s*cellW + (i / (data[0].length - 1)) * (cellW - 6) + 3;
  const sy = v => H - padB - (v / maxBoth) * (H - padT - padB);

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" height="auto" style={{ display:'block' }}>
      {/* y-axis labels */}
      {[0, 1500, 3000, 5000].map(v => (
        <g key={v}>
          <line x1={padL} y1={sy(v)} x2={W-padR} y2={sy(v)} stroke="rgba(255,255,255,0.05)" strokeWidth="0.8"/>
          <text x={padL-8} y={sy(v)+3} textAnchor="end" fontFamily="var(--mono)" fontSize="10" fill="var(--text-3)">{v.toLocaleString()}</text>
        </g>
      ))}

      {/* botted floor band */}
      <rect x={padL} y={sy(maxBot+200)} width={W-padL-padR} height={sy(minBot-200)-sy(maxBot+200)}
        fill="rgba(255,90,95,0.06)" stroke="rgba(255,90,95,0.3)" strokeWidth="0.6" strokeDasharray="3 3"/>
      <text x={padL + 8} y={sy(maxBot+200) - 4} fontFamily="var(--mono)" fontSize="10"
        fill="rgba(255,90,95,0.85)" letterSpacing="0.5">
        ARTIFICIAL FLOOR · 4,920–5,090 CCV · ±1.7% VARIANCE
      </text>

      {/* peer sparklines */}
      {peer.map((sess, s) => {
        const d = sess.map((v, i) => (i===0?'M':'L') + sx(s, i) + ',' + sy(v)).join(' ');
        return <path key={'p'+s} d={d} stroke="var(--text-3)" strokeWidth="1" fill="none" opacity="0.55"/>;
      })}

      {/* botted sparklines */}
      {data.map((sess, s) => {
        const d = sess.map((v, i) => (i===0?'M':'L') + sx(s, i) + ',' + sy(v)).join(' ');
        return <path key={'b'+s} d={d} stroke="var(--red)" strokeWidth="1.4" fill="none"/>;
      })}

      {/* session labels */}
      {data.map((_, s) => (
        <text key={'l'+s} x={padL + s*cellW + cellW/2} y={H - padB + 16}
          textAnchor="middle" fontFamily="var(--mono)" fontSize="9.5" fill="var(--text-4)" letterSpacing="0.5">
          S{String(s+1).padStart(2,'0')}
        </text>
      ))}

      {/* x-axis */}
      <line x1={padL} y1={H - padB} x2={W - padR} y2={H - padB} stroke="rgba(255,255,255,0.1)"/>
      <text x={(W)/2} y={H - 6} fontFamily="var(--mono)" fontSize="10"
        fill="var(--text-3)" textAnchor="middle" letterSpacing="2">LAST 14 STREAMS · CCV PROFILE</text>

      <text x={14} y={(H/2)} fontFamily="var(--mono)" fontSize="10"
        fill="var(--text-3)" textAnchor="middle" letterSpacing="2"
        transform={`rotate(-90 14 ${H/2})`}>↑ CONCURRENT VIEWERS</text>

      {/* legend */}
      <g transform={`translate(${padL}, ${padT - 16})`}>
        <rect width="11" height="2.5" fill="var(--red)"/>
        <text x={16} y={3} fontFamily="var(--mono)" fontSize="10" fill="var(--text-2)" letterSpacing="0.5">{name}</text>
        <rect x="90" width="11" height="2.5" fill="var(--text-3)"/>
        <text x="106" y={3} fontFamily="var(--mono)" fontSize="10" fill="var(--text-2)" letterSpacing="0.5">PEER CREATOR (same category)</text>
      </g>
    </svg>
  );
}

// ───────── BIG VIZ: context-detachment matrix ─────────
function ContextMatrix() {
  // rows: contexts / cols: streamer CCV, peer median, expected delta
  const rows = [
    { ctx: 'Main game — peak window',           ccv: 5040, peer: 4180, exp: '+8%',  delta: '+0.2%', cls: 'flat' },
    { ctx: 'Dead category — 3am EST',           ccv: 4980, peer: 410,  exp: '–82%', delta: '–1.0%', cls: 'flat' },
    { ctx: 'Sponsored low-interest title',      ccv: 5010, peer: 920,  exp: '–55%', delta: '–0.6%', cls: 'flat' },
    { ctx: 'Category surging — event night',    ccv: 5060, peer: 8740, exp: '+62%', delta: '+0.4%', cls: 'flat' },
    { ctx: 'Random indie game test stream',     ccv: 4940, peer: 380,  exp: '–80%', delta: '–1.2%', cls: 'flat' },
    { ctx: 'Just Chatting after main game',     ccv: 5025, peer: 1850, exp: '–28%', delta: '+0.0%', cls: 'flat' },
  ];

  return (
    <div className="matrix" style={{ gridTemplateColumns: '1.6fr 1fr 1fr 1fr 1fr' }}>
      <div className="matrix-cell head"><span>CONTEXT</span></div>
      <div className="matrix-cell head"><span>CHANNEL CCV</span></div>
      <div className="matrix-cell head"><span>PEER MEDIAN</span></div>
      <div className="matrix-cell head"><span>EXPECTED Δ</span></div>
      <div className="matrix-cell head"><span>ACTUAL Δ</span></div>

      {rows.map((r, i) => (
        <React.Fragment key={i}>
          <div className="matrix-cell">
            <span className="lbl" style={{ color: 'var(--text-2)', fontSize: 12.5 }}>{r.ctx}</span>
            <span className="lbl">{i === 0 ? 'baseline' : 'context shift'}</span>
          </div>
          <div className="matrix-cell">
            <span className="val">{r.ccv.toLocaleString()}</span>
            <span className="lbl">CCV</span>
          </div>
          <div className="matrix-cell">
            <span className="val" style={{ color: 'var(--text-2)' }}>{r.peer.toLocaleString()}</span>
            <span className="lbl">peer median</span>
          </div>
          <div className="matrix-cell">
            <span className="val" style={{ color: 'var(--text-2)' }}>{r.exp}</span>
            <span className="lbl">if organic</span>
          </div>
          <div className="matrix-cell">
            <span className="val" style={{ color: 'var(--red)' }}>{r.delta}</span>
            <span className={'delta ' + r.cls}>floor held</span>
          </div>
        </React.Fragment>
      ))}
    </div>
  );
}

// ───────── BIG VIZ: peer co-movement ─────────
function PeerComovement() {
  const W = 760, H = 280;
  const padL = 56, padR = 20, padT = 20, padB = 32;
  const N = 96; // 24h, 15-min intervals

  const series = useMemo(() => {
    const r = rng(91);
    // category total — a wavy curve
    const cat = Array.from({length: N}, (_, i) => {
      return 5000 + Math.sin(i/12)*1800 + Math.cos(i/5)*400 + (r()-0.5)*200;
    });
    // peers move with cat
    const peers = Array.from({length: 4}, (_, p) => {
      const scale = 0.10 + r()*0.18;
      const lag = Math.floor(r()*3);
      return cat.map((v, i) => Math.max(80, v * scale * (0.85 + r()*0.3) + (r()-0.5)*120));
    });
    // target — flat as a board
    const target = Array.from({length: N}, () => 3200 + (r()-0.5)*60);
    return { cat, peers, target };
  }, []);

  const allVals = [...series.cat, ...series.peers.flat(), ...series.target];
  const maxV = Math.max(...allVals) * 1.05;

  const sx = i => padL + (i / (N - 1)) * (W - padL - padR);
  const sy = v => H - padB - (v / maxV) * (H - padT - padB);

  const lineFor = (arr) => arr.map((v, i) => (i===0?'M':'L') + sx(i)+','+sy(v)).join(' ');

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" height="auto" style={{ display:'block' }}>
      {/* grid */}
      {[0, 2000, 4000, 6000].map(v => (
        <g key={v}>
          <line x1={padL} y1={sy(v)} x2={W-padR} y2={sy(v)} stroke="rgba(255,255,255,0.05)"/>
          <text x={padL-8} y={sy(v)+3} textAnchor="end" fontFamily="var(--mono)" fontSize="10" fill="var(--text-3)">{v.toLocaleString()}</text>
        </g>
      ))}
      {/* time markers */}
      {[0, 24, 48, 72, 95].map(i => (
        <text key={i} x={sx(i)} y={H - padB + 14} textAnchor="middle" fontFamily="var(--mono)" fontSize="9.5" fill="var(--text-4)">
          {String(Math.floor((i/96)*24)).padStart(2,'0')}:00
        </text>
      ))}

      {/* category total (area) */}
      <path d={lineFor(series.cat) + ` L${sx(N-1)},${H-padB} L${sx(0)},${H-padB} Z`}
        fill="rgba(160,112,255,0.10)"/>
      <path d={lineFor(series.cat)} stroke="var(--purple)" strokeWidth="1.4" fill="none" opacity="0.95"/>

      {/* peers */}
      {series.peers.map((p, i) => (
        <path key={i} d={lineFor(p)} stroke="var(--text-2)" strokeWidth="1" fill="none" opacity={0.55}/>
      ))}

      {/* target — flat */}
      <path d={lineFor(series.target)} stroke="var(--red)" strokeWidth="1.6" fill="none"/>

      {/* corr labels */}
      <text x={padL + 8} y={padT + 4} fontFamily="var(--mono)" fontSize="10.5" fill="var(--purple-dim)" letterSpacing="0.5">
        CATEGORY TOTAL · 6.4M VIEWER-MIN
      </text>
      <text x={W - padR - 8} y={padT + 4} textAnchor="end" fontFamily="var(--mono)" fontSize="10.5" fill="var(--red)" letterSpacing="0.5">
        TARGET CORR = 0.04 · DETACHED
      </text>

      <text x={(W)/2} y={H - 4} fontFamily="var(--mono)" fontSize="10"
        fill="var(--text-3)" textAnchor="middle" letterSpacing="2">LAST 24H · 15-MIN INTERVALS</text>
    </svg>
  );
}

// ───────── BIG VIZ: viewer-vs-chatter scatter (kept from v1) ─────────
function ViewerChatterPlot({ highlight }) {
  const dots = useMemo(() => {
    const r = rng(101);
    const out = [];
    for (let i=0; i<140; i++) {
      const v = 200 + r()*Math.pow(10, 1.4 + r()*3.0);
      const c = v * (0.08 + r()*0.12) * (0.7 + r()*0.7);
      out.push({ v, c, k: 'legit' });
    }
    for (let i=0; i<90; i++) {
      const v = 500 + r()*Math.pow(10, 1.8 + r()*2.8);
      const c = v * (0.005 + r()*0.04);
      out.push({ v, c, k: 'botted' });
    }
    for (let i=0; i<30; i++) {
      const v = 300 + r()*Math.pow(10, 1.6 + r()*2.6);
      const c = v * (0.035 + r()*0.04);
      out.push({ v, c, k: 'review' });
    }
    return out;
  }, []);

  const W = 760, H = 360;
  const padL = 56, padR = 16, padT = 16, padB = 36;
  const xMin = Math.log10(100), xMax = Math.log10(80000);
  const yMin = Math.log10(2), yMax = Math.log10(6000);
  const sx = v => padL + ((Math.log10(Math.max(v, 100)) - xMin)/(xMax - xMin)) * (W - padL - padR);
  const sy = v => H - padB - ((Math.log10(Math.max(v, 2)) - yMin)/(yMax - yMin)) * (H - padT - padB);
  const lineY = (vx) => sy(Math.pow(10, vx) * 0.10);
  const colorFor = (k) => k === 'legit' ? 'rgba(184,255,92,0.78)'
                        : k === 'botted' ? 'rgba(255,90,95,0.85)'
                        : 'rgba(255,216,77,0.85)';
  const xticks = [100, 1000, 10000, 80000];
  const yticks = [10, 100, 1000];

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" height="auto" style={{ display: 'block' }}>
      <g stroke="rgba(255,255,255,0.05)" strokeWidth="1">
        {xticks.map(t => <line key={'x'+t} x1={sx(t)} y1={padT} x2={sx(t)} y2={H-padB}/>)}
        {yticks.map(t => <line key={'y'+t} x1={padL} y1={sy(t)} x2={W-padR} y2={sy(t)}/>)}
      </g>
      <line x1={sx(100)} y1={lineY(xMin)} x2={sx(80000)} y2={lineY(xMax)}
        stroke="rgba(184,255,92,0.45)" strokeWidth="1.4" strokeDasharray="4 4"/>
      <text x={W - padR} y={lineY(xMax) - 6} textAnchor="end" fontFamily="var(--mono)" fontSize="10" fill="var(--green-dim)" letterSpacing="1">
        EXPECTED · 10% ENGAGEMENT
      </text>
      <line x1={sx(100)} y1={sy(1)} x2={sx(80000)} y2={sy(800)} stroke="rgba(255,90,95,0.35)" strokeWidth="1" strokeDasharray="2 4"/>
      <text x={sx(80000)} y={sy(800) + 12} textAnchor="end" fontFamily="var(--mono)" fontSize="10" fill="rgba(255,90,95,0.7)" letterSpacing="1">
        1% FLOOR · LIKELY SYNTHETIC
      </text>
      {xticks.map(t => (
        <text key={'xl'+t} x={sx(t)} y={H - padB + 16} fontFamily="var(--mono)" fontSize="10" fill="var(--text-3)" textAnchor="middle">{t.toLocaleString()}</text>
      ))}
      {yticks.map(t => (
        <text key={'yl'+t} x={padL - 8} y={sy(t) + 3} fontFamily="var(--mono)" fontSize="10" fill="var(--text-3)" textAnchor="end">{t}</text>
      ))}
      <text x={(W)/2} y={H - 6} fontFamily="var(--mono)" fontSize="10" fill="var(--text-3)" textAnchor="middle" letterSpacing="2">CONCURRENT VIEWERS →</text>
      <text x={14} y={(H/2)} fontFamily="var(--mono)" fontSize="10" fill="var(--text-3)" textAnchor="middle" letterSpacing="2"
        transform={`rotate(-90 14 ${H/2})`}>↑ ACTIVE CHATTERS</text>
      {dots.map((d, i) => (
        <circle key={i} cx={sx(d.v)} cy={sy(Math.max(d.c, 2))} r={2.4} fill={colorFor(d.k)} opacity={d.k === 'legit' ? 0.55 : 0.85}/>
      ))}
      {highlight && (
        <g>
          <circle cx={sx(highlight.viewers)} cy={sy(Math.max(highlight.chatters, 2))} r={8} fill="none" stroke="var(--red)" strokeWidth="1.4"/>
          <circle cx={sx(highlight.viewers)} cy={sy(Math.max(highlight.chatters, 2))} r={4} fill="var(--red)"/>
          <line x1={sx(highlight.viewers)+12} y1={sy(Math.max(highlight.chatters, 2))}
                x2={sx(highlight.viewers)+58} y2={sy(Math.max(highlight.chatters, 2))-20}
                stroke="var(--red)" strokeWidth="1"/>
          <text x={sx(highlight.viewers)+62} y={sy(Math.max(highlight.chatters, 2))-22} fontFamily="var(--mono)" fontSize="10.5" fill="var(--red)" letterSpacing="0.5">
            {highlight.user}
          </text>
          <text x={sx(highlight.viewers)+62} y={sy(Math.max(highlight.chatters, 2))-10} fontFamily="var(--mono)" fontSize="9.5" fill="rgba(255,90,95,0.7)" letterSpacing="0.5">
            {fmt(highlight.viewers)}v · {fmt(highlight.chatters)}c · {highlight.eng}%
          </text>
        </g>
      )}
    </svg>
  );
}

// ───────── small spark for hero ─────────
function HeroSpark() {
  // a dramatic flat red line over a wobbling green peer
  const W = 520, H = 260;
  const N = 90;
  const wob = useMemo(() => {
    const r = rng(13);
    return Array.from({length: N}, (_, i) => 70 + Math.sin(i/8)*40 + Math.cos(i/4)*20 + (r()-0.5)*22);
  }, []);
  const flat = useMemo(() => {
    const r = rng(99);
    return Array.from({length: N}, () => 130 + (r()-0.5)*6);
  }, []);
  const sx = i => 30 + (i/(N-1)) * (W - 50);
  const sy = v => H - 30 - v;

  const dWob = wob.map((v, i) => (i===0?'M':'L') + sx(i) + ',' + sy(v)).join(' ');
  const dFlat = flat.map((v, i) => (i===0?'M':'L') + sx(i) + ',' + sy(v)).join(' ');

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" height="auto" style={{ display: 'block' }}>
      <defs>
        <linearGradient id="wobfill" x1="0" x2="0" y1="0" y2="1">
          <stop offset="0%" stopColor="var(--green)" stopOpacity="0.28"/>
          <stop offset="100%" stopColor="var(--green)" stopOpacity="0"/>
        </linearGradient>
        <linearGradient id="flatfill" x1="0" x2="0" y1="0" y2="1">
          <stop offset="0%" stopColor="var(--red)" stopOpacity="0.16"/>
          <stop offset="100%" stopColor="var(--red)" stopOpacity="0"/>
        </linearGradient>
      </defs>

      {/* grid */}
      {[0, 1, 2, 3].map(i => (
        <line key={i} x1={30} x2={W - 20} y1={50 + i*50} y2={50 + i*50}
          stroke="rgba(255,255,255,0.04)" strokeWidth="0.8"/>
      ))}

      {/* organic */}
      <path d={dWob + ` L${sx(N-1)},${H-30} L${sx(0)},${H-30} Z`} fill="url(#wobfill)"/>
      <path d={dWob} stroke="var(--green)" strokeWidth="1.4" fill="none"/>

      {/* flat synthetic */}
      <path d={dFlat + ` L${sx(N-1)},${H-30} L${sx(0)},${H-30} Z`} fill="url(#flatfill)"/>
      <path d={dFlat} stroke="var(--red)" strokeWidth="1.6" fill="none"/>

      {/* labels */}
      <g fontFamily="var(--mono)" fontSize="10" letterSpacing="0.6">
        <text x={W - 24} y={sy(wob[wob.length-1]) - 6} textAnchor="end" fill="var(--green)">REAL · σ²=0.42</text>
        <text x={W - 24} y={sy(flat[flat.length-1]) - 6} textAnchor="end" fill="var(--red)">SYNTHETIC · σ²=0.004</text>
      </g>

      <text x={30} y={H - 8} fontFamily="var(--mono)" fontSize="10" fill="var(--text-4)" letterSpacing="0.5">
        CCV · LAST 14 STREAMS · SAME CATEGORY
      </text>
    </svg>
  );
}

// ───────── ScoreRing component for dossier ─────────
function ScoreRing({ value, size = 60, label }) {
  const r = (size - 8) / 2;
  const c = 2 * Math.PI * r;
  const off = c - (value/100) * c;
  const color = value < 35 ? 'var(--red)' : value < 65 ? 'var(--yellow)' : 'var(--green)';
  return (
    <span style={{ display: 'inline-grid', placeItems: 'center', width: size, height: size }}>
      <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} style={{ gridArea: '1/1' }}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="rgba(255,255,255,0.06)" strokeWidth="3"/>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={color} strokeWidth="3"
          strokeDasharray={c} strokeDashoffset={off}
          strokeLinecap="round"
          transform={`rotate(-90 ${size/2} ${size/2})`}/>
      </svg>
      <span style={{ gridArea: '1/1', fontFamily: 'var(--mono)', fontSize: 12, color: 'var(--text)', fontVariantNumeric: 'tabular-nums' }}>
        {value}
      </span>
    </span>
  );
}

// expose
Object.assign(window, {
  Plat, Avatar, StatusBadge, RiskPill, Radar,
  ScoreViz, AudienceRigidity, ContextMatrix, PeerComovement, ViewerChatterPlot, HeroSpark, ScoreRing,
  fmt, pct,
});
