/* ============================================================
   Mahjong Rush

   Route colour: crimson. Warm and high-chroma like the rest of the site —
   Joyride sits at hue 32, Commute's lacquer at 25, Spot the Fake at 290 — and
   350 is unclaimed. Chroma is 0.19 to match them; the previous teal was 0.12
   and read washed out beside every other game.

   The wall is a jade felt: the only dark surface on the site, but it is a
   mahjong table, and it borrows the site's anchor green rather than inventing
   a colour of its own.
   ============================================================ */
:root {
  --rush:       oklch(52% 0.19 350);
  --rush-deep:  oklch(38% 0.16 350);
  --rush-soft:  oklch(94% 0.04 350);
  --wall-board: oklch(75% 0.045 68);   /* the tray the tiles sit in — a bamboo tone */
  --wall-edge:  oklch(62% 0.05 64);    /* its inner lip */
  --tile-shadow: #8a7455;              /* the site's signature offset, shared by every other game */
  /* Straight from the tile artwork: #005F00 is the frame every tile is drawn
     with, #CDDACD the pale inner bevel. A back in the same green reads as the
     other side of the same object. */
  --tile-green:  #005F00;
  --tile-green-lt: #0a7a12;
  --tile-green-dk: #003d00;
  --tile-bevel:  #CDDACD;
  --danger:     oklch(58% 0.17 45);   /* orange-red, so warnings don't read as the accent */

  /* The three opponents. Split warm from cool and the wall reads itself: the
     tile YOU have turned over glows gold, the tiles they are looking at ring
     in blue, violet and cyan. Nobody has to learn a legend to know which of
     the four marks is theirs. All three sit clear of the crimson route colour
     and of the green the tile backs are drawn in. */
  --p1: oklch(58% 0.18 258);   /* Ada   — blue   */
  --p2: oklch(56% 0.21 308);   /* Sol   — violet */
  --p3: oklch(58% 0.16 202);   /* Wren  — cyan   */
}

/* Every other game sits on paper-deep; Rush was left on plain paper and read
   lighter than the rest of the site. */
body { background: var(--paper-deep); }

.app { max-width: 1180px; margin: 0 auto; padding: 12px 20px 16px; }
.screen { animation: fade-in .25s ease; }
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .screen { animation: none; }
  * { transition-duration: .01ms !important; animation-duration: .01ms !important; }
}

.rush-band { background: var(--rush-deep); box-shadow: 0 6px 0 oklch(28% 0.12 350); margin-top: 24px; border-radius: 10px 10px 0 0; }
.rush-seal { background: oklch(28% 0.12 350); }
.welcome-eyebrow {
  font-size: .72rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .14em; color: rgba(255,255,255,.85); margin-bottom: 10px;
}

/* ---------- Intro ----------
   Structured like Mobile Commute's: a working miniature of the game, then
   three short steps. The four prose cards this replaced explained the loop
   accurately and showed nothing. */
.intro-body {
  background: var(--panel); border: 2.5px solid var(--ink);
  border-radius: 10px; padding: 24px 24px 28px; margin: 24px 0 40px;
  display: flex; flex-direction: column; align-items: center; gap: 14px;
}
.intro-date {
  font-size: .72rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .12em; color: var(--ink-faint);
}

/* ---- the animated miniature ---- */
.rush-preview {
  width: 268px; border: 2px solid var(--hairline); border-radius: 14px;
  overflow: hidden; box-shadow: 4px 5px 0 var(--paper-deep);
  display: flex; flex-direction: column; background: var(--panel);
}
.rp-bar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 5px 9px; background: var(--rush);
}
.rp-name { font-family: var(--font-display); font-size: .68rem; color: var(--paper); }
.rp-opps { display: flex; gap: 3px; }
.rp-opps i {
  width: 6px; height: 6px; border-radius: 50%; background: rgba(255,255,255,.55);
  animation: rp-blink 2.4s ease-in-out infinite;
}
.rp-opps i:nth-child(2) { animation-delay: .5s; }
.rp-opps i:nth-child(3) { animation-delay: 1.1s; }
@keyframes rp-blink { 0%, 70%, 100% { opacity: .35; } 20%, 45% { opacity: 1; } }

.rp-wall {
  position: relative; background: var(--wall-board); padding: 7px 8px;
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 4px;
}
.rp-wall i, .rp-slot {
  aspect-ratio: 210 / 255; border-radius: 3px;
  background: linear-gradient(163deg, var(--tile-green-lt), var(--tile-green) 45%, var(--tile-green-dk));
  box-shadow: 1px 2px 0 rgba(0, 30, 0, .5);
}
/* The cell the flying tile leaves behind: empties as it lifts, refills as it lands. */
.rp-slot { animation: rp-slot 5s ease-in-out infinite; }
@keyframes rp-slot {
  0%, 18% { opacity: 1; }
  26%, 74% { opacity: 0; }
  86%, 100% { opacity: 1; }
}

.rp-flier {
  position: absolute; left: 50%; top: 50%;
  width: 13%; margin-left: -6.5%; margin-top: -7.9%;
  animation: rp-fly 5s ease-in-out infinite;
}
.rp-flier img {
  width: 100%; display: block;
  filter: drop-shadow(0 0 2px var(--gold-deep)) drop-shadow(0 2px 4px rgba(0,0,0,.5));
}
/* Sits face-down in the wall, turns over, then travels to the hand below. */
@keyframes rp-fly {
  0%, 18%   { opacity: 0; transform: none; }
  26%, 46%  { opacity: 1; transform: none; }
  70%       { opacity: 1; transform: translate(58px, 62px) scale(1.25); }
  76%, 100% { opacity: 0; transform: translate(58px, 62px) scale(1.25); }
}

.rp-hand {
  display: flex; gap: 3px; padding: 6px 8px 8px;
  background: var(--paper-deep); justify-content: center;
}
.rp-hand img, .rp-landing img { width: 26px; display: block; }
.rp-landing { opacity: 0; animation: rp-land 5s ease-in-out infinite; }
@keyframes rp-land { 0%, 70% { opacity: 0; } 76%, 100% { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
  .rp-flier, .rp-slot, .rp-landing, .rp-opps i { animation: none; }
  .rp-flier { opacity: 0; }
  .rp-landing { opacity: 1; }
}

/* ---- how to play ---- */
.intro-how-to-play { width: 100%; max-width: 24rem; display: flex; flex-direction: column; gap: 8px; }
.intro-how-label {
  font-size: .62rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .12em; color: var(--ink-faint);
}
.intro-steps { display: flex; flex-direction: column; gap: 7px; }
.intro-step { display: flex; align-items: flex-start; gap: 10px; }
.intro-step-num {
  width: 22px; height: 22px; border-radius: 50%; flex-shrink: 0; margin-top: 1px;
  background: var(--rush); color: #fff;
  font-size: .68rem; font-weight: 800;
  display: flex; align-items: center; justify-content: center;
}
.intro-step-text { font-size: .84rem; color: var(--ink-soft); line-height: 1.5; }

.intro-note {
  max-width: 24rem; font-size: .8rem; color: var(--ink); text-align: center;
  padding: 9px 14px; background: var(--rush-soft);
  border-left: 3px solid var(--rush); border-radius: 0 5px 5px 0;
}

/* ---------- Top bar ----------
   One bar, full width of the page: the site's own header wearing Rush's colour
   and carrying the game's name. Nothing stacks beneath it, and the race clock
   sits in the corner rather than claiming a second bar. */
.site-header {
  background: var(--rush);
  border-bottom: 4px solid var(--rush-deep);
  box-shadow: 0 4px 0 var(--rush-deep);
}
/* Size comes from the shared game-page header rule; Rush keeps only its own
   seal colour. */
.site-header .brand-seal { background: var(--rush-deep); }
/* Layout and size both live in shared.css now. */

/* ---------- Board ----------
   The board fills the window and the wall absorbs the leftover height, so the
   hand stays on screen on a 720px laptop as well as a tall desktop. */
#screen-game {
  display: flex; flex-direction: column;
  height: calc(100vh - 96px); min-height: 470px;
}
.board {
  background: var(--panel); border: 2.5px solid var(--ink); border-radius: 10px;
  padding: 12px 22px 16px; display: flex; flex-direction: column; gap: 10px;
  position: relative; flex: 1 1 auto; min-height: 0;
}

/* ---------- Opponents ---------- */
.opponents { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.opp {
  background: var(--paper); border: 1.5px solid var(--hairline);
  border-radius: 8px; padding: 4px 9px; min-height: 38px;
  display: flex; align-items: center; gap: 8px;
  transition: border-color .2s, background .2s;
}
.opp[data-opp="1"] { --p: var(--p1); }
.opp[data-opp="2"] { --p: var(--p2); }
.opp[data-opp="3"] { --p: var(--p3); }
/* Crimson here contradicted the dot: Wren would go cyan on the wall and pink
   in the panel. The tint is the player's own colour, barely there. */
.opp.busy {
  border-color: var(--p, var(--rush));
  background: color-mix(in oklab, var(--p, var(--rush)) 11%, var(--paper));
}
.opp.busy .opp-act { color: var(--p, var(--ink-faint)); }
.opp-name { font-weight: 700; font-size: .82rem; display: flex; align-items: center; gap: 6px; flex: none; }
/* The dot is always this player's colour — it is the key to their ring out on
   the wall, so it has to be readable when they are idle too. Busy only turns
   it up. */
.opp-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--p, var(--hairline)); opacity: .4;
  flex-shrink: 0; transition: opacity .2s;
}
.opp.busy .opp-dot { opacity: 1; }

/* Nothing is ever exposed with claiming off, so the card carries a name and an
   activity and should not hold open a row of empty space for tiles. */
.opp.bare { min-height: 30px; }
.opp.bare .opp-act { margin-left: auto; }
body.no-discards .discard-section { display: none; }
.opp-act {
  font-size: .64rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-faint);
  margin-left: auto; flex: none; text-align: right;
}
.opp-exposures {
  display: flex; gap: 6px; align-items: center; min-width: 0;
  overflow-x: auto; scrollbar-width: none;
}
.opp-exposures::-webkit-scrollbar { display: none; }
.opp-set { display: flex; align-items: center; gap: 4px; flex: none; }
.opp-none { font-size: .72rem; color: var(--ink-faint); font-style: italic; }
.expo-set {
  display: flex; gap: 2px; padding: 2px; background: var(--panel);
  border: 1px solid var(--hairline); border-radius: 4px;
}
.expo-tile { width: 19px; height: 23px; }
.expo-tile.redeemable {
  cursor: pointer; outline: 2px solid var(--gold-deep); outline-offset: 1px; border-radius: 3px;
}
.expo-tile.redeemable:hover { transform: translateY(-2px); }

/* ---------- The wall ---------- */
.wall-section { display: flex; flex-direction: column; gap: 6px; flex: 1 1 auto; min-height: 0; position: relative; }
.wall-header {
  position: absolute; top: 6px; left: 10px; right: 10px; z-index: 6;
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; pointer-events: none;
}
.wall-label, .wall-hint {
  /* They float over the tiles, so they carry their own ground. */
  background: oklch(96% 0.014 85 / .93); border-radius: 999px;
  padding: 2px 9px; box-shadow: 0 1px 2px rgba(0, 0, 0, .3);
}
.wall-label {
  font-weight: 800; font-size: .66rem; text-transform: uppercase;
  letter-spacing: .12em; color: var(--ink-soft);
}
.wall-hint { font-size: .7rem; color: var(--ink-soft); }

.wall {
  position: relative; flex: 1 1 auto; min-height: 150px; width: 100%;
  background: var(--wall-board);
  border: 2px solid var(--ink); border-radius: 10px;
  /* A shallow recess: the tiles are lying in a tray, not floating on felt. */
  box-shadow: inset 0 3px 0 var(--wall-edge),
              inset 0 -2px 0 oklch(85% 0.032 72),
              inset 0 10px 22px -8px rgba(60, 40, 14, .4);
  overflow: hidden;
}
.wall.locked { filter: saturate(.55) brightness(.9); }

/* Positioned at its cell centre; size comes from --tile-w, which layoutWall()
   derives from the wall's real pixel box so tiles never overlap however short
   the window is. The ratio is the artwork's own 210x255 — 3:4 letterboxed the
   image, and the container showed through the gap as a white card behind every
   tile, invisible on paper and glaring on felt. */
.slot {
  position: absolute; transform-origin: center;
  width: var(--tile-w, 40px); aspect-ratio: 210 / 255;
  margin-left: calc(var(--tile-w, 40px) / -2);
  margin-top: calc(var(--tile-w, 40px) * -0.607);
  transition: opacity .3s ease;
}
.slot.gone { opacity: 0; pointer-events: none; }

.tile-back {
  width: 100%; height: 100%; border-radius: 5px;
  background: linear-gradient(163deg, var(--tile-green-lt) 0%, var(--tile-green) 45%, var(--tile-green-dk) 100%);
  cursor: pointer; position: relative;
  box-shadow: 2px 3px 0 rgba(0, 30, 0, .55);
  transition: transform .12s, box-shadow .12s;
}
/* The pale inner bevel the tile artwork itself is drawn with. */
.tile-back::after {
  content: ""; position: absolute; inset: 14%; border-radius: 3px;
  border: 1px solid var(--tile-bevel);
  opacity: .5;
}
/* Hover lifts but does not scale: rows sit ~3px apart, so growing a tile made
   it overlap its neighbours and the grid rippled under the cursor. */
.wall:not(.locked) .tile-back:hover {
  transform: translate(-1px, -3px);
  box-shadow: 3px 5px 0 rgba(0, 30, 0, .55);
}
.wall:not(.locked) .tile-back:hover::after { opacity: .85; }
.slot .tile-face {
  width: 100%; height: 100%; position: relative;
  display: flex; align-items: center; justify-content: center;
  transform-origin: center;
  transition: transform .14s ease-out;
}
.slot .tile-face img {
  width: 100%; height: 100%; object-fit: contain; pointer-events: none;
  /* Shadow follows the tile's rounded silhouette, not a square box. */
  filter: drop-shadow(2px 3px 0 var(--tile-shadow));
}
.slot.up { z-index: 40; }
/* The tile you are looking at glows instead of wearing a rectangle. */
.slot.up .tile-face img {
  filter: drop-shadow(0 0 2px var(--gold-deep)) drop-shadow(0 0 6px var(--gold-deep))
          drop-shadow(2px 4px 0 var(--tile-shadow));
}

/* ---------- What the other three are doing ----------

   Two marks, one colour each. A steady 2px ring means somebody has a hand on
   that tile; a ripple that fades once means they took it. Same colour so you
   know who without looking away from the wall, different shape so you know
   which without reading anything.

   Both are deliberately quiet. Nothing here loops: the ring is a hairline that
   fades in and sits still, and the ripple runs for 0.7s and is gone. Three
   opponents on a hundred tiles means at most three rings at a time, and the
   board should still read as a wall of face-down tiles rather than a light
   show. */
.slot[data-watch="1"], .slot[data-took="1"] { --p: var(--p1); }
.slot[data-watch="2"], .slot[data-took="2"] { --p: var(--p2); }
.slot[data-watch="3"], .slot[data-took="3"] { --p: var(--p3); }

/* Above their neighbours, or the ring is clipped by the tiles overlapping it. */
.slot[data-watch] { z-index: 20; }
.slot[data-took]  { z-index: 30; }

.slot[data-watch] .tile-back {
  /* The tile's own drop shadow has to be restated: this is one property. */
  box-shadow: 2px 3px 0 rgba(0, 30, 0, .55),
              0 0 0 2px var(--p),
              0 0 9px -2px var(--p);
  animation: watch-on .2s ease-out;
}
@keyframes watch-on { from { box-shadow: 2px 3px 0 rgba(0, 30, 0, .55); } }

/* Two parts, because an expanding ring alone looks exactly like the watch ring
   in its opening frame — the difference only existed in motion, which is no
   use to anyone glancing at the board. The wash says a tile changed hands and
   is gone inside a third of a second; the ring carries the rest of the way out
   and names who did it. Neither repeats.

   Written with the visible state in the keyframes' `from`, so that killing the
   animation leaves something rather than nothing — see reduced motion below. */
.slot[data-took]::before,
.slot[data-took]::after {
  content: ""; position: absolute; z-index: 2; pointer-events: none;
  border-radius: 7px;
}
.slot[data-took]::before {
  /* Mixed toward white rather than the flat colour: the tile under it is very
     dark green, and 30% of a mid-lightness blue over that moved almost
     nothing. A pale tint lifts, which is what "this one just turned over"
     should look like. */
  inset: 0; background: color-mix(in oklab, var(--p) 58%, white);
  animation: took-wash .7s ease-out forwards;
}
.slot[data-took]::after {
  /* A hair thicker than the watch ring, so the two are still separable once
     the wash has gone. */
  inset: -2px; border: 2.5px solid var(--p);
  box-shadow: 0 0 11px -2px var(--p);
  animation: took-ring .7s ease-out forwards;
}
/* Brief and low: at a third of a second and 30% it reads as a tile flashing
   over, not as a light going off. */
@keyframes took-wash {
  from { opacity: .42; }
  36%  { opacity: 0; }
  to   { opacity: 0; }
}
@keyframes took-ring {
  from { opacity: .95; transform: scale(.95); }
  to   { opacity: 0;   transform: scale(1.34); }
}

/* The shared reduced-motion rule collapses every animation to 0.01ms, which
   would run both of these straight to their end state — invisible — before
   anyone saw them. Drop the animations entirely instead and let the ring
   simply be there for the 0.7s the attribute is set. */
@media (prefers-reduced-motion: reduce) {
  .slot[data-watch] .tile-back { animation-name: none; }
  .slot[data-took]::before { animation-name: none; opacity: .3; }
  .slot[data-took]::after  { animation-name: none; opacity: .9; transform: none; }
}

/* ---------- Discards ---------- */
.discard-section { display: flex; align-items: stretch; gap: 10px; flex: none; }
.discard-header {
  display: flex; flex-direction: column; justify-content: center;
  width: 76px; flex: none; gap: 1px;
}
.discard-label { font-weight: 800; font-size: .68rem; text-transform: uppercase; letter-spacing: .09em; }
.discard-hint { font-size: .66rem; color: var(--ink-faint); }
.discard-row {
  display: flex; gap: 9px; align-items: center; min-height: 74px; flex: 1 1 auto;
  padding: 6px 11px; background: var(--paper);
  border: 1px solid var(--hairline); border-radius: 12px; overflow-x: auto;
}
.discard-empty { font-size: .82rem; color: var(--ink-faint); font-style: italic; }
.discard-tile {
  position: relative; width: 52px; height: 63px; flex: none;
  background: none; border: none; padding: 0;
  cursor: pointer; transition: transform .12s;
}
.discard-tile img {
  width: 100%; height: 100%; object-fit: contain; pointer-events: none;
  filter: drop-shadow(2px 3px 0 var(--tile-shadow));
}
.discard-tile.claimable img {
  filter: drop-shadow(0 0 2px var(--rush)) drop-shadow(0 0 5px var(--rush));
}
.discard-tile:hover { transform: translateY(-4px); }
.decay-bar {
  /* Along the top edge — the bottom belongs to the tile's name label. */
  position: absolute; left: 3px; right: 3px; top: 2px; height: 2px;
  background: rgba(0, 0, 0, .12); border-radius: 2px; overflow: hidden;
}
.decay-fill { height: 100%; background: var(--rush); width: 100%; }

/* ---------- Your hand ---------- */
.hand-section { display: flex; align-items: stretch; gap: 10px; flex: none; }
.hand-header {
  display: flex; flex-direction: column; justify-content: center;
  width: 76px; flex: none; gap: 5px;
}
.hand-label { font-weight: 800; font-size: .68rem; text-transform: uppercase; letter-spacing: .09em; }
.hand-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.btn-sort {
  font-family: var(--font-ui); font-size: .66rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em;
  padding: 4px 10px; border: 1.5px solid var(--hairline); border-radius: 999px;
  background: var(--panel); color: var(--ink-soft); cursor: pointer;
}
.btn-sort:hover { border-color: var(--ink); color: var(--ink); }

.your-exposures { display: flex; flex-wrap: wrap; gap: 8px; }
.your-exposures:empty { display: none; }
.your-exposures .expo-set { background: var(--jade-soft); border-color: var(--jade); }
.your-exposures .expo-tile { width: 30px; height: 36px; }

.hand-tray {
  display: flex; gap: 6px; flex-wrap: nowrap;
  background: var(--paper-deep); border: 1px solid var(--hairline);
  border-radius: 12px; padding: 11px 12px;
}
.hand-tile {
  flex: 1 1 0; min-width: 0; max-width: 66px; aspect-ratio: 210 / 255;
  position: relative; background: none; border: none; padding: 0;
  cursor: grab; transition: transform .12s, filter .12s;
}
/* Drag to reorder — arranging your own tiles is always allowed. touch-action
   keeps a drag on a phone from scrolling the page instead. */
.hand-tile { touch-action: none; }
.hand-tile.dragging { opacity: .3; transform: scale(.94); cursor: grabbing; }
.hand-tile:active { cursor: grabbing; }
.drag-placeholder-el {
  flex: 1 1 0; min-width: 0; max-width: 66px; aspect-ratio: 210 / 255;
  border: 2px dashed var(--rush); border-radius: 5px;
  background: var(--rush-soft); opacity: .7;
}
.hand-tile img {
  width: 100%; height: 100%; object-fit: contain; pointer-events: none;
  filter: drop-shadow(2px 3px 0 var(--tile-shadow));
}
.hand-tile.swappable:hover { transform: translate(-1px, -6px); }
.hand-tile.swappable:hover img {
  filter: drop-shadow(0 0 2px var(--rush)) drop-shadow(0 5px 7px rgba(0, 0, 0, .28));
}
.hand-tile.locked { opacity: .45; cursor: not-allowed; }
.hand-tile.locked::after {
  content: "exposed"; position: absolute; inset: auto 0 2px 0; text-align: center;
  font-size: .52rem; font-weight: 800; text-transform: uppercase; color: var(--ink-faint);
}
/* Fallback if a tile ever has no artwork — every tile in the deck does, so
   this should never render. */
.tile-id { font-size: .7rem; font-weight: 700; color: var(--ink-soft); }

.tile-num-label {
  position: absolute; bottom: 2px; right: 2px;
  font-size: 10px; font-weight: 800; background: rgba(255,255,255,.96);
  border-radius: 3px; padding: 0 3px; line-height: 1.4; color: #111;
  pointer-events: none; box-shadow: 0 1px 3px rgba(0,0,0,.18);
  font-family: system-ui, -apple-system, sans-serif;
}
/* Every face-up tile names itself. You should never have to identify a tile
   from its artwork alone while deciding whether to take it — the label scales
   with the tile so it stays readable as the wall resizes. */
.slot .tile-face, .expo-tile { position: relative; }
.slot .tile-num-label,
.discard-tile .tile-num-label,
.end-tiles .end-tile .tile-num-label,
.win-fly .tile-num-label,
.your-exposures .expo-tile .tile-num-label {
  position: absolute; left: 1px; right: 1px; bottom: 1px;
  display: block; text-align: center;
  font-size: max(7px, calc(var(--label-w, 40px) * 0.25));
  font-weight: 800; line-height: 1.3;
  background: rgba(255,255,255,.95); color: #111;
  border-radius: 2px; padding: 0 1px;
  white-space: nowrap; overflow: hidden; text-overflow: clip;
  box-shadow: 0 1px 2px rgba(0,0,0,.2);
  font-family: system-ui, -apple-system, sans-serif;
  pointer-events: none;
}
.slot                      { --label-w: var(--tile-w, 40px); }
.discard-tile              { --label-w: 52px; }
.your-exposures .expo-tile { --label-w: 30px; }

/* Opponent exposure chips are ~20px — a badge there is unreadable, and the set
   is already identified by the label under it. */
.opp .expo-tile .tile-num-label { display: none; }
.expo-name {
  font-size: .6rem; font-weight: 800; letter-spacing: .03em;
  color: var(--ink-soft); white-space: nowrap;
}

/* ---------- Planning prompt ----------
   Sits over the wall and nothing else. Covering the whole board made the
   button the only thing you could click, which read as "click to continue"
   rather than "arrange your hand first" — the hand has to stay reachable. */
.plan-bar {
  position: absolute; left: 0; right: 0; bottom: 0; top: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  background: rgba(20, 14, 6, .5); border-radius: 10px;
}
.plan-bar.hidden { display: none; }
.plan-card {
  background: var(--ink); border-radius: 12px;
  box-shadow: 0 18px 44px rgba(0,0,0,.45);
  padding: 18px 26px 20px; max-width: 27rem; text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
}
.plan-title { font-family: var(--font-display); font-size: 1.5rem; color: var(--paper); }
.plan-sub {
  font-size: .85rem; line-height: 1.5; color: rgba(255,255,255,.72);
}
/* Two steps: read the card, then start when you're actually ready. One button
   doing both read as "click to continue" and started the game on people. */
.plan-bar .plan-start { display: none; }
.plan-bar.ready { background: rgba(20, 14, 6, .28); }
.plan-bar.ready .plan-card { display: none; }
.plan-bar.ready .plan-start {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
}
.plan-start-note {
  font-size: .74rem; font-weight: 700; letter-spacing: .04em;
  color: rgba(255,255,255,.8); text-shadow: 0 1px 3px rgba(0,0,0,.6);
}

/* During planning the hand is the live part of the screen, so say so. */
body.planning .hand-tray {
  border-color: var(--rush);
  box-shadow: 0 0 0 3px var(--rush-soft);
}
.btn-go { margin-top: 8px; padding: 11px 30px; }

/* ---------- Freeze ---------- */
.freeze-bar {
  position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%);
  z-index: 55;
  width: fit-content; padding: 8px 18px 10px;
  background: var(--ink); border-radius: 999px;
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  box-shadow: 0 6px 18px rgba(0,0,0,.28);
}
.freeze-text {
  font-size: .74rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; color: rgba(255,255,255,.85);
}
.freeze-fill { height: 3px; background: var(--rush); border-radius: 2px; width: 160px; }

/* ---------- Result ---------- */
.end-body {
  background: var(--panel); border: 2.5px solid var(--ink); border-top: none;
  border-radius: 0 0 10px 10px; padding: 32px; margin-bottom: 40px;
  display: flex; flex-direction: column; gap: 22px;
}
.end-card {
  background: var(--paper); border: 1.5px solid var(--hairline);
  border-radius: 8px; padding: 20px; text-align: center;
}
.end-hand-name { font-family: var(--font-display); font-size: 1.5rem; margin-bottom: 14px; }
.end-tiles { display: flex; gap: 4px; justify-content: center; flex-wrap: wrap; }
.end-tiles .end-tile {
  position: relative; width: 50px; height: 61px; --label-w: 50px;
}
.end-tiles .end-tile img {
  width: 100%; height: 100%; object-fit: contain;
  filter: drop-shadow(2px 3px 0 var(--tile-shadow));
}
.end-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; }
.end-stat {
  background: var(--paper); border: 1.5px solid var(--hairline);
  border-radius: 8px; padding: 14px 16px; text-align: center;
}
.end-stat-value { font-family: var(--font-display); font-size: 1.6rem; }
.end-stat-label {
  font-size: .72rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .08em; color: var(--ink-faint); margin-top: 3px;
}
.end-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
#end-band.lost { background: var(--ink-soft); box-shadow: 0 6px 0 var(--ink); }
#end-band.lost .rush-seal { background: var(--ink); }
#end-band.draw { background: var(--gold-deep); box-shadow: 0 6px 0 oklch(45% 0.08 70); }

/* ---------- Toast ---------- */
.toast-wrap {
  position: fixed; bottom: 26px; left: 50%; transform: translateX(-50%);
  z-index: 200; display: flex; flex-direction: column; gap: 8px; align-items: center;
  pointer-events: none;
}
.toast {
  background: var(--ink); color: var(--paper); padding: 10px 20px;
  border-radius: 999px; font-size: .86rem; font-weight: 600;
  box-shadow: 0 6px 18px rgba(0,0,0,.28); animation: toast-in .2s ease;
}
.toast.warn { background: var(--danger); }
.toast.good { background: var(--jade-deep); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* Portrait phones: give the hand the space the wall is wasting.

   100 wall slots sit 14 columns across, so on a phone the tile size is capped
   by WIDTH — measured at 21.8px whether the wall is 500px tall or 280px. Every
   pixel above ~250px does nothing. Meanwhile the hand's thirteen shared what
   was left of the width beside a 76px header, which came to about 16px a tile.
   The layout was giving its readable space to the tiles that are face-down and
   carry nothing to read.

   No cap on the wall is needed to fix it: .hand-section is flex:none and the
   wall is flex:1, so whatever height the hand takes comes out of the wall, and
   the wall only starts shrinking its tiles once it drops under ~250px. This
   leaves it around 370px. */
@media (max-width: 560px) {
  /* The labels float over the felt so they cost no height — but on a phone the
     wall is short enough that two pills sitting on its top row cover tiles you
     need to click. Here they come out of the overlay and sit above it; the few
     pixels that costs come out of height the wall was not using, since its tile
     size is capped by WIDTH at this size. */
  .wall-header {
    position: static; margin-bottom: 4px;
    display: flex; align-items: baseline; justify-content: space-between;
  }
  .wall-label, .wall-hint {
    background: none; box-shadow: none; padding: 0;
  }
  .wall-hint { font-size: .66rem; }

  /* The header goes above the hand rather than beside it — 76px of a 375px
     screen for a label and a button. "Your hand" then goes entirely: it sits
     over the only tiles on screen that are face-up and yours, so it was
     labelling the obvious in prime space. Sort stays, smaller, on the right. */
  .hand-section { flex-direction: column; gap: 0; position: relative; }

  /* The row is gone entirely, not just slimmed. Thirteen tiles wrap to 5/5/3,
     and the last row leaves ~65px clear on each side — so sort lives in that
     gap as a symbol, costing no height at all. The word stays in the DOM for
     screen readers; only its glyphs are hidden. */
  .hand-header {
    position: absolute; right: 3px; bottom: 5px; z-index: 3;
    width: auto; flex-direction: row; gap: 0;
  }
  .hand-label { display: none; }
  .btn-sort {
    width: 27px; height: 27px; padding: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 0; line-height: 1;
    background: var(--panel); border-color: var(--hairline);
  }
  .btn-sort::before {
    content: "⇅";
    font-size: .9rem; font-weight: 700; color: var(--ink-soft);
    letter-spacing: 0;
  }

  /* A flipped tile at 27px is not readable — that is the one moment in the
     game you have to actually read a tile, so it grows while it is face-up.
     The scale goes on .tile-face rather than .slot, because the slot carries an
     inline rotate() from layoutWall that a stylesheet cannot add to. .slot.up
     already lifts to z-index 40, so it grows over its neighbours. */
  .slot.up .tile-face { transform: scale(1.9); }
  /* Tiles in the outermost column sit flush with the tray, so a magnified one
     would lose its edge to the wall's overflow. While your tile is up, let the
     tray spill — it reads as the tile being lifted out of the pile, which is
     what is happening. Only ever one: .up is set for the tile YOU flipped,
     never an opponent's. */
  .wall:has(.slot.up) { overflow: visible; }

  /* Five a row — 5/5/3 — the same wrap the Charleston, the Commute and Pick
     Your Line use. The hand is WIDTH-limited, so the only way to a bigger tile
     is fewer of them per row; the height that costs is what the sort row and
     the tray's old padding give back. 59px against 41px at seven a row. */
  .hand-tray {
    flex-wrap: wrap; justify-content: center;
    row-gap: 6px; gap: 5px; padding: 8px 7px 9px;
  }
  .hand-tray > .hand-tile,
  .hand-tray > .drag-placeholder-el {
    flex: 0 0 calc((100% - 4 * 5px) / 5);
    max-width: 66px;          /* never larger than the artwork */
  }
}

@media (max-width: 760px) {
  .opponents { grid-template-columns: 1fr; }
  /* Bare cards hold a name and a word — they fit three across at any width,
     and stacking them spent three rows of the wall's height on nothing. */
  body.no-discards .opponents { grid-template-columns: repeat(3, 1fr); gap: 6px; }
  body.no-discards .opp { padding: 3px 7px; gap: 5px; }
  body.no-discards .opp-name { font-size: .74rem; }
  body.no-discards .opp-act { font-size: .56rem; }
}

/* The site's primary button is jade — Rush uses its own route colour. */
#screen-game .btn-primary, #screen-intro .btn-primary, #screen-end .btn-primary {
  background: var(--rush);
}
#screen-game .btn-primary:hover, #screen-intro .btn-primary:hover, #screen-end .btn-primary:hover {
  background: var(--rush-deep);
}

/* Short windows: give the wall its height back by trimming the furniture
   around it, rather than crushing the tiles down to something unreadable. */
@media (max-height: 860px) {
  .opp { min-height: 34px; padding: 3px 8px; }
  .expo-tile { width: 17px; height: 21px; }
  .discard-row { min-height: 66px; padding: 4px 8px; }
  .discard-tile { width: 45px; height: 55px; --label-w: 45px; }
  .hand-tile, .drag-placeholder-el { max-width: 52px; }
  .hand-tray { padding: 5px; gap: 4px; }
  .board { gap: 8px; padding: 9px 16px 10px; }
  .discard-header, .hand-header { width: 68px; }
  .your-exposures .expo-tile { width: 26px; height: 32px; --label-w: 26px; }
}

/* ---------- Claim composer ----------
   Calling a discard lifts it out of the row and stages it here. You then click
   your own tiles to add them to the set. Nothing is pre-selected — only you can
   read your card, so only you know whether the line wants a pung or a kong. */
.expo-set.pending {
  border-style: dashed; border-color: var(--rush); border-width: 2px;
  background: var(--rush-soft); gap: 3px; padding: 3px;
  align-items: center;
}
.your-exposures .expo-tile.called {
  outline: 2px solid var(--rush); outline-offset: 1px; border-radius: 3px;
}
.your-exposures .expo-tile.staged { cursor: pointer; }
.your-exposures .expo-tile.staged:hover { transform: translateY(-2px); opacity: .75; }
.claim-slot {
  width: 30px; height: 36px; flex: none;
  border: 2px dashed var(--rush); border-radius: 4px; opacity: .5;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 1rem; color: var(--rush);
}
.claim-bar { display: flex; align-items: center; gap: 8px; margin-left: 10px; }
.claim-kind { font-size: .74rem; font-weight: 700; color: var(--ink-soft); }
.claim-kind.bad { color: var(--danger); }
.btn-claim {
  font-family: var(--font-ui); font-size: .7rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .05em;
  padding: 6px 14px; border: 2px solid var(--ink); border-radius: 999px;
  background: var(--rush); color: #fff; cursor: pointer;
}
.btn-claim:hover { background: var(--rush-deep); }
.btn-claim:disabled { opacity: .4; cursor: not-allowed; }
.btn-claim.ghost { background: var(--panel); color: var(--ink-soft); border-color: var(--hairline); }
.btn-claim.ghost:hover { background: var(--paper-deep); color: var(--ink); }

/* The tray during a claim: what can join the set, and what can't. */
.hand-tile.addable img {
  filter: drop-shadow(0 0 2px var(--rush)) drop-shadow(0 0 5px var(--rush));
}
.hand-tile.addable:hover { transform: translateY(-6px); }
.hand-tile.dimmed { opacity: .35; cursor: not-allowed; }

/* The exposure row wraps once a claim is in progress. */
.your-exposures { align-items: center; }

/* ---------- The win moment ----------
   Ending the instant a winning tile turns over gives the player no chance to
   see what happened. The tile travels to its owner first, the table holds, and
   only then does the result screen arrive. */
.win-fly {
  position: fixed; z-index: 300; pointer-events: none;
  transition: left .95s cubic-bezier(.3,.8,.3,1),
              top .95s cubic-bezier(.3,.8,.3,1),
              width .95s cubic-bezier(.3,.8,.3,1),
              height .95s cubic-bezier(.3,.8,.3,1),
              transform .95s cubic-bezier(.3,.8,.3,1);
}
.win-fly img { width: 100%; height: 100%; object-fit: contain;
  filter: drop-shadow(0 8px 18px rgba(0, 0, 0, .5)); }
.win-fly .tile-num-label { --label-w: 54px; }

.win-banner {
  position: absolute; left: 50%; top: 42%; transform: translate(-50%, -50%);
  z-index: 290; text-align: center; pointer-events: none;
  padding: 18px 40px; border-radius: 14px;
  background: rgba(20, 26, 32, .92);
  box-shadow: 0 16px 44px rgba(0,0,0,.5);
  opacity: 0; transition: opacity .35s ease, transform .35s ease;
}
.win-banner.show { opacity: 1; transform: translate(-50%, -50%) scale(1.04); }
.win-banner-word {
  font-family: var(--font-display); font-size: 2.6rem; line-height: 1;
  color: var(--paper);
}
.win-banner-who {
  margin-top: 6px; font-size: .8rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .12em; color: var(--gold);
}
@media (prefers-reduced-motion: reduce) {
  .win-fly { transition: none; }
  .win-banner { transition: none; }
}
