// shared.jsx — state, hooks, helpers, atoms

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

// ─── App store (balance, history) ─────────────────────
const StoreCtx = createContext(null);

const TIPPED_KEY = 'mahamon.tipped';
const TIP_CREDITS_KEY = 'mahamon.tipCredits';

function StoreProvider({ children, initialBalance = 2500 }) {
  const [balance, setBalance] = useState(initialBalance);
  const [history, setHistory] = useState([]);
  const [pulse, setPulse] = useState(0);
  const [tipped, setTipped] = useState(() => {
    try { return parseInt(localStorage.getItem(TIPPED_KEY) || '0') || 0; } catch (e) { return 0; }
  });
  const [tipCredits, setTipCredits] = useState(() => {
    try { return parseInt(localStorage.getItem(TIP_CREDITS_KEY) || '0') || 0; } catch (e) { return 0; }
  });
  const [tipPulse, setTipPulse] = useState(0);

  useEffect(() => { localStorage.setItem(TIPPED_KEY, String(tipped)); }, [tipped]);
  useEffect(() => { localStorage.setItem(TIP_CREDITS_KEY, String(tipCredits)); }, [tipCredits]);

  const settle = useCallback((game, delta, detail = '') => {
    setBalance((b) => b + delta);
    setHistory((h) => [{ game, delta, detail, t: Date.now() }, ...h].slice(0, 80));
    setPulse((p) => p + 1);
    // Sound feedback
    try {
      if (typeof sounds !== 'undefined') {
        if (delta > 0) {
          // Big win threshold (>10x typical bet ~50 → >500 absolute)
          if (delta >= 500) sounds.bigWin();
          else sounds.win();
        } else if (delta < 0) sounds.loss();
        else sounds.push();
      }
    } catch (e) {}
  }, []);

  const adjust = useCallback((delta) => setBalance((b) => Math.max(0, b + delta)), []);
  const reset = useCallback(() => { setBalance(initialBalance); setHistory([]); }, [initialBalance]);

  // Tip Mahamon — sacrifices balance, builds tipped (lifetime) and tipCredits (spendable for unlocks)
  const tip = useCallback((amount) => {
    const a = Math.floor(amount);
    if (!Number.isFinite(a) || a <= 0) return false;
    let ok = false;
    setBalance((b) => {
      if (b < a) return b;
      ok = true;
      return b - a;
    });
    // microtask so setBalance result is committed before we add
    setTimeout(() => {
      if (!ok) return;
      setTipped((t) => t + a);
      setTipCredits((c) => c + a);
      setHistory((h) => [{ game: 'tip', delta: -a, detail: 'tip · mahamon', t: Date.now() }, ...h].slice(0, 80));
      setTipPulse((p) => p + 1);
      try { if (typeof sounds !== 'undefined') sounds.tip(); } catch (e) {}
    }, 0);
    return true;
  }, []);

  const spendCredits = useCallback((amount) => {
    setTipCredits((c) => Math.max(0, c - amount));
  }, []);

  return (
    <StoreCtx.Provider value={{
      balance, setBalance, history, settle, adjust, reset, pulse,
      tipped, tipCredits, tip, spendCredits, tipPulse
    }}>
      {children}
    </StoreCtx.Provider>);

}
const useStore = () => useContext(StoreCtx);

// ─── Animated number ticker ───────────────────────────
function Ticker({ value, format = (v) => v.toLocaleString('en-US') }) {
  const [shown, setShown] = useState(value);
  const fromRef = useRef(value);
  const startRef = useRef(performance.now());
  const rafRef = useRef(0);
  const dur = 480;

  useEffect(() => {
    cancelAnimationFrame(rafRef.current);
    fromRef.current = shown;
    startRef.current = performance.now();
    const step = (t) => {
      const k = Math.min(1, (t - startRef.current) / dur);
      const e = 1 - Math.pow(1 - k, 3);
      const cur = fromRef.current + (value - fromRef.current) * e;
      setShown(cur);
      if (k < 1) rafRef.current = requestAnimationFrame(step);else
      setShown(value);
    };
    rafRef.current = requestAnimationFrame(step);
    return () => cancelAnimationFrame(rafRef.current);
  }, [value]);

  return <span className="ticker">{format(Math.round(shown))}</span>;
}

// ─── Icons ────────────────────────────────────────────
const ChevLeft = ({ size = 16 }) =>
<svg width={size} height={size} viewBox="0 0 16 16" fill="none">
    <path d="M10 3L5 8L10 13" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
  </svg>;

const ChevRight = ({ size = 16 }) =>
<svg width={size} height={size} viewBox="0 0 16 16" fill="none">
    <path d="M6 3L11 8L6 13" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
  </svg>;

const IconClock = ({ size = 16 }) =>
<svg width={size} height={size} viewBox="0 0 16 16" fill="none">
    <circle cx="8" cy="8" r="6" stroke="currentColor" strokeWidth="1.1" />
    <path d="M8 4.5V8L10.5 9.5" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" />
  </svg>;

const IconClose = ({ size = 16 }) =>
<svg width={size} height={size} viewBox="0 0 16 16" fill="none">
    <path d="M3.5 3.5L12.5 12.5M12.5 3.5L3.5 12.5" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
  </svg>;

const IconGear = ({ size = 16 }) =>
<svg width={size} height={size} viewBox="0 0 16 16" fill="none">
    <circle cx="8" cy="8" r="2" stroke="currentColor" strokeWidth="1.1" />
    <path d="M8 1.5v1.6M8 12.9v1.6M14.5 8h-1.6M3.1 8H1.5M12.6 3.4l-1.1 1.1M4.5 11.5l-1.1 1.1M12.6 12.6l-1.1-1.1M4.5 4.5L3.4 3.4" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" />
  </svg>;

const IconPlay = ({ size = 14 }) =>
<svg width={size} height={size} viewBox="0 0 16 16" fill="none">
    <path d="M4.5 3L12.5 8L4.5 13Z" stroke="currentColor" strokeWidth="1.2" strokeLinejoin="round" fill="none" />
  </svg>;

const IconGift = ({ size = 14 }) =>
<svg width={size} height={size} viewBox="0 0 16 16" fill="none">
    <rect x="2.5" y="6.5" width="11" height="7" stroke="currentColor" strokeWidth="1.1" />
    <path d="M8 6.5V13.5M2 9H14M5 6.5C5 5 6 4 7 4S8 5 8 6.5M11 6.5C11 5 10 4 9 4S8 5 8 6.5" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" />
  </svg>;

const IconRanking = ({ size = 16 }) =>
<svg width={size} height={size} viewBox="0 0 16 16" fill="none">
    <rect x="1.75" y="9.25" width="3.5" height="4.5" stroke="currentColor" strokeWidth="1.1" />
    <rect x="6.25" y="5.75" width="3.5" height="8" stroke="currentColor" strokeWidth="1.1" />
    <rect x="10.75" y="11" width="3.5" height="2.75" stroke="currentColor" strokeWidth="1.1" />
  </svg>;

const IconEdit = ({ size = 13 }) =>
<svg width={size} height={size} viewBox="0 0 14 14" fill="none">
    <path d="M9.7 1.6L12.4 4.3L4.7 12H2V9.3L9.7 1.6Z" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" strokeLinejoin="round" />
    <path d="M8.5 2.8L11.2 5.5" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" />
  </svg>;

const IconLock = ({ size = 12 }) =>
<svg width={size} height={size} viewBox="0 0 14 14" fill="none">
    <rect x="2.5" y="6" width="9" height="6" stroke="currentColor" strokeWidth="1.1" />
    <path d="M4.5 6V4.25C4.5 2.87 5.62 1.75 7 1.75C8.38 1.75 9.5 2.87 9.5 4.25V6" stroke="currentColor" strokeWidth="1.1" />
  </svg>;

const IconArrowUp = ({ size = 22 }) =>
<svg width={size} height={size} viewBox="0 0 22 22" fill="none">
    <path d="M5 17L17 5M17 5H8.5M17 5V13.5" stroke="currentColor" strokeWidth="1.1" strokeLinecap="round" strokeLinejoin="round" />
  </svg>;


// ─── Top bar ──────────────────────────────────────────
function TopBar({ title, onBack, onHistory, onRules, onSettings, onRanking, onTip, balance }) {
  const t = useT();
  return (
    <div className="topbar">
      {onBack ?
      <div className="press icon-btn" onClick={onBack} aria-label="back"><ChevLeft /></div> :
      <img src="logo-wordmark.png" alt="Mahamon House" style={{ height: 65, width: 'auto', display: 'block', marginLeft: -6 }} />
      }
      <div style={{ flex: 1, textAlign: 'center', letterSpacing: '0.16em' }}>{title || ''}</div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
        {balance !== undefined && <span className="balance"><Ticker value={balance} /></span>}
        {onTip &&
        <span className="rules-link press tip-link" onClick={onTip}>{t('topbar.tip')}</span>
        }
        {onRules &&
        <span className="rules-link press" onClick={onRules}>{t('topbar.rules')}</span>
        }
        {onRanking &&
        <div className="press icon-btn" onClick={onRanking} aria-label="ranking"><IconRanking /></div>
        }
        {onHistory &&
        <div className="press icon-btn" onClick={onHistory} aria-label="history"><IconClock /></div>
        }
        {onSettings &&
        <div className="press icon-btn" onClick={onSettings} aria-label="settings"><IconGear /></div>
        }
      </div>
    </div>);

}

// ─── Bet selector ─────────────────────────────────────
function BetSelector({ value, onChange, options = [10, 25, 100, 250, 500] }) {
  const handleClick = (v) => {
    try { if (typeof sounds !== 'undefined') sounds.tick(); } catch (e) {}
    onChange(v);
  };
  return (
    <div style={{ display: 'flex', gap: 8, padding: '0 24px', justifyContent: 'space-between' }}>
      {options.map((v) =>
      <button
        key={v}
        className={'chip press' + (v === value ? ' active' : '')}
        onClick={() => handleClick(v)}>
        {v}</button>
      )}
    </div>);

}

function ResultLine({ result }) {
  if (!result) return <div style={{ height: 18 }} />;
  const isWin = result.delta > 0;
  return (
    <div style={{
      textAlign: 'center', fontSize: 11, letterSpacing: '0.22em', textTransform: 'uppercase',
      color: isWin ? 'var(--win)' : 'var(--text-3)',
      animation: 'fadeIn 320ms ease both',
      fontFeatureSettings: '"tnum"',
      height: 18
    }}>
      {isWin ? `+ ${result.delta.toLocaleString('en-US')}` : result.delta < 0 ? `— ${Math.abs(result.delta).toLocaleString('en-US')}` : '·'}
      {result.label && <span style={{ marginLeft: 12, color: 'var(--text-3)' }}>{result.label}</span>}
    </div>);

}

// ─── Helpers ──────────────────────────────────────────
const SUITS = ['♠', '♥', '♦', '♣'];
const RANKS = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'];

function shuffleDeck() {
  const d = [];
  for (const s of SUITS) for (const r of RANKS) d.push({ s, r });
  for (let i = d.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [d[i], d[j]] = [d[j], d[i]];
  }
  return d;
}

function cardValue(c) {
  if (c.r === 'A') return 11;
  if (['J', 'Q', 'K'].includes(c.r)) return 10;
  return parseInt(c.r);
}

function handTotal(cards) {
  let total = cards.reduce((a, c) => a + cardValue(c), 0);
  let aces = cards.filter((c) => c.r === 'A').length;
  while (total > 21 && aces > 0) {total -= 10;aces--;}
  return total;
}

// ─── Playing card (flat, no gradients) ────────────────
function PlayingCard({ card, faceDown, held, style }) {
  const isRed = card && (card.s === '♥' || card.s === '♦');
  return (
    <div style={{ perspective: 800, ...style }}>
      <div style={{
        width: 64, height: 92, position: 'relative',
        transformStyle: 'preserve-3d',
        transition: 'transform 520ms cubic-bezier(.2,.8,.2,1)',
        transform: faceDown ? 'rotateY(180deg)' : 'rotateY(0deg)'
      }}>
        {/* front */}
        <div className={'pcard' + (isRed ? ' red' : '') + (held ? ' held' : '')} style={{
          position: 'absolute', inset: 0, backfaceVisibility: 'hidden'
        }}>
          {card && <>
            <div className="rank">{card.r}</div>
            <div className="suit">{card.s}</div>
          </>}
        </div>
        {/* back */}
        <div className="pcard face-down" style={{
          position: 'absolute', inset: 0, backfaceVisibility: 'hidden',
          transform: 'rotateY(180deg)'
        }} />
      </div>
    </div>);

}

// ─── History overlay ──────────────────────────────────
function HistoryOverlay({ onClose }) {
  const t = useT();
  const { history } = useStore();
  return (
    <div className="hist fade-in">
      <div className="topbar">
        <div className="brand">{t('history.title')}</div>
        <div className="press icon-btn" onClick={onClose}><IconClose /></div>
      </div>
      <div style={{ flex: 1, overflowY: 'auto' }}>
        {history.length === 0 ?
        <div style={{ padding: '80px 24px', textAlign: 'center', color: 'var(--text-3)', fontSize: 13 }}>
            {t('history.empty')}
          </div> :
        history.map((h, i) =>
        <div key={i} className="hist-row">
            <div>
              <div className="game">{h.game}</div>
              <div style={{ color: 'var(--text-3)', fontSize: 12, marginTop: 4, fontFamily: 'var(--font-mono)' }}>
                {h.detail}
              </div>
            </div>
            <div className={'delta ' + (h.delta > 0 ? 'win' : 'loss')}>
              {h.delta > 0 ? '+' : ''}{h.delta.toLocaleString('en-US')}
            </div>
          </div>
        )}
      </div>
    </div>);

}

// ─── Rules content + overlay ──────────────────────────
const RULES = {
  roulette: {
    title: 'Roulette',
    body: [
    'European wheel — a single zero, 37 pockets in total.',
    'Place an outside bet (red, black, even, odd, low, high) and win even money.',
    'Bet a single number to win 35 to 1.',
    'The ball lands once. The pocket it lands in is the result.']

  },
  blackjack: {
    title: 'Blackjack',
    body: [
    'Beat the dealer by holding a hand closer to 21, without going over.',
    'Aces count 1 or 11. Face cards count 10.',
    'Hit to draw, Stand to hold. Dealer must hit until 17.',
    'A two-card 21 is a Blackjack — pays 3 to 2.',
    'Tie pushes; you keep your stake.']

  },
  slots: {
    title: 'Three Reels',
    body: [
    'Three identical roman numerals on the center line wins.',
    'Two adjacent matches return half your stake.',
    'Payouts: VII × 50, X × 20, IX × 12, V × 6, · × 3.',
    'The strips keep moving until they don\'t.']

  },
  craps: {
    title: 'Dice — Pass Line',
    body: [
    'Come-out roll: 7 or 11 wins, 2/3/12 loses.',
    'Anything else sets the Point.',
    'Roll the Point again before a 7 to win even money.',
    'A 7 before the Point loses (seven-out).']

  },
  poker: {
    title: 'Video Poker — Jacks or Better',
    body: [
    'You are dealt five cards. Hold the cards you want; the rest are redrawn once.',
    'A pair of Jacks or higher returns your stake.',
    'Higher hands pay multiples of your stake. A Royal Flush pays 250 to 1.',
    'There is no dealer — only your final hand counts.']

  },
  coin: {
    title: 'Coin Flip',
    body: [
    'Pick MAHA or MON. The coin lands once.',
    'Even money. Nothing more.']

  },
  baccarat: {
    title: 'Baccarat — Punto Banco',
    body: [
    'Bet on Player, Banker or Tie. Closest to nine wins.',
    'Tens and faces count zero. Aces count one. Totals over nine drop the tens digit.',
    'Player and Banker each receive two cards. A third card may follow, by table rule.',
    'Player pays even. Banker pays even less five percent commission. Tie pays eight to one.',
    'On a tie, Player and Banker stakes return to you.']

  },
  hilo: {
    title: 'Hi-Lo',
    body: [
    'A card is shown. Predict whether the next is higher or lower.',
    'Each correct guess multiplies your stake. Cash out anytime.',
    'A wrong guess clears the streak — and the stake.',
    'Same rank loses. Aces are low, kings are high.']

  },
  crash: {
    title: 'Crash',
    body: [
    'A multiplier rises from one. Cash out before it crashes.',
    'It can crash at any moment. Sometimes immediately.',
    'Set an auto cash-out to lock in a target — or cash by hand.',
    'Greed is the only thing standing between you and the cash.']

  },
  mines: {
    title: 'Mines',
    body: [
    'A grid of twenty-five cells. Some hold mines. The rest are safe.',
    'Reveal cells one at a time. Each safe reveal raises the multiplier.',
    'Hit a mine and the round is over. Cash out before that — anytime.',
    'More mines means a steeper climb, and a steeper fall.']

  },
  dragontiger: {
    title: 'Dragon · Tiger',
    body: [
    'Two cards. One for the Dragon, one for the Tiger.',
    'Bet on the side you think will be higher. Even money.',
    'Bet on a tie for eight to one. The tie is rare and remembered.',
    'Aces are low. Kings are high. Suits don\'t speak.']

  },
  limbo: {
    title: 'Limbo',
    body: [
    'Pick a multiplier. The house rolls a random number.',
    'If the roll is at or above your target, you win the target times your stake.',
    'If it falls short, the stake is gone.',
    'High targets pay more, but the chance is thin.']

  },
  war: {
    title: 'War',
    body: [
    'One card for you. One for the dealer. The higher card wins, even money.',
    'Aces are high. Kings come second.',
    'On a tie, the stake returns to you — quietly.']

  },
  towers: {
    title: 'Towers',
    body: [
    'Eight rows. Four cells each. Some hold traps.',
    'Pick one cell per row. The right one moves you up the tower.',
    'The wrong one ends the climb.',
    'Cash out at any floor. Or push for the top.']

  },
  plinko: {
    title: 'Plinko',
    body: [
    'A ball drops from the top, bouncing through pegs.',
    'Where it lands, that is your multiplier.',
    'The middle slots are common. The edges are rare and rich.',
    'No skill, no decisions — only gravity and chance.']

  },
  keno: {
    title: 'Keno',
    body: [
    'Pick six numbers among eighty.',
    'The house draws twenty.',
    'Match three to win. Match four to climb.',
    'Match six — that is the house remembering you.']

  }
};

function RulesOverlay({ rulesKey, onClose }) {
  const t = useT();
  if (!rulesKey || !RULES[rulesKey]) return null;
  const fallback = RULES[rulesKey];
  // Try i18n first, fall back to RULES object (which is in English)
  const title = t(`rules.${rulesKey}.title`) === `rules.${rulesKey}.title` ? fallback.title : t(`rules.${rulesKey}.title`);
  const bodyRaw = t(`rules.${rulesKey}.body`);
  const body = Array.isArray(bodyRaw) ? bodyRaw : fallback.body;
  const catKey =
    rulesKey === 'poker' || rulesKey === 'baccarat' || rulesKey === 'hilo' || rulesKey === 'dragontiger' || rulesKey === 'war' ? 'rules.card_game' :
    rulesKey === 'roulette' ? 'rules.wheel_game' :
    rulesKey === 'craps' ? 'rules.dice_game' :
    rulesKey === 'slots' ? 'rules.reel_game' :
    rulesKey === 'crash' || rulesKey === 'limbo' ? 'rules.multiplier_game' :
    rulesKey === 'mines' || rulesKey === 'towers' || rulesKey === 'plinko' || rulesKey === 'keno' ? 'rules.grid_game' :
    rulesKey === 'coin' ? 'rules.toss_game' : 'rules.card_game';
  return (
    <div className="hist fade-in" style={{ background: 'var(--bg)' }}>
      <div className="topbar">
        <div className="brand">{t('rules.title')}</div>
        <div className="press icon-btn" onClick={onClose}><IconClose /></div>
      </div>
      <div style={{ flex: 1, overflowY: 'auto', padding: '64px 32px 48px' }}>
        <div className="eyebrow" style={{ marginBottom: 20 }}>{t(catKey)}</div>
        <div className="serif" style={{
          fontSize: 36, fontStyle: 'italic', letterSpacing: '-0.01em',
          lineHeight: 1.05, marginBottom: 40, color: 'var(--text)'
        }}>{title}</div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
          {body.map((p, i) =>
          <div key={i} style={{ display: 'flex', gap: 16, alignItems: 'baseline' }}>
              <div className="mono" style={{ color: 'var(--text-4)', fontSize: 10, width: 18, flexShrink: 0 }}>
                {String(i + 1).padStart(2, '0')}
              </div>
              <div style={{ fontSize: 15, color: 'var(--text-2)', lineHeight: 1.55 }}>{p}</div>
            </div>
          )}
        </div>
        <div style={{ marginTop: 56, paddingTop: 24, borderTop: '1px solid var(--line)', textAlign: 'center' }}>
          <div className="eyebrow" style={{ color: 'var(--text-4)' }}>{t('rules.disclaimer')}</div>
        </div>
      </div>
    </div>);

}

// Strip emoji/symbols/zero-width joiners, used for usernames
function stripEmoji(s) {
  return (s || '')
    .replace(/\p{Extended_Pictographic}/gu, '')
    .replace(/[\u{1F1E6}-\u{1F1FF}]/gu, '')
    .replace(/[\u200D\uFE0F\uFE0E]/g, '');
}

// ─── Tip Mahamon sheet ────────────────────────────────
function TipSheet({ onClose }) {
  const t = useT();
  const { balance, tipped, tipCredits, tip } = useStore();
  const max = Math.max(1, balance);
  const [amount, setAmount] = useState(() => Math.min(50, max));
  const [done, setDone] = useState(false);

  useEffect(() => {
    if (amount > max) setAmount(max);
    if (amount < 1) setAmount(1);
  }, [max]);

  const submit = () => {
    if (amount < 1 || amount > balance) return;
    const ok = tip(amount);
    if (!ok) return;
    setDone(true);
    setTimeout(onClose, 720);
  };

  const presetPercents = [10, 25, 50];

  return (
    <div className="unlock-sheet fade-in">
      <div className="unlock-card tip-card">
        {!done ? (
          <>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 14 }}>
              <div className="eyebrow">{t('tip.title')}</div>
              <button onClick={onClose} className="press" style={{
                background: 'transparent', border: 'none', color: 'var(--text-3)',
                cursor: 'pointer', padding: 4, margin: -4, display: 'flex'
              }}><IconClose size={14}/></button>
            </div>
            <div className="serif" style={{
              fontSize: 36, fontStyle: 'italic', letterSpacing: '-0.01em',
              lineHeight: 1.05, marginBottom: 8, color: 'var(--text)'
            }}>
              {t('tip.heading')}
            </div>
            <div style={{ fontSize: 12, color: 'var(--text-3)', marginBottom: 22, lineHeight: 1.55 }}>
              {t('tip.body')}
            </div>

            {/* Amount */}
            <div style={{
              display: 'flex', alignItems: 'baseline', justifyContent: 'space-between',
              borderTop: '1px solid var(--line)', paddingTop: 18, marginBottom: 10
            }}>
              <div className="eyebrow">{t('tip.amount')}</div>
              <div className="mono" style={{
                fontSize: 36, color: 'var(--text)',
                fontVariantNumeric: 'tabular-nums', letterSpacing: '0.02em',
                fontWeight: 300, lineHeight: 1
              }}>
                {amount.toLocaleString('en-US')}
              </div>
            </div>

            {/* Range */}
            <input type="range" className="tip-range" min={1} max={max}
              value={amount} onChange={(e) => setAmount(parseInt(e.target.value))}
              style={{ '--pct': ((amount - 1) / Math.max(1, max - 1)) * 100 + '%' }}
            />
            <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 6, marginBottom: 18 }}>
              <span className="mono" style={{ fontSize: 10, color: 'var(--text-4)', letterSpacing: '0.06em' }}>1</span>
              <span className="mono" style={{ fontSize: 10, color: 'var(--text-4)', letterSpacing: '0.06em' }}>{max.toLocaleString('en-US')}</span>
            </div>

            {/* Quick presets — % of balance */}
            <div style={{ display: 'flex', gap: 6, marginBottom: 22, flexWrap: 'wrap' }}>
              {presetPercents.map((p) => {
                const v = Math.max(1, Math.floor(balance * p / 100));
                if (v > max) return null;
                return (
                  <button
                    key={p}
                    className={'chip press' + (amount === v ? ' active' : '')}
                    onClick={() => setAmount(v)}
                    style={{ flex: 1 }}
                  >
                    {p}%
                  </button>
                );
              })}
              {max > 0 && (
                <button
                  className={'chip press' + (amount === max ? ' active' : '')}
                  onClick={() => setAmount(max)}
                  style={{ flex: 1 }}
                >{t('tip.all')}</button>
              )}
            </div>

            {/* Stats line */}
            <div style={{
              display: 'flex', justifyContent: 'space-between', alignItems: 'baseline',
              borderTop: '1px solid var(--line)', paddingTop: 14, marginBottom: 22,
              fontSize: 11, color: 'var(--text-3)', letterSpacing: '0.06em'
            }}>
              <span>{t('tip.lifetime')} <span className="mono" style={{ color: 'var(--text-2)', marginLeft: 6 }}>{tipped.toLocaleString('en-US')}</span></span>
              <span>{t('tip.credits')} <span className="mono" style={{ color: 'var(--text-2)', marginLeft: 6 }}>{tipCredits.toLocaleString('en-US')}</span></span>
            </div>

            <div style={{ display: 'flex', gap: 10 }}>
              <button className="btn" onClick={onClose}>{t('tip.cancel')}</button>
              <button className="btn primary" onClick={submit}
                disabled={amount < 1 || amount > balance}>
                {t('tip.cta', { amount: amount.toLocaleString('en-US') })}
              </button>
            </div>
          </>
        ) : (
          <div style={{ padding: '24px 0', textAlign: 'center' }}>
            <div className="serif" style={{
              fontSize: 36, fontStyle: 'italic', letterSpacing: '-0.01em',
              lineHeight: 1.05, color: 'var(--text)', marginBottom: 10
            }}>
              {t('tip.received')}
            </div>
            <div className="mono" style={{ fontSize: 11, color: 'var(--text-3)', letterSpacing: '0.18em', textTransform: 'uppercase' }}>
              {t('tip.received_sub')}
            </div>
          </div>
        )}
      </div>
      <div className="unlock-scrim" onClick={onClose} />
    </div>
  );
}

Object.assign(window, {
  StoreProvider, useStore, Ticker, ChevLeft, ChevRight,
  IconClock, IconClose, IconGear, IconPlay, IconGift, IconRanking, IconEdit, IconArrowUp, IconLock,
  TopBar, BetSelector, ResultLine, PlayingCard, HistoryOverlay, RulesOverlay, RULES, TipSheet,
  shuffleDeck, cardValue, handTotal, SUITS, RANKS, stripEmoji
});