anvilsign in

collin/mahjong

1@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;600;900&display=swap');
2
3:root {
4 /* Depth of each player's strip. `--h-len` is floored so a window too narrow
5 to hold two strips shrinks the tiles rather than going negative.
6
7 Shallower than it looks like it should be, on purpose: discards live in the
8 middle of the table now rather than in a row inside each strip, and the
9 depth that freed up was given to the centre instead of to the tiles. The
10 wall square and the pile inside it are what wanted the room. */
11 --depth: clamp(202px, 26vh, 276px);
12 --h-len: max(160px, calc(100vw - 2 * var(--depth)));
13 --v-len: 100vh;
14 /* Height the action buttons occupy, held whether or not any are showing.
15 The strip is built from its edge inwards, so a bar that grew when 打出
16 appeared would shove the melds and discards along with it — the reservation
17 is what keeps everything above it still. Sized for the tallest button: the
18 ones that name a tile (打出 五萬, 吃 三萬 四萬) carry a third line. */
19 --bar-h: 52px;
20 /* Largest tile a strip can hold. Edge inwards it has to fit the melds (0.86
21 tiles tall), the button bar, the hand (1.71 + 6px) and the nameplate
22 (28px), plus its own padding, gaps and margins — so 2.57 tiles plus 122px.
23
24 It used to be 3.45 tiles: the missing 0.88 is the row of discards, which is
25 in the middle of the table now. `--depth` came down by the same amount
26 rather than the tiles growing, so a tile is the size it always was and the
27 centre is bigger.
28
29 Tile size otherwise follows the strip's *length*, which is why a window
30 with less height than F11 kept full-size tiles and left the strip's
31 contents to be cut off. */
32 --tile-fit: calc((var(--depth) - 122px) / 2.57);
33
34 --felt: #0f4534;
35 --felt-2: #0a3126;
36 --ivory: #f6f0e2;
37 --ivory-edge: #d9cfb6;
38 --ink: #21262b;
39 --gold: #d9b45c;
40 --red: #b3261e;
41 --green: #1c7a4b;
42 --blue: #1d5aa8;
43}
44
45* {
46 box-sizing: border-box;
47 -webkit-tap-highlight-color: transparent;
48}
49
50html,
51body,
52#root {
53 height: 100%;
54 margin: 0;
55 overflow: hidden;
56 overscroll-behavior: none;
57 touch-action: manipulation;
58 user-select: none;
59}
60
61/* Phone browsers hand out a `100%`/`100vh` viewport that includes the address
62 bar they are about to slide away over the top of it; `dvh` is the height the
63 table can actually have. */
64@supports (height: 100dvh) {
65 html,
66 body,
67 #root {
68 height: 100dvh;
69 }
70}
71
72body {
73 font-family: 'Noto Sans TC', system-ui, sans-serif;
74 color: var(--ivory);
75 background: var(--felt-2);
76}
77
78/* ---------- lobby ---------- */
79.lobby {
80 height: 100%;
81 display: flex;
82 flex-direction: column;
83 align-items: center;
84 justify-content: center;
85 gap: 0.5rem;
86 background: radial-gradient(ellipse at center, var(--felt) 0%, var(--felt-2) 75%);
87}
88.lobby h1 {
89 font-size: clamp(3rem, 9vw, 7rem);
90 font-weight: 900;
91 letter-spacing: 0.1em;
92 margin: 0 0 0.5rem;
93 color: var(--gold);
94}
95.lobby p {
96 margin: 0;
97 opacity: 0.75;
98 letter-spacing: 0.15em;
99}
100.lobby .hint {
101 opacity: 0.45;
102 font-size: 0.9rem;
103 margin-bottom: 1.5rem;
104}
105
106/* ---------- table ---------- */
107.table {
108 position: relative;
109 height: 100%;
110 width: 100%;
111 background: radial-gradient(ellipse at center, var(--felt) 0%, var(--felt-2) 80%);
112}
113
114.slot {
115 position: absolute;
116 overflow: hidden;
117}
118.slot-bottom {
119 bottom: 0;
120 left: var(--depth);
121 right: var(--depth);
122 height: var(--depth);
123}
124.slot-top {
125 top: 0;
126 left: var(--depth);
127 right: var(--depth);
128 height: var(--depth);
129}
130.slot-left {
131 left: 0;
132 top: 0;
133 width: var(--depth);
134 height: 100%;
135}
136.slot-right {
137 right: 0;
138 top: 0;
139 width: var(--depth);
140 height: 100%;
141}
142
143.seat {
144 position: absolute;
145 top: 50%;
146 left: 50%;
147 height: var(--depth);
148 display: flex;
149 flex-direction: column;
150 justify-content: flex-end;
151 align-items: center;
152 gap: 2px;
153 padding: 2px 8px 4px;
154 border-radius: 10px;
155 overflow: hidden;
156 transition: background 0.2s;
157}
158.slot-bottom .seat,
159.slot-top .seat {
160 width: var(--h-len);
161 --tile-w: min(calc((var(--h-len) - 40px) / 18), var(--tile-fit), 72px);
162}
163.slot-left .seat,
164.slot-right .seat {
165 width: var(--v-len);
166 --tile-w: min(calc((var(--v-len) - 40px) / 18), var(--tile-fit), 72px);
167}
168.seat.active {
169 background: linear-gradient(to top, rgba(217, 180, 92, 0.22), transparent 70%);
170}
171.seat.claiming {
172 background: linear-gradient(to top, rgba(179, 38, 30, 0.25), transparent 70%);
173 animation: pulse 1.1s ease-in-out infinite;
174}
175@keyframes pulse {
176 50% {
177 background: linear-gradient(to top, rgba(179, 38, 30, 0.45), transparent 70%);
178 }
179}
180
181/* ---------- tiles ---------- */
182/* Faces come from a 43x2 sprite sheet; the second row is the highlighted
183 variant, which we use for the tile a player has lifted to discard. */
184.tile {
185 position: relative;
186 flex: none;
187 --tw: var(--tile-w);
188 width: var(--tw);
189 height: calc(var(--tw) * 1.375);
190 border-radius: calc(var(--tw) * 0.1);
191 transition: transform 0.12s ease, filter 0.12s ease;
192}
193.tile.t-meld {
194 --tw: calc(var(--tile-w) * 0.62);
195}
196.tile.t-small {
197 --tw: calc(var(--tile-w) * 0.64);
198}
199.tile.face {
200 background-image: url('/tiles/tiles.svg');
201 background-repeat: no-repeat;
202 background-size: calc(var(--tw) * 43) calc(var(--tw) * 2.75);
203 background-position: calc(var(--col) * var(--tw) * -1) 0;
204 filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45));
205}
206.tile.back {
207 background-image: url('/tiles/back.png');
208 background-size: 100% 100%;
209 filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45));
210}
211.tile.tappable {
212 cursor: pointer;
213}
214.tile.raised {
215 transform: translateY(calc(var(--tw) * -0.28));
216 filter: drop-shadow(0 10px 12px rgba(0, 0, 0, 0.55));
217}
218/* Lifted tiles switch to the sheet's highlighted row. */
219.tile.face.raised {
220 background-position: calc(var(--col) * var(--tw) * -1) calc(var(--tw) * -1.375);
221}
222.tile.hl {
223 outline: 2px solid var(--gold);
224 outline-offset: 1px;
225 border-radius: calc(var(--tw) * 0.1);
226}
227.tile.dim {
228 opacity: 0.45;
229}
230
231/* ---------- seat sections ---------- */
232.hand {
233 display: flex;
234 gap: 2px;
235 /* the extra top room is where the 摸 tag sits */
236 height: calc(var(--tile-w) * 1.71 + 6px);
237 padding-top: calc(var(--tile-w) * 0.34);
238 align-items: flex-end;
239 flex: none;
240}
241/* Every hand tile sits in a draggable cell. */
242.hand-cell {
243 position: relative;
244 display: flex;
245 align-items: flex-end;
246 touch-action: none;
247 cursor: grab;
248}
249.hand-cell.dragging {
250 z-index: 5;
251 cursor: grabbing;
252}
253.hand-cell.dragging .tile {
254 transform: translateY(calc(var(--tile-w) * -0.2)) scale(1.06);
255 filter: drop-shadow(0 14px 18px rgba(0, 0, 0, 0.65));
256}
257/* Lifted out of the row and being carried: the canvas is drawing it now, so the
258 element steps aside. Its slot stays, so the row does not shuffle around under
259 the tile you are holding and you can still drop it back where it came from. */
260.hand-cell.carrying .tile {
261 visibility: hidden;
262}
263.drawn-slot {
264 margin-left: calc(var(--tile-w) * 0.45);
265}
266.drawn-slot .tile {
267 outline: 2px solid var(--gold);
268 outline-offset: 1px;
269 border-radius: calc(var(--tw) * 0.1);
270}
271.drawn-tag {
272 position: absolute;
273 top: calc(var(--tile-w) * -0.3);
274 left: 0;
275 right: 0;
276 text-align: center;
277 font-size: calc(var(--tile-w) * 0.24);
278 font-weight: 600;
279 letter-spacing: 0.1em;
280 color: var(--gold);
281 white-space: nowrap;
282 pointer-events: none;
283}
284
285.melds {
286 display: flex;
287 gap: 10px;
288 flex-wrap: wrap;
289 justify-content: center;
290 min-height: calc(var(--tile-w) * 0.86);
291 flex: none;
292}
293.meld {
294 display: flex;
295 gap: 1px;
296}
297.meld.flowers {
298 margin-left: 12px;
299 padding-left: 12px;
300 border-left: 1px dashed rgba(246, 240, 226, 0.3);
301}
302/* Only the phone layout has these now — at full size a discard is out in the
303 middle with the others. Kept as the shared base the `.compact` rules below
304 build on, not as something the four strips render. */
305.discards {
306 display: flex;
307 flex-wrap: wrap;
308 gap: 2px;
309 justify-content: center;
310 align-content: flex-start;
311 max-width: 94%;
312 min-height: 0;
313 margin-bottom: 2px;
314 flex: 1 1 0;
315 overflow-y: auto;
316 scrollbar-width: none;
317}
318
319.nameplate {
320 display: flex;
321 align-items: center;
322 gap: 8px;
323 font-size: 13px;
324 letter-spacing: 0.08em;
325 opacity: 0.9;
326 padding-top: 2px;
327 flex: none;
328}
329.nameplate .wind {
330 display: flex;
331 flex-direction: column;
332 align-items: center;
333 line-height: 1;
334 font-size: 18px;
335 font-weight: 900;
336 color: var(--gold);
337}
338.nameplate .wind em {
339 font-style: normal;
340 font-size: 8px;
341 font-weight: 600;
342 letter-spacing: 0.1em;
343 text-transform: uppercase;
344 opacity: 0.6;
345}
346.nameplate .wind.dealer {
347 color: #ff7a68;
348}
349.nameplate .tag {
350 font-size: 11px;
351 padding: 1px 6px;
352 border-radius: 999px;
353 background: rgba(217, 180, 92, 0.2);
354 border: 1px solid rgba(217, 180, 92, 0.5);
355}
356/* Why a tile lobs over the square instead of sliding into it. */
357.nameplate .tag.walled {
358 background: rgba(246, 240, 226, 0.08);
359 border-color: rgba(246, 240, 226, 0.28);
360 opacity: 0.75;
361}
362.nameplate .tag.ting {
363 background: rgba(46, 170, 120, 0.2);
364 border-color: rgba(46, 170, 120, 0.6);
365}
366.nameplate .tag.sacred {
367 background: rgba(179, 38, 30, 0.22);
368 border-color: rgba(224, 108, 96, 0.6);
369 color: #ffc9c2;
370}
371.nameplate .chips {
372 margin-left: 4px;
373 font-weight: 900;
374 font-size: 16px;
375 color: var(--gold);
376}
377
378.actionbar {
379 display: flex;
380 gap: 8px;
381 align-items: center;
382 justify-content: flex-end;
383 padding-right: calc(var(--tile-w) * 0.4);
384 align-self: stretch;
385 /* Reserved even when empty — see --bar-h. */
386 min-height: var(--bar-h);
387 flex-wrap: wrap;
388 flex: none;
389}
390/* Every button on the bar is the same height, so the row is the same height
391 whichever ones are up: without this a 打出 五萬 stands taller than a bare 過
392 and the strip shuffles itself every time you lift a tile. */
393.actionbar .btn {
394 height: var(--bar-h);
395 min-height: 0;
396 padding-top: 4px;
397 padding-bottom: 4px;
398}
399.waiting {
400 font-size: 12px;
401 opacity: 0.5;
402}
403.delta {
404 font-size: 20px;
405 font-weight: 900;
406}
407.delta.up {
408 color: #5fd39a;
409}
410.delta.down {
411 color: #ff8a80;
412}
413
414/* ---------- buttons ---------- */
415.btn {
416 font-family: inherit;
417 font-size: 17px;
418 font-weight: 400;
419 color: #1a1a1a;
420 background: linear-gradient(180deg, #f3e6c4, #d9b45c);
421 border: none;
422 border-radius: 8px;
423 padding: 6px 15px;
424 min-height: 46px;
425 box-shadow: 0 2px 0 #9a7d38;
426 cursor: pointer;
427 display: inline-flex;
428 align-items: center;
429 gap: 6px;
430}
431.btn:active {
432 transform: translateY(2px);
433 box-shadow: none;
434}
435.btn-sub {
436 font-size: 12px;
437 opacity: 0.7;
438}
439
440/* Bilingual label: the called term above, English cue below. */
441.bi {
442 display: flex;
443 flex-direction: column;
444 align-items: center;
445 justify-content: center;
446 line-height: 1.05;
447 gap: 1px;
448}
449.bi .zh {
450 font-size: 19px;
451 font-weight: 400;
452 letter-spacing: 0.06em;
453}
454.bi .en {
455 font-size: 9.5px;
456 font-weight: 600;
457 letter-spacing: 0.11em;
458 text-transform: uppercase;
459 opacity: 0.68;
460 white-space: nowrap;
461}
462.bi .bi-sub {
463 font-size: 11px;
464 opacity: 0.72;
465 white-space: nowrap;
466}
467.big-btn .bi .zh {
468 font-size: 24px;
469}
470.big-btn .bi .en {
471 font-size: 11px;
472}
473.btn.claim.hu {
474 background: linear-gradient(180deg, #ff8a7a, #c0392b);
475 color: #fff;
476 box-shadow: 0 2px 0 #7c231a;
477}
478.btn.claim.kong,
479.btn.claim.pung {
480 background: linear-gradient(180deg, #8fd3ff, #2b7ec0);
481 color: #fff;
482 box-shadow: 0 2px 0 #1c527d;
483}
484.btn.claim.chow {
485 background: linear-gradient(180deg, #9be3b7, #2e9e63);
486 color: #fff;
487 box-shadow: 0 2px 0 #1d6640;
488}
489.btn.sort {
490 min-height: 20px;
491 padding: 1px 8px;
492 font-size: 11px;
493 letter-spacing: 0.06em;
494 border-radius: 999px;
495}
496.btn.proceed {
497 background: linear-gradient(180deg, #f3e6c4, #d9b45c);
498}
499.btn.pass,
500.btn.ghost {
501 background: transparent;
502 color: rgba(246, 240, 226, 0.75);
503 border: 1px solid rgba(246, 240, 226, 0.35);
504 box-shadow: none;
505}
506.btn.discard {
507 background: linear-gradient(180deg, #fff2cf, #e0c070);
508}
509.big-btn {
510 font-size: 22px;
511 padding: 12px 34px;
512 min-height: 56px;
513}
514
515/* ---------- the tiles in the middle ---------- */
516/* One canvas over the whole table, drawn by ui/Pool.tsx. It has to span the
517 table and not just the centre: the strips clip their own contents, so a tile
518 thrown out of a hand would be cut off at the edge of its seat if it were an
519 element. On the canvas there is nothing to clip it.
520
521 Layering: above .wall-ring (0), since the tiles land inside the square and in
522 front of it; level with .facing, which sits later in the DOM and so keeps the
523 centre readout on top; below the panels, which raise themselves. */
524.tile-pool {
525 position: absolute;
526 inset: 0;
527 width: 100%;
528 height: 100%;
529 pointer-events: none;
530 z-index: 1;
531}
532/* The patch of table you can put a finger into to wash the tiles about. Exactly
533 the pool and no more — the canvas covers the whole table and must never take
534 a click meant for a strip, so the reachable part is its own small box, parked
535 over the pool from the measurement by ui/Pool.tsx. */
536.pool-reach {
537 position: absolute;
538 z-index: 1;
539 touch-action: none;
540 cursor: grab;
541}
542.pool-reach:active {
543 cursor: grabbing;
544}
545
546/* ---------- center ---------- */
547.center {
548 position: absolute;
549 inset: var(--depth);
550 display: flex;
551 flex-direction: column;
552 align-items: center;
553 justify-content: center;
554 gap: 10px;
555 pointer-events: none;
556}
557.center > * {
558 pointer-events: auto;
559}
560/* Everything in the centre reads on top of the wall square. */
561.info,
562.last-discard,
563.prompt,
564.result,
565.info {
566 display: flex;
567 flex-direction: column;
568 align-items: center;
569 gap: 2px;
570 opacity: 0.9;
571 text-align: center;
572}
573.info .round,
574.info .wall {
575 display: flex;
576 flex-direction: column;
577 align-items: center;
578 line-height: 1.05;
579}
580.info .wall {
581 margin-top: 10px;
582 padding-top: 10px;
583 border-top: 1px solid rgba(246, 240, 226, 0.14);
584}
585.info .big {
586 font-size: 40px;
587 font-weight: 400;
588 color: var(--gold);
589}
590.info .lbl {
591 font-size: 11px;
592 letter-spacing: 0.06em;
593 opacity: 0.6;
594}
595.info .hand-no {
596 margin-top: 8px;
597 font-size: 11px;
598 opacity: 0.5;
599 letter-spacing: 0.1em;
600}
601/* The wall: a square of four staggered sides, 18 stacks of two each, drawn
602 with the tileset's own blank tile. Sits behind the centre readout. */
603.wall-ring {
604 position: absolute;
605 top: 50%;
606 left: 50%;
607 --ring: min(calc(100vh - 2 * var(--depth) - 16px), calc(100vw - 2 * var(--depth) - 16px), 560px);
608 --ws: calc(var(--ring) / 19.375);
609 --wd: calc(var(--ws) * 1.375);
610 width: var(--ring);
611 height: var(--ring);
612 transform: translate(-50%, -50%);
613 pointer-events: none;
614 z-index: 0;
615}
616.wall-side {
617 position: absolute;
618 display: flex;
619}
620/* Pinwheel: each side starts where the previous one ended. */
621.wall-side.top {
622 top: 0;
623 left: var(--wd);
624 right: 0;
625 height: var(--wd);
626}
627.wall-side.right {
628 top: var(--wd);
629 right: 0;
630 bottom: 0;
631 width: var(--wd);
632 flex-direction: column;
633}
634.wall-side.bottom {
635 bottom: 0;
636 left: 0;
637 right: var(--wd);
638 height: var(--wd);
639 flex-direction: row-reverse;
640}
641.wall-side.left {
642 top: 0;
643 left: 0;
644 bottom: var(--wd);
645 width: var(--wd);
646 flex-direction: column-reverse;
647}
648.wall-stack {
649 position: relative;
650 flex: none;
651 width: var(--ws);
652 height: var(--wd);
653}
654.wall-side.left .wall-stack,
655.wall-side.right .wall-stack {
656 width: var(--wd);
657 height: var(--ws);
658}
659.wall-stack i {
660 position: absolute;
661 top: 50%;
662 left: 50%;
663 width: var(--ws);
664 height: var(--wd);
665 margin: calc(var(--wd) / -2) 0 0 calc(var(--ws) / -2);
666 background-image: url('/tiles/back.png');
667 background-size: 100% 100%;
668 filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.45));
669}
670.wall-side.left .wall-stack i,
671.wall-side.right .wall-stack i {
672 transform: rotate(90deg);
673}
674/* One tile left in the stack. */
675.wall-stack.s1 i {
676 opacity: 0.8;
677 transform: scale(0.86);
678}
679.wall-side.left .wall-stack.s1 i,
680.wall-side.right .wall-stack.s1 i {
681 transform: rotate(90deg) scale(0.86);
682}
683/* Spent stacks stay as ghosts so the square keeps its shape. */
684.wall-stack.s0 i {
685 background: none;
686 filter: none;
687 border: 1px dashed rgba(246, 240, 226, 0.11);
688 border-radius: 2px;
689}
690.wall-stack.dead i {
691 opacity: 0.4;
692 filter: grayscale(0.6);
693}
694.wall-stack.next::after {
695 content: '';
696 position: absolute;
697 inset: -2px;
698 border: 2px solid var(--gold);
699 border-radius: 3px;
700 box-shadow: 0 0 12px rgba(217, 180, 92, 0.7);
701}
702
703/* Text only, and it sits over the pool — so it must not swallow a finger that
704 is reaching in to wash the tiles about. */
705.facing {
706 pointer-events: none;
707 position: relative;
708 z-index: 1;
709 display: flex;
710 flex-direction: column;
711 align-items: center;
712 gap: 8px;
713 transition: transform 0.35s ease;
714}
715
716.last-discard {
717 display: flex;
718 flex-direction: column;
719 align-items: center;
720 gap: 6px;
721 --tile-w: 64px;
722}
723.ld-label {
724 font-size: 12px;
725 opacity: 0.6;
726}
727.prompt {
728 font-size: 15px;
729 letter-spacing: 0.1em;
730 opacity: 0.8;
731}
732.prompt.warn {
733 color: #ffb4a2;
734 font-weight: 600;
735}
736.result {
737 /* Over the pile in the middle, not under it. */
738 z-index: 2;
739 background: rgba(10, 30, 24, 0.94);
740 border: 1px solid rgba(217, 180, 92, 0.45);
741 border-radius: 14px;
742 padding: 18px 26px;
743 display: flex;
744 flex-direction: column;
745 align-items: center;
746 gap: 10px;
747 max-height: 90%;
748 overflow: auto;
749}
750.result h2 {
751 margin: 0;
752 font-size: 24px;
753 color: var(--gold);
754}
755.result .wt {
756 color: var(--ivory);
757 opacity: 0.8;
758}
759.tai-list {
760 display: grid;
761 grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
762 gap: 2px 18px;
763 font-size: 14px;
764 width: 100%;
765}
766.tai-item {
767 display: flex;
768 align-items: baseline;
769 justify-content: space-between;
770 gap: 12px;
771 padding: 2px 0;
772 border-bottom: 1px dotted rgba(246, 240, 226, 0.2);
773}
774.tai-name {
775 display: flex;
776 flex-direction: column;
777 line-height: 1.15;
778}
779.tai-name em {
780 font-style: normal;
781 font-size: 10px;
782 letter-spacing: 0.08em;
783 text-transform: uppercase;
784 opacity: 0.55;
785}
786.tai-val {
787 font-weight: 700;
788 color: var(--gold);
789}
790/* 胡牌 — the arrow that lands on the winner's edge of the table. Sits over the
791 strip rather than in it, so the hand underneath keeps the height it had and
792 nothing shifts when it appears. */
793.win-arrow {
794 position: absolute;
795 top: 0;
796 left: 50%;
797 font-size: 52px;
798 line-height: 0.9;
799 color: var(--gold);
800 text-shadow: 0 0 6px #0a3126, 0 0 20px rgba(217, 180, 92, 0.9);
801 pointer-events: none;
802 z-index: 4;
803 animation: arrow-nudge 0.9s ease-in-out infinite;
804}
805@keyframes arrow-nudge {
806 0%,
807 100% {
808 transform: translate(-50%, 0);
809 }
810 50% {
811 transform: translate(-50%, 9px);
812 }
813}
814
815/* 流局 — who was there and what they were sitting on. */
816.ready-list {
817 display: flex;
818 flex-direction: column;
819 gap: 6px;
820 margin-top: 2px;
821}
822.ready-row {
823 display: flex;
824 align-items: center;
825 gap: 8px;
826 font-size: 14px;
827}
828.ready-row.none {
829 opacity: 0.45;
830}
831.ready-name {
832 min-width: 6.5em;
833 text-align: right;
834}
835.ready-row .tag.ting {
836 font-size: 11px;
837 padding: 1px 6px;
838 border-radius: 999px;
839 background: rgba(46, 170, 120, 0.2);
840 border: 1px solid rgba(46, 170, 120, 0.6);
841 white-space: nowrap;
842}
843.ready-tiles {
844 display: flex;
845 flex-wrap: wrap;
846 gap: 2px;
847 /* Tiles size themselves off their strip's --tile-w, and the centre is not in
848 one — without a size of its own here they come out 0 x 0 and vanish. */
849 --tile-w: 46px;
850}
851.ready-no {
852 font-size: 12px;
853 letter-spacing: 0.04em;
854}
855
856.deltas {
857 display: flex;
858 gap: 16px;
859 font-size: 14px;
860 opacity: 0.9;
861}
862.deltas .up {
863 color: #5fd39a;
864}
865.deltas .down {
866 color: #ff8a80;
867}
868
869/* ---------- help panel ---------- */
870.helpbtn {
871 margin-left: 2px;
872 width: 22px;
873 height: 22px;
874 border-radius: 50%;
875 border: 1px solid rgba(246, 240, 226, 0.35);
876 background: transparent;
877 color: rgba(246, 240, 226, 0.75);
878 font-family: inherit;
879 font-size: 12px;
880 line-height: 1;
881 cursor: pointer;
882}
883/* The rules panel gets its own deeper slot so it is comfortable to read. */
884.help-slot {
885 position: absolute;
886 inset: 0;
887 z-index: 30;
888 background: rgba(4, 18, 14, 0.55);
889}
890.help-slot.slot-bottom,
891.help-slot.slot-top,
892.help-slot.slot-left,
893.help-slot.slot-right {
894 inset: 0;
895 width: auto;
896 height: auto;
897}
898.help {
899 position: absolute;
900 top: 50%;
901 left: 50%;
902 height: var(--hd);
903 z-index: 31;
904 display: flex;
905 flex-direction: column;
906 background: rgba(8, 32, 25, 0.985);
907 border: 1px solid rgba(217, 180, 92, 0.4);
908 border-radius: 12px;
909 padding: 10px 18px 14px;
910 box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
911}
912/* Anchored to the asking player's own edge, and rotated to face them. The
913 leading translate is applied last, so it shifts the box in screen space. */
914.help-slot.slot-bottom .help,
915.help-slot.slot-top .help {
916 width: calc(100vw - 60px);
917 --hd: min(52vh, 560px);
918 --off: calc(50vh - var(--hd) / 2 - 14px);
919}
920.help-slot.slot-left .help,
921.help-slot.slot-right .help {
922 width: calc(100vh - 60px);
923 --hd: min(52vw, 560px);
924 --off: calc(50vw - var(--hd) / 2 - 14px);
925}
926.help-slot.slot-bottom .help {
927 translate: 0 var(--off);
928}
929.help-slot.slot-top .help {
930 translate: 0 calc(var(--off) * -1);
931}
932.help-slot.slot-left .help {
933 translate: calc(var(--off) * -1) 0;
934}
935.help-slot.slot-right .help {
936 translate: var(--off) 0;
937}
938.help-head {
939 display: flex;
940 align-items: center;
941 justify-content: space-between;
942 gap: 16px;
943 flex: none;
944}
945.help-head h3 {
946 margin: 0;
947 font-size: 15px;
948 font-weight: 400;
949 letter-spacing: 0.08em;
950 color: var(--gold);
951}
952.help-head .btn {
953 min-height: 30px;
954 padding: 2px 12px;
955 font-size: 13px;
956}
957.help-body {
958 flex: 1 1 0;
959 min-height: 0;
960 overflow-y: auto;
961 columns: 4;
962 column-gap: 26px;
963 column-fill: auto;
964 padding-top: 8px;
965 overflow-x: auto;
966 scrollbar-width: thin;
967}
968.help-body section {
969 break-inside: avoid;
970 margin-bottom: 10px;
971}
972.help-body h4 {
973 margin: 0 0 2px;
974 font-size: 13px;
975 font-weight: 400;
976 color: var(--gold);
977 letter-spacing: 0.06em;
978}
979.help-body h4 em {
980 font-style: normal;
981 font-size: 9px;
982 letter-spacing: 0.12em;
983 text-transform: uppercase;
984 opacity: 0.55;
985 margin-left: 4px;
986}
987.help-body p {
988 margin: 0 0 4px;
989 display: flex;
990 flex-direction: column;
991 line-height: 1.35;
992 font-size: 11.5px;
993}
994.help-body p em {
995 font-style: normal;
996 font-size: 10.5px;
997 opacity: 0.6;
998}
999.help-body .credit {
1000 opacity: 0.6;
1001}
1002
1003/* ---------- lobby resume ---------- */
1004.lobby .sub {
1005 font-size: 0.95rem;
1006}
1007.resume {
1008 margin-top: 1.4rem;
1009 padding: 12px 26px;
1010 border: 1px solid rgba(217, 180, 92, 0.35);
1011 border-radius: 12px;
1012 background: rgba(0, 0, 0, 0.2);
1013 text-align: center;
1014 display: flex;
1015 flex-direction: column;
1016 gap: 6px;
1017}
1018.resume-line {
1019 font-size: 14px;
1020 color: var(--gold);
1021 letter-spacing: 0.06em;
1022}
1023.resume-scores {
1024 display: flex;
1025 gap: 18px;
1026 justify-content: center;
1027 font-size: 13px;
1028 opacity: 0.8;
1029}
1030.resume-scores b {
1031 color: var(--gold);
1032 font-weight: 600;
1033}
1034.resume-when {
1035 font-size: 11px;
1036 opacity: 0.45;
1037 letter-spacing: 0.08em;
1038}
1039.lobby-buttons {
1040 display: flex;
1041 flex-direction: column;
1042 gap: 12px;
1043 margin-top: 1.6rem;
1044 align-items: stretch;
1045}
1046/* The two ways to play carry a line of explanation under the name. */
1047.lobby-buttons .big-btn {
1048 display: flex;
1049 flex-direction: column;
1050 gap: 3px;
1051 min-width: 360px;
1052 padding: 12px 28px;
1053}
1054.lobby-buttons .big-btn em {
1055 font-style: normal;
1056 font-size: 12px;
1057 font-weight: 400;
1058 letter-spacing: 0.03em;
1059 opacity: 0.72;
1060}
1061
1062/* ---------- sound ---------- */
1063.lobby-extras {
1064 margin-top: 1.4rem;
1065 display: flex;
1066 gap: 12px;
1067 align-items: center;
1068}
1069/* Small rounded button for the things that sit under the main choices. */
1070.btn.pill {
1071 min-height: 34px;
1072 padding: 2px 14px;
1073 font-size: 13px;
1074 letter-spacing: 0.06em;
1075 border-radius: 999px;
1076 gap: 8px;
1077}
1078.sound-toggle.on {
1079 border-color: rgba(217, 180, 92, 0.7);
1080 color: var(--gold);
1081}
1082.sound-toggle.compact {
1083 min-height: 28px;
1084 padding: 2px 9px;
1085 font-size: 15px;
1086}
1087.sound-toggle .spk {
1088 font-size: 15px;
1089 line-height: 1;
1090}
1091
1092/* ---------- shared table controls ---------- */
1093/* Bottom-right of the centre square, opposite the log. Unrotated on purpose:
1094 it belongs to the table, not to a seat. */
1095.table-controls {
1096 position: absolute;
1097 /* Over the pile, same as .result. */
1098 z-index: 2;
1099 right: 10px;
1100 bottom: 8px;
1101 display: flex;
1102 align-items: center;
1103 gap: 8px;
1104}
1105.btn.undo {
1106 min-height: 34px;
1107 padding: 3px 14px;
1108 border-radius: 999px;
1109 gap: 9px;
1110 max-width: 40vh;
1111}
1112.undo-arrow {
1113 font-size: 17px;
1114 line-height: 1;
1115 color: var(--gold);
1116}
1117.undo-what {
1118 display: flex;
1119 flex-direction: column;
1120 align-items: flex-start;
1121 line-height: 1.15;
1122 min-width: 0;
1123}
1124.undo-what .zh {
1125 font-size: 13px;
1126 letter-spacing: 0.05em;
1127}
1128.undo-what .en {
1129 font-size: 9px;
1130 letter-spacing: 0.09em;
1131 text-transform: uppercase;
1132 opacity: 0.55;
1133 white-space: nowrap;
1134 overflow: hidden;
1135 text-overflow: ellipsis;
1136 max-width: 100%;
1137}
1138
1139/* ---------- result: one block per winner ---------- */
1140.result .win {
1141 display: flex;
1142 flex-direction: column;
1143 align-items: center;
1144 gap: 8px;
1145 width: 100%;
1146}
1147.result .win + .win {
1148 padding-top: 10px;
1149 border-top: 1px solid rgba(217, 180, 92, 0.3);
1150}
1151.result .liable {
1152 font-size: 12px;
1153 letter-spacing: 0.08em;
1154 color: #ffb4a2;
1155}
1156.result .multi {
1157 font-size: 12px;
1158 letter-spacing: 0.1em;
1159 color: var(--gold);
1160 opacity: 0.85;
1161}
1162
1163/* ---------- settings ---------- */
1164.over-buttons {
1165 display: flex;
1166 gap: 12px;
1167 align-items: center;
1168}
1169.settings-scrim {
1170 position: absolute;
1171 inset: 0;
1172 z-index: 40;
1173 background: rgba(4, 18, 14, 0.72);
1174 display: flex;
1175 align-items: center;
1176 justify-content: center;
1177 padding: 20px;
1178}
1179.settings {
1180 width: min(1180px, 100%);
1181 max-height: 100%;
1182 display: flex;
1183 flex-direction: column;
1184 background: rgba(8, 32, 25, 0.99);
1185 border: 1px solid rgba(217, 180, 92, 0.4);
1186 border-radius: 14px;
1187 box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
1188 padding: 16px 28px 22px;
1189}
1190.settings-head {
1191 display: flex;
1192 align-items: center;
1193 justify-content: space-between;
1194 gap: 16px;
1195 flex: none;
1196 padding-bottom: 12px;
1197 border-bottom: 1px solid rgba(246, 240, 226, 0.12);
1198}
1199.settings-head h3 {
1200 margin: 0;
1201 font-size: 22px;
1202 font-weight: 400;
1203 letter-spacing: 0.08em;
1204 color: var(--gold);
1205}
1206.settings-head-btns {
1207 display: flex;
1208 gap: 10px;
1209}
1210.settings-head .btn {
1211 min-height: 46px;
1212 padding: 2px 22px;
1213 font-size: 17px;
1214}
1215.settings-body {
1216 /* `1 1 auto`, not `1 1 0`: the panel's own height comes from this content,
1217 so a zero basis collapses the pair of them to nothing. `max-height` on the
1218 panel is what brings the scrollbar back at the other end. */
1219 flex: 1 1 auto;
1220 min-height: 0;
1221 overflow-y: auto;
1222 scrollbar-width: thin;
1223 padding-top: 14px;
1224 /* Columns rather than a grid: grid rows would size to the tallest section
1225 and leave the shorter one padded with air, which is what pushed the last
1226 house rule off the bottom. */
1227 columns: 2;
1228 column-gap: 44px;
1229}
1230.settings-body section {
1231 break-inside: avoid;
1232 margin-bottom: 22px;
1233}
1234.settings-body h4 {
1235 margin: 0 0 10px;
1236 font-size: 16px;
1237 font-weight: 400;
1238 color: var(--gold);
1239 letter-spacing: 0.06em;
1240}
1241.settings-body h4 em {
1242 font-style: normal;
1243 font-size: 11px;
1244 letter-spacing: 0.12em;
1245 text-transform: uppercase;
1246 opacity: 0.55;
1247 margin-left: 5px;
1248}
1249.settings .field {
1250 display: flex;
1251 align-items: center;
1252 gap: 14px;
1253 padding: 5px 0;
1254}
1255.settings .f-label {
1256 display: flex;
1257 align-items: baseline;
1258 gap: 6px;
1259 font-size: 16px;
1260 /* Wide enough for 起始籌碼 STARTING CHIPS, so every input in a section
1261 starts at the same place. */
1262 min-width: 196px;
1263 opacity: 0.85;
1264}
1265/* ...except the player rows, which are labelled 1–4 and need no such room. */
1266.settings .names .f-label {
1267 min-width: 26px;
1268}
1269.settings .f-label em {
1270 font-style: normal;
1271 font-size: 11px;
1272 letter-spacing: 0.1em;
1273 text-transform: uppercase;
1274 opacity: 0.55;
1275}
1276.settings input[type='text'],
1277.settings input:not([type]),
1278.settings input[type='number'] {
1279 flex: 1 1 0;
1280 min-width: 0;
1281 font-family: inherit;
1282 font-size: 18px;
1283 color: var(--ivory);
1284 background: rgba(0, 0, 0, 0.28);
1285 border: 1px solid rgba(246, 240, 226, 0.25);
1286 border-radius: 8px;
1287 padding: 11px 14px;
1288 min-height: 48px;
1289 user-select: text;
1290}
1291.settings input:focus {
1292 outline: none;
1293 border-color: rgba(217, 180, 92, 0.8);
1294}
1295.settings input[type='number'] {
1296 max-width: 150px;
1297}
1298.settings input[type='range'] {
1299 flex: 1 1 0;
1300 height: 34px;
1301 accent-color: var(--gold);
1302}
1303.settings input:disabled {
1304 opacity: 0.4;
1305}
1306.settings .switch {
1307 display: grid;
1308 grid-template-columns: auto 1fr;
1309 gap: 3px 14px;
1310 align-items: center;
1311 padding: 9px 0;
1312 cursor: pointer;
1313}
1314.settings .switch input {
1315 width: 26px;
1316 height: 26px;
1317 accent-color: var(--gold);
1318 cursor: pointer;
1319}
1320.settings .sw-name {
1321 display: flex;
1322 align-items: baseline;
1323 gap: 8px;
1324 font-size: 17px;
1325}
1326.settings .sw-name em {
1327 font-style: normal;
1328 font-size: 11px;
1329 letter-spacing: 0.1em;
1330 text-transform: uppercase;
1331 opacity: 0.55;
1332}
1333.settings .note {
1334 grid-column: 2;
1335 font-size: 13px;
1336 line-height: 1.4;
1337 opacity: 0.55;
1338}
1339.settings .switch.dead {
1340 opacity: 0.4;
1341 cursor: default;
1342}
1343.settings .note.lock {
1344 grid-column: auto;
1345 margin: 4px 0 0;
1346 color: #ffb4a2;
1347 opacity: 0.75;
1348}
1349
1350/* ============================================================================
1351 compact — a phone
1352
1353 The table layout is four strips of --depth arranged around a centre square,
1354 which wants about 700px each way; a phone has that in neither, and in
1355 landscape the top and bottom strips simply overlap. It also assumes four
1356 people sitting around a screen lying flat, which is not what a phone is.
1357
1358 Compact throws both assumptions out. Nothing is rotated — every label reads
1359 upright for the one person holding the phone — the table becomes a grid, the
1360 three other seats shrink to cards showing what is public about them, and the
1361 depth that frees up goes to the hand, which wraps when it has to.
1362
1363 The `compact` class is put on <html> by useCompact(); the breakpoint itself
1364 lives in ui/compact.ts, so it is written down once.
1365 ============================================================================ */
1366
1367.compact .table {
1368 display: grid;
1369 gap: 2px;
1370 /* Landscape is where a notch eats into the sides. */
1371 padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom)
1372 env(safe-area-inset-left);
1373 grid-template-columns: 1fr 1fr 1fr;
1374 grid-template-rows: minmax(0, 1fr) auto auto;
1375 grid-template-areas:
1376 'left across right'
1377 'bar bar bar'
1378 'mine mine mine';
1379}
1380.compact .slot {
1381 position: relative;
1382 inset: auto;
1383 width: auto;
1384 height: auto;
1385 min-width: 0;
1386 min-height: 0;
1387}
1388/* `.slot` and not the edge class alone: the rules sheet carries an edge class
1389 of its own, and would otherwise be parked in the grid area named here. */
1390.compact .slot.slot-left {
1391 grid-area: left;
1392}
1393.compact .slot.slot-top {
1394 grid-area: across;
1395}
1396.compact .slot.slot-right {
1397 grid-area: right;
1398}
1399.compact .slot.slot-bottom {
1400 grid-area: mine;
1401}
1402.compact .center {
1403 grid-area: bar;
1404}
1405/* The strip no longer floats in the middle of a slot — it is the slot. */
1406.compact .seat {
1407 position: relative;
1408 top: auto;
1409 left: auto;
1410 width: auto;
1411 height: 100%;
1412 padding: 2px 4px 3px;
1413}
1414/* Off to one side rather than dead centre: a card is narrow enough that the
1415 arrow would otherwise land on the name it is pointing out. */
1416.compact .win-arrow {
1417 font-size: 24px;
1418 left: 14px;
1419}
1420
1421/* ---------- the other three seats ---------- */
1422/* Read top-down: who they are, how many tiles they hold, what they have laid
1423 down, what they have thrown. The hand is the least interesting of those and
1424 goes smallest; the discards get whatever room is left, since reading them is
1425 the whole of playing against somebody. */
1426.compact .opp .seat {
1427 --tile-w: clamp(13px, calc((100vw / 3 - 20px) / 8.6), 32px);
1428 justify-content: flex-start;
1429 gap: 2px;
1430 border: 1px solid rgba(246, 240, 226, 0.09);
1431 border-radius: 8px;
1432 background: rgba(0, 0, 0, 0.14);
1433}
1434.compact .opp .nameplate {
1435 order: 0;
1436}
1437.compact .opp .hand {
1438 order: 1;
1439}
1440.compact .opp .melds {
1441 order: 2;
1442}
1443.compact .opp .discards {
1444 order: 3;
1445}
1446.compact .opp .actionbar {
1447 order: 4;
1448}
1449
1450.compact .opp .nameplate {
1451 flex-wrap: wrap;
1452 justify-content: center;
1453 gap: 3px 5px;
1454 font-size: 10px;
1455 padding-top: 0;
1456 row-gap: 1px;
1457}
1458.compact .opp .nameplate .wind {
1459 font-size: 13px;
1460}
1461.compact .opp .nameplate .wind em,
1462.compact .opp .nameplate .helpbtn,
1463.compact .opp .nameplate .sort {
1464 display: none;
1465}
1466.compact .opp .nameplate .chips {
1467 font-size: 13px;
1468 margin-left: 0;
1469}
1470.compact .opp .nameplate .tag {
1471 font-size: 9px;
1472 padding: 0 4px;
1473}
1474/* Face-down backs: a count you can see at a glance, nothing more. */
1475.compact .opp .hand {
1476 flex-wrap: wrap;
1477 justify-content: center;
1478 height: auto;
1479 padding-top: 0;
1480 gap: 1px;
1481 max-width: 100%;
1482}
1483.compact .opp .hand .tile {
1484 --tw: calc(var(--tile-w) * 0.52);
1485}
1486.compact .opp .melds {
1487 gap: 5px;
1488 min-height: 0;
1489 max-width: 100%;
1490}
1491.compact .opp .meld.flowers {
1492 margin-left: 5px;
1493 padding-left: 5px;
1494}
1495.compact .opp .discards {
1496 flex: 1 1 auto;
1497 max-width: 100%;
1498 min-height: 0;
1499 overflow-y: auto;
1500 align-content: flex-start;
1501}
1502/* No height is reserved here: an opponent's bar is empty except in hotseat,
1503 and a card that stays still matters less than the room it costs. */
1504.compact .opp .actionbar {
1505 min-height: 0;
1506 padding-right: 0;
1507 justify-content: center;
1508 gap: 4px;
1509}
1510.compact .opp .actionbar .btn {
1511 height: auto;
1512 min-height: 34px;
1513 padding: 2px 8px;
1514}
1515.compact .opp .actionbar .bi .zh {
1516 font-size: 14px;
1517}
1518.compact .opp .delta {
1519 font-size: 15px;
1520}
1521
1522/* ---------- your own seat ---------- */
1523/* A grid rather than the stack the strips use, so the melds and the discards
1524 share one line and the hand gets the depth that saves. */
1525.compact .mine .seat {
1526 --tile-w: min(calc((100vw - 24px) / 18.3), 54px);
1527 display: grid;
1528 /* `minmax(0, auto)`, so a full set of melds wraps inside its column instead
1529 of shouldering the discards off the end of the strip. */
1530 grid-template-columns: minmax(0, auto) minmax(0, 1fr);
1531 grid-template-areas:
1532 'melds discards'
1533 'bar bar'
1534 'hand hand'
1535 'name name';
1536 align-content: end;
1537 align-items: end;
1538 justify-items: center;
1539 gap: 2px 8px;
1540 padding: 2px 6px 3px;
1541}
1542.compact .mine .melds {
1543 grid-area: melds;
1544 justify-content: flex-start;
1545 min-height: 0;
1546 gap: 6px;
1547}
1548.compact .mine .discards {
1549 grid-area: discards;
1550 /* One line that scrolls, kept pinned to its end by Seat.tsx — wrapping would
1551 cost a second row of depth the hand wants more. Reserved even while empty,
1552 since this row and the melds beside it share it: without that the strip
1553 would jump the first time either one appeared. */
1554 flex: none;
1555 flex-wrap: nowrap;
1556 justify-content: flex-start;
1557 align-self: end;
1558 max-width: 100%;
1559 width: 100%;
1560 min-height: calc(var(--tile-w) * 0.9);
1561 margin-bottom: 0;
1562 overflow-x: auto;
1563 overflow-y: hidden;
1564 /* Melds and discards end up shoulder to shoulder here, and both are rows of
1565 tiles — the rule and the smaller face are what tell them apart. */
1566 border-left: 1px solid rgba(246, 240, 226, 0.16);
1567 padding-left: 8px;
1568 opacity: 0.85;
1569}
1570.compact .mine .discards .tile {
1571 --tw: calc(var(--tile-w) * 0.5);
1572}
1573.compact .mine .actionbar {
1574 grid-area: bar;
1575 --bar-h: 44px;
1576 justify-content: center;
1577 padding-right: 0;
1578 gap: 6px;
1579}
1580.compact .mine .hand {
1581 grid-area: hand;
1582 flex-wrap: wrap;
1583 align-content: flex-end;
1584 justify-content: center;
1585 height: auto;
1586 max-width: 100%;
1587 /* The row gap matches the padding for the same reason: the 摸 tag hangs above
1588 its tile, and on a wrapped hand that is somebody else's row. */
1589 gap: calc(var(--tile-w) * 0.34) 2px;
1590 padding-top: calc(var(--tile-w) * 0.34);
1591}
1592.compact .mine .nameplate {
1593 grid-area: name;
1594 flex-wrap: wrap;
1595 justify-content: center;
1596 gap: 3px 7px;
1597 font-size: 11px;
1598 padding-top: 0;
1599}
1600.compact .mine .nameplate .wind {
1601 font-size: 15px;
1602}
1603.compact .mine .nameplate .wind em {
1604 display: none;
1605}
1606.compact .mine .nameplate .chips {
1607 font-size: 14px;
1608}
1609.compact .mine .nameplate .btn.sort {
1610 min-height: 26px;
1611 padding: 1px 9px;
1612 font-size: 12px;
1613}
1614.compact .mine .nameplate .helpbtn {
1615 width: 26px;
1616 height: 26px;
1617 font-size: 13px;
1618}
1619
1620/* ---------- the middle ---------- */
1621/* One bar across the table rather than a square in the middle of it. */
1622.compact .center {
1623 position: relative;
1624 inset: auto;
1625 flex-direction: row;
1626 align-items: center;
1627 justify-content: space-between;
1628 gap: 8px;
1629 padding: 1px 6px;
1630 min-height: 44px;
1631}
1632.compact .facing {
1633 flex: 1 1 auto;
1634 flex-direction: row;
1635 align-items: center;
1636 justify-content: center;
1637 gap: 10px;
1638 min-width: 0;
1639}
1640.compact .last-discard {
1641 --tile-w: 30px;
1642 flex-direction: row;
1643 align-items: center;
1644 gap: 6px;
1645}
1646.compact .ld-label {
1647 font-size: 10px;
1648}
1649.compact .prompt {
1650 font-size: 12px;
1651 text-align: center;
1652}
1653.compact .table-controls {
1654 position: static;
1655 flex: none;
1656}
1657.compact .btn.undo {
1658 max-width: 34vw;
1659 min-height: 30px;
1660 padding: 2px 10px;
1661}
1662.compact .undo-what .zh {
1663 font-size: 11px;
1664}
1665.compact .undo-what .en {
1666 display: none;
1667}
1668/* What the wall square was telling you, in the room a bar has. */
1669.wall-pill {
1670 display: flex;
1671 align-items: baseline;
1672 gap: 5px;
1673 flex: none;
1674 letter-spacing: 0.06em;
1675 opacity: 0.75;
1676}
1677.wall-pill b {
1678 font-size: 18px;
1679 font-weight: 900;
1680 color: var(--gold);
1681}
1682.wall-pill em {
1683 font-style: normal;
1684 font-size: 9px;
1685 text-transform: uppercase;
1686 opacity: 0.7;
1687}
1688
1689/* The hand result covers the table instead of sitting in a square that is no
1690 longer there. */
1691.compact .result {
1692 position: fixed;
1693 top: 50%;
1694 left: 50%;
1695 transform: translate(-50%, -50%);
1696 z-index: 25;
1697 max-width: min(560px, 94vw);
1698 max-height: 92dvh;
1699 padding: 12px 16px;
1700 gap: 8px;
1701}
1702.compact .result h2 {
1703 font-size: 18px;
1704}
1705.compact .tai-list {
1706 grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
1707 gap: 1px 12px;
1708 font-size: 12px;
1709}
1710.compact .ready-row {
1711 font-size: 12px;
1712}
1713.compact .ready-name {
1714 min-width: 4.5em;
1715}
1716.compact .ready-tiles {
1717 --tile-w: 26px;
1718}
1719.compact .deltas {
1720 flex-wrap: wrap;
1721 justify-content: center;
1722 gap: 6px 14px;
1723 font-size: 12px;
1724}
1725.compact .big-btn {
1726 font-size: 18px;
1727 padding: 8px 22px;
1728 min-height: 44px;
1729}
1730
1731/* ---------- portrait ---------- */
1732/* Held upright there is no room for three cards side by side, so they stack in
1733 the order play reaches them, and the hand wraps to two rows. */
1734@media (orientation: portrait) {
1735 .compact .table {
1736 grid-template-columns: 1fr;
1737 grid-template-rows: repeat(3, minmax(0, 1fr)) auto auto;
1738 grid-template-areas:
1739 'right'
1740 'across'
1741 'left'
1742 'bar'
1743 'mine';
1744 }
1745 /* A card gets the whole width here, so the tiles can be read rather than
1746 merely counted; the discards wrap to a second line when they need it. */
1747 .compact .opp .seat {
1748 --tile-w: clamp(18px, calc((100vw - 26px) / 13), 34px);
1749 }
1750 .compact .mine .seat {
1751 --tile-w: min(calc((100vw - 26px) / 9.4), 54px);
1752 }
1753 /* A phone's width will not hold the wall count, who discarded what, the claim
1754 prompt and the table's own buttons on one line. The readout — the part that
1755 changes — takes a line of its own underneath. The room comes out of the
1756 other seats, which are the flexible rows, so the hand never moves. */
1757 .compact .center {
1758 flex-wrap: wrap;
1759 row-gap: 2px;
1760 }
1761 .compact .wall-pill {
1762 order: 0;
1763 }
1764 .compact .table-controls {
1765 order: 1;
1766 }
1767 .compact .facing {
1768 order: 2;
1769 flex-basis: 100%;
1770 }
1771}
1772
1773/* ---------- the panels ---------- */
1774.compact .lobby {
1775 justify-content: flex-start;
1776 overflow-y: auto;
1777 gap: 0.25rem;
1778 padding: 12px 14px calc(14px + env(safe-area-inset-bottom));
1779}
1780.compact .lobby > * {
1781 flex: none;
1782}
1783/* Sized off both axes: a phone held sideways has width to spare and no height
1784 at all, and the title was the first thing to push the buttons off. */
1785.compact .lobby h1 {
1786 font-size: clamp(1.9rem, min(9vw, 11vh), 3.2rem);
1787 margin-bottom: 0.2rem;
1788}
1789.compact .lobby .sub {
1790 font-size: 0.78rem;
1791}
1792/* The screen dividers this asks for are a four-round-a-table idea, and nobody
1793 props cards against a phone. */
1794.compact .lobby .hint {
1795 display: none;
1796}
1797.compact .resume {
1798 margin-top: 0.8rem;
1799 padding: 8px 16px;
1800}
1801.compact .resume-scores {
1802 flex-wrap: wrap;
1803 gap: 4px 12px;
1804 font-size: 12px;
1805}
1806.compact .lobby-buttons {
1807 margin-top: 0.9rem;
1808 gap: 8px;
1809 width: min(460px, 100%);
1810}
1811.compact .lobby-buttons .big-btn {
1812 min-width: 0;
1813 width: 100%;
1814 padding: 8px 16px;
1815}
1816.compact .lobby-extras {
1817 margin-top: 0.9rem;
1818}
1819/* Sideways there is barely 390px of height for a title, a saved game, three
1820 ways to start and the settings link. Everything gives a little so the last of
1821 them lands above the fold, rather than behind a scroll nobody looks for. */
1822@media (orientation: landscape) {
1823 .compact .lobby h1 {
1824 font-size: clamp(1.6rem, 9vh, 2.6rem);
1825 }
1826 .compact .lobby .sub,
1827 .compact .resume-when {
1828 display: none;
1829 }
1830 .compact .resume {
1831 margin-top: 0.4rem;
1832 padding: 6px 14px;
1833 }
1834 .compact .resume-line {
1835 font-size: 12px;
1836 }
1837 .compact .lobby-buttons {
1838 margin-top: 0.5rem;
1839 }
1840 .compact .lobby-buttons .big-btn {
1841 min-height: 40px;
1842 padding: 5px 16px;
1843 font-size: 19px;
1844 }
1845 .compact .lobby-extras {
1846 margin-top: 0.5rem;
1847 }
1848}
1849
1850.compact .help-slot .help {
1851 width: calc(100vw - 16px);
1852 --hd: calc(100dvh - 16px);
1853 translate: none;
1854 padding: 8px 12px 10px;
1855}
1856.compact .help-head h3 {
1857 font-size: 13px;
1858}
1859/* The strip version fills four columns and scrolls sideways, which reads fine
1860 across a laptop's width and not at all on a phone. One column, scrolled the
1861 way every other sheet on the device is. */
1862.compact .help-body {
1863 columns: auto;
1864 column-gap: normal;
1865 overflow-x: hidden;
1866}
1867
1868.compact .settings-scrim {
1869 padding: 6px;
1870}
1871.compact .settings {
1872 padding: 10px 14px 14px;
1873}
1874.compact .settings-head h3 {
1875 font-size: 16px;
1876}
1877.compact .settings-head .btn {
1878 min-height: 38px;
1879 padding: 2px 14px;
1880 font-size: 14px;
1881}
1882/* Two columns sideways, where there is width and no height; one held upright,
1883 where it is the other way round. */
1884.compact .settings-body {
1885 columns: 2;
1886 column-gap: 26px;
1887 padding-top: 10px;
1888}
1889@media (orientation: portrait) {
1890 .compact .settings-body {
1891 columns: 1;
1892 }
1893}
1894.compact .settings-body section {
1895 margin-bottom: 14px;
1896}
1897.compact .settings-body h4 {
1898 margin-bottom: 6px;
1899 font-size: 14px;
1900}
1901.compact .settings .f-label {
1902 min-width: 116px;
1903 font-size: 14px;
1904}
1905.compact .settings input[type='text'],
1906.compact .settings input:not([type]),
1907.compact .settings input[type='number'] {
1908 font-size: 15px;
1909 min-height: 40px;
1910 padding: 8px 10px;
1911}
1912.compact .settings .sw-name {
1913 font-size: 14px;
1914}
1915.compact .settings .note {
1916 font-size: 11px;
1917}
1918
1919/* ---------- fullscreen ---------- */
1920/* Sized to match the sound toggle it stands beside, in both its forms. */
1921.fs-toggle {
1922 min-height: 34px;
1923 padding: 2px 14px;
1924}
1925.fs-toggle.compact {
1926 min-height: 28px;
1927 padding: 3px 9px;
1928}
1929.fs-toggle.on {
1930 border-color: rgba(217, 180, 92, 0.7);
1931 color: var(--gold);
1932}
1933.fs-toggle .fs-icon {
1934 display: block;
1935 flex: none;
1936}