/* ---- Design tokens ----
  Felt:      #0B3D2E (deep jade table)
  Rail:      #2B1B12 (dark walnut)
  Ivory:     #F3ECD9 (tile face)
  Ink:       #241A12 (tile glyph)
  Gold:      #C9A24B (brass accent / dealer marker)
  Red:       #B23A2E (dragon red accent)
  Text hi:   #F3ECD9
  Text lo:   #B9C9BE
*/

* { box-sizing: border-box; }
html, body { height: 100%; }
/* Desktop only: kill the page scrollbar. The game screen is a fixed,
   self-contained layer that manages its own internal scrolling, so the
   document behind it never needs to scroll.
   Deliberately NOT applied on mobile: iOS Safari only collapses its URL bar
   on a page that CAN scroll, so making the document unscrollable pins the
   bar open - which is exactly what brought it back in landscape. Mobile
   keeps a viewport-height minimum (below) so the bar can still auto-hide,
   and the fixed game layer covers the document anyway, so nothing visibly
   scrolls there. */
@media (min-width: 701px) and (min-height: 561px) {
  html, body { overflow: hidden; }
}
/* Painted on <html> as well as <body> so a full-page screenshot (and
   rubber-band overscroll) has no seam: those capture area outside the fixed
   game layer, which would otherwise show through as white or as a visibly
   different green. This is the board's own background colour (#0B3D2E, the
   "Felt" design token at the top of this file) - NOT #0F4A38, which is only
   the lighter stop of the centre-felt's radial gradient and reads as a
   distinctly different shade against the board proper. */
/* Declared on :root so the background propagates to the canvas - the surface
   the browser paints beyond the document itself, which is what a full-page
   screenshot composites against and what shows in overscroll.
   Deliberately NOT `color-scheme: dark`: that was added to stop white bands
   in screenshots, but it makes the browser paint those surfaces BLACK, which
   is where the black bands came from. An explicit background colour fixes
   the original white-band problem on its own, and keeps every exposed
   surface the same jade as the top bar and hand area. */
/* Matches theme-color (#0A2E23, the top bar's own colour) rather than the
   lighter table-felt green (#0B3D2E). These rules paint areas OUTSIDE the
   actual UI - overscroll, screenshot compositing, anything the browser fills
   itself - not the felt or hand area, which keep their own explicit
   backgrounds regardless. Before this they were the felt green while
   theme-color tinted the browser chrome with the top-bar green, so the strip
   behind Safari's URL bar (and any exposed overscroll) never quite matched
   the top bar directly below it - two adjacent but different greens. */
:root { background-color: #0A2E23; }
html { background: #0A2E23; }
body {
  margin: 0;
  background: #0B3D2E;
  background-image:
    radial-gradient(ellipse at top, rgba(255,255,255,0.05), transparent 60%),
    repeating-linear-gradient(45deg, rgba(0,0,0,0.035) 0 2px, transparent 2px 6px);
  color: #F3ECD9;
  font-family: 'Inter', system-ui, sans-serif;
  /* 100vh (the LARGE viewport on iOS) keeps the document a hair taller than
     the visible area, which is the condition Safari needs to collapse its URL
     bar on scroll. The fixed game layer sits on top and covers it, so this
     never shows as actual scrolling content; the desktop rule above removes
     the resulting scrollbar where there is no toolbar to collapse. */
  /* 100vh (the LARGE viewport) leaves the document a little taller than the
     visible area, which is the scroll Safari needs in order to collapse its
     URL bar.
     A previous revision changed this to 100dvh, reasoning that #screen-game
     is a fixed overlay that absorbs every touch so the document can never be
     scrolled anyway. That was only half right: the GAME screen cannot be
     scrolled, but the landing and lobby are normal-flow sections that can -
     and a scroll there collapses the bar, which then stays collapsed going
     into the game. Removing this took away the only route to a hidden bar in
     the browser, so it is back. The cost is that the pre-game screens can be
     nudged a little; that is the mechanism, not a bug. */
  min-height: 100vh;
}

h1, h2, h3 { font-family: 'Noto Serif TC', serif; margin: 0 0 8px; }
.hidden { display: none !important; }
button { font-family: inherit; cursor: pointer; border: none; }
input { font-family: inherit; }

/* ---------- Landing ---------- */
#screen-landing {
  min-height: 100vh; min-height: 100dvh; max-height: 100%; overflow-y: auto;
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.landing-card {
  background: rgba(20, 45, 36, 0.75);
  border: 1px solid rgba(201,162,75,0.35);
  border-radius: 16px;
  padding: 36px 32px;
  max-width: 420px;
  width: 100%;
  box-shadow: 0 20px 60px rgba(0,0,0,0.4);
  backdrop-filter: blur(6px);
}
.brand { text-align: center; margin-bottom: 2px; }
.brand img { width: min(260px, 80%); height: auto; display: block; margin: 0 auto; }
.tag { text-align: center; color: #B9C9BE; font-size: 13px; margin: 10px 0 4px; }
/* Sits below the Create Table button now rather than under the tagline, so
   the large bottom margin it had as a header element no longer applies. */
.version-tag { text-align: center; color: #9DB0A5; font-size: 11px; margin: 10px 0 0; letter-spacing: 0.5px; }

.landing-tabs { display: flex; gap: 6px; margin-bottom: 18px; background: rgba(0,0,0,0.25); border-radius: 10px; padding: 4px; }
.tab-btn { flex: 1; padding: 9px; background: transparent; color: #B9C9BE; border-radius: 8px; font-weight: 600; font-size: 14px; }
.tab-btn.active { background: #C9A24B; color: #241A12; }
.tab-panel { display: none; flex-direction: column; gap: 12px; }
.tab-panel.active { display: flex; }

label { font-size: 12px; color: #B9C9BE; display: flex; flex-direction: column; gap: 6px; font-weight: 600; }
input {
  padding: 11px 12px; border-radius: 8px; border: 1px solid rgba(201,162,75,0.3);
  background: rgba(0,0,0,0.25); color: #F3ECD9; font-size: 15px; outline: none;
}
input:focus { border-color: #C9A24B; }
/* The room-code hint ("Enter the 5 digit room code. e.g. ABCDE") is longer
   than a typical placeholder - shrunk specifically for it (not the base
   input font-size, which stays full-size for whatever the player types) so
   it fits on one line inside the box rather than being clipped. */
#join-code::placeholder { font-size: 12px; letter-spacing: normal; text-transform: none; }

.btn-primary {
  background: linear-gradient(180deg, #D9B25E, #B8873A);
  color: #241A12; font-weight: 700; padding: 12px; border-radius: 9px; font-size: 15px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.3);
}
.btn-primary:active { transform: translateY(1px); }
.btn-secondary {
  background: rgba(255,255,255,0.08); color: #F3ECD9; padding: 10px 14px; border-radius: 8px; font-size: 14px; font-weight: 600;
  border: 1px solid rgba(255,255,255,0.15);
}
.btn-tiny { background: rgba(255,255,255,0.1); color: #F3ECD9; padding: 4px 9px; border-radius: 6px; font-size: 12px; }
.hint { font-size: 12px; color: #8FA396; margin-top: 10px; line-height: 1.5; }
.error { color: #E27055; font-size: 13px; min-height: 16px; }

/* ---------- Lobby ---------- */
#screen-lobby {
  /* No max-height/overflow-y here: that made this section its OWN internal
     scroll region on top of the page's, so adding the ad slot's height could
     create a scrollbar nested inside another scrollbar - confusing, and the
     direct cause of "the page becomes a scrolling page" when the ad was
     added. A single min-height lets this grow with its content like a normal
     block; if that content is taller than the screen, the PAGE scrolls once,
     not the section within it too. */
  /* 100dvh (the DYNAMIC viewport) is what is actually visible; 100vh on
     mobile resolves to the LARGE viewport - the height with browser chrome
     retracted - which is taller than the screen and pushed the page into
     scrolling with nothing below the window to scroll to. vh stays first as
     the fallback for browsers that don't understand dvh. */
  min-height: 100vh; min-height: 100dvh;
  display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 24px;
}
/* Owns the window chrome (background/border/radius) so the card inside it
   carries none of its own. Retained as a single wrapper now that the bot
   options live inside the card - it still gives the window one consistent
   frame and clips content to its rounded corners. */
#lobby-shell {
  background: rgba(20, 45, 36, 0.75); border: 1px solid rgba(201,162,75,0.35); border-radius: 16px;
  max-width: 460px; width: 100%;
  display: flex; flex-direction: column;
  overflow: hidden;   /* clips children to the shell's own rounded corners */
}
.lobby-card {
  padding: 32px; width: 100%;
  display: flex; flex-direction: column;
}
/* order:99 pushes Start Game to the end of the lobby card regardless of its
   DOM position, so Leave Room and the hint need explicit orders after it -
   reordering the HTML alone does nothing here. The hint must be strictly
   higher than Leave Room, not equal: equal order values fall back to DOM
   order, which is what previously left these two in the wrong sequence. */
/* order:100 keeps Leave Room after Start Game (order:99). Both need an
/* Leave and Start now sit in their own row (.lobby-main-actions) rather than
   as direct children of .lobby-card, so the order/margin-top tricks that used
   to push Start to the bottom regardless of markup position are no longer
   needed - the row's own DOM order (leave, then start) is already correct. */
.lobby-main-actions {
  display: flex; gap: 10px; margin-top: auto; width: 100%;
}
.lobby-main-actions > button { flex: 1 1 0; min-width: 0; }
#lobby-hint { text-align: center; margin-top: 10px; }
.lobby-card-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.lobby-card-head h2 { text-align: left; margin: 0; }
#lobby-code { color: #C9A24B; letter-spacing: 3px; }
/* Tighter above (was 10px) - the hint line and the box it describes read
   as one unit rather than two separated items. */
.share-row { display: flex; gap: 8px; margin: 4px 0 22px; }
.share-row input {
  flex: 1; min-width: 0;
  /* Shrunk further (was 12px) to comfortably fit the full share URL, which
     is longer than typical input text and was still crowding the box. */
  font-size: 10px;
}
.lobby-seats { display: grid; gap: 10px; margin-bottom: 12px; }
.lobby-seat {
  display: flex; align-items: center; justify-content: space-between;
  background: rgba(0,0,0,0.25); padding: 10px 14px; border-radius: 9px; font-size: 14px;
}
.seat-tag { font-size: 11px; padding: 3px 8px; border-radius: 20px; font-weight: 700; }
.seat-tag.human { background: #C9A24B; color: #241A12; }
.seat-tag.bot { background: rgba(255,255,255,0.12); color: #B9C9BE; }
/* Bot difficulty label, lobby only. Distinct blue so it reads as a separate
   piece of information from the BOT tag it sits next to. */
.seat-tag-group { display: inline-flex; align-items: center; gap: 5px; flex-shrink: 0; }
.seat-tag.difficulty { background: rgba(126,200,227,0.16); color: #7EC8E3; letter-spacing: 0.4px; }

/* ---------- Game table ---------- */
#screen-game {
  position: fixed; inset: 0;   /* exactly the viewport - no gap under the hand */
  display: flex; flex-direction: column; overflow: hidden;
  /* Matches the top bar (#0A2E23), not the table felt (#0B3D2E). The
     html/root fix a few rounds back covered the seam OUTSIDE this fixed
     layer (behind the browser chrome); this is the seam INSIDE it - any
     gap between this layer's own edges and the topbar/hand-area sitting
     inside it was still showing the lighter felt green, which is what
     "extending above the room name bar and below the hand section" was. */
  background: #0A2E23;
}
#screen-game.hidden { display: none !important; }
.topbar {
  position: relative; z-index: 50;   /* above the result overlay - stays clickable */
  display: flex; align-items: center; justify-content: space-between;
  padding: 4px max(12px, env(safe-area-inset-right)) 4px max(12px, env(safe-area-inset-left));
  /* Fills the notch strip; otherwise the buttons sit close to the top edge. */
  padding-top: max(5px, env(safe-area-inset-top));
  /* Matches the board background rather than a darker shade of its own. */
  background: #0B3D2E; border-bottom: 1px solid rgba(201,162,75,0.25);
  flex-shrink: 0; gap: 8px;
}
.topbar-left { display: flex; align-items: center; gap: 10px; }
.topbar-right { display: flex; align-items: center; gap: 6px; overflow: visible; padding-top: 2px; }
.room-code { font-size: 13px; color: #B9C9BE; }
.room-code b { color: #C9A24B; letter-spacing: 2px; }
/* Wall count sits at the top-centre of the table. */
/* Sits inside the table's padding band rather than taking a row of its own. */
.wall-count {
  position: absolute; top: 1px; left: 50%; transform: translateX(-50%); z-index: 2;
  font-size: 12px; line-height: 16px; color: #B9C9BE; font-weight: 600; text-align: center;
  background: rgba(0,0,0,0.28); padding: 1px 12px; border-radius: 20px;
}

.paused-banner {
  background: #6B4A1E; color: #FBEBC9; text-align: center; padding: 8px 12px; font-size: 13px; font-weight: 600;
  border-bottom: 1px solid rgba(201,162,75,0.4);
}

.table-wrap {
  position: relative; flex: 1 1 0; min-height: 0;
  display: flex; flex-direction: column; align-items: center;
  gap: 4px; padding: 5px; overflow: hidden;
}
/* Opponents share this row and split it evenly between whoever is in play. */
.seats-row {
  display: flex; flex-direction: row; align-items: flex-start; justify-content: center;
  gap: 8px; width: 100%; flex: 0 0 auto;
}
.seats-row > .seat { flex: 1 1 0; min-width: 0; max-width: 100%; overflow: hidden; }
/* Opponent tile size is driven by the seat's own width - see fitSeatTiles(). */
.seat { --seat-w: 26px; font-size: 12px; text-align: center; color: #B9C9BE; min-width: 0; max-width: 100%; }
.seat .tile.mini, .seat .tile.small {
  width: var(--seat-w);
  height: calc(var(--seat-w) * 4 / 3);
  font-size: calc(var(--seat-w) * 0.82);
}
.seat .tile.mini .tile-label, .seat .tile.small .tile-label {
  font-size: max(9px, calc(var(--seat-w) * 0.26)); padding: 0 0 0 2px;
}
/* Too small to carry legible text - show the glyph alone. */
.seat.no-labels .tile-label { display: none; }
.seat-melds, .hidden-hand, .seat-flowers { flex-wrap: wrap; max-width: 100%; }
.center-felt {
  --pile-pad: 20px;                    /* == height of the tiles-left badge */
  width: min(94vw, 560px); max-width: 100%;
  /* Sized as a share of whatever height the table area has, and centred in it.
     If anything is added above or below, this shrinks with the area rather
     than pushing content off-screen. */
  height: 60%; max-height: 60%; min-height: 0;
  margin-top: auto; margin-bottom: auto;
  /* Flat fill matching the current background green (#0B3D2E), not the
     previous radial gradient - a plain, consistent colour rather than a
     custom felt texture just for this one section. */
  background: #0B3D2E;
  border: 6px solid #2B1B12; border-radius: 18px; box-shadow: inset 0 0 40px rgba(0,0,0,0.5);
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  padding: 0; gap: 0; position: relative; overflow: hidden;
}
.discard-pile {
  --pile-w: 36px;
  display: flex; flex-wrap: wrap; justify-content: center; align-content: center; align-items: center;
  gap: 5px; width: 100%; height: 100%;
  flex: 1 1 auto; min-height: 0; overflow: hidden;   /* sized to fit - no scrollbar */
  /* Uniform band around the pile, the height of the tiles-left badge. All the
     space inside it is the pile's to use. */
  padding: var(--pile-pad);
}
/* Every discard tile derives its size from --pile-w, which the client sets so
   the whole pile fits the available area. */
.discard-pile .tile.mini {
  width: var(--pile-w);
  height: calc(var(--pile-w) * 4 / 3);
  font-size: calc(var(--pile-w) * 0.82);
}
.discard-pile .tile.mini .tile-label { font-size: max(9px, calc(var(--pile-w) * 0.24)); padding: 0 0 0 2px; }
/* Matches the JS overlap fraction above (80%) - kept in sync so the
   real-measurement fitter's simulated guess doesn't drift from what the CSS
   actually renders. */
.discard-pile .discard-stack .tile { margin-left: calc(var(--pile-w) * -0.8); }
.discard-pile .discard-stack .tile:first-child { margin-left: 0; }
.discard-pile .stack-count {
  min-width: calc(var(--pile-w) * 0.38); height: calc(var(--pile-w) * 0.34);
  font-size: calc(var(--pile-w) * 0.24); line-height: calc(var(--pile-w) * 0.34);
}
/* Only *identical* tiles overlap (inside .discard-stack, below). Distinct
   tiles keep normal spacing so each one stays fully readable. */
.discard-pile > .tile, .discard-pile > .discard-stack { position: relative; }
.turn-indicator { position: absolute; bottom: 6px; font-size: 12px; color: #C9A24B; font-weight: 700; letter-spacing: 1px; }


.seat-card {
  /* Horizontal padding halved (10px -> 5px): the tiles now sit closer to the
     left and right edges of the card instead of floating in the middle. */
  background: rgba(0,0,0,0.35); border-radius: 10px; padding: 6px 5px; min-width: 92px;
  border: 1px solid rgba(255,255,255,0.08);
  align-items: center; text-align: center;
  transition: box-shadow .2s, transform .2s, background .2s;
}
/* Whose turn it is: the panel keeps its shape and simply changes colour from
   grey to gold, rather than being replaced by a glow. */
.seat-card.active {
  border-color: #F2CE7A;
  background: linear-gradient(180deg, rgba(201,162,75,0.55), rgba(201,162,75,0.32));
  box-shadow: 0 0 0 2px rgba(242,206,122,0.55), 0 0 18px rgba(201,162,75,0.35);
}
.seat-card.active .seat-name, .seat-card.active .seat-player-name { color: #241A12; }
.seat-card.active .seat-wind { background: #241A12; color: #F2CE7A; }
.seat-name {
  font-weight: 700; color: #F3ECD9; font-size: 12px;
  display: flex; align-items: center; justify-content: center; gap: 5px;
  width: 100%; max-width: 100%; padding: 0 2px; flex-wrap: wrap;
}
.seat-offline { order: 3; font-size: 10px; font-weight: 700; color: #E27055; white-space: nowrap; }
/* Wind, name, status and bonus tiles all sit on one centred line and wrap
   together. (They used to be absolutely positioned right, which pushed them
   outside the seat on wide screens.) */
.seat-name > .seat-wind { order: 1; }
.seat-player-name { order: 2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Marks the dealer - the player who started this round - in place of the
   crown emoji this used to show. Applied to both the seat card and the
   Scores table so the two stay consistent. */
.dealer-name { color: #7EC8E3; }
.seat-flowers { order: 4; flex: 0 0 auto; }
.seat-flowers:empty { display: none; }
/* Everything in an opponent's area is centred. */
.seat-melds, .hidden-hand { justify-content: center; }
.seat-wind {
  display: inline-flex; align-items: center; justify-content: center;
  width: 19px; height: 19px; border-radius: 50%; background: #C9A24B; color: #241A12;
  font-size: 11px; font-weight: 900; flex-shrink: 0;
}
.seat-tiles { display: flex; flex-direction: column; align-items: stretch; gap: 3px; width: 100%; min-width: 0; }
.seat-melds { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 5px; justify-content: center; }
/* Within an opponent's completed set, tiles overlap so each set reads as a
   compact stack rather than a full-width run - opponents' cards have far less
   room than the player's own hand. Kong fans (positioned by fitKongFans with
   its own inline margins) are excluded so the two mechanisms can't fight.
   Labels shift to left-aligned here because the visible sliver of each
   overlapped tile is its LEFT edge - a centred label would be hidden under
   the tile stacked on top of it. */
.seat-melds .meld-group:not(.kong-fan) .tile + .tile { margin-left: calc(var(--seat-w) * -0.45); }
.seat-melds .meld-group .tile .tile-label { text-align: left; padding-left: 2px; }

/* ---------- Tiles ----------
   Font fix: the Mahjong Tile Unicode block (U+1F000-U+1F02F) isn't covered by
   every OS's default font set, so some systems render blank boxes. We load
   Noto Sans Symbols 2 (a webfont that does include this block) as the primary
   face for tiles, with emoji/system fallbacks. We also print a small text
   sub-label under every tile (e.g. "3m", "E", "Rd") so a tile is always
   identifiable even in the rare case a font fails to load at all.

   Sizing: .tile is the base size (opponent melds, general use). .large is
   specifically for the player's own hand at the bottom of the screen - the
   thing that most needs to be big and easy to tap, especially on a phone.
   .small is used in compact contexts (the result modal's revealed hands).
   .mini is the discard pile, which needs to fit many tiles at once. */
.tile {
  position: relative;
  display: inline-flex; flex-direction: column; align-items: stretch; justify-content: flex-start;
  width: 38px; height: 50px; background: linear-gradient(180deg,#FBF6E9,#EDE3C8);
  border-radius: 5px; border: 1px solid #C9B78A; box-shadow: 0 2px 3px rgba(0,0,0,0.35), inset 0 0 0 1px rgba(255,255,255,0.5);
  font-size: 30px; line-height: 1; color: #241A12; user-select: none; flex-shrink: 0;
  font-family: 'Noto Sans Symbols 2', 'Noto Sans Symbols', system-ui, 'Segoe UI Symbol', sans-serif;
  overflow: hidden;
}
/* The glyph fills the tile face. Mahjong glyphs carry a lot of internal
   padding in their em box, so they're scaled up and pushed slightly down to
   sit under the corner label rather than floating in the middle. */
/* The label occupies its own strip at the top of the tile and the glyph fills
   everything below it, so the two can never overlap. */
.tile-glyph {
  font-size: 1em; line-height: 1;
  /* Horizontally centred, anchored to the BOTTOM of its box rather than the
     middle - consistent placement across every tile, whatever its label. */
  display: flex; align-items: flex-end; justify-content: center;
  width: 100%; flex: 1 1 auto; min-height: 0;
  overflow: hidden;                    /* keeps the scaled glyph out of the label strip */
  transform: scale(1.5);
  transform-origin: center center;
}
/* No label - the glyph has the whole tile to fill. */
.tile-nolabel .tile-glyph { transform: scale(1.62); }
/* Identifier in its own strip along the top-left of the tile. */
.tile-label {
  /* Left-aligned rather than centred: with the new overlap treatment for
     completed sets (see .meld-overlap below), each covered tile keeps its LEFT
     edge exposed - a centred label would sit right where the covering tile
     lands and disappear; a left-aligned one stays in the visible sliver.
     The bottom padding is the gap that keeps the text off the glyph below it. */
  flex: 0 0 auto; width: 100%; text-align: left; padding: 1px 0 2px 3px;
  font-family: 'Inter', system-ui, sans-serif; font-size: 10px; font-weight: 800;
  min-height: 11px;
  color: #6B5A3A; line-height: 1.05; letter-spacing: -0.3px;
  white-space: nowrap; overflow: hidden; pointer-events: none;
}
.tile.large { width: 42px; height: 56px; font-size: 35px; border-radius: 6px; }
.tile.large .tile-label { font-size: 11px; padding: 0.15em 0 0.35em 2px; }
.tile.small { width: 26px; height: 35px; font-size: 21px; }
.tile.small .tile-label { font-size: 7px; padding: 1px 0 0 2px; }
.tile.mini { width: 36px; height: 48px; font-size: 30px; }
.tile.mini .tile-label { font-size: 9px; padding: 0.15em 0 0.3em 2px; }
.tile.tiny { width: 18px; height: 24px; font-size: 12px; border-radius: 3px; }
.tile.tiny .tile-label { display: none; }
/* Suit colours: the label text is now just a number ("5", not "5 Dot"), so
   colour is what tells the three suits apart at a glance. Dragons are
   coloured to match what they say; White substitutes a dark slate, since
   white text would be invisible on the tile's cream face. Winds and bonus
   tiles (flowers/seasons) stay the neutral default. */
.tile.suit-m .tile-label { color: #B23A2E; }        /* Crak (Characters) - red */
.tile.suit-s .tile-label { color: #1F7A3D; }         /* Bamboo - green */
.tile.suit-p .tile-label { color: #1C4E8C; }         /* Dot (Circles) - blue */
.tile.suit-wind .tile-label { color: #4B4A6B; }      /* Winds - neutral slate */
.tile.suit-dragon-r .tile-label { color: #B23A2E; }  /* Red Dragon */
.tile.suit-dragon-g .tile-label { color: #227A45; }  /* Green Dragon */
.tile.suit-dragon-wh .tile-label { color: #37474F; } /* White Dragon - dark slate substitute */
/* The suit icon (glyph) itself now carries the same colour, not just the
   corner label. Declared on the tile rather than .tile-glyph directly, so the
   already-more-specific .tile-label colour rules above still win for the
   label text - only the glyph (which sets no colour of its own) inherits
   this. Winds and bonus tiles are left at the neutral default ink colour. */
.tile.suit-m { color: #B23A2E; }
.tile.suit-s { color: #1F7A3D; }
.tile.suit-p { color: #1C4E8C; }
.tile.suit-dragon-r { color: #B23A2E; }
.tile.suit-dragon-g { color: #227A45; }
.tile.suit-dragon-wh { color: #37474F; }
/* Face-down tiles carry the game's mark on their back. */
.tile.back {
  background: url('tile-back.svg') center/100% 100% no-repeat, linear-gradient(180deg,#3E6E58,#2B4E3E);
  color: transparent; border-color: #24483A;
}
.tile.clickable { cursor: pointer; transition: box-shadow .12s; }
/* Hover feedback is a subtle ring rather than a lift - tiles no longer move
   at all when hovered or selected, so a wrapped hand row stays visually
   stable. Still scoped to devices that genuinely hover: on touch, :hover and
   :active can stick after the finger lifts. */
@media (hover: hover) and (pointer: fine) {
  .tile.clickable:hover { box-shadow: 0 0 0 2px rgba(201,162,75,0.6); }
}
.meld-group { display: flex; gap: 2px; padding: 2px; background: rgba(0,0,0,0.2); border-radius: 5px; }
/* No flex gap here - the fanned overlap between a Kong's tiles is entirely
   governed by the inline margin-left fitKongFans() sets on each tile; a gap
   on top of that would just add unwanted extra separation. */
.meld-group.kong-fan { gap: 0; }
/* Opponents' completed sets overlap their own tiles (portrait and landscape
   only - see the media blocks), so each set reads as one compact unit rather
   than a run of separate tiles. Only the last tile in a set shows in full;
   the ones behind it keep their LEFT edge exposed, which is why tile labels
   are left-aligned (see .tile-label). The per-tile offset is applied by
   fitMeldOverlaps() from each tile's real measured width - this rule just
   removes the flex gap that would otherwise fight it. */
.seat-melds .meld-group.meld-overlap { gap: 0; }

/* ---------- Hand area ---------- */
.hand-area {
  /* Flat board-background green, matching the top bar - was a dark gradient
     that read as a distinctly different surface from the rest of the board. */
  background: #0B3D2E;
  border-top: 2px solid rgba(201,162,75,0.3);
  padding: 10px 12px max(10px, env(safe-area-inset-bottom));
  flex-shrink: 0;
}
/* Highlighted when it's this player's turn - mirrors the .seat-card.active
   treatment used for the other three seats. */
.hand-area.my-turn {
  border-top-color: #F2CE7A;
  box-shadow: inset 0 6px 0 #C9A24B, inset 0 0 60px rgba(201,162,75,0.18), 0 0 40px rgba(201,162,75,0.3);
  background: linear-gradient(180deg, rgba(201,162,75,0.16), rgba(0,0,0,0.45));
}
/* Matches the two-column split used by .hand-row below it (flexible region +
   fixed region), so the melds - centred within their own region - land
   directly above the hand tiles rather than above the whole row's width. */
.my-melds { display: flex; gap: 6px; flex-wrap: wrap; min-height: 0; justify-content: center; flex: 1 1 auto; }
.my-side-row { display: flex; align-items: flex-start; justify-content: center; gap: 22px; margin-bottom: 2px; flex-wrap: wrap; }
/* Bottom-aligned rather than centred: the hint text (bottom of the tiles
   column) now lines up with the bottom of Sort (bottom of the button column),
   with the hand tiles sitting directly above the hint. */
.hand-row { display: flex; align-items: flex-end; justify-content: center; gap: 14px; }
/* Takes the row width left over by the bonus tray and the button column. Its
   width must not depend on the tiles inside it, or sizing them from it becomes
   circular - which made tile size jump around after a sort. */
.hand-tiles { display: flex; flex-direction: column; align-items: center; min-width: 0; flex: 1 1 auto; }
.my-hand {
  --hand-w: 42px; touch-action: pan-y;
  display: flex; gap: 5px; flex-wrap: wrap; justify-content: center;
  /* align-content:center matters because this is a WRAPPING flex container:
     its default (stretch) spreads the tile row(s) across the whole
     min-height, so any slack between that min-height and what the tiles
     actually need turned into visible dead space above the tiles - i.e. the
     gap under the completed-sets row. Centring collapses that slack evenly
     instead of parking it all at the top. */
  align-content: center;
  min-height: 64px; width: 100%; max-width: 100%;
}
.my-hand .tile.large {
  width: var(--hand-w);
  height: calc(var(--hand-w) * 4 / 3);
  font-size: calc(var(--hand-w) * 0.82);
}
.my-hand .tile.large .tile-label { font-size: calc(var(--hand-w) * 0.24); }
/* Buttons sit in a column to the right of the hand (Discard above Sort). */
.hand-actions { display: flex; flex-direction: column; justify-content: center; gap: 6px; flex: 0 0 auto; }
.hand-actions button { min-width: 92px; padding: 8px 14px; font-size: 14px; }
.hand-actions .btn-primary:disabled { opacity: 0.4; cursor: not-allowed; }
.discard-hint { font-size: 11px; color: #8FA396; display: block; text-align: center; margin: 4px 0 0; line-height: 1.2; width: 100%; }

/* Selected-for-discard state: player picks a tile, then presses Discard. */
/* Highlight only - no lift. The gold ring and glow mark the selection; the
   tile stays put so a wrapped hand row can't visually jitter as tiles are
   picked and unpicked. */
.tile.selected { box-shadow: 0 0 0 3px #C9A24B, 0 6px 10px rgba(0,0,0,0.5); }
/* The tile just drawn, so it's easy to spot among a sorted hand. Cleared as
   soon as the player discards or Kongs. */
.tile.just-drawn { box-shadow: 0 0 0 3px #5FA8D3, 0 4px 8px rgba(0,0,0,0.45); }
.tile.just-drawn.selected { box-shadow: 0 0 0 3px #C9A24B, 0 6px 10px rgba(0,0,0,0.5); }

/* Drag-to-rearrange your own hand */
.tile.dragging { opacity: 0.55; transform: scale(1.08); z-index: 5; }

.btn-mahjong {
  background: linear-gradient(180deg,#D8483A,#96271D); color: #fff; font-weight: 800;
  padding: 12px 22px; border-radius: 9px; font-size: 15px; letter-spacing: 1px;
  box-shadow: 0 0 0 3px rgba(216,72,58,0.3), 0 4px 12px rgba(0,0,0,0.4);
}

/* Flowers / seasons: auto-moved out of the hand, always face-up to everyone. */
.flower-tray { display: flex; gap: 3px; flex-wrap: wrap; align-items: center; justify-content: center; }
.flower-tray:empty { display: none; }
/* Sits on the same row as completed sets, at the end - roughly above the
   Discard/Sort column below, mirroring .hand-actions there. Kept visually
   separate from the melds with a divider, rather than reading as part of the
   button column. The auto margin is no longer needed for positioning now
   that .my-melds itself grows to fill the matching flexible region - it just
   sits naturally at the end of the row. */
.my-flower-tray {
  flex: 0 0 auto; align-self: center;
  display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 3px;
  padding-left: 12px; border-left: 1px solid rgba(201,162,75,0.3);
}
.my-flower-tray:empty { display: none; margin: 0; padding: 0; border: none; }
.flower-tray .tile { border-color: #C9A24B; background: linear-gradient(180deg,#FFF6DC,#F0E0B4); }

/* Face-down tiles representing opponents' concealed hands. */
.hidden-hand { display: flex; gap: 2px; flex-wrap: wrap; justify-content: center; margin-top: 4px; }

/* ---------- Claim response controls (now inside #status-bar) ---------- */
.claim-tile-name { display: inline; font-size: 14px; font-weight: 800; color: #F2CE7A; margin-left: 4px; }
/* Matching the opponent seat-name off-white and the hand-hint grey, so the
   three parts of "X discarded Y" are visually ranked. Font-size/weight are
   inherited from .turn-status; only the colours differ. */
.claim-who { color: #F3ECD9; }
.claim-verb { color: #8FA396; }
.claim-feedback { font-size: 12px; color: #E27055; }
.claim-feedback:empty { display: none; }
/* Buttons flow into as many columns as the available width allows - one on a
   narrow phone, two or three on a wider screen - so a full set (Mahjong, Kong,
   Pong, Chow, Pass) can never stack taller than the bar and get clipped. */
.claim-actions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
  gap: 5px; align-content: center; justify-items: stretch;
  flex: 0 1 auto; max-width: 420px; min-width: 0;
}
/* With no buttons the grid must take no space at all, otherwise it reserves
   its flex-basis and the status text is no longer centred on the bar. */
.claim-actions:empty { display: none; flex: 0 0 0; }
.claim-actions button { padding: 6px 14px; border-radius: 7px; font-weight: 700; font-size: 14px; min-width: 0; white-space: nowrap; }
/* Only genuinely available actions are rendered as buttons. */
.btn-claim { background: #C9A24B; color: #241A12; }
.btn-claim-pass { background: rgba(255,255,255,0.14); color: #F3ECD9; }

/* ---------- Score panel ---------- */
/* Below the top bar: the game on the left, an optional panel docked on the
   right. Panels no longer float over the board, so you can chat and play. */
.game-body { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: row; }
.game-main { flex: 1 1 0; min-width: 0; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }

/* One dock on the right holding Scores, Log and Chat. Whichever are open
   share the height, stacked in that order. */
.side-dock {
  flex: 0 0 auto; width: min(320px, 34vw); min-height: 0;
  background: #12281F; border-left: 1px solid rgba(201,162,75,0.35);
  display: flex; flex-direction: column;
}
.dock-panel {
  flex: 1 1 0; min-height: 0; padding: 10px 12px;
  display: flex; flex-direction: column; overflow: hidden;
}
.dock-panel + .dock-panel { border-top: 1px solid rgba(201,162,75,0.3); }
#log-panel, #chat-panel { flex: 1 1 auto; min-height: 130px; }
#log-panel .game-log { overflow-y: auto; }
.score-panel-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.score-panel-head h3 { font-size: 13px; flex: 1 1 auto; text-align: center; }
#score-table { width: 100%; border-collapse: collapse; font-size: 11px; margin-bottom: 10px; }
#score-table td { padding: 5px 3px; border-bottom: 1px solid rgba(255,255,255,0.08); text-align: center; }
.game-log { font-size: 10.5px; color: #B9C9BE; display: flex; flex-direction: column; gap: 4px; text-align: center; }

/* ---------- Chat ---------- */

.chat-messages { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
/* Full-width rows: each message spans the panel with its own padding, name
   on the left and timestamp pushed to the right edge. The old rule centred
   the text, which is what read as dead space to the left of short names. */
.chat-msg {
  font-size: 11.5px; line-height: 1.4;
  display: flex; align-items: baseline; gap: 4px;
  width: 100%; padding: 1px 8px; text-align: left;
}
.chat-msg .chat-name { font-weight: 700; color: #C9A24B; flex-shrink: 0; }
.chat-msg .chat-text { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
/* margin-left:auto is what pins the time to the right edge of the panel. */
.chat-msg .chat-time { font-size: 10px; color: #9DB0A5; flex-shrink: 0; margin-left: auto; }
.chat-msg.mine .chat-name { color: #8FD6A8; }
.chat-form { display: flex; gap: 6px; }
.chat-form input { flex: 1; padding: 8px 9px; border-radius: 7px; border: 1px solid rgba(201,162,75,0.3); background: rgba(0,0,0,0.25); color: #F3ECD9; font-size: 12px; }

/* ---------- Modal ---------- */
.modal { position: fixed; inset: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 30; padding: 20px; }
.modal-card {
  position: relative; background: #14261F; border: 1px solid rgba(201,162,75,0.4);
  border-radius: 14px; padding: 24px; max-width: 720px; width: 100%;
  max-height: 90vh; max-height: 90dvh; overflow-y: auto; text-align: center;
}
.modal-close {
  position: absolute; top: 6px; right: 8px; z-index: 2;
  background: rgba(255,255,255,0.12); color: #F3ECD9;
  width: 28px; height: 28px; border-radius: 50%; font-size: 14px; line-height: 1;
}
.modal-close:hover { background: rgba(255,255,255,0.22); }
.result-hand { display: flex; flex-direction: column; gap: 10px; margin: 14px 0; text-align: left; }
.result-player-hand { background: rgba(0,0,0,0.2); border-radius: 8px; padding: 8px 10px; }
.result-player-hand.winner { border: 1px solid rgba(201,162,75,0.6); background: rgba(201,162,75,0.08); }
.result-player-label { font-size: 14px; font-weight: 700; color: #C9A24B; margin-bottom: 8px; }
/* W/L/D record badges - colour-coded so the three figures are readable at a
   glance without needing labels spelled out. */
.result-record {
  display: inline-flex; gap: 4px; margin-left: 6px; white-space: nowrap;
  font-size: 11px; font-weight: 800;
}
.result-record > span { padding: 2px 7px; border-radius: 10px; }
.rec-w { color: #8FD6A8; background: rgba(143,214,168,0.14); }
.rec-l { color: #E9A48F; background: rgba(233,164,143,0.14); }
.rec-d { color: #C9B78A; background: rgba(201,183,138,0.14); }
.result-player-tiles { display: flex; gap: 4px; flex-wrap: wrap; align-items: flex-start; }
.result-total { font-size: 16px; font-weight: 700; color: #C9A24B; margin: 6px 0; }
#result-next-host { margin-top: 14px; }

/* Slot the real #chat-panel is moved into while the result screen is open.
   Empty (and so invisible) the rest of the time. The panel itself keeps its
   own styling; .in-result just adapts it to standing alone rather than being
   one section of the side dock. */
.result-chat-slot { display: none; min-height: 0; }
.result-chat-slot:not(:empty) { display: flex; flex-direction: column; }
/* No background/border/radius of its own - that read as "a window inside a
   window". It's now part of the result window itself, separated from the
   rest of the content only by the single divider line the slot's own
   border-top/border-left carries (see the breakpoint rules further down),
   the same seam treatment used elsewhere for one-window-with-sections. */
#chat-panel.in-result {
  display: flex; flex-direction: column; min-height: 0; flex: 1 1 auto;
}
#chat-panel.in-result .chat-messages { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
#chat-panel.in-result .chat-form { flex-shrink: 0; }
/* Only one close control for the whole result window (#btn-close-result).
   The chat's own X is meaningless here - there's no separate "docked" state
   to return the chat to on its own, closing just the chat while the result
   window stays open doesn't make sense - so it's hidden while the chat lives
   inside that window. */
#chat-panel.in-result #btn-close-chat { display: none; }

/* Banner ad slot, below the action buttons. Grey placeholder fill for now so
   the reserved space is visible during testing; flex-shrink:0 keeps it from
   being squeezed away when the card is height-constrained. */
/* ---------- Ad slots ----------
   Collapsed by default: no height, no margin, no background, so a slot with
   no ad in it takes up NO space at all and the surrounding content closes up
   over it. The .ad-filled class (added by the observer in client.js only
   once a slot genuinely renders something with height) is what gives it its
   spacing back. This means an empty slot, or a slot whose ad network
   returned nothing to show, leaves no grey box and no gap behind.
   No fixed height either - the slot takes the height of whatever ad lands in
   it rather than forcing every ad into a 60px band. */
.ad-slot {
  flex-shrink: 0; width: 100%;
  display: none;
  border-radius: 8px; overflow: hidden;
}
.ad-slot.ad-filled {
  display: block;
  margin-top: 12px;
}
/* Scores modal slot sits at the bottom, so it needs the gap above it only. */
#scores-ad.ad-filled { margin-top: 14px; }
/* Chat panel slot: shown on landscape phones and on desktop (where the chat
   docks beside the board). Hidden on portrait phones, where the dock is
   already tight for height. */
/* Portrait phones suppress this slot entirely (dock height is too tight);
   elsewhere it's allowed, but still only appears once actually filled. */
.chat-ad-slot.ad-filled { display: none; }
/* Suppressed specifically inside the result window - that window already has
   its own ad banner (#result-ad), and showing this one too duplicated it
   right above the chat header. Docked-chat display rules elsewhere are
   unaffected since #chat-panel.in-result only applies while it's moved in
   here. */
#chat-panel.in-result .chat-ad-slot { display: none !important; }
@media (min-width: 701px) and (min-height: 561px) {
  .chat-ad-slot.ad-filled { display: block; margin: 0 0 6px; }
}



/* ---------- Chat unread badge + incoming-message banners ---------- */
.chat-btn { position: relative; overflow: visible; }
/* Inline, to the right of the "Chat" label rather than a floating corner
   badge - the absolutely-positioned version sat above the button's top edge
   and got clipped by the top bar's bounds. */
/* Sized in em so it can never exceed the "Chat" label beside it: 0.8em text
   in a 1.25em-tall pill stays smaller than the button's own line box at every
   breakpoint, without needing a matching override per media query.
   inline-flex + align-items:center centres the digits in the pill, and
   vertical-align:middle centres the pill against the label - the two together
   are what keep the number on the same optical line as the text. */
.chat-badge {
  display: inline-flex; align-items: center; justify-content: center;
  margin-left: 5px; vertical-align: middle;
  min-width: 1.25em; height: 1.25em; padding: 0 0.4em;
  border-radius: 999px; background: #D8483A; color: #fff;
  font-size: 0.8em; font-weight: 800; line-height: 1; text-align: center;
}
.chat-badge.hidden { display: none; }

.chat-toasts {
  position: fixed; top: 58px; left: 50%; transform: translateX(-50%);
  z-index: 40; display: flex; flex-direction: column; gap: 6px;
  width: min(92vw, 440px); pointer-events: none;
}
.chat-toast {
  background: rgba(18, 40, 31, 0.97); border: 1px solid rgba(201,162,75,0.5);
  border-left: 4px solid #C9A24B; border-radius: 9px; padding: 8px 12px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.45); font-size: 13px; color: #F3ECD9;
  animation: toastIn .18s ease-out;
}
.chat-toast .toast-name { font-weight: 800; color: #C9A24B; margin-right: 5px; }
.chat-toast.leaving { opacity: 0; transform: translateY(-6px); transition: opacity .3s, transform .3s; }
@keyframes toastIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }

/* ---------- Result screen: completed sets (left) vs leftover tiles (right) ---------- */
.result-sets, .result-remaining { display: flex; gap: 5px; flex-wrap: wrap; align-items: center; }
.result-sets { flex: 0 1 auto; }
.result-remaining { flex: 1 1 auto; opacity: 0.9; }
.result-divider { width: 2px; align-self: stretch; background: rgba(201,162,75,0.4); border-radius: 1px; margin: 0 4px; flex-shrink: 0; }
.result-none { font-size: 11px; color: #9DB0A5; font-style: italic; }

/* ---------- Featured pending discard + drawn-tile banner ----------
   Now lives inside #status-bar rather than a separate bar - see the render
   logic in renderClaimBar(). */
.pending-discard { display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.pending-discard .tile {
  box-shadow: 0 2px 6px rgba(0,0,0,0.5);
  /* var()'s fallback (22px) only applies if the property is unset anywhere in
     the inheritance chain. The old version re-declared the property directly
     on this rule (`--claim-tile-w: 22px;`), which shadows ANY inherited value
     regardless of where it came from - including the one the JS fitter sets
     on the wrapper - so the tile could never actually resize. This is why
     the size appeared stuck no matter what the fitter computed. */
  /* Upper bound is 33px, not the previous 32px - landscape's new target is a
     33px-wide tile (44px tall, Apple's touch-target height converted through
     the fixed 4:3 aspect ratio), and a lower ceiling here would have silently
     clipped that by 1px regardless of what the JS fitter computed. */
  width: clamp(20px, var(--claim-tile-w, 22px), 33px);
  height: calc(clamp(20px, var(--claim-tile-w, 22px), 33px) * 4 / 3);
  font-size: calc(clamp(20px, var(--claim-tile-w, 22px), 33px) * 0.82);
}
.pending-discard .tile .tile-label { font-size: calc(clamp(20px, var(--claim-tile-w, 22px), 33px) * 0.26); }

.status-bar {
  flex-shrink: 0; display: flex; flex-direction: row; align-items: center;
  justify-content: center; gap: 8px; flex-wrap: wrap;
  padding: 3px 12px; background: rgba(201,162,75,0.18); border-top: 1px solid rgba(201,162,75,0.35);
}
/* Everything that used to be the separate claim/pass bar - buttons - now
   shares this row too. */
.status-bar .claim-actions {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(72px, 1fr));
  gap: 5px; flex: 0 1 auto; max-width: 360px; min-width: 0;
}
.status-bar .claim-actions:empty { display: none; }
.turn-status { font-size: 15px; font-weight: 800; color: #F2CE7A; letter-spacing: 0.4px; }
.turn-status:empty { display: none; }
.drew-banner { font-size: 13px; font-weight: 700; color: #F3ECD9; }
.drew-banner:empty { display: none; }
.drew-banner b { color: #F2CE7A; }

/* ---------- Discard pile: identical tiles overlap into a labelled stack ---------- */
.discard-stack { position: relative; display: inline-flex; flex-shrink: 0; }
.discard-stack .tile { margin-left: -22px; }
.discard-stack .tile:first-child { margin-left: 0; }
.discard-stack .stack-count {
  position: absolute; bottom: 2px; right: 2px; min-width: 16px; height: 15px; padding: 0 4px;
  border-radius: 8px; background: #B23A2E; color: #fff; font-size: 10px; font-weight: 800;
  line-height: 15px; text-align: center; z-index: 3; pointer-events: none;
  box-shadow: 0 1px 2px rgba(0,0,0,0.5);
}

/* ================= Mobile optimisation =================
   On narrow screens the three opponent seats are taken OUT of absolute
   positioning (which was causing them to overlap the table) and laid out as a
   proper grid row above the felt. */
@media (max-width: 700px) {
  .topbar { padding: 6px 8px; gap: 4px; }
  .topbar .btn-tiny { padding: 4px 8px; font-size: 12px; }
  .wall-count { font-size: 11px; }

  .table-wrap { gap: 4px; padding: 3px; }
  .seats-row { gap: 5px; }
  .center-felt { width: 100%; min-height: 0; max-height: 54%; }

  .seat-card { padding: 4px 6px; min-width: 0; width: 100%; }

  .seat-name { font-size: 11px; }
  .seat-melds, .hidden-hand, .flower-tray { gap: 2px; }
  /* Opponents' completed sets and bonus tiles match the discard pile's
     readability - same size, and they keep their corner labels. */
  .seat .tile.mini { width: 30px; height: 40px; font-size: 25px; }
  .seat .tile.mini .tile-label { display: block; font-size: 9px; padding: 0 0 0 2px; }
  .seat .tile.small { width: 26px; height: 35px; font-size: 21px; }
  .seat .tile.small .tile-label { display: block; font-size: 8px; padding: 0 0 0 2px; }

  /* padding restates var(--pile-pad) so the badge-clearance band is honoured
     at EVERY breakpoint, not just the base and desktop ones - this gap in
     coverage is why discards could render behind the "tiles left" badge. */
  .discard-pile { gap: 5px; padding: var(--pile-pad) 10px; }
  .discard-pile .tile.mini { width: 32px; height: 43px; font-size: 26px; }
  .discard-pile .tile.mini .tile-label { font-size: 9px; padding: 0 0 0 2px; }
  .discard-stack .tile { margin-left: -14px; }
  .discard-stack .stack-count { min-width: 15px; height: 14px; font-size: 9px; line-height: 14px; }

  .hand-area { padding: 6px 8px env(safe-area-inset-bottom, 0px); }
  .my-hand { gap: 4px; min-height: 56px; }
  .hand-actions { gap: 8px; margin-top: 6px; }
  .hand-actions button { padding: 11px 16px; font-size: 15px; }
  .discard-hint { width: 100%; text-align: center; font-size: 10px; }

  .claim-actions button { padding: 12px 14px; font-size: 15px; flex: 1 1 auto; min-width: 78px; }

  /* max-height is stated twice on purpose: vh first (understood everywhere,
     acts as the fallback), then dvh (dynamic viewport height) - a browser
     that doesn't recognise dvh ignores that whole declaration and keeps the
     vh one, so nothing breaks. Plain vh is unreliable on mobile Safari
     specifically: it's measured against the layout viewport with the address
     bar collapsed, not the shorter viewport actually visible with it showing,
     so 90vh can be MORE than what's really on screen - which is exactly what
     was cutting the result screen off at the top and bottom. dvh tracks the
     real visible height as the toolbar shows or hides.
     Sized to run from just below the top bar down to close to the bottom of
     the viewport, rather than a flat percentage - the safe-area inset is
     subtracted explicitly since it varies by device (notch/dynamic island).
     The small allowance after that covers the top bar's own content (room
     name + buttons) once you're past the notch.
     Note the modal wrapper is also switched to align-items: stretch below:
     it centres its card by default, so the card was only ever as tall as its
     content regardless of how much max-height it was permitted - which is
     why raising the allowance alone kept appearing to do nothing. */
  .modal { padding: 0; align-items: stretch; }
  /* The result modal is one window now: title/hands/buttons, then the ad,
     then the chat, all stacked inside .modal-card via a plain column with
     order (see .result-body/#result-chat-slot/#result-ad below) rather than
     the chat being a separate sibling box beside the card. */
  #modal-result { flex-direction: column; align-items: center; justify-content: center; padding-bottom: env(safe-area-inset-bottom, 0px); }
  #modal-result .modal-card { width: min(96vw, 620px); max-width: 96vw; }
  .modal-card {
    padding: 8px 12px;
    /* Cap only - no fixed height. A blanket `height` here forced EVERY modal
       (Scores, Rules) to fill the screen regardless of how little content it
       had; each now hugs its own content and only scrolls if it exceeds the
       cap. */
    max-height: calc(100vh - env(safe-area-inset-top) - 8px);
    max-height: calc(100dvh - env(safe-area-inset-top) - 8px);
    margin: auto 0;
    display: flex; flex-direction: column;
  }
  /* Portrait order: main content, then the ad, then chat last - "chat below
     the ad block". flex-shrink:0 on each keeps them from being squeezed
     illegibly thin; the CARD itself is what scrolls if the total is taller
     than the cap above. */
  /* Near-full height so the chat at the bottom is reachable without
     scrolling the card itself. The RESULT CONTENT scrolls internally
     instead (see .result-body below), which is what keeps the ad and chat
     pinned in view rather than pushed off the bottom by a long four-hand
     result. */
  #modal-result .modal-card {
    height: auto; max-height: 94vh; max-height: 94dvh;
    margin: 0; overflow: hidden;
  }
  /* flex-shrink:1 + min-height:0 are BOTH required for a flex child to be
     allowed to scroll - shrink:0 (as this had) would make it refuse to give
     up any height, pushing the ad and chat below the fold no matter what the
     card's own cap said. */
  .result-body { order: 0; flex: 1 1 auto; min-height: 0; overflow-y: auto; }
  .result-actions { order: 1; }
  #result-ad { order: 2; flex-shrink: 0; }
  #result-ad.ad-filled { margin-top: 10px; }
  /* display/flex-direction are left to the base .result-chat-slot:not(:empty)
     rule - setting them here unconditionally would show an empty bordered box
     for the instant before moveChatInto() populates it. */
  #result-chat-slot {
    order: 3; flex-shrink: 0;
    height: 130px; margin-top: 10px; border-top: 1px solid rgba(201,162,75,0.35);
  }
  #modal-result .chat-messages { font-size: 11px; }

  #result-title { font-size: 16px; margin-bottom: 2px; }
  .result-total { font-size: 12px; margin: 0; }
  /* The winner's own row keeps its natural height (flex-shrink:0) - it was
     being squeezed along with everything else and clipped its tiles part-way
     down. The other players' block absorbs the shrinking instead, and both
     wrap rather than being cut off. */
  .result-winner { flex-shrink: 0; min-height: 0; overflow: visible; }
  .result-hand { flex-shrink: 1; min-height: 0; overflow-y: auto; }
  .result-hand { gap: 6px; margin: 8px 0; }
  .result-player-hand { padding: 5px 7px; }
  .result-player-label { font-size: 11px; margin-bottom: 3px; }
  .result-wins { font-size: 9px; padding: 1px 5px; }
  /* Was 22x29 with a 19px glyph. The glyph renders at 1.5x its font-size
     (.tile-glyph's transform), so that was ~28px of glyph plus a 7px label in
     a 29px tile - a ~10px overlap, which is why the label sat on top of the
     suit icon. Sized so glyph + label + padding genuinely fits the height:
     18px x 1.5 = 27px glyph, + 8px label + 3px padding = 38px in a 43px tile. */
  .modal-card .tile { width: 32px; height: 43px; font-size: 18px; }
  .modal-card .tile-label { font-size: 8px; }
  .meld-group { padding: 1px; gap: 1px; }
  #result-next-host, #result-leave-guest-row { margin-top: 4px; flex-shrink: 0; }
  #result-next-host button, .result-leave-btn { padding: 8px 16px; font-size: 13px; }
  .result-player-tiles { flex-direction: column; align-items: flex-start; gap: 6px; }
  .result-divider { width: 100%; height: 2px; align-self: auto; margin: 2px 0; }
  .chat-toasts { top: 50px; width: 96vw; }
}

@media (max-width: 480px) {
  .tile { width: 30px; height: 40px; font-size: 24px; }
  .tile .tile-label { font-size: 8px; }
  /* Your own hand is the thing you actually have to read and tap - keep it big.
     14 of these wrap to two rows on a narrow phone, which is fine. */
  .tile.large { width: 40px; height: 53px; font-size: 33px; }
  .tile.large .tile-label { font-size: 10px; }
  .tile.small { width: 24px; height: 32px; font-size: 16px; }
  .tile.mini { width: 30px; height: 40px; font-size: 25px; }
  .tile-label { font-size: 8px; }
  .discard-stack .tile { margin-left: -22px; }
  .center-felt { aspect-ratio: 1.15; }
  .topbar { padding: 8px 10px; flex-wrap: wrap; row-gap: 4px; }
}

/* Narrow phones (~360px): step the hand tiles down just enough that a full
   14-tile hand still fits in two rows rather than spilling into three. */
@media (max-width: 380px) {
  .tile.large { width: 37px; height: 49px; font-size: 30px; }
  .tile.large .tile-label { font-size: 9px; }
  .my-hand { gap: 3px; }
}

/* ================= Landscape phones =================
   Wide but very short (typically 650-950 x 320-450). Vertical space is the
   scarce resource, so: the three opponents share a row across the top that can
   always be seen, the table sits below them, and the hand + actions stay
   pinned at the bottom. Every flex ancestor gets min-height:0 so children can
   actually shrink instead of overflowing and pushing content off-screen -
   which is what was hiding the opponents and clipping the discard pile once
   the claim bar appeared. */
@media (orientation: landscape) and (max-height: 560px) {
  #screen-game { min-height: 0; }
  /* 10% narrower again (was min(259px, 27.5vw)) - landscape has the least
     horizontal room to spare for a side panel. */
  .side-dock { width: min(233px, 24.75vw); }
  /* Landscape phones put the notch/rounded corners on the SIDES, which was
     clipping the room name and the Leave button. Pad by the safe-area insets
     and keep both clusters vertically centred. */
  .topbar {
    padding: 2px max(10px, env(safe-area-inset-right)) 2px max(10px, env(safe-area-inset-left));
    flex-shrink: 0; align-items: center;
  }
  .topbar-left, .topbar-right { align-items: center; }
  .topbar .btn-tiny { padding: 3px 7px; font-size: 11px; }
  .wall-count { display: block; font-size: 10px; padding: 1px 8px; }

  /* Column layout: opponents across the top, table beneath them. (A leftover
     grid here previously placed the seat row and the table side by side.) */
  .table-wrap {
    display: flex; flex-direction: column; align-items: center;
    gap: 3px; padding: 3px 0; flex: 1 1 auto; min-height: 0; overflow: hidden;
  }
  .seat { min-width: 0; min-height: 0; }
  /* Table occupies the middle 80%, with clear space either side.
     The opponents' row takes the height its tiles actually need (capped), and
     the table absorbs whatever is left - rather than both being pinned to
     fixed shares, which was clipping the tiles. */
  .seats-row { width: 100%; gap: 4px; padding: 0 6px; flex: 0 0 32%; min-height: 0; align-items: stretch; }
  .seats-row > .seat { height: 100%; }
  .seat-card { height: 100%; justify-content: center; }

  /* Melds and the concealed hand share ONE line here, always - stacking them
     cost a whole row of vertical space that landscape can't spare. nowrap
     means they never split across two lines regardless of content; the
     fitting logic (SEAT_MAX_ROWS below) shrinks tiles as far as needed to
     keep everything on that single row instead. */
  .seat-tiles { flex-direction: row; flex-wrap: nowrap; justify-content: center; gap: 4px; }
  .seat-melds { margin-top: 0; flex-wrap: nowrap; }
  .hidden-hand { margin-top: 0; flex-wrap: nowrap; }

  /* Trimmings that cost height and add little at this size. */
  .seat-card { padding: 1px 2px; gap: 0; }
  .seat-name { gap: 3px; padding: 0; }
  .seat-card { max-height: none; height: 100%; }

  /* Opponent cards: compact, always visible, never taller than their row. */
  .seat-card {
    width: 100%; padding: 2px 5px; display: flex; flex-direction: column;
    align-items: center; gap: 1px; overflow: hidden;
  }

  .seat-name { font-size: 10px; gap: 3px; width: 100%; }
  .seat-card { align-items: center; }
  .seat-wind { width: 14px; height: 14px; font-size: 9px; }
  .seat-melds, .hidden-hand, .flower-tray { gap: 1px; margin-top: 1px; }
  /* Stays nowrap here too - this used to say "wrap", silently undoing the
     single-line fix declared earlier in this same block (later rules win on
     equal specificity, regardless of intent). */
  /* Never wider than the seat card, and never clipped: the face-down hand
     tiles overlap each other by 50% when the row would otherwise overflow,
     so a full 13-tile hand still fits the section. Melds keep their own
     overlap treatment (.meld-overlap) rather than being squeezed further.
     max-width:100% + min-width:0 are what actually bind the row to the card's
     width - without them a nowrap flex row happily overflows its parent. */
  .seat-melds, .hidden-hand { flex-wrap: nowrap; overflow: hidden; max-width: 100%; min-width: 0; }
  .seat .tile.mini { width: 24px; height: 32px; font-size: 20px; }
  .seat .tile.mini .tile-label { display: block; font-size: 8px; padding: 0 0 0 2px; }
  /* Same size as the .mini melds above: the concealed hand is rendered with
     .small, which was a step smaller, so an opponent's face-down tiles and
     their completed sets didn't line up visually on the same row. */
  .seat .tile.small { width: 24px; height: 32px; font-size: 20px; }
  .seat .tile.small .tile-label { display: block; font-size: 8px; padding: 0 0 0 2px; }

  /* Table: a shallow band whose pile scrolls internally rather than growing. */
  .center-felt {
    width: 80%; margin: 0 auto; flex: 1 1 auto; min-height: 40px;
    height: 100%; max-height: none;
    flex-direction: column; align-items: center; justify-content: flex-start;
    padding: 4px 6px 14px; gap: 2px; border-width: 3px; border-radius: 10px; overflow: hidden;
  }
  .discard-pile {
    flex: 1 1 auto; min-height: 0; overflow-y: auto;
    gap: 4px; align-content: center;
    padding: var(--pile-pad) 8px;
  }
  /* Small enough that two rows fit comfortably, big enough that the corner
     label still reads. */
  .discard-pile .tile.mini { width: 26px; height: 35px; font-size: 21px; }
  .discard-pile .tile.mini .tile-label { font-size: 8px; padding: 0 0 0 2px; }
  .discard-stack .tile { margin-left: -11px; }
  .discard-stack .stack-count { min-width: 14px; height: 13px; font-size: 9px; line-height: 13px; bottom: 1px; right: 1px; }
  .turn-indicator { bottom: 1px; font-size: 9px; }

  /* Status bar and hand are pinned and never squeezed out. */
  .status-bar { flex-shrink: 0; padding: 1px 8px; gap: 8px; }
  .claim-actions { gap: 4px; grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); max-width: 400px; }
  .claim-actions button { padding: 6px 12px; font-size: 13px; }
  .claim-feedback { font-size: 10px; }

  .drew-banner { padding: 1px 8px; font-size: 10px; flex-shrink: 0; }
  /* Slimmer gold turn bar and smaller status text - landscape has no height
     to spare. */
  .hand-area.my-turn { box-shadow: inset 0 3px 0 #C9A24B, inset 0 0 40px rgba(201,162,75,0.14); }
  .turn-status { font-size: 13px; }
  .hand-area { padding: 4px 8px env(safe-area-inset-bottom, 0px); flex-shrink: 0; }
  .hand-row { align-items: flex-end; }
  .hand-area > *:last-child { margin-bottom: 0; }
  .hand-row { align-items: flex-end; }
  .my-side-row { gap: 14px; margin-bottom: 0; }
  /* Enlarged slightly (20 -> 26px) so the label can be shown at a legible
     size, rather than fitting a 20px tile with no label at all. */
  .my-melds .tile { width: 26px; height: 35px; font-size: 21px; }
  .my-melds .tile-label, .my-flower-tray .tile-label { font-size: 8px; padding: 1px 0 1px 2px; }
  .hand-row { gap: 8px; }
  .my-hand {
    gap: 3px; flex-wrap: nowrap; overflow-x: auto; overflow-y: visible;
    justify-content: safe center;
    /* Room for the lifted/selected tile and its outline, which were clipped.
       The lift is 6px with a 3px outline ring, so 9px is the true minimum -
       this had 10px reserved, and that reserved clearance (not any actual
       margin) is most of what read as a gap between the melds and the hand. */
    padding: 9px 2px 0; min-height: 0;
  }
  /* Bonus tiles a touch larger than the hand tiles beside them. */
  .my-flower-tray .tile { width: calc(var(--hand-w) * 1.05); height: calc(var(--hand-w) * 1.05 * 4 / 3); font-size: calc(var(--hand-w) * 0.86); }
  /* A little air between the label and the glyph so they don't touch. */
  .my-hand .tile.large .tile-label { padding-bottom: 0.35em; }
  /* Buttons sit clear of the screen edge rather than flush against it. */
  .hand-actions { margin-right: 10px; }
  .hand-row { padding-right: 6px; }

  .hand-actions { gap: 4px; }
  .hand-actions button { padding: 4px 10px; font-size: 12px; min-width: 0; }
  .discard-hint { display: block; font-size: 9px; margin-top: 2px; line-height: 1.1; }

  /* Landscape has the least height of any layout - shrink the rules text so
     more of it is visible before scrolling. */
  .rules-body { font-size: 10px; line-height: 1.5; padding: 0 44px 40px 12px; }
  .rules-body h3 { font-size: 11px; margin: 10px 0 3px; }
  .modal-rules-card h2 { font-size: 15px; }

  /* Winner screen must FIT the screen rather than scroll: the four hands go
     into a 2x2 grid with compact tiles. */
  .modal { padding: 4px; }
  .modal-card {
    padding: 8px 12px; max-height: 94vh; max-height: 94dvh; max-width: min(86vw, 620px);
    margin: 0 auto; overflow: hidden; display: flex; flex-direction: column;
  }
  /* Result window runs as a wrapping row: main content and chat side by
     side, sharing one window rather than the chat being a separate box next
     to it. The ad's flex-basis:100% forces it onto its own line below both,
     making it read as the window's bottom banner. */
  #modal-result { align-items: center; justify-content: center; }
  #modal-result .modal-card {
    flex-direction: row; flex-wrap: wrap; align-content: flex-start;
    max-width: min(86vw, 760px); margin: 0 auto; overflow: hidden;
  }
  .result-body { order: 0; flex: 1 1 220px; min-width: 0; max-height: 94vh; max-height: 94dvh; overflow-y: auto; }
  /* flex-basis:100% puts the actions on their own full-width line below the
     body+chat row, so the chat column never squeezes them. */
  .result-actions { order: 2; flex: 1 1 100%; }
  /* Landscape only: banner above the "Table chat" heading, spanning the
     panel's full width. */
  .chat-ad-slot { display: flex; }
  /* Widened significantly (was min(184px, 19.2vw)) - it was too thin to read
     comfortably. .result-body's own basis above is reduced to make room, and
     the hand/score sizing below is trimmed further so the narrower body
     still fits everything without its own scrollbar in the common case. */
  #result-chat-slot {
    order: 1; flex: 1 1 260px; max-width: 340px;
    max-height: 94vh; max-height: 94dvh;
    margin-left: 8px; border-left: 1px solid rgba(201,162,75,0.35); padding-left: 8px;
  }
  #modal-result .chat-messages { font-size: 10px; padding: 4px 6px; }
  #result-ad { order: 3; flex: 1 1 100%; }   /* full-width line, below actions */
  #result-ad.ad-filled { margin-top: 6px; }
  #result-title { font-size: 15px; margin-bottom: 2px; }
  .result-total { font-size: 12px; margin: 0; }
  .result-hand {
    display: grid; grid-template-columns: 1fr 1fr; gap: 5px;
    margin: 5px 0; flex: 1 1 auto; min-height: 0; overflow: hidden;
  }
  /* overflow:hidden here silently cut tile rows off mid-tile rather than
     letting them wrap - the tiles are laid out in a wrapping flex row, so
     they only needed room, not clipping. */
  .result-player-hand { padding: 3px 5px; min-width: 0; }
  .result-player-label { font-size: 10px; margin-bottom: 2px; }
  .result-wins { font-size: 9px; padding: 1px 5px; margin-left: 4px; }
  .result-player-tiles { flex-direction: row; flex-wrap: wrap; gap: 2px; align-items: flex-start; }
  .result-sets, .result-remaining { gap: 1px; }
  .result-divider { width: 2px; height: auto; align-self: stretch; margin: 0 2px; }
  /* Trimmed further (was 17x23/15px) so the narrower .result-body - now
     giving up width to a wider chat column - still fits without its own
     horizontal scroll in the common case. */
  .modal-card .tile { width: 14px; height: 19px; font-size: 12px; }
  .modal-card .tile-label { display: none; }
  .modal-card .meld-group { padding: 1px; gap: 1px; }
  #result-next-host { margin-top: 4px; }
  #result-next-host button { padding: 6px 14px; font-size: 13px; }
  #result-next-waiting { font-size: 11px; margin: 2px 0 0; }
}

/* Portrait phones that are simply short */
@media (orientation: portrait) and (max-height: 620px) {
  .center-felt { min-height: 130px; }
  .drew-banner { padding: 3px 8px; font-size: 11px; }
}

/* ---------- Difficulty picker ---------- */
.difficulty-picker { display: flex; gap: 6px; background: rgba(0,0,0,0.25); border-radius: 9px; padding: 4px; }
.diff-btn {
  flex: 1; padding: 9px 6px; background: transparent; color: #B9C9BE;
  border-radius: 7px; font-weight: 700; font-size: 13px;
}
.diff-btn.active { background: #C9A24B; color: #241A12; }

/* ---------- Chow option chooser ---------- */
.chow-options { display: flex; flex-direction: row; flex-wrap: wrap; gap: 6px; width: 100%; align-items: center; justify-content: center; grid-column: 1 / -1; }
.chow-options-label { font-size: 12px; color: #B9C9BE; width: 100%; text-align: center; }
.chow-option {
  display: inline-flex; align-items: center; gap: 4px; padding: 5px 8px;
  background: #C9A24B; border-radius: 8px; cursor: pointer;
}
.chow-option:hover { background: #DCB65F; }
.chow-option .tile { pointer-events: none; }
.chow-cancel { background: rgba(255,255,255,0.14); color: #F3ECD9; padding: 8px 14px; border-radius: 7px; font-weight: 700; }

/* ---------- Concealed Kong ---------- */
.btn-kong {
  background: linear-gradient(180deg,#7C6BC4,#514396); color: #fff; font-weight: 800;
  padding: 12px 20px; border-radius: 9px; font-size: 15px; letter-spacing: 0.5px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.35);
}
/* A concealed Kong is shown to opponents the traditional way: the two end
   tiles face-down, the two middle tiles face-up, so the table knows a Kong
   was declared without seeing exactly which tile it was made from. */

/* Portrait phones: buttons stay in a compact column beside the hand (same
   sizing as landscape) so the discard pile keeps as much room as possible. */
@media (orientation: portrait) and (max-width: 700px) {
  /* Panels dock underneath the board rather than beside it, and the board
     keeps the majority of the height. */
  .game-body { flex-direction: column; }
  .side-dock {
    width: 100%; flex: 0 1 auto; max-height: 37%; min-height: 0;
    border-left: none; border-top: 2px solid rgba(201,162,75,0.35);
  }
  /* The table gives way first - the discard tiles rescale to whatever is left. */
  .game-main { flex: 1 1 0; min-height: 0; }
  .center-felt { min-height: 0; }
  .table-wrap { min-height: 0; }
  /* Room name and controls stay on one line. */
  .topbar { flex-wrap: nowrap; }
  .topbar-left { flex: 0 1 auto; min-width: 0; overflow: hidden; }
  .room-code { white-space: nowrap; font-size: 10px; }
  .topbar-right { flex: 0 0 auto; gap: 4px; }
  .topbar .btn-tiny { padding: 2px 5px; font-size: 10px; }
  /* Trim dead space at both ends: the top bar's background now runs up into
     the notch/dynamic-island strip instead of leaving a bare band above it,
     and the hand sits directly on the bottom safe-area inset. */
  .topbar { padding-top: env(safe-area-inset-top); padding-bottom: 2px; }
  .topbar-right { padding-top: 0; }
  .topbar-left { padding-top: 0; }
  .hand-area { padding-bottom: env(safe-area-inset-bottom, 0px); }
  .discard-hint { margin-top: 3px; font-size: 10px; }
  .drew-banner { padding: 3px 10px; }
  .hand-row { gap: 8px; align-items: flex-end; }
  .hand-actions { gap: 5px; position: relative; }
  .hand-actions button { min-width: 0; padding: 4px 10px; font-size: 12px; }
  .claim-actions { grid-template-columns: repeat(auto-fit, minmax(78px, 1fr)); max-width: 100%; }
  .claim-actions button { padding: 7px 8px; font-size: 13px; }
  .status-bar { gap: 10px; padding: 5px 8px; }
  .my-side-row { gap: 14px; margin-bottom: 0; }
  .btn-mahjong, .btn-kong { padding: 6px 12px; font-size: 13px; }
  .my-flower-tray { margin-right: 8px; padding-right: 8px; }
  .modal-rules-card { max-width: 70vw; max-height: 66vh; padding: 10px; }
  .rules-body { font-size: 10.5px; padding: 0 8px; }
  .rules-body h3 { font-size: 11.5px; }
}

/* Very narrow screens: the corner text can't fit on honour tiles (which have
   no number), so drop it there and keep only the numbers on suit tiles. */
@media (max-width: 420px) {
  .tile-honour .tile-label { display: none; }
}
@media (max-width: 340px) {
  .tile-label { display: none; }   /* nothing fits legibly at this width */
}

/* ================= Desktop / large screens ================= */
@media (min-width: 701px) and (min-height: 561px) {
  /* Result window on desktop: a column now, not a row - chat spans the FULL
     width below the hand result (right after the Leave button, with no
     divider line between them) rather than sitting in a narrow side column.
     The ad stays the bottom banner. */
  #modal-result .modal-card {
    flex-direction: column;
    max-width: min(80vw, 820px); padding: 20px 24px;
  }
  .result-body { order: 0; max-height: 82vh; overflow-y: auto; }
  .result-actions { order: 1; }
  #result-chat-slot {
    order: 2; flex: 0 0 auto; width: 100%;
    height: 220px; margin: 14px 0 0; border: none; padding: 0;
  }
  #result-ad { order: 3; }
  #result-ad.ad-filled { margin-top: 14px; }
  /* Two steps smaller than the shared formula - the desktop pile runs larger
     tiles, and the label was clipping at the wider sizes. */
  .discard-pile .tile.mini .tile-label { font-size: max(9px, calc(var(--pile-w) * 0.17)); }
  .tile { width: 44px; height: 58px; font-size: 34px; }
  .tile .tile-label { font-size: 11px; padding: 2px 0 1px 2px; }
  .tile-glyph { transform: scale(1.18); }        /* a little air under the label */
  .tile.large { width: 52px; height: 69px; font-size: 42px; }
  .tile.large .tile-label { font-size: 13px; padding: 3px 0 1px 2px; }
  .tile.mini { width: 44px; height: 58px; font-size: 35px; }
  .tile.mini .tile-label { font-size: 11px; padding: 2px 0 1px 2px; }
  .tile.small { width: 30px; height: 40px; font-size: 24px; }
  .tile.small .tile-label { font-size: 8px; padding: 1px 0 0 2px; }
  .discard-stack .tile { margin-left: -28px; }
  .discard-pile .tile.mini { width: 46px; height: 61px; font-size: 36px; }

  /* The table is a wide centred rectangle: it uses the horizontal room a big
     screen has, and stops short of the seats above and below it. */
  .center-felt { align-self: center; }
  .discard-pile { padding: var(--pile-pad) 14px; gap: 8px; }
  .table-wrap { gap: 10px; padding: 8px; }
  /* Roomier hand area, and completed sets rendered smaller than hand tiles so
     the two are easy to tell apart at a glance. */
  .hand-area { padding: 10px 16px 12px; }
  .my-hand { min-height: 78px; }
  .my-melds .tile { width: 30px; height: 40px; font-size: 24px; }
  .my-melds .tile .tile-label { font-size: 8px; padding: 1px 0 0 2px; }
  .my-flower-tray .tile { width: 30px; height: 40px; font-size: 24px; }
}

/* The winning seat stays marked until the next hand is dealt. */
.seat-card.hand-winner {
  border-color: #8FD6A8; border-width: 3px; background: rgba(143,214,168,0.18);
  box-shadow: 0 0 0 5px rgba(143,214,168,0.35), 0 0 30px rgba(143,214,168,0.4);
}
.hand-area.hand-winner {
  border-top-color: #8FD6A8;
  box-shadow: inset 0 6px 0 #8FD6A8, 0 0 34px rgba(143,214,168,0.35);
}
.result-player-hand.winner {
  border: 2px solid #8FD6A8; background: rgba(143,214,168,0.16);
  box-shadow: 0 0 0 3px rgba(143,214,168,0.25);
}

/* ---------- Lobby room settings ---------- */
/* .lobby-settings / .toggle-row and the switch styling that lived here were
   removed with the "Fill empty seats with bots" row - the On/Off button in
   the Bots picker is the single control for that now. */
/* No margin here: .bot-options is a flex column with its own gap, so this
   stacked on top of it and left ~20px above the "Bot difficulty" label. */
#difficulty-block { margin-top: 0; }
.seat-tag.empty { background: rgba(226,112,85,0.25); color: #E9A48F; }

/* Table positions with no player in this game collapse away. */
.seat.seat-unused { display: none; }

/* ---------- Hamburger menu ---------- */
.btn-hamburger { display: inline-flex; flex-direction: column; justify-content: center; gap: 3px; padding: 7px 9px; }
.btn-hamburger span { display: block; width: 15px; height: 2px; background: #F3ECD9; border-radius: 1px; }
.menu-panel {
  position: absolute; top: 100%; right: 10px; z-index: 50;
  width: min(250px, 82vw); max-height: min(70vh, 460px);
  background: #12281F;
  border: 1px solid rgba(201,162,75,0.4); border-radius: 10px;
  box-shadow: 0 10px 30px rgba(0,0,0,0.5); padding: 10px;
  display: flex; flex-direction: column; gap: 8px; overflow-y: auto;
}
/* Pushes Leave and the footer to the bottom of the menu. */
.menu-spacer { flex: 1 1 auto; min-height: 46px; border-radius: 8px; background: rgba(255,255,255,0.04); }
.menu-logo { width: 82%; height: auto; margin: 4px auto 0; display: block; opacity: 0.9; }
.menu-copyright { text-align: center; font-size: 10px; color: #9DB0A5; line-height: 1.5; }
.menu-item {
  width: 100%; padding: 10px; border-radius: 8px; font-size: 11px; font-weight: 800;
  background: rgba(255,255,255,0.08); color: #F3ECD9; text-align: center;
  text-transform: uppercase; letter-spacing: 0.5px;
}
.menu-item:hover { background: rgba(255,255,255,0.16); }
.menu-panel .menu-item-leave { background: #B23A2E; color: #fff; }
.menu-panel .menu-item-leave:hover { background: #C9463A; }
.menu-version { text-align: center; font-size: 11px; color: #6B7A70; letter-spacing: 0.5px; }

/* Standings at the foot of the result screen. */

/* Portrait: the table gives way so a docked panel always fits on screen. */
/* A narrow window (portrait-shaped but wider than a phone) also needs the dock
   stacked underneath, otherwise a 34vw side panel squeezes the table to
   nothing. Same shrink rules apply. */
@media (orientation: portrait) and (min-width: 701px) {
  .game-body { flex-direction: column; }
  .side-dock {
    width: 100%; flex: 0 1 auto; max-height: 42%; min-height: 0;
    border-left: none; border-top: 2px solid rgba(201,162,75,0.35);
  }
  .game-main { flex: 1 1 0; min-height: 0; }
  .center-felt { min-height: 0; }
}

/* ---------- Rules and notice dialogs ---------- */
.modal-rules-card {
  position: relative; max-width: min(734px, 94vw); max-height: 88vh;
  overflow: hidden; text-align: left; display: flex; flex-direction: column;
}
.rules-body {
  font-size: 12px; line-height: 1.6; color: #E4E9E3;
  /* Right gutter keeps text clear of the scrollbar AND the scroll arrow;
     bottom gutter keeps the last line clear of it too. */
  padding: 0 52px 46px 16px;
  flex: 1 1 auto; min-height: 0; overflow-y: auto; scrollbar-gutter: stable;
}
.rules-body h3 { font-size: 13px; color: #C9A24B; margin: 14px 0 4px; }
.rules-body h3:first-child { margin-top: 4px; }
.rules-body p, .rules-body ul, .rules-body ol { margin: 4px 0; }
.rules-body ul, .rules-body ol { padding-left: 20px; }
.rules-body li { margin-bottom: 3px; }
.rules-body b { color: #F2CE7A; }
.modal-notice-card { max-width: 360px; }
.modal-notice-card p { font-size: 13px; color: #B9C9BE; line-height: 1.5; margin: 8px 0 16px; }

/* ---------- Desktop: a taller, centred table ---------- */
@media (min-width: 701px) and (min-height: 561px) {
  .center-felt { width: min(95%, 1100px); height: 60%; max-height: 60%; align-self: center; }
}

/* ---------- Portrait: bonus tiles above the Discard button ---------- */
/* Scores as a pop-out rather than a docked panel. */
/* Content-sized in both directions: the width cap keeps the table from
   stretching on a wide screen, and height is left to the content (the shared
   .modal-card max-height still applies as an upper bound). */
/* Doubled from 360px - the W/L/D/PTS table was cramped at the old width. */
.modal-scores-card { max-width: min(720px, 94vw); width: auto; height: auto; }
.modal-scores-card #score-table { font-size: 13px; margin: 10px 0 0; }
.modal-scores-card #score-table td { padding: 7px 4px; }

/* A little breathing room above the top bar, and space so the chat badge
   isn't clipped by the bar's edge. */

/* The top bar already pads for the notch; everything below it needs the same
   treatment, because in landscape the cutout is on the left or right edge and
   would otherwise sit over the seats, table and hand. */
/* The board deliberately runs the full width of the device, ignoring cutout
   insets - only the top bar (which holds the room name and buttons) pads for
   them, so nothing interactive ends up under a notch. */

/* Rules window: 25% narrower, with a hint when its content overflows. */
.rules-scroll-hint {
  /* Parked in the reserved gutter, clear of both the text and the scrollbar. */
  position: absolute; right: 16px; bottom: 14px; z-index: 3;
  width: 30px; height: 30px; border-radius: 50%;
  background: rgba(201,162,75,0.92); color: #241A12;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; line-height: 1; font-weight: 900; pointer-events: none;
  box-shadow: 0 2px 8px rgba(0,0,0,0.5);
  animation: scrollNudge 1.5s ease-in-out infinite;
}
@keyframes scrollNudge { 0%,100% { transform: translateY(0); } 50% { transform: translateY(3px); } }

/* Opponents' bonus tiles at half size - they're reference only. */
.seat .seat-flowers .tile.small {
  width: calc(var(--seat-w) * 0.5);
  height: calc(var(--seat-w) * 0.5 * 4 / 3);
  font-size: calc(var(--seat-w) * 0.41);
}
.seat .seat-flowers .tile.small .tile-label { display: none; }

/* No dead space under the rearrange hint. */
.hand-area > .discard-hint:last-child { margin-bottom: 0; }

/* ---------- Lobby: which seat is yours ---------- */
.lobby-seat.is-you { border: 1px solid #C9A24B; background: rgba(201,162,75,0.16); }
.you-tag {
  font-size: 10px; font-weight: 800; letter-spacing: 0.5px;
  background: #C9A24B; color: #241A12; padding: 2px 7px; border-radius: 20px; margin-left: 6px;
}

/* ---------- Refresh feedback ---------- */
.refresh-flash {
  position: fixed; inset: 0; z-index: 60; display: flex;
  align-items: center; justify-content: center; pointer-events: none;
}
.refresh-flash .spinner {
  width: 64px; height: 64px; border-radius: 50%;
  background: rgba(11,61,46,0.92); border: 3px solid #C9A24B;
  display: flex; align-items: center; justify-content: center;
  font-size: 30px; color: #C9A24B;
  box-shadow: 0 6px 24px rgba(0,0,0,0.55);
  animation: refreshSpin .6s ease-in-out;
}
@keyframes refreshSpin {
  0%   { transform: rotate(0deg) scale(0.7); opacity: 0; }
  25%  { opacity: 1; }
  100% { transform: rotate(360deg) scale(1); opacity: 0; }
}

/* Bonus tiles are half-size on phones to save room; a desktop has space for
   them at full tile size. Declared last so it wins over the base rule - a
   media query adds no specificity of its own. */
@media (min-width: 701px) and (min-height: 561px) {
  .seat .seat-flowers .tile.small {
    width: calc(var(--seat-w) * 0.62);
    height: calc(var(--seat-w) * 0.62 * 4 / 3);
    font-size: calc(var(--seat-w) * 0.5);
  }
  .seat .seat-flowers .tile.small .tile-label { display: none; }
}

/* ---------- Result screen: winner above the line ---------- */
.result-winner { display: flex; flex-direction: column; gap: 8px; margin: 12px 0 0; text-align: left; }
.result-split {
  height: 2px; background: rgba(201,162,75,0.55); border-radius: 1px;
  margin: 12px 0 4px; flex-shrink: 0;
}
.result-winner .result-player-hand { border: 2px solid #C9A24B; background: rgba(201,162,75,0.14); }

/* Scores window: names hard against the left edge. */
#score-table td { text-align: center; }
#score-table tr td:first-child { text-align: left; padding-left: 0; }
#score-table { table-layout: auto; }

/* =====================================================================
   Accessibility and device-class fixes (QA v0.44.0)
   ===================================================================== */

/* Visible focus for keyboard users - there was none at all before. */
:focus-visible {
  outline: 3px solid #F2CE7A;
  outline-offset: 2px;
}
.tile[role="button"]:focus-visible { outline-offset: 3px; }

/* Touch devices get targets at the 44px guideline, regardless of screen size.
   This is keyed on the INPUT METHOD rather than width, which is what actually
   matters - a tablet and a landscape phone both need it, and neither was
   picking up the phone-width rules. */
@media (pointer: coarse) {
  /* Sized to the label with a small margin. Still comfortably above the WCAG
     2.5.8 minimum of 24px, though below Apple's 44pt recommendation. */
  .btn-tiny { min-height: 32px; padding: 4px 9px; font-size: 13px; }
  .claim-actions button { min-height: 34px; padding: 5px 14px; }
  .hand-actions button { min-height: 34px; padding: 5px 14px; min-width: 0; }
  .menu-item { min-height: 42px; }
  .diff-btn { min-height: 38px; }
  .modal-close { width: 36px; height: 36px; font-size: 15px; }
  .chat-form button { min-height: 34px; padding: 0 12px; }
  .lobby-seat { min-height: 44px; }
}

/* Tablets were falling into the DESKTOP block: mouse-sized controls and a
   34vw side dock on a touch screen. Give them their own treatment. */
@media (pointer: coarse) and (min-width: 701px) {
  .side-dock { width: min(360px, 38vw); }
  .tile.large { width: 56px; height: 75px; font-size: 45px; }
  .tile.large .tile-label { font-size: 13px; }
  .hand-actions button { min-width: 120px; font-size: 15px; }
  .claim-actions button { font-size: 15px; }
  .topbar .btn-tiny { font-size: 14px; }
}

/* A short desktop window is not a phone. The landscape-phone block keys on
   height alone, so shrinking a browser window vertically used to switch the
   whole layout to the compressed phone treatment. Restore desktop spacing
   where the pointer is fine (mouse/trackpad). */
@media (pointer: fine) and (orientation: landscape) and (max-height: 560px) and (min-width: 1024px) {
  .seats-row { flex: 0 0 42%; }
  .center-felt { width: min(92%, 1100px); }
  .tile.large { width: 40px; height: 53px; font-size: 33px; }
  .hand-actions button { min-width: 96px; font-size: 13px; padding: 7px 12px; }
  .discard-hint { display: block; }
}

/* Respect a user's reduced-motion preference. */
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important; }
}

/* Result button next to the "X got Mahjong!" text, not centred on its own. */
.status-result-btn { margin-left: 2px; flex-shrink: 0; }

/* ---------- Leave Table on the result screen ---------- */
/* Static block outside the scrolling .result-body - flex-shrink:0 so it keeps
   its height when the body above it is tall, which is what stops the buttons
   being pushed out of view or scrolled away. */
.result-actions { flex-shrink: 0; }
.result-actions-row {
  display: flex; align-items: center; justify-content: center;
  gap: 10px; margin-top: 6px; flex-wrap: wrap;
}
/* Matches the Leave Table button beside it: same uppercase treatment,
   padding and font-size, so the pair reads as one set of equal actions. */
#btn-next-hand {
  padding: 10px 20px; border-radius: 8px; font-size: 13px; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.5px;
}
/* Same red-on-white treatment as the menu's Leave Table. */
.result-leave-btn {
  padding: 10px 20px; border-radius: 8px; font-size: 13px; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.5px;
  background: #B23A2E; color: #fff;
}
.result-leave-btn:hover { background: #C9463A; }
/* Lets a player back out of a room before the game starts - the lobby had no
   way to leave at all before, short of closing the tab. Same red-on-white
   treatment as the other Leave buttons, self-contained rather than relying
   on a .menu-panel ancestor (there isn't one here). */
.lobby-leave-btn {
  /* Matches Start Game's own padding/font-size now that they sit side by
     side in .lobby-main-actions (previously stacked, with this deliberately
     smaller/secondary-looking below a full-width Start). */
  display: block; width: auto; margin: 0;
  padding: 12px; border-radius: 9px; font-size: 15px; font-weight: 800;
  text-transform: uppercase; letter-spacing: 0.5px;
  background: #B23A2E; color: #fff; text-align: center;
}
.lobby-leave-btn:hover { background: #C9463A; }

/* ---------- Viewport height ----------
   dvh (dynamic viewport height) is the unit that tracks the visible area as
   mobile browser chrome shows and hides - exactly the problem here. vh is
   stated first as the fallback for anything that doesn't understand dvh; a
   browser that doesn't ignores the second declaration entirely.
   This replaces an earlier JS approach driven by visualViewport.height,
   which was the wrong measurement (see the note in client.js) and made the
   bottom gap larger rather than closing it. */
#screen-game { height: 100vh; height: 100dvh; }
/* While the on-screen keyboard is up, the game layer shrinks to the height
   that is actually visible above it (set from visualViewport in client.js).
   Without this the layer keeps its full height, the keyboard covers the
   bottom of it, and the space under the chat input becomes unreachable dead
   area. Scoped to .keyboard-open so normal play is completely unaffected. */
html.keyboard-open #screen-game { height: var(--kb-height); }
/* The bottom safe-area inset reserves room for the home indicator. That is
   correct in an installed app, but in a browser the chrome already occupies
   that region, so reserving it again is what left a band above the URL bar. */
@media (display-mode: standalone), (display-mode: fullscreen) {
  .hand-area { padding-bottom: env(safe-area-inset-bottom, 0px); }
}
@media (display-mode: browser) {
  .hand-area { padding-bottom: 2px; }
}

/* ---------- Portrait: reclaim slack for an open dock panel ----------
   With the chat or log open in portrait, vertical space is at a premium.
   These rules close up the gaps around the felt so the panel below can grow
   into that space, rather than the board keeping padding it does not need.
   Scoped to portrait phones and to the dock actually being open, so normal
   play is untouched. */
@media (orientation: portrait) and (max-width: 700px) {
  #screen-game.dock-open .table-wrap { gap: 0; padding: 0; }
  #screen-game.dock-open .center-felt { margin-top: 0; margin-bottom: 0; }
  #screen-game.dock-open .seats-row { padding-bottom: 0; }
  #screen-game.dock-open .hand-area { padding-top: 2px; }
  #screen-game.dock-open .my-side-row { margin-bottom: 0; }
  /* Four-player table: the opponents' face-down tiles are the biggest block
     of reclaimable height, and they convey nothing actionable - the hand
     count is shown instead as a one-line label. Dropped only while a panel is open
     and only at a full table; at 2-3 players the space freed above is
     already enough. */
  #screen-game.dock-open-full-table .hidden-hand { display: none; }
  /* ...replaced by a one-line count, so the information those tiles carried
     (how many tiles each opponent holds) is still there. */
  #screen-game.dock-open-full-table .hidden-hand-count { display: block; }
}

/* Compact substitute for an opponent's face-down tiles - see the dock-open
   rules above. Hidden unless those tiles are collapsed. */
.hidden-hand-count { display: none; font-size: 10px; font-weight: 700; color: #B9C9BE; text-align: center; padding: 1px 0; }

/* A bot-count option that exceeds the free seats stays visible but inert, so
   the row does not reflow as people join and leave. */
.diff-btn.unavailable { opacity: 0.35; cursor: not-allowed; }

/* The bot options sit inside the lobby card now, directly under the toggle -
   there is no separate panel and no sideways window growth. A dashed rule
   separates them from the seat list above, reading as a fold within one
   window rather than the edge of a second one. */
/* Separate section under the bots toggle, divided from it by a dashed rule -
   a fold within the one window, not the edge of a second one. */
.bot-options {
  border-top: 1px dashed rgba(201,162,75,0.25);
  /* No margin-top: the seat list above already ends with its own bottom
     margin, and stacking the two left a visible gap above the divider. */
  padding-top: 12px; margin-top: 0; margin-bottom: 14px;
  display: flex; flex-direction: column; gap: 8px;
}
.bot-options.hidden { display: none; }
#botcount-block.hidden, #difficulty-block.hidden { display: none; }
/* The hints inside these blocks are captions for the picker directly above
   them, so they sit tight to it rather than carrying .hint's default 10px
   lead-in - which, on top of the flex gap between blocks, opened a gap
   between the count hint and the "Bot difficulty" heading below it. */
.bot-options .hint { margin-top: 4px; }
/* The ON/OFF control sits in the picker row and matches the number buttons
   beside it, so the whole row reads as one set of choices. */
.diff-btn.bots-onoff { min-width: 52px; letter-spacing: 0.5px; }
.diff-btn.hidden { display: none; }

/* Landscape phones specifically: everything more compact, since height is
   the scarce dimension there rather than width. */
@media (orientation: landscape) and (max-height: 560px) {
  #screen-lobby { padding: 6px 8px; }
  /* Wider than the shared 460px cap - a landscape phone has plenty of
     horizontal room and the narrow column wasted most of it while making
     the card taller than it needed to be. */
  #lobby-shell { max-height: 100%; overflow-y: auto; max-width: min(96vw, 760px); }
  .lobby-card { padding: 10px 14px; }
  .lobby-card h2 { font-size: 17px; margin-bottom: 4px; }
  .lobby-card .hint { font-size: 10.5px; margin-top: 5px; }
  .share-row { margin-top: 4px; }
  .share-row input { font-size: 9px; padding: 6px 8px; }
  /* Two columns instead of one - a 4-seat list stacked in a single column
     was needlessly tall for a screen where height is what's scarce. */
  .lobby-seats { grid-template-columns: 1fr 1fr; gap: 4px; margin-top: 6px; margin-bottom: 10px; }
  .lobby-seat { min-height: 34px; padding: 5px 8px; font-size: 12px; }
  /* No width/min-width override here - Start now shares a row with Leave
     (.lobby-main-actions), and a leftover width:100% from when it was the
     only button in its own row would have overridden the flex:1 split that
     keeps the two evenly matched, likely squeezing Leave into a sliver. */
  #btn-start { padding: 9px; font-size: 14px; }
  /* Bots and difficulty side by side rather than stacked - same reasoning
     as the two-column seat list above: trade width for height. */
  .bot-options { flex-direction: row; gap: 14px; padding-top: 8px; margin-bottom: 8px; }
  .bot-options > div { flex: 1 1 0; min-width: 0; }
  .difficulty-picker { gap: 4px; }
  .diff-btn { padding: 6px 8px; font-size: 12px; min-height: 32px; }
}

/* Grey placeholder banner below the whole lobby window. */
.lobby-ad-slot { max-width: 460px; }
.lobby-ad-slot.ad-filled { margin-top: 14px; }

/* ---------- Landing / lobby additions ---------- */
.landing-card, .lobby-card { position: relative; }
/* Rules reachable before joining a table, same spot on both cards. */
.corner-rules { position: absolute; top: 12px; right: 12px; z-index: 2; }

.rejoin-prompt {
  background: rgba(201,162,75,0.12); border: 1px solid rgba(201,162,75,0.45);
  border-radius: 12px; padding: 12px; margin-bottom: 14px;
  display: flex; flex-direction: column; gap: 10px;
}
.rejoin-prompt.hidden { display: none; }
.rejoin-text { font-size: 13px; color: #F3ECD9; }
.rejoin-actions { display: flex; gap: 8px; }
.rejoin-actions button { flex: 1 1 0; padding: 9px; font-size: 13px; }

/* Host-only control on a lobby seat row. */
.seat-kick {
  background: rgba(226,112,85,0.18); color: #E9A48F;
  border-radius: 50%; width: 22px; height: 22px; line-height: 1;
  font-size: 12px; font-weight: 800; flex-shrink: 0;
}
.seat-kick:hover { background: #B23A2E; color: #fff; }

/* Final standings on the game-over screen. */
.gameover-standings { width: 100%; margin: 12px 0; border-collapse: collapse; font-size: 13px; }
.gameover-standings td { padding: 6px 4px; text-align: center; }
.gameover-standings td:first-child { text-align: left; font-weight: 700; }
.gameover-standings .go-h { font-size: 11px; color: #8FA396; }
.gameover-standings .rec-w { color: #8FD6A8; font-weight: 700; }
.gameover-standings .rec-l { color: #E9A48F; font-weight: 700; }
.gameover-standings .rec-d { color: #C9B78A; font-weight: 700; }

/* No CSS visibility rule here on purpose: the JS already feature-detects
   requestFullscreen support (see client.js) and shows/hides the button
   accordingly, on any device that genuinely supports it - Android Chrome
   does, hiding the URL bar entirely, which is the "play like an app" ask.
   A blanket CSS hide here (as there used to be) overrode that correct,
   feature-based decision and hid the button even where it would have worked.
   iOS Safari still won't show it (no support until 16.4, and this stays
   conservative rather than risking a dead button there); installing to the
   home screen is the reliable route on iOS - see the manifest, which
   already launches display:fullscreen once installed. */

/* ---------- MCR scoring display ---------- */
.result-scoring {
  margin: 8px 0; padding: 8px 10px; border-radius: 8px;
  background: rgba(0,0,0,0.22); text-align: left;
}
.result-scoring.hidden { display: none; }
.score-line { display: flex; justify-content: space-between; gap: 12px; font-size: 12px; padding: 2px 0; color: #E4E9E3; }
.score-line b { color: #F2CE7A; font-weight: 800; }
.score-total { border-top: 1px solid rgba(201,162,75,0.35); margin-top: 4px; padding-top: 5px; font-weight: 700; }
.score-gain b { color: #8FD6A8; }
.score-gain b.score-loss { color: #E9A48F; }
/* Running point total beside each player's W/L/D on the result screen. */
.rec-p { color: #F2CE7A; background: rgba(242,206,122,0.14); padding: 2px 7px; border-radius: 10px; }

/* ---------- Install prompt ---------- */
.install-prompt {
  display: flex; align-items: center; gap: 10px;
  background: rgba(201,162,75,0.12); border: 1px solid rgba(201,162,75,0.45);
  border-radius: 12px; padding: 10px 12px;
  /* Sits BELOW the Create Table button now, so the gap belongs above it. */
  margin: 14px 0 0;
  font-size: 12px; color: #F3ECD9;
}
.install-prompt.hidden { display: none; }
.install-prompt span { flex: 1 1 auto; }
.install-prompt button { flex-shrink: 0; }

/* ---------- Result standings + collapsible detail ---------- */
.result-standings { margin: 8px 0; }
.result-standings.hidden { display: none; }
.stand-row {
  display: flex; align-items: center; gap: 8px;
  padding: 3px 8px; font-size: 12px; border-radius: 6px;
}
.stand-row:nth-child(odd) { background: rgba(0,0,0,0.18); }
.stand-name { flex: 1 1 auto; text-align: left; color: #F3ECD9; }
.stand-swing { font-size: 11px; font-weight: 700; }
.stand-swing.pos { color: #8FD6A8; }
.stand-swing.neg { color: #E9A48F; }
.stand-total { color: #F2CE7A; font-weight: 800; min-width: 44px; text-align: right; }
.scoring-toggle { display: inline-flex; margin: 2px auto 6px; }
.scoring-toggle.hidden { display: none; }

/* ---------- Landing: landscape two-column ----------
   Portrait and desktop keep the single stacked column (the .landing-brand /
   .landing-main wrappers are inert there). Landscape phones are short, and
   the stacked card was taller than the viewport - so branding moves to the
   left and everything interactive to the right, halving the height. */
@media (orientation: landscape) and (max-height: 560px) {
  #screen-landing { padding: 8px; align-items: center; }
  .landing-card {
    display: flex; flex-direction: row; align-items: center; gap: 18px;
    max-width: 680px; padding: 14px 18px;
    /* Never taller than the screen; scrolls internally in the rare case it
       still is, rather than being clipped at both ends as before. */
    max-height: calc(100vh - 16px); max-height: calc(100dvh - 16px);
    overflow-y: auto;
  }
  .landing-brand {
    flex: 0 0 42%;
    display: flex; flex-direction: column;
    /* Centred both ways within its own column, so the logo sits squarely
       above the tagline rather than drifting to one side of it. */
    align-items: center; justify-content: center; text-align: center;
  }
  .landing-brand .brand { margin-bottom: 0; width: 100%; }
  /* Larger than the 180px this started at - landscape has the horizontal room
     to spare, and the logo was the one thing that got smaller in the layout
     that was meant to make better use of the width. */
  .landing-brand .brand img { width: min(240px, 100%); margin: 0 auto; }
  .landing-brand .tag { font-size: 11px; margin: 8px 0 0; }
  .landing-main { flex: 1 1 auto; min-width: 0; }
  /* Compact the controls to match the reduced height. */
  .landing-tabs { margin-bottom: 8px; }
  .landing-card label { font-size: 12px; }
  .landing-card input { padding: 7px 10px; font-size: 13px; }
  .landing-card .btn-primary { padding: 9px; font-size: 14px; }
  .version-tag { margin: 6px 0 0; font-size: 10px; }
  .install-prompt { padding: 7px 9px; font-size: 11px; margin: 8px 0 0; }
  .rejoin-prompt { padding: 8px; margin-bottom: 8px; }
  .rejoin-text { font-size: 11.5px; }
  .rejoin-actions button { padding: 7px; font-size: 12px; }
  .corner-rules { top: 8px; right: 8px; }
}

/* Mobile: Share replaces Copy entirely - the OS share sheet includes a copy
   action anyway, so two buttons is redundant on a small screen. Scoped to
   .has-share (set by JS only when navigator.share actually exists), so a
   phone on HTTP - where Share is unavailable - still keeps Copy. */
@media (orientation: portrait) and (max-width: 700px),
       (orientation: landscape) and (max-height: 560px) {
  .share-row.has-share #btn-copy { display: none; }
}

/* On/Off state beside the bots toggle label. */
.bots-state { color: #8FA396; font-weight: 800; letter-spacing: 0.5px; }
.bots-state.on { color: #8FD6A8; }

/* Marks the room host in the lobby seat list. Light blue, matching the
   dealer-name treatment used on the game board. */
.host-tag {
  color: #7EC8E3; font-weight: 800; font-size: 11px;
  letter-spacing: 0.6px; margin-right: 5px;
}

/* Column headers for the result-screen standings. Muted and non-bold so they
   read as labels rather than another player row. */
.stand-row.stand-head {
  background: none; color: #8FA396; font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.5px;
  border-bottom: 1px solid rgba(201,162,75,0.25); margin-bottom: 2px;
}
.stand-row.stand-head .stand-name,
.stand-row.stand-head .stand-swing,
.stand-row.stand-head .stand-total { color: inherit; font-weight: 700; }

/* ---------- Mobile lobby: trim dead vertical space ----------
   Phone screens have no room for the generous desktop spacing, and the lobby
   was carrying it wholesale. Scoped to touch/mobile widths so the desktop
   layout is untouched. */
@media (orientation: portrait) and (max-width: 700px) {
  /* Portrait gets its own compact padding; landscape sets a tighter one in
     its own block above, and putting it in a shared rule here would override
     that (equal specificity, and this block comes later). */
  .lobby-card { padding: 14px 16px; }
}
@media (orientation: portrait) and (max-width: 700px),
       (orientation: landscape) and (max-height: 560px) {
  /* Centred on screen. It was top-aligned in the previous revision to remove
     an empty band above it, but the real cause of that band was the oversized
     margins inside the card (fixed separately) - top-aligning was treating
     the symptom and left the window sitting oddly high. */
  #screen-lobby { padding: 8px; justify-content: center; }
  #lobby-shell { margin-top: 0; }
  /* Padding is NOT set here: the landscape block above already sets a tighter
     value, and repeating it here would override that (this block comes later
     and both selectors have equal specificity). Portrait picks up its padding
     from its own block instead. */
  /* 22px below the share row was the gap above the player list. */
  .share-row { margin: 4px 0 10px; }
  .lobby-card-head h2 { font-size: 18px; }
  .lobby-card-head + .hint { margin-top: 4px; }
  .lobby-seats { margin-bottom: 10px; }
}

/* Shown instead of the game when another tab on this device already owns it. */
.tab-lockout {
  max-width: 460px; margin: 12vh auto; padding: 28px;
  background: rgba(20, 45, 36, 0.75); border: 1px solid rgba(201,162,75,0.35);
  border-radius: 16px; text-align: center; color: #F3ECD9;
}
.tab-lockout p { color: #B9C9BE; font-size: 13px; line-height: 1.6; }
.tab-lockout button { margin-top: 14px; padding: 12px 24px; }

/* Doubling the scores width leaves room for the table to breathe. */
.modal-scores-card #score-table { width: 100%; }
