/* Sybilhunt — "Who are Sybils" page. window-mounted. Uses kit-components primitives. */

function WReveal({ children, delay = 0, y = 24 }) {
  const ref = React.useRef(null);
  const [shown, setShown] = React.useState(false);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => { if (e.isIntersecting) { setShown(true); io.unobserve(el); } });
    }, { threshold: 0.16 });
    io.observe(el);
    return () => io.disconnect();
  }, []);
  return (
    <div ref={ref} style={{ opacity: shown ? 1 : 0, transform: shown ? 'none' : `translateY(${y}px)`, transition: `opacity 620ms var(--ease-out) ${delay}ms, transform 620ms var(--ease-out) ${delay}ms` }}>
      {children}
    </div>
  );
}

function WCountUp({ to, prefix = '', suffix = '', dur = 1500, decimals = 0 }) {
  const ref = React.useRef(null);
  const [val, setVal] = React.useState(0);
  React.useEffect(() => {
    const el = ref.current;
    if (!el) return;
    let raf, started = false;
    const io = new IntersectionObserver((entries) => {
      entries.forEach((e) => {
        if (e.isIntersecting && !started) {
          started = true;
          const t0 = performance.now();
          const tick = (t) => {
            const p = Math.min(1, (t - t0) / dur);
            const eased = 1 - Math.pow(1 - p, 3);
            setVal(to * eased);
            if (p < 1) raf = requestAnimationFrame(tick);
          };
          raf = requestAnimationFrame(tick);
        }
      });
    }, { threshold: 0.5 });
    io.observe(el);
    return () => { io.disconnect(); cancelAnimationFrame(raf); };
  }, [to, dur]);
  const fmt = (n) => Number(n).toLocaleString('en-US', { minimumFractionDigits: decimals, maximumFractionDigits: decimals });
  return <span ref={ref} style={{ fontVariantNumeric: 'tabular-nums' }}>{prefix}{fmt(val)}{suffix}</span>;
}

function WNav({ onClick, active, children }) {
  return (
    <button onClick={onClick} style={{ background: 'none', border: 'none', cursor: active ? 'default' : 'pointer', fontFamily: 'var(--font-body)', fontWeight: active ? 700 : 500, fontSize: 15, color: active ? 'var(--text-strong)' : 'var(--text-muted)', padding: 0, transition: 'color var(--dur-fast)' }}
      onMouseEnter={(e) => { if (!active) e.currentTarget.style.color = 'var(--text-strong)'; }}
      onMouseLeave={(e) => { if (!active) e.currentTarget.style.color = 'var(--text-muted)'; }}>
      {children}
    </button>
  );
}

function WhoHeader() {
  const go = (page) => { window.location.href = page; };
  return (
    <header style={{ position: 'sticky', top: 0, zIndex: 100, height: 'var(--header-h)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 clamp(20px, 4vw, 48px)', background: 'rgba(250,250,250,0.88)', backdropFilter: 'blur(14px)', WebkitBackdropFilter: 'blur(14px)', borderBottom: '1px solid var(--border-subtle)' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 36 }}>
        <Logo size={28} onClick={() => go('index.html')} />
        <nav style={{ display: 'flex', alignItems: 'center', gap: 28 }}>
          <WNav onClick={() => go('index.html')}>Home</WNav>
          <WNav active>Who are Sybils</WNav>
          <WNav onClick={() => go('experience.html')}>Hunters</WNav>
          <WNav onClick={() => go('docs.html')}>Docs</WNav>
        </nav>
      </div>
      <Button variant="primary" size="sm" onClick={() => go('index.html')} rightIcon={<span>→</span>}>Start hunting</Button>
    </header>
  );
}

// ── Content data ─────────────────────────────────────────────
const WHO_TRAITS = [
  { t: 'One operator, many wallets', d: 'A single person (or a small team) controls dozens to thousands of addresses, each dressed up to look like an independent, ordinary user.' },
  { t: 'Manufactured activity', d: 'Wallets are drip-fed identical amounts, run the same swaps, bridge on the same days — just enough on-chain history to pass an eligibility filter.' },
  { t: 'Built to farm rewards', d: 'The goal isn\u2019t to use the product. It\u2019s to multiply one person\u2019s share of an airdrop, a grants pool, or an incentive program.' },
];

// Arbitrum airdrop — X-explore / WuBlockchain report, Mar 2023
const ARB_HEADLINE = [
  { v: <WCountUp to={253} suffix="M" />, label: 'ARB tokens claimed by Sybils', tone: 'coral', sub: '≈ 21.8% of the entire airdrop' },
  { v: <WCountUp to={148595} />, label: 'Sybil addresses identified', tone: 'coral', sub: 'out of 624,136 wallets analyzed' },
  { v: <WCountUp to={4000} prefix="~" />, label: 'Sybil communities', tone: 'default', sub: 'coordinated wallet clusters' },
  { v: <WCountUp to={279328} />, label: 'multi-account addresses', tone: 'default', sub: '≈ 47.96% of tokens — 557M ARB' },
];

const ARB_METHODS = [
  { tag: 'Via exchanges', addrs: '2,997', detail: 'addresses withdrew tiny, near-identical amounts from Binance (0.00114–0.00116 ETH, ~$2) over a 5-day window — then behaved in lockstep.', accent: 'var(--coral-300)' },
  { tag: 'Via bridges', addrs: '1,114', detail: 'addresses crossed to Arbitrum through the HOP bridge with fixed ~0.0025 ETH (~$4) deposits, and together collected 1.08M ARB.', accent: 'var(--cyan-300)' },
  { tag: 'Via smart contracts', addrs: '9,483', detail: 'donor addresses funded thousands of wallets through the Disperse contract — one seed paid 1,274 airdrop wallets — netting 10.98M ARB.', accent: 'var(--gold-400)' },
  { tag: 'Single actors', addrs: '198–202', detail: 'wallets run by one attacker earned 174,375 and 204,250 ARB by staying active after the snapshot and mirroring behaviour across Optimism.', accent: 'var(--signal-400)' },
];

function WhoApp() {
  const go = (page) => { window.location.href = page; };
  return (
    <div>
      <WhoHeader />

      {/* Hero */}
      <section style={{ padding: 'clamp(56px, 11vh, 132px) clamp(20px, 4vw, 48px) clamp(20px, 4vh, 40px)', maxWidth: 'var(--container-max)', margin: '0 auto' }}>
        <WReveal>
          <div className="sh-overline" style={{ marginBottom: 16 }}>Who are Sybils</div>
          <h1 style={{ fontSize: 'clamp(36px, 6vw, 68px)', maxWidth: 900, lineHeight: 1.03, letterSpacing: '-0.02em' }}>
            One person, pretending to be a crowd
          </h1>
          <p style={{ fontFamily: 'var(--font-body)', fontSize: 'clamp(17px, 2vw, 21px)', color: 'var(--text-muted)', marginTop: 22, maxWidth: 760, lineHeight: 1.6 }}>
            A <strong style={{ color: 'var(--text-strong)' }}>Sybil</strong> is a single actor who operates many wallets or accounts at once to impersonate a whole community of real users. When a project distributes rewards “per person,” the Sybil quietly collects dozens — or thousands — of shares that were meant for everyone else.
          </p>
        </WReveal>
      </section>

      {/* Traits */}
      <section style={{ padding: 'clamp(20px, 4vh, 48px) clamp(20px, 4vw, 48px) clamp(48px, 8vh, 96px)', maxWidth: 'var(--container-max)', margin: '0 auto' }}>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 18 }}>
          {WHO_TRAITS.map((c, i) => (
            <WReveal key={c.t} delay={i * 90}>
              <Card interactive padding={28} style={{ height: '100%' }}>
                <div style={{ fontFamily: 'var(--font-mono)', fontWeight: 700, fontSize: 22, color: 'var(--signal-500)' }}>{String(i + 1).padStart(2, '0')}</div>
                <div style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 21, color: 'var(--text-strong)', margin: '14px 0 10px', letterSpacing: '-0.01em' }}>{c.t}</div>
                <p style={{ fontSize: 15, color: 'var(--text-muted)', lineHeight: 1.6 }}>{c.d}</p>
              </Card>
            </WReveal>
          ))}
        </div>
      </section>

      {/* Case study intro */}
      <section style={{ padding: 'clamp(48px, 9vh, 110px) clamp(20px, 4vw, 48px) clamp(20px, 3vh, 32px)', maxWidth: 'var(--container-max)', margin: '0 auto' }}>
        <WReveal>
          <div className="sh-overline" style={{ marginBottom: 14, color: 'var(--coral-300)' }}>Case study · Arbitrum airdrop, March 2023</div>
          <h2 style={{ fontSize: 'clamp(28px, 4.4vw, 50px)', maxWidth: 820, lineHeight: 1.04, letterSpacing: '-0.02em' }}>
            What a real Sybil attack looks like
          </h2>
          <p style={{ fontSize: 'clamp(16px, 2vw, 19px)', color: 'var(--text-muted)', marginTop: 20, maxWidth: 760, lineHeight: 1.6 }}>
            When Arbitrum launched the ARB token, analysts at <strong style={{ color: 'var(--text-strong)' }}>X-explore</strong> and WuBlockchain ran the entire recipient list through a community-detection model. What they found is the clearest public picture of Sybil farming to date.
          </p>
        </WReveal>
      </section>

      {/* Headline stats — infographic */}
      <section style={{ padding: 'clamp(16px, 3vh, 32px) clamp(20px, 4vw, 48px) clamp(40px, 7vh, 80px)', maxWidth: 'var(--container-max)', margin: '0 auto' }}>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(230px, 1fr))', gap: 18 }}>
          {ARB_HEADLINE.map((s, i) => (
            <WReveal key={s.label} delay={i * 80}>
              <div style={{ background: 'var(--surface-card)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-lg)', padding: 28, height: '100%', boxShadow: 'var(--inset-hairline), var(--shadow-md)', borderTop: `3px solid ${s.tone === 'coral' ? 'var(--coral-300)' : 'var(--signal-500)'}` }}>
                <div style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 'clamp(34px, 5vw, 54px)', lineHeight: 1, letterSpacing: '-0.02em', color: s.tone === 'coral' ? 'var(--coral-300)' : 'var(--text-strong)' }}>{s.v}</div>
                <div style={{ fontSize: 15, color: 'var(--text-body)', fontWeight: 600, marginTop: 14, lineHeight: 1.4 }}>{s.label}</div>
                <div style={{ fontSize: 13, color: 'var(--text-faint)', marginTop: 6, fontFamily: 'var(--font-mono)' }}>{s.sub}</div>
              </div>
            </WReveal>
          ))}
        </div>
      </section>

      {/* Share bar visualization */}
      <section style={{ padding: 'clamp(20px, 4vh, 48px) clamp(20px, 4vw, 48px) clamp(48px, 8vh, 96px)', maxWidth: 'var(--container-max)', margin: '0 auto' }}>
        <WReveal>
          <div style={{ background: 'var(--surface-card)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-xl)', padding: 'clamp(28px, 4vw, 44px)', boxShadow: 'var(--inset-hairline), var(--shadow-md)' }}>
            <div className="sh-overline" style={{ marginBottom: 20 }}>How the airdrop was split</div>
            <ShareBar />
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: '14px 28px', marginTop: 24 }}>
              <LegendDot color="var(--coral-300)" label="Confirmed Sybils — 21.8%" />
              <LegendDot color="#E9A23B" label="Other multi-account wallets — 26.2%" />
              <LegendDot color="var(--signal-500)" label="Genuine participants — 52%" />
            </div>
            <p style={{ fontSize: 14.5, color: 'var(--text-muted)', lineHeight: 1.6, marginTop: 22, maxWidth: 760 }}>
              Nearly half of every ARB address belonged to a same-person cluster, and more than one in five tokens went to a confirmed Sybil — value drained straight out of the pool intended for real users.
            </p>
          </div>
        </WReveal>
      </section>

      {/* Methods */}
      <section style={{ padding: 'clamp(20px, 4vh, 48px) clamp(20px, 4vw, 48px) clamp(56px, 9vh, 110px)', maxWidth: 'var(--container-max)', margin: '0 auto' }}>
        <WReveal>
          <div className="sh-overline" style={{ marginBottom: 14 }}>How they hid</div>
          <h2 style={{ fontSize: 'clamp(26px, 3.6vw, 42px)', maxWidth: 680, lineHeight: 1.05 }}>Four tricks used to dodge detection</h2>
        </WReveal>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))', gap: 18, marginTop: 40 }}>
          {ARB_METHODS.map((m, i) => (
            <WReveal key={m.tag} delay={i * 80}>
              <div style={{ position: 'relative', background: 'var(--surface-card)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-lg)', padding: 26, height: '100%', boxShadow: 'var(--inset-hairline), var(--shadow-md)' }}>
                <div style={{ display: 'inline-block', fontFamily: 'var(--font-mono)', fontSize: 12, letterSpacing: '0.06em', textTransform: 'uppercase', color: m.accent, border: `1px solid ${m.accent}`, borderRadius: 'var(--radius-pill)', padding: '4px 12px' }}>{m.tag}</div>
                <div style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 'clamp(28px, 4vw, 40px)', color: 'var(--text-strong)', margin: '18px 0 4px', letterSpacing: '-0.02em' }}>{m.addrs}</div>
                <div style={{ fontSize: 12.5, color: 'var(--text-faint)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.05em', marginBottom: 12 }}>addresses</div>
                <p style={{ fontSize: 14.5, color: 'var(--text-muted)', lineHeight: 1.55 }}>{m.detail}</p>
              </div>
            </WReveal>
          ))}
        </div>
      </section>

      {/* Why it matters + CTA */}
      <section style={{ padding: '0 clamp(20px, 4vw, 48px) clamp(56px, 9vh, 110px)' }}>
        <WReveal>
          <div style={{ position: 'relative', overflow: 'hidden', maxWidth: 'var(--container-max)', margin: '0 auto', borderRadius: 'var(--radius-2xl)', padding: 'clamp(40px, 7vw, 80px) clamp(28px, 5vw, 72px)', textAlign: 'center', background: 'linear-gradient(135deg, #0F1A12 0%, #15301B 60%, #0E2A18 100%)', boxShadow: '0 30px 80px rgba(15,40,22,0.28)' }}>
            <div aria-hidden="true" style={{ position: 'absolute', inset: 0, background: 'radial-gradient(680px 340px at 50% -10%, rgba(125,200,32,0.30), transparent 60%)', pointerEvents: 'none' }} />
            <div style={{ position: 'relative' }}>
              <h2 style={{ fontSize: 'clamp(28px, 5vw, 52px)', lineHeight: 1.05, letterSpacing: '-0.02em', color: '#fff', maxWidth: 780, margin: '0 auto' }}>Every Sybil caught is value returned to real people</h2>
              <p style={{ fontSize: 'clamp(16px, 2vw, 19px)', color: 'rgba(255,255,255,0.72)', marginTop: 16, maxWidth: 560, marginInline: 'auto' }}>The Arbitrum case was found after the fact. On Sybilhunt, you catch them before the pool is paid out — and take a share for it.</p>
              <div style={{ display: 'flex', justifyContent: 'center', marginTop: 32 }}>
                <Button variant="primary" size="lg" onClick={() => go('index.html')} rightIcon={<span>→</span>} style={{ height: 60, fontSize: 18, padding: '0 38px' }}>Start hunting Sybils</Button>
              </div>
            </div>
          </div>
        </WReveal>
      </section>

      <footer style={{ borderTop: '1px solid var(--border-subtle)', padding: 'clamp(36px, 6vh, 64px) clamp(20px, 4vw, 48px)', background: '#F0F2F0' }}>
        <div style={{ maxWidth: 'var(--container-max)', margin: '0 auto', display: 'flex', flexWrap: 'wrap', gap: 20, justifyContent: 'space-between', alignItems: 'center' }}>
          <div>
            <Logo size={26} onClick={() => go('index.html')} />
            <p style={{ fontSize: 13, color: 'var(--text-faint)', marginTop: 14, maxWidth: 420 }}>Case-study figures: X-explore &amp; WuBlockchain analysis of the Arbitrum airdrop, March 2023. © 2026 Sybilhunt.</p>
          </div>
          <WNav onClick={() => go('index.html')}>← Back to home</WNav>
        </div>
      </footer>
    </div>
  );
}

function LegendDot({ color, label }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
      <span style={{ width: 12, height: 12, borderRadius: 3, background: color, flex: 'none' }}></span>
      <span style={{ fontSize: 14, color: 'var(--text-body)' }}>{label}</span>
    </div>
  );
}

function ShareBar() {
  const ref = React.useRef(null);
  const [go, setGo] = React.useState(false);
  React.useEffect(() => {
    const el = ref.current; if (!el) return;
    const io = new IntersectionObserver((es) => es.forEach((e) => { if (e.isIntersecting) { setGo(true); io.unobserve(el); } }), { threshold: 0.4 });
    io.observe(el); return () => io.disconnect();
  }, []);
  const seg = (w, c, delay) => ({ width: go ? w : '0%', background: c, height: '100%', transition: `width 1100ms var(--ease-out) ${delay}ms` });
  return (
    <div ref={ref} style={{ display: 'flex', width: '100%', height: 46, borderRadius: 'var(--radius-md)', overflow: 'hidden', background: 'var(--surface-raised)' }}>
      <div style={seg('21.8%', 'var(--coral-300)', 0)} />
      <div style={seg('26.2%', '#E9A23B', 180)} />
      <div style={seg('52%', 'var(--signal-500)', 360)} />
    </div>
  );
}

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