/* global React, ReactDOM */
const { useState, useRef, useCallback, useEffect } = React;

// =============================================================================
// Constants
// =============================================================================
const MAX_BET_DEFAULT = 100;
const MIN_BET_DEFAULT = 0.10;
const HOUSE_EDGE_DEFAULT = 0.035; // 0.965 RTP backend variant
const MIN_MULTIPLIER = 1.01;
const MAX_MULTIPLIER = 1000;
const GAME_UUID = (typeof window !== 'undefined' && window.LIMBO_GAME_UUID) || 'limbo_965';
const API_BASE = (typeof window !== 'undefined' && window.RGS_API_BASE) || '';

// =============================================================================
// Helpers
// =============================================================================
function fmt(n) {
  return '$' + Number(n).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function winChance(target, rtp) {
  if (target <= 1) return 100;
  return Math.min(100, (rtp / target) * 100);
}

// =============================================================================
// Audio (Web Audio API, oscillator-based)
// =============================================================================
class AudioEngine {
  constructor() { this.ctx = null; this.enabled = true; }
  getCtx() {
    if (!this.ctx) this.ctx = new (window.AudioContext || window.webkitAudioContext)();
    return this.ctx;
  }
  toggle() { this.enabled = !this.enabled; return this.enabled; }
  play(freq, duration, type = 'sine', vol = 0.15) {
    if (!this.enabled) return;
    try {
      const ctx = this.getCtx();
      const osc = ctx.createOscillator();
      const gain = ctx.createGain();
      osc.type = type;
      osc.frequency.value = freq;
      gain.gain.setValueAtTime(vol, ctx.currentTime);
      gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);
      osc.connect(gain); gain.connect(ctx.destination);
      osc.start(); osc.stop(ctx.currentTime + duration);
    } catch (e) { /* ignore */ }
  }
  sndTick(progress) { this.play(300 + progress * 600, 0.04, 'sine', 0.06); }
  sndBet()    { this.play(250, 0.12, 'sine', 0.08); }
  sndWin() {
    this.play(660, 0.15, 'sine', 0.12);
    setTimeout(() => this.play(880, 0.15, 'sine', 0.12), 80);
    setTimeout(() => this.play(1100, 0.25, 'sine', 0.14), 160);
    setTimeout(() => this.play(1320, 0.35, 'sine', 0.10), 260);
  }
  sndBigWin() {
    this.play(660, 0.15, 'sine', 0.15);
    setTimeout(() => this.play(880, 0.15, 'sine', 0.15), 80);
    setTimeout(() => this.play(1100, 0.2, 'sine', 0.15), 160);
    setTimeout(() => this.play(1320, 0.2, 'sine', 0.15), 240);
    setTimeout(() => this.play(1540, 0.4, 'sine', 0.18), 320);
    setTimeout(() => this.play(1760, 0.5, 'sine', 0.12), 420);
  }
  sndLose() {
    this.play(200, 0.25, 'sine', 0.08);
    setTimeout(() => this.play(160, 0.3, 'sine', 0.06), 100);
  }
}

// =============================================================================
// RGS client
// =============================================================================
let TOKEN = null;
async function api(path, opts = {}) {
  const headers = {};
  if (TOKEN) headers['Authorization'] = 'Bearer ' + TOKEN;
  if (opts.body !== undefined) headers['Content-Type'] = 'application/json';
  const r = await fetch(API_BASE + path, {
    method: opts.method || 'GET',
    headers,
    body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
  });
  const data = await r.json().catch(() => ({}));
  if (data && data.error_code) {
    const err = new Error(data.error_description || data.error_code);
    err.errorCode = data.error_code;
    throw err;
  }
  if (!r.ok) throw new Error('HTTP ' + r.status);
  return data;
}
async function initDemo() {
  const r = await api('/api/v1/init-demo', { method: 'POST', body: { game_uuid: GAME_UUID } });
  TOKEN = new URL(r.url, 'http://x').searchParams.get('token');
  if (!TOKEN) throw new Error('init-demo: missing token');
  return TOKEN;
}
const getState  = () => api('/api/v1/state');
const placeBetServer = (amount, target) => api('/api/v1/bet', {
  method: 'POST',
  body: { amount: Number(amount).toFixed(2), target_multiplier: Number(target).toFixed(2) },
});

// =============================================================================
// GameInfoModal
// =============================================================================
function GameInfoModal({ open, onClose, rgsInfo }) {
  const live = rgsInfo !== null;
  const rtpPct = live ? rgsInfo.rtp * 100 : (1 - HOUSE_EDGE_DEFAULT) * 100;
  const minBet = live ? rgsInfo.minBet : MIN_BET_DEFAULT;
  const maxBet = live ? rgsInfo.maxBet : MAX_BET_DEFAULT;
  const maxMult = live && rgsInfo.maxMultiplier > 0 ? rgsInfo.maxMultiplier : MAX_MULTIPLIER;
  const maxPayout = live ? rgsInfo.maxPayout : MAX_BET_DEFAULT * MAX_MULTIPLIER;
  const source = live ? 'live from dice-rgs' : 'offline fallback';

  return (
    <div
      className={`modal-overlay${open ? ' show' : ''}`}
      onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
    >
      <div className="modal">
        <button className="modal-close" onClick={onClose}>&times;</button>
        <h2>Game Info</h2>
        <div className="modal-payout-box">
          <span className="label">Max Multiplier</span>
          <span className="value">{maxMult.toLocaleString(undefined, { maximumFractionDigits: 2 })}x</span>
        </div>
        <div className="modal-payout-box">
          <span className="label">Max Win</span>
          <span className="value">${maxPayout.toLocaleString()}</span>
        </div>
        <div className="modal-payout-box">
          <span className="label">Bet Range</span>
          <span className="value">${minBet.toFixed(2)} — ${maxBet.toFixed(2)}</span>
        </div>
        <div className="info-box">
          <span className="info-icon">{'∞'}</span>
          <p>
            Limbo is a multiplier game. Set your target multiplier, place a bet, and a random
            multiplier is generated. If the result meets or exceeds your target, you win!
          </p>
        </div>
        <h3>How to Play</h3>
        <ol>
          <li><strong>Set your bet</strong> — enter the amount you want to wager.</li>
          <li><strong>Choose a target</strong> — pick a multiplier (e.g. 2.00x). Higher targets = bigger payouts but lower win chance.</li>
          <li><strong>Press BET</strong> — a random multiplier is generated.</li>
          <li><strong style={{ color: '#0ECC68' }}>Win</strong> — if the result is ≥ your target, you win bet × target.</li>
          <li><strong style={{ color: '#EF4060' }}>Lose</strong> — if the result is below your target, you lose your bet.</li>
        </ol>
        <h3>House Edge &amp; RTP</h3>
        <div className="info-box">
          <span className="info-icon">{'📊'}</span>
          <p>
            <strong>RTP: {rtpPct.toFixed(2)}%</strong> ({source}) — House edge is {(100 - rtpPct).toFixed(2)}%.
            Each round is independent. Max bet ${maxBet.toFixed(2)}. Your payout = bet × target multiplier.
          </p>
        </div>
        <h3>Tips</h3>
        <ul>
          <li>Lower targets give you the best win chance.</li>
          <li>Higher targets pay more but hit less often.</li>
          <li>Every outcome is provably fair — tap Fair Play to verify.</li>
          <li>Use Auto mode to run multiple rounds automatically.</li>
          <li>Each round is fully independent — streaks don{"'"}t affect odds.</li>
        </ul>
      </div>
    </div>
  );
}

// =============================================================================
// ProvablyFairModal
// =============================================================================
function ProvablyFairModal({ open, onClose, seedHash }) {
  const [activeTab, setActiveTab] = useState('seeds');
  const [clientSeed, setClientSeed] = useState(() => 'demo_' + Math.random().toString(36).slice(2, 10));
  const [copied, setCopied] = useState(null);
  const hash = seedHash || 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';

  const handleCopy = (text, label) => {
    if (navigator.clipboard) navigator.clipboard.writeText(text);
    setCopied(label);
    setTimeout(() => setCopied(null), 1500);
  };
  const handleRegen = () => setClientSeed('demo_' + Math.random().toString(36).slice(2, 10));

  return (
    <div
      className={`modal-overlay${open ? ' show' : ''}`}
      onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
    >
      <div className="modal">
        <button className="modal-close" onClick={onClose}>&times;</button>
        <h2>Provably Fair</h2>
        <p className="pf-desc">
          This game uses <strong>Provably Fair</strong> technology to generate each
          multiplier result. You can verify that every outcome is fair and unmanipulated.
        </p>

        <div className="pf-toggle-row">
          <button className={`pf-toggle-btn${activeTab === 'seeds' ? ' active' : ''}`} onClick={() => setActiveTab('seeds')}>Seeds</button>
          <button className={`pf-toggle-btn${activeTab === 'verify' ? ' active' : ''}`} onClick={() => setActiveTab('verify')}>Verify</button>
        </div>

        {activeTab === 'seeds' && (
          <React.Fragment>
            <div className="pf-section">
              <div className="pf-label"><span className="pf-icon">{'🖥'}</span> Client Seed</div>
              <div className="pf-sublabel">Generated on your side — you control this</div>
              <div className="pf-input-row">
                <input type="text" value={clientSeed} onChange={(e) => setClientSeed(e.target.value)} />
                <button className="pf-btn-sm" title="Copy" onClick={() => handleCopy(clientSeed, 'client')}>
                  {copied === 'client' ? '✓' : 'CP'}
                </button>
                <button className="pf-btn-sm" title="Regenerate" onClick={handleRegen}>R</button>
              </div>
            </div>

            <div className="pf-section">
              <div className="pf-label"><span className="pf-icon">{'🔒'}</span> Server Seed SHA256</div>
              <div className="pf-sublabel">Committed before you bet — revealed after</div>
              <div className="pf-hash-box">{hash}</div>
              <div className="pf-cp-row">
                <button className="pf-btn-sm" title="Copy" onClick={() => handleCopy(hash, 'hash')}>
                  {copied === 'hash' ? '✓' : 'CP'}
                </button>
              </div>
              <p className="pf-note">
                This hash is committed <strong>before</strong> you bet. After the round,
                the server seed is revealed so you can verify SHA256(seed) matches.
              </p>
            </div>
          </React.Fragment>
        )}

        {activeTab === 'verify' && (
          <div className="pf-how">
            <div className="pf-how-title">How Limbo Provably Fair Works</div>
            <ol>
              <li>Server generates a secret seed and shows you its SHA256 hash</li>
              <li>You set your client seed (your influence on the randomness)</li>
              <li>You place a bet with your chosen target multiplier</li>
              <li>Result = HMAC-SHA256(server_seed, client_seed:nonce) mapped to a multiplier via inverse CDF</li>
              <li>If result ≥ target, you win bet × target</li>
              <li>Server reveals the seed — verify SHA256 matches the pre-committed hash</li>
              <li>New seed is pre-generated for the next round</li>
            </ol>
          </div>
        )}
      </div>
    </div>
  );
}

// =============================================================================
// LimboApp
// =============================================================================
function LimboApp() {
  const [loading, setLoading] = useState(true);
  const [connected, setConnected] = useState(false);
  const [balance, setBalance] = useState(0);
  const [currency, setCurrency] = useState('USD');
  const [bet, setBet] = useState('1.00');
  const [targetMultiplier, setTargetMultiplier] = useState('2.00');
  const [minBet, setMinBet] = useState(MIN_BET_DEFAULT);
  const [maxBet, setMaxBet] = useState(MAX_BET_DEFAULT);
  const [rtp, setRtp] = useState(1 - HOUSE_EDGE_DEFAULT);
  const [seedHash, setSeedHash] = useState('');

  const [isPlaying, setIsPlaying] = useState(false);
  const [resultMultiplier, setResultMultiplier] = useState(null);
  const [lastWon, setLastWon] = useState(null);
  const [animatingValue, setAnimatingValue] = useState(null);
  const [history, setHistory] = useState([]);
  const [soundEnabled, setSoundEnabled] = useState(true);
  const [mode, setMode] = useState('manual');
  const [gameInfoOpen, setGameInfoOpen] = useState(false);
  const [pfModalOpen, setPfModalOpen] = useState(false);
  const [gamePhase, setGamePhase] = useState('idle');
  const [showPayout, setShowPayout] = useState(null);
  const [particles, setParticles] = useState([]);
  const [alert, setAlert] = useState(null);
  const [rgsInfo, setRgsInfo] = useState(null);
  const [freeGrant, setFreeGrant] = useState(null);
  const [freeBetSummary, setFreeBetSummary] = useState(null);
  const [freeWelcome, setFreeWelcome] = useState(false);
  const [faved, setFaved] = useState(localStorage.getItem('fav_limbo') === 'true');

  const [autoRunning, setAutoRunning] = useState(false);
  const [autoRounds, setAutoRounds] = useState('10');
  const [autoPlayed, setAutoPlayed] = useState(0);
  const [autoWins, setAutoWins] = useState(0);
  const [autoLosses, setAutoLosses] = useState(0);
  const [autoProfit, setAutoProfit] = useState(0);
  const autoRunningRef = useRef(false);
  const autoStopRef = useRef(false);

  const audioRef = useRef(null);
  const balanceRef = useRef(0);
  const gameIdRef = useRef(0);
  const freeBetTrackerRef = useRef(null);

  useEffect(() => { balanceRef.current = balance; }, [balance]);
  useEffect(() => { audioRef.current = new AudioEngine(); }, []);

  const fetchFreeRounds = useCallback(async () => {
    // Preview mode: ?demo-free in URL shows mock free rounds UI
    if (window.location.search.indexOf('demo-free') !== -1) {
      setFreeGrant(function (prev) {
        if (prev && prev._mock) {
          var left = prev.rounds_left - 1;
          if (left <= 0) {
            // Free rounds exhausted — show summary if tracker has data
            var tracker = freeBetTrackerRef.current;
            if (tracker && tracker.rounds > 0) {
              setFreeBetSummary({ totalWinnings: tracker.totalWinnings, totalBet: tracker.totalBet, rounds: tracker.rounds });
            }
            freeBetTrackerRef.current = null;
            return null;
          }
          return Object.assign({}, prev, { rounds_left: left, rounds_used: prev.rounds_total - left });
        }
        // First time — initialize tracker and show welcome
        freeBetTrackerRef.current = { totalWinnings: 0, totalBet: 0, rounds: 0 };
        setFreeWelcome(true);
        return { _mock: true, id: 'mock', game_uuid: GAME_UUID, bet_amount: '5.00', rounds_total: 5, rounds_used: 0, rounds_left: 5, status: 'active' };
      });
      return;
    }
    try {
      const data = await api('/api/v1/freerounds');
      const grants = data && data.grants ? data.grants : [];
      const active = grants.find(function(g) {
        return g.status === 'active' && g.rounds_left > 0 && g.game_uuid === GAME_UUID;
      });
      if (active) {
        if (!freeBetTrackerRef.current) {
          freeBetTrackerRef.current = { totalWinnings: 0, totalBet: 0, rounds: 0 };
          setFreeWelcome(true);
        }
      } else {
        if (freeBetTrackerRef.current && freeBetTrackerRef.current.rounds > 0) {
          setFreeBetSummary({...freeBetTrackerRef.current});
          freeBetTrackerRef.current = null;
        }
      }
      setFreeGrant(active || null);
    } catch (e) {
      setFreeGrant(null);
    }
  }, []);

  // Boot: init demo + load state
  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        await initDemo();
        const state = await getState();
        if (cancelled) return;
        setBalance(parseFloat(state.balance));
        balanceRef.current = parseFloat(state.balance);
        setCurrency(state.currency);
        if (state.config) {
          if (state.config.min_bet) setMinBet(parseFloat(state.config.min_bet));
          if (state.config.max_bet) setMaxBet(parseFloat(state.config.max_bet));
          if (state.config.rtp) setRtp(parseFloat(state.config.rtp));
        }
        if (state.next_seed_hash) setSeedHash(state.next_seed_hash);
        setRgsInfo({
          rtp: state.config ? parseFloat(state.config.rtp) : (1 - HOUSE_EDGE_DEFAULT),
          minBet: state.config ? parseFloat(state.config.min_bet) : MIN_BET_DEFAULT,
          maxBet: state.config ? parseFloat(state.config.max_bet) : MAX_BET_DEFAULT,
          maxPayout: state.config && state.config.max_payout ? parseFloat(state.config.max_payout) : MAX_BET_DEFAULT * MAX_MULTIPLIER,
          maxMultiplier: state.game_data && state.game_data.max_target_multiplier ? parseFloat(state.game_data.max_target_multiplier) : MAX_MULTIPLIER,
          stages: [],
        });
        setConnected(true);
        await new Promise(r => setTimeout(r, 2500));
        setLoading(false);
        fetchFreeRounds();
      } catch (e) {
        if (!cancelled) {
          setAlert('Connection failed: ' + e.message);
          setTimeout(() => setAlert(null), 3500);
          setLoading(false);
        }
      }
    })();
    return () => { cancelled = true; };
  }, []);

  const playRound = useCallback(async () => {
    const betAmt = parseFloat(bet);
    const target = parseFloat(targetMultiplier);

    if (!connected) { setAlert('Not connected'); setTimeout(() => setAlert(null), 2500); return; }
    if (!freeGrant) {
      if (!isFinite(betAmt) || betAmt < minBet) return;
      if (betAmt > maxBet) { setAlert(`Max bet is $${maxBet.toFixed(2)}`); setTimeout(() => setAlert(null), 2500); return; }
      if (betAmt > balanceRef.current) {
        setAlert('Insufficient balance');
        setTimeout(() => setAlert(null), 2500);
        return;
      }
    }
    if (!isFinite(target) || target < MIN_MULTIPLIER || target > MAX_MULTIPLIER) return;

    setResultMultiplier(null);
    setLastWon(null);
    setAnimatingValue(0);
    setIsPlaying(true);

    await new Promise((r) => setTimeout(r, 30));

    setGamePhase('counting');
    if (audioRef.current) audioRef.current.sndBet();

    let resp;
    try {
      resp = await placeBetServer(betAmt, target);
    } catch (e) {
      setIsPlaying(false);
      setGamePhase('idle');
      setAlert(e.message || 'Bet failed');
      setTimeout(() => setAlert(null), 2500);
      return;
    }

    const gd = resp.game_data || {};
    const result = parseFloat(gd.rolled_multiplier) || 1;
    const won = gd.won === true;
    const payout = parseFloat(gd.payout) || 0;

    if (freeBetTrackerRef.current) {
      var updated = {
        rounds: freeBetTrackerRef.current.rounds + 1,
        totalBet: freeBetTrackerRef.current.totalBet + (freeGrant ? parseFloat(freeGrant.bet_amount) : betAmt),
        totalWinnings: freeBetTrackerRef.current.totalWinnings + (won ? payout : 0),
      };
      freeBetTrackerRef.current = updated;
    }

    setBalance(parseFloat(resp.balance));
    balanceRef.current = parseFloat(resp.balance);
    if (resp.next_seed_hash) setSeedHash(resp.next_seed_hash);

    // Animate the count-up from 1 to result
    const steps = 10, stepTime = 30;
    for (let i = 0; i <= steps; i++) {
      const progress = i / steps;
      const eased = 1 - Math.pow(1 - progress, 2);
      const current = 1 + (result - 1) * eased;
      setAnimatingValue(+current.toFixed(2));
      if (i % 2 === 0 && audioRef.current) audioRef.current.sndTick(progress);
      if (i < steps) await new Promise((r) => setTimeout(r, stepTime));
    }
    setAnimatingValue(null);

    if (won) {
      if (target >= 10 && audioRef.current) audioRef.current.sndBigWin();
      else if (audioRef.current) audioRef.current.sndWin();
    } else if (audioRef.current) {
      audioRef.current.sndLose();
    }

    setResultMultiplier(result);
    setLastWon(won);
    setGamePhase('result');
    setIsPlaying(false);

    if (won) {
      setShowPayout(`+${fmt(payout)}`);
      setTimeout(() => setShowPayout(null), 1200);
      const newParticles = Array.from({ length: 14 }, (_, i) => {
        const angle = ((360 / 14) * i + Math.random() * 25) * (Math.PI / 180);
        const dist = 60 + Math.random() * 50;
        return {
          id: Date.now() + i,
          x: 50, y: 50,
          tx: Math.cos(angle) * dist,
          ty: Math.sin(angle) * dist,
        };
      });
      setParticles(newParticles);
      setTimeout(() => setParticles([]), 800);
    }

    setTimeout(() => setGamePhase('idle'), 800);

    gameIdRef.current++;
    setHistory((prev) => [{
      id: gameIdRef.current,
      target, result, bet: betAmt, payout, won,
    }, ...prev].slice(0, 50));

    fetchFreeRounds();
  }, [bet, targetMultiplier, connected, minBet, maxBet, freeGrant, fetchFreeRounds]);

  const startAuto = useCallback(async () => {
    const rounds = parseInt(autoRounds) || 10;
    autoRunningRef.current = true;
    autoStopRef.current = false;
    setAutoRunning(true);
    setAutoPlayed(0);
    setAutoWins(0);
    setAutoLosses(0);
    setAutoProfit(0);
    const startBalance = balanceRef.current;

    for (let i = 0; i < rounds; i++) {
      if (autoStopRef.current || !autoRunningRef.current) break;
      const betAmt = parseFloat(bet);
      if (betAmt > balanceRef.current || balanceRef.current <= 0) break;
      const balBefore = balanceRef.current;
      await playRound();
      const balAfter = balanceRef.current;
      const roundProfit = balAfter - balBefore;
      if (roundProfit >= 0) {
        setAutoWins(w => w + 1);
      } else {
        setAutoLosses(l => l + 1);
      }
      setAutoProfit(balanceRef.current - startBalance);
      setAutoPlayed(i + 1);
      if (i < rounds - 1 && !autoStopRef.current) {
        await new Promise((r) => setTimeout(r, 800));
      }
    }
    autoRunningRef.current = false;
    setAutoRunning(false);
  }, [autoRounds, bet, playRound]);

  const stopAuto = () => {
    autoStopRef.current = true;
    autoRunningRef.current = false;
    setAutoRunning(false);
  };

  const chance = winChance(parseFloat(targetMultiplier) || 2, rtp);
  const displayValue = animatingValue !== null && animatingValue > 0
    ? animatingValue.toFixed(2)
    : resultMultiplier !== null ? resultMultiplier.toFixed(2) : '1.00';
  const resultClass = lastWon === true ? 'result-win' : lastWon === false ? 'result-lose' : '';
  const handleSoundToggle = () => {
    const e = audioRef.current ? audioRef.current.toggle() : true;
    setSoundEnabled(e);
  };

  const totalAutoRounds = parseInt(autoRounds) || 10;

  const halve = (prev) => Math.max(minBet, parseFloat(prev) / 2).toFixed(2);
  const dbl = (prev) => Math.min(balanceRef.current, maxBet, parseFloat(prev) * 2).toFixed(2);
  const maxBetVal = () => Math.min(balanceRef.current, maxBet).toFixed(2);

  if(loading) return (
    <div className="loading-screen">
      <div className="loading-content">
        <img className="loading-logo" src="logo-mybc.svg" alt="MYBC"/>
        <h1 style={{fontSize:24,fontWeight:900,color:"#FFFFFF",margin:"12px 0 4px",fontFamily:"var(--font-primary)",letterSpacing:1}}>Limbo</h1>
        <div className="loading-bar-wrap"><div className="loading-bar"/></div>
        <p className="loading-text">Loading your experience</p>
      </div>
    </div>
  );

  return (
    <React.Fragment>
      <div className="app">
        <div className="header">
          <div className="header-left">
            <div className="game-name">
              <img className="ico" src="icon-limbo.svg" alt="Limbo" style={{height:20,width:20,filter:"brightness(0) invert(1)"}}/>
              <span>Limbo</span>
            </div>
          </div>
          <div className="header-bal-mobile">
            <div className="bet-balance-icon">$</div>
            <span className="bet-balance-amount">{Number(balance).toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2})}</span>
          </div>
          <div className="header-right">
            <div className="fairplay" onClick={() => setPfModalOpen(true)}>Fair Play</div>
            <div className="info" onClick={() => setGameInfoOpen(true)}>i</div>
          </div>
        </div>

        {/* Auto progress bar — mobile only */}
        <div className="mobile-auto-progress" style={{height:24,flexShrink:0,display:'flex',flexDirection:'column',justifyContent:'center',padding:'0 14px',visibility:autoRunning?'visible':'hidden'}}>
          <div style={{display:'flex',justifyContent:'space-between',fontSize:10,color:'#8B8BA3',fontWeight:600}}>
            <span>R:<b style={{color:'#fff'}}>{autoPlayed}</b>/<b style={{color:'#fff'}}>{totalAutoRounds}</b></span>
            <span>W:<b style={{color:'#0ECC68'}}>{autoWins}</b> L:<b style={{color:'#F85F5D'}}>{autoLosses}</b></span>
            <span style={{color:autoProfit>=0?'#0ECC68':'#F85F5D',fontWeight:700}}>{autoProfit>=0?'+':''}{autoProfit.toFixed(2)}</span>
          </div>
          <div style={{height:2,background:'rgba(255,255,255,0.06)',borderRadius:2,marginTop:2,overflow:'hidden'}}>
            <div style={{height:'100%',background:'#F7931A',borderRadius:2,width:totalAutoRounds>0?`${(autoPlayed/totalAutoRounds*100)}%`:'0%',transition:'width 0.3s'}}/>
          </div>
        </div>

        <div className="row">
          <aside className="side">
            <div className="mode-toggle">
              <button className={mode === 'manual' ? 'active' : ''} onClick={() => setMode('manual')} disabled={autoRunning}>Manual</button>
              <button className={mode === 'auto' ? 'active' : ''} onClick={() => setMode('auto')} disabled={autoRunning}>Auto</button>
            </div>

            {mode === 'manual' && (
              <div className="tab-panel">
                {freeGrant && (
                  <div className="free-bet-banner">
                    <span className="free-bet-icon">{'🎁'}</span>
                    <div className="free-bet-info">
                      <div className="free-bet-title">FREE ROUNDS</div>
                      <div className="free-bet-count">{freeGrant.rounds_left} of {freeGrant.rounds_total} remaining</div>
                      <div className="free-bet-progress">
                        <div className="free-bet-progress-fill" style={{ width: ((freeGrant.rounds_left / freeGrant.rounds_total) * 100) + '%' }} />
                      </div>
                    </div>
                  </div>
                )}

                <div className="bet-balance-row">
                  <div className="bet-balance-icon">$</div>
                  <div className="bet-balance-text"><span className="bet-balance-label">Balance</span><span className="bet-balance-amount">{Number(balance).toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2})}</span></div>
                </div>
                <div className="auto-bet-row">
                  <div className={'input-row' + (freeGrant ? ' locked' : '')}>
                    <span className="currency">$</span>
                    <input type="number" value={freeGrant ? freeGrant.bet_amount : bet} onChange={(e) => setBet(e.target.value)} disabled={isPlaying || !!freeGrant} min={minBet} max={maxBet} step="0.10" />
                    <div className="chips">
                      <button className="chip" onClick={() => setBet((p) => halve(p))} disabled={isPlaying || !!freeGrant}>{'½'}</button>
                      <button className="chip" onClick={() => setBet((p) => dbl(p))} disabled={isPlaying || !!freeGrant}>2x</button>
                      <button className="chip" onClick={() => setBet(maxBetVal())} disabled={isPlaying || !!freeGrant}>Max</button>
                    </div>
                  </div>
                  <button className={'auto-start-btn' + (freeGrant ? ' free-mode' : '')} onClick={playRound} disabled={isPlaying || !connected}>
                    {!connected ? 'CONNECTING…' : isPlaying ? 'Rolling...' : freeGrant ? 'FREE ROUND' : 'BET'}
                  </button>
                </div>
                <div className="field">
                  <label className="label">Target Multiplier</label>
                  <div className="input-row target-input">
                    <input type="number" value={targetMultiplier} onChange={(e) => setTargetMultiplier(e.target.value)} disabled={isPlaying} min={MIN_MULTIPLIER} max={MAX_MULTIPLIER} step="0.01" />
                    <span className="currency">x</span>
                  </div>
                </div>

                <div className="side-stats">
                  <div className="stat-box">
                    <span className="stat-label">Win Chance</span>
                    <span className="stat-val accent">{chance.toFixed(2)}%</span>
                  </div>
                  <div className="stat-box">
                    <span className="stat-label">Payout</span>
                    <span className="stat-val green">{fmt(parseFloat(freeGrant ? freeGrant.bet_amount : (bet || '0')) * parseFloat(targetMultiplier || '0'))}</span>
                  </div>
                </div>
              </div>
            )}

            {mode === 'auto' && (
              <div className="tab-panel">
                {freeGrant && (
                  <div className="free-bet-banner">
                    <span className="free-bet-icon">{'🎁'}</span>
                    <div className="free-bet-info">
                      <div className="free-bet-title">FREE ROUNDS</div>
                      <div className="free-bet-count">{freeGrant.rounds_left} of {freeGrant.rounds_total} remaining</div>
                      <div className="free-bet-progress">
                        <div className="free-bet-progress-fill" style={{ width: ((freeGrant.rounds_left / freeGrant.rounds_total) * 100) + '%' }} />
                      </div>
                    </div>
                  </div>
                )}

                <div className="bet-balance-row">
                  <div className="bet-balance-icon">$</div>
                  <div className="bet-balance-text"><span className="bet-balance-label">Balance</span><span className="bet-balance-amount">{Number(balance).toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2})}</span></div>
                </div>
                <div className="auto-bet-row">
                  <div className={'input-row' + (freeGrant ? ' locked' : '')}>
                    <span className="currency">$</span>
                    <input type="number" value={freeGrant ? freeGrant.bet_amount : bet} onChange={(e) => setBet(e.target.value)} disabled={autoRunning || !!freeGrant} min={minBet} max={maxBet} step="0.10" />
                    <div className="chips">
                      <button className="chip" onClick={() => setBet((p) => halve(p))} disabled={autoRunning || !!freeGrant}>{'½'}</button>
                      <button className="chip" onClick={() => setBet((p) => dbl(p))} disabled={autoRunning || !!freeGrant}>2x</button>
                      <button className="chip" onClick={() => setBet(maxBetVal())} disabled={autoRunning || !!freeGrant}>Max</button>
                    </div>
                  </div>
                  {autoRunning ? (
                    <button className="stop-btn" onClick={stopAuto}>Stop Auto</button>
                  ) : (
                    <button className={'auto-start-btn' + (freeGrant ? ' free-mode' : '')} onClick={startAuto} disabled={isPlaying || !connected}>{freeGrant ? 'Start Auto (Free Rounds)' : 'Start Auto'}</button>
                  )}
                </div>
                <div className="field">
                  <label className="label">Target Multiplier</label>
                  <div className="input-row target-input">
                    <input type="number" value={targetMultiplier} onChange={(e) => setTargetMultiplier(e.target.value)} disabled={autoRunning} min={MIN_MULTIPLIER} max={MAX_MULTIPLIER} step="0.01" />
                    <span className="currency">x</span>
                  </div>
                </div>
                <div className="field">
                  <label className="label small">Rounds</label>
                  <div className="input-row">
                    <input type="number" value={autoRounds} onChange={(e) => setAutoRounds(e.target.value)} disabled={autoRunning} min={1} max={1000} />
                  </div>
                </div>
                {autoRunning && (
                  <div className="auto-progress-bar-wrap">
                    <div className="auto-progress-text">Round {autoPlayed} / {autoRounds}</div>
                    <div className="auto-progress-bar">
                      <div className="auto-progress-fill" style={{ width: `${(autoPlayed / (parseInt(autoRounds) || 1)) * 100}%` }} />
                    </div>
                  </div>
                )}
                <div className="side-stats">
                  <div className="stat-box">
                    <span className="stat-label">Win Chance</span>
                    <span className="stat-val accent">{chance.toFixed(2)}%</span>
                  </div>
                  <div className="stat-box">
                    <span className="stat-label">Payout</span>
                    <span className="stat-val green">{fmt(parseFloat(bet || '0') * parseFloat(targetMultiplier || '0'))}</span>
                  </div>
                </div>
              </div>
            )}
          </aside>

          <main className="main">
            <div className="history-bar">
              {history.length === 0 ? (
                <span className="history-empty">No games yet</span>
              ) : history.map((h) => (
                <span key={h.id} className={`history-chip ${h.won ? 'hc-win' : 'hc-lose'}`}>{h.result.toFixed(2)}x</span>
              ))}
            </div>

            <div className={`game-area phase-${gamePhase} ${resultClass}`}>
              <div className="bg-ambient">
                <div className="stars" />
                <div className="nebula n1" />
                <div className="nebula n2" />
                <div className="infinity-ring ring-1" />
                <div className="infinity-ring ring-2" />
                <div className="infinity-ring ring-3" />
                <div className={`energy-wave ${gamePhase === 'counting' ? 'active' : ''}`} />
                <div className={`energy-wave wave-2 ${gamePhase === 'counting' ? 'active' : ''}`} />
              </div>

              {gamePhase === 'result' && lastWon && <div className="bg-flash win" />}
              {gamePhase === 'result' && (
                <React.Fragment>
                  <div className={`ripple ${resultClass}`} />
                  <div className={`ripple ripple-2 ${resultClass}`} />
                </React.Fragment>
              )}

              <div className="cube-wrap">
                <div className={`cube-track ${resultClass} phase-${gamePhase}`}>
                  {(() => {
                    const tgt = parseFloat(targetMultiplier) || 2;
                    const val = animatingValue !== null && animatingValue > 0
                      ? animatingValue
                      : resultMultiplier !== null ? resultMultiplier : 1;
                    const fillPct = tgt <= 1 ? 0 : Math.max(0, Math.min(100, ((val - 1) / (tgt - 1)) * 100));
                    return (
                      <React.Fragment>
                        <div className={`cube-fill ${resultClass} ${animatingValue !== null ? 'animating' : ''}`} style={{ height: `${fillPct}%` }} />
                        {fillPct > 0 && (
                          <div className={`cube-indicator ${resultClass} ${animatingValue !== null ? 'animating' : ''}`} style={{ bottom: `${fillPct}%` }} />
                        )}
                      </React.Fragment>
                    );
                  })()}
                  <div className={`cube-value ${resultClass} ${animatingValue !== null ? 'animating' : ''} phase-${gamePhase}`}>
                    {displayValue}x
                  </div>

                  {particles.map((p) => (
                    <div key={p.id} className="particle" style={{ left: `${p.x}%`, top: `${p.y}%`, '--tx': `${p.tx}px`, '--ty': `${p.ty}px` }} />
                  ))}
                </div>

                {showPayout && (
                  <div className="payout-float">{showPayout}</div>
                )}
              </div>
            </div>
          </main>
        </div>

        <div className="bottom">
          <div className="bottom-icons">
            <div className={`ic sound-toggle${!soundEnabled ? ' muted' : ''}`} title="Toggle Sound" onClick={handleSoundToggle}>
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                <polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" />
                <path className="sound-waves" d="M15.54 8.46a5 5 0 0 1 0 7.07M19.07 4.93a10 10 0 0 1 0 14.14" style={{ display: soundEnabled ? undefined : 'none' }} />
              </svg>
            </div>
            <div className={`ic${faved ? ' faved' : ''}`} title="Favorite" onClick={() => { const next = !faved; setFaved(next); localStorage.setItem('fav_limbo', next); }}>
              <svg width="18" height="18" viewBox="0 0 24 24" fill={faved ? "currentColor" : "none"} stroke="currentColor" strokeWidth="2">
                <polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" />
              </svg>
            </div>
          </div>
          <div className="bottom-logo">MYBC</div>
        </div>
      </div>

      {alert && !gameInfoOpen && !pfModalOpen && (
        <div className="alert-toast">
          <span className="alert-icon">{'⚠'}</span>
          {alert}
        </div>
      )}

      <GameInfoModal open={gameInfoOpen} onClose={() => setGameInfoOpen(false)} rgsInfo={rgsInfo} />
      <ProvablyFairModal open={pfModalOpen} onClose={() => setPfModalOpen(false)} seedHash={seedHash} />

      {freeWelcome && freeGrant && (
        <div className="modal-overlay show" onClick={() => setFreeWelcome(false)}>
          <div className="modal free-bet-welcome" onClick={(e) => e.stopPropagation()}>
            <div className="fbw-icon">{'\uD83C\uDF81'}</div>
            <h2>Free Rounds Available!</h2>
            <p className="fbw-desc">You have been awarded free rounds for this game. The bet amount is fixed at <strong>{fmt(parseFloat(freeGrant.bet_amount))}</strong> per round.</p>
            <div className="fbw-count">
              <span className="fbw-count-num">{freeGrant.rounds_total}</span>
              <span className="fbw-count-label">Free Rounds</span>
            </div>
            <button className="fbw-btn" onClick={() => setFreeWelcome(false)}>START PLAYING</button>
          </div>
        </div>
      )}

      {freeBetSummary && (
        <div className="modal-overlay show" onClick={() => setFreeBetSummary(null)}>
          <div className="modal free-bet-summary" onClick={(e) => e.stopPropagation()}>
            <button className="modal-close" onClick={() => setFreeBetSummary(null)}>&times;</button>
            <div className="fbs-icon">{'\uD83C\uDF81'}</div>
            <h2>Free Rounds Complete!</h2>
            <div className="fbs-stats">
              <div className="fbs-stat">
                <span className="fbs-stat-label">Rounds Played</span>
                <span className="fbs-stat-val">{freeBetSummary.rounds}</span>
              </div>
            </div>
            <div className="fbs-winnings">
              <span className="fbs-winnings-label">Total Won</span>
              <span className="fbs-winnings-val">{fmt(freeBetSummary.totalWinnings)}</span>
            </div>
            <button className="fbs-btn" onClick={() => setFreeBetSummary(null)}>CONTINUE PLAYING</button>
          </div>
        </div>
      )}
    </React.Fragment>
  );
}

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