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