| ⋯ 239 unchanged lines |
| 240 | 240 | | room, or at somebody else's joke. */ |
| 241 | 241 | | .seat.awaited { |
| 242 | 242 | | background: linear-gradient(to top, rgba(217, 180, 92, 0.34), transparent 78%); |
| 243 | + | animation: spotlight 2.6s ease-in-out infinite; |
| 244 | + | } |
| 245 | + | /* And it breathes. A gold wash that simply sits there is read once and then |
| 246 | + | stops being read — it becomes part of the felt, which is exactly what this |
| 247 | + | must not be, because the person it is for is the one who has not looked up. |
| 248 | + | Movement is the thing peripheral vision answers to. |
| 249 | + | |
| 250 | + | Slow, and never all the way off: two and a half seconds is a breath rather |
| 251 | + | than a blink, and the low end still says clearly whose go it is to anybody |
| 252 | + | who glances at that moment. The red on `claiming` runs at half this and is |
| 253 | + | meant to: a claim window closes and this does not. */ |
| 254 | + | @keyframes spotlight { |
| 255 | + | 50% { |
| 256 | + | background: linear-gradient(to top, rgba(217, 180, 92, 0.62), transparent 86%); |
| 257 | + | } |
| 243 | 258 | | } |
| 244 | 259 | | /* And the name at the outer end of the strip, since that is what somebody |
| 245 | 260 | | glancing up reads to find out who — the felt says *where*, the plate says |
| ⋯ 571 unchanged lines |
| 817 | 832 | | Ivory, not coloured: a mahjong tile is ivory and it is the *character* that |
| 818 | 833 | | is red or green. So the call takes the colour and the face never does. |
| 819 | 834 | | |
| 820 | | - | Full size only. A phone's bar is a row of its own with room to write on, and |
| 821 | | - | a thumb wants a button it can hit. */ |
| 835 | + | The phone gets them too. Its bar is a row of its own rather than the end of |
| 836 | + | the hand row, but a call is still a thing you do with a tile and it should |
| 837 | + | still look like one — so `.ctl-actions` is scoped in alongside, and sizes its |
| 838 | + | tiles off a `--tile-w` of its own that is what a thumb needs rather than what |
| 839 | + | seventeen across a hand allows. Not the compact *table*, where four hands |
| 840 | + | share one phone and a tile is too small to write a call on. */ |
| 822 | 841 | | /* Not a white rectangle standing in for a tile: the tile itself. The sheet the |
| 823 | 842 | | hand is drawn from has a forty-third cell with no character on it — a blank |
| 824 | 843 | | tile — and that is what a button is drawn with, at the same size, off the |
| ⋯ 7 unchanged lines |
| 832 | 851 | | Pressed, it turns over to the sheet's second row — the highlighted variant, |
| 833 | 852 | | which is what a tile lifted out of the hand switches to. The same gesture |
| 834 | 853 | | lights the same way whether it is a tile or a call. */ |
| 835 | | - | :root:not(.compact) .actionbar .btn { |
| 854 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn { |
| 836 | 855 | | position: relative; |
| 837 | 856 | | flex-direction: column; |
| 838 | 857 | | width: var(--tile-w); |
| ⋯ 14 unchanged lines |
| 853 | 872 | | /* Under the finger it lifts, the way a tile does — a good deal less far, since |
| 854 | 873 | | a button that jumped a quarter of its own height would be a button that moved |
| 855 | 874 | | out from under the click. */ |
| 856 | | - | :root:not(.compact) .actionbar .btn:hover { |
| 875 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn:hover { |
| 857 | 876 | | transform: translateY(calc(var(--tile-w) * -0.07)); |
| 858 | 877 | | filter: drop-shadow(0 5px 7px rgba(0, 0, 0, 0.5)); |
| 859 | 878 | | } |
| 860 | | - | :root:not(.compact) .actionbar .btn:active { |
| 879 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn:active { |
| 861 | 880 | | transform: translateY(1px); |
| 862 | 881 | | background-position: calc(var(--tile-w) * -42) calc(var(--tile-w) * -1.375); |
| 863 | 882 | | filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45)); |
| ⋯ 19 unchanged lines |
| 883 | 902 | | at column 42 is white from 14% of its width to 98%, and from 1% of its height |
| 884 | 903 | | to 91%. As padding rather than a nudge, so the grid inside centres on the |
| 885 | 904 | | face and the English sits along the face's own bottom edge. */ |
| 886 | | - | :root:not(.compact) .actionbar .btn .bi { |
| 905 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn .bi { |
| 887 | 906 | | display: grid; |
| 888 | 907 | | grid-template-columns: auto auto; |
| 889 | 908 | | grid-template-rows: 1fr auto; |
| ⋯ 6 unchanged lines |
| 896 | 915 | | padding: calc(var(--tile-w) * 0.015) calc(var(--tile-w) * 0.02) |
| 897 | 916 | | calc(var(--tile-w) * 0.125) calc(var(--tile-w) * 0.14); |
| 898 | 917 | | } |
| 899 | | - | :root:not(.compact) .actionbar .btn .bi .zh { |
| 918 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn .bi .zh { |
| 900 | 919 | | grid-row: 1; |
| 901 | 920 | | grid-column: 2; |
| 902 | 921 | | /* Its own width, centred in the column, rather than stretched to fill it — |
| ⋯ 13 unchanged lines |
| 916 | 935 | | needs out across every track it covers, so the empty 五萬 column took half of |
| 917 | 936 | | the difference and carried the reading right along with it. In one column it |
| 918 | 937 | | sizes that column and nothing else, and the pair of them centre together. */ |
| 919 | | - | :root:not(.compact) .actionbar .btn .bi .en { |
| 938 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn .bi .en { |
| 920 | 939 | | grid-row: 2; |
| 921 | 940 | | grid-column: 2; |
| 922 | 941 | | justify-self: center; |
| ⋯ 5 unchanged lines |
| 928 | 947 | | } |
| 929 | 948 | | /* An English-only table has nothing standing above it, so the name is the face |
| 930 | 949 | | rather than the caption under one. */ |
| 931 | | - | :root:not(.compact) .actionbar .btn .bi .en:first-child { |
| 950 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn .bi .en:first-child { |
| 932 | 951 | | grid-row: 1 / -1; |
| 933 | 952 | | font-size: calc(var(--tile-w) * 0.22); |
| 934 | 953 | | opacity: 0.85; |
| ⋯ 1 unchanged line |
| 936 | 955 | | /* Which tile it is about — 打出 五萬, 吃 三萬 四萬. Small, because the tile it |
| 937 | 956 | | names is lifted out of the row right beside it, and written the same way |
| 938 | 957 | | down the face as the call it belongs to. */ |
| 939 | | - | :root:not(.compact) .actionbar .btn .bi .bi-sub { |
| 958 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn .bi .bi-sub { |
| 940 | 959 | | grid-row: 1; |
| 941 | 960 | | grid-column: 1; |
| 942 | 961 | | justify-self: center; |
| ⋯ 4 unchanged lines |
| 947 | 966 | | opacity: 0.75; |
| 948 | 967 | | } |
| 949 | 968 | | /* The character carries the colour, the way the dragons do. */ |
| 950 | | - | :root:not(.compact) .actionbar .btn.claim.hu { |
| 969 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn.claim.hu { |
| 951 | 970 | | color: #b3261e; |
| 952 | 971 | | } |
| 953 | | - | :root:not(.compact) .actionbar .btn.claim.pung, |
| 954 | | - | :root:not(.compact) .actionbar .btn.claim.kong { |
| 972 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn.claim.pung, |
| 973 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn.claim.kong { |
| 955 | 974 | | color: #1c527d; |
| 956 | 975 | | } |
| 957 | | - | :root:not(.compact) .actionbar .btn.claim.chow { |
| 976 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn.claim.chow { |
| 958 | 977 | | color: #1d6640; |
| 959 | 978 | | } |
| 960 | 979 | | /* 看牌 is the only button a covered hand has, and it is the one button here |
| 961 | 980 | | that is not done *with* a tile — it is done to the card lying over them. So |
| 962 | 981 | | it wears the back rather than the face, and reads as the thing beside it. */ |
| 963 | | - | :root:not(.compact) .actionbar .btn.reveal { |
| 982 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn.reveal { |
| 964 | 983 | | background: url('./assets/back.png') center / 100% 100%; |
| 965 | 984 | | color: var(--ivory); |
| 966 | 985 | | text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); |
| ⋯ 2 unchanged lines |
| 969 | 988 | | the ring says so — which is what stops four face-down hands and no buttons |
| 970 | 989 | | reading as a table that has stopped rather than one waiting for somebody to |
| 971 | 990 | | look at their tiles. */ |
| 972 | | - | :root:not(.compact) .actionbar .btn.reveal.hot { |
| 991 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn.reveal.hot { |
| 973 | 992 | | outline: 2px solid var(--gold); |
| 974 | 993 | | outline-offset: -2px; |
| 975 | 994 | | /* Gold off the tile's own outline rather than off a rectangle behind it: the |
| ⋯ 3 unchanged lines |
| 979 | 998 | | } |
| 980 | 999 | | /* 過 and 摸牌 are not calls, so they are the plain face — and a pass reads as |
| 981 | 1000 | | the quiet one it is. */ |
| 982 | | - | :root:not(.compact) .actionbar .btn.pass, |
| 983 | | - | :root:not(.compact) .actionbar .btn.ghost { |
| 1001 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn.pass, |
| 1002 | + | :is(:root:not(.compact) .actionbar, .ctl-actions) .btn.ghost { |
| 984 | 1003 | | color: rgba(33, 38, 43, 0.65); |
| 985 | 1004 | | } |
| 986 | 1005 | | /* The one that plays a tile wore a gold rim drawn round the face. The face is |
| ⋯ 1595 unchanged lines |
| 2582 | 2601 | | screen, with the common screen doing everything else. Tiles get the room — |
| 2583 | 2602 | | they are the only thing this screen is for. */ |
| 2584 | 2603 | | .ctl { |
| 2585 | | - | /* The whole hand in one row: 17 tiles and their gaps must fit the width. */ |
| 2586 | | - | --tile-w: clamp(30px, calc((100vw - 56px) / 17.6), 76px); |
| 2604 | + | /* What a *button* is here: the calls are drawn as tiles, and a tile a thumb |
| 2605 | + | has to hit is not the same size as a tile in a row of seventeen. The hand |
| 2606 | + | sizes itself off how many tiles have to fit — see `.ctl-hand .hand` — so |
| 2607 | + | the two no longer have to be the same number. */ |
| 2608 | + | --tile-w: clamp(46px, 13vmin, 74px); |
| 2609 | + | --bar-h: calc(var(--tile-w) * 1.375); |
| 2587 | 2610 | | position: fixed; |
| 2588 | 2611 | | inset: 0; |
| 2589 | 2612 | | display: flex; |
| ⋯ 100 unchanged lines |
| 2690 | 2713 | | font-weight: 600; |
| 2691 | 2714 | | color: var(--gold); |
| 2692 | 2715 | | } |
| 2716 | + | /* A row of tiles, so it is laid out as one: they stand on a common line and |
| 2717 | + | the gap between them is the gap between tiles. */ |
| 2693 | 2718 | | .ctl-actions { |
| 2694 | 2719 | | display: flex; |
| 2695 | | - | align-items: center; |
| 2720 | + | align-items: flex-end; |
| 2696 | 2721 | | justify-content: center; |
| 2697 | | - | gap: 8px; |
| 2722 | + | gap: var(--hand-gap); |
| 2698 | 2723 | | flex-wrap: wrap; |
| 2724 | + | row-gap: 6px; |
| 2699 | 2725 | | min-height: var(--bar-h); |
| 2700 | | - | padding: 2px 10px; |
| 2726 | + | padding: 4px 10px; |
| 2701 | 2727 | | } |
| 2702 | 2728 | | .ctl-hint { |
| 2703 | 2729 | | font-size: 11px; |
| 2704 | 2730 | | opacity: 0.55; |
| 2705 | 2731 | | } |
| 2732 | + | /* The tiles get the middle of the screen. This is the one thing the phone is |
| 2733 | + | for, and a row pinned to the bottom edge left the hand sitting under the |
| 2734 | + | thumb holding the phone with the whole screen empty above it. */ |
| 2706 | 2735 | | .ctl-hand { |
| 2707 | | - | margin-top: auto; |
| 2736 | + | flex: 1; |
| 2708 | 2737 | | display: flex; |
| 2738 | + | align-items: center; |
| 2709 | 2739 | | justify-content: center; |
| 2740 | + | min-height: 0; |
| 2710 | 2741 | | padding: 0 8px calc(10px + env(safe-area-inset-bottom, 0px)); |
| 2711 | 2742 | | } |
| 2712 | 2743 | | .ctl-hand .hand { |
| 2744 | + | /* The whole hand in one row: 17 tiles and their gaps must fit the width. */ |
| 2745 | + | --tile-w: min(76px, calc((100vw - 56px) / 17.6)); |
| 2713 | 2746 | | flex-wrap: nowrap; |
| 2714 | 2747 | | } |
| 2715 | | - | /* Held upright, seventeen tiles will never make one row. Break into two and |
| 2716 | | - | size the tiles off half the count instead of shrinking them past reading. |
| 2717 | | - | The row gap leaves room for the 摸 tag over a tile in the lower row. */ |
| 2748 | + | /* Held upright, seventeen tiles will never make one row. |
| 2749 | + | |
| 2750 | + | So the hand is laid out as a grid rather than left to wrap: wrapping fills |
| 2751 | + | the first line and drops whatever is left onto the second, which for a |
| 2752 | + | sixteen-tile hand on a narrow screen is nine and seven, or ten and six as |
| 2753 | + | the width changes under it. A grid of `--hand-cols` — half the hand, rounded |
| 2754 | + | up, counted in ui/Hand — is two lines of the same length whatever the screen |
| 2755 | + | is doing, and the tiles are then sized off that count rather than shrunk to |
| 2756 | + | fit a row that was never going to hold them. |
| 2757 | + | |
| 2758 | + | The tile just drawn takes a line of its own above both, which is the same |
| 2759 | + | thing the daylight beside it says at a full-size table: this one is not part |
| 2760 | + | of the sorted hand yet. At the top rather than the bottom because that is |
| 2761 | + | where the eye starts, and because the 摸 tag over it needs air above it. */ |
| 2718 | 2762 | | @media (orientation: portrait) { |
| 2719 | | - | .ctl { |
| 2720 | | - | --tile-w: clamp(34px, calc((100vw - 44px) / 9.4), 76px); |
| 2721 | | - | } |
| 2722 | 2763 | | .ctl-hand .hand { |
| 2723 | | - | flex-wrap: wrap; |
| 2764 | + | --tile-w: min(76px, calc((100vw - 40px) / (var(--hand-cols, 9) + 0.4))); |
| 2765 | + | display: grid; |
| 2766 | + | grid-template-columns: repeat(var(--hand-cols, 9), var(--tile-w)); |
| 2724 | 2767 | | justify-content: center; |
| 2725 | | - | /* Bounded, or it stays one long row: a `flex: none` item is happy to |
| 2726 | | - | overflow its container rather than wrap inside it. */ |
| 2768 | + | gap: var(--hand-gap); |
| 2769 | + | row-gap: calc(var(--tile-w) * 0.5); |
| 2727 | 2770 | | width: 100%; |
| 2728 | 2771 | | height: auto; |
| 2729 | | - | row-gap: calc(var(--tile-w) * 0.4); |
| 2772 | + | /* Room over the top line for the 摸 tag, which hangs above its tile. */ |
| 2773 | + | padding-top: calc(var(--tile-w) * 0.42); |
| 2774 | + | } |
| 2775 | + | .ctl-hand .hand .drawn-slot { |
| 2776 | + | order: -1; |
| 2777 | + | grid-column: 1 / -1; |
| 2778 | + | justify-self: center; |
| 2779 | + | /* Its own line — the gap that sets it apart in a row is what the line is |
| 2780 | + | doing here, and left in it would push it off the middle. */ |
| 2781 | + | margin-left: 0; |
| 2730 | 2782 | | } |
| 2731 | 2783 | | } |
| 2784 | + | /* The wire has gone, on a phone holding a live hand. A bar across the top, |
| 2785 | + | because what is underneath it has stopped working even though it still looks |
| 2786 | + | as though it has not: everything is dimmed a little and nothing takes a tap |
| 2787 | + | while this is up. */ |
| 2788 | + | .ctl-cut-bar { |
| 2789 | + | position: fixed; |
| 2790 | + | top: 0; |
| 2791 | + | left: 0; |
| 2792 | + | right: 0; |
| 2793 | + | z-index: 6; |
| 2794 | + | padding: 6px 12px calc(6px + env(safe-area-inset-top, 0px)); |
| 2795 | + | text-align: center; |
| 2796 | + | font-size: 13px; |
| 2797 | + | background: rgba(120, 44, 34, 0.94); |
| 2798 | + | color: var(--ivory); |
| 2799 | + | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45); |
| 2800 | + | } |
| 2801 | + | .ctl-cut-bar em { |
| 2802 | + | font-style: normal; |
| 2803 | + | opacity: 0.7; |
| 2804 | + | margin-left: 6px; |
| 2805 | + | } |
| 2806 | + | .ctl-cut .ctl-actions, |
| 2807 | + | .ctl-cut .ctl-hand { |
| 2808 | + | opacity: 0.45; |
| 2809 | + | pointer-events: none; |
| 2810 | + | } |
| 2811 | + | /* A phone in a room with no table in it. Says which of the two it is and then |
| 2812 | + | gets out of the way — there is nothing here to press. */ |
| 2813 | + | .ctl-adrift { |
| 2814 | + | align-items: center; |
| 2815 | + | justify-content: center; |
| 2816 | + | text-align: center; |
| 2817 | + | gap: 6px; |
| 2818 | + | padding: 24px; |
| 2819 | + | } |
| 2820 | + | .ctl-adrift h1 { |
| 2821 | + | font-size: 26px; |
| 2822 | + | margin: 0; |
| 2823 | + | } |
| 2824 | + | .ctl-adrift .sub { |
| 2825 | + | letter-spacing: 0.28em; |
| 2826 | + | color: var(--gold); |
| 2827 | + | margin: 0; |
| 2828 | + | } |
| 2829 | + | .ctl-adrift .adrift-line { |
| 2830 | + | font-size: 17px; |
| 2831 | + | font-weight: 600; |
| 2832 | + | margin: 10px 0 0; |
| 2833 | + | } |
| 2834 | + | .ctl-adrift .hint { |
| 2835 | + | max-width: 30ch; |
| 2836 | + | opacity: 0.7; |
| 2837 | + | font-size: 13px; |
| 2838 | + | line-height: 1.5; |
| 2839 | + | margin: 0; |
| 2840 | + | } |
| 2841 | + | |
| 2732 | 2842 | | /* The tile in the fingers once it has left the row, and its exit: flung, it |
| 2733 | 2843 | | keeps going up off the screen — the common screen takes it from here. */ |
| 2734 | 2844 | | .ctl-held { |
| ⋯ 142 unchanged lines |
| 2877 | 2987 | | opacity: 0.75; |
| 2878 | 2988 | | } |
| 2879 | 2989 | | |
| 2990 | + | /* How many other devices are in the room, in the top-left corner of the felt. |
| 2991 | + | Small and dim: it is a thing to glance at when you are wondering whether a |
| 2992 | + | scan worked, not a thing to read while playing. Out of the way of all four |
| 2993 | + | strips, which sit against their own edges. */ |
| 2994 | + | .net-count { |
| 2995 | + | position: absolute; |
| 2996 | + | top: 6px; |
| 2997 | + | left: 8px; |
| 2998 | + | z-index: 55; |
| 2999 | + | font-size: 11px; |
| 3000 | + | letter-spacing: 0.02em; |
| 3001 | + | color: rgba(246, 240, 226, 0.42); |
| 3002 | + | pointer-events: none; |
| 3003 | + | user-select: none; |
| 3004 | + | } |
| 3005 | + | |
| 2880 | 3006 | | /* ---------- the wire, when it goes ---------- */ |
| 2881 | 3007 | | /* One line across the top of the felt. The room is already reconnecting on its |
| 2882 | 3008 | | own, so this says what is happening rather than asking for anything, and it |
| ⋯ 15 unchanged lines |
| 2898 | 3024 | | text-align: center; |
| 2899 | 3025 | | pointer-events: none; |
| 2900 | 3026 | | } |
| 3027 | + | |
| 3028 | + | /* ---------- movement, for anyone who would rather not ---------- */ |
| 3029 | + | /* The table says things twice — the gold is also a name in gold, the clock is |
| 3030 | + | also a bar — so nothing here is the only way anything is said, and the |
| 3031 | + | breathing can simply stop. What is left is the bright end of each animation |
| 3032 | + | held still, which is the state they were all built around. */ |
| 3033 | + | @media (prefers-reduced-motion: reduce) { |
| 3034 | + | .seat.awaited, |
| 3035 | + | .seat.claiming, |
| 3036 | + | .ctl-turn::before, |
| 3037 | + | .ctl-claim::before { |
| 3038 | + | animation: none; |
| 3039 | + | } |
| 3040 | + | } |