Files
casino/cmd/arcade/static/style.css
drjones 48a9120fe4 feat: auto cash-out targets, 1% house edge, terminal aesthetic
Auto cash-out closes a position at exactly the chosen target rather than
the next tick's multiplier, and fires whenever the target is at or below
the crash point. This is the feature that makes the game playable over a
network, where manual timing is at the mercy of latency.

House edge drops from 2% to 1% across crash and scratch. Scratch prize
tables retuned so the published 99% RTP is exact.

Adds docs/API.md: the client uses no private endpoints, so anyone can
write a bot against the same API.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 16:24:31 +00:00

355 lines
11 KiB
CSS

/* Quantum Arcade — terminal aesthetic.
*
* Phosphor green on black, monospace everywhere, CRT scanlines and a faint
* flicker. Colour is rationed: amber for money at risk, magenta for the
* multiplier, red for the crash. Everything else is green on black, because
* a terminal that shouts about everything says nothing. */
:root {
--black: #000000;
--panel: #030806;
--green: #00ff9c;
--green-dim: #0a7a52;
--green-ghost: #063a29;
--amber: #ffb000;
--magenta: #ff2e88;
--red: #ff3355;
--grid: #06181200;
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body {
margin: 0;
min-height: 100%;
background: var(--black);
color: var(--green);
font: 14px/1.5 ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
"Liberation Mono", monospace;
overscroll-behavior: none;
text-shadow: 0 0 6px #00ff9c40;
}
/* Faint hex-grid ornament behind everything. */
.filigree {
position: fixed; inset: 0;
width: 100%; height: 100%;
color: #0d2b21;
opacity: 0.5;
pointer-events: none;
z-index: 0;
}
/* CRT scanlines and a slow flicker over the whole page. */
body::after {
content: "";
position: fixed; inset: 0; z-index: 50;
pointer-events: none;
background: repeating-linear-gradient(
to bottom,
#00ff9c08 0px, #00ff9c08 1px,
transparent 1px, transparent 3px);
animation: flicker 5s infinite steps(60);
}
@keyframes flicker {
0%, 96%, 100% { opacity: 0.5; }
97% { opacity: 0.75; }
98% { opacity: 0.35; }
}
body > * { position: relative; z-index: 1; }
/* ---------- chrome ---------- */
.topbar {
display: flex; align-items: center; gap: 12px;
padding: 10px 14px;
border-bottom: 1px solid var(--green-ghost);
background: #00120c;
}
.brand {
font-weight: 700; letter-spacing: 0.18em; font-size: 12px;
}
.brand::before { content: "> "; color: var(--green-dim); }
.brand span { color: var(--amber); margin-left: 5px; }
.balance { margin-left: auto; text-align: right; line-height: 1.15; }
.balance .label {
display: block; font-size: 9px; letter-spacing: 0.18em;
text-transform: uppercase; color: var(--green-dim);
}
.balance .value {
font-variant-numeric: tabular-nums;
font-size: 17px; font-weight: 700; color: var(--amber);
text-shadow: 0 0 14px #ffb00066;
}
.sound {
background: none; border: 1px solid var(--green-ghost); color: var(--green-dim);
width: 32px; height: 32px; border-radius: 3px; font-size: 14px;
}
.sound.on { color: var(--green); border-color: var(--green); }
/* ---------- panels ---------- */
.panel { max-width: 460px; margin: 0 auto; padding: 34px 20px; }
.center { text-align: center; }
h1 {
font-size: 19px; margin: 0 0 10px; letter-spacing: 0.1em;
text-transform: uppercase;
}
h1::after {
content: "_";
animation: blink 1.1s steps(2) infinite;
color: var(--green);
}
@keyframes blink { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } }
h2 {
font-size: 11px; letter-spacing: 0.16em; text-transform: uppercase;
color: var(--green-dim); margin: 0 0 10px;
}
h2::before { content: "// "; }
.muted { color: var(--green-dim); }
.small { font-size: 12px; }
.fineprint {
font-size: 10.5px; color: var(--green-dim); margin-top: 16px;
word-break: break-all;
}
input {
width: 100%; padding: 11px 12px; margin: 7px 0;
background: #00140e; border: 1px solid var(--green-ghost); border-radius: 3px;
color: var(--green); font-size: 16px; /* 16px stops iOS zooming on focus */
font-family: inherit;
}
input:focus { outline: none; border-color: var(--green); box-shadow: 0 0 10px #00ff9c33; }
input::placeholder { color: var(--green-ghost); }
button {
font: inherit; cursor: pointer; border-radius: 3px;
border: 1px solid var(--green-ghost); background: #00140e; color: var(--green);
padding: 10px 14px; letter-spacing: 0.06em;
}
button:active { transform: translateY(1px); }
.primary {
background: #00291c; color: var(--green);
border: 1px solid var(--green); font-weight: 700;
letter-spacing: 0.12em; text-transform: uppercase;
box-shadow: 0 0 18px #00ff9c33, inset 0 0 18px #00ff9c11;
width: 100%; padding: 14px;
}
.primary.big { font-size: 15px; padding: 17px; }
/* Money at risk turns amber and pulses — the one element that demands a
decision looks different from everything else. */
.primary.cashout {
background: #2a1c00; color: var(--amber); border-color: var(--amber);
box-shadow: 0 0 26px #ffb00055, inset 0 0 18px #ffb00011;
animation: urge 800ms ease-in-out infinite;
}
.primary:disabled { opacity: 0.35; box-shadow: none; animation: none; }
@keyframes urge {
0%, 100% { box-shadow: 0 0 18px #ffb00044, inset 0 0 14px #ffb00011; }
50% { box-shadow: 0 0 34px #ffb000aa, inset 0 0 22px #ffb00022; }
}
/* ---------- tabs ---------- */
.tabs {
display: flex; gap: 4px; padding: 10px 10px 0;
max-width: 620px; margin: 0 auto;
}
.tab {
flex: 1; padding: 9px 3px; font-size: 11px; letter-spacing: 0.1em;
text-transform: uppercase; background: none; border: none;
color: var(--green-dim); border-bottom: 1px solid var(--green-ghost);
border-radius: 0;
}
.tab.active {
color: var(--green); border-bottom-color: var(--green);
text-shadow: 0 0 10px #00ff9c88;
}
.view { max-width: 620px; margin: 0 auto; padding: 12px 12px 60px; }
/* ---------- crash stage ---------- */
.gamepick { display: flex; gap: 4px; margin-bottom: 10px; }
.gamepick button {
flex: 1; font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase;
padding: 8px 3px; color: var(--green-dim);
}
.gamepick button.on {
color: var(--green); border-color: var(--green); background: #00291c;
}
.stage {
position: relative; border-radius: 3px; overflow: hidden;
border: 1px solid var(--green-ghost);
background: #000603;
aspect-ratio: 4 / 3;
}
#canvas { width: 100%; height: 100%; display: block; }
.readout {
position: absolute; inset: 0; display: flex; flex-direction: column;
align-items: center; justify-content: center; pointer-events: none;
}
.multiplier {
font-size: clamp(42px, 16vw, 82px); font-weight: 700;
font-variant-numeric: tabular-nums; letter-spacing: -0.01em;
color: var(--magenta); text-shadow: 0 0 30px #ff2e8877;
}
.multiplier.crashed {
color: var(--red); text-shadow: 0 0 34px #ff335599;
animation: glitch 260ms steps(2) 3;
}
.multiplier.won {
color: var(--amber); text-shadow: 0 0 40px #ffb000aa;
}
@keyframes glitch {
0% { transform: translate(0, 0); }
25% { transform: translate(-3px, 1px); }
50% { transform: translate(3px, -1px); }
75% { transform: translate(-2px, -2px); }
100% { transform: translate(0, 0); }
}
.state {
font-size: 10px; letter-spacing: 0.22em; text-transform: uppercase;
color: var(--green-dim); margin-top: 5px;
}
.controls { margin-top: 12px; }
.stakerow { display: flex; align-items: center; gap: 4px; margin-bottom: 8px; }
.chip { flex: 1; font-variant-numeric: tabular-nums; padding: 11px 3px; font-size: 13px; }
.chip.on { border-color: var(--green); color: var(--green); background: #00291c; }
.unit { font-size: 10px; color: var(--green-dim); letter-spacing: 0.1em; }
/* Auto cash-out target row. */
.autorow {
display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
border: 1px solid var(--green-ghost); border-radius: 3px; padding: 8px 10px;
}
.autorow label {
font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase;
color: var(--green-dim); white-space: nowrap;
}
.autorow input {
margin: 0; padding: 6px 8px; text-align: right; width: 90px;
font-variant-numeric: tabular-nums;
}
.autorow .x { color: var(--green-dim); font-size: 13px; }
.autorow.armed { border-color: var(--amber); }
.autorow.armed label, .autorow.armed .x { color: var(--amber); }
.autorow.armed input { color: var(--amber); border-color: var(--amber); }
.hint {
min-height: 17px; margin-top: 7px; font-size: 11.5px;
color: var(--green-dim); text-align: center;
}
.hint.bad { color: var(--red); }
.hint.good { color: var(--amber); }
.players { margin-top: 12px; display: flex; flex-direction: column; gap: 3px; }
.player {
display: flex; align-items: center; gap: 8px; padding: 7px 10px;
background: #00120c; border: 1px solid var(--green-ghost); border-radius: 3px;
font-size: 12.5px;
}
.player .who { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.player .amt { font-variant-numeric: tabular-nums; color: var(--green-dim); }
.player.out { border-color: #4a3300; }
.player.out .amt { color: var(--amber); }
.proof { margin-top: 16px; }
.proof summary {
cursor: pointer; font-size: 10px; letter-spacing: 0.16em;
text-transform: uppercase; color: var(--green-dim); padding: 7px 0;
}
.kv { display: flex; gap: 8px; font-size: 10.5px; padding: 3px 0; }
.kv span { color: var(--green-dim); min-width: 92px; }
.kv code { word-break: break-all; color: var(--green); opacity: 0.75; }
/* ---------- cards / scratch ---------- */
.card {
background: #00120c; border: 1px solid var(--green-ghost);
border-radius: 3px; padding: 14px; margin-bottom: 12px;
}
.card h3 {
margin: 0 0 4px; font-size: 15px; letter-spacing: 0.1em;
text-transform: uppercase; color: var(--green);
}
.grid { display: grid; gap: 6px; margin: 12px 0; }
.grid.c9 { grid-template-columns: repeat(3, 1fr); }
.grid.c6 { grid-template-columns: repeat(3, 1fr); }
.cell {
aspect-ratio: 1; display: grid; place-items: center;
font-size: 24px; border-radius: 3px;
background: #001f16; border: 1px solid var(--green-ghost);
color: var(--green-dim);
transition: background 200ms ease, color 200ms ease;
}
.cell.revealed { background: #000603; color: var(--green); border-color: var(--green-ghost); }
.cell.hit {
border-color: var(--amber); color: var(--amber);
box-shadow: 0 0 16px #ffb00055;
}
.odds { width: 100%; border-collapse: collapse; font-size: 11.5px; margin-top: 8px; }
.odds th, .odds td {
text-align: left; padding: 5px 3px; border-bottom: 1px solid var(--green-ghost);
}
.odds th {
color: var(--green-dim); font-weight: 400; font-size: 9.5px;
letter-spacing: 0.14em; text-transform: uppercase;
}
.odds td:last-child, .odds th:last-child {
text-align: right; font-variant-numeric: tabular-nums;
}
.rtp { color: var(--amber); font-weight: 700; }
.result { text-align: center; padding: 8px 0; font-size: 14px; }
.result.win {
color: var(--amber); font-weight: 700; letter-spacing: 0.08em;
text-transform: uppercase;
}
/* ---------- wallet ---------- */
.pubkey {
display: block; word-break: break-all; font-size: 10.5px;
background: #000603; padding: 9px; border-radius: 3px; margin: 7px 0;
color: var(--green-dim); border: 1px solid var(--green-ghost);
}
.history { display: flex; flex-direction: column; gap: 2px; }
.entry {
display: flex; gap: 8px; font-size: 11.5px; padding: 7px 9px;
background: #000603; border-radius: 3px;
}
.entry .kind { flex: 1; color: var(--green-dim); }
.entry .delta { font-variant-numeric: tabular-nums; }
.entry .delta.pos { color: var(--amber); }
.entry .delta.neg { color: var(--green-dim); }
.entry .after {
font-variant-numeric: tabular-nums; color: var(--green-dim);
min-width: 70px; text-align: right;
}
.verify-out { margin-top: 10px; font-size: 11.5px; }
.verify-out .ok { color: var(--green); font-weight: 700; }
.verify-out .bad { color: var(--red); font-weight: 700; }
.verify-out .kv code { font-size: 10px; }