Three WebGL scenes on one renderer and one context, since a phone should not allocate a context per game: a rocket straining against gravity, a decaying orbit, and a tower that sways harder the higher it stacks. The loop stops when the tab is hidden. Navigation moves to the bottom, where a thumb already is. New Stats view with balance history, cash-out rate, and a distribution chart that plots observed crash points against what the published maths predicts — the honest version of a hot-numbers board. Charts are hand-built SVG, ~300 lines, rather than a library that would cost more to load than the 3D engine. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
260 lines
9.0 KiB
HTML
260 lines
9.0 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no">
|
||
<meta name="theme-color" content="#000000">
|
||
<meta name="mobile-web-app-capable" content="yes">
|
||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||
<title>QUANTUM ARCADE</title>
|
||
<link rel="stylesheet" href="/style.css">
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Circuit-trace ornament, tiled once behind everything. -->
|
||
<svg class="filigree" aria-hidden="true">
|
||
<defs>
|
||
<pattern id="orn" width="60" height="52" patternUnits="userSpaceOnUse">
|
||
<g fill="none" stroke="currentColor" stroke-width="0.7">
|
||
<path d="M15 0 L45 0 L60 26 L45 52 L15 52 L0 26 Z"/>
|
||
<path d="M30 26 L60 26 M30 26 L15 0 M30 26 L15 52"/>
|
||
<circle cx="30" cy="26" r="1.6"/>
|
||
<circle cx="0" cy="26" r="1.2"/>
|
||
<circle cx="60" cy="26" r="1.2"/>
|
||
</g>
|
||
</pattern>
|
||
</defs>
|
||
<rect width="100%" height="100%" fill="url(#orn)"/>
|
||
</svg>
|
||
|
||
<header class="topbar">
|
||
<div class="brand">QUANTUM<span>ARCADE</span></div>
|
||
<div class="balance" id="balance-wrap" hidden>
|
||
<span class="label">balance</span>
|
||
<span class="value" id="balance">—</span>
|
||
</div>
|
||
<button class="sound" id="sound-toggle" title="Ambient sound">♪</button>
|
||
</header>
|
||
|
||
<!-- Sign-in -->
|
||
<section class="panel center" id="signin">
|
||
<h1>ACCESS TERMINAL</h1>
|
||
<p class="muted small">
|
||
No account. No email. No password. This device generates a keypair
|
||
that <em>is</em> your identity.
|
||
</p>
|
||
<input id="nickname" maxlength="20" placeholder="handle" autocomplete="off">
|
||
<button class="primary" id="enter">Connect</button>
|
||
<p class="fineprint" id="keynote"></p>
|
||
<div class="cryptobadge">
|
||
<span>Provably fair</span>
|
||
<span>SHA-256 commit-reveal</span>
|
||
<span>AGPL-3.0</span>
|
||
</div>
|
||
</section>
|
||
|
||
<main id="app" hidden>
|
||
|
||
<!-- ============ CRASH ============ -->
|
||
<section class="view" id="view-crash">
|
||
<div class="gamepick" id="gamepick"></div>
|
||
|
||
<div class="stage">
|
||
<canvas id="scene"></canvas>
|
||
<div class="readout">
|
||
<div class="multiplier" id="multiplier">1.00×</div>
|
||
<div class="state" id="state">connecting…</div>
|
||
</div>
|
||
<div class="stagetop">
|
||
<span class="pot" id="potline"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Live crash history strip, directly under the stage. -->
|
||
<div class="strip" id="strip"></div>
|
||
|
||
<div class="controls">
|
||
<div class="stakerow">
|
||
<button class="chip" data-stake="1000">1</button>
|
||
<button class="chip" data-stake="5000">5</button>
|
||
<button class="chip" data-stake="25000">25</button>
|
||
<button class="chip" data-stake="100000">100</button>
|
||
<span class="unit">sats</span>
|
||
</div>
|
||
|
||
<div class="autorow" id="autorow">
|
||
<label for="auto-target">auto out</label>
|
||
<input id="auto-target" type="number" min="1.01" step="0.1"
|
||
inputmode="decimal" placeholder="off">
|
||
<span class="x">×</span>
|
||
<div class="presets">
|
||
<button data-auto="1.5">1.5</button>
|
||
<button data-auto="2">2</button>
|
||
<button data-auto="5">5</button>
|
||
</div>
|
||
</div>
|
||
|
||
<button class="primary big" id="action">Place bet</button>
|
||
<div class="hint" id="hint"></div>
|
||
</div>
|
||
|
||
<div class="players" id="players"></div>
|
||
|
||
<details class="proof">
|
||
<summary>Fairness for this round</summary>
|
||
<div class="kv"><span>commitment</span><code id="commitment">—</code></div>
|
||
<div class="kv"><span>revealed seed</span><code id="revealed">sealed until the round ends</code></div>
|
||
<p class="muted small">
|
||
The commitment is published before betting opens. The crash point comes
|
||
from that seed combined with every player's key — so it cannot be
|
||
chosen after seeing who joined.
|
||
</p>
|
||
</details>
|
||
</section>
|
||
|
||
<!-- ============ SCRATCH ============ -->
|
||
<section class="view" id="view-scratch" hidden>
|
||
<div id="tickets"></div>
|
||
</section>
|
||
|
||
<!-- ============ PORTFOLIO ============ -->
|
||
<section class="view" id="view-portfolio" hidden>
|
||
|
||
<div class="tiles">
|
||
<div class="tile">
|
||
<span class="tile-label">balance</span>
|
||
<span class="tile-value" id="t-balance">—</span>
|
||
<div class="tile-spark" id="spark-balance"></div>
|
||
</div>
|
||
<div class="tile">
|
||
<span class="tile-label">session</span>
|
||
<span class="tile-value" id="t-session">—</span>
|
||
<span class="tile-sub" id="t-session-sub">since you connected</span>
|
||
</div>
|
||
<div class="tile">
|
||
<span class="tile-label">wagered</span>
|
||
<span class="tile-value" id="t-wagered">—</span>
|
||
<span class="tile-sub" id="t-plays">0 plays</span>
|
||
</div>
|
||
<div class="tile">
|
||
<span class="tile-label">best hit</span>
|
||
<span class="tile-value" id="t-best">—</span>
|
||
<span class="tile-sub">largest single win</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Balance over time</h2>
|
||
<div class="chart" id="chart-balance"></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Cash-out rate</h2>
|
||
<div class="chart chart-donut" id="chart-donut"></div>
|
||
<p class="muted small" id="donut-note"></p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Where rounds ended</h2>
|
||
<div class="chart" id="chart-dist"></div>
|
||
<p class="muted small">
|
||
Bars are what actually happened. The dashed line is what the published
|
||
maths predicts. They should converge — that is the point.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<h2>Recent rounds</h2>
|
||
<div class="chart" id="chart-history"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ WALLET ============ -->
|
||
<section class="view" id="view-wallet" hidden>
|
||
<div class="card">
|
||
<h2>Your key</h2>
|
||
<p class="muted small">Share this so friends can send you sats.</p>
|
||
<code class="pubkey" id="pubkey">—</code>
|
||
<button id="copykey">Copy</button>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Send sats</h2>
|
||
<input id="to-key" placeholder="recipient key" autocomplete="off">
|
||
<input id="send-amt" type="number" min="1" inputmode="numeric"
|
||
placeholder="amount in sats">
|
||
<button class="primary" id="send">Send</button>
|
||
<div class="hint" id="send-hint"></div>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Every change to your balance</h2>
|
||
<div id="history" class="history"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ VERIFY ============ -->
|
||
<section class="view" id="view-verify" hidden>
|
||
<div class="card">
|
||
<h2>Check any round</h2>
|
||
<p class="muted small">
|
||
Enter a round number. Your phone recomputes the outcome from the
|
||
published seeds — it does not take the server's word for anything.
|
||
</p>
|
||
<input id="verify-id" type="number" inputmode="numeric" min="1"
|
||
placeholder="round number">
|
||
<button class="primary" id="do-verify">Verify</button>
|
||
<div id="verify-out" class="verify-out"></div>
|
||
</div>
|
||
<div class="card">
|
||
<h2>Underlying tech</h2>
|
||
<dl class="specs">
|
||
<dt>Identity</dt>
|
||
<dd>Ed25519 signed challenge, single-use and replay-proof.
|
||
Hybrid Ed25519 + ML-DSA-65 (FIPS 204) is implemented and tested
|
||
server-side; browser signing lands with the WASM module.</dd>
|
||
<dt>Transport</dt>
|
||
<dd>TLS 1.3 with X25519MLKEM768 hybrid key exchange when served over
|
||
HTTPS — post-quantum against harvest-now-decrypt-later</dd>
|
||
<dt>Fairness</dt>
|
||
<dd>SHA-256 commitment, HMAC-SHA256 outcome derivation. Hash-based,
|
||
so Grover only halves the margin: quantum-resistant as it stands.</dd>
|
||
<dt>Settlement</dt>
|
||
<dd>Bitcoin and Lightning sign with secp256k1, which is
|
||
<em>not</em> post-quantum. No application choice changes that.</dd>
|
||
<dt>Simulation</dt>
|
||
<dd>Q32.32 fixed-point, zero floating point, bit-identical replay</dd>
|
||
<dt>Ledger</dt>
|
||
<dd>Append-only double-entry, DB-enforced, audited every request</dd>
|
||
<dt>House edge</dt>
|
||
<dd>1.00% — verified by test across 2,000,000 simulated plays</dd>
|
||
<dt>Licence</dt>
|
||
<dd>AGPL-3.0 — every line auditable, forks must publish</dd>
|
||
</dl>
|
||
</div>
|
||
</section>
|
||
|
||
</main>
|
||
|
||
<!-- Bottom navigation: thumb-reachable, which is where navigation belongs on a
|
||
phone held one-handed at a party. -->
|
||
<nav class="tabs" id="tabs" hidden>
|
||
<button class="tab active" data-view="crash">
|
||
<span class="ico">▲</span><span>Crash</span>
|
||
</button>
|
||
<button class="tab" data-view="scratch">
|
||
<span class="ico">◈</span><span>Scratch</span>
|
||
</button>
|
||
<button class="tab" data-view="portfolio">
|
||
<span class="ico">▤</span><span>Stats</span>
|
||
</button>
|
||
<button class="tab" data-view="wallet">
|
||
<span class="ico">◉</span><span>Wallet</span>
|
||
</button>
|
||
<button class="tab" data-view="verify">
|
||
<span class="ico">✓</span><span>Verify</span>
|
||
</button>
|
||
</nav>
|
||
|
||
<script type="module" src="/app.js"></script>
|
||
</body>
|
||
</html>
|