/* Sybilhunt — "Sybilhunters' experience" page. window-mounted. Uses kit-components primitives. */

function EReveal({ 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.14 });
    io.observe(el);
    const fb = setTimeout(() => setShown(true), 240);
    return () => { io.disconnect(); clearTimeout(fb); };
  }, []);
  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 ECountUp({ 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 ENav({ 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 ExpHeader() {
  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 }}>
          <ENav onClick={() => go('index.html')}>Home</ENav>
          <ENav onClick={() => go('who.html')}>Who are Sybils</ENav>
          <ENav active>Hunters</ENav>
          <ENav onClick={() => go('docs.html')}>Docs</ENav>
        </nav>
      </div>
      <Button variant="primary" size="sm" onClick={() => go('index.html')} rightIcon={<span>→</span>}>Start hunting</Button>
    </header>
  );
}

// ── Aggregate stats ──────────────────────────────────────────
const EXP_TOTALS = [
  { v: <ECountUp to={1.9} decimals={1} prefix="$" suffix="M" />, label: 'paid to hunters', tone: 'signal' },
  { v: <ECountUp to={38} suffix="k" />, label: 'Sybil wallets exposed', tone: 'default' },
  { v: <ECountUp to={210} prefix="+" />, label: 'confirmed clusters', tone: 'default' },
  { v: <ECountUp to={94} suffix="%" />, label: 'submissions confirmed', tone: 'default' },
];

// ── Hunter stories ───────────────────────────────────────────
const HUNTERS = [
  {
    handle: ' mule.eth ', tag: 'crypt0zeke',
    campaign: 'Hop Protocol · bridge hunt', wallets: '1,142', payout: '$84,300', accent: 'var(--signal-500)',
    hook: 'Fixed-amount bridge deposits gave a whole farm away.',
    story: 'Noticed 1,142 wallets that each bridged exactly 0.0025 ETH within the same 5-day window, then made identical first swaps. Clustered them by shared funding hops and submitted on day two of the campaign — the earliest report on that farm.',
    method: ['Bridge-deposit fingerprint', 'Shared funder graph', 'Timing correlation'],
  },
  {
    handle: 'bitblondy', tag: 'Optimism RetroPGF',
    campaign: 'Optimism · airdrop filter', wallets: '2,760', payout: '$121,000', accent: 'var(--coral-500)',
    hook: 'One CEX withdrawal pattern unravelled 2,760 addresses.',
    story: 'Pulled every wallet that withdrew $2.01 of ETH from the same Binance sub-account across 72 hours. The amounts were too consistent to be human. Cross-checked contract-call order to rule out false positives, then packaged the cluster.',
    method: ['CEX-withdrawal clustering', 'Amount variance analysis', 'Call-order matching'],
  },
  {
    handle: 'sonar_ghost', tag: 'anon',
    campaign: 'zkSync · sybil sweep', wallets: '640', payout: '$47,900', accent: 'var(--cyan-500)',
    hook: 'A recycled gas wallet linked 640 “strangers”.',
    story: 'Every wallet in the cluster topped up gas from a single relayer, then returned the dust to it after the snapshot. That round-trip is invisible on a per-wallet view — only the money graph exposes it.',
    method: ['Gas round-trip trace', 'Dust-return detection', 'Relayer attribution'],
  },
  {
    handle: 'clusterfox', tag: 'anon',
    campaign: 'EigenLayer · restaking', wallets: '3,410', payout: '$260,000', accent: 'var(--gold-400)',
    hook: 'The biggest single payout on the platform so far.',
    story: 'A restaking farm split 3,410 wallets across three bridges to look independent. clusterfox joined records from Arbitrum, Optimism and Base — identical restake timing across chains was the tell — and proved one operator behind all of them.',
    method: ['Cross-chain join', 'Restake-timing match', 'Multi-bridge de-anon'],
  },
  {
    handle: 'lyra.base', tag: 'anon',
    campaign: 'Gitcoin · grants round', wallets: '880', payout: '$36,500', accent: 'var(--signal-500)',
    hook: 'Fake donors inflating a matching pool, caught in a day.',
    story: 'Spotted 880 “donors” that all funded from one Disperse contract minutes before the round opened and gave identical amounts to the same three projects. Quadratic funding punishes this hard — the cluster was confirmed within hours.',
    method: ['Disperse-contract trace', 'Donation-pattern match', 'Quadratic anomaly flag'],
  },
  {
    handle: 'nullroute', tag: 'anon',
    campaign: 'Polymarket · active hunt', wallets: '1,530', payout: '$96,700', accent: 'var(--coral-500)',
    hook: 'Wash-trading loops hiding as organic volume.',
    story: 'Mapped 1,530 accounts trading the same markets against each other in tight loops to fake volume. The positions always netted to zero across the ring. nullroute reconstructed the loop graph and filed before the incentive snapshot closed.',
    method: ['Wash-loop reconstruction', 'Net-position analysis', 'Counterparty ring graph'],
  },
];

function initials(h) {
  const s = h.trim().replace(/[._]/g, ' ').split(' ').filter(Boolean);
  return (s[0][0] + (s[1] ? s[1][0] : s[0][1] || '')).toUpperCase();
}

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

      {/* Hero */}
      <section style={{ padding: 'clamp(56px, 11vh, 132px) clamp(20px, 4vw, 48px) clamp(20px, 4vh, 40px)', maxWidth: 'var(--container-max)', margin: '0 auto' }}>
        <EReveal>
          <div className="sh-overline" style={{ marginBottom: 16 }}>Sybilhunters' experience</div>
          <h1 style={{ fontSize: 'clamp(36px, 6vw, 68px)', maxWidth: 900, lineHeight: 1.03, letterSpacing: '-0.02em' }}>
            Real hunters. Real clusters. Real payouts.
          </h1>
          <p style={{ fontFamily: 'var(--font-body)', fontSize: 'clamp(17px, 2vw, 21px)', color: 'var(--text-muted)', marginTop: 22, maxWidth: 760, lineHeight: 1.6 }}>
            Every reward on Sybilhunt is earned by exposing a specific, provable cluster. Here are six hunters — how they spotted the pattern, how many wallets they unmasked, and what the pool paid them for it.
          </p>
        </EReveal>
      </section>

      {/* Totals */}
      <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(200px, 1fr))', gap: 18 }}>
          {EXP_TOTALS.map((s, i) => (
            <EReveal key={s.label} delay={i * 80}>
              <div style={{ 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)', borderTop: `3px solid ${s.tone === 'signal' ? 'var(--signal-500)' : 'var(--border-strong)'}` }}>
                <div style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 'clamp(32px, 4.6vw, 50px)', lineHeight: 1, letterSpacing: '-0.02em', color: s.tone === 'signal' ? 'var(--signal-400)' : 'var(--text-strong)' }}>{s.v}</div>
                <div style={{ fontSize: 14.5, color: 'var(--text-muted)', fontWeight: 600, marginTop: 14, lineHeight: 1.4 }}>{s.label}</div>
              </div>
            </EReveal>
          ))}
        </div>
      </section>

      {/* Hunter stories */}
      <section style={{ padding: 'clamp(20px, 4vh, 48px) clamp(20px, 4vw, 48px) clamp(56px, 9vh, 110px)', maxWidth: 'var(--container-max)', margin: '0 auto' }}>
        <EReveal>
          <div className="sh-overline" style={{ marginBottom: 14 }}>Hunt logs</div>
          <h2 style={{ fontSize: 'clamp(26px, 3.6vw, 44px)', maxWidth: 680, lineHeight: 1.05 }}>Six catches, six methods</h2>
        </EReveal>
        <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(340px, 1fr))', gap: 20, marginTop: 44 }}>
          {HUNTERS.map((h, i) => (
            <EReveal key={h.handle} delay={(i % 2) * 90}>
              <article style={{ display: 'flex', flexDirection: 'column', height: '100%', background: 'var(--surface-card)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-xl)', padding: 'clamp(24px, 3vw, 32px)', boxShadow: 'var(--inset-hairline), var(--shadow-md)' }}>
                {/* Head: avatar + handle + campaign */}
                <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
                  <div style={{ flex: 'none', width: 48, height: 48, borderRadius: '50%', display: 'grid', placeItems: 'center', fontFamily: 'var(--font-mono)', fontWeight: 700, fontSize: 16, color: h.accent, background: `color-mix(in oklab, ${h.accent} 14%, var(--surface-raised))`, border: `1px solid color-mix(in oklab, ${h.accent} 40%, transparent)` }}>{initials(h.handle)}</div>
                  <div style={{ minWidth: 0 }}>
                    <div style={{ fontFamily: 'var(--font-mono)', fontWeight: 700, fontSize: 16, color: 'var(--text-strong)' }}>{h.handle.trim()}</div>
                    <div style={{ fontSize: 13, color: 'var(--text-faint)' }}>{h.campaign}</div>
                  </div>
                </div>

                <p style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 'clamp(18px, 2.2vw, 21px)', color: 'var(--text-strong)', margin: '22px 0 10px', lineHeight: 1.3, letterSpacing: '-0.01em' }}>{h.hook}</p>
                <p style={{ fontSize: 15, color: 'var(--text-muted)', lineHeight: 1.62, flex: '1 1 auto' }}>{h.story}</p>

                {/* Method tags */}
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, margin: '20px 0 22px' }}>
                  {h.method.map((m) => (
                    <span key={m} style={{ fontSize: 12, fontFamily: 'var(--font-mono)', color: 'var(--text-faint)', background: 'var(--surface-raised)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-pill)', padding: '5px 11px' }}>{m}</span>
                  ))}
                </div>

                {/* Result strip */}
                <div style={{ display: 'flex', gap: 14, paddingTop: 20, borderTop: '1px solid var(--border-subtle)' }}>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 'clamp(20px, 2.6vw, 26px)', color: 'var(--text-strong)', letterSpacing: '-0.02em' }}>{h.wallets}</div>
                    <div className="sh-overline" style={{ fontSize: 10, marginTop: 4 }}>wallets exposed</div>
                  </div>
                  <div style={{ flex: 1, textAlign: 'right' }}>
                    <div style={{ fontFamily: 'var(--font-display)', fontWeight: 700, fontSize: 'clamp(20px, 2.6vw, 26px)', color: 'var(--signal-400)', letterSpacing: '-0.02em' }}>{h.payout}</div>
                    <div className="sh-overline" style={{ fontSize: 10, marginTop: 4 }}>reward paid</div>
                  </div>
                </div>
              </article>
            </EReveal>
          ))}
        </div>
      </section>

      {/* CTA */}
      <section style={{ padding: '0 clamp(20px, 4vw, 48px) clamp(56px, 9vh, 110px)' }}>
        <EReveal>
          <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' }}>Your hunt log starts here</h2>
              <p style={{ fontSize: 'clamp(16px, 2vw, 19px)', color: 'rgba(255,255,255,0.72)', marginTop: 16, maxWidth: 540, marginInline: 'auto' }}>The active Polymarket campaign is open now. Trace a cluster, submit the proof, claim your share.</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>
        </EReveal>
      </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: 440 }}>Hunter stories are illustrative composites of real Sybil-detection techniques. Handles anonymized. © 2026 Sybilhunt.</p>
          </div>
          <ENav onClick={() => go('index.html')}>← Back to home</ENav>
        </div>
      </footer>
    </div>
  );
}

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