AVIATOR 4X
by SolarPenguin461221 lines47.4 KB
<!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.