/* Dutch Blitz — phone client.
   One disc carries five things: fill (face colour), ring (whose deck), rank,
   ♂/♀ (derived from the fill) and a count when there is more than one card
   under it. The tile around the disc is the tap target; the circle is the card.
   No third-party fonts, no images, no analytics. */

:root {
  /* THE FLAG NAVY SHELL (Rob, 2026-11-03). #003776 is the official Idaho flag blue and it is now the
     app's dark: every surface below is one step on that ramp, so the shell, the wells and the panels
     read as one material. Every text pairing is measured against these hexes, not chosen by eye. */
  --navy: #003776;
  --navy-0: #001A3D;   /* the deepest well: the chips that sit on a bright face */
  --navy-1: #002B5C;   /* recessed: empty slots, bars, the flip's cards */
  --navy-2: #003776;   /* the shell itself */
  --navy-3: #083C7A;   /* a panel */
  --navy-4: #0B4689;   /* a panel on a panel, and every raised control */
  --navy-5: #13549C;   /* pressed, or chosen */
  --navy-hi: #2E6DB8;  /* the hairline that separates a control from its panel */
  --bg: var(--navy-2);
  --panel: var(--navy-3);
  --panel-2: var(--navy-4);
  --line: #1B5AA8;
  --fg: #F2F7FF;
  --muted: #A8C6E8;
  --ok: #34C759;
  --warn: #FFB020;

  /* the rank plate — one black ring inside every disc, sized off the disc itself */
  --plate-ink: #12151C;
  --disc-hair: 2px;

  /* face colours — always four, and now the official flag four */
  --fc-red: #EF1439;
  --fc-blue: #86E2FF;
  --fc-yellow: #FEE835;
  --fc-green: #36E218;

  /* player rings — eight */
  --ring-1: #FF3B30;
  --ring-2: #007AFF;
  --ring-3: #34C759;
  --ring-4: #FFCC00;
  --ring-5: #AF52DE;
  --ring-6: #FF9500;
  --ring-7: #00C7BE;
  --ring-8: #8E8E93;

  --disc: 60px;
  --tile: 74px;
  /* The Blitz pile is the objective: 1.2× every other disc, framed in the app's own neutral. Both
     follow --disc through the phone breakpoints, so the ratio holds at every width. */
  --blitz-disc: calc(var(--disc) * 1.2);
  --blitz-frame: #eef1f6;
  --board-gap: 6px;
  --board-gap-x: 10px;
  --radius: 14px;
}

* { box-sizing: border-box; }

[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  background: var(--bg);
  color: var(--fg);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  overscroll-behavior: none;
  -webkit-tap-highlight-color: transparent;
}

body { user-select: none; -webkit-user-select: none; }

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

#app {
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* ------------------------------------------------------------------ topbar */
.topbar {
  flex: 0 0 auto;
  height: 42px;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 10px;
  border-bottom: 1px solid var(--line);
  background: var(--panel);
}
/* The brand is decoration: on a 390 px phone with a test chip, a watcher chip and a connection dot,
   it is the thing that gives way first (never a control). */
.brand { display: inline-flex; align-items: center; gap: 6px; font-weight: 800; letter-spacing: .01em; font-size: 15px; flex: 0 1 auto; min-width: 0; overflow: hidden; }
/* The approved mark, immediately before the wordmark (Rob, 2026-11-03). 26 px in a 42 px bar: it
   never shrinks, and the wordmark behind it takes every shortage as an ellipsis. */
.brandmark { width: 26px; height: 26px; flex: 0 0 auto; display: block; }
.brandtext { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* EXT v5 — a robot opponent is seated: say "test table" where every screen can see it. */
.testchip {
  font-size: 9px;
  font-weight: 800;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: #ffd7a8;
  border: 1px solid #6b4a1f;
  background: #2a2013;
  border-radius: 999px;
  padding: 2px 7px;
}
.testchip:not([hidden]) { display: inline-block; }
.conn {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  color: var(--muted);
}
.conn .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); }
.conn.offline .dot { background: #FF453A; }
.conn.offline { color: #FF8A80; }
.ghost {
  background: var(--panel-2);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 5px 9px;
  font: inherit;
  font-size: 11px;
}
.ghost[aria-pressed="true"] { background: var(--navy-5); border-color: #8FB0FF; }

/* ------------------------------------------------------------------ banner */
.banner {
  flex: 0 0 auto;
  padding: 7px 12px;
  font-size: 12px;
  background: #43201d;
  color: #ffd7d2;
  border-bottom: 1px solid #6b2f29;
}
.banner strong { margin-right: 4px; }
.banner.forfeit { background: #3a1220; color: #ffd0e0; border-bottom-color: #6b1f3c; }

/* ----------------------------------------------------------------- screens */
.screen { flex: 1 1 auto; min-height: 0; overflow: hidden; }
.screen.game {
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  gap: 3px;
  padding: 5px 10px 3px;
}

/* ------------------------------------------------------------------- lobby */
.screen.lobby { padding: 14px 14px 8px; overflow-y: auto; }
.lobby-title { font-size: 22px; margin: 2px 0 2px; }
.lobby-sub { color: var(--muted); font-size: 13px; margin: 0 0 12px; }
/* t_b9239a82 — the lesson is for the person who has not played yet, so it sits on the first screen
   with the entry choices and is never behind a seat. Ghost: quiet, thumb-sized, never a third
   primary action beside "Start a new table". */
.lesson-row { margin: 0 0 12px; }
.lesson-row .howto { padding: 9px 12px; font-size: 12.5px; }
.sectionhead {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--muted);
  margin: 12px 0 6px;
}
.nameform { margin: 0 0 10px; }
.nameform label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 6px; }
.namerow { display: flex; gap: 8px; }
/* Rob, 2026-11-03 — one primary action under the name: start a table. Full width, nothing beside it. */
.newtable { display: block; width: 100%; margin: 0 0 6px; }
input {
  flex: 1 1 auto;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 11px 12px;
  color: var(--fg);
  font: inherit;
  font-size: 15px;
  min-width: 0;
}
input:focus { outline: 2px solid #8FB0FF; outline-offset: 1px; }

.roster { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.roster li {
  display: flex;
  align-items: center;
  gap: 9px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 11px;
  padding: 8px 10px;
}
.roster .name { font-size: 14px; font-weight: 600; }
.roster .name small { display: block; font-size: 10px; color: var(--muted); font-weight: 500; }
.roster .rtt {
  margin-left: auto;
  text-align: right;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.roster .rtt b { font-weight: 700; }
.roster .rtt small { display: block; color: var(--muted); font-size: 10px; }
.roster .rtt.unknown { color: var(--muted); font-style: italic; }
/* EXT v5 — a bot seat says BOT, never a latency that implies a phone. */
.roster .rtt.botmark { color: #ffd7a8; }
.roster .rtt.botmark b { letter-spacing: .08em; font-size: 10px; }
.roster li[data-bot="1"] { border-style: dashed; background: var(--navy-1); }
.hint.testhint { color: #cbb894; }
/* A seat that is away inside the §3.4 grace keeps its place; dim it so presence is visible at a
   glance without hiding the ready badge. */
.roster li[data-connected="0"] { opacity: .55; background: transparent; border-style: dashed; }

/* ------------------------------------------------------------- table list (v8)
   RC 1.3 (Rob, 2026-10-31) — before a seat is taken the lobby IS the list of tables.
   One row per room: the code (the thing you say out loud), who is there, and one Join.
   A row that is playing says so in its own button and cannot be joined, so a live game
   is information rather than a refusal. */
.tables { margin: 0 0 14px; }
.tables-count { margin-left: 6px; }
/* t_0d7080e6 — the grid's implicit `auto` column sized itself to the widest row's max-content
   (451 px inside a 362 px column at 390 wide), which pushed Watch and Join past the screen edge
   that `.screen { overflow: hidden }` then clipped. One `minmax(0, 1fr)` track keeps the row at the
   column's width; the title ellipsis below is what gives way, not the buttons. */
.table-list { list-style: none; margin: 0 0 8px; padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; }
.table-row {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 11px;
  padding: 8px 10px;
}
.table-row.mine { border-color: #8FB0FF; }
/* Rob, 2026-11-02 — the row's own line is the whole label; a second code column printed the code
   twice on one row. */
.table-meta { flex: 1 1 auto; min-width: 0; font-size: 13px; }
.table-meta b { display: block; font-size: 12.5px; font-weight: 700; }
.table-meta small { display: block; color: var(--muted); font-size: 11px; }
/* Rob, 2026-11-02 — the creator's name leads the row; the state under it is the invitation.
   t_120d3bed — the label is four separate units (name, code, head count, score target) instead of
   one string, so that when space is short ONLY the name gives way: it carries the ellipsis, the
   code and the target never do. The name is recoverable from the roster; the code you say out
   loud, and the target you play to, are not. */
.table-meta b.table-title { display: flex; align-items: baseline; min-width: 0; }
.table-who { display: inline-flex; align-items: baseline; flex: 0 1 auto; min-width: 0; white-space: nowrap; }
.table-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.table-code, .table-counts, .table-target, .table-dash { flex: 0 0 auto; white-space: nowrap; }
.table-facts { flex: 0 0 auto; white-space: nowrap; }
.table-row.waiting { border-color: #3d7a55; background: #14241b; }
.table-row.waiting .table-state {
  color: #6fe39a;
  font-weight: 700;
  font-size: 10.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.table-join {
  flex: 0 0 auto;
  background: var(--panel-2);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 9px 12px;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
}
.table-join.shut { opacity: .55; font-weight: 600; }
.table-join:disabled { color: var(--muted); }
.tables-empty { margin: 0 0 8px; }
.tables-note { margin-top: 8px; }
.table-line { font-size: 11.5px; }

/* ring identity chip: hue + stroke style + seat digit, never hue alone */
.ringmini {
  position: relative;
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 3px solid var(--ring, #888);
  display: grid;
  place-items: center;
  font-size: 10px;
  font-weight: 800;
  color: var(--fg);
  background: var(--navy-1);
}
.ringmini.ring-dashed { border-style: dashed; }
.ringmini.ring-dotted { border-style: dotted; border-width: 3.5px; }
.ringmini.ring-double { border-width: 1.5px; }
.ringmini.ring-double::after {
  content: "";
  position: absolute;
  inset: 1px;
  border: 1.5px solid var(--ring, #888);
  border-radius: 50%;
}

.spread {
  margin-top: 14px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 11px;
  padding: 10px 12px;
}
.spread-head { display: flex; justify-content: space-between; font-size: 12px; color: var(--muted); }
.spread-head b { color: var(--fg); font-variant-numeric: tabular-nums; }
.spread-bar {
  position: relative;
  height: 6px;
  border-radius: 3px;
  background: var(--navy-1);
  margin: 8px 0 8px;
  overflow: hidden;
}
.spread-bar span { display: block; height: 100%; width: 0; background: var(--ok); border-radius: 3px; transition: width .35s ease; }
.spread-bar span.over { background: var(--warn); }
.spread-bar .thresh { position: absolute; top: -2px; left: 66.6%; width: 2px; height: 10px; background: #7C8DA8; }
.spread-warn { margin: 0; font-size: 12px; line-height: 1.4; color: #ffd98a; background: #3a2f12; border: 1px solid #5e4a17; border-radius: 8px; padding: 7px 9px; }
.spread-ok { margin: 0; font-size: 11px; color: var(--muted); }

/* ------------------------------------------------------------------- settings */
/* Rob, 2026-11-03 — the CREATOR's table setup. Deliberately quieter than READY: a dashed, muted
   panel with small controls, so the primary action still reads as the primary action. A joiner
   never sees this box at all; their one line is .rules-line below. */
.settings {
  background: transparent;
  border: 1px dashed var(--line);
  border-radius: 12px;
  padding: 8px 10px;
  margin: 12px 0 4px;
}
.settings-title {
  margin: 0 0 4px;
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted);
}
.setting { display: flex; align-items: center; gap: 8px; padding: 2px 0; }
.setting .k { font-size: 12.5px; color: #c3ccdd; }
.setting .v {
  margin-left: auto;
  font-size: 15px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.setting .lock { font-size: 10px; color: var(--muted); flex: 1 1 45%; text-align: right; }
.target-choice { display: flex; gap: 6px; margin: 0 0 0 auto; }
.target-btn {
  min-width: 78px;
  height: 34px;
  border-radius: 9px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  font-weight: 700;
}
.target-btn.on, .target-btn[aria-pressed="true"] { color: #fff; background: var(--navy-5); border-color: #8FB0FF; }
.botrow { display: flex; gap: 6px; margin-top: 6px; }
.botrow .botseat { min-height: 36px; padding: 7px 10px; font-size: 12.5px; }
/* The joiner's whole surface: the rules in force, and who set them. No controls, no button. */
.rules-line {
  margin: 12px 0 0;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--panel);
  color: #c3ccdd;
  font-size: 12.5px;
  text-align: center;
}

/* -------------------------------------------------------------- high scores */
/* RC 1.2 — a quiet lobby control and an inline panel. It sits in the lobby's own flow: opening it
   pushes nothing off screen and covers nothing, so READY stays reachable underneath. */
.highscores { margin: 10px 0 0; }
.hs-toggle { width: 100%; padding: 8px 10px; font-size: 12px; }
.hs-toggle[aria-expanded="true"] { background: var(--navy-5); border-color: #8FB0FF; }
.hs-panel {
  margin-top: 8px;
  padding: 10px 12px 12px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  text-align: left;
}
.hs-head { display: flex; align-items: baseline; gap: 8px; }
.hs-head h2 { margin: 0; font-size: 13px; letter-spacing: .06em; text-transform: uppercase; color: #c3ccdd; }
.hs-month { margin-left: auto; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }
.hs-boards { display: flex; gap: 12px; margin-top: 8px; }
.hs-board { flex: 1 1 0; min-width: 0; }
.hs-board h3 { margin: 0 0 4px; font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.hs-list { list-style: none; margin: 0; padding: 0; }
.hs-list li {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 3px 0;
  border-top: 1px solid rgba(27, 90, 168, .7);
  font-size: 12px;
}
.hs-list li:first-child { border-top: 0; }
.hs-list .place { width: 12px; color: var(--muted); font-size: 10.5px; }
.hs-list .who { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 700; }
.hs-list .val { font-variant-numeric: tabular-nums; font-weight: 800; }
.hs-list .when { flex: 0 0 auto; color: var(--muted); font-size: 10px; }
.hs-empty { margin: 2px 0 0; font-size: 11.5px; color: var(--muted); font-style: italic; }
.hs-note { margin-top: 10px; }

/* footer identity */
.you {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  font-size: 11px;
  color: var(--muted);
}
.you .ringmini { width: 22px; height: 22px; border-width: 2.5px; font-size: 9px; }
#soundBtn { margin-left: 0; }

.lobby-actions { display: flex; gap: 8px; margin-top: 12px; }
.big {
  flex: 1 1 auto;
  background: var(--panel-2);
  color: var(--fg);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 13px 14px;
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  min-height: 46px;
}
/* Rob, 2026-11-03 — THE PRIMARY ACTION WEARS THE FLAG'S GOLD. `.big.start` (#1f6f3f / #2b8c52) was
   the last survivor of the green-felt direction the navy shell replaced. Gold #FFBB2B is the flag's
   own, and the ink on it is the rank plate's #12151C: 10.78:1, measured, not chosen by eye. The
   gold marks the LOBBY's one forward action per state — "Start a new table" before a seat, the
   seated "I'm ready". The table list's Join buttons are repeats, not primaries, and stay navy; the
   lesson keeps every control navy, because it is the one screen where buttons and cards share the
   page and gold would read as the yellow pile (#FEE835 is 1.36:1 from gold in luminance).
   Geometry is untouched: same padding, same min-height, same place on the screen. */
.big.primary {
  background: #FFBB2B;
  color: #12151C;
  border-color: #D99A17;
}
.big.primary:not(:disabled):hover { background: #FFC94F; border-color: #E5A61C; }
.big.primary:not(:disabled):active { background: #E9A81F; border-color: #C08E14; }
/* Focus: a light ring, OFFSET so it is drawn on the navy shell, never inside the gold. White sits
   1.69:1 on gold and 10.8:1 on the shell — an inset ring would vanish; this one cannot. */
.big.primary:focus-visible { outline: 3px solid #F2F7FF; outline-offset: 2px; }
/* Disabled ("Start a new table" until the name field has a name): still recognisably the start
   button, plainly not live, and the ink on the dull gold is 7.73:1. */
.big.primary:disabled { background: #C7A45A; border-color: #A88A45; color: #12151C; cursor: not-allowed; }
.hint { font-size: 11px; color: var(--muted); line-height: 1.45; margin: 8px 0 0; }
.install-hint { color: #a8b6d4; }

/* -------------------------------------------------------------------- board */
/* THE FIXED BOARD (Rob, 2026-11-02). Sixteen slots, always: four colour columns in the one fixed
   order — red, blue, yellow, green — and four positions in each. The grid is a constant; it does
   not depend on how many piles are live, so nothing moves, grows or shrinks when a stack opens, is
   built on, or retires. Cards keep the size they have always had; the room comes from the gutters
   and the surrounding chrome. */
.board {
  display: grid;
  /* A column's footprint is its card PLUS its own 3 px padding and 1 px ring. The track carries the
     whole column, so the ring encloses the slots; sizing the track at --tile alone let the cards
     spill into the gutter and the four columns read as one block. */
  grid-template-columns: repeat(4, calc(var(--tile) + 8px));
  grid-auto-rows: min-content;
  gap: 0 var(--board-gap-x);
  justify-content: center;
  align-content: start;
}

/* Rob, 2026-11-02: the four Spud columns are separated by the free width, shared evenly — 20 px
   gutters on phones, 12 px above 480 px — never by shrinking a card. The board is capped at
   four columns + three gutters and centred, so the leftover goes BETWEEN the columns instead of
   accumulating at the edges, and the four still read as one table. */
#dutchBoard {
  width: 100%;
  max-width: calc(4 * (var(--tile) + 8px) + 36px);
  margin: 0 auto;
  justify-content: space-between;
  column-gap: 0;
}

.board-col {
  display: flex;
  flex-direction: column;
  gap: var(--board-gap);
  padding: 3px;
  border-radius: 11px;
  /* One thin ring per column, in that column's own face colour — the colour IS the label's job here. */
  border: 1px solid var(--col);
}
.board-col[data-colour="red"] { --col: var(--fc-red); }
.board-col[data-colour="blue"] { --col: var(--fc-blue); }
.board-col[data-colour="yellow"] { --col: var(--fc-yellow); }
.board-col[data-colour="green"] { --col: var(--fc-green); }

/* ------------------------------------------------- every region named on the felt (Rob, 2026-11-02)
   One outline language, used the same way everywhere: a thin 1 px line, never a fill, so a new player
   can name every pile on the board by looking at it. Spud = each column in its face colour (above);
   Solitaire = grey; Gust = a warm accent, because it is the thing the whole game is racing to empty;
   Wind = neutral, the quiet holding pile. */
.felt-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 3px 6px 2px;
  border-radius: 12px;
  border: 1px solid transparent;
}
.group-label {
  font-size: 9.5px;
  line-height: 1;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.group-row { display: flex; justify-content: center; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.felt-group.solitaire { border-color: #7C8DA8; }
.felt-group.gust { border-color: #b8792a; }
.felt-group.wind { border-color: var(--line); }
.own-piles { display: flex; justify-content: center; align-items: flex-start; gap: 8px; width: 100%; }

/* An EMPTY SLOT: a grey disc ringed thinly in its column's colour, showing the 1 it would accept.
   Same box as a live tile, so the column's rhythm never changes when a pile arrives or leaves. */
.board-slot {
  width: var(--tile);
  min-height: calc(var(--tile) + 14px);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 2px 0 0;
  background: transparent;
  border: 0;
  border-radius: 12px;
  color: inherit;
  font: inherit;
  touch-action: manipulation;
}
.board-slot .disc.empty {
  background: var(--navy-4);
  border: 2px solid var(--fill, #3a4256);
  border-style: solid;
  opacity: .8;
  box-shadow: none;
}
/* The card an empty slot would accept. Measured on #0B4689: the column colour as the numeral read
   2.05:1 (blue), 3.10:1 (green), 3.74:1 (red), 4.90:1 (yellow) — below the 3:1 large-text floor for
   two of the four, and this "1" is the affordance Rob asked for. The RING keeps the colour identity;
   the numeral is neutral and legible (7.9:1). */
.board-slot .disc.empty .n { font-size: 20px; font-weight: 700; color: #aab6cf; opacity: 1; }
.board-slot .disc.empty .fsym { opacity: .55; }
/* The natural target for an armed 1: the same ring, said louder, in the slot the stack will stand in. */
.board-slot.open-slot {
  outline: 2px dashed rgba(110, 231, 255, .75);
  outline-offset: 1px;
  border-radius: 12px;
}
.board-slot.open-slot .disc.empty { opacity: 1; border-color: #8fe6ff; }

/* The finished stacks, shown only when the tally's toggle asks for them. Outside the board, because
   putting them back into the grid is exactly the reflow the fixed board exists to prevent. */
.finished {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--board-gap) var(--board-gap-x);
  margin-top: 2px;
}

/* t_699be067 — a selected 1 with a free slot has one more legal target than the tiles: the slot
   it would open. The board carries a quiet outline, and the empty circle in that colour's column
   becomes the target (`.board-slot.open-slot`, styled with the board above). */
.board.open-target {
  outline: 2px dashed rgba(110, 231, 255, .55);
  outline-offset: 6px;
  border-radius: 12px;
}

/* The quiet tally of what has left the board, and the one-tap "show finished" toggle. */
.tally {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px 10px;
  margin: 2px 0 0;
  font-size: 11px;
  color: var(--muted);
  line-height: 1.2;
}
.tally-label { text-transform: lowercase; }
.tally-colour { white-space: nowrap; }
.tally-colour::before {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-right: 4px;
  border-radius: 50%;
  background: var(--fill);
  vertical-align: 1px;
}
.tally-colour[data-colour="red"] { --fill: var(--fc-red); }
.tally-colour[data-colour="blue"] { --fill: var(--fc-blue); }
.tally-colour[data-colour="yellow"] { --fill: var(--fc-yellow); }
.tally-colour[data-colour="green"] { --fill: var(--fc-green); }
.tally-toggle {
  font: inherit;
  font-size: 11px;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: #c3ccdd;
}
.tally-toggle[aria-pressed="true"] { background: var(--line); color: #fff; }

/* A retired pile is shown only on request: dimmed, flat, and not a target. */
.tile.retired .disc { opacity: .45; filter: grayscale(.25); }
.tile.retired .tilelabel { color: var(--muted); }

/* §84 — with the hand exhausted the Wood Pile is the new hand and has to be picked UP: the tile is
   a pile you are about to take, not a card you may tap. It reads as held, and the flip control
   beside it carries the highlight. */
.tile.locked { cursor: default; }
.tile.locked .disc { opacity: .55; filter: saturate(.6); box-shadow: var(--insul), 0 2px 5px rgba(0, 0, 0, .45); }
.tile.locked .tilelabel { color: var(--muted); }
/* ROB'S RULE (Rob, 2026-11-08) — this pass earned its slip: the Wind tile IS the slip control, so
   it reads as live and the ring says "tap me", not "I am a pile to pick up". */
.tile[data-slip="1"] { outline: 2px solid #ffbb2b; outline-offset: 2px; }
.flip.next {
  border-style: solid;
  border-color: #fff;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .35), 0 6px 16px rgba(0, 0, 0, .45);
  animation: next-glow 1.6s ease-in-out infinite;
}
/* A glow, not a blink: the control is the way forward and has to stay legible the whole time. */
@keyframes next-glow {
  0%, 100% { box-shadow: 0 0 0 2px rgba(255, 255, 255, .30), 0 6px 16px rgba(0, 0, 0, .45); }
  50% { box-shadow: 0 0 0 5px rgba(255, 255, 255, .50), 0 8px 20px rgba(0, 0, 0, .50); }
}
@media (prefers-reduced-motion: reduce) { .flip.next { animation: none; } }

/* t_dcae5a01 — nothing left to turn: the control is still readable and tappable (a tap explains
   why, it never sends a refused action), but it must not look like the way forward. */
.flip.lastcards { border-style: dashed; color: var(--muted); }
.flip.lastcards .count { color: var(--muted); }
.flip.lastcards .deckfan i { opacity: .45; }

/* A seat with no legal move at all says so, in place of the tap hint. */
.game-hint.stuck { color: #ffd166; }

.mine, .handbar {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  gap: 12px;
  flex-wrap: wrap;
  width: 100%;
}
.mine { row-gap: 6px; }
.handbar .flip { flex: 1 1 auto; max-width: 236px; }

/* --------------------------------------------------------------------- tile */
.tile {
  position: relative;
  width: var(--tile);
  min-height: calc(var(--tile) + 14px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  padding: 2px 0 0;
  background: transparent;
  border: 0;
  border-radius: 12px;
  color: inherit;
  font: inherit;
  touch-action: manipulation;
}
.tile:focus-visible { outline: 2px solid #8fb0ff; outline-offset: 2px; }
.tilelabel {
  font-size: 11px;
  line-height: 1.2;
  color: var(--muted);
  letter-spacing: .01em;
  white-space: nowrap;
}
.tile.filled .tilelabel { color: #c3ccdd; }

/* ---------------------------------------------------------------------- disc */
.disc {
  position: relative;
  /* The disc's own size, so the ring, the plate and its ring all hold their proportions at 60 px,
     at the 52 px phone board and on the 116 px lesson disc. */
  --disc-size: var(--disc);
  /* Rob, 2026-11-03: the hue is a channel, not a border. Navy sits on both sides of it, so a player's
     colour is always read against navy — never against the bright face it sits on, never against the
     shell. Before this, yellow-on-yellow measured 1.03:1 and four seats were effectively invisible. */
  --insul: inset 0 0 0 var(--disc-hair) var(--navy), 0 0 0 var(--disc-hair) var(--navy);
  width: var(--disc-size);
  height: var(--disc-size);
  flex: 0 0 auto;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--fill, #333);
  color: #fff;
  border: calc(var(--disc-size) * .1167) solid var(--ring, #666);
  box-shadow: var(--insul), 0 2px 5px rgba(0, 0, 0, .35), inset 0 -4px 10px rgba(0, 0, 0, .16);
  transition: transform .12s ease;
}
/* ring stroke style — the non-colour half of player identity */
.disc.ring-dashed { border-style: dashed; }
.disc.ring-dotted { border-style: dotted; border-width: calc(var(--disc-size) * .13); }
/* A double ring is two hue lines with navy between them, drawn INSIDE the channel rather than on the
   face: the whole stack (hue / navy / hue / navy) occupies the same 9 px a single ring takes, so a
   double-ring seat never eats more of the card than any other. */
.disc.ring-double {
  border-width: calc(var(--disc-size) * .05);
  --insul:
    inset 0 0 0 var(--disc-hair) var(--navy),
    inset 0 0 0 calc(var(--disc-hair) * 2) var(--ring, #666),
    inset 0 0 0 calc(var(--disc-hair) * 3) var(--navy),
    0 0 0 var(--disc-hair) var(--navy);
}

/* The rank plate (Rob, 2026-11-03): the number keeps its black ink and gains one black ring of its
   own — the separator between the rank and the player ring. 0.5667 of the disc wide, ring 0.0417,
   ink 0.3333: at 60 px that is a 34 px plate, a 2.5 px ring and 20 px of ink, so a two-digit 10
   clears the ring; the same proportions hold at 52 px and at 116 px. */
.disc .n {
  width: calc(var(--disc-size) * .5667);
  height: calc(var(--disc-size) * .5667);
  display: grid;
  place-items: center;
  border: calc(var(--disc-size) * .0417) solid var(--plate-ink);
  border-radius: 50%;
  color: var(--plate-ink);
  font-size: calc(var(--disc-size) * .3333);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  text-shadow: none;
}
/* Two digits need smaller ink to keep the same air inside one plate: at 52 px a 17.3 px "10" measured
   0.7 px of clearance to its ring, which reads as touching. 0.285 of the disc measured 2.4 px clear. */
.disc .n.two {
  font-size: calc(var(--disc-size) * .285);
  letter-spacing: -.03em;
}

/* boy / girl — derived from the fill colour, never a separate attribute */
.disc .sex {
  position: absolute;
  left: -3px;
  bottom: -3px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--navy-0);
  border: 1px solid rgba(242, 247, 255, .45);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  display: grid;
  place-items: center;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .8);
}

/* count: how many cards are under this one */
.disc .count {
  position: absolute;
  right: -5px;
  top: -4px;
  min-width: 18px;
  height: 18px;
  padding: 0 4px;
  border-radius: 9px;
  background: #f2f5fb;
  color: var(--plate-ink);
  border: 1.5px solid var(--plate-ink);
  font-size: 10px;
  font-weight: 800;
  line-height: 1;
  display: grid;
  place-items: center;
  font-variant-numeric: tabular-nums;
}

/* face-colour symbol: the non-colour half of the fill axis */
.disc .fsym {
  position: absolute;
  right: -3px;
  bottom: -3px;
  width: 17px;
  height: 17px;
  border-radius: 50%;
  background: rgba(0, 0, 0, .66);
  border: 1px solid rgba(255, 255, 255, .3);
  color: #fff;
  display: grid;
  place-items: center;
}
.disc .fsym svg { width: 12px; height: 12px; display: block; }

/* empty Dutch pile: ring + symbol, never a blank hole. It carries no seat, so it carries no navy
   channel either — the dashed face-colour ring IS the whole story. */
.disc.empty {
  background: var(--navy-0);
  border-style: dashed;
  border-color: var(--fill, #3a4256);
  opacity: .9;
  box-shadow: none;
}
/* an empty slot's "1" is a plain numeral: no plate, because there is no rank yet */
.disc.empty .n {
  width: auto;
  height: auto;
  border: 0;
  background: none;
  color: var(--fill, #3a4256);
  font-size: calc(var(--disc-size) * .3667);
  text-shadow: none;
}
.disc.empty .fsym { background: transparent; border-color: transparent; color: var(--fill, #8f9ab5); }
.disc.empty.neutral { border-color: #46506a; }
.disc.empty.neutral .n { color: #5b6580; }

/* ------------------------------------------------------------- the Blitz Pile
   Rob, 2026-10-31: "everyone is asking to have the blitz pile somehow specified so that it's easier to
   see and notice… it blends in, since all of the tokens on the entire phone are all the same."
   The Blitz pile is the objective — emptying it wins the hand — and it was drawn exactly like a Post
   pile. It is now emphatic on three channels a thumb can see at arm's length, and NONE of them is
   colour: the four face colours carry meaning, so a fifth hue would read as a fifth face.

     size   1.2× the other discs (52 → 62 px on a phone), set by --blitz-disc from --disc
     frame  the seat ring (whose deck — unchanged) plus a 4 px bright-neutral accent at 2.5 px clear:
            two concentric strokes read as "this one is framed" without the box, which would fight the
            disc language the lesson teaches (ring = whose deck, fill = face, number = rank, badge = depth)
     lift   a deeper, softer shadow, so it sits above the table instead of level with it

   The tile is widened by exactly the frame's footprint, so the larger disc can never reach into a
   neighbour's tap target; the disc's own centre is still the thing you tap, and its corners are still
   thumb room. The tutorial's own Blitz tile carries the same treatment (`data-tut-own`), because the
   lesson is drawn with the game's own parts and must not drift from it. */
.tile[data-pile="blitz"], .tile[data-tut-own="blitz"] { width: calc(var(--blitz-disc) + 16px); gap: 8px; }
.tile[data-pile="blitz"] .disc, .tile[data-tut-own="blitz"] .disc {
  --disc-size: var(--blitz-disc);
  width: var(--disc-size);
  height: var(--disc-size);
  outline: 4px solid var(--blitz-frame);
  outline-offset: 2.5px;
  box-shadow: var(--insul), 0 9px 20px rgba(0, 0, 0, .62), 0 3px 8px rgba(0, 0, 0, .55), inset 0 -6px 12px rgba(0, 0, 0, .18);
}
/* the count on the rim is the number people watch: it keeps its dark edge against the bright frame */
.tile[data-pile="blitz"] .disc .count, .tile[data-tut-own="blitz"] .disc .count {
  min-width: 20px; height: 20px; font-size: 11px; right: -6px; top: -5px; border-width: 2px;
}
.tile[data-pile="blitz"] .tilelabel, .tile[data-tut-own="blitz"] .tilelabel {
  color: #dfe6f4; font-weight: 700; letter-spacing: .04em;
}

/* ------------------------------------------------------------------ effects */
.tile.target .disc {
  box-shadow: var(--insul), 0 0 0 3px #fff, 0 0 20px rgba(255, 255, 255, .45), inset 0 -4px 10px rgba(0, 0, 0, .16);
  transform: translateY(-1px);
}
.tile.target .disc::before {
  content: "";
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .55);
  animation: pulse 1s ease-in-out infinite;
}
.tile.selected .disc { box-shadow: var(--insul), 0 0 0 3px #fff, 0 6px 14px rgba(0, 0, 0, .5); transform: translateY(-2px); }
.tile.live .disc { box-shadow: var(--insul), 0 0 0 2px rgba(255, 255, 255, .35), 0 2px 5px rgba(0, 0, 0, .45); }
.tile.hit .disc { animation: pop .22s ease-out; }
.tile.missed .disc { animation: shake .42s ease-in-out; }
.tile.over .disc { box-shadow: var(--insul), 0 0 0 3px #fff, 0 0 22px rgba(255, 255, 255, .5); }

@keyframes pulse { 0%, 100% { opacity: .25; transform: scale(.98); } 50% { opacity: .85; transform: scale(1.06); } }
@keyframes pop { 0% { transform: scale(.86); } 60% { transform: scale(1.08); } 100% { transform: scale(1); } }
@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px) rotate(-3deg); }
  45% { transform: translateX(5px) rotate(3deg); }
  70% { transform: translateX(-3px) rotate(-1deg); }
}

/* ------------------------------------------------------------------- handbar */
.flip {
  width: 150px;
  min-height: var(--tile);
  border-radius: var(--radius);
  border: 1px dashed var(--line);
  background: linear-gradient(180deg, var(--navy-4), var(--navy-1));
  color: var(--fg);
  font: inherit;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 4px;
}
.flip .count { font-size: 24px; font-weight: 800; line-height: 1; font-variant-numeric: tabular-nums; }
.flip .label { font-size: 10px; color: var(--muted); text-align: center; }
.flip .deckfan { display: flex; gap: 2px; margin-top: 3px; }
.flip .deckfan i { display: block; width: 7px; height: 10px; border-radius: 2px; background: var(--navy-4); border: 1px solid var(--line); }

/* ---------------------------------------------------------------------- opps */
.opps {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px 8px;
}
.opp {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 3px 7px;
  min-width: 0;
}
/* seven opponents in two columns: the last one takes the empty cell */
.opps > .opp:last-child:nth-child(odd) { grid-column: span 2; }
.opp .ringmini { width: 22px; height: 22px; border-width: 2.5px; font-size: 9px; }
.opp .ringmini.ring-dotted { border-width: 3px; }
.opp .meta { min-width: 0; display: flex; flex-direction: column; line-height: 1.15; }
/* t_646edbc5 — a roster cell is 181px at 390 wide, and the name line takes ~10-20px of that for the
   Gust count, so the label under the name has ~107px. "seat 2 · hand 12 · wood 24" measures 120px
   there: it wrapped, and a wrapped line is 10.3px the eight-seat screen does not have — the last
   opponent row went out of the box. The label is ONE line, always: it may ellipsize, it may never
   grow the row. The seat number it used to repeat is the ring's job. */
.opp .n { font-size: 12px; font-weight: 700; white-space: nowrap; display: flex; min-width: 0; }
.opp .n .nm { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.opp .blitzlabel {
  font-size: 9px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.opp .blitz {
  margin-left: auto;
  font-size: 14px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: #dfe5f0;
}
.opp.disconnected { opacity: .45; }
/* HOUSE RULE (Rob, 2026-10-31) — a person's seat a bot is playing: marked, never disguised as a player.
   The mark is its own flex item at the end of the name line, so the ellipsis that shortens a long
   name can never eat it (t_646edbc5). */
.opp.autobot .n { color: #9dbcff; }
.opp .n .botmark { flex: none; }

/* ------------------------------------------------------------------ actionbar */
.actionbar {
  flex: 0 0 auto;
  display: flex;
  gap: 8px;
  align-items: center;
  padding: 6px 10px calc(6px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--line);
  background: var(--panel);
}
.dutch-call {
  margin-left: auto;
  height: 44px;
  padding: 0 26px;
  border-radius: 12px;
  border: 1px solid #6d2a2a;
  background: linear-gradient(180deg, #a92c2c, #8a1f1f);
  color: #fff;
  font: inherit;
  font-size: 16px;
  font-weight: 800;
  letter-spacing: .01em;
}
.dutch-call:active { transform: translateY(1px); }

/* ------------------------------------------------------------- countdown */
.countdownScreen {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  background: var(--bg);
}
.cd-label { margin: 0; font-size: 13px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.cd-num {
  font-size: 132px;
  font-weight: 800;
  line-height: .95;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 8px 30px rgba(0, 0, 0, .7);
}
.cd-hint { margin: 0; font-size: 12px; color: var(--muted); }

/* ------------------------------------------------------- BLITZ! and scores */
.blitzScreen {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  background: var(--bg);
  padding: 18px;
  /* The panel's own bottom edge clears the strip the phone keeps for its system control (the bar
     with the centre close/back button). Nothing this screen draws may reach into it. */
  padding-bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
}
/* Centre in the free space when it fits; start at the top (and scroll) when it does not. */
.blitz-splash, .scores { margin: auto 0; }
.blitz-splash { text-align: center; animation: blitzIn .28s cubic-bezier(.2, .9, .3, 1.2); }
.blitz-word {
  font-size: clamp(64px, 22vw, 104px);
  font-weight: 900;
  letter-spacing: -.02em;
  line-height: .9;
  background: linear-gradient(180deg, #ffe86a, #ff9d2e 55%, #ff4d3d);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 6px 18px rgba(255, 120, 40, .35));
}
.blitz-who { margin: 14px 0 0; font-size: 16px; font-weight: 600; color: #f2f5fb; }
@keyframes blitzIn { from { opacity: 0; transform: scale(.82); } to { opacity: 1; transform: scale(1); } }

/* THE CONTINUE CONTROL SITS DIRECTLY UNDER THE TABLE, NOT UNDER THE ROSTER (Rob, 2026-11-02).
   On a phone the bottom of the viewport belongs to the OS: its centre close/back control lives
   there, and a primary action parked above it loses the tap and closes the window mid-session.
   So the head — outcome, then the one control that moves the hand on — is fixed at the top of the
   panel, and the roster scrolls in the only remaining box. The control therefore cannot move when
   its own text changes ("Next hand" / "Waiting for the table" / "Back to the tables"), because the
   roster below it takes every change in height. */
.scores {
  width: 100%;
  max-width: 360px;
  max-height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.scores-head { flex: 0 0 auto; }
.scores h2 { font-size: 18px; margin: 0 0 10px; text-align: center; }
/* The continue control is the first thing in the post-hand block (Rob, 2026-11-03 evening): it sits
   directly under the board — the Wind pile and its in-hand row are the last of the table — and the
   outcome heading and the scores follow it. It is the same `.big.primary` gold as the lobby's forward
   action, not a second primary style. */
.continue-row { flex: 0 0 auto; }
.continue-row .big { display: block; width: 100%; }
.scores-head .match-over { margin: 0 0 10px; }
/* The roster is the only scrolling box, so every seat stays reachable without moving the control.
   It is focusable (tabindex on the element) so a keyboard can scroll it too. */
.scores-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  padding-right: 2px;
}
.scores-body:focus-visible { outline: 2px solid #8FB0FF; outline-offset: 2px; border-radius: 8px; }
.scores ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; align-content: start; }
.score-row {
  display: flex;
  align-items: center;
  gap: 9px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 8px 10px;
}
.score-row.me { border-color: #8FB0FF; background: var(--navy-4); }
.score-name { font-size: 14px; font-weight: 600; display: flex; flex-direction: column; }
.score-name small { font-size: 10.5px; font-weight: 500; color: var(--muted); }
.score-blitz { margin-left: auto; font-size: 10.5px; color: var(--muted); }
.score-num { font-size: 18px; font-weight: 800; min-width: 34px; text-align: right; font-variant-numeric: tabular-nums; }
#nextHand { width: 100%; }

/* --------------------------------------------------- temporary table controls
   Rob, 2026-10-30: "add that button and maybe remove it later, and make sure it's
   in a location that you wouldn't hit it accidentally." They sit in their own
   section BELOW every primary action, separated by real whitespace and a hairline,
   and are plain text so they never compete with READY or Next hand. During a hand
   their parent screens are hidden, and table-controls.js hides them too. */
.table-controls {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 8px 22px;
  margin: 26px 0 10px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
.scores .table-controls { margin-top: 30px; }
.table-link {
  background: none;
  border: 0;
  padding: 8px 4px;
  font: inherit;
  font-size: 12px;
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  -webkit-tap-highlight-color: transparent;
}
.table-link.armed {
  color: #ffd7d2;
  font-weight: 700;
  text-decoration: none;
}
.table-link:focus-visible { outline: 2px solid #8FB0FF; outline-offset: 2px; }

/* --------------------------------------------------------------- pause/vote */
.pauseScreen {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 24px 20px;
  background: radial-gradient(circle at 50% 40%, #2a1013 0%, #0a0709 70%);
  text-align: center;
}
.pause-badge {
  font-size: clamp(38px, 13vw, 60px);
  font-weight: 900;
  letter-spacing: .08em;
  color: #ffd9d4;
  border: 3px solid #ff6a5e;
  border-radius: 16px;
  padding: 6px 18px;
  box-shadow: 0 0 0 8px rgba(255, 106, 94, .09), 0 0 40px rgba(255, 106, 94, .25);
}
.pause-who { margin: 8px 0 0; font-size: 19px; font-weight: 700; color: #f4f7fc; max-width: 320px; }
.pause-waiting {
  list-style: none;
  margin: 2px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 12.5px;
  color: #ffb4ab;
  letter-spacing: .02em;
}
.pause-waiting li b { color: #f4f7fc; }
.pause-waiting li span { font-variant-numeric: tabular-nums; }
.pause-timer { margin: 0; font-size: 13px; color: #ffb4ab; }
.vote { display: flex; flex-direction: column; align-items: center; gap: 10px; margin-top: 6px; }
.kick {
  min-width: 200px;
  height: 56px;
  border-radius: 14px;
  border: 1px solid #7d2b2b;
  background: linear-gradient(180deg, #c03434, #8f1f1f);
  color: #fff;
  font: inherit;
  font-size: 18px;
  font-weight: 800;
  letter-spacing: .02em;
}
.kick:disabled { filter: grayscale(.6) brightness(.7); }
.tally {
  margin: 0;
  font-size: 15px;
  color: #f4f7fc;
  font-variant-numeric: tabular-nums;
}
.tally b { font-size: 34px; font-weight: 900; margin-right: 4px; }
.vote-hint { margin: 0; font-size: 11.5px; color: var(--muted); max-width: 280px; }

/* ------------------------------------------------------------------- overlays */
/* HOUSE RULE (Rob, 2026-10-31) — the table-wide line: names the person, says why, says play goes
   on. It sits at the top, out of the thumb zone, and it is legible at 390 × 844. */
.tableNotice {
  position: fixed;
  left: 50%;
  top: max(8px, env(safe-area-inset-top));
  transform: translateX(-50%);
  width: min(94vw, 420px);
  background: var(--navy-1);
  border: 2px solid #7aa2ff;
  border-radius: 12px;
  padding: 10px 14px;
  font-size: 13.5px;
  font-weight: 700;
  line-height: 1.35;
  color: #eaf0ff;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .55);
  z-index: 60;
}

/* HOUSE RULE (Rob, 2026-10-31) — "Are you still there?" Full screen, above everything, one tap
   anywhere answers. The countdown is the largest thing on the phone after the question. */
.idlePrompt {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(0, 14, 40, .86);
  backdrop-filter: blur(2px);
  -webkit-tap-highlight-color: transparent;
}
.idlePrompt[hidden] { display: none; }
.idle-panel {
  width: min(92vw, 340px);
  max-height: 84vh;
  overflow: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
  background: var(--navy-3);
  border: 2px solid #7aa2ff;
  border-radius: 20px;
  padding: 22px 18px 20px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .7);
}
.idle-kicker {
  margin: 0;
  font-size: 11px;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--muted);
}
.idle-title {
  margin: 0;
  font-size: 26px;
  line-height: 1.15;
  font-weight: 900;
  color: #fff;
}
.idle-count {
  margin: 2px 0 0;
  font-size: 72px;
  line-height: 1;
  font-weight: 900;
  font-variant-numeric: tabular-nums;
  color: #ffd479;
  text-shadow: 0 2px 14px rgba(255, 212, 121, .35);
}
.idle-body {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
  color: #eaf0ff;
}
.idle-warn {
  margin: 4px 0 0;
  font-size: 12.5px;
  line-height: 1.4;
  color: #ffb4ab;
  max-width: 280px;
}
.toast {
  position: fixed;
  left: 50%;
  bottom: 74px;
  transform: translateX(-50%);
  max-width: min(92vw, 380px);
  background: var(--navy-1);
  border: 1px solid var(--line);
  border-radius: 11px;
  padding: 9px 13px;
  font-size: 12.5px;
  line-height: 1.35;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .5);
  z-index: 30;
}
.missed {
  position: fixed;
  left: 50%;
  top: 46%;
  transform: translate(-50%, -50%) scale(.9);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  background: #2b1210;
  border: 2px solid #ff6a5e;
  border-radius: 16px;
  padding: 12px 20px;
  box-shadow: 0 12px 34px rgba(0, 0, 0, .6);
  z-index: 40;
  animation: missedIn .18s ease-out forwards;
}
.missed b { font-size: 20px; letter-spacing: .01em; color: #ffd9d4; }
.missed span { font-size: 11.5px; color: #ffb4ab; }
@keyframes missedIn { from { opacity: 0; transform: translate(-50%, -50%) scale(.8); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } }

/* draggable disc + flight ghost */
.dragghost, .ghostcard {
  position: fixed;
  z-index: 50;
  pointer-events: none;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-weight: 800;
  color: var(--plate-ink);
  text-shadow: none;
  border: 4px solid var(--ring, #666);
  box-shadow: 0 10px 24px rgba(0, 0, 0, .55);
}

/* colour-blind simulation preview (review aid, not a game mode) */
#app.cvd-deuteranopia { filter: url(#cvd-deuteranopia); }
#app.cvd-protanopia { filter: url(#cvd-protanopia); }

/* ------------------------------------------------------------------ tutorial */
/* The lesson draws with the game's own .board/.mine/.handbar/.tile/.disc
   classes on purpose — what is taught is literally what is played. Only the
   lesson's chrome lives here. */
.tutorial {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 8px 12px max(6px, env(safe-area-inset-bottom));
  min-height: 0;
}
.tut-head { display: flex; align-items: center; gap: 8px; }
.tut-progress {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--muted);
  white-space: nowrap;
}
.tut-beats { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.tut-beats i { display: block; width: 6px; height: 6px; border-radius: 50%; background: var(--line); }
.tut-beats i.done { background: #8FB0FF; }
.tut-beats i.on { width: 16px; border-radius: 3px; background: #fff; }
.tut-skip { min-height: 30px; padding: 4px 12px; font-size: 12px; }

.tut-stage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  overflow: hidden;
}
.tut-board { display: flex; flex-direction: column; gap: 5px; width: 100%; align-items: center; }
.tut-region-label {
  margin: 0;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--muted);
  text-align: center;
}
.tut-region-label b { color: #c3ccdd; font-weight: 700; }
.mine.spot, .handbar.spot {
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .16);
  border-radius: 14px;
  padding: 4px 0;
}

.tut-coach {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 8px 11px;
  min-height: 76px;
}
.tut-title { font-size: 14px; margin: 0 0 3px; line-height: 1.25; }
.tut-body { font-size: 12.5px; line-height: 1.42; color: #c3ccdd; margin: 0; }

.tut-actions { display: flex; flex-direction: column; gap: 6px; }
.tut-row-actions { display: flex; gap: 8px; }
.tut-row-actions .big { min-height: 42px; padding: 9px 12px; font-size: 14px; }
.tut-row-actions .ghost { min-width: 78px; min-height: 42px; }
#tutReady { min-height: 42px; padding: 9px 12px; font-size: 14px; }
#tutReady:disabled { filter: grayscale(.35) brightness(.82); }

/* --- the disc, layer by layer: the highlighted layer is the pointed-at one */
.anat { display: flex; flex-direction: column; align-items: center; gap: 7px; width: 100%; }
.anat-discwrap { position: relative; padding: 8px; }
.disc.big { --disc-size: 116px; width: var(--disc-size); height: var(--disc-size); }
.disc.big .sex { width: 26px; height: 26px; font-size: 17px; left: -6px; bottom: -6px; }
.disc.big .count { min-width: 27px; height: 26px; font-size: 13px; right: -8px; top: -8px; }
.disc.big .fsym { width: 24px; height: 24px; right: -6px; bottom: -6px; }
.disc.big .fsym svg { width: 16px; height: 16px; }
.anat-hl {
  position: absolute;
  pointer-events: none;
  border: 3px dashed #fff;
  box-shadow: 0 0 18px rgba(255, 255, 255, .45);
  animation: anatPulse 1.15s ease-in-out infinite;
}
@keyframes anatPulse { 0%, 100% { opacity: .95; } 50% { opacity: .4; } }
.anat-hl-ring { inset: 4px; border-radius: 50%; }
.anat-hl-fill { inset: 19px; border-radius: 50%; background: rgba(255, 255, 255, .16); }
.anat-hl-number { left: 50%; top: 50%; width: 74px; height: 74px; transform: translate(-50%, -50%); border-radius: 50%; }
.anat-hl-sex { left: 1px; bottom: 1px; width: 38px; height: 38px; border-radius: 50%; }
.anat-hl-count { right: 1px; top: 1px; width: 38px; height: 38px; border-radius: 50%; }
.anat-legend { display: grid; gap: 2px; width: 100%; }
.anat-row {
  display: flex;
  gap: 8px;
  align-items: baseline;
  padding: 3px 7px;
  border-radius: 8px;
  border: 1px solid transparent;
}
.anat-row.on { background: var(--navy-4); border-color: #8FB0FF; }
.anat-row .anat-tag { flex: 0 0 58px; font-size: 12px; color: var(--muted); }
.anat-row.on .anat-tag { color: #fff; }
.anat-row .anat-text { font-size: 11.5px; line-height: 1.32; color: var(--muted); }
.anat-row.on .anat-text { color: #eef1f6; }

/* --- the goal */
.tut-goal { display: flex; align-items: center; gap: 16px; padding: 0 6px; }
.tut-goal-disc { position: relative; }
.tut-goal-cap {
  position: absolute;
  left: 50%;
  top: -20px;
  transform: translateX(-50%);
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}
.tut-goal-facts { display: grid; gap: 7px; min-width: 0; }
.tut-fact { display: flex; gap: 7px; align-items: baseline; font-size: 11.5px; color: #c3ccdd; line-height: 1.25; }
.tut-fact b { font-size: 15px; color: #fff; min-width: 26px; font-variant-numeric: tabular-nums; }

/* Phones: the disc is 52 px and NEVER smaller — the number is the card. Four fixed colour columns
   (4 × 64 px of column, the ring included) and four positions in each: sixteen slots. The 20 px
   gutters are shared out of whatever width is left, capped at 4 × 64 + 3 × 20 = 316 px, so the board
   reads as one table on a 336 px viewport and never overflows a narrower one. */
@media (max-width: 480px) {
  :root { --disc: 52px; --tile: 56px; --board-gap: 4px; --board-gap-x: 4px; }
  #dutchBoard { max-width: calc(4 * (var(--tile) + 8px) + 60px); }
}

/* Narrow phones: the card is 52 px here, so four columns are 4 × 60 = 240 px and the same 20 px
   gutters fit inside a 320 px screen (4 × 60 + 3 × 20 = 300 px = the whole content width). */
@media (max-width: 380px) {
  :root { --disc: 52px; --tile: 52px; --board-gap: 3px; --board-gap-x: 2px; }
  .board { gap: 0 2px; }
  .board-col { gap: 3px; }
  .mine, .handbar { gap: 8px; }
}

/* The lesson draws its own fixed scene: six to a row is the lesson's layout, not the live board's. */
.tut-board .board { grid-template-columns: repeat(6, var(--tile)); grid-auto-flow: row; gap: 5px var(--board-gap-x); }


/* ------------------------------------------------------------- spectating (v10)
   EXT v10 (Rob, 2026-11-02) — "view only instead of join the game".

   Three pieces, no new layout: the watcher count is a pill in the topbar (zero height cost, visible
   in the lobby and at the table), Watch is a second action on a playing table's row, and the watch
   bar is one line above the felt. The board itself is the players' board, unchanged — a spectator
   holds no cards, so the Solitaire and Wind groups are not drawn at all. */
/* The watcher count. It sits in the topbar (visible in the lobby AND at the table, no layout height),
   so on a 390 px phone it must stay quiet: no uppercase tracking, no ellipsis, just the number. */
.watchchip {
  flex: 0 0 auto;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0;
  color: #cfe0ff;
  border: 1px solid var(--line);
  background: var(--navy-1);
  border-radius: 999px;
  padding: 2px 7px;
  white-space: nowrap;
}
.watchchip:not([hidden]) { display: inline-block; }

/* The row's actions: Watch beside the shut Join, so a live hand is a window, not a closed door. */
.table-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
.table-watch {
  flex: 0 0 auto;
  background: var(--navy-4);
  color: #e8f0ff;
  border: 1px solid var(--navy-hi);
  border-radius: 9px;
  padding: 9px 12px;
  font: inherit;
  font-size: 13px;
  font-weight: 700;
}
.table-watch:active { background: var(--navy-5); }
/* Both actions on one 390 px row: the dead Join shrinks to its state word rather than squeezing
   Watch into an ellipsis. */
.table-row.playing .table-join { padding: 9px 8px; font-size: 10.5px; }

.watchbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 2px;
  padding: 3px 8px;
  border-radius: 9px;
  background: var(--navy-1);
  border: 1px solid var(--line);
  color: #dbe7ff;
  font-size: 11px;
  min-height: 24px;
}
.watchbar-dot {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #6ea8ff;
}
.watchbar-text {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 700;
  line-height: 1.15;
  /* The sentence is the point: it wraps to a second line rather than ellipsizing. */
  white-space: normal;
}
.watchbar-back { margin-left: auto; flex: 0 0 auto; padding: 3px 7px; font-size: 10.5px; }

/* --------------------------------------------------------- the phone's open-table row (t_120d3bed)
   Five things — name, code, head count, score target, actions — do not fit on one 362 px line at
   390 wide without crushing one of them, and the one that was being crushed was the score target,
   the only place a joiner can read the rules before sitting down. So on a phone the row is two
   lines:

     line 1   the creator's name and the table code
     line 2   the head count, the score target, and the actions

   The units are separate elements, so the NAME is what truncates (it has the ellipsis; the code,
   the count and the target are nowrap and never shrink). Type sizes are untouched and so are the
   buttons — these are pressed with a thumb. */
@media (max-width: 559px) {
  .table-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "who   state"
      "facts actions";
    column-gap: 8px;
    row-gap: 3px;
    align-items: center;
  }
  /* The list is still one row per table; the row's own box keeps the border, the padding and the
     tap-target height, while its two label elements become grid items of that box. */
  .table-meta, .table-meta b.table-title { display: contents; }
  .table-who { grid-area: who; min-width: 0; }
  /* The state is derived (playing/waiting, a watcher count); if a 320 px phone cannot hold it
     beside the name it ellipsizes rather than pushing the name or the facts. */
  .table-state { grid-area: state; justify-self: end; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .table-facts { grid-area: facts; }
  /* The ` - ` that separated the code from the head count on the single-line row would be a
     dangling dash at the end of line 1 here; the two lines are the separator. */
  .table-facts .table-dash { display: none; }
  .table-actions { grid-area: actions; justify-self: end; }
}

/* Narrower than a phone's comfortable 362 px column the facts and the two actions cannot share a
   line (161 + 8 + 139 > the content width), so the row goes to three: identity, facts, then the
   state with the actions beside it — where a long watcher count may ellipsize and nothing else. */
@media (max-width: 380px) {
  .table-row {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "who   who"
      "facts facts"
      "state actions";
  }
  .table-state { justify-self: start; }
}
