| ⋯ 228 unchanged lines |
| 229 | 229 | | .seat.active { |
| 230 | 230 | | background: linear-gradient(to top, rgba(217, 180, 92, 0.22), transparent 70%); |
| 231 | 231 | | } |
| 232 | + | /* Whose go it is, on a screen with four people round it. `active` is the same |
| 233 | + | thing said to a player who is already looking at their own tiles; this is the |
| 234 | + | version the *room* can read — it goes on a covered hand too, since whose turn |
| 235 | + | it is was never one of the things the card over a hand is hiding, and the |
| 236 | + | seat that has not noticed is exactly the one it is for. |
| 237 | + | |
| 238 | + | Twice the gold `active` has and up twice as far into the strip: at a party |
| 239 | + | table the person being waited on may be looking at their phone, across the |
| 240 | + | room, or at somebody else's joke. */ |
| 241 | + | .seat.awaited { |
| 242 | + | background: linear-gradient(to top, rgba(217, 180, 92, 0.34), transparent 78%); |
| 243 | + | } |
| 244 | + | /* And the name at the outer end of the strip, since that is what somebody |
| 245 | + | glancing up reads to find out who — the felt says *where*, the plate says |
| 246 | + | *who*, and at a table of people who have known each other five minutes those |
| 247 | + | are not the same question. */ |
| 248 | + | .seat.awaited .nameplate { |
| 249 | + | opacity: 1; |
| 250 | + | } |
| 251 | + | .seat.awaited .nameplate .pname { |
| 252 | + | color: var(--gold); |
| 253 | + | text-shadow: 0 0 10px rgba(217, 180, 92, 0.55); |
| 254 | + | } |
| 232 | 255 | | .seat.claiming { |
| 233 | 256 | | background: linear-gradient(to top, rgba(179, 38, 30, 0.25), transparent 70%); |
| 234 | 257 | | animation: pulse 1.1s ease-in-out infinite; |
| ⋯ 4 unchanged lines |
| 239 | 262 | | } |
| 240 | 263 | | } |
| 241 | 264 | | |
| 265 | + | /* ---------- the seven seconds ---------- */ |
| 266 | + | /* A bar of gold along the inner edge of the strip — the edge that faces the |
| 267 | + | middle of the table, which is the one everybody at the screen is looking |
| 268 | + | over. Every seat is rotated so that its own top is the inner edge, so `top` |
| 269 | + | is the right answer at all four of them without asking which. |
| 270 | + | |
| 271 | + | It drains from both ends towards the middle rather than from one end to the |
| 272 | + | other, because two of the four strips are upside down from wherever you are |
| 273 | + | sitting and a bar that empties leftwards for you empties rightwards for the |
| 274 | + | person opposite. Draining symmetrically reads the same from every chair. |
| 275 | + | |
| 276 | + | Nothing happens when it reaches zero. The bar is gone and the line it leaves |
| 277 | + | breathes, and the table goes on waiting — see ui/TurnClock. */ |
| 278 | + | .turn-clock { |
| 279 | + | position: absolute; |
| 280 | + | top: 0; |
| 281 | + | left: 8px; |
| 282 | + | right: 8px; |
| 283 | + | height: 5px; |
| 284 | + | border-radius: 999px; |
| 285 | + | overflow: hidden; |
| 286 | + | pointer-events: none; |
| 287 | + | z-index: 3; |
| 288 | + | } |
| 289 | + | .turn-clock i { |
| 290 | + | display: block; |
| 291 | + | height: 100%; |
| 292 | + | border-radius: inherit; |
| 293 | + | background: linear-gradient( |
| 294 | + | 90deg, |
| 295 | + | rgba(217, 180, 92, 0.45), |
| 296 | + | var(--gold) 28%, |
| 297 | + | var(--gold) 72%, |
| 298 | + | rgba(217, 180, 92, 0.45) |
| 299 | + | ); |
| 300 | + | box-shadow: 0 0 9px rgba(217, 180, 92, 0.65); |
| 301 | + | animation: turn-drain var(--turn-secs, 7s) linear forwards; |
| 302 | + | } |
| 303 | + | @keyframes turn-drain { |
| 304 | + | from { |
| 305 | + | transform: scaleX(1); |
| 306 | + | } |
| 307 | + | to { |
| 308 | + | transform: scaleX(0); |
| 309 | + | } |
| 310 | + | } |
| 311 | + | /* What is left at zero: the same line, dim, breathing. Delayed by exactly the |
| 312 | + | seconds the bar took, so it starts as the gold runs out. */ |
| 313 | + | .turn-clock::after { |
| 314 | + | content: ''; |
| 315 | + | position: absolute; |
| 316 | + | inset: 1px 0; |
| 317 | + | border-radius: inherit; |
| 318 | + | background: rgba(217, 180, 92, 0.5); |
| 319 | + | opacity: 0; |
| 320 | + | animation: turn-empty 1.5s ease-in-out var(--turn-secs, 7s) infinite; |
| 321 | + | } |
| 322 | + | @keyframes turn-empty { |
| 323 | + | 0%, |
| 324 | + | 100% { |
| 325 | + | opacity: 0.12; |
| 326 | + | } |
| 327 | + | 50% { |
| 328 | + | opacity: 0.55; |
| 329 | + | } |
| 330 | + | } |
| 331 | + | |
| 242 | 332 | | /* ---------- tiles ---------- */ |
| 243 | 333 | | /* Faces come from a 43x2 sprite sheet; the second row is the highlighted |
| 244 | 334 | | variant, which we use for the tile a player has lifted to discard. */ |
| ⋯ 475 unchanged lines |
| 720 | 810 | | |
| 721 | 811 | | Full size only. A phone's bar is a row of its own with room to write on, and |
| 722 | 812 | | a thumb wants a button it can hit. */ |
| 813 | + | /* Not a white rectangle standing in for a tile: the tile itself. The sheet the |
| 814 | + | hand is drawn from has a forty-third cell with no character on it — a blank |
| 815 | + | tile — and that is what a button is drawn with, at the same size, off the |
| 816 | + | same file, through the same `background-size` arithmetic as `.tile.face`. So |
| 817 | + | a button has the bevel the tiles have, the two grey sides they stand on |
| 818 | + | (`sideLight` down the left, `sideDark` along the bottom), the black hairline |
| 819 | + | round the outside, and the shadow the drop-shadow filter takes from the art's |
| 820 | + | own alpha rather than from a rectangle. It cannot drift from the tiles beside |
| 821 | + | it, because it is one of them. |
| 822 | + | |
| 823 | + | Pressed, it turns over to the sheet's second row — the highlighted variant, |
| 824 | + | which is what a tile lifted out of the hand switches to. The same gesture |
| 825 | + | lights the same way whether it is a tile or a call. */ |
| 723 | 826 | | :root:not(.compact) .actionbar .btn { |
| 827 | + | position: relative; |
| 724 | 828 | | flex-direction: column; |
| 725 | 829 | | width: var(--tile-w); |
| 726 | 830 | | height: var(--bar-h); |
| 727 | 831 | | min-height: 0; |
| 728 | | - | padding: 2px 1px; |
| 832 | + | padding: 0; |
| 729 | 833 | | gap: 0; |
| 834 | + | border: none; |
| 730 | 835 | | border-radius: calc(var(--tile-w) * 0.1); |
| 731 | | - | /* The white the tiles themselves are, not the bone the panels are: a tile's |
| 732 | | - | face is white and its sides are the two greys in `LOOK` (ui/tileArt.ts), |
| 733 | | - | and the bottom stop here is that same `sideLight`, so a button standing in |
| 734 | | - | the row reads as one more tile rather than as a card lying beside them. */ |
| 735 | | - | background: linear-gradient(180deg, #ffffff, #d7d7d7); |
| 836 | + | background: url('./assets/tiles.svg') no-repeat; |
| 837 | + | background-size: calc(var(--tile-w) * 43) calc(var(--tile-w) * 2.75); |
| 838 | + | background-position: calc(var(--tile-w) * -42) 0; |
| 839 | + | box-shadow: none; |
| 840 | + | filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45)); |
| 736 | 841 | | color: var(--ink); |
| 737 | | - | border: none; |
| 738 | | - | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.45), inset 0 calc(var(--tile-w) * -0.06) 0 rgba(0, 0, 0, 0.09); |
| 739 | | - | overflow: hidden; |
| 842 | + | transition: transform 0.12s ease, filter 0.12s ease; |
| 843 | + | } |
| 844 | + | /* Under the finger it lifts, the way a tile does — a good deal less far, since |
| 845 | + | a button that jumped a quarter of its own height would be a button that moved |
| 846 | + | out from under the click. */ |
| 847 | + | :root:not(.compact) .actionbar .btn:hover { |
| 848 | + | transform: translateY(calc(var(--tile-w) * -0.07)); |
| 849 | + | filter: drop-shadow(0 5px 7px rgba(0, 0, 0, 0.5)); |
| 740 | 850 | | } |
| 741 | 851 | | :root:not(.compact) .actionbar .btn:active { |
| 742 | 852 | | transform: translateY(1px); |
| 743 | | - | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.45); |
| 853 | + | background-position: calc(var(--tile-w) * -42) calc(var(--tile-w) * -1.375); |
| 854 | + | filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45)); |
| 744 | 855 | | } |
| 745 | 856 | | /* The call is written down the tile, the way the character on a tile's face is |
| 746 | 857 | | written: one on top of the next, in the middle of the face, with the English |
| ⋯ 9 unchanged lines |
| 756 | 867 | | and centres what is in it, one row that is exactly the English. The reading |
| 757 | 868 | | (打出) is the right-hand column, and the tile it is about (五萬) the column to |
| 758 | 869 | | its left, because vertical Chinese reads right to left. */ |
| 870 | + | /* Written on the face, which is not the middle of the tile: the art stands the |
| 871 | + | tile up on `LOOK.depth` of thickness towards the bottom left, so the face is |
| 872 | + | inset by the left side's width and the bottom side's height and hardly at all |
| 873 | + | on the other two. Measured off the sheet rather than guessed — the blank cell |
| 874 | + | at column 42 is white from 14% of its width to 98%, and from 1% of its height |
| 875 | + | to 91%. As padding rather than a nudge, so the grid inside centres on the |
| 876 | + | face and the English sits along the face's own bottom edge. */ |
| 759 | 877 | | :root:not(.compact) .actionbar .btn .bi { |
| 760 | 878 | | display: grid; |
| 761 | 879 | | grid-template-columns: auto auto; |
| ⋯ 4 unchanged lines |
| 766 | 884 | | line-height: 1; |
| 767 | 885 | | height: 100%; |
| 768 | 886 | | width: 100%; |
| 887 | + | padding: calc(var(--tile-w) * 0.015) calc(var(--tile-w) * 0.02) |
| 888 | + | calc(var(--tile-w) * 0.125) calc(var(--tile-w) * 0.14); |
| 769 | 889 | | } |
| 770 | 890 | | :root:not(.compact) .actionbar .btn .bi .zh { |
| 771 | 891 | | grid-row: 1; |
| ⋯ 71 unchanged lines |
| 843 | 963 | | :root:not(.compact) .actionbar .btn.reveal.hot { |
| 844 | 964 | | outline: 2px solid var(--gold); |
| 845 | 965 | | outline-offset: -2px; |
| 846 | | - | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.45), 0 0 10px rgba(217, 180, 92, 0.45); |
| 966 | + | /* Gold off the tile's own outline rather than off a rectangle behind it: the |
| 967 | + | shadow already comes from the art's alpha, and a second glow drawn as a box |
| 968 | + | would put a corner where the tile has none. */ |
| 969 | + | filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.45)) drop-shadow(0 0 6px rgba(217, 180, 92, 0.75)); |
| 847 | 970 | | } |
| 848 | 971 | | /* 過 and 摸牌 are not calls, so they are the plain face — and a pass reads as |
| 849 | 972 | | the quiet one it is. */ |
| ⋯ 2 unchanged lines |
| 852 | 975 | | color: rgba(33, 38, 43, 0.65); |
| 853 | 976 | | } |
| 854 | 977 | | /* The one that plays a tile: the gold edge every other gold button has, kept |
| 855 | | - | as a rim rather than a face. */ |
| 856 | | - | :root:not(.compact) .actionbar .btn.discard, |
| 857 | | - | :root:not(.compact) .actionbar .btn.proceed { |
| 858 | | - | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.45), inset 0 0 0 2px var(--gold); |
| 978 | + | as a rim rather than a face — and drawn round the *face* rather than round |
| 979 | + | the whole tile, since a rectangle of gold across the two grey sides would be |
| 980 | + | a rectangle lying on top of a tile rather than a line drawn on one. Same |
| 981 | + | insets as the writing, off the same measurement of the art. */ |
| 982 | + | :root:not(.compact) .actionbar .btn.discard::after, |
| 983 | + | :root:not(.compact) .actionbar .btn.proceed::after { |
| 984 | + | content: ''; |
| 985 | + | position: absolute; |
| 986 | + | top: calc(var(--tile-w) * 0.015); |
| 987 | + | right: calc(var(--tile-w) * 0.02); |
| 988 | + | bottom: calc(var(--tile-w) * 0.125); |
| 989 | + | left: calc(var(--tile-w) * 0.14); |
| 990 | + | border: 2px solid var(--gold); |
| 991 | + | border-radius: calc(var(--tile-w) * 0.07); |
| 992 | + | pointer-events: none; |
| 859 | 993 | | } |
| 860 | 994 | | .big-btn { |
| 861 | 995 | | font-size: 22px; |
| ⋯ 1667 unchanged lines |
| 2529 | 2663 | | color: var(--gold); |
| 2530 | 2664 | | font-weight: 600; |
| 2531 | 2665 | | } |
| 2666 | + | /* The table has come round to this phone. Four people at one screen and a |
| 2667 | + | phone each is a game that stalls on whoever is not looking at either, so the |
| 2668 | + | whole screen answers rather than a line of text at the top of it: a gold rim |
| 2669 | + | edge to edge, breathing, with the seven seconds running along the top. It |
| 2670 | + | buzzes once as well — see `calling` in ui/Controller. |
| 2671 | + | |
| 2672 | + | Red for a claim, because a claim window is somebody else's tile going past |
| 2673 | + | and it is the one thing here with a clock on it that is not yours. */ |
| 2674 | + | .ctl-turn::before, |
| 2675 | + | .ctl-claim::before { |
| 2676 | + | content: ''; |
| 2677 | + | position: fixed; |
| 2678 | + | inset: 0; |
| 2679 | + | pointer-events: none; |
| 2680 | + | z-index: 2; |
| 2681 | + | animation: ctl-breathe 2.1s ease-in-out infinite; |
| 2682 | + | } |
| 2683 | + | .ctl-turn::before { |
| 2684 | + | box-shadow: inset 0 0 0 3px var(--gold), inset 0 0 36px rgba(217, 180, 92, 0.3); |
| 2685 | + | } |
| 2686 | + | .ctl-claim::before { |
| 2687 | + | box-shadow: inset 0 0 0 3px #e0574a, inset 0 0 36px rgba(179, 38, 30, 0.32); |
| 2688 | + | } |
| 2689 | + | @keyframes ctl-breathe { |
| 2690 | + | 50% { |
| 2691 | + | opacity: 0.45; |
| 2692 | + | } |
| 2693 | + | } |
| 2694 | + | .ctl-turn .ctl-status, |
| 2695 | + | .ctl-claim .ctl-status { |
| 2696 | + | font-size: 16px; |
| 2697 | + | } |
| 2698 | + | /* Across the top of the phone, edge to edge — there is no strip to sit inside |
| 2699 | + | here, the screen is the strip. */ |
| 2700 | + | .ctl .turn-clock { |
| 2701 | + | position: fixed; |
| 2702 | + | left: 0; |
| 2703 | + | right: 0; |
| 2704 | + | height: 6px; |
| 2705 | + | border-radius: 0; |
| 2706 | + | z-index: 4; |
| 2707 | + | } |
| 2532 | 2708 | | .ctl-last { |
| 2533 | 2709 | | display: inline-flex; |
| 2534 | 2710 | | align-items: center; |
| ⋯ 258 unchanged lines |