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
533/* ---------- center ---------- */
534.center {
535 position: absolute;
536 inset: var(--depth);
537 display: flex;
538 flex-direction: column;
539 align-items: center;
540 justify-content: center;
541 gap: 10px;
542 pointer-events: none;
543}
544.center > * {
545 pointer-events: auto;
546}
547/* Everything in the centre reads on top of the wall square. */
548.info,
549.last-discard,
550.prompt,
551.result,
552.info {
553 display: flex;
554 flex-direction: column;
555 align-items: center;
556 gap: 2px;
557 opacity: 0.9;
558 text-align: center;
559}
560.info .round,
561.info .wall {
562 display: flex;
563 flex-direction: column;
564 align-items: center;
565 line-height: 1.05;
566}
567.info .wall {
568 margin-top: 10px;
569 padding-top: 10px;
570 border-top: 1px solid rgba(246, 240, 226, 0.14);
571}
572.info .big {
573 font-size: 40px;
574 font-weight: 400;
575 color: var(--gold);
576}
577.info .lbl {
578 font-size: 11px;
579 letter-spacing: 0.06em;
580 opacity: 0.6;
581}
582.info .hand-no {
583 margin-top: 8px;
584 font-size: 11px;
585 opacity: 0.5;
586 letter-spacing: 0.1em;
587}
588/* The wall: a square of four staggered sides, 18 stacks of two each, drawn
589 with the tileset's own blank tile. Sits behind the centre readout. */
590.wall-ring {
591 position: absolute;
592 top: 50%;
593 left: 50%;
594 --ring: min(calc(100vh - 2 * var(--depth) - 16px), calc(100vw - 2 * var(--depth) - 16px), 560px);
595 --ws: calc(var(--ring) / 19.375);
596 --wd: calc(var(--ws) * 1.375);
597 width: var(--ring);
598 height: var(--ring);
599 transform: translate(-50%, -50%);
600 pointer-events: none;
601 z-index: 0;
602}
603.wall-side {
604 position: absolute;
605 display: flex;
606}
607/* Pinwheel: each side starts where the previous one ended. */
608.wall-side.top {
609 top: 0;
610 left: var(--wd);
611 right: 0;
612 height: var(--wd);
613}
614.wall-side.right {
615 top: var(--wd);
616 right: 0;
617 bottom: 0;
618 width: var(--wd);
619 flex-direction: column;
620}
621.wall-side.bottom {
622 bottom: 0;
623 left: 0;
624 right: var(--wd);
625 height: var(--wd);
626 flex-direction: row-reverse;
627}
628.wall-side.left {
629 top: 0;
630 left: 0;
631 bottom: var(--wd);
632 width: var(--wd);
633 flex-direction: column-reverse;
634}
635.wall-stack {
636 position: relative;
637 flex: none;
638 width: var(--ws);
639 height: var(--wd);
640}
641.wall-side.left .wall-stack,
642.wall-side.right .wall-stack {
643 width: var(--wd);
644 height: var(--ws);
645}
646.wall-stack i {
647 position: absolute;
648 top: 50%;
649 left: 50%;
650 width: var(--ws);
651 height: var(--wd);
652 margin: calc(var(--wd) / -2) 0 0 calc(var(--ws) / -2);
653 background-image: url('/tiles/back.png');
654 background-size: 100% 100%;
655 filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.45));
656}
657.wall-side.left .wall-stack i,
658.wall-side.right .wall-stack i {
659 transform: rotate(90deg);
660}
661/* One tile left in the stack. */
662.wall-stack.s1 i {
663 opacity: 0.8;
664 transform: scale(0.86);
665}
666.wall-side.left .wall-stack.s1 i,
667.wall-side.right .wall-stack.s1 i {
668 transform: rotate(90deg) scale(0.86);
669}
670/* Spent stacks stay as ghosts so the square keeps its shape. */
671.wall-stack.s0 i {
672 background: none;
673 filter: none;
674 border: 1px dashed rgba(246, 240, 226, 0.11);
675 border-radius: 2px;
676}
677.wall-stack.dead i {
678 opacity: 0.4;
679 filter: grayscale(0.6);
680}
681.wall-stack.next::after {
682 content: '';
683 position: absolute;
684 inset: -2px;
685 border: 2px solid var(--gold);
686 border-radius: 3px;
687 box-shadow: 0 0 12px rgba(217, 180, 92, 0.7);
688}
689
690.facing {
691 position: relative;
692 z-index: 1;
693 display: flex;
694 flex-direction: column;
695 align-items: center;
696 gap: 8px;
697 transition: transform 0.35s ease;
698}
699
700.last-discard {
701 display: flex;
702 flex-direction: column;
703 align-items: center;
704 gap: 6px;
705 --tile-w: 64px;
706}
707.ld-label {
708 font-size: 12px;
709 opacity: 0.6;
710}
711.prompt {
712 font-size: 15px;
713 letter-spacing: 0.1em;
714 opacity: 0.8;
715}
716.prompt.warn {
717 color: #ffb4a2;
718 font-weight: 600;
719}
720.result {
721 /* Over the pile in the middle, not under it. */
722 z-index: 2;
723 background: rgba(10, 30, 24, 0.94);
724 border: 1px solid rgba(217, 180, 92, 0.45);
725 border-radius: 14px;
726 padding: 18px 26px;
727 display: flex;
728 flex-direction: column;
729 align-items: center;
730 gap: 10px;
731 max-height: 90%;
732 overflow: auto;
733}
734.result h2 {
735 margin: 0;
736 font-size: 24px;
737 color: var(--gold);
738}
739.result .wt {
740 color: var(--ivory);
741 opacity: 0.8;
742}
743.tai-list {
744 display: grid;
745 grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
746 gap: 2px 18px;
747 font-size: 14px;
748 width: 100%;
749}
750.tai-item {
751 display: flex;
752 align-items: baseline;
753 justify-content: space-between;
754 gap: 12px;
755 padding: 2px 0;
756 border-bottom: 1px dotted rgba(246, 240, 226, 0.2);
757}
758.tai-name {
759 display: flex;
760 flex-direction: column;
761 line-height: 1.15;
762}
763.tai-name em {
764 font-style: normal;
765 font-size: 10px;
766 letter-spacing: 0.08em;
767 text-transform: uppercase;
768 opacity: 0.55;
769}
770.tai-val {
771 font-weight: 700;
772 color: var(--gold);
773}
774/* 胡牌 — the arrow that lands on the winner's edge of the table. Sits over the
775 strip rather than in it, so the hand underneath keeps the height it had and
776 nothing shifts when it appears. */
777.win-arrow {
778 position: absolute;
779 top: 0;
780 left: 50%;
781 font-size: 52px;
782 line-height: 0.9;
783 color: var(--gold);
784 text-shadow: 0 0 6px #0a3126, 0 0 20px rgba(217, 180, 92, 0.9);
785 pointer-events: none;
786 z-index: 4;
787 animation: arrow-nudge 0.9s ease-in-out infinite;
788}
789@keyframes arrow-nudge {
790 0%,
791 100% {
792 transform: translate(-50%, 0);
793 }
794 50% {
795 transform: translate(-50%, 9px);
796 }
797}
798
799/* 流局 — who was there and what they were sitting on. */
800.ready-list {
801 display: flex;
802 flex-direction: column;
803 gap: 6px;
804 margin-top: 2px;
805}
806.ready-row {
807 display: flex;
808 align-items: center;
809 gap: 8px;
810 font-size: 14px;
811}
812.ready-row.none {
813 opacity: 0.45;
814}
815.ready-name {
816 min-width: 6.5em;
817 text-align: right;
818}
819.ready-row .tag.ting {
820 font-size: 11px;
821 padding: 1px 6px;
822 border-radius: 999px;
823 background: rgba(46, 170, 120, 0.2);
824 border: 1px solid rgba(46, 170, 120, 0.6);
825 white-space: nowrap;
826}
827.ready-tiles {
828 display: flex;
829 flex-wrap: wrap;
830 gap: 2px;
831 /* Tiles size themselves off their strip's --tile-w, and the centre is not in
832 one — without a size of its own here they come out 0 x 0 and vanish. */
833 --tile-w: 46px;
834}
835.ready-no {
836 font-size: 12px;
837 letter-spacing: 0.04em;
838}
839
840.deltas {
841 display: flex;
842 gap: 16px;
843 font-size: 14px;
844 opacity: 0.9;
845}
846.deltas .up {
847 color: #5fd39a;
848}
849.deltas .down {
850 color: #ff8a80;
851}
852
853/* ---------- help panel ---------- */
854.helpbtn {
855 margin-left: 2px;
856 width: 22px;
857 height: 22px;
858 border-radius: 50%;
859 border: 1px solid rgba(246, 240, 226, 0.35);
860 background: transparent;
861 color: rgba(246, 240, 226, 0.75);
862 font-family: inherit;
863 font-size: 12px;
864 line-height: 1;
865 cursor: pointer;
866}
867/* The rules panel gets its own deeper slot so it is comfortable to read. */
868.help-slot {
869 position: absolute;
870 inset: 0;
871 z-index: 30;
872 background: rgba(4, 18, 14, 0.55);
873}
874.help-slot.slot-bottom,
875.help-slot.slot-top,
876.help-slot.slot-left,
877.help-slot.slot-right {
878 inset: 0;
879 width: auto;
880 height: auto;
881}
882.help {
883 position: absolute;
884 top: 50%;
885 left: 50%;
886 height: var(--hd);
887 z-index: 31;
888 display: flex;
889 flex-direction: column;
890 background: rgba(8, 32, 25, 0.985);
891 border: 1px solid rgba(217, 180, 92, 0.4);
892 border-radius: 12px;
893 padding: 10px 18px 14px;
894 box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
895}
896/* Anchored to the asking player's own edge, and rotated to face them. The
897 leading translate is applied last, so it shifts the box in screen space. */
898.help-slot.slot-bottom .help,
899.help-slot.slot-top .help {
900 width: calc(100vw - 60px);
901 --hd: min(52vh, 560px);
902 --off: calc(50vh - var(--hd) / 2 - 14px);
903}
904.help-slot.slot-left .help,
905.help-slot.slot-right .help {
906 width: calc(100vh - 60px);
907 --hd: min(52vw, 560px);
908 --off: calc(50vw - var(--hd) / 2 - 14px);
909}
910.help-slot.slot-bottom .help {
911 translate: 0 var(--off);
912}
913.help-slot.slot-top .help {
914 translate: 0 calc(var(--off) * -1);
915}
916.help-slot.slot-left .help {
917 translate: calc(var(--off) * -1) 0;
918}
919.help-slot.slot-right .help {
920 translate: var(--off) 0;
921}
922.help-head {
923 display: flex;
924 align-items: center;
925 justify-content: space-between;
926 gap: 16px;
927 flex: none;
928}
929.help-head h3 {
930 margin: 0;
931 font-size: 15px;
932 font-weight: 400;
933 letter-spacing: 0.08em;
934 color: var(--gold);
935}
936.help-head .btn {
937 min-height: 30px;
938 padding: 2px 12px;
939 font-size: 13px;
940}
941.help-body {
942 flex: 1 1 0;
943 min-height: 0;
944 overflow-y: auto;
945 columns: 4;
946 column-gap: 26px;
947 column-fill: auto;
948 padding-top: 8px;
949 overflow-x: auto;
950 scrollbar-width: thin;
951}
952.help-body section {
953 break-inside: avoid;
954 margin-bottom: 10px;
955}
956.help-body h4 {
957 margin: 0 0 2px;
958 font-size: 13px;
959 font-weight: 400;
960 color: var(--gold);
961 letter-spacing: 0.06em;
962}
963.help-body h4 em {
964 font-style: normal;
965 font-size: 9px;
966 letter-spacing: 0.12em;
967 text-transform: uppercase;
968 opacity: 0.55;
969 margin-left: 4px;
970}
971.help-body p {
972 margin: 0 0 4px;
973 display: flex;
974 flex-direction: column;
975 line-height: 1.35;
976 font-size: 11.5px;
977}
978.help-body p em {
979 font-style: normal;
980 font-size: 10.5px;
981 opacity: 0.6;
982}
983.help-body .credit {
984 opacity: 0.6;
985}
986
987/* ---------- lobby resume ---------- */
988.lobby .sub {
989 font-size: 0.95rem;
990}
991.resume {
992 margin-top: 1.4rem;
993 padding: 12px 26px;
994 border: 1px solid rgba(217, 180, 92, 0.35);
995 border-radius: 12px;
996 background: rgba(0, 0, 0, 0.2);
997 text-align: center;
998 display: flex;
999 flex-direction: column;
1000 gap: 6px;
1001}
1002.resume-line {
1003 font-size: 14px;
1004 color: var(--gold);
1005 letter-spacing: 0.06em;
1006}
1007.resume-scores {
1008 display: flex;
1009 gap: 18px;
1010 justify-content: center;
1011 font-size: 13px;
1012 opacity: 0.8;
1013}
1014.resume-scores b {
1015 color: var(--gold);
1016 font-weight: 600;
1017}
1018.resume-when {
1019 font-size: 11px;
1020 opacity: 0.45;
1021 letter-spacing: 0.08em;
1022}
1023.lobby-buttons {
1024 display: flex;
1025 flex-direction: column;
1026 gap: 12px;
1027 margin-top: 1.6rem;
1028 align-items: stretch;
1029}
1030/* The two ways to play carry a line of explanation under the name. */
1031.lobby-buttons .big-btn {
1032 display: flex;
1033 flex-direction: column;
1034 gap: 3px;
1035 min-width: 360px;
1036 padding: 12px 28px;
1037}
1038.lobby-buttons .big-btn em {
1039 font-style: normal;
1040 font-size: 12px;
1041 font-weight: 400;
1042 letter-spacing: 0.03em;
1043 opacity: 0.72;
1044}
1045
1046/* ---------- sound ---------- */
1047.lobby-extras {
1048 margin-top: 1.4rem;
1049 display: flex;
1050 gap: 12px;
1051 align-items: center;
1052}
1053/* Small rounded button for the things that sit under the main choices. */
1054.btn.pill {
1055 min-height: 34px;
1056 padding: 2px 14px;
1057 font-size: 13px;
1058 letter-spacing: 0.06em;
1059 border-radius: 999px;
1060 gap: 8px;
1061}
1062.sound-toggle.on {
1063 border-color: rgba(217, 180, 92, 0.7);
1064 color: var(--gold);
1065}
1066.sound-toggle.compact {
1067 min-height: 28px;
1068 padding: 2px 9px;
1069 font-size: 15px;
1070}
1071.sound-toggle .spk {
1072 font-size: 15px;
1073 line-height: 1;
1074}
1075
1076/* ---------- shared table controls ---------- */
1077/* Bottom-right of the centre square, opposite the log. Unrotated on purpose:
1078 it belongs to the table, not to a seat. */
1079.table-controls {
1080 position: absolute;
1081 /* Over the pile, same as .result. */
1082 z-index: 2;
1083 right: 10px;
1084 bottom: 8px;
1085 display: flex;
1086 align-items: center;
1087 gap: 8px;
1088}
1089.btn.undo {
1090 min-height: 34px;
1091 padding: 3px 14px;
1092 border-radius: 999px;
1093 gap: 9px;
1094 max-width: 40vh;
1095}
1096.undo-arrow {
1097 font-size: 17px;
1098 line-height: 1;
1099 color: var(--gold);
1100}
1101.undo-what {
1102 display: flex;
1103 flex-direction: column;
1104 align-items: flex-start;
1105 line-height: 1.15;
1106 min-width: 0;
1107}
1108.undo-what .zh {
1109 font-size: 13px;
1110 letter-spacing: 0.05em;
1111}
1112.undo-what .en {
1113 font-size: 9px;
1114 letter-spacing: 0.09em;
1115 text-transform: uppercase;
1116 opacity: 0.55;
1117 white-space: nowrap;
1118 overflow: hidden;
1119 text-overflow: ellipsis;
1120 max-width: 100%;
1121}
1122
1123/* ---------- result: one block per winner ---------- */
1124.result .win {
1125 display: flex;
1126 flex-direction: column;
1127 align-items: center;
1128 gap: 8px;
1129 width: 100%;
1130}
1131.result .win + .win {
1132 padding-top: 10px;
1133 border-top: 1px solid rgba(217, 180, 92, 0.3);
1134}
1135.result .liable {
1136 font-size: 12px;
1137 letter-spacing: 0.08em;
1138 color: #ffb4a2;
1139}
1140.result .multi {
1141 font-size: 12px;
1142 letter-spacing: 0.1em;
1143 color: var(--gold);
1144 opacity: 0.85;
1145}
1146
1147/* ---------- settings ---------- */
1148.over-buttons {
1149 display: flex;
1150 gap: 12px;
1151 align-items: center;
1152}
1153.settings-scrim {
1154 position: absolute;
1155 inset: 0;
1156 z-index: 40;
1157 background: rgba(4, 18, 14, 0.72);
1158 display: flex;
1159 align-items: center;
1160 justify-content: center;
1161 padding: 20px;
1162}
1163.settings {
1164 width: min(1180px, 100%);
1165 max-height: 100%;
1166 display: flex;
1167 flex-direction: column;
1168 background: rgba(8, 32, 25, 0.99);
1169 border: 1px solid rgba(217, 180, 92, 0.4);
1170 border-radius: 14px;
1171 box-shadow: 0 18px 50px rgba(0, 0, 0, 0.6);
1172 padding: 16px 28px 22px;
1173}
1174.settings-head {
1175 display: flex;
1176 align-items: center;
1177 justify-content: space-between;
1178 gap: 16px;
1179 flex: none;
1180 padding-bottom: 12px;
1181 border-bottom: 1px solid rgba(246, 240, 226, 0.12);
1182}
1183.settings-head h3 {
1184 margin: 0;
1185 font-size: 22px;
1186 font-weight: 400;
1187 letter-spacing: 0.08em;
1188 color: var(--gold);
1189}
1190.settings-head-btns {
1191 display: flex;
1192 gap: 10px;
1193}
1194.settings-head .btn {
1195 min-height: 46px;
1196 padding: 2px 22px;
1197 font-size: 17px;
1198}
1199.settings-body {
1200 /* `1 1 auto`, not `1 1 0`: the panel's own height comes from this content,
1201 so a zero basis collapses the pair of them to nothing. `max-height` on the
1202 panel is what brings the scrollbar back at the other end. */
1203 flex: 1 1 auto;
1204 min-height: 0;
1205 overflow-y: auto;
1206 scrollbar-width: thin;
1207 padding-top: 14px;
1208 /* Columns rather than a grid: grid rows would size to the tallest section
1209 and leave the shorter one padded with air, which is what pushed the last
1210 house rule off the bottom. */
1211 columns: 2;
1212 column-gap: 44px;
1213}
1214.settings-body section {
1215 break-inside: avoid;
1216 margin-bottom: 22px;
1217}
1218.settings-body h4 {
1219 margin: 0 0 10px;
1220 font-size: 16px;
1221 font-weight: 400;
1222 color: var(--gold);
1223 letter-spacing: 0.06em;
1224}
1225.settings-body h4 em {
1226 font-style: normal;
1227 font-size: 11px;
1228 letter-spacing: 0.12em;
1229 text-transform: uppercase;
1230 opacity: 0.55;
1231 margin-left: 5px;
1232}
1233.settings .field {
1234 display: flex;
1235 align-items: center;
1236 gap: 14px;
1237 padding: 5px 0;
1238}
1239.settings .f-label {
1240 display: flex;
1241 align-items: baseline;
1242 gap: 6px;
1243 font-size: 16px;
1244 /* Wide enough for 起始籌碼 STARTING CHIPS, so every input in a section
1245 starts at the same place. */
1246 min-width: 196px;
1247 opacity: 0.85;
1248}
1249/* ...except the player rows, which are labelled 1–4 and need no such room. */
1250.settings .names .f-label {
1251 min-width: 26px;
1252}
1253.settings .f-label em {
1254 font-style: normal;
1255 font-size: 11px;
1256 letter-spacing: 0.1em;
1257 text-transform: uppercase;
1258 opacity: 0.55;
1259}
1260.settings input[type='text'],
1261.settings input:not([type]),
1262.settings input[type='number'] {
1263 flex: 1 1 0;
1264 min-width: 0;
1265 font-family: inherit;
1266 font-size: 18px;
1267 color: var(--ivory);
1268 background: rgba(0, 0, 0, 0.28);
1269 border: 1px solid rgba(246, 240, 226, 0.25);
1270 border-radius: 8px;
1271 padding: 11px 14px;
1272 min-height: 48px;
1273 user-select: text;
1274}
1275.settings input:focus {
1276 outline: none;
1277 border-color: rgba(217, 180, 92, 0.8);
1278}
1279.settings input[type='number'] {
1280 max-width: 150px;
1281}
1282.settings input[type='range'] {
1283 flex: 1 1 0;
1284 height: 34px;
1285 accent-color: var(--gold);
1286}
1287.settings input:disabled {
1288 opacity: 0.4;
1289}
1290.settings .switch {
1291 display: grid;
1292 grid-template-columns: auto 1fr;
1293 gap: 3px 14px;
1294 align-items: center;
1295 padding: 9px 0;
1296 cursor: pointer;
1297}
1298.settings .switch input {
1299 width: 26px;
1300 height: 26px;
1301 accent-color: var(--gold);
1302 cursor: pointer;
1303}
1304.settings .sw-name {
1305 display: flex;
1306 align-items: baseline;
1307 gap: 8px;
1308 font-size: 17px;
1309}
1310.settings .sw-name em {
1311 font-style: normal;
1312 font-size: 11px;
1313 letter-spacing: 0.1em;
1314 text-transform: uppercase;
1315 opacity: 0.55;
1316}
1317.settings .note {
1318 grid-column: 2;
1319 font-size: 13px;
1320 line-height: 1.4;
1321 opacity: 0.55;
1322}
1323.settings .switch.dead {
1324 opacity: 0.4;
1325 cursor: default;
1326}
1327.settings .note.lock {
1328 grid-column: auto;
1329 margin: 4px 0 0;
1330 color: #ffb4a2;
1331 opacity: 0.75;
1332}
1333
1334/* ============================================================================
1335 compact — a phone
1336
1337 The table layout is four strips of --depth arranged around a centre square,
1338 which wants about 700px each way; a phone has that in neither, and in
1339 landscape the top and bottom strips simply overlap. It also assumes four
1340 people sitting around a screen lying flat, which is not what a phone is.
1341
1342 Compact throws both assumptions out. Nothing is rotated — every label reads
1343 upright for the one person holding the phone — the table becomes a grid, the
1344 three other seats shrink to cards showing what is public about them, and the
1345 depth that frees up goes to the hand, which wraps when it has to.
1346
1347 The `compact` class is put on <html> by useCompact(); the breakpoint itself
1348 lives in ui/compact.ts, so it is written down once.
1349 ============================================================================ */
1350
1351.compact .table {
1352 display: grid;
1353 gap: 2px;
1354 /* Landscape is where a notch eats into the sides. */
1355 padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom)
1356 env(safe-area-inset-left);
1357 grid-template-columns: 1fr 1fr 1fr;
1358 grid-template-rows: minmax(0, 1fr) auto auto;
1359 grid-template-areas:
1360 'left across right'
1361 'bar bar bar'
1362 'mine mine mine';
1363}
1364.compact .slot {
1365 position: relative;
1366 inset: auto;
1367 width: auto;
1368 height: auto;
1369 min-width: 0;
1370 min-height: 0;
1371}
1372/* `.slot` and not the edge class alone: the rules sheet carries an edge class
1373 of its own, and would otherwise be parked in the grid area named here. */
1374.compact .slot.slot-left {
1375 grid-area: left;
1376}
1377.compact .slot.slot-top {
1378 grid-area: across;
1379}
1380.compact .slot.slot-right {
1381 grid-area: right;
1382}
1383.compact .slot.slot-bottom {
1384 grid-area: mine;
1385}
1386.compact .center {
1387 grid-area: bar;
1388}
1389/* The strip no longer floats in the middle of a slot — it is the slot. */
1390.compact .seat {
1391 position: relative;
1392 top: auto;
1393 left: auto;
1394 width: auto;
1395 height: 100%;
1396 padding: 2px 4px 3px;
1397}
1398/* Off to one side rather than dead centre: a card is narrow enough that the
1399 arrow would otherwise land on the name it is pointing out. */
1400.compact .win-arrow {
1401 font-size: 24px;
1402 left: 14px;
1403}
1404
1405/* ---------- the other three seats ---------- */
1406/* Read top-down: who they are, how many tiles they hold, what they have laid
1407 down, what they have thrown. The hand is the least interesting of those and
1408 goes smallest; the discards get whatever room is left, since reading them is
1409 the whole of playing against somebody. */
1410.compact .opp .seat {
1411 --tile-w: clamp(13px, calc((100vw / 3 - 20px) / 8.6), 32px);
1412 justify-content: flex-start;
1413 gap: 2px;
1414 border: 1px solid rgba(246, 240, 226, 0.09);
1415 border-radius: 8px;
1416 background: rgba(0, 0, 0, 0.14);
1417}
1418.compact .opp .nameplate {
1419 order: 0;
1420}
1421.compact .opp .hand {
1422 order: 1;
1423}
1424.compact .opp .melds {
1425 order: 2;
1426}
1427.compact .opp .discards {
1428 order: 3;
1429}
1430.compact .opp .actionbar {
1431 order: 4;
1432}
1433
1434.compact .opp .nameplate {
1435 flex-wrap: wrap;
1436 justify-content: center;
1437 gap: 3px 5px;
1438 font-size: 10px;
1439 padding-top: 0;
1440 row-gap: 1px;
1441}
1442.compact .opp .nameplate .wind {
1443 font-size: 13px;
1444}
1445.compact .opp .nameplate .wind em,
1446.compact .opp .nameplate .helpbtn,
1447.compact .opp .nameplate .sort {
1448 display: none;
1449}
1450.compact .opp .nameplate .chips {
1451 font-size: 13px;
1452 margin-left: 0;
1453}
1454.compact .opp .nameplate .tag {
1455 font-size: 9px;
1456 padding: 0 4px;
1457}
1458/* Face-down backs: a count you can see at a glance, nothing more. */
1459.compact .opp .hand {
1460 flex-wrap: wrap;
1461 justify-content: center;
1462 height: auto;
1463 padding-top: 0;
1464 gap: 1px;
1465 max-width: 100%;
1466}
1467.compact .opp .hand .tile {
1468 --tw: calc(var(--tile-w) * 0.52);
1469}
1470.compact .opp .melds {
1471 gap: 5px;
1472 min-height: 0;
1473 max-width: 100%;
1474}
1475.compact .opp .meld.flowers {
1476 margin-left: 5px;
1477 padding-left: 5px;
1478}
1479.compact .opp .discards {
1480 flex: 1 1 auto;
1481 max-width: 100%;
1482 min-height: 0;
1483 overflow-y: auto;
1484 align-content: flex-start;
1485}
1486/* No height is reserved here: an opponent's bar is empty except in hotseat,
1487 and a card that stays still matters less than the room it costs. */
1488.compact .opp .actionbar {
1489 min-height: 0;
1490 padding-right: 0;
1491 justify-content: center;
1492 gap: 4px;
1493}
1494.compact .opp .actionbar .btn {
1495 height: auto;
1496 min-height: 34px;
1497 padding: 2px 8px;
1498}
1499.compact .opp .actionbar .bi .zh {
1500 font-size: 14px;
1501}
1502.compact .opp .delta {
1503 font-size: 15px;
1504}
1505
1506/* ---------- your own seat ---------- */
1507/* A grid rather than the stack the strips use, so the melds and the discards
1508 share one line and the hand gets the depth that saves. */
1509.compact .mine .seat {
1510 --tile-w: min(calc((100vw - 24px) / 18.3), 54px);
1511 display: grid;
1512 /* `minmax(0, auto)`, so a full set of melds wraps inside its column instead
1513 of shouldering the discards off the end of the strip. */
1514 grid-template-columns: minmax(0, auto) minmax(0, 1fr);
1515 grid-template-areas:
1516 'melds discards'
1517 'bar bar'
1518 'hand hand'
1519 'name name';
1520 align-content: end;
1521 align-items: end;
1522 justify-items: center;
1523 gap: 2px 8px;
1524 padding: 2px 6px 3px;
1525}
1526.compact .mine .melds {
1527 grid-area: melds;
1528 justify-content: flex-start;
1529 min-height: 0;
1530 gap: 6px;
1531}
1532.compact .mine .discards {
1533 grid-area: discards;
1534 /* One line that scrolls, kept pinned to its end by Seat.tsx — wrapping would
1535 cost a second row of depth the hand wants more. Reserved even while empty,
1536 since this row and the melds beside it share it: without that the strip
1537 would jump the first time either one appeared. */
1538 flex: none;
1539 flex-wrap: nowrap;
1540 justify-content: flex-start;
1541 align-self: end;
1542 max-width: 100%;
1543 width: 100%;
1544 min-height: calc(var(--tile-w) * 0.9);
1545 margin-bottom: 0;
1546 overflow-x: auto;
1547 overflow-y: hidden;
1548 /* Melds and discards end up shoulder to shoulder here, and both are rows of
1549 tiles — the rule and the smaller face are what tell them apart. */
1550 border-left: 1px solid rgba(246, 240, 226, 0.16);
1551 padding-left: 8px;
1552 opacity: 0.85;
1553}
1554.compact .mine .discards .tile {
1555 --tw: calc(var(--tile-w) * 0.5);
1556}
1557.compact .mine .actionbar {
1558 grid-area: bar;
1559 --bar-h: 44px;
1560 justify-content: center;
1561 padding-right: 0;
1562 gap: 6px;
1563}
1564.compact .mine .hand {
1565 grid-area: hand;
1566 flex-wrap: wrap;
1567 align-content: flex-end;
1568 justify-content: center;
1569 height: auto;
1570 max-width: 100%;
1571 /* The row gap matches the padding for the same reason: the 摸 tag hangs above
1572 its tile, and on a wrapped hand that is somebody else's row. */
1573 gap: calc(var(--tile-w) * 0.34) 2px;
1574 padding-top: calc(var(--tile-w) * 0.34);
1575}
1576.compact .mine .nameplate {
1577 grid-area: name;
1578 flex-wrap: wrap;
1579 justify-content: center;
1580 gap: 3px 7px;
1581 font-size: 11px;
1582 padding-top: 0;
1583}
1584.compact .mine .nameplate .wind {
1585 font-size: 15px;
1586}
1587.compact .mine .nameplate .wind em {
1588 display: none;
1589}
1590.compact .mine .nameplate .chips {
1591 font-size: 14px;
1592}
1593.compact .mine .nameplate .btn.sort {
1594 min-height: 26px;
1595 padding: 1px 9px;
1596 font-size: 12px;
1597}
1598.compact .mine .nameplate .helpbtn {
1599 width: 26px;
1600 height: 26px;
1601 font-size: 13px;
1602}
1603
1604/* ---------- the middle ---------- */
1605/* One bar across the table rather than a square in the middle of it. */
1606.compact .center {
1607 position: relative;
1608 inset: auto;
1609 flex-direction: row;
1610 align-items: center;
1611 justify-content: space-between;
1612 gap: 8px;
1613 padding: 1px 6px;
1614 min-height: 44px;
1615}
1616.compact .facing {
1617 flex: 1 1 auto;
1618 flex-direction: row;
1619 align-items: center;
1620 justify-content: center;
1621 gap: 10px;
1622 min-width: 0;
1623}
1624.compact .last-discard {
1625 --tile-w: 30px;
1626 flex-direction: row;
1627 align-items: center;
1628 gap: 6px;
1629}
1630.compact .ld-label {
1631 font-size: 10px;
1632}
1633.compact .prompt {
1634 font-size: 12px;
1635 text-align: center;
1636}
1637.compact .table-controls {
1638 position: static;
1639 flex: none;
1640}
1641.compact .btn.undo {
1642 max-width: 34vw;
1643 min-height: 30px;
1644 padding: 2px 10px;
1645}
1646.compact .undo-what .zh {
1647 font-size: 11px;
1648}
1649.compact .undo-what .en {
1650 display: none;
1651}
1652/* What the wall square was telling you, in the room a bar has. */
1653.wall-pill {
1654 display: flex;
1655 align-items: baseline;
1656 gap: 5px;
1657 flex: none;
1658 letter-spacing: 0.06em;
1659 opacity: 0.75;
1660}
1661.wall-pill b {
1662 font-size: 18px;
1663 font-weight: 900;
1664 color: var(--gold);
1665}
1666.wall-pill em {
1667 font-style: normal;
1668 font-size: 9px;
1669 text-transform: uppercase;
1670 opacity: 0.7;
1671}
1672
1673/* The hand result covers the table instead of sitting in a square that is no
1674 longer there. */
1675.compact .result {
1676 position: fixed;
1677 top: 50%;
1678 left: 50%;
1679 transform: translate(-50%, -50%);
1680 z-index: 25;
1681 max-width: min(560px, 94vw);
1682 max-height: 92dvh;
1683 padding: 12px 16px;
1684 gap: 8px;
1685}
1686.compact .result h2 {
1687 font-size: 18px;
1688}
1689.compact .tai-list {
1690 grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
1691 gap: 1px 12px;
1692 font-size: 12px;
1693}
1694.compact .ready-row {
1695 font-size: 12px;
1696}
1697.compact .ready-name {
1698 min-width: 4.5em;
1699}
1700.compact .ready-tiles {
1701 --tile-w: 26px;
1702}
1703.compact .deltas {
1704 flex-wrap: wrap;
1705 justify-content: center;
1706 gap: 6px 14px;
1707 font-size: 12px;
1708}
1709.compact .big-btn {
1710 font-size: 18px;
1711 padding: 8px 22px;
1712 min-height: 44px;
1713}
1714
1715/* ---------- portrait ---------- */
1716/* Held upright there is no room for three cards side by side, so they stack in
1717 the order play reaches them, and the hand wraps to two rows. */
1718@media (orientation: portrait) {
1719 .compact .table {
1720 grid-template-columns: 1fr;
1721 grid-template-rows: repeat(3, minmax(0, 1fr)) auto auto;
1722 grid-template-areas:
1723 'right'
1724 'across'
1725 'left'
1726 'bar'
1727 'mine';
1728 }
1729 /* A card gets the whole width here, so the tiles can be read rather than
1730 merely counted; the discards wrap to a second line when they need it. */
1731 .compact .opp .seat {
1732 --tile-w: clamp(18px, calc((100vw - 26px) / 13), 34px);
1733 }
1734 .compact .mine .seat {
1735 --tile-w: min(calc((100vw - 26px) / 9.4), 54px);
1736 }
1737 /* A phone's width will not hold the wall count, who discarded what, the claim
1738 prompt and the table's own buttons on one line. The readout — the part that
1739 changes — takes a line of its own underneath. The room comes out of the
1740 other seats, which are the flexible rows, so the hand never moves. */
1741 .compact .center {
1742 flex-wrap: wrap;
1743 row-gap: 2px;
1744 }
1745 .compact .wall-pill {
1746 order: 0;
1747 }
1748 .compact .table-controls {
1749 order: 1;
1750 }
1751 .compact .facing {
1752 order: 2;
1753 flex-basis: 100%;
1754 }
1755}
1756
1757/* ---------- the panels ---------- */
1758.compact .lobby {
1759 justify-content: flex-start;
1760 overflow-y: auto;
1761 gap: 0.25rem;
1762 padding: 12px 14px calc(14px + env(safe-area-inset-bottom));
1763}
1764.compact .lobby > * {
1765 flex: none;
1766}
1767/* Sized off both axes: a phone held sideways has width to spare and no height
1768 at all, and the title was the first thing to push the buttons off. */
1769.compact .lobby h1 {
1770 font-size: clamp(1.9rem, min(9vw, 11vh), 3.2rem);
1771 margin-bottom: 0.2rem;
1772}
1773.compact .lobby .sub {
1774 font-size: 0.78rem;
1775}
1776/* The screen dividers this asks for are a four-round-a-table idea, and nobody
1777 props cards against a phone. */
1778.compact .lobby .hint {
1779 display: none;
1780}
1781.compact .resume {
1782 margin-top: 0.8rem;
1783 padding: 8px 16px;
1784}
1785.compact .resume-scores {
1786 flex-wrap: wrap;
1787 gap: 4px 12px;
1788 font-size: 12px;
1789}
1790.compact .lobby-buttons {
1791 margin-top: 0.9rem;
1792 gap: 8px;
1793 width: min(460px, 100%);
1794}
1795.compact .lobby-buttons .big-btn {
1796 min-width: 0;
1797 width: 100%;
1798 padding: 8px 16px;
1799}
1800.compact .lobby-extras {
1801 margin-top: 0.9rem;
1802}
1803/* Sideways there is barely 390px of height for a title, a saved game, three
1804 ways to start and the settings link. Everything gives a little so the last of
1805 them lands above the fold, rather than behind a scroll nobody looks for. */
1806@media (orientation: landscape) {
1807 .compact .lobby h1 {
1808 font-size: clamp(1.6rem, 9vh, 2.6rem);
1809 }
1810 .compact .lobby .sub,
1811 .compact .resume-when {
1812 display: none;
1813 }
1814 .compact .resume {
1815 margin-top: 0.4rem;
1816 padding: 6px 14px;
1817 }
1818 .compact .resume-line {
1819 font-size: 12px;
1820 }
1821 .compact .lobby-buttons {
1822 margin-top: 0.5rem;
1823 }
1824 .compact .lobby-buttons .big-btn {
1825 min-height: 40px;
1826 padding: 5px 16px;
1827 font-size: 19px;
1828 }
1829 .compact .lobby-extras {
1830 margin-top: 0.5rem;
1831 }
1832}
1833
1834.compact .help-slot .help {
1835 width: calc(100vw - 16px);
1836 --hd: calc(100dvh - 16px);
1837 translate: none;
1838 padding: 8px 12px 10px;
1839}
1840.compact .help-head h3 {
1841 font-size: 13px;
1842}
1843/* The strip version fills four columns and scrolls sideways, which reads fine
1844 across a laptop's width and not at all on a phone. One column, scrolled the
1845 way every other sheet on the device is. */
1846.compact .help-body {
1847 columns: auto;
1848 column-gap: normal;
1849 overflow-x: hidden;
1850}
1851
1852.compact .settings-scrim {
1853 padding: 6px;
1854}
1855.compact .settings {
1856 padding: 10px 14px 14px;
1857}
1858.compact .settings-head h3 {
1859 font-size: 16px;
1860}
1861.compact .settings-head .btn {
1862 min-height: 38px;
1863 padding: 2px 14px;
1864 font-size: 14px;
1865}
1866/* Two columns sideways, where there is width and no height; one held upright,
1867 where it is the other way round. */
1868.compact .settings-body {
1869 columns: 2;
1870 column-gap: 26px;
1871 padding-top: 10px;
1872}
1873@media (orientation: portrait) {
1874 .compact .settings-body {
1875 columns: 1;
1876 }
1877}
1878.compact .settings-body section {
1879 margin-bottom: 14px;
1880}
1881.compact .settings-body h4 {
1882 margin-bottom: 6px;
1883 font-size: 14px;
1884}
1885.compact .settings .f-label {
1886 min-width: 116px;
1887 font-size: 14px;
1888}
1889.compact .settings input[type='text'],
1890.compact .settings input:not([type]),
1891.compact .settings input[type='number'] {
1892 font-size: 15px;
1893 min-height: 40px;
1894 padding: 8px 10px;
1895}
1896.compact .settings .sw-name {
1897 font-size: 14px;
1898}
1899.compact .settings .note {
1900 font-size: 11px;
1901}
1902
1903/* ---------- fullscreen ---------- */
1904/* Sized to match the sound toggle it stands beside, in both its forms. */
1905.fs-toggle {
1906 min-height: 34px;
1907 padding: 2px 14px;
1908}
1909.fs-toggle.compact {
1910 min-height: 28px;
1911 padding: 3px 9px;
1912}
1913.fs-toggle.on {
1914 border-color: rgba(217, 180, 92, 0.7);
1915 color: var(--gold);
1916}
1917.fs-toggle .fs-icon {
1918 display: block;
1919 flex: none;
1920}