/* ==========================================================================
   bdk-site.jsx — the browsable website shell that wraps KevOS.
   Default experience = website (nav + pages). "Desktop mode" (#/os) boots the
   original OS (<BdkApp/>, defined in bdk-main.jsx). Feature pages REUSE the
   existing app components (LeaderApp, TiersApp, ChallengeApp, ClipsApp,
   FreeMoneyApp, TierMatchApp) — no rewrite. Loaded after bdk-apps/bdk-toys and
   before bdk-main (which renders <BdkRoot/>).
   ========================================================================== */
(function () {
  const SB = 'https://qfsxpgacusbkevkzpsfj.supabase.co';
  const ANON = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFmc3hwZ2FjdXNia2V2a3pwc2ZqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODE0OTA1MjAsImV4cCI6MjA5NzA2NjUyMH0.lWAl0CjKMFEZncmSUohICospb0-sHjyFMznKiGgAC9Y';
  const HDR = { apikey: ANON, Authorization: 'Bearer ' + ANON };
  const AFF = 'https://yeet.com/register?aff=BDKev';
  const BGVID = '/backgrounds/bg-11.mp4';
  const BGPOSTER = '/backgrounds/bg-11.png';
  const CREW = '/overlay/assets/crew.png';
  // Kev's real numbers, baseline as of June 15, 2026 (YEET username: KevTheDegen).
  // Future tips he sends in YEET chat can be auto-added to GIVEN_AWAY later.
  const KSTATS = { deposits: 197593.43, withdrawals: 212969.16, netUp: 15375.73, tipsSent: 15587.09, lbGiven: 6000, since: 'June 15' };
  const GIVEN_AWAY = KSTATS.tipsSent + KSTATS.lbGiven;
  // The firehose already held $1,882 of Kev's tips at baseline time; only count
  // live tips ABOVE that anchor on top of the baseline, so nothing double-counts.
  const TIPS_ANCHOR = 1882;
  function usd(n) { return '$' + Math.round(Number(n) || 0).toLocaleString('en-US'); }

  const NAV = [['', 'Home'], ['games', 'Games'], ['bounties', 'Bounties'], ['leaderboard', 'Leaderboard'], ['rewards', 'Rewards'], ['clips', 'Clips'], ['wiki', 'Wiki'], ['pnl', 'P&L']];

  // Kev's curated lists (casino_game_id). Add ids here to populate the filters,
  // or we wire an admin toggle later. Reviewed fills in as clips get attached.
  const KEV_FAV = [];
  const KEV_REVIEWED = [];
  const PROVIDERS = ['155io', '1spin4win', '3oaks', '7rings', 'Amigo', 'Animo Live', 'Avatarux', 'Aviatrix', 'BackseatGaming', 'Barbara Bang', 'Belatra', 'Bgaming', 'Big Daddy Gaming', 'Blueprint', 'Bsg', 'Clutch Gaming', 'Degen Studios', 'ELA Games', 'Endorphina', 'Evolution', 'Fantasma', 'Fcgaming', 'Groove', 'Habanero', 'Hacksaw', 'Happy Duck', 'Justslots', 'Mancala', 'NE Games', 'Netent', 'Nolimit', 'Novomatic', 'Onetouch', 'Penguinking', 'Peterandsons', 'Pgsoft', 'Playngo', 'Playtech', 'Popiplay', 'Pragmatic', 'Quickspin', 'Redtiger', 'Relax Gaming', 'ShadyLady', 'Slotmill', 'Spnmnl', 'Spribe', 'Tadagaming', 'Thunderkick', 'True Labs', 'Wicked Games', 'Yeet', 'Yggdrasil'];

  function money(v) {
    if (v == null || v === '') return '';
    var n = Number(v); if (!isFinite(n)) return '';
    if (Math.abs(n) >= 100) return '$' + Math.round(n).toLocaleString('en-US');
    return '$' + n.toLocaleString('en-US', { maximumFractionDigits: 2 });
  }
  function num(v) { return Math.round(Number(v) || 0).toLocaleString('en-US'); }
  function go(r) { location.hash = r ? ('#/' + r) : '#/'; }
  function useRoute() {
    const parse = () => (location.hash || '').replace(/^#\/?/, '').split('?')[0];
    const [r, setR] = React.useState(parse);
    React.useEffect(() => {
      const f = () => setR(parse());
      window.addEventListener('hashchange', f);
      return () => window.removeEventListener('hashchange', f);
    }, []);
    return r;
  }

  /* ---------- error boundary so one flaky app can't white-screen the site ---------- */
  class WsBoundary extends React.Component {
    constructor(p) { super(p); this.state = { err: null }; }
    static getDerivedStateFromError(e) { return { err: e }; }
    render() {
      if (this.state.err) {
        return (
          <div className="ws-wrap" style={{ padding: '48px 22px' }}>
            <div className="ws-panel"><div className="ws-building">
              <div className="badge">🛠️</div>
              <h2>{this.props.title || 'This section'} is warming up</h2>
              <p>Give it a second and reload. It'll be right back.</p>
            </div></div>
          </div>
        );
      }
      return this.props.children;
    }
  }

  /* ---------- live cycling aura card (same look as the /aura overlay) ---------- */
  function WsAuraCard({ list }) {
    const [idx, setIdx] = React.useState(0);
    const [anim, setAnim] = React.useState('show');
    React.useEffect(() => { setIdx(0); }, [list.length]);
    React.useEffect(() => {
      if (!list || list.length < 2) return;
      const iv = setInterval(() => {
        setAnim('hide');
        setTimeout(() => { setIdx((i) => (i + 1) % list.length); setAnim('show'); }, 430);
      }, 3600);
      return () => clearInterval(iv);
    }, [list]);
    if (!list || !list.length) return null;
    const a = list[idx % list.length];
    const n = num(a.multiplier);
    const ns = n.replace(/[^0-9]/g, '').length <= 3 ? 52 : n.length <= 4 ? 46 : n.length <= 5 ? 38 : 32;
    return (
      <div className={'ws-acard ' + anim} key={idx}>
        <div className="ws-athumb" style={a.thumbnail ? { backgroundImage: 'url(' + a.thumbnail + ')' } : {}}>
          {a.thumbnail ? null : <div className="ph"><span></span><span className="g">{a.game_name}</span></div>}
        </div>
        <div className="ws-ainfo">
          <span className="ws-atag">⚡ Aura Hit</span>
          <div>
            <div className="ws-auser">{a.username === 'hidden' ? 'Hidden' : a.username}</div>
            <div className="ws-amult" style={{ '--ns': ns + 'px' }}><span className="n">{n}</span><span className="x">×</span></div>
          </div>
          <div className="ws-astat">
            <div className="ws-acell"><i>Bet</i><b>{money(a.fiat_bet)}</b></div>
            <div className="ws-acell win"><i>Won</i><b>{money(a.fiat_win)}</b></div>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- nav ---------- */
  function WsNav({ route }) {
    const [open, setOpen] = React.useState(false);
    return (
      <React.Fragment>
        <nav className="ws-nav"><div className="ws-wrap ws-nav-in">
          <div className="ws-brand" onClick={() => go('')}><span className="dot"></span>BizDev<span className="ws-grad">Kev</span></div>
          <div className="ws-links">{NAV.map(([k, l]) => <a key={k} className={route === k ? 'on' : ''} onClick={() => go(k)}>{l}</a>)}</div>
          <div className="ws-nav-right">
            <a className="ws-ghost" onClick={() => go('os')} title="Boot the classic KevOS desktop">▦ Desktop mode</a>
            <a className="ws-btn" href={AFF} target="_blank" rel="noopener">Join YEET →</a>
            <button className="ws-burger" onClick={() => setOpen((o) => !o)} aria-label="Menu">☰</button>
          </div>
        </div></nav>
        {open && (
          <div className="ws-mobnav" style={{ display: 'flex' }}>
            {NAV.concat([['os', '▦ Desktop mode']]).map(([k, l]) => <a key={k} className={route === k ? 'on' : ''} onClick={() => { go(k); setOpen(false); }}>{l}</a>)}
            <a href={AFF} target="_blank" rel="noopener" style={{ color: 'var(--ws-accent2)' }}>Join YEET →</a>
          </div>
        )}
      </React.Fragment>
    );
  }

  /* ---------- "given back" showcase + raw-balance ---------- */
  function WsGiveBack() {
    const [given, setGiven] = React.useState(GIVEN_AWAY);
    React.useEffect(() => {
      let al = true;
      fetch(SB + '/rest/v1/kev_tips?select=tips_total&id=eq.1', { headers: HDR })
        .then((r) => (r.ok ? r.json() : [])).then((d) => { if (al && Array.isArray(d) && d[0]) { const live = Number(d[0].tips_total) || 0; setGiven(GIVEN_AWAY + Math.max(0, live - TIPS_ANCHOR)); } }).catch(() => {});
      return () => { al = false; };
    }, []);
    return (
      <section className="ws-section" style={{ paddingTop: 8 }}><div className="ws-wrap">
        <div className="ws-give">
          <div className="ws-give-main">
            <div className="ws-seg-eyebrow" style={{ marginBottom: 10 }}>Given back to the crew</div>
            <div className="ws-give-amt">{usd(given)}<span>+</span></div>
            <p className="ws-give-sub">Real money, straight to the community, and climbing.</p>
          </div>
          <div className="ws-give-side">
            <div className="ws-rawbadge">
              <div className="rb-top">RAW BALANCE · ZERO FILL</div>
              <p>Every dollar you see me play is my own deposited money. No casino fill, no comped balance, no fake stacks. Real risk on every spin.</p>
              <a className="ws-give-pnl" onClick={() => go('pnl')}>Up {usd(KSTATS.netUp)} lifetime · see my P&amp;L →</a>
            </div>
          </div>
        </div>
      </div></section>
    );
  }

  /* ---------- home ---------- */
  function WsHome() {
    const [auras, setAuras] = React.useState([]);
    const [copied, setCopied] = React.useState(false);
    React.useEffect(() => {
      let al = true;
      const load = () => fetch(SB + '/rest/v1/yeet_auras?select=username,game_name,multiplier,fiat_bet,fiat_win,thumbnail&order=id.desc&limit=20', { headers: HDR })
        .then((r) => r.json()).then((rows) => { if (al && Array.isArray(rows)) setAuras(rows); }).catch(() => {});
      load(); const iv = setInterval(load, 30000);
      return () => { al = false; clearInterval(iv); };
    }, []);
    const copy = () => { try { navigator.clipboard.writeText('BDKev'); setCopied(true); setTimeout(() => setCopied(false), 1400); } catch (e) {} };
    const tick = auras.length ? auras.concat(auras).map((a, i) => (
      <span key={i}><b>{a.username === 'hidden' ? 'Hidden' : a.username}</b> · {num(a.multiplier)}× · {a.game_name} · <em>{money(a.fiat_win)}</em></span>
    )) : null;
    return (
      <React.Fragment>
        <header className="ws-hero">
          <div className="ws-wrap ws-hero-grid">
            <div>
              <span className="ws-eyebrow"><span className="pip"></span>YEET Partner · Code BDKev</span>
              <h1 className="ws-h1">Play smarter on <img className="ws-yeetlogo" src="/bdk/assets/yeet/yeet-logo.svg" alt="YEET" /> with the <em>whole crew</em> behind you.</h1>
              <p className="ws-lede">Every game rated and reviewed, a live wager race, real bounties, and my own P&amp;L on the table. Nothing hidden. Join under my code and you're in.</p>
              <div className="ws-hero-cta">
                <a className="ws-cta-lg" href={AFF} target="_blank" rel="noopener">JOIN YEET → CLAIM BONUS</a>
                <div className="ws-code"><div><div className="lbl">Code</div><div className="val">BDKev</div></div><button className="cp" onClick={copy}>{copied ? 'COPIED' : 'COPY'}</button></div>
              </div>
              <div className="ws-hstats">
                <div className="ws-hstat"><div className="n g">$3,000+</div><div className="k">Monthly leaderboards</div></div>
                <div className="ws-hstat"><div className="n">6,218</div><div className="k">Games reviewed, 1 by 1</div></div>
                <div className="ws-hstat"><div className="n">Top 8</div><div className="k">Paid every race</div></div>
              </div>
            </div>
            <div className="ws-aura-wrap">
              <div className="ws-aura-label"><span className="live"></span>Live big wins on YEET</div>
              <div className="ws-aurastage"><WsAuraCard list={auras} /></div>
            </div>
          </div>
          {tick && <div className="ws-ticker"><div className="run">{tick}</div></div>}
        </header>

        <section className="ws-section"><div className="ws-wrap">
          <div className="ws-seg-head">
            <div className="ws-seg-eyebrow">Everything, one tap away</div>
            <h2 className="ws-seg-h">Browse the whole <span className="ws-grad">operation</span></h2>
            <p className="ws-seg-sub">The parts that used to be app windows on a desktop, now real pages you can actually find on your phone.</p>
          </div>
          <div className="ws-feat">
            <div className="ws-card wide" onClick={() => go('games')}>
              <div className="ws-wheel"></div>
              <div className="ic">🎰</div>
              <h3>The Games <span className="ws-chip live">Live</span></h3>
              <p>Every one of YEET's games, with RTP, provider, and my review coming per game. Can't decide? Spin the wheel and it picks my next game.</p>
              <div className="ws-metric">
                <div className="m"><div className="n g">6,218</div><div className="k">Games in the database</div></div>
                <div className="m"><div className="n">Wheel</div><div className="k">Picks my next play</div></div>
              </div>
              <span className="go">Browse games <span className="arw">→</span></span>
              <img className="ws-card-mascot" src="/bdk/assets/crew/mascot-4.png" alt="" />
            </div>
            <div className="ws-card" onClick={() => go('leaderboard')}><div className="ic">▲</div><h3>Wager Race <span className="ws-chip warn">Paused</span></h3><p>The biweekly $ race under code BDKev. Top 8 paid, standings pulled live from YEET.</p><span className="go">View leaderboard <span className="arw">→</span></span></div>
            <div className="ws-card" onClick={() => go('pnl')}><div className="ic">📈</div><h3>Kev's P&amp;L <span className="ws-chip soon">Building</span></h3><p>Every bet, win and loss I make on stream, straight from the tape. Up bad or up good.</p><span className="go">See the damage <span className="arw">→</span></span><img className="ws-card-mascot" src="/bdk/assets/crew/mascot-1.png" alt="" /></div>
            <div className="ws-card" onClick={() => go('rewards')}><div className="ic">◇</div><h3>Rewards &amp; Tiers</h3><p>Cash back at every tier, plus tier match if you're already ranked somewhere else.</p><span className="go">See rewards <span className="arw">→</span></span></div>
            <div className="ws-card" onClick={() => go('bounties')}><div className="ic">🎯</div><h3>Bounties <span className="ws-chip live">Live</span></h3><p>Hit a target multiplier on the called game and I pay out. Wanted posters, real rewards.</p><span className="go">See bounties <span className="arw">→</span></span><img className="ws-card-mascot" src="/bdk/assets/crew/mascot-2.png" alt="" /></div>
            <div className="ws-card" onClick={() => go('clips')}><div className="ic">►</div><h3>Clips &amp; Reviews</h3><p>Me actually playing each game. The clip and my verdict on every game's page.</p><span className="go">Watch <span className="arw">→</span></span></div>
            <div className="ws-card" onClick={() => go('wiki')}><div className="ic">📖</div><h3>Wiki</h3><p>Provably fair, seeds, RTP, volatility, superstitions. The gambling knowledge worth knowing.</p><span className="go">Read up <span className="arw">→</span></span><img className="ws-card-mascot" src="/bdk/assets/crew/mascot-3.png" alt="" /></div>
          </div>
        </div></section>

        <WsGiveBack />

        <section className="ws-section" style={{ paddingTop: 6 }}><div className="ws-wrap">
          <div className="ws-band">
            <h2>Let's get you <span className="ws-grad">paid</span>.</h2>
            <p>Join YEET under code <b style={{ color: 'var(--ws-text)' }}>BDKev</b>, then DM me to claim your bonus. Takes 60 seconds · 18+.</p>
            <a className="ws-cta-lg" href={AFF} target="_blank" rel="noopener">JOIN YEET → CLAIM BONUS</a>
            <img className="crew" src={CREW} alt="The BizDevKev crew" />
          </div>
        </div></section>
      </React.Fragment>
    );
  }

  /* ---------- games (live grid + wheel) ---------- */
  // Many games 403 on their s6 art, so fall back s6 -> s3 -> branded placeholder.
  function WsGameImg({ g, className }) {
    const srcs = [g.image_s6, g.image_s3].filter(Boolean);
    const [i, setI] = React.useState(0);
    React.useEffect(() => { setI(0); }, [g.casino_game_id]);
    if (i >= srcs.length) {
      return (
        <div className="ws-artph">
          <span className="pp">{g.provider_name || (g.is_slot ? 'Slot' : 'Game')}</span>
          <span className="pn">{g.name}</span>
          <span className="plogo">YEET</span>
        </div>
      );
    }
    return <img className={className} src={srcs[i]} alt={g.name} loading="lazy" onError={() => setI((x) => x + 1)} />;
  }
  function WsGames() {
    const [rows, setRows] = React.useState([]);
    const [qi, setQi] = React.useState('');
    const [q, setQ] = React.useState('');
    const [sort, setSort] = React.useState('yeet_created_at.desc');
    const [limit, setLimit] = React.useState(48);
    const [total, setTotal] = React.useState(null);
    const [loading, setLoading] = React.useState(true);
    const [wheel, setWheel] = React.useState(false);
    const [filt, setFilt] = React.useState('all');   // all | fav | reviewed
    const [prov, setProv] = React.useState('');
    const [flags, setFlags] = React.useState(null);  // { fav:[ids], rev:[ids] } from kev_game_flags
    const providers = PROVIDERS;
    React.useEffect(() => {
      let al = true;
      fetch(SB + '/rest/v1/kev_game_flags?select=casino_game_id,is_favorite,is_reviewed', { headers: HDR })
        .then((r) => (r.ok ? r.json() : [])).then((d) => {
          if (!al) return; const fav = [], rev = [];
          (Array.isArray(d) ? d : []).forEach((x) => { if (x.is_favorite) fav.push(x.casino_game_id); if (x.is_reviewed) rev.push(x.casino_game_id); });
          setFlags({ fav: fav, rev: rev });
        }).catch(() => { if (al) setFlags({ fav: [], rev: [] }); });
      return () => { al = false; };
    }, []);
    React.useEffect(() => { const t = setTimeout(() => { setQ(qi); setLimit(48); }, 320); return () => clearTimeout(t); }, [qi]);
    const idList = filt === 'fav' ? (flags ? flags.fav : null) : filt === 'reviewed' ? (flags ? flags.rev : null) : null;
    const flagsLoading = (filt === 'fav' || filt === 'reviewed') && flags === null;
    const emptyFilter = !flagsLoading && idList !== null && idList.length === 0;
    React.useEffect(() => {
      if (flagsLoading) { setLoading(true); return; }
      if (emptyFilter) { setRows([]); setTotal(0); setLoading(false); return; }
      let al = true; setLoading(true);
      let url = SB + '/rest/v1/yeet_games?select=casino_game_id,name,url_id,provider_name,rtp,is_slot,image_s6,image_s3&order=' + sort + '&limit=' + limit;
      if (q.trim()) url += '&name=ilike.*' + encodeURIComponent(q.trim()) + '*';
      if (prov) url += '&provider_name=eq.' + encodeURIComponent(prov);
      if (idList) url += '&casino_game_id=in.(' + idList.join(',') + ')';
      fetch(url, { headers: Object.assign({}, HDR, { Prefer: 'count=exact', Range: '0-' + (limit - 1) }) })
        .then((r) => { const cr = r.headers.get('content-range'); if (cr) { const t = cr.split('/')[1]; if (t && t !== '*') setTotal(Number(t)); } return r.json(); })
        .then((d) => { if (al) { setRows(Array.isArray(d) ? d : []); setLoading(false); } })
        .catch(() => { if (al) setLoading(false); });
      return () => { al = false; };
    }, [q, sort, limit, prov, filt, emptyFilter, flagsLoading]);
    const chip = (key, label) => <span className={'ws-fchip' + (filt === key ? ' on' : '')} onClick={() => { setFilt(key); setLimit(48); }}>{label}</span>;
    const showTotal = total != null && filt === 'all' && !prov && !q.trim();
    return (
      <div className="ws-page"><div className="ws-wrap">
        <div className="ws-page-head">
          <h1>The Games</h1>
          <p>Every game on YEET, {showTotal ? total.toLocaleString() : '6,218'} and counting. My reviews and clips are rolling out per game.</p>
        </div>
        <div className="ws-fchips">
          {chip('all', 'All games')}
          {chip('fav', "⭐ Kev's Favorites")}
          {chip('reviewed', '✅ Reviewed')}
        </div>
        <div className="ws-tools">
          <div className="ws-search"><span style={{ opacity: .6 }}>🔍</span><input placeholder="Search games…" value={qi} onChange={(e) => setQi(e.target.value)} /></div>
          <select className="ws-select" value={prov} onChange={(e) => { setProv(e.target.value); setLimit(48); }}>
            <option value="">All providers</option>
            {providers.map((p) => <option key={p} value={p}>{p}</option>)}
          </select>
          <select className="ws-select" value={sort} onChange={(e) => { setSort(e.target.value); setLimit(48); }}>
            <option value="yeet_created_at.desc">Newest</option>
            <option value="name.asc">A–Z</option>
            <option value="rtp.desc.nullslast">Highest RTP</option>
            <option value="provider_name.asc">Provider</option>
          </select>
          <button className="ws-btn" onClick={() => setWheel(true)}>🎡 Spin</button>
          <span className="ws-gcount">{loading ? 'loading…' : rows.length + (total ? ' of ' + total.toLocaleString() : '') + ' shown'}</span>
        </div>
        {emptyFilter ? (
          <div className="ws-panel"><div className="ws-building">
            <div className="badge">{filt === 'fav' ? '⭐' : '✅'}</div>
            <h2>{filt === 'fav' ? 'No favorites picked yet' : 'No reviews yet'}</h2>
            <p>{filt === 'fav' ? "Kev's favorite slots will show up here soon." : 'Games I have reviewed on stream will land here as I play them.'}</p>
          </div></div>
        ) : (
          <React.Fragment>
            <div className="ws-gamegrid">
              {rows.map((g) => (
                <div className="ws-game" key={g.casino_game_id} onClick={() => go('game/' + g.casino_game_id)} title={g.name}>
                  <WsGameImg g={g} />
                  {g.rtp ? <span className="rtp">{Number(g.rtp).toFixed(2)}%</span> : null}
                </div>
              ))}
            </div>
            {!loading && total && rows.length < total ? (
              <div className="ws-more"><button className="ws-btn" onClick={() => setLimit((l) => l + 48)}>Load more games</button></div>
            ) : null}
          </React.Fragment>
        )}
        {wheel && <WsWheel onClose={() => setWheel(false)} maxId={total || 6218} />}
      </div></div>
    );
  }

  function WsWheel({ onClose, maxId }) {
    const [spinning, setSpinning] = React.useState(false);
    const [winner, setWinner] = React.useState(null);
    const [deg, setDeg] = React.useState(0);
    const spin = () => {
      if (spinning) return;
      setSpinning(true); setWinner(null);
      setDeg((d) => d + 360 * 5 + Math.floor(Math.random() * 360));
      const off = Math.floor(Math.random() * Math.max(1, maxId - 1));
      fetch(SB + '/rest/v1/yeet_games?select=casino_game_id,name,provider_name,rtp,image_s6,image_s3&order=casino_game_id.asc&limit=1&offset=' + off, { headers: HDR })
        .then((r) => r.json())
        .then((d) => { const g = Array.isArray(d) && d[0]; setTimeout(() => { setWinner(g || null); setSpinning(false); }, 4400); })
        .catch(() => setTimeout(() => setSpinning(false), 4400));
    };
    React.useEffect(() => { spin(); /* auto-spin on open */ }, []);
    return (
      <div className="ws-modal" onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}>
        <div className="ws-modal-card">
          <button className="ws-x" onClick={onClose} aria-label="Close">✕</button>
          <h3>Spin for my next game</h3>
          <div className="sub">The wheel picks from all {maxId.toLocaleString()} games on YEET.</div>
          <div className="ws-wheelbox">
            <div className="ws-wheelptr"></div>
            <div className="ws-wheelspin" style={{ transform: 'rotate(' + deg + 'deg)' }}></div>
            <div className="ws-wheelhub">{spinning ? '…' : 'SPIN'}</div>
          </div>
          {winner ? (
            <div className="ws-winner">
              {(winner.image_s6 || winner.image_s3) ? <img src={winner.image_s6 || winner.image_s3} alt={winner.name} /> : null}
              <div><div className="wn">{winner.name}</div><div className="wp">{winner.provider_name || ''}{winner.rtp ? ' · ' + Number(winner.rtp).toFixed(2) + '% RTP' : ''}</div></div>
            </div>
          ) : null}
          <div style={{ display: 'flex', gap: 10, justifyContent: 'center' }}>
            <button className="ws-ghost" onClick={spin} disabled={spinning}>{spinning ? 'Spinning…' : 'Spin again'}</button>
            {winner ? <a className="ws-btn" href={AFF} target="_blank" rel="noopener">Play on Yeet →</a> : null}
          </div>
        </div>
      </div>
    );
  }

  /* ---------- P&L (building) ---------- */
  function wsX(m) { m = Number(m) || 0; return (m >= 1000 ? Math.round(m).toLocaleString('en-US') : (Math.round(m * 10) / 10).toLocaleString('en-US')) + '×'; }
  function wsDT(t) { try { return new Date(t).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } catch (e) { return ''; } }
  function WsPnl() {
    const [live, setLive] = React.useState(null);
    React.useEffect(() => {
      let al = true;
      const load = () => fetch(SB + '/rest/v1/kev_pnl?select=wagered,won,net,bets,biggest,recent_wins&id=eq.1', { headers: HDR })
        .then((r) => (r.ok ? r.json() : [])).then((d) => { if (al && Array.isArray(d) && d[0]) setLive(d[0]); }).catch(() => {});
      load(); const iv = setInterval(load, 30000);
      return () => { al = false; clearInterval(iv); };
    }, []);
    const wins = (live && live.recent_wins) || [];
    return (
      <div className="ws-page"><div className="ws-wrap">
        <div className="ws-page-head"><h1>Kev's P&amp;L</h1><p>My real numbers on YEET as KevTheDegen. Raw balance, zero fill, nothing hidden.</p></div>
        <div className="ws-pnl-grid">
          <div className="ws-pnl-card big"><div className="k">Lifetime net</div><div className="v up">+{usd(KSTATS.netUp)}</div><div className="s">Lifetime withdrawn minus deposited. Up on YEET, and every dollar of it is real.</div></div>
          <div className="ws-pnl-card"><div className="k">Lifetime deposits</div><div className="v">{usd(KSTATS.deposits)}</div></div>
          <div className="ws-pnl-card"><div className="k">Lifetime withdrawals</div><div className="v">{usd(KSTATS.withdrawals)}</div></div>
          <div className="ws-pnl-card"><div className="k">Given to the crew</div><div className="v give">{usd(GIVEN_AWAY)}</div></div>
        </div>
        {live ? (
          <React.Fragment>
            <div className="ws-pnl-sub"><span className="live"></span>Live play stats · every bet I make, from the tape</div>
            <div className="ws-pnl-grid">
              <div className="ws-pnl-card"><div className="k">Total wagered</div><div className="v">{usd(live.wagered)}</div></div>
              <div className="ws-pnl-card"><div className="k">Total won</div><div className="v give">{usd(live.won)}</div></div>
              <div className="ws-pnl-card"><div className="k">Biggest win</div><div className="v g">{live.biggest ? wsX(live.biggest.m) : '—'}</div>{live.biggest ? <div className="s">{usd(live.biggest.w)} on {live.biggest.g}</div> : null}</div>
              <div className="ws-pnl-card"><div className="k">Bets tracked</div><div className="v">{Number(live.bets).toLocaleString()}</div></div>
            </div>
            <div className="ws-pnl-sub" style={{ marginTop: 26 }}><span className="live"></span>Latest wins · 50× and up</div>
            {wins.length ? (
              <div className="ws-winsfeed"><div className="ws-winstrack" style={{ animationDuration: Math.max(18, wins.length * 2.4) + 's' }}>
                {wins.concat(wins).map((w, i) => (
                  <div className="ws-winrow" key={i}>
                    <span className="wx">{wsX(w.m)}</span>
                    <span className="wg">{w.g || 'Unknown game'}<small>{wsDT(w.t)}</small></span>
                    <span className="ww">{usd(w.w)}</span>
                  </div>
                ))}
              </div></div>
            ) : (
              <div className="ws-panel"><div className="ws-building" style={{ padding: '24px' }}><div className="badge">🎰</div><h2>No 50× wins captured yet</h2><p>They land here the moment I hit one on stream.</p></div></div>
            )}
          </React.Fragment>
        ) : (
          <div className="ws-panel" style={{ marginTop: 18 }}><div className="ws-building" style={{ padding: '30px 24px' }}>
            <div className="badge">📈</div><h2>Loading the tape…</h2><p>Pulling my live play stats from YEET's feed.</p>
          </div></div>
        )}
      </div></div>
    );
  }

  /* ---------- generic page wrapper for reused OS apps ---------- */
  function WsPageApp({ title, sub, children }) {
    return (
      <div className="ws-page"><div className="ws-wrap">
        <div className="ws-page-head"><h1>{title}</h1>{sub ? <p>{sub}</p> : null}</div>
        <div className="ws-panel narrow" style={{ margin: '0 auto' }}>{children}</div>
      </div></div>
    );
  }

  function WsFooter() {
    return (
      <footer className="ws-footer"><div className="ws-wrap ws-foot">
        <div><div className="fb">BizDev<span className="ws-grad">Kev</span></div><div className="fn">Play for fun, not funds · 18+ · Gamble responsibly</div></div>
        <div className="fl"><a href={AFF} target="_blank" rel="noopener">Join YEET</a><a onClick={() => go('os')}>Desktop mode ▦</a></div>
      </div></footer>
    );
  }

  /* ---------- slot detail page ---------- */
  function WsGame({ id }) {
    const [g, setG] = React.useState(null);
    const [state, setState] = React.useState('loading');
    React.useEffect(() => {
      let al = true; setState('loading'); setG(null);
      fetch(SB + '/rest/v1/yeet_games?select=*&casino_game_id=eq.' + encodeURIComponent(id) + '&limit=1', { headers: HDR })
        .then((r) => r.json())
        .then((d) => { if (!al) return; if (Array.isArray(d) && d[0]) { setG(d[0]); setState('ok'); } else setState('none'); })
        .catch(() => { if (al) setState('err'); });
      return () => { al = false; };
    }, [id]);
    if (state === 'loading') return <div className="ws-page"><div className="ws-wrap"><p style={{ color: 'var(--ws-muted)' }}>Loading…</p></div></div>;
    if (state !== 'ok' || !g) return <div className="ws-page"><div className="ws-wrap"><span className="ws-back" onClick={() => go('games')}>← All games</span><div className="ws-page-head"><h1>Game not found</h1><p>That game isn't in the database.</p></div></div></div>;
    const rel = g.yeet_created_at ? new Date(g.yeet_created_at).toLocaleDateString('en-US', { year: 'numeric', month: 'short' }) : null;
    const tags = Array.isArray(g.tags) ? g.tags.filter((t) => !/^(mobile|desktop|slots|new-releases)$/i.test(t)).slice(0, 8) : [];
    return (
      <div className="ws-detail"><div className="ws-wrap">
        <span className="ws-back" onClick={() => go('games')}>← All games</span>
        <div className="ws-dhero">
          <div className="ws-dart"><WsGameImg g={g} /></div>
          <div className="ws-dinfo">
            <h1>{g.name}</h1>
            <div className="ws-dprov">{(g.provider_name || 'YEET') + (g.is_slot ? ' · Slot' : '')}</div>
            {tags.length ? <div className="ws-dtags">{tags.map((t, i) => <span className="ws-tag" key={i}>{String(t).replace(/-/g, ' ')}</span>)}</div> : null}
            <div className="ws-specs">
              <div className="ws-spec"><div className="k">RTP</div><div className={'v' + (g.rtp ? ' g' : ' pending')}>{g.rtp ? Number(g.rtp).toFixed(2) + '%' : 'TBA'}</div></div>
              <div className="ws-spec"><div className="k">Volatility</div><div className="v pending">Coming soon</div></div>
              <div className="ws-spec"><div className="k">Max win</div><div className="v pending">Coming soon</div></div>
              <div className="ws-spec"><div className="k">Provider</div><div className="v" style={{ fontSize: 15 }}>{g.provider_name || 'YEET'}</div></div>
              {rel ? <div className="ws-spec"><div className="k">On YEET since</div><div className="v" style={{ fontSize: 15 }}>{rel}</div></div> : null}
            </div>
            <div className="ws-dcta">
              <a className="ws-cta-lg" href={AFF} target="_blank" rel="noopener">Play on Yeet →</a>
              <span className="ws-gcount">Play under code BDKev</span>
            </div>
          </div>
        </div>
        <div className="ws-section-lite">
          <h2>🎬 Kev's Review</h2>
          <div className="ws-review">
            <div className="ws-noreview">
              <span className="em">🎥</span>
              <h3>No review yet</h3>
              <p style={{ margin: 0, maxWidth: 440 }}>I haven't played {g.name} on stream yet. When I do, my honest take and the clip land right here.</p>
            </div>
            <div className="ws-clipslot"><span className="em">▶</span><span>Clip drops after I play it live</span></div>
          </div>
        </div>
      </div></div>
    );
  }

  /* ---------- bounties (one piece style wanted posters) ---------- */
  function WsBounties() {
    const [bounties, setBounties] = React.useState(null);
    const [art, setArt] = React.useState({});
    React.useEffect(() => {
      let al = true;
      fetch(SB + '/rest/v1/yeet_bounties?select=*&order=status.asc,created_at.desc', { headers: HDR })
        .then((r) => r.json())
        .then((rows) => {
          if (!al || !Array.isArray(rows)) { if (al) setBounties([]); return; }
          setBounties(rows);
          const ids = rows.map((b) => b.casino_game_id).filter(Boolean);
          if (ids.length) {
            fetch(SB + '/rest/v1/yeet_games?select=casino_game_id,name,image_s6,image_s3,provider_name,is_slot&casino_game_id=in.(' + ids.join(',') + ')', { headers: HDR })
              .then((r) => r.json()).then((gs) => { if (al && Array.isArray(gs)) { const m = {}; gs.forEach((g) => { m[g.casino_game_id] = g; }); setArt(m); } }).catch(() => {});
          }
        })
        .catch(() => { if (al) setBounties([]); });
      return () => { al = false; };
    }, []);
    const tilts = [-2, 1.5, -1, 2, -1.5, 1];
    return (
      <div className="ws-page"><div className="ws-wrap">
        <div className="ws-page-head"><h1>Bounties</h1></div>
        <p className="ws-bounties-intro">Hit the target on the wanted game and I pay out, first come first served. Play under code <b style={{ color: 'var(--ws-text)' }}>BDKev</b> and you're eligible. Fresh bounties drop on stream and in Discord all the time.</p>
        {bounties == null ? <p style={{ color: 'var(--ws-muted)' }}>Loading bounties…</p> :
         !bounties.length ? <div className="ws-panel"><div className="ws-building"><div className="badge">🎯</div><h2>No bounties live right now</h2><p>New ones drop on stream and in Discord. Check back soon.</p></div></div> :
         <div className="ws-poster-grid">
           {bounties.map((b, i) => {
             const g = art[b.casino_game_id];
             const won = b.status === 'won';
             const elig = b.eligibility === 'fam' ? 'Crew only' : b.eligibility === 'viewer' ? 'Live viewers' : 'Anyone on YEET';
             return (
               <div className={'ws-poster' + (won ? ' won' : '')} key={b.id} style={{ '--tilt': tilts[i % tilts.length] + 'deg' }}>
                 {won ? <div className="wstamp">Captured</div> : null}
                 <div className="wtop">BY ORDER OF BIZDEVKEV</div>
                 <div className="wanted">WANTED</div>
                 <div className="wframe">{g && (g.image_s6 || g.image_s3) ? <WsGameImg g={g} /> : <div className="ph">{b.game_name}</div>}</div>
                 <div className="dead">DEAD OR ALIVE</div>
                 <div className="wtitle">{b.game_name}</div>
                 <div className="wsub">{won ? 'Bagged by ' + (b.won_by || 'a degen') : 'Land the multiplier below'}</div>
                 <div className="wreward"><div className="lbl">REWARD</div><div className="amt">{b.prize || 'TBA'}</div></div>
                 <div className="whow">Land <b>{Number(b.min_multiplier || 0).toLocaleString()}×</b> on <b>{b.game_name}</b>{b.min_bet ? <span> at <b>${b.min_bet}</b>+ a spin</span> : null}, under code <b>BDKev</b>. First to hit it wins.</div>
                 <div className="welig"><span>{elig}</span></div>
               </div>
             );
           })}
         </div>}
      </div></div>
    );
  }

  /* ---------- wiki (prefilled gambling knowledge) ---------- */
  const WIKI = [
    { slug: 'provably-fair', cat: 'Fairness', title: 'Provably Fair, explained', blurb: 'How you can mathematically verify a bet was never rigged.',
      lede: 'Provably fair is the whole reason crypto casinos can be trusted more than a black-box slot on a cruise ship. It lets you check, after the fact, that the result of your bet was decided before you clicked and could not have been tampered with.',
      body: [
        { t: 'h', c: 'The problem it solves' },
        { t: 'p', c: 'On a traditional online casino you take it on faith that the RNG (random number generator) is honest. You cannot see it, and neither can any regulator in real time. Provably fair flips that: the casino commits to a result in advance, hands you the ingredients, and you verify the math yourself.' },
        { t: 'h', c: 'How it works, in plain terms' },
        { t: 'ul', c: ['The casino generates a secret <b>server seed</b> and shows you only its hash (a scrambled fingerprint it cannot change later).', 'You provide (or the client generates) a <b>client seed</b> that the casino cannot predict.', 'Every bet combines the server seed, your client seed, and a <b>nonce</b> (a bet counter) to produce the outcome.', 'When you rotate your seed, the casino reveals the old server seed. You hash it yourself and confirm it matches the fingerprint you were shown before you bet.'] },
        { t: 'call', c: 'Because the fingerprint was locked in <b>before</b> your bet and the reveal matches it, the casino could not have changed the result based on what you wagered. That is the whole trick.' },
        { t: 'h', c: 'What it does NOT do' },
        { t: 'p', c: 'Provably fair proves the result was not tampered with. It does <b>not</b> change the house edge, and it does not make a game +EV. A provably fair coin flip with a 1% edge is still a 1% edge. Fair is not the same as favorable.' },
      ] },
    { slug: 'seeds-and-nonces', cat: 'Fairness', title: 'Server seeds, client seeds & nonces', blurb: 'The three ingredients behind every provably fair result, and how to rotate them.',
      lede: 'If provably fair is the recipe, seeds and nonces are the ingredients. Understanding them takes five minutes and makes the verify button actually mean something.',
      body: [
        { t: 'h', c: 'Server seed' },
        { t: 'p', c: 'A long random string the casino generates and keeps secret. You are shown its <b>hash</b> up front. It stays hidden until you rotate it, so the casino is locked into it but you cannot use it to predict outcomes early.' },
        { t: 'h', c: 'Client seed' },
        { t: 'p', c: 'A string on your side. Most clients auto-generate one, but you can set your own. Its only job is to make sure the casino cannot fully control the inputs, since it does not know what you will pick.' },
        { t: 'h', c: 'Nonce' },
        { t: 'p', c: 'A simple counter that ticks up by one every bet: 0, 1, 2, 3. It guarantees that even with the same seeds, each consecutive bet produces a different, deterministic result.' },
        { t: 'call', c: 'Result = hash( server seed + client seed + nonce ), converted into a number and mapped to the game outcome. Same three inputs always give the same output, which is exactly why it is verifiable.' },
        { t: 'h', c: 'When to rotate' },
        { t: 'p', c: 'Rotate your seed pair whenever you want to lock in a batch for verification, or if you are just superstitious about a cold run. Rotating reveals the previous server seed so you can verify everything you played on it. It does not improve your odds. New seed, same math, same edge.' },
      ] },
    { slug: 'rtp-and-house-edge', cat: 'Fundamentals', title: 'RTP and the house edge', blurb: 'What "96.5% RTP" actually means for your wallet over time.',
      lede: 'RTP (Return To Player) is the single most quoted stat on a slot, and the most misread. Here is what it really tells you and what it hides.',
      body: [
        { t: 'h', c: 'The one-line version' },
        { t: 'p', c: 'RTP is the percentage of all money wagered that a game pays back <b>over millions of spins</b>. A 96.5% RTP means the house keeps 3.5% on average across everyone, forever. That 3.5% is the <b>house edge</b>.' },
        { t: 'ul', c: ['96.5% RTP = 3.5% house edge.', 'It is a long-run average, not a promise for your session.', 'Over one night you can be up 10x or down to zero. RTP says nothing about a single session.'] },
        { t: 'h', c: 'Why you still lose with 96%+ RTP' },
        { t: 'p', c: 'Because the edge compounds on <b>turnover</b>, not deposits. Wager $1,000 through a 96.5% game and you feed the house ~$35 in expected value, even if that $1,000 was the same $100 cycled ten times. High RTP slows the bleed. It does not stop it.' },
        { t: 'call', c: 'Rule of thumb: your expected loss ≈ total amount wagered × house edge. Cut turnover or cut edge to lose less. Nothing else moves that number.' },
        { t: 'h', c: 'Watch for RTP versions' },
        { t: 'p', c: 'Many slots ship in multiple RTP configs (e.g. 96.5%, 94%, 92%) and the casino picks which one to run. Two casinos, same game, different RTP. Always check the number shown on the game, which is why we list it on every game page here.' },
      ] },
    { slug: 'volatility', cat: 'Fundamentals', title: 'Volatility (variance), explained', blurb: 'Why two 96% slots can feel completely different to play.',
      lede: 'RTP tells you how much comes back. Volatility tells you how bumpy the ride is getting there. It is the difference between a slow grind and a heart-attack machine.',
      body: [
        { t: 'h', c: 'Low vs high' },
        { t: 'ul', c: ['<b>Low volatility:</b> frequent small wins, shallow swings. Your balance drifts down slowly with lots of little hits. Easy on the bankroll, rarely life-changing.', '<b>High volatility:</b> long dead streaks punctuated by rare huge hits. Most spins give nothing, then one bonus pays 2,000x. Brutal on a small bankroll.'] },
        { t: 'h', c: 'Same RTP, opposite feel' },
        { t: 'p', c: 'A low-vol and a high-vol slot can both be 96.5% RTP. The house takes the same cut long-term, but the high-vol game gets there by taking your money slowly and occasionally handing back a fortune, while the low-vol game nickel-and-dimes both directions.' },
        { t: 'call', c: 'Match volatility to your bankroll. High-vol slots need more spins (more money) to realistically reach the big feature, so bet smaller per spin on them or you bust before the math has a chance.' },
        { t: 'h', c: 'Hit frequency' },
        { t: 'p', c: 'A related stat: how often <i>any</i> win lands. High-vol games often have a low hit frequency, which is why they feel cold. That coldness is by design, not a glitch and not a sign you are "due."' },
      ] },
    { slug: 'gamblers-fallacy', cat: 'Mindset', title: "The Gambler's Fallacy & slot superstitions", blurb: '"It\'s due for a bonus" is the most expensive sentence in the hobby.',
      lede: 'Slots have no memory. Every spin is independent. Almost every superstition in this hobby is a story we tell to feel in control of pure math. Here are the big ones, debunked.',
      body: [
        { t: 'h', c: "The Gambler's Fallacy" },
        { t: 'p', c: 'The belief that a run of losses makes a win "due." It does not. A high-volatility slot that has gone 300 spins without a bonus has the <b>exact same</b> odds of bonusing on spin 301 as it did on spin 1. The machine cannot remember your losing streak.' },
        { t: 'h', c: 'Common myths' },
        { t: 'ul', c: ['<b>"Hot and cold" machines:</b> there is no hot or cold. There is variance you are pattern-matching after the fact.', '<b>"Bet bigger to trigger the bonus":</b> bet size does not change the odds of a feature. It only changes how fast you win or lose.', '<b>"This slot owes me":</b> it owes you nothing. RTP is a statement about millions of spins, not your session.', '<b>"Change the seed to get lucky":</b> a new seed is new random math with the identical edge.'] },
        { t: 'call', c: 'The only edge you truly control is how much you wager and when you stop. Everything else is the same math dressed in a different superstition.' },
      ] },
    { slug: 'bankroll-management', cat: 'Strategy', title: 'Bankroll management', blurb: 'The only "strategy" on a negative-EV game that actually matters.',
      lede: 'You cannot beat the house edge over time. What you <i>can</i> do is control how long you last, how much you risk, and whether you walk away with anything. That is bankroll management, and it is the closest thing to skill that slots have.',
      body: [
        { t: 'h', c: 'Set the session bankroll first' },
        { t: 'p', c: 'Decide the amount you are willing to lose <b>before</b> you open the casino, and treat it as the price of entertainment, not an investment. Never top up mid-tilt. The refill button is where nights go wrong.' },
        { t: 'h', c: 'Size your bet to survive variance' },
        { t: 'ul', c: ['A common guideline: keep your base bet around <b>0.5% to 1%</b> of your session bankroll on high-volatility slots, so a cold streak does not wipe you before a feature can land.', 'Lower volatility can tolerate a slightly larger bet since swings are shallower.', 'If a single spin can make or break your night, your bet is too big for your roll.'] },
        { t: 'h', c: 'Use stop-losses and win goals' },
        { t: 'p', c: 'Pick a stop-loss (walk when the session bankroll is gone) and a win goal (bank a chunk and lower stakes when you hit a big multiplier). Booking a win is the only way variance ever ends in your favor.' },
        { t: 'call', c: 'You will never out-strategy the edge. You can absolutely out-discipline it. Set the numbers cold, then obey the numbers.' },
      ] },
    { slug: 'bonus-buys-and-hunts', cat: 'Fundamentals', title: 'Bonus buys & bonus hunts', blurb: 'What "buying the bonus" really costs, and how a hunt works.',
      lede: 'Two of the most misunderstood parts of modern slots: the bonus buy button and the bonus hunt format you see on stream. Here is what is actually happening.',
      body: [
        { t: 'h', c: 'Bonus buys' },
        { t: 'p', c: 'Most feature buys cost around <b>80x to 100x</b> your bet to instantly trigger the bonus round. Convenient, but the RTP on a buy is usually similar to (sometimes slightly higher than) base play, so it is <b>not</b> a shortcut to profit. It is a way to skip the base game and go straight to variance, faster and pricier.' },
        { t: 'call', c: 'A 100x buy on a $2 bet is a $200 spin. Buys concentrate variance hard. Great content, rough on a small bankroll.' },
        { t: 'h', c: 'Bonus hunts' },
        { t: 'p', c: 'A bonus hunt is a format: you play base game (or buy bonuses) across many slots, collecting triggered features without opening them, then open them all in a row at the end. You compare the total you spent collecting bonuses against what they pay back.' },
        { t: 'ul', c: ['<b>Starting balance</b> = what you begin the hunt with.', '<b>Break-even</b> = the average multiplier each bonus needs to return to get your money back.', 'The "opening" is pure variance theater. The math was set when the features triggered.'] },
      ] },
    { slug: 'multipliers-and-max-win', cat: 'Fundamentals', title: 'Multipliers & max win', blurb: 'What a 12,500x cap means, and why you will rarely sniff it.',
      lede: 'Every clip that goes viral is someone hitting a monster multiplier. Understanding what those numbers mean keeps the hype honest.',
      body: [
        { t: 'h', c: 'Multiplier = payout ÷ bet' },
        { t: 'p', c: 'A 500x hit on a $1 bet pays $500. Simple. The eye-watering numbers on stream are almost always small bets times a rare, enormous multiplier. The <b>bet size</b> is the part the clip title never shows you.' },
        { t: 'h', c: 'Max win cap' },
        { t: 'p', c: 'Most slots publish a <b>max win</b>, e.g. 5,000x, 12,500x, or the famous 30,000x+ monsters. That is the hard ceiling. Hitting it is extraordinarily rare, often longer odds than a lottery-tier event, which is exactly why those clips exist at all.' },
        { t: 'call', c: 'A high max win almost always means high volatility. The 100,000x ceiling is the marketing. The 400 dead spins to get near it are the reality.' },
      ] },
    { slug: 'responsible-play', cat: 'Mindset', title: 'Playing responsibly', blurb: 'It is entertainment, not income. Keep it that way.',
      lede: 'This hobby is fun when it is a form of entertainment you have budgeted for, and miserable the moment it becomes a plan to make money. The house edge guarantees the second one loses.',
      body: [
        { t: 'h', c: 'The mindset that keeps it fun' },
        { t: 'ul', c: ['Only ever play with money you have already decided you can lose.', 'Set a time and money limit before you start, and use the casino\'s own deposit and loss limits to enforce it.', 'Never chase losses. Chasing is how a bad session becomes a bad month.', 'Do not gamble to escape stress, boredom, or to win back rent. That is when it stops being a game.'] },
        { t: 'h', c: 'Warning signs' },
        { t: 'p', c: 'Betting more than you planned, hiding it, borrowing to play, or feeling like you <i>need</i> to win it back are all red flags. If any of that sounds familiar, take a break and use the tools below.' },
        { t: 'call', c: 'In the US, the National Problem Gambling Helpline is <b>1-800-522-4700</b>, free and confidential, 24/7. Most casinos also offer self-exclusion and cool-off periods. Using them is a strength, not a weakness.' },
        { t: 'p', c: '18+ only. Gambling is entertainment, not income. Play responsibly.' },
      ] },
  ];

  function WikiBlocks({ body }) {
    return body.map((b, i) => {
      if (b.t === 'h') return <h2 key={i}>{b.c}</h2>;
      if (b.t === 'call') return <div className="callout" key={i} dangerouslySetInnerHTML={{ __html: b.c }}></div>;
      if (b.t === 'ul') return <ul key={i}>{b.c.map((li, j) => <li key={j} dangerouslySetInnerHTML={{ __html: li }}></li>)}</ul>;
      return <p key={i} dangerouslySetInnerHTML={{ __html: b.c }}></p>;
    });
  }

  function WsWiki({ slug }) {
    const [cat, setCat] = React.useState('All');
    if (slug) {
      const a = WIKI.find((x) => x.slug === slug);
      if (!a) return <div className="ws-page"><div className="ws-wrap"><span className="ws-back" onClick={() => go('wiki')}>← All articles</span><div className="ws-page-head"><h1>Article not found</h1></div></div></div>;
      return (
        <div className="ws-page"><div className="ws-wrap">
          <span className="ws-back" onClick={() => go('wiki')}>← All articles</span>
          <article className="ws-article">
            <div className="eyebrow">{a.cat}</div>
            <h1>{a.title}</h1>
            <p className="lede">{a.lede}</p>
            <img className="ws-article-hero" src={'/wiki-img/' + a.slug + '.jpg'} alt="" />
            <WikiBlocks body={a.body} />
            <p className="disclaimer">Written by BizDevKev. General information for entertainment, not financial or betting advice. 18+ · Gamble responsibly.</p>
          </article>
        </div></div>
      );
    }
    const cats = ['All'].concat(WIKI.map((a) => a.cat).filter((c, i, arr) => arr.indexOf(c) === i));
    const list = cat === 'All' ? WIKI : WIKI.filter((a) => a.cat === cat);
    return (
      <div className="ws-page"><div className="ws-wrap">
        <div className="ws-page-head ws-wiki-hero"><h1>The Wiki</h1><p>Straight talk on how this stuff actually works, from a degen who reads the math. No fluff, no "systems", no BS.</p></div>
        <div className="ws-wiki-cats">{cats.map((c) => <span key={c} className={'ws-wiki-cat' + (c === cat ? ' on' : '')} onClick={() => setCat(c)}>{c}</span>)}</div>
        <div className="ws-wiki-grid">
          {list.map((a) => (
            <div className="ws-wiki-card" key={a.slug} onClick={() => go('wiki/' + a.slug)}>
              <div className="ws-wiki-thumb"><img src={'/wiki-img/' + a.slug + '.jpg'} alt="" loading="lazy" /></div>
              <span className="cat">{a.cat}</span>
              <h3>{a.title}</h3>
              <p>{a.blurb}</p>
              <span className="rd">Read →</span>
            </div>
          ))}
        </div>
      </div></div>
    );
  }

  /* ---------- website shell ---------- */
  function WsSite() {
    const route = useRoute();
    React.useEffect(() => { window.scrollTo(0, 0); }, [route]);
    let content, title = '';
    if (route.slice(0, 5) === 'game/') { content = <WsGame id={route.slice(5)} />; title = 'Game'; }
    else if (route === 'wiki') { content = <WsWiki />; title = 'Wiki'; }
    else if (route.slice(0, 5) === 'wiki/') { content = <WsWiki slug={route.slice(5)} />; title = 'Wiki'; }
    else switch (route) {
      case 'games': content = <WsGames />; title = 'Games'; break;
      case 'bounties': content = <WsBounties />; title = 'Bounties'; break;
      case 'leaderboard': content = <WsPageApp title="Leaderboard" sub="Biweekly wager race under code BDKev. Top 8 paid."><LeaderApp /></WsPageApp>; title = 'Leaderboard'; break;
      case 'rewards': content = <WsPageApp title="Rewards & Tiers" sub="Cash back at every tier, plus tier match."><TiersApp /><div style={{ height: 18 }}></div><TierMatchApp /></WsPageApp>; title = 'Rewards'; break;
      case 'clips': content = <WsPageApp title="Clips" sub="Me actually playing, with a clip and my verdict per game."><ClipsApp /></WsPageApp>; title = 'Clips'; break;
      case 'pnl': content = <WsPnl />; title = 'P&L'; break;
      default: content = <WsHome />;
    }
    return (
      <div className="ws">
        <video className="ws-bgvid" src={BGVID} poster={BGPOSTER} autoPlay loop muted playsInline></video>
        <div className="ws-bgscrim"></div>
        <div className="ws-app">
          <WsNav route={route} />
          <WsBoundary key={route} title={title || 'This page'}>{content}</WsBoundary>
          <WsFooter />
        </div>
      </div>
    );
  }

  /* ---------- top-level switch: website vs the classic OS ---------- */
  function BdkRoot() {
    const route = useRoute();
    React.useEffect(() => { document.body.style.overflow = route === 'os' ? 'hidden' : 'auto'; }, [route]);
    if (route === 'os') {
      return (
        <React.Fragment>
          <BdkApp />
          <button onClick={() => go('')} title="Back to the website"
            style={{ position: 'fixed', bottom: 14, left: 14, zIndex: 999999, background: 'rgba(10,11,16,.82)', color: '#f4f3f8', border: '1px solid rgba(255,255,255,.2)', borderRadius: 10, padding: '9px 14px', fontFamily: "'Space Grotesk',sans-serif", fontWeight: 700, fontSize: 12, letterSpacing: '.02em', cursor: 'pointer', backdropFilter: 'blur(8px)' }}>
            ← Exit desktop
          </button>
        </React.Fragment>
      );
    }
    return <WsSite />;
  }

  window.BdkRoot = BdkRoot;
})();
