feat: playable arcade — rooms, identity, client, deployment
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>
This commit is contained in:
141
cmd/arcade/static/index.html
Normal file
141
cmd/arcade/static/index.html
Normal file
@@ -0,0 +1,141 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user