Round length is now bounded: the multiplier follows a hyperbolic curve diverging at 60s, replacing an exponential one where a 275x crash point produced a two-and-a-half minute round. Fixes seed reveal, which silently failed every round because pgx cannot encode a fixed-size byte array as bytea. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
142 lines
4.8 KiB
HTML
142 lines
4.8 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">
|
||
<title>Quantum Arcade</title>
|
||
<link rel="stylesheet" href="/style.css">
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Ornate background filigree, drawn once as an SVG pattern and tiled. -->
|
||
<svg class="filigree" aria-hidden="true">
|
||
<defs>
|
||
<pattern id="orn" width="120" height="120" patternUnits="userSpaceOnUse">
|
||
<g fill="none" stroke="currentColor" stroke-width="0.6">
|
||
<circle cx="60" cy="60" r="46"/>
|
||
<circle cx="60" cy="60" r="30"/>
|
||
<circle cx="60" cy="60" r="14"/>
|
||
<path d="M60 0 L60 120 M0 60 L120 60"/>
|
||
<path d="M17 17 L103 103 M103 17 L17 103"/>
|
||
<path d="M60 14 q26 22 0 46 q-26-24 0-46"/>
|
||
<path d="M14 60 q22 26 46 0 q-24-26-46 0"/>
|
||
</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: a nickname and nothing else. -->
|
||
<section class="panel center" id="signin">
|
||
<h1>Enter the arcade</h1>
|
||
<p class="muted">
|
||
No account, no email, no password. Your device generates a key that
|
||
<em>is</em> your identity. Keep the device, keep the balance.
|
||
</p>
|
||
<input id="nickname" maxlength="20" placeholder="pick a name" autocomplete="off">
|
||
<button class="primary" id="enter">Enter</button>
|
||
<p class="fineprint" id="keynote"></p>
|
||
</section>
|
||
|
||
<main id="app" hidden>
|
||
|
||
<nav class="tabs">
|
||
<button class="tab active" data-view="crash">Crash</button>
|
||
<button class="tab" data-view="scratch">Scratchers</button>
|
||
<button class="tab" data-view="wallet">Wallet</button>
|
||
<button class="tab" data-view="verify">Verify</button>
|
||
</nav>
|
||
|
||
<!-- ============ CRASH ============ -->
|
||
<section class="view" id="view-crash">
|
||
<div class="gamepick" id="gamepick"></div>
|
||
|
||
<div class="stage">
|
||
<canvas id="canvas"></canvas>
|
||
<div class="readout">
|
||
<div class="multiplier" id="multiplier">1.00×</div>
|
||
<div class="state" id="state">connecting…</div>
|
||
</div>
|
||
</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>
|
||
<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 is
|
||
derived 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>
|
||
|
||
<!-- ============ 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" 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" min="1" placeholder="round number">
|
||
<button class="primary" id="do-verify">Verify</button>
|
||
<div id="verify-out" class="verify-out"></div>
|
||
</div>
|
||
</section>
|
||
|
||
</main>
|
||
|
||
<script src="/app.js"></script>
|
||
</body>
|
||
</html>
|