🎮ArcadeLab

AVIATOR 4X

by SolarPenguin46
1221 lines47.4 KB
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>AVIATOR 4X</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=Orbitron:wght@700;800;900&display=swap" rel="stylesheet">
<style>
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
:root{
  --bg:#05070d;--panel:rgba(19,25,42,.72);--line:rgba(255,255,255,.06);
  --text:#eef2fb;--muted:#7a8aa8;--red:#ff3b47;--green:#22d17b;
  --gold:#ffc043;--blue:#4a8bff;--orange:#ff8a3d;
}
html,body{height:100%;width:100%}
body{
  font-family:'Inter',system-ui,-apple-system,sans-serif;color:var(--text);
  background:
    radial-gradient(900px 520px at 50% -8%, #17233f 0%, transparent 62%),
    radial-gradient(800px 460px at 50% 118%, #1d1226 0%, transparent 62%),
    var(--bg);
  display:flex;align-items:center;justify-content:center;padding:10px;overflow:hidden;
}

/* ================= AUTH SCREEN ================= */
.authScreen{
  position:fixed;inset:0;z-index:100;display:none;
  align-items:center;justify-content:center;padding:16px;overflow-y:auto;
  background:
    radial-gradient(900px 520px at 50% -8%, #1a2542 0%, transparent 62%),
    radial-gradient(800px 460px at 50% 118%, #221430 0%, transparent 62%),
    var(--bg);
}
.authScreen.open{display:flex}
.authCard{
  width:100%;max-width:400px;border-radius:22px;padding:24px 22px;
  background:linear-gradient(180deg,rgba(24,32,52,.92),rgba(11,15,26,.96));
  border:1px solid var(--line);
  box-shadow:0 40px 100px rgba(0,0,0,.7), inset 0 1px 0 rgba(255,255,255,.05);
  animation:authRise .4s cubic-bezier(.34,1.4,.64,1);
}
@keyframes authRise{from{opacity:0;transform:translateY(24px) scale(.97)}to{opacity:1;transform:none}}
.authLogo{
  font-family:'Orbitron',sans-serif;font-weight:900;font-size:24px;letter-spacing:2px;
  text-align:center;display:flex;align-items:baseline;justify-content:center;gap:4px;
  background:linear-gradient(90deg,#fff,#9fb4d8);
  -webkit-background-clip:text;background-clip:text;color:transparent;
}
.authLogo b{background:linear-gradient(90deg,var(--red),var(--orange));-webkit-background-clip:text;background-clip:text;color:transparent}
.authTag{
  text-align:center;font-size:9px;font-weight:800;letter-spacing:2.4px;
  color:var(--muted);margin-top:6px;margin-bottom:22px;
}
.tabs{
  display:flex;gap:6px;padding:5px;border-radius:14px;margin-bottom:18px;
  background:rgba(255,255,255,.035);border:1px solid var(--line);
}
.tabs .tab{
  flex:1;height:40px;border-radius:10px;border:none;cursor:pointer;
  font-family:'Orbitron';font-weight:800;font-size:12px;letter-spacing:1.4px;
  background:transparent;color:var(--muted);transition:.2s;
}
.tabs .tab.active{
  color:#04120b;background:linear-gradient(180deg,#3df39a,#12b866);
  box-shadow:0 6px 18px rgba(34,209,123,.28);
}
.authForm{display:flex;flex-direction:column;gap:13px}
.authForm.hidden{display:none}
.aField{display:flex;flex-direction:column;gap:6px}
.aField label{font-size:9px;font-weight:800;letter-spacing:1.4px;color:var(--muted)}
.aField input{
  width:100%;height:48px;border-radius:12px;border:1px solid var(--line);
  background:rgba(255,255,255,.035);color:var(--text);
  font-family:'Inter';font-weight:600;font-size:14px;padding:0 14px;outline:none;
  transition:border-color .2s,background .2s;
}
.aField input:focus{border-color:rgba(74,139,255,.55);background:rgba(74,139,255,.08)}
.aField input::placeholder{color:#4c5872}
.aErr{
  font-size:11px;font-weight:700;color:var(--red);letter-spacing:.3px;
  min-height:14px;text-align:center;
}
.aErr.ok{color:var(--green)}
.aGo{
  height:52px;border:none;border-radius:14px;cursor:pointer;
  font-family:'Orbitron';font-weight:900;font-size:15px;letter-spacing:1.4px;color:#04120b;
  background:linear-gradient(180deg,#3df39a,#12b866);
  box-shadow:0 10px 26px rgba(34,209,123,.32),inset 0 1px 0 rgba(255,255,255,.4);
  transition:transform .12s,filter .2s;margin-top:4px;
}
.aGo:hover{filter:brightness(1.07)}
.aGo:active{transform:scale(.98)}
.guestBtn{
  width:100%;height:44px;margin-top:14px;border-radius:12px;cursor:pointer;
  border:1px solid var(--line);background:transparent;color:var(--muted);
  font-family:'Inter';font-weight:800;font-size:11px;letter-spacing:1.4px;transition:.2s;
}
.guestBtn:hover{background:rgba(255,255,255,.05);color:#cfe0ff}

body:not(.authed) .app{display:none}

/* ================= APP SHELL ================= */
.app{width:100%;max-width:470px;height:min(880px,calc(100dvh - 20px));display:flex;flex-direction:column;gap:10px}

.topbar{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:2px 4px}
.logo{
  font-family:'Orbitron',sans-serif;font-weight:900;font-size:17px;letter-spacing:1.5px;
  display:flex;align-items:baseline;gap:3px;
  background:linear-gradient(90deg,#fff,#9fb4d8);
  -webkit-background-clip:text;background-clip:text;color:transparent;
}
.logo b{background:linear-gradient(90deg,var(--red),var(--orange));-webkit-background-clip:text;background-clip:text;color:transparent}
.topRight{display:flex;align-items:center;gap:8px}
.userChip{
  display:flex;align-items:center;gap:6px;padding:4px 6px 4px 4px;
  background:var(--panel);border:1px solid var(--line);border-radius:12px;
  backdrop-filter:blur(10px);
}
.userChip .av{
  width:26px;height:26px;border-radius:8px;flex:0 0 auto;
  display:flex;align-items:center;justify-content:center;
  font-family:'Orbitron';font-weight:900;font-size:11px;color:#04120b;
  background:linear-gradient(135deg,#3df39a,#12b866);
  box-shadow:0 3px 10px rgba(34,209,123,.35);
}
.userChip .nm{
  font-size:10px;font-weight:800;color:#c7d6f0;letter-spacing:.4px;
  max-width:70px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.userChip .out{
  width:22px;height:22px;border-radius:7px;border:1px solid var(--line);cursor:pointer;
  background:rgba(255,59,71,.1);color:#ff8b93;
  font-size:12px;line-height:1;display:flex;align-items:center;justify-content:center;
  transition:.18s;
}
.userChip .out:hover{background:rgba(255,59,71,.25);color:#fff}
.userChip .out:active{transform:scale(.92)}
.balance{
  display:flex;flex-direction:column;align-items:flex-end;gap:1px;
  background:var(--panel);border:1px solid var(--line);border-radius:12px;
  padding:6px 11px;backdrop-filter:blur(10px);
}
.balance span{font-size:8px;letter-spacing:1.6px;color:var(--muted);font-weight:800}
.balance b{font-family:'Orbitron';font-size:14px;font-weight:800;color:var(--green)}
.cashBtns{display:flex;flex-direction:column;gap:4px}
.cashBtns button{
  height:22px;padding:0 9px;border-radius:8px;border:1px solid transparent;
  font-family:'Orbitron';font-weight:800;font-size:9px;letter-spacing:.8px;
  cursor:pointer;transition:.18s;white-space:nowrap;
}
.cashBtns .dep{color:#04120b;background:linear-gradient(180deg,#3df39a,#12b866);box-shadow:0 4px 12px rgba(34,209,123,.3)}
.cashBtns .dep:hover{filter:brightness(1.08)}
.cashBtns .wd{color:#2a1500;background:linear-gradient(180deg,#ffd76a,#ff9a1f);box-shadow:0 4px 12px rgba(255,160,40,.3)}
.cashBtns .wd:hover{filter:brightness(1.08)}
.cashBtns button:active{transform:scale(.95)}

/* history */
.history{
  display:flex;gap:6px;overflow:hidden;padding:2px 4px;height:26px;align-items:center;
  -webkit-mask-image:linear-gradient(90deg,#000 80%,transparent);
  mask-image:linear-gradient(90deg,#000 80%,transparent);
}
.chip{
  flex:0 0 auto;font-size:11px;font-weight:800;padding:4px 9px;border-radius:999px;
  border:1px solid transparent;font-family:'Orbitron';letter-spacing:.5px;
  animation:chipIn .35s ease both;
}
.chip.lo{color:#ff8b93;background:rgba(255,59,71,.12);border-color:rgba(255,59,71,.35)}
.chip.mid{color:#8fb4ff;background:rgba(74,139,255,.12);border-color:rgba(74,139,255,.35)}
.chip.hi{color:#ffd479;background:rgba(255,192,67,.13);border-color:rgba(255,192,67,.42);box-shadow:0 0 14px rgba(255,192,67,.22)}
@keyframes chipIn{from{transform:translateX(-10px) scale(.85);opacity:0}to{transform:none;opacity:1}}

/* prng bar */
.fair{
  display:flex;align-items:center;gap:8px;padding:0 6px;height:24px;
  font-size:10px;font-weight:800;letter-spacing:.8px;color:var(--muted);overflow:hidden;
}
.fair .dot{width:6px;height:6px;border-radius:50%;background:var(--green);box-shadow:0 0 8px var(--green);flex:0 0 auto;animation:blip 2.4s ease-in-out infinite}
@keyframes blip{0%,100%{opacity:1}50%{opacity:.3}}
.fair .seed{
  font-family:'Orbitron';color:#a9bde0;cursor:pointer;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;flex:1 1 auto;min-width:0;
}
.fair .seed:hover{color:#fff}
.fair .nonce{font-family:'Orbitron';color:#6d7f9e;flex:0 0 auto}
.fair .next{
  flex:0 0 auto;display:flex;align-items:center;gap:4px;
  font-family:'Orbitron';font-weight:900;font-size:10px;letter-spacing:.4px;
  padding:3px 8px;border-radius:999px;cursor:pointer;user-select:none;white-space:nowrap;
  color:#ffd479;background:rgba(255,192,67,.12);border:1px solid rgba(255,192,67,.38);
  transition:color .2s,background .2s,border-color .2s,box-shadow .2s;
  box-shadow:0 0 12px rgba(255,192,67,.14);
}
.fair .next::before{content:'NEXT';font-size:7px;letter-spacing:1.2px;opacity:.7;font-weight:800}
.fair .next.live{
  color:#9dc0ff;background:rgba(74,139,255,.13);border-color:rgba(74,139,255,.4);
  box-shadow:0 0 12px rgba(74,139,255,.16);
}
.fair .next.live::before{content:'LIVE'}
.fair .next b{font-weight:900}
.fair .next.masked b{filter:blur(5px);opacity:.75}
.fair .next.masked::before{opacity:.4}
.fair .next.pop{animation:nxPop .45s cubic-bezier(.34,1.56,.64,1)}
@keyframes nxPop{0%{transform:scale(1)}40%{transform:scale(1.13)}100%{transform:scale(1)}}
.fair button{
  flex:0 0 auto;height:20px;padding:0 9px;border-radius:7px;border:1px solid var(--line);
  background:rgba(74,139,255,.1);color:#9fb8e8;font-family:'Inter';
  font-size:9px;font-weight:800;letter-spacing:1px;cursor:pointer;transition:.18s;
}
.fair button:hover{background:rgba(74,139,255,.22);color:#dbe8ff}
.fair button:active{transform:scale(.94)}

/* stage */
.stage{
  position:relative;flex:1;min-height:0;border-radius:20px;overflow:hidden;
  background:linear-gradient(180deg,rgba(20,27,46,.85),rgba(9,12,22,.92));
  border:1px solid var(--line);
  box-shadow:inset 0 0 70px rgba(74,139,255,.05),0 24px 60px rgba(0,0,0,.5);
}
canvas{position:absolute;inset:0;display:block}
.hud{
  position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:10px;pointer-events:none;
}
.mult{
  font-family:'Orbitron';font-weight:900;font-size:clamp(40px,14vw,62px);
  line-height:1;letter-spacing:-1px;color:#fff;text-shadow:0 0 34px rgba(255,255,255,.22);
  transition:color .18s,text-shadow .18s;
}
.mult.mid{color:var(--gold);text-shadow:0 0 36px rgba(255,192,67,.42)}
.mult.high{color:var(--green);text-shadow:0 0 36px rgba(34,209,123,.5)}
.mult.dead{color:var(--red);text-shadow:0 0 36px rgba(255,59,71,.55)}
.status{font-size:11px;font-weight:700;letter-spacing:1.6px;text-transform:uppercase;color:var(--muted)}
.progress{position:absolute;left:0;right:0;bottom:0;height:3px;background:rgba(255,255,255,.05);opacity:0;transition:opacity .25s}
.progress.on{opacity:1}
.progress i{display:block;height:100%;width:0;background:linear-gradient(90deg,var(--blue),var(--red));box-shadow:0 0 12px rgba(255,59,71,.6);transition:width .1s linear}
.toast{
  position:absolute;left:50%;top:24%;transform:translate(-50%,-50%) scale(.9);
  font-family:'Orbitron';font-weight:900;font-size:20px;opacity:0;pointer-events:none;white-space:nowrap;
  color:var(--gold);text-shadow:0 0 24px rgba(255,192,67,.65);
}
.toast.show{animation:toastPop 1.15s ease forwards}
.toast.warn{color:var(--red);text-shadow:0 0 24px rgba(255,59,71,.6);font-family:'Inter';font-size:13px;font-weight:800;letter-spacing:1px}
@keyframes toastPop{
  0%{opacity:0;transform:translate(-50%,-30%) scale(.85)}
  18%{opacity:1;transform:translate(-50%,-50%) scale(1.06)}
  70%{opacity:1;transform:translate(-50%,-62%) scale(1)}
  100%{opacity:0;transform:translate(-50%,-95%) scale(.94)}
}

/* panel */
.panel{
  background:var(--panel);border:1px solid var(--line);border-radius:18px;padding:12px;
  display:flex;flex-direction:column;gap:10px;backdrop-filter:blur(14px);
  box-shadow:0 18px 40px rgba(0,0,0,.45);
}
.row{display:flex;gap:10px}
.field{flex:1;display:flex;flex-direction:column;gap:6px;min-width:0}
.field label{font-size:9px;font-weight:800;letter-spacing:1.4px;color:var(--muted)}
.field input{
  width:100%;height:46px;border-radius:12px;border:1px solid var(--line);
  background:rgba(255,255,255,.035);color:var(--text);
  font-family:'Orbitron';font-weight:700;font-size:15px;padding:0 12px;outline:none;
  transition:border-color .2s,background .2s;
}
.field input:focus{border-color:rgba(74,139,255,.55);background:rgba(74,139,255,.08)}
.field input::placeholder{color:#4c5872}
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
input[type=number]{-moz-appearance:textfield}
.quick{display:grid;grid-template-columns:repeat(5,1fr);gap:8px}
.quick button{
  height:36px;border-radius:10px;border:1px solid var(--line);
  background:rgba(255,255,255,.04);color:var(--muted);
  font-family:'Orbitron';font-weight:700;font-size:12px;cursor:pointer;transition:.18s;
}
.quick button:hover{background:rgba(74,139,255,.14);color:#cfe0ff;border-color:rgba(74,139,255,.35)}
.quick button:active{transform:scale(.95)}
.action{
  height:58px;border:1px solid transparent;border-radius:14px;cursor:pointer;user-select:none;
  font-family:'Orbitron';font-weight:900;font-size:16px;letter-spacing:1px;color:#04120b;
  background:linear-gradient(180deg,#3df39a,#12b866);
  box-shadow:0 10px 26px rgba(34,209,123,.3),inset 0 1px 0 rgba(255,255,255,.4);
  transition:transform .12s,filter .2s,background .25s,box-shadow .25s;
}
.action:active{transform:scale(.98)}
.action.cash{
  color:#2a1500;background:linear-gradient(180deg,#ffd76a,#ff9a1f);
  box-shadow:0 10px 30px rgba(255,160,40,.4),inset 0 1px 0 rgba(255,255,255,.45);
  animation:cashPulse 1s ease-in-out infinite;
}
@keyframes cashPulse{0%,100%{filter:brightness(1)}50%{filter:brightness(1.13)}}
.action.cancel{
  color:#ffd9dc;background:linear-gradient(180deg,#3a2030,#241722);
  border-color:rgba(255,59,71,.35);box-shadow:0 8px 22px rgba(0,0,0,.4);
}

/* modal */
.modal{
  position:fixed;inset:0;z-index:50;display:none;
  align-items:center;justify-content:center;padding:16px;
  background:rgba(3,5,10,.72);backdrop-filter:blur(8px);
}
.modal.open{display:flex;animation:mFade .2s ease}
@keyframes mFade{from{opacity:0}to{opacity:1}}
.sheet{
  width:100%;max-width:400px;border-radius:20px;padding:18px;
  background:linear-gradient(180deg,#151c2e,#0c1019);
  border:1px solid var(--line);box-shadow:0 30px 80px rgba(0,0,0,.65);
  display:flex;flex-direction:column;gap:14px;
  animation:mRise .28s cubic-bezier(.34,1.4,.64,1);
}
@keyframes mRise{from{opacity:0;transform:translateY(22px) scale(.97)}to{opacity:1;transform:none}}
.sheet h3{
  font-family:'Orbitron';font-weight:900;font-size:15px;letter-spacing:1.4px;
  display:flex;align-items:center;gap:8px;
}
.sheet h3 .tag{
  font-family:'Inter';font-size:9px;font-weight:800;letter-spacing:1px;
  padding:3px 7px;border-radius:999px;color:#04120b;
}
.sheet.dep h3 .tag{background:linear-gradient(90deg,#3df39a,#12b866)}
.sheet.wd  h3 .tag{background:linear-gradient(90deg,#ffd76a,#ff9a1f);color:#2a1500}
.sheet .sub{font-size:11px;color:var(--muted);font-weight:600;line-height:1.5;margin-top:-8px}
.sheet .cur{
  display:flex;align-items:center;justify-content:space-between;
  padding:9px 12px;border-radius:12px;border:1px solid var(--line);
  background:rgba(255,255,255,.03);font-size:10px;font-weight:800;letter-spacing:1px;color:var(--muted);
}
.sheet .cur b{font-family:'Orbitron';font-size:14px;color:var(--green)}
.amtGrid{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}
.amtGrid button{
  height:42px;border-radius:12px;border:1px solid var(--line);
  background:rgba(255,255,255,.04);color:#c7d6f0;
  font-family:'Orbitron';font-weight:800;font-size:12px;cursor:pointer;transition:.18s;
}
.amtGrid button:hover{background:rgba(74,139,255,.15);border-color:rgba(74,139,255,.4);color:#fff}
.amtGrid button.sel{background:rgba(74,139,255,.22);border-color:rgba(74,139,255,.6);color:#fff;box-shadow:0 0 14px rgba(74,139,255,.25)}
.amtGrid button:active{transform:scale(.95)}
.sheet .field input{height:52px;font-size:17px;text-align:center;letter-spacing:1px}
.sheetActions{display:flex;gap:10px}
.sheetActions button{
  flex:1;height:52px;border-radius:14px;border:1px solid transparent;cursor:pointer;
  font-family:'Orbitron';font-weight:900;font-size:14px;letter-spacing:1px;transition:.15s;
}
.sheetActions button:active{transform:scale(.98)}
.sheetActions .ghost{
  flex:0 0 38%;color:#a9bde0;background:rgba(255,255,255,.04);border-color:var(--line);
}
.sheetActions .ghost:hover{background:rgba(255,255,255,.08);color:#fff}
.sheetActions .go{color:#04120b;background:linear-gradient(180deg,#3df39a,#12b866);box-shadow:0 10px 24px rgba(34,209,123,.3)}
.sheet.wd .sheetActions .go{
  color:#2a1500;background:linear-gradient(180deg,#ffd76a,#ff9a1f);
  box-shadow:0 10px 24px rgba(255,160,40,.35);
}
.sheetActions .go:hover{filter:brightness(1.07)}
.note{font-size:10px;color:#5b6a85;font-weight:600;text-align:center;line-height:1.5}

/* global toast */
.gToast{
  position:fixed;left:50%;top:14%;transform:translate(-50%,-14px);z-index:80;
  padding:12px 20px;border-radius:14px;pointer-events:none;
  font-family:'Orbitron';font-weight:900;font-size:14px;letter-spacing:.5px;
  background:linear-gradient(180deg,#1c2740,#121a2b);border:1px solid rgba(255,255,255,.1);
  box-shadow:0 18px 40px rgba(0,0,0,.55);opacity:0;white-space:nowrap;
}
.gToast.show{animation:gPop 1.8s ease forwards}
.gToast.ok{color:#4ff0a5;border-color:rgba(34,209,123,.4);box-shadow:0 18px 40px rgba(0,0,0,.55),0 0 30px rgba(34,209,123,.18)}
.gToast.warn{color:#ff8b93;border-color:rgba(255,59,71,.4);box-shadow:0 18px 40px rgba(0,0,0,.55),0 0 30px rgba(255,59,71,.18)}
@keyframes gPop{
  0%{opacity:0;transform:translate(-50%,-20px) scale(.94)}
  12%{opacity:1;transform:translate(-50%,0) scale(1)}
  80%{opacity:1;transform:translate(-50%,0) scale(1)}
  100%{opacity:0;transform:translate(-50%,-10px) scale(.97)}
}
</style>
</head>
<body>

<!-- ============ AUTH SCREEN ============ -->
<div class="authScreen open" id="authScreen">
  <div class="authCard">
    <div class="authLogo">AVIATOR <b>4X</b></div>
    <p class="authTag">PLAY · WIN · CASH OUT</p>

    <div class="tabs">
      <button class="tab active" data-tab="login" type="button">LOG IN</button>
      <button class="tab" data-tab="register" type="button">REGISTER</button>
    </div>

    <form class="authForm" id="loginForm" autocomplete="on">
      <div class="aField">
        <label>USERNAME OR EMAIL</label>
        <input id="loginUser" type="text" autocomplete="username" placeholder="your username" required>
      </div>
      <div class="aField">
        <label>PASSWORD</label>
        <input id="loginPass" type="password" autocomplete="current-password" placeholder="••••••••" required>
      </div>
      <p class="aErr" id="loginErr"></p>
      <button class="aGo" type="submit">LOG IN</button>
    </form>

    <form class="authForm hidden" id="registerForm" autocomplete="on">
      <div class="aField">
        <label>USERNAME</label>
        <input id="regUser" type="text" autocomplete="username" placeholder="at least 3 characters" required minlength="3" maxlength="16">
      </div>
      <div class="aField">
        <label>EMAIL</label>
        <input id="regEmail" type="email" autocomplete="email" placeholder="you@example.com" required>
      </div>
      <div class="aField">
        <label>PASSWORD</label>
        <input id="regPass" type="password" autocomplete="new-password" placeholder="at least 4 characters" required minlength="4">
      </div>
      <div class="aField">
        <label>CONFIRM PASSWORD</label>
        <input id="regPass2" type="password" autocomplete="new-password" placeholder="repeat password" required minlength="4">
      </div>
      <p class="aErr" id="regErr"></p>
      <button class="aGo" type="submit">CREATE ACCOUNT</button>
    </form>

    <button class="guestBtn" id="guestBtn" type="button">CONTINUE AS GUEST</button>
  </div>
</div>

<!-- ============ MAIN APP ============ -->
<div class="app">

  <div class="topbar">
    <div class="logo">AVIATOR <b>4X</b></div>
    <div class="topRight">
      <div class="userChip">
        <span class="av" id="userAv">A</span>
        <span class="nm" id="userName">guest</span>
        <button class="out" id="logoutBtn" title="Log out">⎋</button>
      </div>
      <div class="balance"><span>BALANCE (KSH)</span><b id="balance">KSh 10,000.00</b></div>
      <div class="cashBtns">
        <button class="dep" id="depBtn">+ DEPOSIT</button>
        <button class="wd"  id="wdBtn">− WITHDRAW</button>
      </div>
    </div>
  </div>

  <div class="history" id="history"></div>

  <div class="fair">
    <span class="dot"></span>
    <span class="seed" id="seedTxt" title="Click to set a custom seed">seed —</span>
    <span class="nonce" id="nonceTxt">#0</span>
    <span class="next" id="nextCrash" title="Next crash point from the PRNG — click to hide">—</span>
    <button id="linkBtn" title="Copy shareable PRNG link">LINK</button>
  </div>

  <div class="stage" id="stage">
    <canvas id="cv"></canvas>
    <div class="hud">
      <div class="mult" id="mult">1.00×</div>
      <div class="status" id="status">NEXT ROUND IN 5.0s</div>
    </div>
    <div class="progress" id="progWrap"><i id="prog"></i></div>
    <div class="toast" id="toast"></div>
  </div>

  <div class="panel">
    <div class="row">
      <div class="field">
        <label>BET (KSH)</label>
        <input id="bet" type="number" value="100" min="10" step="10" inputmode="numeric">
      </div>
      <div class="field">
        <label>AUTO CASH OUT</label>
        <input id="auto" type="number" placeholder="—" min="1.01" step="0.1" inputmode="decimal">
      </div>
    </div>
    <div class="quick">
      <button data-q="half">½</button>
      <button data-q="double">2×</button>
      <button data-q="500">500</button>
      <button data-q="1000">1K</button>
      <button data-q="max">MAX</button>
    </div>
    <button class="action" id="action">PLACE BET</button>
  </div>

</div>

<!-- ===== DEPOSIT MODAL ===== -->
<div class="modal" id="depModal">
  <div class="sheet dep">
    <h3>DEPOSIT <span class="tag">M-PESA / CARD</span></h3>
    <p class="sub">Add funds to your Aviator balance. Demo mode — no real money is charged.</p>
    <div class="cur"><span>CURRENT BALANCE</span><b id="depBal">KSh 10,000.00</b></div>
    <div class="amtGrid" id="depGrid">
      <button data-v="500">KSh 500</button>
      <button data-v="1000">KSh 1,000</button>
      <button data-v="2500">KSh 2,500</button>
      <button data-v="5000">KSh 5,000</button>
      <button data-v="10000">KSh 10,000</button>
      <button data-v="50000">KSh 50,000</button>
    </div>
    <div class="field">
      <label>OR ENTER AMOUNT (KSH)</label>
      <input id="depAmt" type="number" min="10" step="10" placeholder="e.g. 1500" inputmode="numeric">
    </div>
    <div class="sheetActions">
      <button class="ghost" data-close type="button">CANCEL</button>
      <button class="go" id="depGo" type="button">DEPOSIT</button>
    </div>
    <p class="note">Minimum deposit: KSh 10 · Funds credited instantly</p>
  </div>
</div>

<!-- ===== WITHDRAW MODAL ===== -->
<div class="modal" id="wdModal">
  <div class="sheet wd">
    <h3>WITHDRAW <span class="tag">TO M-PESA</span></h3>
    <p class="sub">Cash out your winnings. Demo mode — no real money is transferred.</p>
    <div class="cur"><span>AVAILABLE</span><b id="wdBal">KSh 10,000.00</b></div>
    <div class="amtGrid" id="wdGrid">
      <button data-v="500">KSh 500</button>
      <button data-v="1000">KSh 1,000</button>
      <button data-v="2500">KSh 2,500</button>
      <button data-v="5000">KSh 5,000</button>
      <button data-v="10000">KSh 10,000</button>
      <button data-v="all">ALL</button>
    </div>
    <div class="field">
      <label>OR ENTER AMOUNT (KSH)</label>
      <input id="wdAmt" type="number" min="10" step="10" placeholder="e.g. 1500" inputmode="numeric">
    </div>
    <div class="sheetActions">
      <button class="ghost" data-close type="button">CANCEL</button>
      <button class="go" id="wdGo" type="button">WITHDRAW</button>
    </div>
    <p class="note">Minimum withdrawal: KSh 10 · You cannot withdraw while a bet is in flight</p>
  </div>
</div>

<div class="gToast" id="gToast"></div>

<script>
(function(){
'use strict';

/* =========================================================
   AUTH  —  localStorage-backed user accounts
   ========================================================= */
const USERS_KEY   = 'aviator4x_users';
const SESSION_KEY = 'aviator4x_session';

function loadUsers(){
  try { return JSON.parse(localStorage.getItem(USERS_KEY)) || {}; } catch(e){ return {}; }
}
function saveUsers(u){
  try { localStorage.setItem(USERS_KEY, JSON.stringify(u)); } catch(e){}
}
function setSession(username){
  try { localStorage.setItem(SESSION_KEY, username); } catch(e){}
}
function clearSession(){
  try { localStorage.removeItem(SESSION_KEY); } catch(e){}
}
function getSession(){
  try { return localStorage.getItem(SESSION_KEY); } catch(e){ return null; }
}
/* demo-grade hash — do NOT use in production */
function hashPw(s){
  let h = 5381;
  for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) | 0;
  h = ((h << 5) + h) ^ 0x9e3779b9;
  return (h >>> 0).toString(16);
}

let currentUser = null;

function applyUser(user, savedBalance){
  currentUser = user;
  balance = (typeof savedBalance === 'number' && isFinite(savedBalance)) ? savedBalance : 10000;

  const initial = (user.username || 'G').charAt(0).toUpperCase();
  userAv.textContent = initial;
  userName.textContent = user.guest ? 'guest' : user.username;
  logoutBtn.title = user.guest ? 'Exit guest mode' : 'Log out';

  history = [];
  renderHistory();
  toWaiting();
  uiKey = '';

  document.body.classList.add('authed');
  authScreen.classList.remove('open');
}

function persistBalance(){
  if (!currentUser || currentUser.guest) return;
  const users = loadUsers();
  if (users[currentUser.username]){
    users[currentUser.username].balance = balance;
    saveUsers(users);
  }
}

/* auth DOM refs */
const authScreen = document.getElementById('authScreen');
const tabs       = document.querySelectorAll('.tabs .tab');
const loginForm  = document.getElementById('loginForm');
const registerForm = document.getElementById('registerForm');
const loginErr   = document.getElementById('loginErr');
const regErr     = document.getElementById('regErr');
const guestBtn   = document.getElementById('guestBtn');
const userAv     = document.getElementById('userAv');
const userName   = document.getElementById('userName');
const logoutBtn  = document.getElementById('logoutBtn');

tabs.forEach(t => t.addEventListener('click', () => {
  tabs.forEach(x => x.classList.toggle('active', x === t));
  const isLogin = t.dataset.tab === 'login';
  loginForm.classList.toggle('hidden', !isLogin);
  registerForm.classList.toggle('hidden', isLogin);
  loginErr.textContent = '';
  regErr.textContent = '';
}));

loginForm.addEventListener('submit', e => {
  e.preventDefault();
  loginErr.className = 'aErr';
  loginErr.textContent = '';

  const id = document.getElementById('loginUser').value.trim().toLowerCase();
  const pw = document.getElementById('loginPass').value;
  if (!id || !pw){ loginErr.textContent = 'Enter your credentials'; return; }

  const users = loadUsers();
  let key = null;
  for (const k in users){
    if (k.toLowerCase() === id || (users[k].email || '').toLowerCase() === id){ key = k; break; }
  }
  if (!key){ loginErr.textContent = 'Account not found'; return; }
  if (users[key].pw !== hashPw(pw)){ loginErr.textContent = 'Incorrect password'; return; }

  setSession(key);
  applyUser({ username: key, email: users[key].email }, users[key].balance);
  showG('WELCOME BACK, ' + key.toUpperCase(), true);
});

registerForm.addEventListener('submit', e => {
  e.preventDefault();
  regErr.className = 'aErr';
  regErr.textContent = '';

  const u   = document.getElementById('regUser').value.trim();
  const em  = document.getElementById('regEmail').value.trim();
  const p1  = document.getElementById('regPass').value;
  const p2  = document.getElementById('regPass2').value;

  if (u.length < 3){ regErr.textContent = 'Username too short (min 3)'; return; }
  if (!/^[A-Za-z0-9_]+$/.test(u)){ regErr.textContent = 'Letters, numbers, underscore only'; return; }
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(em)){ regErr.textContent = 'Enter a valid email'; return; }
  if (p1.length < 4){ regErr.textContent = 'Password too short (min 4)'; return; }
  if (p1 !== p2){ regErr.textContent = 'Passwords do not match'; return; }

  const users = loadUsers();
  for (const k in users){
    if (k.toLowerCase() === u.toLowerCase()){ regErr.textContent = 'Username already taken'; return; }
    if ((users[k].email || '').toLowerCase() === em.toLowerCase()){ regErr.textContent = 'Email already registered'; return; }
  }

  users[u] = { email: em, pw: hashPw(p1), balance: 10000, joined: Date.now() };
  saveUsers(users);
  setSession(u);
  registerForm.reset();

  applyUser({ username: u, email: em }, 10000);
  showG('ACCOUNT CREATED · +KSh 10,000', true);
});

guestBtn.addEventListener('click', () => {
  applyUser({ username: 'guest', email: '', guest: true }, 10000);
  showG('PLAYING AS GUEST', true);
});

logoutBtn.addEventListener('click', () => {
  persistBalance();
  clearSession();
  currentUser = null;
  balance = 10000;
  history = [];
  renderHistory();
  uiKey = '';
  document.body.classList.remove('authed');
  authScreen.classList.add('open');
  tabs.forEach(x => x.classList.toggle('active', x.dataset.tab === 'login'));
  loginForm.classList.remove('hidden');
  registerForm.classList.add('hidden');
  loginForm.reset(); registerForm.reset();
  loginErr.textContent = ''; regErr.textContent = '';
});

/* =========================================================
   PRNG  —  deterministic, seedable, shareable via ?seed=&n=
   ========================================================= */
function xmur3(str){
  let h = 1779033703 ^ str.length;
  for (let i = 0; i < str.length; i++){
    h = Math.imul(h ^ str.charCodeAt(i), 3432918353);
    h = (h << 13) | (h >>> 19);
  }
  return function(){
    h = Math.imul(h ^ (h >>> 16), 2246822507);
    h = Math.imul(h ^ (h >>> 13), 3266489909);
    h ^= h >>> 16;
    return h >>> 0;
  };
}
function sfc32(state){
  return function(){
    const a = state.a, b = state.b, c = state.c, d = state.d;
    const t = (((a + b) | 0) + d) | 0;
    state.d = (d + 1) | 0;
    state.a = b ^ (b >>> 9);
    state.b = (c + (c << 3)) | 0;
    state.c = ((c << 21) | (c >>> 11)) | 0;
    state.c = (state.c + t) | 0;
    return (t >>> 0) / 4294967296;
  };
}
function randomSeed(){
  const a = new Uint32Array(2);
  (window.crypto || window.msCrypto).getRandomValues(a);
  return (a[0].toString(16) + a[1].toString(16)).padStart(16, '0');
}

const params = new URLSearchParams(location.search);
let seed  = (params.get('seed') || '').trim();
let nonce = Math.max(0, parseInt(params.get('n') || '0', 10) || 0);
if (!seed) seed = randomSeed();

let rngState, rngNext;
function resetRng(){
  const h = xmur3(String(seed));
  rngState = { a: h() | 0, b: h() | 0, c: h() | 0, d: h() | 0 };
  rngNext  = sfc32(rngState);
  for (let i = 0; i < 16; i++) rngNext();
  for (let i = 0; i < nonce; i++) rngNext();
}
function crashFromU(u){
  const raw = 0.99 / (1 - u);
  return Math.max(1.00, Math.min(Math.floor(raw * 100) / 100, 5000));
}
function peekCrash(){
  const clone = sfc32({ a: rngState.a, b: rngState.b, c: rngState.c, d: rngState.d });
  return crashFromU(clone());
}
function nextCrash(){
  const u = rngNext();
  nonce++;
  return crashFromU(u);
}
resetRng();

function syncUrl(){
  const url = location.pathname + '?seed=' + encodeURIComponent(seed) + '&n=' + nonce;
  try { window.history.replaceState(null, '', url); } catch(e){}
}

/* ========== refs ========== */
const $ = s => document.querySelector(s);
const cv = $('#cv'), ctx = cv.getContext('2d');
const stage = $('#stage'), multEl = $('#mult'), statusEl = $('#status');
const progWrap = $('#progWrap'), progEl = $('#prog'), toastEl = $('#toast');
const histEl = $('#history'), balEl = $('#balance');
const betInput = $('#bet'), autoInput = $('#auto'), btn = $('#action');
const seedTxt = $('#seedTxt'), nonceTxt = $('#nonceTxt'), linkBtn = $('#linkBtn');
const nextEl = $('#nextCrash');

const depBtn = $('#depBtn'), wdBtn = $('#wdBtn');
const depModal = $('#depModal'), wdModal = $('#wdModal');
const depAmt = $('#depAmt'), wdAmt = $('#wdAmt');
const depBal = $('#depBal'), wdBal = $('#wdBal');
const depGo = $('#depGo'), wdGo = $('#wdGo');
const depGrid = $('#depGrid'), wdGrid = $('#wdGrid');
const gToastEl = $('#gToast');

/* ========== consts / state ========== */
const K = 0.12, WAIT = 5, PAD = 26, MIN_BET = 10, MIN_CASH = 10;

let W = 0, H = 0, DPR = 1;
const G = { phase:'waiting', t:0, m:1, wait:WAIT, crash:2, pathT:0, crashT:0, crashAnim:0 };

let balance   = 10000;
let queued    = 0;
let active    = 0;
let cashedOut = false;
let history   = [];
let preview = null, shownValue = null, peekHidden = false;

function refreshPreview(){ preview = peekCrash(); }

/* ========== canvas sizing ========== */
function resize(){
  const r = stage.getBoundingClientRect();
  W = r.width; H = r.height;
  if (W < 4 || H < 4) return;
  DPR = Math.min(window.devicePixelRatio || 1, 2);
  cv.width  = Math.round(W * DPR);
  cv.height = Math.round(H * DPR);
  cv.style.width  = W + 'px';
  cv.style.height = H + 'px';
  ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
}
new ResizeObserver(resize).observe(stage);
window.addEventListener('resize', resize);
resize();

/* ========== helpers ========== */
const money = n => 'KSh ' + n.toLocaleString('en-KE',{
  minimumFractionDigits: 2, maximumFractionDigits: 2
});

let gToastTimer = null;
function showG(txt, ok){
  gToastEl.textContent = txt;
  gToastEl.className = 'gToast ' + (ok ? 'ok' : 'warn');
  void gToastEl.offsetWidth;
  gToastEl.classList.add('show');
  clearTimeout(gToastTimer);
  gToastTimer = setTimeout(() => gToastEl.classList.remove('show'), 1800);
}

/* ========== game flow ========== */
function toWaiting(){
  G.phase = 'waiting'; G.wait = WAIT; G.t = 0; G.m = 1; G.pathT = 0; G.crashAnim = 0;
}
function startRound(){
  G.phase = 'flying'; G.t = 0; G.m = 1; G.pathT = 0; G.crashAnim = 0;
  G.crash = nextCrash();
  cashedOut = false;
  active = queued; queued = 0;
}
function doCrash(){
  G.phase = 'crashed'; G.crashT = 0; G.crashAnim = 0; G.pathT = G.t;
  history.unshift(G.crash);
  if (history.length > 16) history.pop();
  renderHistory();
  refreshPreview();
  syncUrl();
  active = 0;
}
function cashOut(){
  if (active <= 0 || cashedOut) return;
  const win = active * G.m;
  balance += win;
  cashedOut = true;
  active = 0;
  persistBalance();
  showToast('+' + money(win));
  if (navigator.vibrate) navigator.vibrate(18);
}

/* ========== update ========== */
function update(dt){
  if (!currentUser) return;
  if (G.phase === 'waiting'){
    G.wait -= dt;
    if (G.wait <= 0) startRound();
  }
  else if (G.phase === 'flying'){
    G.t += dt;
    G.m = Math.exp(K * G.t);
    if (G.m >= G.crash){ G.m = G.crash; doCrash(); }
    else if (active > 0 && !cashedOut){
      const a = Number(autoInput.value);
      if (a >= 1.01 && G.m >= a) cashOut();
    }
  }
  else if (G.phase === 'crashed'){
    G.crashT += dt;
    G.crashAnim = Math.min(1, G.crashAnim + dt / 0.55);
    if (G.crashT > 2.6) toWaiting();
  }
}

/* ========== rendering ========== */
function pointAt(t){
  const m  = Math.exp(K * t);
  const xN = 1 - 1 / (1 + t * 0.42);
  const yN = 1 - 1 / (1 + (m - 1) * 0.30);
  return [ PAD + xN * (W - 2 * PAD), (H - PAD) - yN * (H - 2 * PAD - 6), m ];
}
function drawGrid(){
  ctx.save();
  ctx.strokeStyle = 'rgba(255,255,255,.035)';
  ctx.lineWidth = 1;
  for (let i = 1; i < 7; i++){
    const y = Math.round(H * i / 7) + .5;
    ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
  }
  for (let i = 1; i < 6; i++){
    const x = Math.round(W * i / 6) + .5;
    ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke();
  }
  ctx.restore();
}
function drawTrail(tt){
  const N = 70, pts = [];
  for (let i = 0; i <= N; i++) pts.push(pointAt(tt * i / N));
  ctx.beginPath();
  ctx.moveTo(pts[0][0], pts[0][1]);
  for (let i = 1; i <= N; i++) ctx.lineTo(pts[i][0], pts[i][1]);
  ctx.lineTo(pts[N][0], H); ctx.lineTo(pts[0][0], H); ctx.closePath();
  const fg = ctx.createLinearGradient(0, 0, 0, H);
  fg.addColorStop(0, 'rgba(255,90,60,.20)');
  fg.addColorStop(1, 'rgba(255,90,60,0)');
  ctx.fillStyle = fg; ctx.fill();

  ctx.beginPath();
  ctx.moveTo(pts[0][0], pts[0][1]);
  for (let i = 1; i <= N; i++) ctx.lineTo(pts[i][0], pts[i][1]);
  ctx.lineJoin = 'round'; ctx.lineCap = 'round'; ctx.lineWidth = 4;

  const x1 = pts[0][0], y1 = pts[0][1];
  const x2 = Math.max(pts[N][0], x1 + 1), y2 = pts[N][1];
  const lg = ctx.createLinearGradient(x1, y1, x2, y2);
  lg.addColorStop(0, '#ff3b47'); lg.addColorStop(1, '#ffc043');

  ctx.strokeStyle = lg;
  ctx.shadowColor = 'rgba(255,90,60,.8)';
  ctx.shadowBlur  = 20;
  ctx.stroke();
  ctx.shadowBlur = 0;
}
function drawPlane(tt){
  let [x, y] = pointAt(tt);
  const [px, py] = pointAt(Math.max(0, tt - 0.05));
  let ang = Math.atan2(y - py, x - px);
  let alpha = 1;
  if (G.phase === 'crashed'){
    const e = G.crashAnim * G.crashAnim;
    x += e * W * 0.7; y -= e * H * 0.5;
    alpha = 1 - e; ang -= e * 1.1;
  }
  ctx.save();
  ctx.globalAlpha = alpha;
  const rg = ctx.createRadialGradient(x, y, 0, x, y, 46);
  rg.addColorStop(0, 'rgba(255,140,80,.42)');
  rg.addColorStop(1, 'rgba(255,140,80,0)');
  ctx.fillStyle = rg;
  ctx.beginPath(); ctx.arc(x, y, 46, 0, Math.PI * 2); ctx.fill();

  ctx.translate(x, y); ctx.rotate(ang);
  ctx.beginPath();
  ctx.moveTo(20, 0); ctx.lineTo(-3, -8); ctx.lineTo(0, -2.5);
  ctx.lineTo(-13, -2.5); ctx.lineTo(-13, 2.5); ctx.lineTo(0, 2.5);
  ctx.lineTo(-3, 8); ctx.closePath();
  ctx.fillStyle = '#fff';
  ctx.shadowColor = 'rgba(255,190,140,.95)';
  ctx.shadowBlur = 16;
  ctx.fill();
  ctx.shadowBlur = 0;
  ctx.strokeStyle = 'rgba(255,255,255,.85)'; ctx.lineWidth = 1; ctx.stroke();
  ctx.restore();
}
function render(){
  if (W < 4) { resize(); return; }
  ctx.clearRect(0, 0, W, H);
  drawGrid();
  const tt = (G.phase === 'flying') ? G.t : G.pathT;
  if (tt > 0.0005){ drawTrail(tt); drawPlane(tt); }
  else { drawPlane(0.0008); }
}

/* ========== UI ========== */
function renderHistory(){
  histEl.innerHTML = history.map(h => {
    const cls = h < 2 ? 'lo' : (h < 10 ? 'mid' : 'hi');
    return '<span class="chip ' + cls + '">' + h.toFixed(2) + '×</span>';
  }).join('');
}
function renderPreview(){
  const live = (G.phase === 'flying' || G.phase === 'crashed');
  nextEl.className = 'next' + (live ? ' live' : '') + (peekHidden ? ' masked' : '');
  if (preview === null) return;
  if (preview !== shownValue){
    nextEl.innerHTML = '<b>' + preview.toFixed(2) + '×</b>';
    shownValue = preview;
    nextEl.classList.remove('pop');
    void nextEl.offsetWidth;
    nextEl.classList.add('pop');
  }
}

let uiKey = '';
function updateUI(){
  if (!currentUser) return;
  const key = [G.phase, G.m.toFixed(2), G.wait.toFixed(1), queued, active, cashedOut, balance.toFixed(2), nonce].join('|');
  if (key === uiKey) return;
  uiKey = key;

  balEl.textContent = money(balance);

  let mTxt, mCls = '', stTxt;
  if (G.phase === 'flying'){
    mTxt = G.m.toFixed(2) + '×';
    mCls = G.m >= 10 ? 'high' : (G.m >= 2 ? 'mid' : '');
    stTxt = cashedOut ? 'CASHED OUT' : 'CURRENT PAYOUT';
  } else if (G.phase === 'crashed'){
    mTxt = G.crash.toFixed(2) + '×'; mCls = 'dead'; stTxt = 'FLEW AWAY';
  } else {
    mTxt = '1.00×';
    stTxt = 'NEXT ROUND IN ' + Math.max(0, G.wait).toFixed(1) + 's';
  }
  multEl.textContent = mTxt;
  multEl.className = 'mult ' + mCls;
  statusEl.textContent = stTxt;

  progWrap.classList.toggle('on', G.phase === 'waiting');
  if (G.phase === 'waiting') progEl.style.width = (100 * (1 - Math.max(0, G.wait) / WAIT)) + '%';

  if (G.phase === 'flying' && active > 0 && !cashedOut){
    btn.textContent = 'CASH OUT ' + money(active * G.m);
    btn.className = 'action cash';
  } else if (queued > 0){
    btn.textContent = 'CANCEL BET ' + money(queued);
    btn.className = 'action cancel';
  } else {
    btn.textContent = 'PLACE BET';
    btn.className = 'action';
  }

  seedTxt.textContent = 'seed ' + seed.slice(0, 12);
  nonceTxt.textContent = '#' + nonce;
  renderPreview();
}

let toastTimer = null;
function showToast(txt, warn){
  toastEl.textContent = txt;
  toastEl.className = 'toast' + (warn ? ' warn' : '');
  void toastEl.offsetWidth;
  toastEl.classList.add('show');
  clearTimeout(toastTimer);
  toastTimer = setTimeout(() => toastEl.classList.remove('show'), 1150);
}

/* ========== main loop ========== */
let last = performance.now();
function loop(now){
  const dt = Math.min((now - last) / 1000, 0.05);
  last = now;
  update(dt);
  render();
  updateUI();
  requestAnimationFrame(loop);
}

/* ========== bet controls ========== */
btn.addEventListener('click', () => {
  if (G.phase === 'flying' && active > 0 && !cashedOut){ cashOut(); return; }
  if (queued > 0){ balance += queued; queued = 0; persistBalance(); return; }

  const amt = Math.floor(Number(betInput.value));
  if (!isFinite(amt) || amt < MIN_BET){ showToast('MINIMUM BET KSh ' + MIN_BET, true); return; }
  if (amt > balance){ showToast('INSUFFICIENT BALANCE', true); return; }

  balance -= amt;
  queued = amt;
  persistBalance();
});

document.querySelectorAll('.quick button').forEach(b => {
  b.addEventListener('click', () => {
    let v = Number(betInput.value) || 0;
    switch (b.dataset.q){
      case 'half':   v = Math.max(MIN_BET, Math.floor(v / 2)); break;
      case 'double': v = Math.max(MIN_BET, Math.floor(v * 2)); break;
      case '500':    v = 500; break;
      case '1000':   v = 1000; break;
      case 'max':    v = Math.max(MIN_BET, Math.floor(balance)); break;
    }
    betInput.value = v;
  });
});

/* ========== DEPOSIT / WITHDRAW ========== */
function openModal(m){
  if (m === depModal){ depBal.textContent = money(balance); depAmt.value = ''; clearSel(depGrid); }
  else               { wdBal.textContent  = money(balance); wdAmt.value  = ''; clearSel(wdGrid);  }
  m.classList.add('open');
}
function closeModal(m){ m.classList.remove('open'); }
function clearSel(grid){ grid.querySelectorAll('button').forEach(b => b.classList.remove('sel')); }

depBtn.addEventListener('click', () => openModal(depModal));
wdBtn .addEventListener('click', () => {
  if (G.phase === 'flying' && active > 0){ showG('CASH OUT YOUR BET FIRST', false); return; }
  openModal(wdModal);
});

[depModal, wdModal].forEach(m => {
  m.addEventListener('click', e => { if (e.target === m) closeModal(m); });
  m.querySelectorAll('[data-close]').forEach(b => b.addEventListener('click', () => closeModal(m)));
});

depGrid.querySelectorAll('button').forEach(b => {
  b.addEventListener('click', () => {
    clearSel(depGrid); b.classList.add('sel'); depAmt.value = b.dataset.v;
  });
});
wdGrid.querySelectorAll('button').forEach(b => {
  b.addEventListener('click', () => {
    clearSel(wdGrid); b.classList.add('sel');
    wdAmt.value = (b.dataset.v === 'all') ? Math.floor(balance) : b.dataset.v;
  });
});
depAmt.addEventListener('input', () => clearSel(depGrid));
wdAmt .addEventListener('input', () => clearSel(wdGrid));

depGo.addEventListener('click', () => {
  const amt = Math.floor(Number(depAmt.value));
  if (!isFinite(amt) || amt < MIN_CASH){ showG('MINIMUM DEPOSIT KSh ' + MIN_CASH, false); return; }
  if (amt > 500000){ showG('MAX DEPOSIT KSh 500,000', false); return; }

  balance += amt;
  persistBalance();
  uiKey = '';
  depBal.textContent = money(balance);
  closeModal(depModal);
  showG('+' + money(amt) + ' DEPOSITED', true);
  if (navigator.vibrate) navigator.vibrate([12, 40, 12]);
});

wdGo.addEventListener('click', () => {
  const amt = Math.floor(Number(wdAmt.value));
  if (!isFinite(amt) || amt < MIN_CASH){ showG('MINIMUM WITHDRAWAL KSh ' + MIN_CASH, false); return; }
  if (amt > balance){ showG('INSUFFICIENT BALANCE', false); return; }

  balance -= amt;
  persistBalance();
  uiKey = '';
  wdBal.textContent = money(balance);
  closeModal(wdModal);
  showG('−' + money(amt) + ' WITHDRAWN', true);
  if (navigator.vibrate) navigator.vibrate([12, 40, 12]);
});

window.addEventListener('keydown', e => {
  if (e.key === 'Escape'){ closeModal(depModal); closeModal(wdModal); }
});

/* ========== PRNG bar controls ========== */
nextEl.addEventListener('click', () => {
  peekHidden = !peekHidden;
  renderPreview();
  showToast(peekHidden ? 'NEXT CRASH HIDDEN' : 'NEXT CRASH SHOWN');
});

linkBtn.addEventListener('click', () => {
  syncUrl();
  const url = location.href;
  const done = () => showToast('LINK COPIED');
  if (navigator.clipboard && navigator.clipboard.writeText){
    navigator.clipboard.writeText(url).then(done, () => fallbackCopy(url, done));
  } else {
    fallbackCopy(url, done);
  }
});
function fallbackCopy(txt, cb){
  const ta = document.createElement('textarea');
  ta.value = txt; ta.style.position = 'fixed'; ta.style.opacity = '0';
  document.body.appendChild(ta); ta.select();
  try { document.execCommand('copy'); cb(); } catch(e){ showToast('COPY FAILED', true); }
  document.body.removeChild(ta);
}

seedTxt.addEventListener('click', () => {
  const s = prompt('Enter PRNG seed (letters/numbers).\nSame seed + same nonce = same crash sequence.', seed);
  if (s === null) return;
  const clean = s.trim();
  if (!clean) return;
  seed = clean; nonce = 0;
  resetRng(); refreshPreview(); shownValue = null;
  history = []; renderHistory(); toWaiting(); syncUrl();
  showToast('SEED SET');
});

window.addEventListener('keydown', e => {
  if (e.code === 'Space' && document.activeElement.tagName !== 'INPUT' && currentUser){
    e.preventDefault(); btn.click();
  }
});

/* ========== boot ========== */
resetRng();
refreshPreview();
toWaiting();
renderHistory();
syncUrl();
requestAnimationFrame(loop);

/* auto-login if a session exists */
(function tryRestoreSession(){
  const sess = getSession();
  if (!sess) return;
  const users = loadUsers();
  if (users[sess]){
    applyUser({ username: sess, email: users[sess].email }, users[sess].balance);
  } else {
    clearSession();
  }
})();

})();
</script>
</body>
</html>

Game Source: AVIATOR 4X

Creator: SolarPenguin46

Libraries: none

Complexity: complex (1221 lines, 47.4 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: aviator-4x-solarpenguin46" to link back to the original. Then publish at arcadelab.ai/publish.