/* ================================================================ reset & base */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { height: 100%; overflow: hidden; }
body {
  height: 100%; overflow: auto;
  font-family: system-ui, -apple-system, sans-serif;
  background: #0d3320; color: #e8e0d0;
  -webkit-tap-highlight-color: transparent;
  user-select: none;
}
.hidden { display: none !important; }
.screen { min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 24px 16px; }

/* ================================================================ home */
.back-home { align-self: flex-start; color: #8ab89a; text-decoration: none; font-size: 13px; margin-bottom: 16px; }
.logo { font-size: clamp(28px, 6vw, 44px); letter-spacing: 4px; text-align: center; margin: 12px 0 8px; color: #e8d5a3; }
.tagline { max-width: 520px; text-align: center; font-size: 14px; line-height: 1.55; color: #b0c8a8; margin-bottom: 24px; }
.field { width: 100%; max-width: 320px; padding: 10px 14px; border: 2px solid #2a6b45; border-radius: 8px; background: #0a2a1a; color: #e8e0d0; font-size: 16px; text-align: center; outline: none; }
.field:focus { border-color: #4db87a; }
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 10px 22px; border: none; border-radius: 8px; font-size: 15px; font-weight: 600; cursor: pointer; transition: background .15s, opacity .15s; }
.btn:disabled { opacity: .4; pointer-events: none; }
.btn.primary { background: #2a8c52; color: #fff; }
.btn.primary:hover { background: #34a862; }
.btn.secondary { background: #1a5a38; color: #c8e0d0; }
.btn.secondary:hover { background: #226b44; }
.btn.ghost { background: none; color: #8ab89a; padding: 6px 10px; font-size: 13px; }
.btn.ghost:hover { color: #b8e0c8; }
.divider { width: 100%; max-width: 320px; text-align: center; font-size: 13px; color: #5a8a6a; margin: 12px 0; }
.join-row { display: flex; gap: 8px; width: 100%; max-width: 320px; }
.join-row .field { flex: 1; }
#home-status { font-size: 13px; min-height: 20px; margin: 8px 0; }
#home-status.error { color: #e85a5a; }
.small-print { max-width: 520px; text-align: center; font-size: 11px; line-height: 1.5; color: #5a8a6a; margin-top: 20px; }

/* ================================================================ lobby */
.lobby-panel { width: 100%; max-width: 400px; }
.lobby-label { font-size: 12px; color: #5a8a6a; text-transform: uppercase; letter-spacing: 1px; margin-top: 16px; }
#lobby-code { font-size: 32px; font-weight: 700; letter-spacing: 6px; color: #e8d5a3; }
.copy-row { display: flex; gap: 8px; margin: 4px 0 12px; }
#lobby-players { list-style: none; }
#lobby-players li { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid #1a4a32; font-size: 15px; min-height: 42px; }
#lobby-players li.empty { opacity: .55; }
.p-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.empty-label { color: #6a9a7a; font-style: italic; }
.seat-remove { margin-left: auto; }
.av { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 13px; color: #fff; flex-shrink: 0; }
.av.s0 { background: #2a8c52; } .av.s1 { background: #b85a2a; } .av.s2 { background: #5a5ab8; } .av.s3 { background: #8a2a6a; }
.av.bot { background: #4a4a4a; }
.empty-av { background: transparent; border: 2px dashed #2a6b45; }
#variant-picker { display: flex; gap: 6px; flex-wrap: wrap; margin: 6px 0; }
#variant-picker .chip { padding: 6px 14px; border-radius: 16px; font-size: 13px; cursor: pointer; background: #1a4a32; color: #8ab89a; border: 2px solid transparent; }
#variant-picker .chip.active { background: #2a6b45; color: #e8d5a3; border-color: #4db87a; }
#variant-blurb { font-size: 13px; color: #8ab89a; margin: 4px 0 12px; min-height: 36px; }
#lobby-hint { font-size: 13px; color: #5a8a6a; min-height: 20px; margin-bottom: 8px; }

/* ================================================================ game layout */
/* at least a full screen (top-anchored, slack at the bottom), but allowed to
   grow taller than the viewport at big tile sizes — the page then scrolls
   instead of clipping the hand off the bottom */
#screen-game { padding: 0; display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }
.topbar { display: flex; align-items: center; gap: 8px; padding: 6px 12px; background: #0a2a1a; border-bottom: 1px solid #1a4a32; min-height: 40px; font-size: 13px; flex-shrink: 0; position: relative; z-index: 70; }
.topbar span { color: #8ab89a; }
.spacer { flex: 1; }
#room-chip { font-weight: 700; color: #e8d5a3 !important; letter-spacing: 2px; }

/* tile-size popover (opened by the Size button) */
#size-popover {
  /* inset enough to clear the page scrollbar (macOS overlay bars sit over content) */
  position: absolute; top: 44px; right: 24px; z-index: 80;
  background: #0d3320; border: 1px solid #2a6b45; border-radius: 10px;
  padding: 10px 12px; box-shadow: 0 8px 24px rgba(0,0,0,.5);
  display: flex; flex-direction: column; gap: 8px; min-width: 220px;
}
#size-popover .sp-row { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #b0c8a8; }
#size-popover label { width: 72px; flex-shrink: 0; }
#size-popover input[type="range"] { flex: 1; accent-color: #4db87a; }
#size-popover span { width: 40px; text-align: right; color: #8ab89a; font-variant-numeric: tabular-nums; }

/* 2x2 square table: one equal quadrant per player, me at the bottom-left, turn
   order counter-clockwise (BL -> BR -> TR -> TL). Equal quadrants keep every
   player's discard tray the same size. --bubble-reserve is the strip kept above
   the top row so those players' speech bubbles clear the top bar. */
#table {
  /* headroom for the top players' speech bubbles, which stack upward from the
     seat; one bubble's worth is enough to keep them off the top bar */
  --bubble-reserve: 40px;
  flex: 0 0 auto; display: grid; gap: 8px; padding: var(--bubble-reserve) 4px 0; position: relative;
  /* stretch across the width and centre the tracks, so the side columns get a
     generous FIXED width (room for a few melds without clipping) while the centre
     stays a fixed width — nothing resizes as content changes */
  align-self: stretch; justify-content: center;
  /* Side columns grow with the window up to whatever THIS player's tile sizes
     actually need: the sets column beside the pond, or a full concealed hand,
     whichever is wider. A fixed cap would mean raising the Played/Discards
     sliders could never fit side by side however wide the screen. At 100% this
     works out to the same 440px as before. */
  /* 36px of fixed furniture: 8px of gaps inside the sets box, 16px inside the
     pond, 4px between them and 2 x 4px of seat padding */
  --seat-max: calc((320px * var(--ts-played) + 240px * var(--ts-disc)) * var(--ts-base) + 36px);
  /* --seat-max caps the side columns at the SIDE-BY-SIDE board width. Below that
     the track is simply the space available, which is what the stacked shapes
     spread into. */
  /* the centre is as narrow as its widest fixed element, the "Your turn" pill */
  grid-template-columns: minmax(0, var(--seat-max)) 92px minmax(0, var(--seat-max));
  grid-template-rows: auto auto;
}

/* ---- player quadrants */
/* Padding is kept to 4px on purpose. Every pixel of furniture here is paid for
   twice over in the table's width, and it is what decides how narrow a window
   can still hold the sets beside the pond — so the quadrants stay tight and the
   layout keeps its landscape shape as long as possible. */
.seat { display: flex; flex-direction: column; align-items: center; gap: 4px; overflow: visible; padding: 4px; min-width: 0; min-height: 0; border-radius: 10px; transition: box-shadow .15s, background .15s; }
/* whose turn it is — a gold ring around the active quadrant, drawn INSET. An
   outset glow paints outside the box without occupying any, so it can only look
   right if the neighbours leave it room, and that room comes out of the tiles'
   budget. Inset, it costs nothing and can never bleed into the seat next door. */
.seat.active-turn, #my-zone.active-turn {
  box-shadow: inset 0 0 0 2px rgba(232,213,163,.9), inset 0 0 16px 2px rgba(232,213,163,.35);
  background: rgba(232,213,163,.08);
}
.seat-inner { display: flex; flex-direction: column; align-items: center; gap: 4px; width: 100%; }

.seat-info { display: flex; align-items: center; gap: 6px; font-size: 12px; flex-wrap: wrap; justify-content: center; }
.seat-name { font-weight: 600; color: #e8d5a3; max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.seat-wind { background: #2a6b45; color: #e8d5a3; padding: 1px 6px; border-radius: 4px; font-size: 11px; font-weight: 700; }
.seat-wind.dealer { background: #b85a2a; }
.seat-score { color: #8ab89a; font-size: 11px; }

.seat-tl { grid-column: 1; grid-row: 1; align-self: start; }
.seat-tr { grid-column: 3; grid-row: 1; align-self: start; }
.seat-bl { grid-column: 1; grid-row: 2; align-self: start; }
.seat-br { grid-column: 3; grid-row: 2; align-self: start; }
/* every quadrant is laid out the same way, top to bottom: name, concealed hand,
   played sets, then discards — so the name always sits above the discards */

/* reserve a row for the concealed hand and for played tiles so the quadrant
   doesn't shift as tiles get melded / flowers drawn / the hand shrinks. The
   played row is a FIXED one-row height that scrolls sideways when a player racks
   up many melds/flowers, so it never grows and shoves the rest of the table. */
/* Name, concealed hand and pond form one column, so the name and hand sit ABOVE
   the pond and share its centre line. The column is exactly the pond's width, so
   the hand's face-down tiles compress to match rather than widening the quadrant. */
.seat-pond { display: flex; flex-direction: column; align-items: center; gap: 4px; flex: 0 0 auto;
  width: calc(var(--pond-cols) * 40px * var(--ts-base) * var(--ts-disc)
            + (var(--pond-cols) - 1) * 2px + 6px); }
/* A concealed hand is as wide as its tiles, not as wide as the pond: capping it
   at the pond meant raising Others' hands past ~30% just squeezed the tiles
   instead of growing them. It is anchored to the quadrant's INNER edge and grows
   OUTWARD, over the empty band sitting above the sets column, so the four hands
   line up towards the middle of the table. --hand-max (set by relayout) stops it
   at the quadrant's edge. */
.seat-hand { display: flex; gap: 1px; flex-wrap: nowrap; align-items: center;
  height: calc(52px * var(--ts-base) * var(--ts-ohand) + 4px);
  width: max-content; max-width: var(--hand-max, 100%); overflow: hidden; }
.seat-tl .seat-hand, .seat-bl .seat-hand { align-self: flex-end; justify-content: flex-end; }
.seat-tr .seat-hand, #my-zone .seat-hand { align-self: flex-start; justify-content: flex-start; }
/* Played sets sit on the OUTER edge beside the pond — two sets to a row — so the
   discard tray keeps its full width and the quadrant stays short. */
/* wraps rather than squeezing: if the sets and the pond can't share a line (big
   tile sizes, narrow window) the sets drop to their own line and the pond keeps
   its full size */
/* bottom-aligned so the sets column lines up with the POND, not with the name
   sitting above it — both have fixed heights, so their bottoms sit flush */
.seat-board { display: flex; flex-wrap: wrap; gap: 4px; align-items: flex-end; justify-content: center; max-width: 100%; }
/* The pond group is first in the DOM so a wrap puts it on the TOP line and drops
   the sets beneath it; these reversals keep the sets on the quadrant's outer edge
   while they're side by side. */
.seat-tl .seat-board, .seat-bl .seat-board { flex-direction: row-reverse; }  /* sets to the left  */
.seat-tr .seat-board, #my-zone .seat-board { flex-direction: row; }          /* sets to the right */

/* Exactly two melds fit per row, and eight flowers fill a row of their own —
   both come to 8 tiles wide plus 8px of gaps (2 melds: 6 x 1px inside + 2px
   between; flowers: 7 x 1px). Only the TILES take the size knob, so the gaps are
   added outside the multiplication rather than being scaled along with them; the
   height is likewise exactly --played-rows tile-heights plus the gaps between
   them. That leaves no dead strip at the box's edges at any tile size, while
   still reserving the ruleset's maximum so the quadrant never resizes. */
.seat-played, #my-played {
  display: flex; flex-wrap: wrap; gap: 2px; align-content: flex-start; flex: 0 0 auto;
  width: calc(var(--played-cols) * 40px * var(--ts-base) * var(--ts-played)
            + var(--played-gaps) * 1px);
  /* exactly the rows it reserves — or, beside the pond, at least as tall as the
     pond is, so that bottom-pinning the box puts its TOP edge on the pond's top
     edge and the sets start level with the first discard (see --sets-floor) */
  height: max(calc(var(--played-rows) * 52px * var(--ts-base) * var(--ts-played)
                 + (var(--played-rows) - 1) * 2px), var(--sets-floor, 0px));
  overflow: hidden;
}
/* sets hug the pond and grow outward */
.seat-tl .seat-played, .seat-bl .seat-played { justify-content: flex-end; }
.seat-tr .seat-played, #my-played { justify-content: flex-start; }
/* Flowers & seasons share one row, on the SAME 1px pitch the tiles inside a meld
   use. Anything looser makes the row wider than the meld rows beneath it, and a
   row of 8 tiles that outruns every row below reads as bigger tiles even when
   they measure identically (8 x 40px + 7 x 1px = 327px, against 328px of melds). */
.played-row { display: flex; gap: 1px; flex-wrap: nowrap; }

/* No width breakpoint here on purpose: .seat-board wraps on its own, so the sets
   stay beside the pond whenever they actually fit (which depends on the tile-size
   sliders as much as the window) and only drop below it when they don't. */
/* discards live in a dedicated, recessed 3×8 tray so they read as a pile,
   clearly separate from the played sets above them */
.seat-discards, #my-discards {
  /* discards scale with the Discards slider; the pond keeps its 8 columns and,
     if that gets wider than the seat, scrolls horizontally inside the tray
     instead of overflowing — so the table layout never breaks */
  --tsd: var(--ts-disc);
  display: grid;
  /* 24 slots either way — more than a player can discard in a hand (the wall
     only allows ~19 draws each). 6 wide x 4 deep keeps the pond narrow enough to
     sit beside the sets; once the two have to stack, --pond-cols goes to 8 or 12
     and the tray gets correspondingly shallower. */
  grid-template-columns: repeat(var(--pond-cols), calc(40px * var(--ts-base) * var(--tsd)));
  grid-auto-rows: calc(52px * var(--ts-base) * var(--tsd));
  align-content: start; gap: 2px; padding: 3px;
  /* The pond never shrinks to fit: if it can't sit beside the played sets they
     wrap to their own line, and the pond reshapes to a wider, shallower tray. So
     it is never squeezed or clipped at any tile size. */
  flex: 0 0 auto; overflow: auto;
  height: calc(var(--pond-rows) * 52px * var(--ts-base) * var(--tsd)
             + (var(--pond-rows) - 1) * 2px + 6px);
  border-radius: 5px; background: rgba(0,0,0,.13);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.05);
}

/* ---- center column: the just-discarded tile rests here until it's claimed or
   the next player draws; the column also spaces the left & right players apart */
#center {
  grid-column: 2; grid-row: 1 / -1; overflow: visible;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  text-align: center;
}
/* The centre stacks fixed-height slots so the round/wall/honba readouts, the
   turn banner and the resting discard never shove one another around as they
   appear, change width, or clear. */
/* whose-turn banner: a quiet pill for others, a bold gold pulse for "Your turn".
   Keeps its height even when empty so nothing below it jumps. */
#turn-label { height: 24px; max-width: 100%; display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700; padding: 0 12px; border-radius: 999px; box-sizing: border-box; }
#turn-label .tl-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#turn-label.mine { color: #14261a; background: #e8d5a3; box-shadow: 0 0 12px 2px rgba(232,213,163,.5); animation: turnPulse 1.6s ease-in-out infinite; }
@keyframes turnPulse {
  0%, 100% { box-shadow: 0 0 9px 1px rgba(232,213,163,.4); }
  50% { box-shadow: 0 0 18px 5px rgba(232,213,163,.75); }
}
@media (prefers-reduced-motion: reduce) { #turn-label.mine { animation: none; } }

/* round / wall / honba stacked, each on its own centred line, so one changing
   width (or honba appearing) never nudges the others */
#center-info { display: flex; flex-direction: column; align-items: center; gap: 2px; font-size: 12px; color: #8ab89a; }
#round-wind-display { color: #e8d5a3; font-weight: 700; white-space: nowrap; min-height: 1.15em; }
#wall-display { color: #cfe3d5; font-weight: 600; white-space: nowrap; min-height: 1.15em; }
#sticks-display { white-space: nowrap; min-height: 1.15em; } /* reserved even when empty */
#dora-display { display: flex; flex-direction: column; gap: 3px; align-items: center; }
.dora-tiles { display: flex; gap: 3px; flex-wrap: wrap; justify-content: center; max-width: 100%; }
#dora-display:empty { display: none; }

/* resting discard: reserve the tile's height so the slot never collapses */
#last-discard { display: flex; align-items: center; justify-content: center; gap: 6px;
  min-height: calc(52px * var(--ts-base) * var(--ts-disc) + 12px); }
#last-discard:not(:empty) { border-radius: 8px; padding: 5px; }
/* the box only appears once the tile has landed — otherwise an empty shadowed
   panel sits at the centre for the whole flight */
#last-discard:not(:empty):not(.flying) { background: rgba(10,26,18,.55); box-shadow: 0 2px 10px rgba(0,0,0,.4);
  transition: background .18s ease, box-shadow .18s ease; }
/* drop the box instantly when the flight starts (no fade-out), fade it back in
   when the tile lands — the transition above only applies on the way in */
#last-discard.flying { transition: none; }

/* ---- my zone is the bottom-left quadrant (a .seat); just tune spacing */
#my-zone { gap: 3px; }
#my-zone .seat-info { justify-content: center; }
/* played/hand tiles always keep their size (never squish) */
.seat-played > *, #my-played > *, .played-row > * { flex-shrink: 0; }
/* hide scrollbars on all the fixed-size seat trays so a scrollbar never eats
   layout width/height — they still scroll by wheel/drag */
.seat-hand, .seat-played, #my-played, .seat-discards, #my-discards { scrollbar-width: none; }
.seat-hand::-webkit-scrollbar, .seat-played::-webkit-scrollbar, #my-played::-webkit-scrollbar,
.seat-discards::-webkit-scrollbar, #my-discards::-webkit-scrollbar { display: none; }

/* ================================================================ tiles */
/* Four size knobs, set live by the Size sliders: --ts-hand (my hand),
   --ts-ohand (opponents' concealed hands), --ts-played (flowers & melds) and
   --ts-disc (every discard). --ts-base is a device factor (smaller on phones).
   Each tile derives a unit --u from base × its knob; every dimension — the tile
   and the face drawn inside it (in em, so it tracks the tile) — is a multiple.
   ONE canonical tile (40 x 52 units) is shared by every category, so a given
   percentage means the SAME physical tile wherever it is spent: 90% played and
   95% discards are directly comparable, and flowers match the melds beside them. */
#screen-game { --ts-base: 1; --ts-hand: 1; --ts-ohand: 1; --ts-played: 1; --ts-disc: 1; }
/* Seat shape, rewritten by relayout() in app.js whenever the WINDOW changes (see
   there). These are the side-by-side defaults: a deep 6-wide pond and a sets
   column two melds across. Stacked, they widen to 8 or 12 pond columns and three
   or four melds a row so the quadrant gets shorter instead of taller. */
#screen-game { --pond-cols: 6; --pond-rows: 4; --played-cols: 8; --played-gaps: 8; --played-rows: 4; --sets-floor: 0px; }

.tile {
  --u: calc(1px * var(--ts-base) * var(--ts-disc));
  display: inline-flex; align-items: center; justify-content: center; overflow: hidden;
  width: calc(40 * var(--u)); height: calc(52 * var(--u)); font-size: calc(52 * var(--u));
  border-radius: calc(4 * var(--u));
  background: linear-gradient(165deg, #fdf9ee, #ece1c6);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.75), 0 1px 2px rgba(0,0,0,.3);
  color: #1a1a1a; flex-shrink: 0; position: relative; line-height: 1; text-align: center; cursor: default;
}

/* hand-drawn tile face (sized in em, relative to the tile) — no frame, so
   nothing sits behind the corner index */
.face { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
/* man: small blue numeral above a larger red 萬 */
.face.man { flex-direction: column; gap: .01em; color: #17439b; }
.face.man .mnum { font-size: .36em; font-weight: 800; line-height: 1; }
.face.man .msuit { font-size: .46em; font-weight: 800; line-height: 1; color: #b0202a; }
.face .honor { font-size: .66em; font-weight: 800; line-height: 1; }
.face.wind .honor { color: #27324a; }
.face.dragon-R .honor { color: #c62828; }
.face.dragon-G .honor { color: #1a7d31; }
.face.dragon-W .honor { color: #2560c8; }
.face.flower .honor { color: #b5651d; font-size: .5em; }
/* pips / bamboo placed absolutely inside a padded content box so each number
   gets its traditional (slanted / clustered / fanned) layout with gaps — the
   per-tile coordinates & sizes live in app.js (PIN_POS / SOU_POS) */
.pip-abs { position: absolute; inset: 6% 6%; }
/* pin: two-tone concentric-ring dots (blue outer, cream, red centre) */
.pip { position: absolute; transform: translate(-50%, -50%); border-radius: 50%;
       background: radial-gradient(circle, #2560c8 0 34%, #fdf9ee 36% 52%, #b0202a 54%); }
.face.pin.one .pip { background: radial-gradient(circle, #b0202a 0 22%, #fdf9ee 24% 40%, #2560c8 42% 58%, #fdf9ee 60% 72%, #b0202a 74%); }
/* sou: green bamboo stalks with a little leaf */
.bamboo { position: absolute; border-radius: .3em .3em .07em .07em; background: linear-gradient(#5ad089, #12742f); }
.bamboo .leaf { position: absolute; left: 50%; top: -.02em; background: #2a9d52; border-radius: 50% 50% 50% 0; }
.face.sou.one .bamboo { border-radius: .3em .3em .07em .07em; }

/* small English index, top-right — sized in em (relative to the tile's own
   font-size, which equals its height) so the number keeps the SAME proportion
   on every tile, hand or discard, big or small */
.tile-idx {
  position: absolute; top: .05em; right: .08em; z-index: 1;
  font-size: .23em; font-weight: 800; line-height: 1;
  color: rgba(20,20,20,.75);
  text-shadow: 0 0 .06em #fdf9ee, 0 0 .04em #fdf9ee;
}
.tile.man .tile-idx { color: #17439b; }
.tile.pin .tile-idx { color: #b0202a; }
.tile.sou .tile-idx { color: #1a7d31; }
.tile.wind .tile-idx { color: #2b2b2b; }
.tile.flower .tile-idx { color: #b5651d; }
.tile-idx.left { left: .08em; right: auto; }

.tile.facedown { background: linear-gradient(160deg, #2f7a4a, #1d5230); color: transparent; box-shadow: inset 0 1px 0 rgba(255,255,255,.14), 0 1px 2px rgba(0,0,0,.3); }
.tile.facedown::after { content: ''; position: absolute; inset: calc(3 * var(--u)); border: 1px solid rgba(255,255,255,.14); border-radius: 2px; }
.tile.highlight { outline: 2px solid #4db87a; }
.tile.last-draw { outline: 2px solid #e8d5a3; }
.tile.riichi-mark { box-shadow: inset 0 calc(-3 * var(--u)) 0 #cc2222, 0 1px 2px rgba(0,0,0,.3); }
/* every copy of the tile under the pointer, wherever it is face up */
.tile.match { outline: 2px solid #f0a63c; z-index: 2;
  box-shadow: 0 0 0 3px rgba(240,166,60,.3), 0 1px 2px rgba(0,0,0,.3); }

/* hand tiles — sized by --ts-hand */
#hand {
  display: flex; gap: 3px; justify-content: center; align-items: flex-end;
  margin-top: 16px; padding: 6px 12px 8px; min-height: 60px; flex-wrap: wrap; touch-action: pan-y;
}
#hand .tile {
  --u: calc(1px * var(--ts-base) * var(--ts-hand));
  cursor: pointer; transition: transform .12s ease, box-shadow .1s;
}
#hand .tile:hover { transform: translateY(-4px); box-shadow: 0 4px 12px rgba(0,0,0,.3); }
#hand .tile.selected { transform: translateY(-8px); box-shadow: 0 0 0 2px #e8d5a3, 0 6px 16px rgba(0,0,0,.4); }
#hand .tile.dragging { opacity: .95; box-shadow: 0 8px 18px rgba(0,0,0,.5); transition: none; z-index: 5; }

/* played (flowers + melds) — sized by --ts-played */
.seat-played .tile, #my-played .tile, .meld .tile { --u: calc(1px * var(--ts-base) * var(--ts-played)); }
.meld { display: flex; gap: 1px; }
/* opponents' concealed (face-down) hands — sized by --ts-ohand */
/* face-down tiles carry no information, so when a long concealed hand is wider
   than the pond column they squeeze together rather than being cropped off */
.seat-hand .tile { --u: calc(1px * var(--ts-base) * var(--ts-ohand)); flex-shrink: 0; }

/* discards — sized by --ts-disc; over-wide ponds scroll (see .seat-discards) */
.seat-discards .tile, #my-discards .tile { --u: calc(1px * var(--ts-base) * var(--ts-disc)); }
#last-discard .tile { --u: calc(1px * var(--ts-base) * var(--ts-disc)); }

/* ================================================================ action bar */
#action-bar {
  display: flex; gap: 6px; justify-content: center; align-items: center;
  padding: 6px 12px; min-height: 48px; flex-wrap: wrap; flex-shrink: 0;
}
#action-bar .btn { padding: 8px 16px; font-size: 14px; }
.btn.ron { background: #cc2222; color: #fff; }
.btn.ron:hover { background: #e02a2a; }
.btn.tsumo { background: #cc8822; color: #fff; }
.btn.tsumo:hover { background: #e0992a; }
.btn.riichi-btn { background: #5a2a8c; color: #fff; }
.btn.riichi-btn:hover { background: #6a3a9c; }
.btn.chi-btn { background: #2a5a8c; color: #fff; }
.btn.chi-btn:hover { background: #3a6a9c; }
.btn.pon-btn { background: #2a8c5a; color: #fff; }
.btn.pon-btn:hover { background: #3a9c6a; }
.btn.kan-btn { background: #8c5a2a; color: #fff; }
.btn.kan-btn:hover { background: #9c6a3a; }

/* Hong Kong's banked faan, sitting under the action bar — the last thing under
   your hand, where you're already looking while picking a discard. It keeps its
   height whether or not there is anything to say, so the action bar above it
   never moves as sets are melded. */
#faan-bar {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  min-height: 40px; font-size: 12px; padding: 0 12px; text-align: center;
}
.faan-row { display: flex; align-items: center; justify-content: center; gap: 8px; max-width: 100%; }
#faan-routes { color: #8ab89a; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#faan-routes b { color: #cfe3d5; font-weight: 600; }
#faan-routes .ready { color: #7fd4a0; font-weight: 700; }
#faan-bar.hidden { display: none !important; }
#faan-locked { color: #e8d5a3; font-weight: 700; white-space: nowrap; }
#faan-locked.short { color: #d8a05a; }
#faan-parts { color: #7fa38c; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* a claim you don't want keeps coming back every time that tile is thrown, so
   each call carries a chip that retires it for the rest of the hand */
/* a claim button names its tiles by showing them — no suit notation to learn */
.btn-tiles { display: inline-flex; gap: 1px; margin-left: 6px; vertical-align: middle; }
.btn-tiles .tile { --u: .5px; }

.claim-group { display: inline-flex; align-items: stretch; }
.claim-group .btn { border-top-right-radius: 0; border-bottom-right-radius: 0; }
.claim-mute {
  border: none; cursor: pointer; font-size: 11px; line-height: 1; padding: 0 7px;
  background: rgba(0,0,0,.28); color: rgba(255,255,255,.55);
  border-radius: 0 8px 8px 0; border-left: 1px solid rgba(0,0,0,.3);
}
.claim-mute:hover { background: rgba(0,0,0,.45); color: #fff; }
.claim-note { font-size: 12px; color: #5a8a6a; align-self: center; display: inline-flex; align-items: center; gap: 6px; }
.claim-mute.undo { border-radius: 6px; border-left: none; background: #1a5a38; color: #c8e0d0; padding: 2px 8px; }

/* the written-out glossary at the foot of Hands & scoring */
.gloss-head { color: #8ab89a; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; margin: 12px 0 4px; }
.gloss-row { display: grid; grid-template-columns: minmax(140px, 200px) 1fr; gap: 12px; padding: 5px 6px; border-radius: 6px; align-items: start; }
.gloss-row:nth-child(odd) { background: rgba(255,255,255,.04); }
.gloss-term { color: #e8d5a3; font-weight: 700; font-size: 12px; }
.gloss-def { color: #b9d3c3; font-size: 12px; line-height: 1.55; }
.gloss-tiles { display: flex; gap: 2px; margin-top: 6px; flex-wrap: wrap; }
.gloss-tiles .tile, .gloss-def .tile { --u: .55px; }
@media (max-width: 620px) { .gloss-row { grid-template-columns: 1fr; gap: 2px; } }

/* a term of art that explains itself: dotted underline, note on hover or focus */
.term { border-bottom: 1px dotted currentColor; cursor: help; outline: none; }
.term:hover, .term:focus-visible { color: #e8d5a3; }
.tip {
  /* above .modal (100) and the flash banner (150), below toasts (200) — the
     terms it explains live INSIDE those panels, so anything lower renders
     behind the thing you are reading */
  /* the note takes the pointer so its own terms can be reached; closing runs on
     a short delay so crossing the gap from word to note doesn't slam it shut */
  position: fixed; z-index: 170; max-width: 320px; pointer-events: auto;
  background: #0b2a1b; border: 1px solid #2a6b45; border-radius: 10px;
  padding: 9px 11px; box-shadow: 0 10px 28px rgba(0,0,0,.6);
  font-size: 12px; line-height: 1.5; text-align: left;
}
.tip-title { color: #e8d5a3; font-weight: 700; margin-bottom: 3px; }
.tip-body { color: #b9d3c3; }
.tip-tiles { display: flex; gap: 2px; margin-top: 8px; flex-wrap: wrap; }
/* one size for every example tile in a note, flat strip or arrow pair alike —
   these sit outside #screen-game, so they get no --ts-* to scale from */
.tip .tile { --u: .62px; }
/* "this tile means that one" — a relation needs an arrow, not a row of tiles */
.ex-pairs { margin-top: 8px; }
.ex-caption { font-size: 10px; color: #7fa38c; letter-spacing: .04em; margin-bottom: 5px; }
.ex-pair-row { display: flex; gap: 12px; flex-wrap: wrap; }
.ex-pair { display: flex; flex-direction: column; align-items: center; gap: 3px; }
.ex-pair-tiles { display: flex; align-items: center; gap: 4px; }
.ex-arrow { color: #7fa38c; font-size: 12px; }
.ex-note { font-size: 10px; color: #6f9a80; white-space: nowrap; }
/* a whole cycle, so one example never has to stand in for the order */
.ex-chain { display: flex; align-items: center; gap: 8px; margin-top: 7px; flex-wrap: wrap; }
.ex-chain-label { font-size: 10px; color: #6f9a80; min-width: 46px; }
.ex-chain-tiles { display: flex; align-items: center; gap: 3px; }
/* the closing repeat is the same tile again, not another one */
.ex-wrap { opacity: .45; }
.tip-tiles .tile { --u: .62px; }

/* the full list of ways to a Hong Kong win, in place under the hand — the page
   grows and scrolls rather than a panel covering the board you are reading */
#ways-panel { width: 100%; max-width: 780px; margin: 6px auto 0; padding-bottom: 28px; text-align: left; }
#ways-intro { font-size: 11px; color: #6f9a80; line-height: 1.5; margin-bottom: 6px; }
#ways-body { display: flex; flex-direction: column; gap: 2px; }
.ways-row {
  display: grid; grid-template-columns: 64px minmax(150px, 1fr) minmax(160px, 1.2fr);
  gap: 10px; align-items: baseline; font-size: 12px;
  padding: 5px 6px; border-radius: 6px; background: rgba(255,255,255,.03);
}
.ways-row:nth-child(odd) { background: rgba(255,255,255,.06); }
.ways-faan { color: #e8d5a3; font-weight: 700; font-variant-numeric: tabular-nums; }
.ways-name { color: #cfe3d5; }
.ways-need { color: #7fa38c; }
@media (max-width: 620px) { .ways-row { grid-template-columns: 56px 1fr; } .ways-need { grid-column: 1 / -1; } }

/* ================================================================ feed (history log) */
/* a floating popup anchored above the 📜 button, styled like the settings drawer;
   hidden by default — toggle it with 📜 (common/feedtoggle.js) */
#feed {
  position: fixed; left: 12px; bottom: 58px; z-index: 90;
  width: 300px; max-width: calc(100vw - 24px); max-height: 42vh; overflow-y: auto;
  padding: 10px 12px; font-size: 12px; line-height: 1.45; color: #b0c8a8;
  background: #0d3320; border: 1px solid #2a6b45; border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0,0,0,.5);
}
#feed::before {
  content: 'Game log'; display: block; margin-bottom: 6px; padding-bottom: 5px;
  border-bottom: 1px solid #1a4a32; color: #e8d5a3; font-weight: 700; font-size: 11px;
  text-transform: uppercase; letter-spacing: .05em;
}
#feed div { padding: 1px 0; }

/* ================================================================ modals */
.modal { position: fixed; inset: 0; z-index: 100; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,.65); padding: 16px; overflow-y: auto; }
.panel { background: #122e20; border: 1px solid #2a6b45; border-radius: 12px; padding: 24px; max-width: 420px; width: 100%; }
.panel.wide { max-width: 560px; }
.panel h2 { color: #e8d5a3; margin-bottom: 12px; font-size: 18px; }
.rules { padding-left: 20px; font-size: 13px; line-height: 1.6; color: #b0c8a8; }
.rules li { margin-bottom: 8px; }
.rules b { color: #e8d5a3; }
.btn-row { display: flex; gap: 8px; margin-top: 12px; }
/* special-hands cheatsheet */
#cheat-body { max-height: 60vh; overflow-y: auto; margin-bottom: 14px; }
.cheat-sec { margin-bottom: 18px; }
.cheat-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.cheat-name { color: #e8d5a3; font-weight: 700; font-size: 15px; }
.cheat-tag { font-size: 10px; text-transform: uppercase; letter-spacing: .05em; font-weight: 700;
  color: #14261a; background: #e8d5a3; border-radius: 999px; padding: 2px 8px; }
.cheat-note { font-size: 12px; color: #8ab89a; line-height: 1.45; margin-bottom: 8px; }
.cheat-row { display: grid; grid-template-columns: minmax(108px, auto) minmax(64px, auto) 1fr; gap: 10px;
  align-items: baseline; padding: 4px 0; border-top: 1px solid rgba(255,255,255,.07); font-size: 12.5px; }
.cheat-hand { color: #cfe3d5; font-weight: 600; }
.cheat-val { color: #f0c674; white-space: nowrap; font-variant-numeric: tabular-nums; }
.cheat-req { color: #9fbf9f; line-height: 1.4; }
@media (max-width: 600px) {
  .cheat-row { grid-template-columns: 1fr auto; gap: 4px 10px; }
  .cheat-req { grid-column: 1 / -1; }
}

/* hand-end breakdown: a label:value ledger of how the points were reached */
#he-detail { font-size: 13px; line-height: 1.45; color: #b0c8a8; margin-bottom: 14px; }
.he-row { display: flex; justify-content: space-between; gap: 12px; padding: 2px 0; }
.he-row .he-label { color: #b0c8a8; }
.he-row .he-val { color: #dfe6f2; font-variant-numeric: tabular-nums; white-space: nowrap; }
.he-yaku .he-label { color: #9fbf9f; }
.he-row.he-total { border-top: 1px solid #2a6b45; margin-top: 4px; padding-top: 5px; font-weight: 600; }
.he-row.he-total .he-label, .he-row.he-total .he-val { color: #e8d5a3; }
.he-pay { border-top: 1px dashed #2a6b45; margin-top: 8px; padding-top: 6px; }
.he-pay .he-val { color: #f0c674; }

#he-scores { font-size: 13px; color: #8ab89a; border-top: 1px solid #2a6b45; padding-top: 8px; }
.he-score-row { display: grid; grid-template-columns: 1fr auto auto 3.5em; gap: 12px; align-items: baseline; padding: 2px 0; }
.he-ready { text-align: right; color: #5a8a6a; }
.he-ready.is-ready { color: #6fcf7f; font-weight: 700; }
.he-ready.not-ready { color: #d8a05a; }
.he-score-row.he-score-head { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: #5a8a6a; padding-bottom: 4px; }
.he-pname { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #cfe3d5; }
.he-score-head .he-pname { color: #5a8a6a; }
.he-delta { text-align: right; min-width: 64px; font-variant-numeric: tabular-nums; color: #8ab89a; }
.he-delta.up { color: #6fcf7f; }
.he-delta.down { color: #e07a6a; }
.he-ptotal { text-align: right; min-width: 60px; font-variant-numeric: tabular-nums; color: #cfe3d5; }
#he-wait, #go-wait { font-size: 13px; color: #5a8a6a; margin-top: 8px; }
#go-rank { list-style: none; counter-reset: rank; }
#go-rank li { counter-increment: rank; padding: 6px 0; border-bottom: 1px solid #1a4a32; display: flex; justify-content: space-between; }
#go-rank li::before { content: counter(rank) ". "; color: #5a8a6a; margin-right: 6px; }
.credit { font-size: 11px; color: #5a8a6a; margin-top: 12px; }

/* the score panel can be tucked away to study the board; this says how to bring
   it back, and never eats a click itself */
#he-peek, #step-peek {
  position: fixed; left: 50%; transform: translateX(-50%); top: 50px; z-index: 75;
  background: rgba(10,26,18,.92); border: 1px solid #2a6b45; color: #e8d5a3;
  padding: 6px 14px; border-radius: 999px; font-size: 12px; font-weight: 600;
  box-shadow: 0 4px 16px rgba(0,0,0,.5); pointer-events: none;
}

/* the bots-are-holding prompt sits low, out of the board's way, and pulses
   gently so it reads as a live state rather than a label */
#step-peek { top: auto; bottom: 14px; border-color: #3a6b8a; color: #cfe3f2; animation: stepPulse 2s ease-in-out infinite; }
@keyframes stepPulse { 0%, 100% { opacity: .7; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { #step-peek { animation: none; } }

/* ================================================================ chat */
/* sits clear of the page scrollbar (macOS overlay scrollbars float over content) */
#chat { position: fixed; bottom: 12px; right: 24px; z-index: 50; display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
#chat-toggle { width: 40px; height: 40px; border-radius: 50%; border: none; background: #2a6b45; font-size: 18px; cursor: pointer; position: relative; }
#chat-unread { position: absolute; top: -4px; right: -4px; background: #cc2222; color: #fff; font-size: 10px; width: 18px; height: 18px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; }
#chat-panel { width: 260px; max-height: 300px; background: #122e20; border: 1px solid #2a6b45; border-radius: 8px; display: flex; flex-direction: column; }
.chat-head { padding: 8px 12px; font-size: 13px; font-weight: 600; color: #e8d5a3; border-bottom: 1px solid #1a4a32; }
#chat-msgs { flex: 1; overflow-y: auto; padding: 8px; max-height: 200px; }
.chat-msg { font-size: 12px; padding: 2px 0; }
.chat-name { font-weight: 600; margin-right: 4px; }
.chat-name.s0 { color: #4db87a; } .chat-name.s1 { color: #e0882a; } .chat-name.s2 { color: #8a8ae0; } .chat-name.s3 { color: #d06aa0; }
.chat-time { color: #5a8a6a; font-size: 10px; margin-right: 4px; }
.chat-text { color: #b0c8a8; }
.chat-msg.mine .chat-text { color: #e8e0d0; }
#chat-form { display: flex; border-top: 1px solid #1a4a32; }
#chat-input { flex: 1; padding: 8px; background: transparent; border: none; color: #e8e0d0; font-size: 13px; outline: none; }
.chat-send { background: none; border: none; color: #4db87a; font-size: 16px; padding: 8px; cursor: pointer; }
#chat-peek { background: #122e20; border: 1px solid #2a6b45; border-radius: 8px; padding: 6px 12px; max-width: 240px; font-size: 12px; cursor: pointer; }

/* ================================================================ toasts & banners */
#toasts { position: fixed; top: 12px; left: 50%; transform: translateX(-50%); z-index: 200; display: flex; flex-direction: column; gap: 4px; align-items: center; pointer-events: none; }
.toast { background: #122e20; border: 1px solid #2a6b45; padding: 8px 16px; border-radius: 8px; font-size: 13px; color: #e8d5a3; animation: fadeIn .2s; transition: opacity .3s; }
.toast.gone { opacity: 0; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(-8px); } }

.flash {
  position: fixed; top: 40%; left: 50%; transform: translate(-50%, -50%);
  z-index: 150; font-size: 22px; font-weight: 800; color: #e8d5a3;
  text-shadow: 0 2px 12px rgba(0,0,0,.5); pointer-events: none;
  animation: flashIn var(--dur, 1.7s) ease-out forwards;
}
@keyframes flashIn { 0% { opacity: 0; transform: translate(-50%,-50%) scale(.8); } 15% { opacity: 1; transform: translate(-50%,-50%) scale(1.05); } 30% { transform: translate(-50%,-50%) scale(1); } 80% { opacity: 1; } 100% { opacity: 0; } }
.flash.big { font-size: 30px; }

/* ================================================================ bubble layer */
#bubble-layer { position: fixed; inset: 0; pointer-events: none; z-index: 60; }
/* newest bubble sits at the bottom (nearest the seat); the stack grows upward,
   is capped (max-height set in JS) and clips its oldest at the top. DOM order is
   chronological & append-only (no re-animation/blink); justify-content:flex-end
   pins the newest to the bottom and pushes the oldest off the top when full */
#bubble-layer .bubble-stack { position: absolute; display: flex; flex-direction: column; justify-content: flex-end; align-items: center; gap: 4px; overflow: hidden; transform: translateX(-50%); }
#bubble-layer .chat-bubble {
  position: relative; background: #f5f0e0; color: #1a1a1a; padding: 4px 10px;
  border-radius: 10px; font-size: 12px; max-width: 200px; white-space: pre-wrap;
  pointer-events: none; animation: bubbleIn .2s ease-out;
}
#bubble-layer .chat-bubble.say { background: rgba(26,74,50,.96); color: #e8d5a3; }
#bubble-layer .chat-bubble::after { display: none; }
#bubble-layer .chat-bubble:last-child::after {
  display: block; content: ''; position: absolute;
  left: 50%; bottom: -12px; transform: translateX(-50%);
  border: 6px solid transparent; border-top-color: #f5f0e0;
}
#bubble-layer .chat-bubble.say:last-child::after { border-top-color: rgba(26,74,50,.96); }
@keyframes bubbleIn { from { opacity: 0; transform: translateY(6px); } }

/* ================================================================ claim calls */
#fx-layer { position: fixed; inset: 0; pointer-events: none; z-index: 65; }
/* a big, showy PON/CHI/KAN/RON over the claiming player's quadrant. It lives in
   a fixed overlay, so its size costs the table nothing — it scales with the
   window and is capped so it still sits inside one quadrant at the peak of the
   pop (a 3-letter call at 104px is ~230px wide against a ~540px quadrant). */
.call-fx {
  position: fixed; transform: translate(-50%, -50%); white-space: nowrap;
  font-size: clamp(56px, 6vw, 104px); font-weight: 900; letter-spacing: .08em; color: #ffe08a;
  /* a tight dark rim first so the word stays legible over a pile of tiles, then
     the coloured bloom */
  text-shadow: 0 2px 0 rgba(0,0,0,.6), 0 4px 16px rgba(0,0,0,.8), 0 0 34px rgba(255,200,90,.65);
  animation: callFx 1.5s cubic-bezier(.2,1.3,.4,1) forwards;
}
.call-pon { color: #ffd166; text-shadow: 0 2px 0 rgba(0,0,0,.6), 0 4px 16px rgba(0,0,0,.8), 0 0 36px rgba(255,209,102,.7); }
.call-chi { color: #7fd4ff; text-shadow: 0 2px 0 rgba(0,0,0,.6), 0 4px 16px rgba(0,0,0,.8), 0 0 36px rgba(127,212,255,.7); }
.call-kan { color: #ff9d6e; text-shadow: 0 2px 0 rgba(0,0,0,.6), 0 4px 16px rgba(0,0,0,.8), 0 0 36px rgba(255,157,110,.7); }
/* Ron ends the hand — give it the loudest bloom of the four */
.call-ron { color: #ff7a7a; text-shadow: 0 2px 0 rgba(0,0,0,.6), 0 4px 16px rgba(0,0,0,.85), 0 0 44px rgba(255,122,122,.85); }
@keyframes callFx {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(.3) rotate(-8deg); }
  18%  { opacity: 1; transform: translate(-50%, -50%) scale(1.3) rotate(3deg); }
  32%  { transform: translate(-50%, -50%) scale(1) rotate(0); }
  68%  { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  /* then float upward as it fades out, so the call reads clearly as it leaves */
  100% { opacity: 0; transform: translate(-50%, calc(-50% - 80px)) scale(1.05); }
}

/* ================================================================ responsive */
@media (max-width: 600px) {
  /* device factor shrinks every tile so the table fits a phone; the Size
     sliders still scale on top of this. */
  #screen-game { --ts-base: 0.66; }
  #table { gap: 5px 6px; padding: var(--bubble-reserve) 4px 0; }
  .seat-name { max-width: 78px; font-size: 11px; }
}
