summaryrefslogtreecommitdiffstats
path: root/web/play/style.css
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-08 03:29:35 +0100
committersalmonik <salmonik@tuta.io>2026-11-08 03:29:35 +0100
commit058432130628d00764663ecf819a9adc6793768d (patch)
treed3f49f111fbfaa48647cca2600adcf9017af2d30 /web/play/style.css
parentweb: serve latest.php through PHP and refuse stray PHP files (diff)
downloadzagan-bet-058432130628d00764663ecf819a9adc6793768d.tar.gz
zagan-bet-058432130628d00764663ecf819a9adc6793768d.zip
v5.0.0: protocol v5 and the browser frontend
Protocol v5 replaces newline lines with bounded [u32 len][text] frames and REQ/RES/EVT envelopes. Requests carry correlation ids and the server replays the last 256 replies per connection, so a gateway retry cannot double-debit and a late reply cannot clobber a newer request. HELLO/PONG carry server time; crash rounds announce started_at and push coalesced 10 Hz ticks. Crash now commits sha256(seed) before a round and reveals the seed when it ends, so clients recompute the crash point. Adds the webd/ gateway: one blocking thread, one ClientCore and one pinned upstream TLS connection per session, an HttpOnly session cookie, takeover, and a 60-second grace window. The browser sends Command JSON and renders View JSON; it never speaks the game protocol or stores credentials. Adds the vanilla TypeScript browser app at /play/ (login, lobby, all four games, history, notices, crash verification), the nginx /play and /ws routes, and the headless DOM smoke test. webd and the app stay out of the TUI/GUI/server build graph. The client/server version moves to 5.0.0 in lockstep; v4 peers are refused at HELLO.
Diffstat (limited to '')
-rw-r--r--web/play/style.css407
1 files changed, 407 insertions, 0 deletions
diff --git a/web/play/style.css b/web/play/style.css
new file mode 100644
index 0000000..20842d2
--- /dev/null
+++ b/web/play/style.css
@@ -0,0 +1,407 @@
1:root {
2 --bg: #0a0e12;
3 --surface: #11171d;
4 --raised: #171f27;
5 --hover: #1d2731;
6 --line: #242e38;
7 --text: #d9e2ea;
8 --dim: #8a98a6;
9 --faint: #5b6773;
10 --accent: #4fb8c9;
11 --win: #4ec27b;
12 --loss: #e05a5a;
13 --gold: #e0b64a;
14 --felt: #0d5c39;
15 --felt-dark: #09472c;
16 --felt-line: #1f7d52;
17 --felt-text: #ecf7f0;
18 --red: #b0313a;
19 --black: #151b21;
20 --zero: #106b45;
21 --radius-sm: 4px;
22 --radius-md: 8px;
23 --radius-lg: 12px;
24 --sans: "Open Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
25 --mono: "Hack", ui-monospace, "SFMono-Regular", "Cascadia Code", monospace;
26}
27
28* { box-sizing: border-box; }
29html, body { height: 100%; }
30body {
31 margin: 0;
32 background:
33 radial-gradient(1100px 600px at 80% -10%, rgba(79, 184, 201, 0.06), transparent 60%),
34 var(--bg);
35 color: var(--text);
36 font-family: var(--sans);
37 font-size: 14px;
38 line-height: 1.5;
39 -webkit-font-smoothing: antialiased;
40}
41[hidden] { display: none !important; }
42
43a { color: var(--accent); text-decoration: none; }
44a:hover { text-decoration: underline; }
45button, input { font: inherit; color: inherit; }
46button { cursor: pointer; }
47button:disabled { cursor: not-allowed; opacity: 0.45; }
48
49.shell { min-height: 100%; display: flex; flex-direction: column; }
50
51.topbar {
52 display: flex;
53 align-items: center;
54 gap: 16px;
55 min-height: 64px;
56 padding: 0 24px;
57 border-bottom: 1px solid var(--line);
58 background: rgba(10, 14, 18, 0.86);
59 backdrop-filter: blur(10px);
60 position: sticky;
61 top: 0;
62 z-index: 20;
63}
64.brand {
65 color: var(--text);
66 font-family: var(--mono);
67 font-size: 19px;
68 letter-spacing: 0.04em;
69 white-space: nowrap;
70}
71.brand span { color: var(--win); }
72.brand:hover { text-decoration: none; }
73.tagline {
74 color: var(--faint);
75 font-family: var(--mono);
76 font-size: 11px;
77 text-transform: uppercase;
78 letter-spacing: 0.12em;
79}
80.spacer { flex: 1; }
81.session { display: flex; align-items: center; gap: 12px; }
82.session .who { color: var(--dim); font-family: var(--mono); font-size: 12px; }
83.session .who strong { color: var(--text); font-weight: 600; }
84.balance {
85 color: var(--win);
86 font-family: var(--mono);
87 font-size: 16px;
88 padding: 4px 10px;
89 border: 1px solid var(--line);
90 border-radius: var(--radius-sm);
91 background: var(--surface);
92 white-space: nowrap;
93}
94
95main { width: min(1240px, 100%); margin: 0 auto; padding: 28px 24px 64px; flex: 1; }
96
97h1, h2, h3 { margin: 0; font-weight: 600; }
98h1 { font-size: 26px; letter-spacing: -0.01em; }
99h2 { font-size: 18px; }
100.muted { color: var(--dim); }
101.faint { color: var(--faint); }
102.mono { font-family: var(--mono); }
103.label {
104 font-family: var(--mono);
105 font-size: 10px;
106 text-transform: uppercase;
107 letter-spacing: 0.16em;
108 color: var(--dim);
109}
110.error-text { color: var(--loss); }
111.win-text { color: var(--win); }
112
113.btn {
114 border: 1px solid var(--line);
115 background: var(--raised);
116 color: var(--text);
117 padding: 9px 14px;
118 border-radius: var(--radius-md);
119 line-height: 1.2;
120 transition: background 120ms ease, border-color 120ms ease, transform 80ms ease;
121}
122.btn:hover:not(:disabled) { background: var(--hover); border-color: #33404d; }
123.btn:active:not(:disabled) { transform: translateY(1px); }
124.btn.primary {
125 background: var(--win);
126 border-color: var(--win);
127 color: #06120b;
128 font-weight: 700;
129}
130.btn.primary:hover:not(:disabled) { filter: brightness(1.08); }
131.btn.accent {
132 background: rgba(79, 184, 201, 0.14);
133 border-color: var(--accent);
134 color: var(--accent);
135 font-weight: 600;
136}
137.btn.danger {
138 background: rgba(224, 90, 90, 0.12);
139 border-color: rgba(224, 90, 90, 0.55);
140 color: var(--loss);
141}
142.btn.ghost { background: transparent; }
143.btn.small { padding: 5px 9px; font-size: 12px; }
144.btn.wide { width: 100%; }
145.btn.big { padding: 13px 18px; font-size: 15px; }
146
147.input {
148 width: 100%;
149 background: var(--bg);
150 border: 1px solid var(--line);
151 border-radius: var(--radius-md);
152 padding: 10px 12px;
153 outline: none;
154}
155.input:focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(79, 184, 201, 0.18); }
156.field { display: grid; gap: 6px; }
157.row { display: flex; align-items: center; gap: 10px; }
158.row.wrap { flex-wrap: wrap; }
159.hr { height: 1px; background: var(--line); border: 0; margin: 18px 0; }
160
161/* Auth */
162.auth-wrap { display: grid; place-items: center; min-height: 52vh; }
163.auth-card {
164 width: min(430px, 100%);
165 background: var(--surface);
166 border: 1px solid var(--line);
167 border-radius: var(--radius-lg);
168 padding: 26px;
169 box-shadow: 0 24px 70px rgba(0, 0, 0, 0.35);
170}
171.auth-tabs { display: flex; gap: 4px; margin-bottom: 20px; background: var(--bg); padding: 4px; border-radius: var(--radius-md); }
172.auth-tab { flex: 1; border: 0; background: transparent; color: var(--dim); padding: 8px; border-radius: var(--radius-sm); }
173.auth-tab.active { background: var(--raised); color: var(--text); }
174.auth-card form { display: grid; gap: 14px; }
175.auth-foot { margin-top: 16px; font-size: 12px; color: var(--faint); }
176
177/* Lobby */
178.lobby-head { display: flex; align-items: end; gap: 18px; margin-bottom: 22px; flex-wrap: wrap; }
179.lobby-head .spacer { flex: 1; }
180.game-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
181.game-card {
182 background: var(--surface);
183 border: 1px solid var(--line);
184 border-radius: var(--radius-lg);
185 padding: 18px;
186 display: flex;
187 flex-direction: column;
188 gap: 12px;
189 min-height: 178px;
190 position: relative;
191 overflow: hidden;
192}
193.game-card::before {
194 content: "";
195 position: absolute;
196 inset: 0 auto 0 0;
197 width: 3px;
198 background: var(--accent);
199 opacity: 0.7;
200}
201.game-card.crash::before { background: var(--gold); }
202.game-card.roulette::before { background: var(--felt-line); }
203.game-card.blackjack::before { background: var(--loss); }
204.game-card h3 { font-size: 17px; }
205.game-card p { margin: 0; color: var(--dim); font-size: 13px; flex: 1; }
206.game-card .row { justify-content: space-between; }
207.bet-input { width: 92px; }
208
209/* Game shell */
210.game-head { display: flex; align-items: center; gap: 14px; margin-bottom: 18px; }
211.game-head .spacer { flex: 1; }
212.game-body {
213 display: grid;
214 grid-template-columns: minmax(0, 1fr) 320px;
215 gap: 16px;
216 align-items: start;
217}
218.board {
219 background: var(--surface);
220 border: 1px solid var(--line);
221 border-radius: var(--radius-lg);
222 padding: 18px;
223 min-height: 560px;
224 overflow: hidden;
225}
226.sidebar {
227 background: var(--surface);
228 border: 1px solid var(--line);
229 border-radius: var(--radius-lg);
230 padding: 18px;
231 display: flex;
232 flex-direction: column;
233 gap: 14px;
234 position: sticky;
235 top: 84px;
236}
237.sidebar .actions { margin-top: auto; display: grid; gap: 8px; }
238.sidebar .stat { display: flex; justify-content: space-between; gap: 12px; font-size: 13px; }
239.sidebar .stat strong { font-family: var(--mono); font-weight: 600; }
240.note { color: var(--dim); font-size: 12px; }
241.chip-row { display: flex; flex-wrap: wrap; gap: 6px; }
242.chip-btn {
243 font-family: var(--mono);
244 font-size: 12px;
245 border-radius: 999px;
246 border: 1px solid var(--line);
247 background: var(--raised);
248 padding: 5px 10px;
249}
250.chip-btn.active { border-color: var(--gold); color: var(--gold); background: rgba(224, 182, 74, 0.1); }
251
252/* Slots */
253.reels {
254 display: grid;
255 grid-template-columns: repeat(5, minmax(60px, 1fr));
256 gap: 10px;
257 max-width: 760px;
258 margin: 24px auto 0;
259}
260.reel { display: grid; gap: 8px; }
261.tile {
262 aspect-ratio: 1 / 0.92;
263 display: grid;
264 place-items: center;
265 border-radius: var(--radius-md);
266 border: 1px solid var(--line);
267 background: var(--bg);
268 font-family: var(--mono);
269 font-size: clamp(26px, 5vw, 54px);
270 font-weight: 700;
271 color: var(--dim);
272 transition: color 90ms ease, border-color 90ms ease, background 90ms ease;
273}
274.tile.sym-C { color: #c8d1da; }
275.tile.sym-L { color: #7cc4d4; }
276.tile.sym-O { color: #d8c27a; }
277.tile.sym-G { color: #83d09a; }
278.tile.sym-D { color: #d48d8d; }
279.tile.sym-S { color: #e0b64a; }
280.tile.win { border-color: var(--win); background: rgba(78, 194, 123, 0.12); color: var(--win); }
281.tile.spinning { color: var(--faint); }
282.win-lines { text-align: center; margin-top: 18px; color: var(--dim); font-size: 12px; min-height: 18px; }
283
284/* Cards */
285.hand { margin: 16px 0; }
286.hand .label { margin-bottom: 8px; }
287.cards { display: flex; gap: 8px; flex-wrap: wrap; min-height: 116px; }
288.card {
289 width: 76px;
290 height: 108px;
291 border-radius: 9px;
292 background: #f2f5f7;
293 color: #121820;
294 border: 1px solid #cfd8df;
295 padding: 8px;
296 display: flex;
297 flex-direction: column;
298 justify-content: space-between;
299 font-family: var(--mono);
300 font-weight: 700;
301 box-shadow: 0 8px 18px rgba(0, 0, 0, 0.22);
302}
303.card.red { color: #a51f2d; }
304.card.back { background: repeating-linear-gradient(135deg, #1d2731, #1d2731 6px, #26333f 6px, #26333f 12px); border-color: #33404d; }
305.card .rank { font-size: 22px; line-height: 1; }
306.card .suit { font-size: 22px; align-self: flex-end; }
307.card.back .rank, .card.back .suit { color: var(--faint); }
308.total { color: var(--dim); font-family: var(--mono); font-size: 12px; }
309.total strong { color: var(--text); font-size: 15px; }
310.outcome { margin-top: 20px; padding: 14px 16px; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--bg); }
311
312/* Crash */
313.crash-wrap { position: relative; }
314.crash-number {
315 font-family: var(--mono);
316 font-size: clamp(44px, 9vw, 86px);
317 line-height: 1;
318 letter-spacing: -0.04em;
319}
320.crash-number.running { color: var(--gold); }
321.crash-number.crashed { color: var(--loss); }
322.crash-number.cashed { color: var(--win); }
323.crash-meta { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 10px; color: var(--faint); font-size: 11px; font-family: var(--mono); }
324.verify { margin-top: 12px; padding: 10px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--bg); font-size: 11px; color: var(--dim); word-break: break-all; }
325.verify.ok { border-color: rgba(78, 194, 123, 0.4); color: var(--win); }
326.verify.bad { border-color: rgba(224, 90, 90, 0.5); color: var(--loss); }
327
328/* Roulette */
329.roulette-wrap { position: relative; }
330canvas { display: block; width: 100%; }
331.roulette-canvas { height: 540px; }
332.crash-canvas { height: 360px; }
333.wheel-result {
334 position: absolute;
335 top: 16px;
336 right: 16px;
337 padding: 8px 12px;
338 border-radius: var(--radius-md);
339 border: 1px solid var(--line);
340 background: rgba(10, 14, 18, 0.86);
341 font-family: var(--mono);
342}
343.wheel-result .num { font-size: 26px; font-weight: 700; }
344.wheel-result.red .num { color: #f06a74; }
345.wheel-result.black .num { color: #dfe8ef; }
346.wheel-result.green .num { color: #66d49a; }
347
348/* History */
349.table-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius-lg); }
350table { width: 100%; border-collapse: collapse; background: var(--surface); }
351th, td { text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--line); font-size: 13px; white-space: nowrap; }
352th { color: var(--dim); font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.12em; }
353tr:last-child td { border-bottom: 0; }
354td.num { font-family: var(--mono); }
355td.pos { color: var(--win); }
356td.neg { color: var(--loss); }
357
358/* Toasts and overlays */
359#toasts { position: fixed; right: 18px; bottom: 18px; display: grid; gap: 8px; z-index: 50; width: min(380px, calc(100vw - 36px)); }
360.toast {
361 background: var(--surface);
362 border: 1px solid var(--line);
363 border-left: 3px solid var(--accent);
364 border-radius: var(--radius-md);
365 padding: 10px 12px;
366 font-size: 13px;
367 box-shadow: 0 12px 30px rgba(0, 0, 0, 0.35);
368 animation: toast-in 160ms ease-out;
369}
370.toast.good { border-left-color: var(--win); }
371.toast.bad { border-left-color: var(--loss); }
372@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } }
373.overlay {
374 position: fixed;
375 inset: 0;
376 background: rgba(5, 8, 11, 0.78);
377 display: grid;
378 place-items: center;
379 z-index: 60;
380 padding: 24px;
381}
382.modal {
383 width: min(440px, 100%);
384 background: var(--surface);
385 border: 1px solid var(--line);
386 border-radius: var(--radius-lg);
387 padding: 24px;
388 text-align: center;
389}
390.modal p { color: var(--dim); }
391
392@media (max-width: 900px) {
393 .game-body { grid-template-columns: 1fr; }
394 .sidebar { position: static; }
395 .tagline { display: none; }
396}
397@media (max-width: 620px) {
398 main { padding: 18px 14px 54px; }
399 .topbar { padding: 0 14px; gap: 10px; }
400 .session .who { display: none; }
401 .balance { font-size: 14px; }
402 .reels { gap: 6px; }
403 .tile { font-size: clamp(18px, 7vw, 34px); }
404 .card { width: 58px; height: 84px; }
405 .crash-canvas { height: 260px; }
406 .roulette-canvas { height: 400px; }
407}