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