fuzzyworld

explodorama / files / src / style.css

style.css

Blame: the commit that last changed each line · file · history

644cfd0b 2026-10-20 Pavel Safronov1/* Colours taken from the Kenney sprites, so the bars and the board look like one set. */
5551eb37 2026-10-20 Pavel Safronov2:root {
644cfd0b 2026-10-20 Pavel Safronov3 --sky: #c3e3ff;
4 --ink: #353541;
5 --muted: #6b6b7b;
6 --accent: #2cc987;
7 --accent-dark: #1f9e69;
8 --panel: #fff;
5551eb37 2026-10-20 Pavel Safronov9}
10
11html,
12body {
13 margin: 0;
14 height: 100%;
15 overflow: hidden;
644cfd0b 2026-10-20 Pavel Safronov16 background: var(--sky);
5551eb37 2026-10-20 Pavel Safronov17 color: var(--ink);
644cfd0b 2026-10-20 Pavel Safronov18 font: 16px/1.3 ui-rounded, 'SF Pro Rounded', system-ui, -apple-system, sans-serif;
5551eb37 2026-10-20 Pavel Safronov19 /* A game surface: no pull-to-refresh, no double-tap zoom, no text selection. */
20 touch-action: none;
21 user-select: none;
22 -webkit-user-select: none;
23 -webkit-tap-highlight-color: transparent;
24 overscroll-behavior: none;
25}
26
644cfd0b 2026-10-20 Pavel Safronov27img {
28 pointer-events: none;
29}
30
31.sr-only {
32 position: absolute;
33 width: 1px;
34 height: 1px;
35 overflow: hidden;
36 clip-path: inset(50%);
37 white-space: nowrap;
38}
39
5551eb37 2026-10-20 Pavel Safronov40#hud {
41 position: fixed;
42 top: 0;
43 left: 0;
44 right: 0;
45 z-index: 2;
46 display: flex;
644cfd0b 2026-10-20 Pavel Safronov47 align-items: center;
99869630 2026-10-23 Pavel Safronov48 gap: 12px;
644cfd0b 2026-10-20 Pavel Safronov49 padding: calc(env(safe-area-inset-top) + 6px) 16px 6px;
50 background: var(--ink);
51 color: #fff;
52}
53
52d68b59 2026-10-24 Pavel Safronov54/* The items sit in a fixed number of rows that never wrap (user request, 2026-10-23: the bar
55 grew a row as power-ups and charges came, and the board moved down). On a phone or a narrow
56 screen: three rows, the third (passes, Star, charges) kept even while empty. On a wide screen:
57 one row. A row whose content is too wide scrolls sideways instead of growing. The round
58 buttons stay on the right, stacked beside the rows on a phone, side by side on a wide screen. */
99869630 2026-10-23 Pavel Safronov59#hud-items {
60 flex: 1;
61 min-width: 0;
644cfd0b 2026-10-20 Pavel Safronov62 display: flex;
52d68b59 2026-10-24 Pavel Safronov63 flex-direction: column;
64 gap: 4px;
65}
66
67.hud-row {
68 display: flex;
69 flex-wrap: nowrap;
99869630 2026-10-23 Pavel Safronov70 justify-content: space-between;
71 align-items: center;
52d68b59 2026-10-24 Pavel Safronov72 gap: 12px;
73 min-height: 30px;
74 overflow-x: auto;
75 scrollbar-width: none;
e3384c6b 2026-10-24 Pavel Safronov76 /* A row that scrolls also clips, so an item bumping (scale 1.3, below) at its edge was cut off
77 (user report, 2026-10-24: Turn, first in its row). Room for it inside the row, given back
78 with negative margins so nothing moves. */
79 padding: 6px 16px;
80 margin: -6px -16px;
52d68b59 2026-10-24 Pavel Safronov81}
82
83.hud-row::-webkit-scrollbar {
84 display: none;
85}
86
87#hud-third {
88 justify-content: flex-start;
89}
90
91@media (min-width: 900px) {
92 #hud-items {
93 flex-direction: row;
94 align-items: center;
95 gap: 12px;
96 }
97
98 .hud-row {
99 flex: none;
100 }
101
102 #hud-third {
103 flex: 1 1 0;
104 min-width: 0;
105 }
99869630 2026-10-23 Pavel Safronov106}
107
108/* On a phone the menu sits above "?" (user request, 2026-10-22); in the HTML "?" comes first,
109 which is the order wanted side by side on a wide screen. */
110#hud-buttons {
111 display: flex;
112 flex-direction: column-reverse;
113 gap: 4px;
114}
115
116@media (min-width: 601px) {
117 #hud-buttons {
118 flex-direction: row;
119 gap: 6px;
120 }
121}
122
92cc4ee3 2026-11-02 Pavel Safronov123/* On a phone, with the fullscreen button there, three round buttons stacked would be taller than
124 the three rows beside them (and grow the bar), so all three shrink to fit, 31px. */
125@media (max-width: 600px) {
126 #hud-buttons:has(#fullscreen-button:not([hidden])) {
127 gap: 2px;
128 }
129
130 #hud-buttons:has(#fullscreen-button:not([hidden])) button {
131 width: 31px;
132 height: 31px;
133 padding-bottom: 2px;
134 }
135
136 #hud-buttons:has(#fullscreen-button:not([hidden])) #info-button img {
137 width: 16px;
138 height: 16px;
139 }
140
141 #hud-buttons:has(#fullscreen-button:not([hidden])) #menu-button span {
142 width: 13px;
143 }
144}
145
52d68b59 2026-10-24 Pavel Safronov146.hud-row > span {
99869630 2026-10-23 Pavel Safronov147 display: flex;
52d68b59 2026-10-24 Pavel Safronov148 flex: none;
644cfd0b 2026-10-20 Pavel Safronov149 align-items: center;
150}
151
152#hud .icon {
153 width: 28px;
154 height: 28px;
155 /* A light rim, so the dark bomb sprite stands out on the dark bar. */
156 filter: drop-shadow(0 0 1.5px rgb(255 255 255 / 0.7));
157}
158
99869630 2026-10-23 Pavel Safronov159/* One-colour ink icons (the fuse timer) turn white on the dark bar. */
160#hud .icon.mono {
161 filter: brightness(0) invert(1);
162}
163
27f24fed 2026-10-20 Pavel Safronov164#hud .icon.small {
165 width: 22px;
166 height: 22px;
167 margin-left: 6px;
168}
169
644cfd0b 2026-10-20 Pavel Safronov170#hud .digit {
171 width: 20px;
172 height: 20px;
173 margin-right: -4px;
174}
175
176#hud .icon + .sr-only + .digit {
177 margin-left: 2px;
178}
179
180#hud .digit + .icon {
181 margin-left: 10px;
182}
183
beb9f2b7 2026-10-21 Pavel Safronov184#hud .tag {
185 padding: 1px 6px;
186 border-radius: 6px;
187 background: #ffc83d;
188 color: var(--ink);
189 font: 11px 'Kenney Future', ui-rounded, system-ui, sans-serif;
190}
191
192#hud .tag + .tag {
193 margin-left: 3px;
194}
195
196#hud-modes:empty {
197 display: none;
198}
199
c37a2b0b 2026-10-20 Pavel Safronov200#hud small.par {
201 margin: 0 0 0 6px;
202 font-size: 12px;
203 opacity: 0.7;
204}
205
644cfd0b 2026-10-20 Pavel Safronov206#hud small {
207 margin-right: 4px;
208 font-size: 13px;
209 font-weight: 800;
210 letter-spacing: 0.04em;
211 text-transform: uppercase;
5551eb37 2026-10-20 Pavel Safronov212}
213
b89399da 2026-11-01 Pavel Safronov214#toast {
215 position: fixed;
216 /* A popover (top layer, over dialogs): undo its centred default. */
217 inset: auto;
218 margin: 0;
219 overflow: visible;
220 left: 50%;
221 bottom: calc(env(safe-area-inset-bottom) + 96px);
222 z-index: 3;
223 align-items: center;
224 gap: 10px;
225 padding: 8px 16px 8px 10px;
226 border: 2px solid var(--ink);
227 border-radius: 16px;
228 background: #ece3cc;
229 color: var(--ink);
230 pointer-events: none;
231 opacity: 0;
232 transform: translate(-50%, 8px);
233 transition: opacity 0.25s, transform 0.25s;
234}
235
236#toast:popover-open {
237 display: flex;
238}
239
240#toast.show {
241 opacity: 1;
242 transform: translate(-50%, 0);
243}
244
245#toast img {
246 width: 44px;
247 height: 44px;
248 image-rendering: auto;
249}
250
251#toast strong,
252#toast span {
253 display: block;
254}
255
256#toast span {
257 font-size: 12px;
258 color: var(--muted);
259}
260
261@media (prefers-reduced-motion: reduce) {
262 #toast {
263 transition: none;
264 }
265}
266
92cc4ee3 2026-11-02 Pavel Safronov267/* The autopilot's note (main.ts `autopilotNote`): top centre below the top bar, whose height sets `top`. Not a popover, so dialogs cover it. */
268#autopilot {
269 position: fixed;
270 top: 56px;
271 left: 50%;
272 z-index: 3;
273 display: flex;
274 align-items: center;
275 gap: 10px;
276 max-width: calc(100vw - 32px);
277 transform: translateX(-50%);
278 padding: 6px 8px 6px 16px;
279 border: 2px solid var(--ink);
280 border-radius: 16px;
281 background: #ece3cc;
282 color: var(--ink);
283 font-weight: bold;
284 pointer-events: none;
285}
286
287#autopilot[hidden] {
288 display: none;
289}
290
291#autopilot .autopilot-hint {
292 display: flex;
293 align-items: center;
294}
295
296#autopilot .pad-glyph {
297 width: 28px;
298 height: 28px;
299 filter: drop-shadow(0 0 1px rgb(0 0 0 / 0.55));
300}
301
302#autopilot .key-cap {
303 box-sizing: border-box;
304 min-width: 28px;
305 height: 26px;
306 padding: 0 7px;
307 border: 2px solid var(--ink);
308 border-bottom-width: 4px;
309 border-radius: 6px;
310 background: #fff;
311 color: var(--ink);
312 font: bold 12px/22px ui-monospace, monospace;
313 text-align: center;
314}
315
316/* A touch screen has no Esc key. */
317@media (pointer: coarse) {
318 #autopilot .key-cap {
319 display: none;
320 }
321}
322
323#autopilot button {
324 pointer-events: auto;
325 padding: 2px 12px;
326 border: 2px solid var(--ink);
327 border-radius: 10px;
328 background: var(--panel);
329 box-shadow: 0 2px 0 #1e1e27;
330 color: var(--ink);
331 font: inherit;
332 font-size: 13px;
333 font-weight: 800;
334}
335
336/* While it walks the action bar does nothing (main.ts `boardLocked`). */
337body.autopilot #actions {
338 opacity: 0.4;
339 pointer-events: none;
340}
341
ebcd0a8f 2026-11-02 Pavel Safronov342/* The controller's target: what confirming does (main.ts `padLabel`), above the action bar; bottom is set from the bar's height. */
343#pad-label {
344 white-space: pre-line;
345 position: fixed;
346 left: 50%;
347 bottom: 96px;
348 z-index: 3;
349 max-width: calc(100vw - 32px);
350 transform: translateX(-50%);
351 padding: 8px 16px;
352 border: 2px solid var(--ink);
353 border-radius: 16px;
354 background: #ece3cc;
355 color: var(--ink);
356 font-weight: bold;
357 text-align: center;
358 pointer-events: none;
359}
360
361#pad-label[hidden] {
362 display: none;
363}
364
5551eb37 2026-10-20 Pavel Safronov365#actions {
366 position: fixed;
367 bottom: 0;
368 left: 0;
369 right: 0;
370 z-index: 2;
371 display: flex;
c37a2b0b 2026-10-20 Pavel Safronov372 flex-wrap: wrap;
5551eb37 2026-10-20 Pavel Safronov373 gap: 8px;
374 padding: 8px 16px calc(env(safe-area-inset-bottom) + 8px);
644cfd0b 2026-10-20 Pavel Safronov375 background: var(--ink);
5551eb37 2026-10-20 Pavel Safronov376}
377
99869630 2026-10-23 Pavel Safronov378#actions > button {
d3e4ed26 2026-10-25 Pavel Safronov379 /* Icons only (names are in the explanation strip and the tooltip). Up to four to a row: Move,
380 Bomb, the charge (when one is held, 2026-10-24) and Wait, sharing the row's width. */
381 position: relative;
382 flex: 1 0 calc(25% - 6px);
644cfd0b 2026-10-20 Pavel Safronov383 display: grid;
99869630 2026-10-23 Pavel Safronov384 grid-template-columns: auto;
644cfd0b 2026-10-20 Pavel Safronov385 align-items: center;
99869630 2026-10-23 Pavel Safronov386 justify-items: center;
644cfd0b 2026-10-20 Pavel Safronov387 justify-content: center;
c37a2b0b 2026-10-20 Pavel Safronov388 min-width: 0;
5551eb37 2026-10-20 Pavel Safronov389 min-height: 56px;
644cfd0b 2026-10-20 Pavel Safronov390 margin-bottom: 4px;
391 border: 3px solid var(--ink);
5551eb37 2026-10-20 Pavel Safronov392 border-radius: 12px;
644cfd0b 2026-10-20 Pavel Safronov393 background: var(--panel);
394 /* Kenney-style chunky button: a solid shadow that it sinks into when pressed. */
395 box-shadow: 0 4px 0 #1e1e27;
5551eb37 2026-10-20 Pavel Safronov396 color: var(--ink);
397 font: inherit;
644cfd0b 2026-10-20 Pavel Safronov398 font-weight: 800;
399}
400
401#actions .icon {
402 width: 36px;
403 height: 36px;
404}
405
99869630 2026-10-23 Pavel Safronov406#actions > button[hidden] {
c37a2b0b 2026-10-20 Pavel Safronov407 display: none;
408}
409
720cc9d3 2026-11-03 Pavel Safronov410/* Cancel (Q89): a round button over the bar's top right, while an action is picked or auto-move
411 has a destination. Not for the pad (L1 does it) nor the demo. */
412#actions > button.cancel {
413 position: absolute;
414 right: 16px;
415 top: -56px;
416 flex: none;
417 width: 48px;
418 min-width: 48px;
419 min-height: 48px;
420 margin: 0;
421 padding: 0;
422 border-radius: 50%;
423}
424
425#actions > button.cancel img {
426 width: 28px;
427 height: 28px;
428 pointer-events: none;
429}
430
431body.pad #actions > button.cancel,
432body.demo #actions > button.cancel {
433 display: none;
434}
435
d3e4ed26 2026-10-25 Pavel Safronov436/* The charge button with no charge: an empty slot, a dashed square where a charge's icon goes. */
437#actions .slot {
438 width: 30px;
439 height: 30px;
440 border: 3px dashed currentColor;
441 border-radius: 8px;
442 opacity: 0.6;
443}
444
445/* The charge button's uses left, in a small badge at its bottom right (the icon's own badge is
446 at its top right). */
447#actions .count {
448 position: absolute;
449 bottom: 4px;
450 right: 6px;
451 min-width: 18px;
452 padding: 0 4px;
453 border-radius: 9px;
454 background: var(--ink);
455 color: #fff;
456 font: 800 12px/18px ui-rounded, system-ui, sans-serif;
457}
458
99869630 2026-10-23 Pavel Safronov459/* On a phone there is no keyboard to hint at. */
c37a2b0b 2026-10-20 Pavel Safronov460@media (max-width: 600px) {
ea380ee8 2026-10-22 Pavel Safronov461 #actions kbd {
c37a2b0b 2026-10-20 Pavel Safronov462 display: none;
463 }
99869630 2026-10-23 Pavel Safronov464}
465
466#actions > button:active:not(:disabled) {
644cfd0b 2026-10-20 Pavel Safronov467 transform: translateY(4px);
468 box-shadow: none;
5551eb37 2026-10-20 Pavel Safronov469}
470
82adee18 2026-10-20 Pavel Safronov471/* Auto-move: Move has become "Go", one step toward the flag per press. */
99869630 2026-10-23 Pavel Safronov472#actions > button.auto {
82adee18 2026-10-20 Pavel Safronov473 border-color: var(--accent-dark);
474 box-shadow: 0 4px 0 var(--accent-dark);
475}
476
e3d8dc49 2026-10-27 Pavel Safronov477/* Wait, when waiting would cost a life (user request, 2026-10-26): red, like the danger overlay. */
478#actions > button.deadly:not(:disabled) {
479 border-color: #c0392b;
480 background: #ffd9d4;
481 box-shadow: 0 4px 0 #8e2a20;
482}
483
99869630 2026-10-23 Pavel Safronov484#actions > button[aria-pressed='true'] {
5551eb37 2026-10-20 Pavel Safronov485 background: var(--accent);
486 color: #fff;
487}
488
99869630 2026-10-23 Pavel Safronov489/* One-colour ink icons (Move, Wait) turn white on the green of a picked button. */
52d68b59 2026-10-24 Pavel Safronov490#actions > button[aria-pressed='true'] .mono {
99869630 2026-10-23 Pavel Safronov491 filter: brightness(0) invert(1);
492}
493
494#actions > button:disabled {
644cfd0b 2026-10-20 Pavel Safronov495 opacity: 0.45;
5551eb37 2026-10-20 Pavel Safronov496}
497
498#actions kbd {
499 font: 11px ui-monospace, monospace;
500 color: var(--muted);
501}
502
99869630 2026-10-23 Pavel Safronov503#actions > button[aria-pressed='true'] kbd {
644cfd0b 2026-10-20 Pavel Safronov504 color: #e2fff2;
5551eb37 2026-10-20 Pavel Safronov505}
506
ebcd0a8f 2026-11-02 Pavel Safronov507/* The controller's picture of the button, beside the icon (shown while the pad is in use, body.pad,
508 set and cleared in main.ts; the key hint goes then). The pair sits on one row and shrinks
509 together on a narrow bar: the icon is 36px, the picture 32px (about 90% of it), each able to
510 shrink to fit the button. The button keeps its height (min-height 56px). */
511#actions .pad-glyph {
512 display: none;
513 pointer-events: none;
514 /* Nintendo's buttons are white: a hairline keeps them from vanishing on the light button. */
515 filter: drop-shadow(0 0 1px rgb(0 0 0 / 0.55));
516}
517
518body.pad #actions > button {
519 display: flex;
520 align-items: center;
521 justify-content: center;
522 gap: 4px;
523}
524
525body.pad #actions .icon,
526body.pad #actions .slot {
527 flex: 0 1 36px;
528 min-width: 0;
529 height: auto;
530 aspect-ratio: 1;
531}
532
533body.pad #actions .slot {
534 flex-basis: 30px;
535}
536
537body.pad #actions .pad-glyph {
538 display: block;
539 flex: 0 1 32px;
540 width: 32px;
541 min-width: 0;
542 height: auto;
543 aspect-ratio: 1;
544}
545
546body.pad #actions kbd {
547 display: none;
548}
549
5551eb37 2026-10-20 Pavel Safronov550#banner {
551 position: fixed;
552 inset: 0;
553 z-index: 3;
554 display: grid;
555 place-content: center;
644cfd0b 2026-10-20 Pavel Safronov556 justify-items: center;
5551eb37 2026-10-20 Pavel Safronov557 padding: 16px;
805e7a9f 2026-11-03 Pavel Safronov558 /* The same backdrop as a dialog's. */
559 background: rgb(53 53 65 / 0.6);
5551eb37 2026-10-20 Pavel Safronov560}
561
562#banner[hidden] {
563 display: none;
564}
565
00ad7d0a 2026-10-24 Pavel Safronov566/* A Developer run's victory (controls.ts): framed in red, as its achievements will be. */
567#banner.developer {
568 box-shadow: inset 0 0 0 8px #e8253f;
569}
570
ea380ee8 2026-10-22 Pavel Safronov571/* Animations for the HTML parts: the banner comes in after a short pause, so the death or the
572 level-cleared jump is seen first; dialogs pop up over a fading backdrop. */
573@keyframes fade-in {
574 from {
575 opacity: 0;
576 }
577}
578
579@keyframes pop-in {
580 from {
581 opacity: 0;
582 transform: scale(0.9);
583 }
584}
585
586#banner:not([hidden]) {
587 animation: fade-in 0.3s ease-out 0.35s backwards;
588}
589
805e7a9f 2026-11-03 Pavel Safronov590#banner:not([hidden]) > .panel {
ea380ee8 2026-10-22 Pavel Safronov591 animation: pop-in 0.3s ease-out 0.35s backwards;
592}
593
594#dialog[open] .panel {
595 animation: pop-in 0.18s ease-out;
596}
597
598#dialog[open]::backdrop {
599 animation: fade-in 0.18s ease-out;
600}
601
602/* Closing (dialog.ts): the panel shrinks away and the backdrop clears, then it closes. */
603@keyframes pop-out {
604 to {
605 opacity: 0;
606 transform: scale(0.9);
607 }
608}
609
610@keyframes fade-out {
611 to {
612 opacity: 0;
613 }
614}
615
616#dialog.closing .panel {
617 animation: pop-out 0.15s ease-in forwards;
618}
619
620#dialog.closing::backdrop {
621 animation: fade-out 0.15s ease-in forwards;
622}
623
624/* A top-bar item that just changed (controls.ts `show`) bumps. */
625@keyframes bump {
626 40% {
627 transform: scale(1.3);
628 }
629}
630
631#hud .hud-item.bump {
632 animation: bump 0.3s ease-out;
633}
634
635@media (prefers-reduced-motion: reduce) {
636 #banner:not([hidden]),
805e7a9f 2026-11-03 Pavel Safronov637 #banner:not([hidden]) > .panel,
ea380ee8 2026-10-22 Pavel Safronov638 #dialog[open] .panel,
639 #dialog[open]::backdrop,
640 #hud .hud-item.bump {
641 animation: none;
642 }
643}
644
805e7a9f 2026-11-03 Pavel Safronov645/* The end-of-level and end-of-run screen is a panel like a dialog's (user report, 2026-11-02: bare
646 text on a scrim looked unfinished); its three buttons are the dialog's .ui-button ones. */
647#banner .panel {
648 display: flex;
649 flex-direction: column;
650 align-items: center;
651 box-sizing: border-box;
652 width: min(360px, calc(100vw - 32px));
653 max-height: calc(100vh - 32px);
654 max-height: calc(100dvh - 32px);
655 /* On a short screen the panel scrolls, so the buttons stay reachable. */
656 overflow-y: auto;
657 scrollbar-width: none;
658 gap: 10px;
659 padding: 14px 18px 18px;
660 border: 12px solid transparent;
661 border-image: url('../assets/kenney-ui-rpg/panel_beigeLight.png') 12 fill / 12px stretch;
662 color: var(--ink);
663 text-align: center;
664}
665
644cfd0b 2026-10-20 Pavel Safronov666#banner img {
805e7a9f 2026-11-03 Pavel Safronov667 width: 72px;
668 height: 72px;
644cfd0b 2026-10-20 Pavel Safronov669}
670
5551eb37 2026-10-20 Pavel Safronov671#banner p {
672 margin: 0;
805e7a9f 2026-11-03 Pavel Safronov673 font-size: 18px;
674 font-weight: 700;
675 line-height: 1.35;
644cfd0b 2026-10-20 Pavel Safronov676}
677
805e7a9f 2026-11-03 Pavel Safronov678#banner .ui-button {
679 align-self: stretch;
5551eb37 2026-10-20 Pavel Safronov680}
a4fab0fb 2026-10-21 Pavel Safronov681
805e7a9f 2026-11-03 Pavel Safronov682#banner .ui-button[hidden] {
0580e85e 2026-10-24 Pavel Safronov683 display: none;
684}
685
a4fab0fb 2026-10-21 Pavel Safronov686/* ---- Kenney UI pack: the font, the dialog, its buttons, and the menu button ---- */
687
688@font-face {
689 font-family: 'Kenney Future';
690 src: url('../assets/kenney-ui/Kenney Future.ttf') format('truetype');
691 font-display: swap;
692}
693
694/* The top bar's items are buttons that open their info. */
695#hud .hud-item {
696 display: flex;
697 align-items: center;
698 padding: 2px;
699 border: 0;
700 border-radius: 8px;
701 background: none;
702 color: inherit;
703 font: inherit;
704 cursor: pointer;
705}
706
707#hud .hud-item:active {
708 background: rgb(255 255 255 / 0.15);
709}
710
711#hud .hud-item + .hud-item {
712 margin-left: 4px;
713}
714
99869630 2026-10-23 Pavel Safronov715/* "What is that?": a round button like the menu's, with Kenney's Game Icons "question"; green
716 while it is on (the next tap explains). */
717#info-button {
718 display: grid;
719 place-content: center;
720 width: 40px;
721 height: 40px;
722 padding: 0 0 3px;
723 border: 0;
724 background: url('../assets/kenney-ui/Grey/button_round_depth_flat.png') center / contain no-repeat;
725 cursor: pointer;
726}
727
728#info-button img {
729 width: 20px;
730 height: 20px;
731 opacity: 0.85;
732}
733
734#info-button[aria-pressed='true'] {
735 background-image: url('../assets/kenney-ui/Green/button_round_depth_flat.png');
736}
737
738#info-button[aria-pressed='true'] img {
739 filter: invert(1);
740 opacity: 1;
741}
742
743#info-button:disabled {
744 opacity: 0.45;
745}
746
92cc4ee3 2026-11-02 Pavel Safronov747/* Fullscreen (main.ts; hidden where the browser has none): a round button like the other two,
748 with Kenney's Game Icons "larger"; "smaller" while in fullscreen. */
749#fullscreen-button {
750 width: 40px;
751 height: 40px;
752 padding: 0 0 3px;
753 border: 0;
754 background:
755 url('../assets/kenney-icons/larger.png') center / 20px no-repeat,
756 url('../assets/kenney-ui/Grey/button_round_depth_flat.png') center / contain no-repeat;
757 cursor: pointer;
758}
759
760#fullscreen-button[hidden] {
761 display: none;
762}
763
764@media (max-width: 600px) {
765 #fullscreen-button {
766 background-size: 16px, contain;
767 }
768}
769
770#fullscreen-button[aria-pressed='true'] {
771 background-image:
772 url('../assets/kenney-icons/smaller.png'),
773 url('../assets/kenney-ui/Grey/button_round_depth_flat.png');
774}
775
a4fab0fb 2026-10-21 Pavel Safronov776#menu-button {
777 display: grid;
778 place-content: center;
779 gap: 3px;
780 width: 40px;
781 height: 40px;
782 padding: 0 0 3px;
783 border: 0;
784 background: url('../assets/kenney-ui/Grey/button_round_depth_flat.png') center / contain no-repeat;
785 cursor: pointer;
786}
787
788#menu-button span {
789 width: 16px;
790 height: 3px;
791 border-radius: 2px;
792 background: var(--ink);
793}
794
795#dialog {
796 max-width: min(360px, calc(100vw - 32px));
797 overflow: visible;
798 padding: 0;
799 border: 0;
800 background: none;
801 color: var(--ink);
802}
803
804#dialog::backdrop {
805 background: rgb(53 53 65 / 0.6);
806}
807
808/* The RPG expansion's parchment panel, stretched by its edges (9-slice). */
ac534e57 2026-10-25 Pavel Safronov809/* A column: picture, title, body, buttons. It is never taller than the screen — what the browser
810 shows of it, without the address bar (dvh; vh where dvh isn't known), with room for the close
811 button sticking out above — and only the body scrolls, so the buttons always show (user report, 2026-10-24: a long offer's Choose button ran
812 off the bottom of a phone; the body's height was a guess from 100vh). */
a4fab0fb 2026-10-21 Pavel Safronov813#dialog .panel {
814 position: relative;
ac534e57 2026-10-25 Pavel Safronov815 display: flex;
816 flex-direction: column;
817 align-items: center;
818 box-sizing: border-box;
819 max-height: calc(100vh - 64px);
820 max-height: calc(100dvh - 64px);
a4fab0fb 2026-10-21 Pavel Safronov821 gap: 10px;
822 padding: 14px 18px 18px;
823 border: 12px solid transparent;
824 border-image: url('../assets/kenney-ui-rpg/panel_beigeLight.png') 12 fill / 12px stretch;
825 text-align: center;
826}
827
828#dialog .panel:focus {
829 outline: none;
830}
831
832#dialog-close {
833 position: absolute;
834 top: -26px;
835 right: -26px;
836 width: 40px;
837 height: 40px;
838 padding: 0;
839 border: 0;
840 background:
841 url('../assets/kenney-ui/Grey/icon_cross.png') center 45% / 16px no-repeat,
842 url('../assets/kenney-ui/Grey/button_round_depth_flat.png') center / contain no-repeat;
843 cursor: pointer;
844}
845
846#dialog-icon {
847 width: 64px;
848 height: 64px;
849}
850
851#dialog-title {
852 margin: 0;
853 font: 22px/1.2 'Kenney Future', ui-rounded, system-ui, sans-serif;
854}
855
856#dialog-body {
ac534e57 2026-10-25 Pavel Safronov857 flex: 0 1 auto;
858 align-self: stretch;
859 min-height: 0;
beb9f2b7 2026-10-21 Pavel Safronov860 overflow-y: auto;
99869630 2026-10-23 Pavel Safronov861 /* Scrolls by touch, wheel or trackpad, without a scroll bar across the parchment. */
862 scrollbar-width: none;
33a0256b 2026-10-24 Pavel Safronov863 /* The body has touch-action: none (a game surface); a finger may still scroll this. */
864 touch-action: pan-y;
a4fab0fb 2026-10-21 Pavel Safronov865 font-size: 15px;
866 line-height: 1.4;
867}
868
869#dialog-body p {
870 margin: 0 0 8px;
871}
872
873#dialog-body .credit {
874 font-size: 12px;
875 color: var(--muted);
876}
877
878#dialog-body a {
879 color: inherit;
880}
881
52d68b59 2026-10-24 Pavel Safronov882/* A dialog answered from its body (the gem's choice) has no buttons below: more room for it. */
883#dialog-buttons:empty {
884 display: none;
885}
886
99869630 2026-10-23 Pavel Safronov887/* The almanac (ui/almanac.ts): tabs, then a picture and the words for each thing. */
888.almanac-tabs {
889 position: sticky;
890 top: 0;
e6bb7c42 2026-10-25 Pavel Safronov891 /* Over the entries scrolling under it (user report, 2026-10-25: their text showed through). */
892 z-index: 2;
b89399da 2026-11-01 Pavel Safronov893 /* As many equal tabs per row as fit, none narrower than its label (user report, 2026-11-01: five tabs overran). */
894 display: grid;
895 grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
99869630 2026-10-23 Pavel Safronov896 gap: 6px;
897 padding-bottom: 8px;
898 background: #ece3cc;
899}
900
6c3fcc31 2026-10-26 Pavel Safronov901/* Equal tabs that always fit the row (user report, 2026-10-25: on a phone they ran off both
902 ends, cut off). Four since the Board tab: two rows of two on a phone. */
99869630 2026-10-23 Pavel Safronov903.almanac-tabs button {
e6bb7c42 2026-10-25 Pavel Safronov904 flex: 1 1 0;
905 min-width: 0;
906 padding: 4px 2px;
99869630 2026-10-23 Pavel Safronov907 white-space: nowrap;
908 border: 2px solid var(--ink);
909 border-radius: 14px;
910 background: none;
911 color: var(--ink);
e6bb7c42 2026-10-25 Pavel Safronov912 font: 10px 'Kenney Future', ui-rounded, system-ui, sans-serif;
99869630 2026-10-23 Pavel Safronov913 cursor: pointer;
914}
915
916.almanac-tabs button[aria-pressed='true'] {
917 border-color: var(--accent-dark);
918 background: var(--accent);
919 color: #fff;
920}
921
922#dialog-body .almanac-intro {
923 font-size: 13px;
924 color: var(--muted);
925}
926
927.almanac-entry {
928 display: grid;
929 grid-template-columns: 44px 1fr;
930 gap: 10px;
931 align-items: start;
932 padding: 8px 0;
933 border-top: 1px solid rgb(53 53 65 / 0.15);
934 text-align: left;
935}
936
92cc4ee3 2026-11-02 Pavel Safronov937.almanac-entry > img {
99869630 2026-10-23 Pavel Safronov938 width: 44px;
939 height: 44px;
940}
941
942#dialog-body .almanac-entry p {
943 margin: 2px 0 0;
944 font-size: 14px;
945}
946
32dd53cd 2026-10-25 Pavel Safronov947/* An almanac entry with a clip (ui/almanac.ts): a button that opens it, a small ▶ on the right. */
948button.almanac-entry.has-clip {
949 position: relative;
52d68b59 2026-10-24 Pavel Safronov950 width: 100%;
32dd53cd 2026-10-25 Pavel Safronov951 padding-right: 34px;
952 border: 0;
953 border-top: 1px solid rgb(53 53 65 / 0.15);
954 background: none;
955 color: inherit;
956 font: inherit;
957 cursor: pointer;
958}
959
960button.almanac-entry.has-clip::after {
961 content: '▶';
962 position: absolute;
963 right: 4px;
964 top: 50%;
965 display: grid;
966 place-items: center;
967 width: 24px;
968 height: 24px;
969 margin-top: -12px;
970 border-radius: 50%;
971 background: var(--accent);
972 color: #fff;
973 font-size: 11px;
974}
975
976button.almanac-entry.has-clip:active {
977 background: rgb(53 53 65 / 0.06);
978}
979
ac534e57 2026-10-25 Pavel Safronov980/* The gem's choice (ui/offer.ts): the clip of the selected power-up, kept in view while the cards
981 scroll; a tap on a card selects it (ringed green), and the Choose button takes it. */
32dd53cd 2026-10-25 Pavel Safronov982.offer-clip {
983 position: sticky;
984 top: 0;
985 z-index: 1;
986 padding-bottom: 8px;
987 background: #ece3ce;
988}
989
e6bb7c42 2026-10-25 Pavel Safronov990/* What a power-up changes, in numbers (ui/info.ts changeOf): "Blast 2 → 3". */
991.offer-choice .change,
992.shop-item .change {
993 margin: 2px 0 0;
994 color: var(--accent-dark);
995 font-weight: 800;
996}
997
32dd53cd 2026-10-25 Pavel Safronov998.offer-choice {
999 width: 100%;
ac534e57 2026-10-25 Pavel Safronov1000 margin: 0 0 8px;
32dd53cd 2026-10-25 Pavel Safronov1001 text-align: left;
52d68b59 2026-10-24 Pavel Safronov1002 padding: 8px 10px;
1003 border: 2px solid var(--ink);
1004 border-radius: 12px;
1005 background: #fff8e6;
1006 box-shadow: 0 3px 0 rgb(53 53 65 / 0.35);
1007 color: var(--ink);
1008 font: inherit;
1009 cursor: pointer;
1010}
1011
ac534e57 2026-10-25 Pavel Safronov1012/* In place of the clip, for a power-up with nothing to show on the board (Heart, Coin bag): its
1013 picture, big, in a room-shaped box as wide as a clip. */
1014.offer-still {
32dd53cd 2026-10-25 Pavel Safronov1015 display: block;
ac534e57 2026-10-25 Pavel Safronov1016 box-sizing: border-box;
1017 width: 100%;
1018 max-width: 300px;
1019 aspect-ratio: 9 / 7;
1020 margin: 0 auto;
1021 padding: 18%;
1022 border-radius: 8px;
1023 background: #fff8e6;
1024 object-fit: contain;
32dd53cd 2026-10-25 Pavel Safronov1025}
1026
ac534e57 2026-10-25 Pavel Safronov1027.offer-still[hidden] {
1028 display: none;
1029}
1030
1031/* The selected one. */
1032.offer-choice[aria-pressed='true'] {
1033 border-color: var(--accent-dark);
1034 background: #d9f6e9;
32dd53cd 2026-10-25 Pavel Safronov1035}
1036
52d68b59 2026-10-24 Pavel Safronov1037.offer-choice:active {
1038 transform: translateY(2px);
1039 box-shadow: none;
1040}
1041
a4fab0fb 2026-10-21 Pavel Safronov1042/* Keyboard focus in the dialog: a yellow ring, like the count badges. */
1043#dialog button:focus-visible {
1044 outline: 3px solid #ffc83d;
1045 outline-offset: 2px;
1046}
1047
1048#dialog-buttons {
1049 display: grid;
1050 gap: 8px;
1051 width: 100%;
1052}
1053
1054/* The UI pack's rectangle buttons, stretched by their edges; the darker strip at the bottom is
1055 the button's depth, which it sinks into when pressed. */
1056.ui-button {
1057 display: flex;
1058 align-items: center;
1059 justify-content: center;
1060 gap: 8px;
1061 min-height: 52px;
1062 padding: 0 12px;
1063 border-style: solid;
1064 border-width: 12px 12px 16px;
1065 border-color: transparent;
1066 border-image: url('../assets/kenney-ui/Green/button_rectangle_depth_flat.png') 24 24 32 fill / 12px 12px 16px stretch;
1067 background: none;
1068 color: #fff;
1069 font: 16px 'Kenney Future', ui-rounded, system-ui, sans-serif;
1070 cursor: pointer;
1071}
1072
1073.ui-button.danger {
1074 border-image-source: url('../assets/kenney-ui/Red/button_rectangle_depth_flat.png');
1075}
1076
1077.ui-button.plain {
1078 border-image-source: url('../assets/kenney-ui/Grey/button_rectangle_depth_flat.png');
1079 color: var(--ink);
1080}
1081
e78b7781 2026-10-25 Pavel Safronov1082/* Not now (the shop's Buy without enough coins): greyed, and no press. */
1083.ui-button:disabled {
1084 opacity: 0.45;
1085 cursor: default;
1086}
1087
1088.ui-button:active:not(:disabled) {
a4fab0fb 2026-10-21 Pavel Safronov1089 transform: translateY(3px);
1090}
1091
1092.ui-button img {
1093 height: 20px;
1094}
beb9f2b7 2026-10-21 Pavel Safronov1095
1096/* The new-run dialog's choices. */
1097#dialog-body fieldset.choices {
1098 display: grid;
1099 gap: 8px;
1100 margin: 0 0 10px;
1101 padding: 8px 10px 10px;
1102 border: 2px solid #d9cba8;
1103 border-radius: 10px;
1104 text-align: left;
1105}
1106
1107#dialog-body legend {
1108 padding: 0 4px;
1109 font: 13px 'Kenney Future', ui-rounded, system-ui, sans-serif;
1110}
1111
1112#dialog-body .choices label {
1113 display: flex;
1114 align-items: flex-start;
1115 gap: 8px;
1116 font-size: 14px;
1117 line-height: 1.3;
1118}
1119
1120#dialog-body .choices input {
1121 flex: none;
1122 width: 20px;
1123 height: 20px;
1124 margin: 0;
1125 accent-color: var(--accent);
1126}
1127
1128#dialog-body .choices b {
1129 display: block;
1130}
1131
0580e85e 2026-10-24 Pavel Safronov1132/* The seed field: a text box over its explanation. */
1133#dialog-body .choices label.seed {
1134 flex-direction: column;
1135 font-size: 13px;
1136}
1137
1138#dialog-body .choices label.seed input {
1139 width: 100%;
1140 height: 40px;
1141 box-sizing: border-box;
1142 padding: 0 10px;
1143 border: 2px solid #d9cba8;
1144 border-radius: 8px;
1145 background: #fff;
1146 color: var(--ink);
1147 font: 600 16px ui-rounded, system-ui, sans-serif;
1148}
1149
1150#dialog-body .choices label.seed input::placeholder {
1151 color: #b5aa90;
1152}
1153
beb9f2b7 2026-10-21 Pavel Safronov1154#dialog-body .warning {
1155 color: #b3122e;
1156 font-weight: 700;
1157}
1158
b89399da 2026-11-01 Pavel Safronov1159#dialog-body .channel-box {
1160 margin: 0 0 10px;
1161 padding: 8px 10px;
1162 border: 2px solid var(--ink);
1163 border-radius: 12px;
1164 text-align: center;
1165}
1166
1167#dialog-body .channel-box p {
1168 margin: 0 0 6px;
1169}
1170
1171#dialog-body .channel-box p:last-child {
1172 margin-bottom: 0;
1173}
1174
1175.channel-sub {
1176 display: block;
1177 font-size: 12px;
1178 color: var(--muted);
1179}
1180
1181.channel-switch {
1182 margin: 2px 0 6px;
1183 padding: 6px 12px;
1184 border: 2px solid var(--accent-dark);
1185 border-radius: 14px;
1186 background: var(--accent);
1187 color: #fff;
1188 font: 11px 'Kenney Future', ui-rounded, system-ui, sans-serif;
1189 cursor: pointer;
1190}
1191
beb9f2b7 2026-10-21 Pavel Safronov1192#dialog-body .mode {
1193 font-size: 13px;
1194 text-align: left;
1195}
1196
1197
99869630 2026-10-23 Pavel Safronov1198#dialog-body::-webkit-scrollbar {
1199 display: none;
beb9f2b7 2026-10-21 Pavel Safronov1200}
52d68b59 2026-10-24 Pavel Safronov1201
e6bb7c42 2026-10-25 Pavel Safronov1202/* The view (render/camera.ts): the same 9 × 13 tiles on every screen. Its box is set each frame;
1203 its shadow is the bars round it, over the rest of the board, under the top and action bars. */
1204#view-mask {
1205 position: fixed;
1206 z-index: 1;
1207 box-shadow: 0 0 0 100vmax var(--ink);
1208 pointer-events: none;
1209}
1210
1211#view-mask[hidden] {
1212 display: none;
1213}
1214
52d68b59 2026-10-24 Pavel Safronov1215/* The demo before the game (main.ts): the board alone, a caption panel at the bottom. */
d5e3ad4d 2026-10-24 Pavel Safronov1216body.demo #banner {
52d68b59 2026-10-24 Pavel Safronov1217 display: none;
1218}
1219
e3384c6b 2026-10-24 Pavel Safronov1220/* The top bar is on the TV too, as wide as it (main.ts). */
1221body.demo #hud {
0bfdc8e6 2026-10-25 Pavel Safronov1222 top: var(--demo-title, 0px);
e3384c6b 2026-10-24 Pavel Safronov1223 left: var(--tv-left, 0px);
1224 right: var(--tv-left, 0px);
1225}
1226
0bfdc8e6 2026-10-25 Pavel Safronov1227/* The demo's title, on top, above the TV (user request, 2026-10-25): title, TV, caption. In the
1228 rounded system font, extra bold: Kenney Future's blocky letters (an X like an H) were hard to
1229 read this big (user request, 2026-10-23). */
1230#demo-title {
1231 position: fixed;
1232 top: 0;
1233 left: 0;
1234 right: 0;
1235 z-index: 10;
1236 margin: 0;
1237 padding: calc(env(safe-area-inset-top) + 8px) 16px 6px;
1238 background: var(--ink);
1239 color: #fff;
1240 text-align: center;
1241 font: 800 30px/1.15 ui-rounded, 'SF Pro Rounded', system-ui, -apple-system, sans-serif;
1242 letter-spacing: 0.01em;
1243}
1244
1245#demo-title[hidden] {
1246 display: none;
1247}
1248
e3384c6b 2026-10-24 Pavel Safronov1249/* The top bar's tour in the demo (main.ts): the item it is about, ringed. */
1250.demo-spot {
1251 border-radius: 10px;
1252 outline: 3px solid #ffc83d;
1253 outline-offset: 2px;
1254}
1255
52d68b59 2026-10-24 Pavel Safronov1256#demo {
1257 position: fixed;
1258 left: 0;
1259 right: 0;
1260 bottom: 0;
e3384c6b 2026-10-24 Pavel Safronov1261 /* Above the CRT screen and the demo's dialog (below): the words are not on the TV. */
1262 z-index: 10;
52d68b59 2026-10-24 Pavel Safronov1263 padding: 12px 16px calc(env(safe-area-inset-bottom) + 16px);
1264 background: var(--ink);
1265 color: #fff;
1266 text-align: center;
1267}
1268
1269#demo[hidden] {
1270 display: none;
1271}
1272
1273/* The words, with a scene's icon beside them (not in the line, which would space it unevenly). */
1274#demo-caption {
1275 display: flex;
1276 align-items: center;
1277 justify-content: center;
1278 gap: 10px;
1279 min-height: 3.9em;
1280 margin: 0;
1281 font-size: 17px;
1282 line-height: 1.3;
1283}
1284
1285/* A scene about one charge or power-up shows its icon before the words. */
1286#demo .demo-icon {
1287 flex: none;
1288 width: 34px;
1289 height: 34px;
1290 /* A light rim, so a dark sprite (a bomb) stands out on the dark panel, as in the top bar. */
1291 filter: drop-shadow(0 0 1.5px rgb(255 255 255 / 0.7));
1292}
1293
1294/* One-colour ink icons (the padlock) turn white on the dark panel. */
1295#demo .demo-icon.mono {
1296 filter: brightness(0) invert(1);
1297}
1298
33a0256b 2026-10-24 Pavel Safronov1299/* Which scene this is, and the buttons to go through them (main.ts): ◀ n / N ▶, and ↺ back to
1300 the first, set apart. They don't end the demo. */
1301#demo .demo-nav {
1302 display: flex;
1303 align-items: center;
1304 justify-content: center;
1305 gap: 10px;
1306 margin-top: 8px;
1307}
1308
1309#demo-count {
1310 min-width: 4.5em;
1311 font-variant-numeric: tabular-nums;
1312 font-weight: 700;
1313}
1314
1315#demo .demo-nav button {
1316 width: 36px;
1317 height: 36px;
1318 border: 0;
1319 border-radius: 50%;
1320 background: rgb(255 255 255 / 0.14) center / 18px no-repeat;
1321 cursor: pointer;
1322}
1323
1324#demo #demo-prev,
1325#demo #demo-next {
1326 background-image: url('../assets/kenney-icons/White/arrowRight.png');
1327}
1328
1329#demo-prev {
1330 transform: scaleX(-1);
1331}
1332
1333#demo #demo-restart {
bb3823d2 2026-10-31 Pavel Safronov1334 margin-left: 0;
33a0256b 2026-10-24 Pavel Safronov1335 background-image: url('../assets/kenney-ui/Extra/icon_repeat_light.png');
1336 background-size: 22px;
1337}
1338
bb3823d2 2026-10-31 Pavel Safronov1339/* Shuffle (main.ts): lit while on. */
1340#demo #demo-shuffle {
1341 margin-left: 14px;
1342 background-image: url('../assets/kenney-board-icons/dice_3D_detailed.png');
1343 background-size: 24px;
1344 opacity: 0.55;
1345}
1346
1347#demo #demo-shuffle[aria-pressed='true'] {
1348 box-shadow: 0 0 0 3px #ffc83d;
1349 opacity: 1;
1350}
1351
805e7a9f 2026-11-03 Pavel Safronov1352/* The pad's button for each demo control (main.ts `syncDemoGlyphs`), on its corner, only while the demo is drawn for a controller. */
1353#demo .demo-nav button {
1354 position: relative;
1355}
1356
1357#demo .demo-nav .pad-glyph {
1358 display: none;
1359 position: absolute;
1360 right: -8px;
1361 bottom: -8px;
1362 width: 20px;
1363 height: 20px;
1364 pointer-events: none;
1365}
1366
1367#demo.pad .demo-nav .pad-glyph {
1368 display: block;
1369}
1370
1371/* The previous-scene button is drawn mirrored; its picture is not, and it still sits on the lower right. */
1372#demo #demo-prev .pad-glyph {
1373 right: auto;
1374 left: -8px;
1375 transform: scaleX(-1);
1376}
1377
92cc4ee3 2026-11-02 Pavel Safronov1378/* The input button (main.ts, Q61): a pill under the scene buttons, naming what the demo is shown for. */
1379#demo .demo-input-row {
1380 margin-top: 6px;
1381}
1382
1383#demo #demo-input {
1384 width: auto;
1385 height: 32px;
1386 padding: 0 14px;
1387 border-radius: 16px;
1388 color: inherit;
1389 font: inherit;
1390 font-size: 0.9rem;
1391 font-weight: 700;
1392}
1393
1394/* The demo's "how to play" line, under the title (it is inside #demo-title, so the TV's room already counts it). */
1395#demo-title .demo-hint {
1396 display: block;
1397 margin-top: 2px;
52d68b59 2026-10-24 Pavel Safronov1398 color: #ffc83d;
92cc4ee3 2026-11-02 Pavel Safronov1399 font-size: 16px;
1400 line-height: 24px;
52d68b59 2026-10-24 Pavel Safronov1401 font-weight: 800;
1402 animation: demo-hint 1.6s ease-in-out infinite;
1403}
1404
92cc4ee3 2026-11-02 Pavel Safronov1405#demo-title .pad-glyph {
1406 width: 22px;
1407 height: 22px;
1408 vertical-align: middle;
1409}
1410
52d68b59 2026-10-24 Pavel Safronov1411@keyframes demo-hint {
1412 50% {
1413 opacity: 0.45;
1414 }
1415}
1416
1417@media (prefers-reduced-motion: reduce) {
92cc4ee3 2026-11-02 Pavel Safronov1418 #demo-title .demo-hint {
36c5d6a2 2026-10-24 Pavel Safronov1419 animation: none;
1420 }
52d68b59 2026-10-24 Pavel Safronov1421}
1422
36c5d6a2 2026-10-24 Pavel Safronov1423/* The demo's action bar (main.ts): the real one, so the finger can press its buttons, but as a
1424 tray right under the board (main.ts places and sizes it), apart from the caption panel. */
1425body.demo #actions {
1426 right: auto;
1427 bottom: auto;
1428 box-sizing: border-box;
1429 padding: 8px 8px 4px;
1430 border-radius: 14px;
52d68b59 2026-10-24 Pavel Safronov1431}
1432
36c5d6a2 2026-10-24 Pavel Safronov1433/* A button the demo's finger is pressing: as the real press (:active above). */
1434#actions > button.pressed:not(:disabled) {
1435 transform: translateY(4px);
1436 box-shadow: none;
1437}
1438
1439/* The demo's finger (Kenney's pointing hand). Its box sits on the point it touches; the image
e3384c6b 2026-10-24 Pavel Safronov1440 is shifted so the fingertip, not the corner, is there. In front of everything on the CRT,
1441 glass included: it is a real finger on the screen. */
36c5d6a2 2026-10-24 Pavel Safronov1442#demo-finger {
1443 position: fixed;
1444 left: 0;
1445 top: 0;
e3384c6b 2026-10-24 Pavel Safronov1446 z-index: 9;
36c5d6a2 2026-10-24 Pavel Safronov1447 pointer-events: none;
1448 transition: opacity 0.3s;
1449}
1450
1451#demo-finger[hidden] {
1452 display: none;
1453}
1454
1455#demo-finger.glide {
1456 transition: transform 0.4s ease-in-out, opacity 0.3s;
1457}
1458
1459#demo-finger.away {
1460 opacity: 0;
1461}
1462
1463#demo-finger img {
1464 display: block;
1465 width: 64px;
1466 height: 64px;
1467 margin: -14px 0 0 -11px;
1468 transform-origin: 11px 14px;
1469 transition: transform 0.12s;
1470 filter: drop-shadow(0 2px 3px rgb(0 0 0 / 0.35));
1471}
1472
92cc4ee3 2026-11-02 Pavel Safronov1473/* The demo's virtual controller (ui/padOverlay.ts): a line drawing of a pad (assets/controller-art/,
1474 CC BY 4.0) with Kenney's button pictures on the drawn buttons. Every size comes from --pad-size, the pad's width; the
1475 pictures' places are set inline by padOverlay.ts. The pictures lie under the drawing, so its
1476 lines stay on top; a pressed button is lit (.on). */
1477.pad-overlay {
1478 --pad-size: 220px;
1479 position: relative;
1480 width: var(--pad-size);
1481 flex: none;
1482 pointer-events: none;
1483}
1484
1485/* The drawing is thin white-grey line art: a dark halo and a glow make it read on the dark demo. */
1486.pad-overlay > .pad-body {
1487 position: absolute;
1488 inset: 0;
1489 width: 100%;
1490 height: 100%;
1491 opacity: 0.95;
1492 filter: drop-shadow(0 0 1px #fff) drop-shadow(0 0 1.5px rgb(0 0 0 / 0.9));
1493}
1494
1495.pad-overlay.compact {
1496 --pad-size: 120px;
1497}
1498
1499.pad-overlay[hidden] {
1500 display: none;
1501}
1502
1503/* In the demo's panel, under the words; in a clip, over the board's lower corner. */
1504#demo > .pad-overlay {
1505 margin: 4px auto 0;
1506}
1507
1508.clip-frame > .pad-overlay {
1509 position: absolute;
1510 right: 4px;
1511 bottom: 4px;
1512 z-index: 2;
1513}
1514
1515/* Hold to confirm (ui/padHold.ts, holdRing.ts, padOverlay.ts): a ring round a face button that fills
1516 clockwise from 12 o'clock as the button is held (--p, 0..1) and turns green when full. The
1517 progress is the point, so it stays under reduced motion; nothing here animates. */
1518.hold-ring {
1519 position: absolute;
1520 aspect-ratio: 1;
1521 border-radius: 50%;
1522 pointer-events: none;
1523 background: conic-gradient(#ffd23f calc(var(--p, 0) * 360deg), rgb(255 255 255 / 0.28) 0);
1524 -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
1525 mask: radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 4px));
1526 filter: drop-shadow(0 0 2px rgb(0 0 0 / 0.6));
1527}
1528
1529.hold-ring.full {
1530 background: #5cff9b;
1531}
1532
1533.hold-ring[hidden] {
1534 display: none;
1535}
1536
1537.hold-ring.floating {
1538 position: fixed;
1539 z-index: 1000;
1540}
1541
1542/* On the demo's controller: the same place as the button (padOverlay.ts sets it), a size bigger. */
1543.pad-overlay .hold-ring {
1544 transform: scale(1.4);
1545}
1546
1547/* The controller's pictures inside the demo's words. */
1548#demo-caption .pad-glyph {
1549 width: 28px;
1550 height: 28px;
1551 vertical-align: middle;
1552}
1553
1554/* The white pictures (d-pad, stick, shoulders, Start) are white at rest: the d-pad and the stick
1555 show a press by the red arm in their own picture, the others by a scale and a glow. The face
1556 buttons are grey at rest and take their colour only while pressed (Pavel, 2026-11-02: a lit one
1557 was hard to tell from an idle one when both were coloured). */
1558.pad-overlay .pad-part {
1559 position: absolute;
1560 height: auto;
1561 aspect-ratio: 1;
1562 transition: opacity 0.1s, transform 0.1s, filter 0.1s;
1563}
1564
1565.pad-overlay .pad-part.l1.on,
1566.pad-overlay .pad-part.r1.on,
1567.pad-overlay .pad-part.start.on {
1568 transform: scale(1.15);
1569 filter: drop-shadow(0 0 4px #ffd23f);
1570}
1571
1572/* A face button's picture is clipped to its drawn circle; lit, it is drawn bigger inside the same circle (the clip grows with the scale, so it is shrunk by as much). */
1573.pad-overlay .pad-part.face {
1574 opacity: 0.85;
1575 filter: grayscale(1) brightness(1.7);
1576 clip-path: circle(var(--clip, 50%));
1577}
1578
1579.pad-overlay .pad-part.face.on {
1580 opacity: 1;
1581 filter: grayscale(0) brightness(1.15) saturate(1.2);
1582 transform: scale(1.15);
1583 clip-path: circle(calc(var(--clip, 50%) / 1.15));
1584}
1585
1586@media (prefers-reduced-motion: reduce) {
1587 .pad-overlay .pad-part {
1588 transition: none;
1589 }
1590
1591 .pad-overlay .pad-part.on {
1592 transform: none;
1593 }
1594
1595 .pad-overlay .pad-part.face.on {
1596 clip-path: circle(var(--clip, 50%));
1597 }
1598}
1599
1600/* The keyboard demo's key overlay (ui/keyOverlay.ts): Kenney's key pictures on a grid, white at rest, a
1601 pressed key lit (.on). Left: Esc ? Home over Alt Shift Enter; middle: the arrows as an inverted T; right: M B V over Space. */
1602.key-overlay {
1603 --key: min(34px, calc((100vw - 64px) / 10.5));
1604 display: grid;
1605 grid-template-columns: repeat(10, var(--key));
1606 grid-template-rows: repeat(2, var(--key));
1607 gap: 4px;
1608 justify-content: center;
1609 flex: none;
1610 pointer-events: none;
1611}
1612
1613.key-overlay.compact {
1614 --key: 22px;
1615}
1616
1617.key-overlay[hidden] {
1618 display: none;
1619}
1620
1621#demo > .key-overlay {
1622 margin: 10px auto 0;
1623}
1624
1625.key-overlay .key-part {
1626 width: 100%;
1627 height: 100%;
1628 object-fit: contain;
1629 border-radius: 22%;
1630 transition: transform 0.1s, background-color 0.1s, filter 0.1s;
1631}
1632
1633.key-overlay .key-part.esc { grid-area: 1 / 1; }
1634.key-overlay .key-part.inspect { grid-area: 1 / 2; }
1635.key-overlay .key-part.home { grid-area: 1 / 3; }
1636.key-overlay .key-part.alt { grid-area: 2 / 1; }
1637.key-overlay .key-part.shift { grid-area: 2 / 2; }
1638.key-overlay .key-part.enter { grid-area: 2 / 3; }
1639.key-overlay .key-part.up { grid-area: 1 / 5; }
1640.key-overlay .key-part.left { grid-area: 2 / 4; }
1641.key-overlay .key-part.down { grid-area: 2 / 5; }
1642.key-overlay .key-part.right { grid-area: 2 / 6; }
1643.key-overlay .key-part.move { grid-area: 1 / 8; }
1644.key-overlay .key-part.bomb { grid-area: 1 / 9; }
1645.key-overlay .key-part.charge { grid-area: 1 / 10; }
1646.key-overlay .key-part.wait { grid-area: 2 / 8 / 3 / 11; }
1647
1648.key-overlay .key-part.on {
1649 background-color: #ffd23f;
1650 transform: scale(1.12);
1651 filter: drop-shadow(0 0 4px #ffd23f);
1652}
1653
1654@media (prefers-reduced-motion: reduce) {
1655 .key-overlay .key-part {
1656 transition: none;
1657 }
1658
1659 .key-overlay .key-part.on {
1660 transform: none;
1661 }
1662}
1663
58f1e700 2026-10-24 Pavel Safronov1664/* The demo's "You" tag over the player (main.ts places it on the top of their tile, following
1665 them as they move): a green pill pointing down at them. Above the finger, so a tap on the
1666 tile over the player doesn't hide it. */
1667#demo-you {
1668 position: fixed;
1669 left: 0;
1670 top: 0;
1671 z-index: 5;
1672 pointer-events: none;
1673}
1674
1675#demo-you[hidden] {
1676 display: none;
1677}
1678
1679#demo-you::before {
1680 content: 'You';
1681 position: absolute;
1682 bottom: 6px;
1683 left: 0;
1684 transform: translateX(-50%);
1685 padding: 2px 9px 3px;
1686 border: 2px solid var(--ink);
1687 border-radius: 999px;
1688 background: var(--accent);
1689 /* A white halo round the dark rim, so it stands out on the grey stone of the top wall too. */
1690 box-shadow: 0 0 0 2px #fff;
1691 color: #fff;
1692 font: 800 13px/1.1 ui-rounded, 'SF Pro Rounded', system-ui, -apple-system, sans-serif;
1693 white-space: nowrap;
1694}
1695
1696/* Its point, a small triangle under the pill. */
1697#demo-you::after {
1698 content: '';
1699 position: absolute;
1700 bottom: 0;
1701 left: -6px;
1702 border: 6px solid transparent;
1703 border-bottom: 0;
1704 border-top-color: var(--ink);
1705}
1706
36c5d6a2 2026-10-24 Pavel Safronov1707/* Pressing: the hand sinks toward the screen. */
1708#demo-finger.down img {
1709 transform: scale(0.86);
1710}
1711
92cc4ee3 2026-11-02 Pavel Safronov1712/* The mouse demo's pointer (Kenney's RPG sword cursor, tip at the top left): the box sits on its tip. */
1713#demo-finger.mouse img {
1714 width: 44px;
1715 height: 48px;
1716 margin: -3px 0 0 -3px;
1717 transform-origin: 3px 3px;
1718}
1719
36c5d6a2 2026-10-24 Pavel Safronov1720/* Where the finger taps, a ripple spreads; while it holds, a ring closes in (main.ts sets its
e3384c6b 2026-10-24 Pavel Safronov1721 time, --hold, to the long press's). Each sits on the point, drawn by its ::before; on the
1722 screen, so above its dialog and under the glass. */
36c5d6a2 2026-10-24 Pavel Safronov1723.demo-ripple,
1724.demo-ring {
1725 position: fixed;
1726 left: 0;
1727 top: 0;
e3384c6b 2026-10-24 Pavel Safronov1728 z-index: 7;
36c5d6a2 2026-10-24 Pavel Safronov1729 pointer-events: none;
1730}
1731
1732.demo-ripple::before,
1733.demo-ring::before {
1734 content: '';
1735 position: absolute;
1736 width: 48px;
1737 height: 48px;
1738 margin: -24px 0 0 -24px;
1739 border-radius: 50%;
1740 box-sizing: border-box;
1741}
1742
1743.demo-ripple::before {
1744 /* White with a thin dark rim, to show on the pale floor and the dark bar alike. */
1745 border: 4px solid #fff;
1746 box-shadow: 0 0 0 2px rgb(53 53 65 / 0.55), inset 0 0 0 2px rgb(53 53 65 / 0.55);
1747 animation: demo-ripple 0.6s ease-out forwards;
1748}
1749
1750.demo-ring::before {
1751 border: 5px solid #ffc83d;
1752 box-shadow: 0 0 0 2px rgb(53 53 65 / 0.55);
1753 animation: demo-ring var(--hold, 0.45s) linear forwards;
1754}
1755
1756@keyframes demo-ripple {
1757 from {
1758 transform: scale(0.3);
1759 opacity: 1;
1760 }
1761 to {
1762 transform: scale(1.5);
1763 opacity: 0;
1764 }
1765}
1766
1767@keyframes demo-ring {
1768 from {
1769 transform: scale(1.8);
1770 opacity: 0.2;
1771 }
1772 to {
1773 transform: scale(0.8);
1774 opacity: 1;
52d68b59 2026-10-24 Pavel Safronov1775 }
1776}
0580e85e 2026-10-24 Pavel Safronov1777
1778/* The high-score board (ui/scores.ts): a card per run, a Developer run's with a red border. */
1779#dialog-body .scores-intro {
1780 font-size: 13px;
1781 color: var(--muted);
1782}
1783
1784#dialog-body ol.scores {
1785 display: grid;
1786 gap: 8px;
1787 margin: 0;
1788 padding: 0;
1789 list-style: none;
1790 text-align: left;
1791}
1792
1793#dialog-body .score {
1794 padding: 8px 10px;
1795 border: 2px solid #d9cba8;
1796 border-radius: 10px;
1797 background: #fff8e6;
1798}
1799
1800#dialog-body .score.developer {
1801 border: 3px solid #e8253f;
1802}
1803
1804#dialog-body .score p {
1805 margin: 2px 0 0;
1806 font-size: 13px;
1807}
1808
1809#dialog-body .score-head {
1810 display: flex;
1811 gap: 8px;
1812 align-items: baseline;
1813 font-weight: 800;
1814}
1815
1816#dialog-body .score-head span:last-child {
1817 margin-left: auto;
1818}
1819
1820#dialog-body .score-powers {
1821 display: flex;
1822 flex-wrap: wrap;
1823 align-items: center;
1824 gap: 2px;
1825}
1826
1827#dialog-body .score-powers img {
1828 width: 22px;
1829 height: 22px;
1830}
1831
1832#dialog-body .score-powers span {
1833 margin-left: 4px;
1834 color: var(--muted);
1835}
1836
1837#dialog-body .score-meta {
1838 color: var(--muted);
1839}
1840
1841#dialog-body .score-meta b {
1842 color: #e8253f;
1843}
e3384c6b 2026-10-24 Pavel Safronov1844
1845/* The demo's dialog (dialog.ts `setDialogInDemo`): not a modal over the page, but over the
1846 demo's screen only — centred above the caption panel, which stays undimmed (it is above it). */
1847#dialog.in-demo {
1848 position: fixed;
1849 inset: 0 0 var(--demo-panel, 0px);
1850 z-index: 6;
1851 height: fit-content;
1852 margin: auto;
1853 box-shadow: 0 0 0 100vmax rgb(53 53 65 / 0.6);
1854 animation: fade-in 0.18s ease-out;
1855}
1856
1857/* The demo's CRT (main.ts): over the screen part — board, top bar, buttons — under the caption
1858 panel. Scan lines a few pixels apart, a vignette, the tube's rounded glass with a faint glare,
1859 and the bezel round it; a slight flicker. */
1860#demo-crt {
1861 position: fixed;
0bfdc8e6 2026-10-25 Pavel Safronov1862 inset: var(--demo-title, 0px) var(--tv-left, 0px) var(--demo-panel, 0px);
e3384c6b 2026-10-24 Pavel Safronov1863 z-index: 8;
1864 overflow: hidden;
1865 /* Black bars either side of a TV narrower than the screen (main.ts: at most 4:3). */
1866 box-shadow: 0 0 0 100vmax #000;
1867 pointer-events: none;
1868 background:
1869 repeating-linear-gradient(to bottom, rgb(0 0 0 / 0.18) 0 1px, transparent 1px 3px),
1870 radial-gradient(ellipse at center, transparent 58%, rgb(0 0 0 / 0.42) 100%);
1871 animation: crt-flicker 0.15s steps(2) infinite;
1872}
1873
1874#demo-crt[hidden] {
1875 display: none;
1876}
1877
1878/* The bezel: everything outside the glass's rounded corners, in the dark plastic of an old set,
1879 with the glass's edge shaded in. Above the rolling bar and lines below. */
1880#demo-crt::before {
1881 content: '';
1882 position: absolute;
1883 z-index: 1;
1884 inset: 6px;
1885 border-radius: 28px / 22px;
1886 box-shadow:
1887 0 0 0 60px #1e1e27,
1888 inset 0 0 24px 4px rgb(0 0 0 / 0.55);
1889}
1890
1891/* A faint glare on the glass, top left. */
1892#demo-crt::after {
1893 content: '';
1894 position: absolute;
1895 inset: 6px;
1896 border-radius: 28px / 22px;
1897 background: linear-gradient(150deg, rgb(255 255 255 / 0.1), transparent 35%);
1898}
1899
1900@keyframes crt-flicker {
1901 50% {
1902 opacity: 0.93;
1903 }
1904}
1905
1906/* What moves on the glass (user request, 2026-10-24): a soft dark bar rolling slowly down, as a
1907 TV filmed by a camera shows, and two thin bright lines sweeping down faster, out of step. */
1908#demo-crt > i {
1909 position: absolute;
1910 left: 0;
1911 right: 0;
1912 top: 0;
1913}
1914
1915.crt-roll {
1916 height: 28%;
1917 background: linear-gradient(transparent, rgb(0 0 0 / 0.14) 45%, rgb(0 0 0 / 0.14) 55%, transparent);
1918 animation: crt-sweep 7s linear infinite;
1919}
1920
1921.crt-line {
1922 height: 4px;
1923 background: linear-gradient(transparent, rgb(255 255 255 / 0.28), transparent);
1924 animation: crt-sweep 3.4s linear infinite;
1925}
1926
1927.crt-line + .crt-line {
1928 animation-duration: 5.3s;
1929 animation-delay: -2.1s;
1930}
1931
1932/* From just above the screen to just below it (the screen is the parent, 100% tall). */
1933@keyframes crt-sweep {
1934 from {
1935 top: -30%;
1936 }
1937 to {
1938 top: 100%;
1939 }
1940}
1941
1942/* Interference (main.ts `vacuum`): snow over the whole screen, the HTML parts included (the
1943 shader tears and snows only the board), jumping every frame. */
1944#demo-crt.interference {
1945 background:
1946 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1.4 -.55'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"),
1947 repeating-linear-gradient(to bottom, rgb(0 0 0 / 0.18) 0 1px, transparent 1px 3px),
1948 radial-gradient(ellipse at center, transparent 58%, rgb(0 0 0 / 0.42) 100%);
1949 animation: crt-snow 0.2s steps(4) infinite;
1950}
1951
1952@keyframes crt-snow {
1953 25% {
1954 background-position: 37px 91px, 0 0, 0 0;
1955 }
1956 50% {
1957 background-position: 113px 23px, 0 0, 0 0;
1958 }
1959 75% {
1960 background-position: 71px 140px, 0 0, 0 0;
1961 }
1962}
1963
1964@media (prefers-reduced-motion: reduce) {
1965 #demo-crt,
1966 #demo-crt.interference {
1967 animation: none;
1968 }
1969
1970 #demo-crt > i {
1971 display: none;
1972 }
1973}
33a0256b 2026-10-24 Pavel Safronov1974
32dd53cd 2026-10-25 Pavel Safronov1975/* A clip (ui/clip.ts): the scene, drawn by the game, above a dialog's words or on the banner. */
1976canvas.clip {
33a0256b 2026-10-24 Pavel Safronov1977 display: block;
1978 width: 100%;
1979 max-width: 300px;
1980 height: auto;
32dd53cd 2026-10-25 Pavel Safronov1981 /* A room's shape (9 × 7) while its first scene is being made; then the scene's own. */
1982 aspect-ratio: auto 9 / 7;
33a0256b 2026-10-24 Pavel Safronov1983 margin: 0 auto;
1984 border-radius: 8px;
1985}
32dd53cd 2026-10-25 Pavel Safronov1986
ac534e57 2026-10-25 Pavel Safronov1987/* A clip's canvas that isn't wanted (the banner's when nothing is replayed, the offer's for Heart). */
1988canvas.clip[hidden] {
1989 display: none;
1990}
1991
32dd53cd 2026-10-25 Pavel Safronov1992/* A clip with the finger (ui/clip.ts): the canvas in a frame, the finger and its marks over it. */
1993.clip-frame {
1994 position: relative;
1995 max-width: 300px;
1996 margin: 0 auto;
1997 overflow: hidden;
1998 border-radius: 8px;
1999}
2000
2001.clip-frame canvas.clip {
2002 max-width: none;
2003}
2004
2005.clip-finger {
2006 position: absolute;
2007 left: 0;
2008 top: 0;
2009 z-index: 2;
2010 pointer-events: none;
2011 transition: transform 0.3s ease-in-out, opacity 0.3s;
2012}
2013
2014.clip-finger.away {
2015 opacity: 0;
2016}
2017
2018.clip-finger img {
2019 display: block;
2020 width: 44px;
2021 height: 44px;
2022 margin: -10px 0 0 -8px;
2023 transform-origin: 8px 10px;
2024 transition: transform 0.12s;
2025 filter: drop-shadow(0 2px 3px rgb(0 0 0 / 0.35));
2026}
2027
2028.clip-finger.down img {
2029 transform: scale(0.86);
2030}
2031
2032/* The demo's ripple and ring, inside the clip's frame instead of on the page. */
b89399da 2026-11-01 Pavel Safronov2033.clip-frame .clip-choice {
2034 position: absolute;
2035 left: 6%;
2036 right: 6%;
2037 bottom: 6%;
2038 padding: 6px 8px;
2039 border: 2px solid var(--ink);
2040 border-radius: 12px;
2041 background: #ece3cc;
2042 color: var(--ink);
2043 font-size: 11px;
2044 text-align: center;
2045 pointer-events: none;
2046}
2047
2048.clip-frame .clip-choice div {
2049 display: flex;
2050 flex-wrap: wrap;
2051 justify-content: center;
2052 gap: 4px;
2053 margin-top: 4px;
2054}
2055
2056.clip-frame .clip-choice span {
2057 display: inline-flex;
2058 align-items: center;
2059 gap: 3px;
2060 padding: 2px 6px;
2061 border: 2px solid transparent;
2062 border-radius: 10px;
2063}
2064
2065.clip-frame .clip-choice span.picked {
2066 border-color: var(--accent-dark);
2067 background: var(--accent);
2068 color: #fff;
2069}
2070
2071.clip-frame .clip-choice img {
2072 width: 18px;
2073 height: 18px;
2074}
2075
32dd53cd 2026-10-25 Pavel Safronov2076.clip-frame .demo-ripple,
2077.clip-frame .demo-ring {
2078 position: absolute;
2079 z-index: 1;
2080}
2081
2082/* The "Ouch!" banner's replay of the player's last turns (main.ts `rememberTurn`). */
2083#banner .clip-frame {
805e7a9f 2026-11-03 Pavel Safronov2084 width: min(300px, 100%);
32dd53cd 2026-10-25 Pavel Safronov2085}
e78b7781 2026-10-25 Pavel Safronov2086
2087/* The shop (ui/shop.ts): the coins on top, then the clip and the items as in the gem's offer, each
2088 with its price on the right of its name; a sold one dimmed. */
2089#dialog-body .shop-coins {
2090 display: flex;
2091 align-items: center;
2092 justify-content: center;
2093 gap: 6px;
e6bb7c42 2026-10-25 Pavel Safronov2094 box-sizing: border-box;
2095 height: 36px;
2096 margin: 0;
2097 /* Stays at the top while the items scroll under it (user report, 2026-10-25: scrolling hid the
2098 coins), on the parchment's colour so nothing shows through. */
2099 position: sticky;
2100 top: 0;
2101 z-index: 1;
2102 padding: 4px 0;
2103 background: #ece3cc;
e78b7781 2026-10-25 Pavel Safronov2104}
2105
2106#dialog-body .shop-coins img,
2107.shop-item .price img {
2108 width: 20px;
2109 height: 20px;
2110}
2111
2112.shop-item strong {
2113 display: flex;
2114 align-items: center;
2115 justify-content: space-between;
2116 gap: 8px;
2117}
2118
2119.shop-item .price {
2120 display: inline-flex;
2121 align-items: center;
2122 gap: 3px;
2123 font-weight: 800;
2124}
2125
2126.shop-item.sold {
2127 opacity: 0.5;
2128 cursor: default;
2129}
2130
0bfdc8e6 2026-10-25 Pavel Safronov2131/* The menu (user request, 2026-10-25: it was "very packed"): Resume and New side by side, then
805e7a9f 2026-11-03 Pavel Safronov2132 Almanac, Scores and Changes in a row of small buttons, then Demo and Bug (six columns: the big
2133 buttons take three each, the first three small ones two each, the last two small ones three each). */
0bfdc8e6 2026-10-25 Pavel Safronov2134#dialog .panel.menu #dialog-buttons {
805e7a9f 2026-11-03 Pavel Safronov2135 grid-template-columns: repeat(6, minmax(0, 1fr));
0bfdc8e6 2026-10-25 Pavel Safronov2136}
2137
2138#dialog .panel.menu .ui-button {
805e7a9f 2026-11-03 Pavel Safronov2139 grid-column: span 3;
0bfdc8e6 2026-10-25 Pavel Safronov2140 box-sizing: border-box;
2141 min-width: 0;
2142 min-height: 46px;
2143 white-space: nowrap;
2144}
2145
2146#dialog .panel.menu .ui-button.plain {
805e7a9f 2026-11-03 Pavel Safronov2147 grid-column: span 2;
0bfdc8e6 2026-10-25 Pavel Safronov2148 min-height: 40px;
2149 padding: 0;
805e7a9f 2026-11-03 Pavel Safronov2150 /* Thinner edges than a full-size button, so a word as long as ALMANAC fits a third of the row on a phone. */
0bfdc8e6 2026-10-25 Pavel Safronov2151 border-width: 8px 6px 12px;
2152 border-image-width: 8px 6px 12px;
2153 font-size: 10px;
2154}
2155
805e7a9f 2026-11-03 Pavel Safronov2156#dialog .panel.menu .ui-button.plain:nth-last-child(-n + 2) {
2157 grid-column: span 3;
2158}
2159
e6bb7c42 2026-10-25 Pavel Safronov2160/* In the shop the clip sticks just under the coins, which stick at the very top. */
2161#dialog .panel.shop .offer-clip {
2162 top: 36px;
2163}
2164
e78b7781 2026-10-25 Pavel Safronov2165/* The shop's buttons: Restock and Next level side by side, one short row. The selected item's own
2166 Buy button (smaller; only on the selected card), and a smaller clip, so the items have room. */
2167#dialog .panel.shop #dialog-buttons {
2168 grid-template-columns: 1fr 1fr;
2169}
2170
2171#dialog .panel.shop .ui-button {
2172 min-height: 44px;
2173 font-size: 13px;
2174 white-space: nowrap;
2175}
2176
2177#dialog .panel.shop .offer-clip .clip-frame,
2178#dialog .panel.shop .offer-clip .offer-still {
2179 max-width: 200px;
2180}
2181
2182.shop-item {
2183 box-sizing: border-box;
2184 cursor: pointer;
2185}
2186
2187.shop-item .buy {
2188 display: none;
2189 width: 100%;
2190 margin-top: 6px;
2191}
2192
2193#dialog .panel.shop .shop-item .buy {
2194 min-height: 38px;
2195 font-size: 13px;
2196}
2197
2198.shop-item[aria-pressed='true'] .buy {
2199 display: flex;
2200}
0b63d158 2026-10-25 Pavel Safronov2201
2202/* The frame-rate meter (ui/fps.ts): with ?fps. */
2203#fps {
2204 position: fixed;
2205 left: 4px;
2206 bottom: 4px;
2207 z-index: 1000;
2208 padding: 2px 6px;
2209 border-radius: 4px;
2210 background: rgb(0 0 0 / 0.65);
2211 color: #fff;
2212 font: 11px/1.3 ui-monospace, monospace;
2213 white-space: pre;
2214 pointer-events: none;
2215}
5607bea2 2026-10-31 Pavel Safronov2216
2217/* The changes dialog (ui/whatsnew.ts): a heading per release, each change tagged with its kind and date. */
2218#dialog-body .release-head {
2219 margin: 14px 0 2px;
2220 font-size: 16px;
2221}
2222
2223#dialog-body .release-head small {
2224 color: var(--muted);
2225 font-size: 11px;
2226 font-weight: normal;
2227}
2228
2229#dialog-body .almanac-entry p.change-kind {
2230 font-size: 11px;
2231 color: var(--muted);
2232}
b8849d36 2026-11-01 Pavel Safronov2233
2234/* The bar of a scene's turns (ui/timeline.ts): filling as it plays, with a mark where its moment
2235 is, lit once that has passed. In the demo's panel and under each dialog's clip. */
2236.timeline {
2237 position: relative;
2238 height: 6px;
2239 margin: 8px auto 0;
2240 max-width: 260px;
2241 border-radius: 3px;
2242 background: rgb(128 128 128 / 0.3);
2243}
2244
2245.timeline i {
2246 display: block;
2247 height: 100%;
2248 border-radius: 3px;
2249 background: var(--accent);
2250 transition: width 0.5s linear;
2251}
2252
2253.timeline b {
2254 position: absolute;
2255 top: -4px;
2256 width: 4px;
2257 height: 14px;
2258 margin-left: -2px;
2259 border-radius: 2px;
2260 background: #ffc83d;
2261 box-shadow: 0 0 0 1px rgb(53 53 65 / 0.7);
2262 opacity: 0.8;
2263}
2264
2265.timeline b.passed {
2266 opacity: 1;
2267}
2268
92cc4ee3 2026-11-02 Pavel Safronov2269/* The demo's bar sits on the TV's bottom edge, inside the glass, above the caption panel (user
2270 request, 2026-11-02; it was under the controller in the panel). Over the CRT, under the panel. */
2271#demo-time {
2272 position: fixed;
2273 left: calc(var(--tv-left, 0px) + 18px);
2274 right: calc(var(--tv-left, 0px) + 18px);
2275 bottom: calc(var(--demo-panel, 0px) + 12px);
2276 z-index: 9;
2277 max-width: none;
2278 margin: 0;
2279 pointer-events: none;
2280}
2281
2282body:not(.demo) #demo-time {
2283 display: none;
b8849d36 2026-11-01 Pavel Safronov2284}
ebcd0a8f 2026-11-02 Pavel Safronov2285
2286/* The controller in dialogs and the banner (ui/padDialog.ts): the pictures of the face buttons on
2287 the buttons they press, and the selector over the element the d-pad is on. Only while the pad
2288 is in use (body.pad). */
2289#dialog .pad-glyph,
2290#banner .pad-glyph {
2291 display: none;
2292 width: 22px;
2293 height: 22px;
2294 flex: none;
2295 pointer-events: none;
2296}
2297
2298body.pad #dialog .pad-glyph,
2299body.pad #banner .pad-glyph {
2300 display: inline-block;
2301 vertical-align: middle;
2302}
2303
2304#banner .pad-glyph {
2305 margin-left: 8px;
2306}
2307
92cc4ee3 2026-11-02 Pavel Safronov2308/* The selected item's confirm picture (selector mode; the hold ring goes round it), and the almanac's
2309 LB / RB pictures on the tabs they go to. */
2310#dialog .sel-glyph {
2311 margin-left: 8px;
2312}
2313
805e7a9f 2026-11-03 Pavel Safronov2314/* A small button of the menu (Almanac, Scores, Changes, Demo, Bug) has no room for a picture beside its
720cc9d3 2026-11-03 Pavel Safronov2315 word: it hangs on the corner instead of pushing the word aside (user report, 2026-11-02). */
2316#dialog .panel.menu .ui-button.plain {
2317 position: relative;
2318}
2319
2320#dialog .panel.menu .ui-button.plain .pad-glyph {
2321 position: absolute;
2322 top: -10px;
2323 right: -8px;
2324 margin: 0;
2325 width: 20px;
2326 height: 20px;
2327}
2328
92cc4ee3 2026-11-02 Pavel Safronov2329#dialog .tab-glyph {
2330 width: 16px;
2331 height: 16px;
2332 margin-left: 4px;
2333 /* The shoulder pictures are white; the tabs are light. */
2334 filter: invert(1);
2335}
2336
ebcd0a8f 2026-11-02 Pavel Safronov2337/* A shop item's Buy shows its Cross picture only on the selected item. */
2338.shop-item .buy .pad-glyph {
2339 display: none !important;
2340}
2341
2342body.pad .shop-item[aria-pressed='true'] .buy .pad-glyph {
2343 display: inline-block !important;
2344}
2345
2346/* The X (when no footer button is the Circle one) carries its picture beside it. */
2347body.pad #dialog-close .pad-glyph {
2348 display: block;
2349 position: absolute;
2350 top: -6px;
2351 right: -6px;
2352 width: 24px;
2353 height: 24px;
2354}
2355
2356/* Not over the demo's scene: it has no pad to speak of. */
2357#dialog.in-demo .pad-glyph {
2358 display: none !important;
2359}
2360
2361body.pad #dialog .pad-sel {
2362 outline: 4px solid #ffc83d;
2363 outline-offset: 2px;
2364 border-radius: 10px;
2365}
2366
2367/* The controller's cursor (main.ts, padTiles.ts): the action bar shows what the cursor's tile
2368 offers. Not possible there: greyed. Costs a life: red outline and a warning mark. A press on a
2369 greyed one flashes it. */
2370body.pad #actions > button[data-pad='off'] {
2371 opacity: 0.35;
2372 filter: grayscale(1);
2373}
2374
2375body.pad #actions > button[data-pad='danger'] {
2376 position: relative;
2377 outline: 3px solid #e5484d;
2378 outline-offset: -3px;
2379}
2380
2381body.pad #actions > button[data-pad='danger']::after {
2382 content: '\26A0';
2383 position: absolute;
2384 top: -1px;
2385 right: 3px;
2386 color: #e5484d;
2387 font-size: 12px;
2388 line-height: 1;
2389}
2390
2391#actions > button.pad-flash {
2392 animation: pad-flash 0.35s;
2393}
2394
2395@keyframes pad-flash {
2396 0%, 100% { transform: none; }
2397 25% { transform: translateX(-4px); }
2398 75% { transform: translateX(4px); }
2399}
2400
2401/* The cursor's label: one row per button, its picture first (the same size rule as the action bar). */
2402#pad-label .pad-line {
2403 display: flex;
2404 align-items: center;
2405 gap: 8px;
2406 text-align: left;
2407}
2408
aef53511 2026-11-02 Pavel Safronov2409#pad-label .pad-glyph,
2410#pad-hint .pad-glyph {
ebcd0a8f 2026-11-02 Pavel Safronov2411 flex: none;
2412 width: 28px;
2413 height: 28px;
2414 filter: drop-shadow(0 0 1px rgb(0 0 0 / 0.55));
2415}
aef53511 2026-11-02 Pavel Safronov2416
2417/* The legend for the shoulder buttons (main.ts `padHint`): above the label, same family of style,
720cc9d3 2026-11-03 Pavel Safronov2418 smaller. L1 is dimmed when there is nothing for it to undo. At the top of the board (Q84); `top`
2419 is set from the top bar. */
aef53511 2026-11-02 Pavel Safronov2420#pad-hint {
2421 position: fixed;
2422 left: 50%;
720cc9d3 2026-11-03 Pavel Safronov2423 top: 56px;
aef53511 2026-11-02 Pavel Safronov2424 z-index: 3;
2425 display: flex;
720cc9d3 2026-11-03 Pavel Safronov2426 flex-wrap: wrap;
2427 justify-content: center;
2428 gap: 4px 16px;
aef53511 2026-11-02 Pavel Safronov2429 max-width: calc(100vw - 32px);
2430 transform: translateX(-50%);
2431 padding: 4px 12px;
2432 border: 2px solid var(--ink);
2433 border-radius: 16px;
2434 background: #ece3cc;
2435 color: var(--ink);
2436 font-size: 13px;
2437 font-weight: bold;
2438 pointer-events: none;
2439}
2440
2441#pad-hint[hidden] {
2442 display: none;
2443}
2444
2445#pad-hint .pad-hint-item {
2446 display: flex;
2447 align-items: center;
2448 gap: 6px;
2449 white-space: nowrap;
2450}
2451
720cc9d3 2026-11-03 Pavel Safronov2452/* What the shoulder button does, as a picture (black on the cream box). */
2453#pad-hint .pad-hint-symbol {
2454 flex: none;
2455 width: 20px;
2456 height: 20px;
2457}
2458
aef53511 2026-11-02 Pavel Safronov2459#pad-hint .pad-hint-item.dim {
2460 opacity: 0.5;
2461}
92cc4ee3 2026-11-02 Pavel Safronov2462
2463/* A key's cap in the cursor's label and hint when the keyboard plays the board (main.ts `keyCap`),
2464 where the pad's shows the button's picture. */
2465#pad-label .key-cap,
2466#pad-hint .key-cap {
2467 flex: none;
2468 box-sizing: border-box;
2469 min-width: 28px;
2470 height: 26px;
2471 padding: 0 7px;
2472 border: 2px solid var(--ink);
2473 border-bottom-width: 4px;
2474 border-radius: 6px;
2475 background: #fff;
2476 color: var(--ink);
2477 font: bold 12px/22px ui-monospace, monospace;
2478 text-align: center;
2479 white-space: nowrap;
2480}
2481
2482#pad-hint .key-cap {
2483 height: 22px;
2484 min-width: 22px;
2485 font-size: 11px;
2486 line-height: 18px;
2487}
720cc9d3 2026-11-03 Pavel Safronov2488
2489/* The top-bar item "What is that?" is aimed at with the d-pad (main.ts `syncHudAim`; Q82). */
2490#hud .pad-aimed {
2491 outline: 4px solid #ffc83d;
2492 outline-offset: 2px;
2493 border-radius: 10px;
2494}