:root {
  --bg: #f3f5f2;
  --surface: #ffffff;
  --fg: #1f2a24;
  --muted: #5d6b63;
  --border: #d6ddd8;
  --accent: #2f6b4f;
  --on-accent: #ffffff;
  --danger: #b3261e;
  --away-bg: #fbe9c6;
  --away-fg: #6b4a00;
  --card-face: #ffffff;
  --card-edge: #c3ccc6;
  --card-black: #1d2320;
  --card-red: #c62828;
  --card-back: #2f6b4f;
  --card-back-2: #3d8462;
  --felt-1: #3f8a66;
  --felt-2: #2c6a4c;
  --felt-rim: #a8c9b6;
  --felt-text: #f1f7f3;
  --turn: #e0a526;
  --turn-text: #8a5d00;
  --turn-ring: #d32f2f;
  --star: #2f7bd8; /* The Mind's shurikens */
  --done-ring: #2e9e5b;
  --seat-bg: #ffffff;
  --seat-edge: #b9c4bd;
  --danger-strong: #c62828;
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #141a17;
    --surface: #1e2622;
    --fg: #e8eee9;
    --muted: #9aa8a0;
    --border: #2f3a34;
    --accent: #6fbf93;
    --on-accent: #10251b;
    --danger: #ff8a80;
    --away-bg: #4a3a12;
    --away-fg: #f5dc9c;
    --card-face: #f3f5f3;
    --card-edge: #0d110f;
    --card-black: #1d2320;
    --card-red: #c62828;
    --card-back: #2f6b4f;
    --card-back-2: #3d8462;
    --felt-1: #285a43;
    --felt-2: #1b4231;
    --felt-rim: #4f7a63;
    --felt-text: #e3efe8;
    --turn: #f2bd4b;
    --turn-text: #f2bd4b;
    --turn-ring: #ff5a4f;
    --star: #4f97ec;
    --done-ring: #5fd38d;
    --seat-bg: #34413a;
    --seat-edge: #5d6e64;
    --danger-strong: #d23b30;
    color-scheme: dark;
  }
}

* { box-sizing: border-box; }

html, body { margin: 0; }

/* Nothing on the site is text to select: a long press would otherwise
   highlight words and bring up copy or search pop-ups. Text boxes still
   work as normal. */
body {
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
input, textarea, [contenteditable] {
  user-select: text;
  -webkit-user-select: text;
  -webkit-touch-callout: default;
}

/* Read out to screen readers, not shown. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* No double-tap zoom anywhere (panning and scrolling still work); see nozoom.js. */
html { touch-action: manipulation; }

body {
  background: var(--bg);
  color: var(--fg);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* dvh: the height actually visible, which shrinks while a phone's address
     bar is showing (vh is the height with it hidden). */
  min-height: 100vh;
  min-height: 100dvh;
  -webkit-tap-highlight-color: transparent;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--accent);
  padding: calc(14px + env(safe-area-inset-top, 0px)) max(16px, env(safe-area-inset-right, 0px)) 14px max(16px, env(safe-area-inset-left, 0px));
}

.brand {
  color: var(--on-accent);
  font-weight: 700;
  font-size: 1.15rem;
  text-decoration: none;
}

main {
  max-width: 640px;
  margin: 0 auto;
  padding: 20px max(16px, env(safe-area-inset-right, 0px)) 96px max(16px, env(safe-area-inset-left, 0px));
}

h1 { font-size: 1.5rem; margin: 0 0 16px; }

/* Game list */
/* minmax(0, 1fr): every game box is exactly the page's width, never wider
   because of its text (a long description gets "…" instead). */
.game-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 12px;
}

.game-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  color: inherit;
  text-decoration: none;
}

.game-card:active { transform: scale(0.98); }
.game-card:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

.game-icon {
  flex: none;
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
  border-radius: 10px;
  background: var(--bg);
  font-size: 1.6rem;
  font-weight: 800;
}

.game-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; }

/* The game's name on the left, its player count on the right, lined up. */
.game-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.game-name { font-weight: 700; font-size: 1.1rem; }
/* One line; cut off with "…" if a screen is too narrow for it. */
.game-meta {
  overflow: hidden;
  color: var(--muted);
  font-size: 0.9rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.game-players {
  flex: none;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--bg);
  color: var(--muted);
  font-size: 0.8rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Game screen */
.back-link {
  display: inline-block;
  color: var(--accent);
  text-decoration: none;
  font-weight: 600;
  margin-bottom: 12px;
}

.game-actions { margin-top: 24px; }

/* Cards: drawn with HTML and CSS, sized by --card-w. */
:root { --card-w: 74px; }

.playing-card {
  position: relative;
  display: block;
  flex: none;
  width: var(--card-w);
  aspect-ratio: 5 / 7;
  border: 1px solid var(--card-edge);
  border-radius: 8px;
  background: var(--card-face);
  color: var(--card-black);
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.25);
  font-weight: 700;
  user-select: none;
  -webkit-user-select: none;
}

.playing-card.red { color: var(--card-red); }

/* A number card (The Mind): the number in the corner and big in the middle.
   Up to three digits, so the middle number is a little smaller than a suit. */
.number-card .number-pip {
  font-size: calc(var(--card-w) * 0.36);
  font-weight: 800;
  letter-spacing: -0.02em;
}

/* Top-left corner stays visible when cards overlap in a hand. Its text and
   the center suit scale with the card's width, so one --card-w sizes it all;
   the corner never goes below 12px so small cards stay readable. */
.card-corner {
  position: absolute;
  top: calc(var(--card-w) * 0.075);
  left: calc(var(--card-w) * 0.075);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  font-size: max(0.75rem, calc(var(--card-w) * 0.235));
  line-height: 1;
}

.card-pip {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: calc(var(--card-w) * 0.45);
  line-height: 1;
}

.playing-card.back {
  background: repeating-linear-gradient(45deg, var(--card-back) 0 5px, var(--card-back-2) 5px 10px);
  box-shadow: inset 0 0 0 3px var(--card-face), 0 1px 3px rgb(0 0 0 / 0.25);
}

/* Each slot shrinks down to a sliver so the whole hand fits on screen; the
   card inside keeps its size and overlaps the next one. If even slivers
   don't fit, the hand scrolls sideways. Auto margins center it without
   cutting off the first card when it scrolls. */
/* Scrolls sideways by swiping, with no visible scrollbar. */
.hand {
  display: flex;
  overflow-x: auto;
  padding: 4px 4px 12px;
  scrollbar-width: none;
}

.hand::-webkit-scrollbar { display: none; }

.hand-slot {
  flex: 0 1 var(--card-w);
  min-width: calc(var(--card-w) * 0.33); /* enough to show the corner rank and suit */
}

/* Tappable hand: selected cards lift and get an outline. */
button.hand-slot {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
  touch-action: manipulation;
}

.hand-slot .playing-card { transition: transform 0.12s ease; }

/* Cards about to be played: picked by you, or by the game (see handEl). */
.hand-slot.selected .playing-card {
  transform: translateY(calc(var(--card-w) * -0.22));
  box-shadow: 0 0 0 3px var(--accent), 0 4px 10px rgb(0 0 0 / 0.3);
}

button.hand-slot:focus-visible { outline: none; }
button.hand-slot:focus-visible .playing-card { outline: 3px solid var(--accent); outline-offset: 2px; }

.hand:has(button.hand-slot) { padding-top: 20px; }

@media (prefers-reduced-motion: reduce) {
  .hand-slot .playing-card { transition: none; }
}

.hand-slot:first-child { margin-left: auto; }
.hand-slot:last-child { flex-shrink: 0; margin-right: auto; }

/* The gap a card just played leaves (cards.js gapEl): its slot as it was,
   the card showing until it sets off for the pile, then shrinking away so
   the cards beside it slide together. (The card before a last gap keeps
   its full width, as the last card does.) */
.hand-slot.hand-gap {
  flex: 0 0 var(--gap-w);
  min-width: 0;
  pointer-events: none;
}
.hand-slot:has(+ .hand-gap:last-child) { flex-shrink: 0; }
@keyframes hand-gap-close {
  from { flex-basis: var(--gap-w); }
  to { flex-basis: 0px; }
}
@keyframes hand-gap-card {
  to { visibility: hidden; }
}

.my-hand { margin-top: 20px; }
.my-hand h2 { margin-bottom: 8px; }

/* The table: a rounded rectangle filling its area. table.js places each seat
   (in px) evenly around the felt's edge, centered on its point. The center
   holds the pile.

   Everything on the table is sized from the table area itself (cqmin = the
   smaller of its width and height, in %; see .table-area below), each within
   limits: a small phone gets a compact table, a tablet a roomy one, and
   nothing gets too small to read or tap. */
.table {
  --avatar: clamp(34px, 11cqmin, 64px); /* seat circles */
  --pile-w: clamp(46px, 17cqmin, 112px); /* cards in the middle */
  --t-font: clamp(0.8rem, 3.6cqmin, 1.25rem); /* text on the felt */
  position: relative;
  width: 100%;
  height: 100%;
}

.table.crowded { --avatar: clamp(28px, 9cqmin, 52px); }

/* The felt's edge runs through the seat circles' centers. These insets (and
   the corner radius) must match seatInsets() in table.js: above, half a
   circle and its turn ring; below, half a circle and the name under it. */
.table {
  --felt-top: calc(var(--avatar) * 0.5 + 26px); /* room above the top row for a speech bubble */
  --felt-bottom: calc(var(--avatar) * 0.5 + 6px + 1.25 * max(0.7rem, var(--avatar) * 0.3));
}
.table-felt {
  position: absolute;
  inset: var(--felt-top) calc(var(--avatar) * 0.825 + 2px) var(--felt-bottom);
  border-radius: 28px;
  background: radial-gradient(ellipse at center, var(--felt-1), var(--felt-2));
  box-shadow: inset 0 0 0 5px var(--felt-rim);
}

/* The middle of the table, nudged up a little: the bottom row of seats (with
   your name under your circle) reaches further in than the top row, and the
   BS! button below the pile needs to clear it. */
.table-center {
  position: absolute;
  inset: var(--felt-top) 0 var(--felt-bottom); /* the middle of the felt */
  display: grid;
  place-items: center;
  pointer-events: none;
}

.table-center > * { pointer-events: auto; }

/* Seats (and their speech bubbles) draw above everything in the middle of
   the table, like the BS reveal, so nobody gets covered. The list itself has
   no height, so it never blocks taps on the table. */
.seats-ring { position: relative; z-index: 3; list-style: none; margin: 0; padding: 0; }

.seat-spot {
  position: absolute;
  transform: translate(-50%, calc(var(--avatar) / -2)); /* the circle's center on the felt's edge */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px; /* room for the turn ring's pulse above the name */
  width: calc(var(--avatar) * 1.65);
}

.seat-avatar {
  position: relative;
  z-index: 2; /* with its card count, over the cards fanned in front of the seat (.seat-cards) */
  width: var(--avatar);
  height: var(--avatar);
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--seat-bg);
  border: 2px solid var(--seat-edge);
  font-weight: 700;
  font-size: calc(var(--avatar) * 0.42);
  line-height: 1;
}

.seat-spot.me .seat-avatar {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.seat-spot.away .seat-avatar { opacity: 0.45; }

/* Someone with nothing left to play (seat.done): a steady green ring. */
.seat-spot.done .seat-avatar { box-shadow: 0 0 0 3px var(--bg), 0 0 0 6px var(--done-ring); }

/* Whose move it is: a pulsing red ring. table.js sets a negative
   animation-delay from the clock so the pulse keeps its rhythm when the
   screen is redrawn. */
.seat-spot.active .seat-avatar {
  box-shadow: 0 0 0 3px var(--bg), 0 0 0 6px var(--turn-ring);
  animation: turn-pulse 1.4s ease-in-out infinite;
}

/* The ring itself breathes: it thickens and its glow grows straight off its
   edge, both together, so it reads as one pulsing ring. */
@keyframes turn-pulse {
  0%, 100% {
    box-shadow: 0 0 0 3px var(--bg), 0 0 0 5px var(--turn-ring),
      0 0 4px 5px color-mix(in srgb, var(--turn-ring) 45%, transparent);
  }
  50% {
    box-shadow: 0 0 0 3px var(--bg), 0 0 0 7px var(--turn-ring),
      0 0 6px 7px color-mix(in srgb, var(--turn-ring) 45%, transparent);
  }
}

@media (prefers-reduced-motion: reduce) {
  .seat-spot.active .seat-avatar { animation: none; }
}

/* Card count: a small badge hanging off the avatar's lower-right edge, so
   the initial stays readable. */
.seat-count {
  position: absolute;
  left: 62%;
  bottom: -6px;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 1px 5px 1px 3px;
  border-radius: 999px;
  background: var(--fg);
  color: var(--bg);
  font-size: max(0.62rem, calc(var(--avatar) * 0.26));
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}

.seat-count .mini-back { width: 0.7em; height: 1em; }
/* A thin black edge, so the count stands out over cards laid in front of
   the seat and over the felt alike (every game). */
.seat-count { box-shadow: 0 0 0 1.5px #000; }

/* Cards laid in front of a seat (table.js seat.cards): fanned around the
   seat's circle like a hand that player holds up, pointing toward the pile.
   table.js works out the size (--card-w), how far out from the circle's
   center the cards start (--inner) and each card's direction (--angle). The
   fan's origin is the circle's center. */
.seat-cards {
  position: absolute;
  z-index: 1; /* over the seat's name, which the fan can reach */
  top: calc(var(--avatar) / 2);
  left: 50%;
  width: 0;
  height: 0;
  pointer-events: none;
}

.seat-cards .playing-card {
  position: absolute;
  left: calc(var(--card-w) / -2);
  bottom: calc(var(--inner) + var(--pick, 0px)); /* (--pick: slid out, e.g. a card picked) */
  width: var(--card-w);
  box-shadow: 0 1px 3px rgb(0 0 0 / 0.3);
  transform-origin: 50% calc(100% + var(--inner) + var(--pick, 0px));
  rotate: var(--angle, 0deg);
}
.seat-cards .card-corner { font-size: max(0.55rem, calc(var(--card-w) * 0.32)); }
/* Too small for two numbers: just the one in the corner, the part that shows
   when they overlap. */
.seat-cards .card-pip { display: none; }

/* The Mind's Hard Mode reveal at the end of a level (mind.js hardDealEl):
   everything else on the table fades out, a face-down pile gathers and
   moves down, turns over, and its cards are dealt into rows from the top
   left, stopping at the first one out of order, which turns red. mind.js
   sets the timing and the spots (--py: the pile, --sx/--sy: a card's spot
   in the rows, from the middle of the table). */
.table-stack.hard-revealing > :not(.hard-deal, .end-title),
.table.hard-revealing .seat-cards {
  animation: reveal-fade 400ms ease-out var(--rv-late, 0ms) both;
}
@keyframes reveal-fade {
  to { opacity: 0; }
}
/* The title comes up where the reveal's pile was, under the dealt rows. */
.table-stack.hard-revealing > .end-title {
  --title-y: -50%;
  bottom: auto;
  top: calc(50% + var(--rv-pile-y, 0px));
  translate: -50% -50%;
}
.hard-deal {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 5;
  width: 0;
  height: 0;
  perspective: 700px;
  pointer-events: none;
}
.deal-faces { position: absolute; inset: 0; }
/* Full size, like the pile. */
.hard-deal { --card-w: var(--pile-w); }
.hard-deal .playing-card {
  position: absolute;
  left: calc(var(--card-w) / -2);
  top: calc(var(--card-w) * -0.7);
  width: var(--card-w);
  translate: 0 var(--py, 0px);
  z-index: var(--zp);
  box-shadow: 0 1px 4px rgb(0 0 0 / 0.35);
}
@keyframes deal-gather {
  from { opacity: 0; translate: 0 0; }
  to { opacity: 1; translate: 0 var(--py, 0px); }
}
@keyframes deal-flip-away {
  to { rotate: y 90deg; }
}
@keyframes deal-flip-in {
  from { rotate: y -90deg; }
  to { rotate: y 0deg; }
}
/* (z-index: under the pile's next cards until it leaves, then over the
   cards already dealt.) */
@keyframes deal-out {
  from { translate: 0 var(--py, 0px); z-index: var(--zp); }
  2% { z-index: var(--zd); }
  to { translate: var(--sx, 0px) var(--sy, 0px); z-index: var(--zd); }
}
@keyframes deal-bad {
  to { box-shadow: 0 0 0 2px var(--danger-strong), 0 0 12px var(--danger-strong); }
}
@media (prefers-reduced-motion: reduce) {
  .hard-deal *, .hard-revealing > * { animation-duration: 1ms !important; }
}

/* The Mind's card range, faint, just under the pile. */
.mind-range {
  margin: 0;
  color: var(--felt-text);
  opacity: 0.55;
  font-size: calc(var(--t-font) * 0.8);
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* A card that should have been played first (The Mind, when the team loses). */
.playing-card.too-low {
  box-shadow: 0 0 0 2px var(--danger-strong), 0 0 10px var(--danger-strong);
}

/* A card on its way to the pile, from wherever it was played (a seat, or
   your hand; see ui/play.js). play.js sets where it starts (--from-x, --from-y, relative to
   its place on the pile), its size there (--from-scale) and the turn that
   straightens it out (--from-rotate); it ends where the pile drew it. */
.playing-card.flying { z-index: 4; } /* over the text under the pile, and the seats it passes */

@keyframes card-fly {
  from {
    translate: var(--from-x, 0) var(--from-y, 0);
    scale: var(--from-scale, 1);
    rotate: var(--from-rotate, 0deg);
  }
}
/* The Mind, after a mistake: the card played too soon slides down off the
   pile, all the way clear of it, for the lower cards to go in under it,
   then slides back up (mind.js). */
@keyframes card-lift {
  to { transform: translateY(108%); }
}
@keyframes card-settle {
  from { transform: translateY(108%); }
  to { transform: none; }
}

/* A card waiting its turn to fly in (play.js flyCards). */
@keyframes card-wait {
  from, to { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .playing-card.flying { animation-duration: 1ms !important; }
}

/* Your card going onto the pile face down turns over on the way (play.js
   turnOver): while it flies it's just a holder for its face, turning away
   edge-on, then a back of its own, turning in. */
.pile-stack .playing-card.fly-turn,
.playing-card.fly-turn {
  background: none;
  border-color: transparent;
  box-shadow: none;
}
.playing-card.fly-turn > .playing-card {
  position: absolute;
  inset: -1px;
  width: auto;
  transform: none;
}
@keyframes fly-face-out {
  from { transform: perspective(500px) rotateY(0deg); }
  to { transform: perspective(500px) rotateY(90deg); visibility: hidden; }
}
@keyframes fly-back-in {
  from { transform: perspective(500px) rotateY(-90deg); }
  to { transform: perspective(500px) rotateY(0deg); }
}

.seat-label {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: max(0.7rem, calc(var(--avatar) * 0.3));
  line-height: 1.25; /* the felt leaves exactly this much room (see .table) */
  font-weight: 600;
}

/* With cards fanned in front of the seat, the name goes on the far side of
   the circle (table.js sets --lx and --ly from the circle's center). */
.seat-label.away-from-fan {
  position: absolute;
  top: calc(var(--avatar) / 2);
  left: 50%;
  max-width: calc(var(--avatar) * 2.5);
  translate: calc(-50% + var(--lx, 0px)) calc(-50% + var(--ly, 0px));
}

/* The cards fanning out from the circle when they're laid down, one after
   another, and the name sliding out of their way from under the circle.
   table.js sets --fan-late (how far along, kept across redraws), --toward
   (the fan's middle) and each card's --i. */
.seat-spot.fanning .seat-cards .playing-card {
  animation: fan-out 520ms cubic-bezier(0.2, 0.8, 0.3, 1) calc(var(--fan-late) + var(--i, 0) * 35ms) both;
}
@keyframes fan-out {
  from { rotate: var(--toward); scale: 0.3; opacity: 0; }
  30% { opacity: 1; }
}
.seat-spot.fanning .seat-label.away-from-fan {
  animation: label-out 450ms cubic-bezier(0.3, 0.7, 0.3, 1) var(--fan-late) both;
}
@keyframes label-out {
  from { translate: -50% calc(-50% + var(--ly0, 0px)); }
}
@media (prefers-reduced-motion: reduce) {
  .seat-spot.fanning .seat-cards .playing-card,
  .seat-spot.fanning .seat-label.away-from-fan { animation: none; }
}

/* A player who has left the room: their name, then "(left)" in red. */
.seat-left { color: var(--danger-strong); }

.pile {
  position: relative;
  display: grid;
  place-items: center;
}

/* .spaced: the same room around it as .pile-stack, so the pile does not
   move when the first card lands. */
.pile-empty.spaced { margin: 6px 0; }

.pile-empty {
  width: var(--pile-w);
  aspect-ratio: 5 / 7;
  border: 2px dashed var(--felt-rim);
  border-radius: 8px;
}

/* How many cards are in the pile: a badge on its corner, like the seats' counts. */
.pile-count {
  position: absolute;
  right: -0.9em;
  bottom: -0.4em;
  z-index: 1;
  min-width: 1.6em;
  padding: 1px 0.45em;
  border-radius: 999px;
  background: var(--fg);
  color: var(--bg);
  font-size: max(0.7rem, calc(var(--pile-w) * 0.16));
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* BS: the pile's count stays on top of the cards played onto it (their gold
   edge reaches the corner), with a thin black edge, like The Mind's seat
   counts. */
.pile > .pile-count {
  z-index: 5;
  box-shadow: 0 0 0 1.5px #000;
}

/* Only the pile takes up space here, so it sits exactly in the middle of the
   table. Text and buttons hang below it and the BS reveal above it,
   positioned absolutely so they never push the pile around. */
.table-stack {
  position: relative;
  display: grid;
  place-items: center;
}

.table-below {
  position: absolute;
  left: 50%;
  translate: -50% 0;
  width: clamp(170px, 60cqi, 340px);
  display: grid;
  justify-items: center;
  gap: clamp(6px, 2cqmin, 12px);
}

.table-below { top: calc(100% + clamp(6px, 2cqmin, 12px)); }

/* BS's text line sits above the pile instead, its text at the bottom (next
   to the pile). */
.table-above {
  position: absolute;
  bottom: calc(100% + clamp(6px, 2cqmin, 12px));
  left: 50%;
  translate: -50% 0;
  width: clamp(170px, 60cqi, 340px);
  display: grid;
  justify-items: center;
}

/* The text line under the pile. Fixed height (room for two lines, text
   sitting at the bottom) so the countdown and BS! button below it never move. */
.table-status {
  height: 2.6em;
  margin: 0;
  display: grid;
  align-content: end;
  color: var(--felt-text);
  font-size: var(--t-font);
  font-weight: 700;
  line-height: 1.3;
  text-align: center;
  text-wrap: balance;
  overflow: hidden;
}


/* On your turn the pile is a button: tap it to play the cards you picked.
   Once you've picked some it glows and says what you're about to claim. */
/* No padding: the button must be exactly the pile's size, or the rank box
   and count badge (placed from its edges) shift on your turn. A card is
   already a big enough tap target. */
.pile-button {
  padding: 0;
  border: none;
  border-radius: 14px;
  background: none;
  font: inherit;
  cursor: pointer;
  touch-action: manipulation;
}

.pile-button:focus-visible { outline: 3px solid var(--turn); outline-offset: 2px; }

/* The glow follows the pile's messy outline. */
.pile-button.ready .pile-stack,
.pile-button.ready .pile-empty {
  border-color: var(--turn);
  animation: pile-glow 1.2s ease-in-out infinite alternate;
}

@keyframes pile-glow {
  from { filter: drop-shadow(0 0 2px var(--turn)) drop-shadow(0 0 4px var(--turn)); }
  to { filter: drop-shadow(0 0 3px var(--turn)) drop-shadow(0 0 14px var(--turn)); }
}

@media (prefers-reduced-motion: reduce) {
  .pile-button.ready .pile-stack,
  .pile-button.ready .pile-empty {
    animation: none;
    filter: drop-shadow(0 0 3px var(--turn)) drop-shadow(0 0 10px var(--turn));
  }
}

/* A messy face-down pile: cards stacked in one spot, each turned a little
   (the turn comes from cards.js). The latest play is fanned on top. */
.pile-stack {
  position: relative;
  display: block;
  width: var(--pile-w);
  aspect-ratio: 5 / 7;
  margin: 6px 0;
}

.pile-stack { --card-w: var(--pile-w); } /* so faces on the pile scale with it */

/* The empty pile's dashed outline stays under the cards once they're on it
   (every game), peeking out where they're turned. */
.pile-stack::before {
  content: "";
  position: absolute;
  inset: 0;
  border: 2px dashed var(--felt-rim);
  border-radius: 8px;
}
.pile-button.ready .pile-stack::before { border-color: var(--turn); }

.pile-stack .playing-card {
  position: absolute;
  inset: 0;
  width: 100%;
}

.pile-stack .playing-card.latest {
  box-shadow: inset 0 0 0 3px var(--card-face), 0 0 0 2px var(--turn), 0 1px 3px rgb(0 0 0 / 0.25);
}
/* A card turning over on its way to the pile (play.js turnOver): the gold
   edge is on its face and its back, so it turns over with the card. */
.pile-stack .playing-card.latest.fly-turn { box-shadow: none; }
.pile-stack .playing-card.latest.fly-turn > .playing-card {
  box-shadow: inset 0 0 0 3px var(--card-face), 0 0 0 2px var(--turn), 0 1px 3px rgb(0 0 0 / 0.25);
}
.pile-stack .playing-card.latest.fly-turn > .fly-face {
  box-shadow: 0 0 0 2px var(--turn), 0 1px 3px rgb(0 0 0 / 0.25);
}

.pile-button:active { scale: 0.96; }

.table-status.pile-hint { color: var(--turn); }

/* The rank being played, big in the middle of the pile: white with a dark
   outline so it reads over card backs, faces and the empty outline alike.
   It never catches taps, so tapping it still plays your cards. */
.pile-rank {
  position: absolute;
  inset: 0;
  z-index: 5; /* over the cards played onto the pile, landed or still flying */
  display: grid;
  place-items: center;
  pointer-events: none;
  color: #fff;
  font-size: calc(var(--pile-w) * 0.62);
  font-weight: 900;
  line-height: 1;
  -webkit-text-stroke: calc(var(--pile-w) * 0.035) rgb(0 0 0 / 0.75);
  paint-order: stroke fill;
  text-shadow: 0 2px 8px rgb(0 0 0 / 0.5);
}


/* Between the table and your hand: the BS! button's slot, as wide as the
   table's felt (--felt-inset comes from table.js). It keeps its height even
   when empty, so the table doesn't change size when the button appears. */
.game-body .bs-slot {
  flex: none;
  height: clamp(48px, 7.5cqb, 66px);
  margin: 0 var(--felt-inset, 16px);
}

/* The BS! button, always in its slot. During the countdown it's also the
   countdown: a dark track with a red fill that bs.js shrinks from full to
   empty (the right edge moving left). The rest of the time it's grayed out.
   ".button.bs-button" so it beats the general .button style further down. */
.button.bs-button {
  position: relative;
  isolation: isolate; /* keeps the fill behind the label */
  overflow: hidden;
  width: 100%;
  height: 100%;
  min-height: 0;
  border-radius: 16px;
  background: color-mix(in srgb, var(--felt-2) 55%, black);
  box-shadow: inset 0 0 0 3px rgb(255 255 255 / 0.18), 0 4px 16px rgb(0 0 0 / 0.35);
  color: #fff;
  font-size: clamp(1.4rem, 4.5cqb, 2.3rem);
  font-weight: 900;
  letter-spacing: 0.05em;
}

.bs-fill {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--danger-strong);
  transform-origin: left;
}

.bs-label { position: relative; text-shadow: 0 2px 6px rgb(0 0 0 / 0.35); }

/* Grayed out when you can't call: between plays, on your turn, and right
   after your own play (you then still see the countdown, faded). */
.button.bs-button:disabled {
  background: var(--border);
  box-shadow: none;
  color: var(--muted);
}

.button.bs-button:disabled .bs-fill { opacity: 0.35; }
.button.bs-button:disabled .bs-label { text-shadow: none; }

/* Comic-style speech bubbles from a seat: white with a thick black outline
   and a slanted tail from one corner, leaning toward the speaker. The bubble
   sits up and to the side of the speaker (above for the bottom row, below
   for the top row), always leaning toward the middle of the table.
   table.js sets data-side (which table edge) and data-half (left or right
   half), and fades the bubble in and out over time (opacity and scale), so
   there's no CSS animation here. */
.seat-spot.speaking { z-index: 2; }

/* Sized from the table (like everything on it); padding and the tail are
   in em so the whole bubble scales with its text. */
.seat-bubble {
  --ink: #141414;
  --paper: #ffffff;
  --line: 3px;
  --tail-w: 0.8em; /* tail width where it leaves the bubble */
  --tail-h: 1em; /* how far it reaches */
  --tail-x: 0.9em; /* where it leaves, from the bubble's near corner */
  --lean: 0.45; /* how far the tip leans past where it leaves, per unit of reach */
  position: absolute;
  z-index: 1;
  padding: 0.45em 0.85em;
  border: var(--line) solid var(--ink);
  border-radius: 1.1em;
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 4px 12px rgb(0 0 0 / 0.35);
  font-size: clamp(0.85rem, 4.4cqmin, 1.5rem);
  font-weight: 800;
  line-height: 1.1;
  white-space: nowrap;
  width: max-content; /* as wide as its text, wherever it sits in the seat */
}

/* A BS call: the same bubble in red (outline, tail and text). */
.seat-bubble.call {
  --ink: var(--danger-strong);
}

/* The tail: a black triangle with a smaller white one inside it, so the
   outline carries on around the tail. Each is a right triangle (from CSS
   borders) leaned with skewX so its tip slants toward the speaker. */
.seat-bubble::before,
.seat-bubble::after {
  content: "";
  position: absolute;
  width: 0;
  height: 0;
  border-style: solid;
  border-color: transparent;
}

/* Tails hanging down from the bubble's bottom edge (bottom row and sides). */
.seat-bubble.tail-down-left::before,
.seat-bubble.tail-down-left::after,
.seat-bubble.tail-down-right::before,
.seat-bubble.tail-down-right::after { top: 100%; transform-origin: top; }
.seat-bubble.tail-down-left::before,
.seat-bubble.tail-down-right::before { border-top-color: var(--ink); }
.seat-bubble.tail-down-left::after,
.seat-bubble.tail-down-right::after { top: calc(100% - var(--line)); border-top-color: var(--paper); }

/* Tip on the left (speaker below-left): square edge on the left, slant on the right. */
.seat-bubble.tail-down-left::before { left: var(--tail-x); border-width: var(--tail-h) var(--tail-w) 0 0; transform: skewX(-24deg); }
.seat-bubble.tail-down-left::after {
  left: calc(var(--tail-x) + var(--line) * 1.2);
  border-width: calc(var(--tail-h) - var(--line) * 2.6) calc(var(--tail-w) - var(--line) * 2.2) 0 0;
  transform: skewX(-24deg);
}

/* Tip on the right (speaker below-right). */
.seat-bubble.tail-down-right::before { right: var(--tail-x); border-width: var(--tail-h) 0 0 var(--tail-w); transform: skewX(24deg); }
.seat-bubble.tail-down-right::after {
  right: calc(var(--tail-x) + var(--line) * 1.2);
  border-width: calc(var(--tail-h) - var(--line) * 2.6) 0 0 calc(var(--tail-w) - var(--line) * 2.2);
  transform: skewX(24deg);
}

/* Tails reaching up from the bubble's top edge (top row). */
.seat-bubble.tail-up-left::before,
.seat-bubble.tail-up-left::after,
.seat-bubble.tail-up-right::before,
.seat-bubble.tail-up-right::after { bottom: 100%; transform-origin: bottom; }
.seat-bubble.tail-up-left::before,
.seat-bubble.tail-up-right::before { border-bottom-color: var(--ink); }
.seat-bubble.tail-up-left::after,
.seat-bubble.tail-up-right::after { bottom: calc(100% - var(--line)); border-bottom-color: var(--paper); }

.seat-bubble.tail-up-left::before { left: var(--tail-x); border-width: 0 var(--tail-w) var(--tail-h) 0; transform: skewX(24deg); }
.seat-bubble.tail-up-left::after {
  left: calc(var(--tail-x) + var(--line) * 1.2);
  border-width: 0 calc(var(--tail-w) - var(--line) * 2.2) calc(var(--tail-h) - var(--line) * 2.6) 0;
  transform: skewX(24deg);
}

.seat-bubble.tail-up-right::before { right: var(--tail-x); border-width: 0 0 var(--tail-h) var(--tail-w); transform: skewX(-24deg); }
.seat-bubble.tail-up-right::after {
  right: calc(var(--tail-x) + var(--line) * 1.2);
  border-width: 0 0 calc(var(--tail-h) - var(--line) * 2.6) calc(var(--tail-w) - var(--line) * 2.2);
  transform: skewX(-24deg);
}

/* A tail from the middle of the bubble's edge, straight at the speaker. */
.seat-bubble.tail-down-center::before,
.seat-bubble.tail-down-center::after,
.seat-bubble.tail-up-center::before,
.seat-bubble.tail-up-center::after { left: 50%; translate: -50% 0; }
.seat-bubble.tail-down-center::before { top: 100%; border-width: var(--tail-h) calc(var(--tail-h) * 0.56) 0; border-top-color: var(--ink); }
/* The white inside is the same shape, its slanted sides --line in from the
   black ones (so the outline is as thick as the bubble's): with the tail's
   half-width 0.56 of its height, that's 1.05 x --line less height. */
.seat-bubble.tail-down-center::after {
  --inner-h: calc(var(--tail-h) - var(--line) * 1.05);
  top: calc(100% - var(--line));
  border-width: var(--inner-h) calc(var(--inner-h) * 0.56) 0;
  border-top-color: var(--paper);
}
.seat-bubble.tail-up-center::before { bottom: 100%; border-width: 0 calc(var(--tail-h) * 0.56) var(--tail-h); border-bottom-color: var(--ink); }
.seat-bubble.tail-up-center::after {
  --inner-h: calc(var(--tail-h) - var(--line) * 1.05);
  bottom: calc(100% - var(--line));
  border-width: 0 calc(var(--inner-h) * 0.56) var(--inner-h);
  border-bottom-color: var(--paper);
}

/* Where each bubble sits: always above the speaker's circle, in one of
   three styles (table.js sets data-half). Its tail's tip lands on the circle.
   (In the seat's box the circle is centered at 50%, var(--avatar)/2; the box
   is var(--avatar) * 1.65 wide. translate: 0 -100% makes "top" the bubble's
   bottom edge.) */

/* Left and right halves of the table: as wide as the text, hanging out
   away from the middle of the table, the tail landing on the circle and the
   bubble kept on the screen. table.js (placeBubble) sets how far along the
   tail leaves (--tail-x) and the bubble's left or right. */
.seat-spot:is([data-half="left"], [data-half="right"]) .seat-bubble {
  top: calc(var(--avatar) * 0.12 - var(--tail-h));
  translate: 0 -100%;
}
.seat-spot[data-half="left"] .seat-bubble {
  transform-origin: calc(var(--tail-x) - var(--tail-h) * var(--lean)) calc(100% + var(--tail-h));
}
.seat-spot[data-half="right"] .seat-bubble {
  transform-origin: calc(100% - var(--tail-x) + var(--tail-h) * var(--lean)) calc(100% + var(--tail-h));
}

/* The middle seat of the top or bottom row: centered above the circle, the
   tail from the middle of its bottom edge. */
.seat-spot[data-half="center"] .seat-bubble {
  left: 50%;
  top: calc(var(--avatar) * 0.12 - var(--tail-h)); /* same height as the left and right bubbles */
  translate: -50% -100%;
  transform-origin: 50% calc(100% + var(--tail-h));
}

/* The BS reveal (bs.js revealSequence): the played cards slide out of the
   pile into a face-down row under it, flip left to right, and the lies light
   up red. The pile rises first to make room for the row, and settles back
   as it's all picked up. bs.js sets each step's timing inline (from when BS
   was called) and the sizes in px (--lift, --step, --row-gap, --drop,
   --row-scale, --row-h). The animations here only say what each step looks like. */
@keyframes reveal-lift {
  from { translate: 0 0; }
  to { translate: 0 calc(-1 * var(--lift, 0px)); }
}

@keyframes reveal-settle {
  from { translate: 0 calc(-1 * var(--lift, 0px)); }
  to { translate: 0 0; }
}

.reveal-stage {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 2;
  width: 0;
  height: 0;
  pointer-events: none;
}

.reveal-card {
  --card-w: var(--pile-w);
  position: absolute;
  top: 0;
  left: 0;
  width: var(--pile-w);
  aspect-ratio: 5 / 7;
  perspective: 700px;
}

/* From its spot in the messy pile (--px, --py, --pr from cards.js) to its
   place in the row. */
@keyframes reveal-slide {
  from { transform: translate(-50%, -50%) translate(var(--px), var(--py)) rotate(var(--pr)); z-index: var(--zp, auto); }
  2% { z-index: var(--zd, auto); }
  to { transform: var(--in-row); z-index: var(--zd, auto); }
}

/* A card's place in the row (the end of reveal-slide, the start of
   reveal-collect). The last translate is its pickup flight, 0 until then. */
.reveal-card {
  --in-row: translate(-50%, -50%)
    translate(calc((var(--i) - var(--mid)) * var(--step, 0px)), var(--drop, 0px))
    translate(0px, 0px)
    scale(var(--row-scale, 1));
}

.reveal-inner {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
}

@keyframes reveal-flip {
  from { transform: rotateY(0deg); }
  to { transform: rotateY(-180deg); }
}

.reveal-face {
  position: absolute;
  inset: 0;
  border-radius: 8px;
  backface-visibility: hidden;
}

.reveal-face .playing-card { width: 100%; height: 100%; }
.reveal-front { transform: rotateY(180deg); }

/* A card that isn't the claimed rank, at the verdict: it lights up red and
   hops up out of the row (a fifth of its height; bs.js fitRow leaves room). */
@keyframes reveal-lie {
  from { box-shadow: 0 0 0 0 transparent; translate: 0 0; }
  to { box-shadow: 0 0 0 3px var(--danger-strong), 0 0 18px var(--danger-strong); translate: 0 calc(var(--pile-w) * -0.28); }
}

/* A card that is the claimed rank, at the verdict: a green outline. */
@keyframes reveal-true {
  from { box-shadow: 0 0 0 0 transparent; }
  to { box-shadow: 0 0 0 3px #2fa35b, 0 0 14px #2fa35b; }
}

/* Picked up: each card in the row, and the rest of the pile, flies into
   the circle of whoever picks them up, shrinking around its own center as
   it goes (bs.js aimAt sets --to-x/--to-y for each). */
@keyframes reveal-collect {
  from { transform: var(--in-row); }
  80% { opacity: 1; }
  to {
    transform: translate(-50%, -50%)
      translate(calc((var(--i) - var(--mid)) * var(--step, 0px)), var(--drop, 0px))
      translate(var(--to-x, 0px), var(--to-y, 0px))
      scale(calc(var(--row-scale, 1) * 0.3));
    opacity: 0;
  }
}
@keyframes reveal-collect-pile {
  80% { opacity: 1; }
  to { transform: translate(var(--to-x, 0px), var(--to-y, 0px)) scale(0.3); opacity: 0; }
}

.reveal-old {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}

/* Above the pile: "Dan called BS on Cat…", then the verdict in its place,
   sitting next to the pile. */
.revealing .table-above {
  width: clamp(200px, 86cqi, 420px); /* wider, so the verdict fits on one line where it can */
}

.reveal-line > span {
  grid-area: 1 / 1;
  align-self: end;
}

.reveal-verdict.caught { color: #ffb4a9; }
.reveal-verdict.truthful { color: #bff0cf; }

@keyframes reveal-out {
  to { opacity: 0; }
}

@keyframes reveal-in {
  from { opacity: 0; scale: 0.85; }
  to { opacity: 1; scale: 1; }
}

/* With less motion: same steps at the same moments, without the movement. */
@media (prefers-reduced-motion: reduce) {
  .revealing,
  .reveal-card,
  .reveal-inner,
  .reveal-front,
  .reveal-old,
  .reveal-line > span { animation-duration: 1ms !important; }
}

.card-row {
  --card-w: 50px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px;
}

.mini-back {
  width: 11px;
  height: 15px;
  border-radius: 2px;
  background: repeating-linear-gradient(45deg, var(--card-back) 0 2px, var(--card-back-2) 2px 4px);
}

/* Buttons and forms: everything tappable is at least 48px tall. */
.button {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0 28px;
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  touch-action: manipulation;
}

.button.secondary {
  background: transparent;
  color: var(--accent);
  border: 2px solid var(--accent);
}

.button:disabled {
  background: var(--border);
  color: var(--muted);
  cursor: default;
}

.button:focus-visible,
.link-button:focus-visible,
.input:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

.link-button {
  min-height: 44px;
  padding: 0 8px;
  border: none;
  background: none;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.link-button.danger { color: var(--danger); }
.link-button.center { display: block; margin: 16px auto 0; }

h2 { font-size: 1.1rem; margin: 0; }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 16px;
}

.form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 16px;
}

.label { font-weight: 600; }

/* 16px or larger, or iPhones zoom in when the field is tapped. */
.input {
  width: 100%;
  min-height: 48px;
  padding: 0 14px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  font-size: 1.05rem;
}

.code-input {
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-weight: 700;
}

.row { display: flex; gap: 8px; }
.row .input { flex: 1; min-width: 0; }

.join-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 28px;
}

/* The home page's two sections: "Join By Room Code:" and "Or Host A Game:". */
.home-heading { font-size: 1.3rem; margin: 0 0 12px; }

.form-error { color: var(--danger); margin: 0; min-height: 0; }

/* A game's options on Create room (screens/game.js), under the name: a
   checkbox per option, its name, and what it does in small text below. */
.options {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 6px 0 0;
  padding: 0;
  border: none;
}
.options > legend { padding: 0; margin-bottom: 10px; }
.option { display: flex; align-items: flex-start; gap: 12px; cursor: pointer; }
.option-box { flex: none; width: 22px; height: 22px; margin: 1px 0 0; accent-color: var(--accent); }
.option-text { display: flex; flex-direction: column; gap: 2px; }
.option-name { font-weight: 600; }
.option-help { color: var(--muted); font-size: 0.85rem; line-height: 1.35; }
/* In the lobby, for everyone but the host: shown, but greyed out. */
.options:disabled .option { cursor: default; opacity: 0.55; }
.room-content > .options { margin-bottom: 12px; }

/* A pop-up over the page (the "leave this room?" question on back). */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: grid;
  place-items: center;
  padding: 16px max(16px, env(safe-area-inset-right, 0px)) 16px max(16px, env(safe-area-inset-left, 0px));
  background: rgb(0 0 0 / 0.5);
}

.modal {
  width: min(100%, 360px);
  display: grid;
  gap: 12px;
  box-shadow: 0 12px 32px rgb(0 0 0 / 0.35);
}

.modal h2 { font-size: 1.2rem; }
.modal p { margin: 0; color: var(--muted); }
.modal-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 4px; }

.button.danger-button { background: var(--danger-strong); color: #fff; }
.form-error:empty { display: none; }

.notice {
  margin: 0;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--bg);
}

.muted { color: var(--muted); }
.small { font-size: 0.9rem; }
.center { text-align: center; }

/* Room */
.eyebrow {
  margin: 0;
  text-align: center;
  color: var(--muted);
  font-size: 0.85rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.room-code {
  margin: 4px 0 16px;
  text-align: center;
  font-size: clamp(2.5rem, 15vw, 3.5rem);
  font-weight: 800;
  letter-spacing: 0.25em;
  padding-left: 0.25em; /* balances the trailing letter-spacing */
  font-variant-numeric: tabular-nums;
}

/* A game's page: the Create room form, then How to play with the player
   count in its top-right corner (the game's name is in the banner). */
.how-to-play { display: grid; gap: 10px; }

.how-to-play-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.how-to-play ul { margin: 0; padding-left: 1.1em; display: grid; gap: 10px; line-height: 1.45; }

/* Copies the game link (the link itself isn't shown). */
.invite-button { width: 100%; margin-bottom: 24px; }

.player-list {
  list-style: none;
  margin: 10px 0 20px;
  padding: 0;
  display: grid;
  gap: 8px;
}

.player {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 56px;
  padding: 8px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
}

.player.empty {
  border-style: dashed;
  background: transparent;
  color: var(--muted);
}

.avatar {
  flex: none;
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 700;
}

.player.empty .avatar { background: var(--border); }

.player-name {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.pill {
  flex: none;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--bg);
  font-size: 0.8rem;
  font-weight: 600;
}

.pill.away { background: var(--away-bg); color: var(--away-fg); }

.actions { display: grid; gap: 12px; }
.actions .card { display: grid; gap: 12px; }
.actions .card p { margin: 0; }

/* Shown while the connection is down; actions pause until it's back. */
.conn-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  padding: 12px max(16px, env(safe-area-inset-right, 0px)) calc(12px + env(safe-area-inset-bottom, 0px)) max(16px, env(safe-area-inset-left, 0px));
  background: var(--away-bg);
  color: var(--away-fg);
  text-align: center;
  font-weight: 600;
}

.offline button { pointer-events: none; opacity: 0.5; }

.offscreen { position: fixed; left: -9999px; }

/* Full-screen game: while a game is running the page doesn't scroll. The
   game bar sits on top, the table takes whatever height is left, and the
   status and your hand stay pinned at the bottom. */
body.in-game {
  height: 100vh;
  height: 100dvh;
  /* No minimum: a min-height wins over height, and any taller than what's
     visible pushes the top or bottom of the game under the address bar. */
  min-height: 0;
  overflow: hidden;
  overscroll-behavior: none;
}

body.in-game .topbar { display: none; }

/* Nothing drawn in the game (like a meld effect reaching past the screen)
   may make the page bigger than the screen: iPhones zoom the whole page out
   to fit when it does. Clipped here, at the screen's edges rather than the
   page margins, so speech bubbles can reach right out to them. */
body.in-game main {
  overflow: clip;
  height: 100%;
  display: flex;
  flex-direction: column;
  padding-top: calc(4px + env(safe-area-inset-top, 0px));
  padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px));
}

body.in-game .room-content {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* In a game, the bar above the table holds just the ⋮ menu, at the right,
   kept short so the table gets the height. The button keeps a full 44px tap
   area by overhanging the bar a little. */
/* The bar's text (and everything in it, sized in em) grows with the
   screen, like the table: about 1rem on a phone, up to 1.6rem on a tablet. */
.game-bar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  height: 2em;
  font-size: clamp(1rem, 0.55rem + 2.2vmin, 1.6rem);
}

/* A game's short status on the left of the bar (e.g. The Mind's level). */
.game-bar-text {
  margin: 0 auto 0 0;
  font-weight: 700;
  color: var(--muted);
}

.game-menu { position: relative; flex: none; margin-right: -10px; }

/* The Mind's lives, on the right of the bar: a heart and the number. It
   shakes when a life is lost and pops when one is earned (mind.js sets
   --late, so a redraw carries on rather than starts over). */
.lives {
  --late: 0ms;
  display: flex;
  align-items: center;
  gap: 0.3em;
  margin: 0 0.25em 0 0;
  font-weight: 700;
  font-size: 1.05em;
  font-variant-numeric: tabular-nums;
}
.lives-heart { width: 1.3em; height: 1.3em; fill: #e53935; }
.lives-lost .lives-heart { animation: heart-lost 900ms ease-out var(--late) both; }
.lives-gained .lives-heart { animation: heart-gained 900ms ease-out var(--late) both; }
@keyframes heart-lost {
  0%, 100% { translate: 0; scale: 1; }
  10% { scale: 1.3; }
  20% { translate: -4px 0; }
  35% { translate: 4px 0; }
  50% { translate: -3px 0; }
  65% { translate: 2px 0; }
  80% { translate: 0; }
}
@keyframes heart-gained {
  0% { scale: 1; }
  30% { scale: 1.45; }
  60% { scale: 0.9; }
  100% { scale: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .lives-heart { animation: none !important; }
}

/* The Mind: a life earned, a big "+" and heart under the "Level N" banner;
   the heart then flies up into the lives in the bar (mind.js bonusLifeEl
   sets the timing and --to-x/--to-y/--to-scale). */
.bonus-life {
  position: absolute;
  top: calc(50% + var(--pile-w) * 0.55);
  left: 50%;
  z-index: 5;
  translate: -50% 0;
  display: flex;
  align-items: center;
  gap: 4px;
  pointer-events: none;
}
.bonus-plus {
  color: #fff;
  font-size: calc(var(--pile-w) * 0.75);
  font-weight: 900;
  line-height: 1;
  -webkit-text-stroke: calc(var(--pile-w) * 0.03) rgb(0 0 0 / 0.6);
  paint-order: stroke fill;
  text-shadow: 0 4px 16px rgb(0 0 0 / 0.5);
}
.bonus-icon {
  width: calc(var(--pile-w) * 0.8);
  height: calc(var(--pile-w) * 0.8);
  filter: drop-shadow(0 4px 10px rgb(0 0 0 / 0.45));
}
.bonus-heart { fill: #e53935; }
.bonus-star { fill: var(--star); }

/* The Mind's shurikens in the bar, before the lives (barEnd). */
.bar-counts { display: flex; align-items: center; gap: 0.7em; }
.stars-icon { width: 1.3em; height: 1.3em; fill: var(--star); }
.stars-gained .stars-icon { animation: star-gained 900ms ease-out var(--late) both; }
@keyframes star-gained {
  0% { scale: 1; rotate: 0deg; }
  40% { scale: 1.45; }
  100% { scale: 1; rotate: 360deg; }
}

/* The Mind's Use Shuriken button: BS's BS! button, in blue. Each vote fills
   it a little more from the left (mind.js starSlotEl). Once you've voted it
   stays down, still blue, and can't be pressed again. */
.star-fill { background: var(--star); transition: transform 300ms ease-out; }
.button.star-button.voted:disabled {
  background: color-mix(in srgb, var(--felt-2) 55%, black);
  box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--star) 70%, white);
  color: #fff;
}
.button.star-button.voted:disabled .bs-fill { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .star-fill, .stars-gained .stars-icon { transition: none; animation: none; }
}
@keyframes bonus-in {
  from { opacity: 0; scale: 0.7; }
}
@keyframes bonus-plus-out {
  to { opacity: 0; }
}
@keyframes bonus-fly {
  90% { opacity: 1; }
  to { translate: var(--to-x, 0) var(--to-y, 0); scale: var(--to-scale, 0.3); opacity: 0.9; }
}
@media (prefers-reduced-motion: reduce) {
  .bonus-life, .bonus-life * { animation-duration: 1ms !important; }
}

.menu-button {
  width: max(44px, 2.4em);
  height: max(44px, 2.4em);
  display: grid;
  place-items: center;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--fg);
  font: inherit;
  font-size: 1.45em;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
}

.menu-button[aria-expanded="true"],
.menu-button:active { background: var(--border); }
.menu-button:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

.menu-list {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 5;
  min-width: 190px;
  padding: 6px;
  display: grid;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.2);
}

.menu-item {
  min-height: 48px;
  padding: 0 14px;
  border: none;
  border-radius: 8px;
  background: none;
  color: var(--fg);
  font: inherit;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.menu-item:active { background: var(--bg); }
.menu-item:focus-visible { outline: 3px solid var(--accent); outline-offset: -3px; }
.menu-item.danger { color: var(--danger); }

/* The game area is measured (container-type) so things in it can be sized
   from its width (cqi) and height (cqb). Cards in your hand are a share of
   the width, but never so tall they crowd the table on a short screen. */
.game-body {
  --card-w: clamp(54px, min(19cqi, 12.5cqb), 112px);
  container-type: size;
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Nothing that comes and goes may change the table's size, so these float
   on top instead of taking space: errors near the top, and the end-of-game
   buttons as a card over the table. */
.game-body > .form-error {
  position: absolute;
  top: 4px;
  left: 0;
  right: 0;
  z-index: 4;
  padding: 10px 12px;
  border-radius: 10px;
  background: var(--surface);
  box-shadow: 0 4px 16px rgb(0 0 0 / 0.25);
}

/* The end of a game, on the table (see endingFor in room.js): the result
   just above the pile, the buttons just under it. */
.end-title {
  position: absolute;
  bottom: calc(100% + clamp(8px, 2.5cqmin, 16px));
  left: 50%;
  translate: -50% 0;
  z-index: 6; /* over everything on the table: the seats, cards fanned at them, cards in flight */
  margin: 0;
  color: var(--felt-text);
  font-size: calc(var(--t-font) * 1.6);
  font-weight: 900;
  white-space: nowrap;
  text-shadow: 0 2px 6px rgb(0 0 0 / 0.45);
  animation: end-in 300ms ease-out var(--since, 0ms) both;
}

/* BS: the rematch/leave buttons over the BS! button and your hand, which
   stay where they were (hidden) so the table doesn't move. */
.game-body .end-zone { position: relative; flex: none; display: flex; flex-direction: column; gap: 8px; }
.end-zone > :not(.end-cover) { visibility: hidden; }
.end-cover { position: absolute; inset: 0; display: grid; align-content: center; }

/* The Mind: the buttons where your hand was; the slot keeps the hand's
   height (see .game-body .hand) so the table doesn't move. */
.game-body .end-slot {
  min-height: calc(max(20px, var(--card-w) * 0.24) + var(--card-w) * 1.4 + 16px);
  display: grid;
  align-content: center;
}

/* A win: the letters do the wave, rising and falling one after another. */
.end-title .wave-ch { display: inline-block; }
.end-title:not(.lost) .wave-ch { animation: wave 1.6s ease-in-out calc(var(--i) * 80ms + 300ms + var(--since, 0ms)) infinite; }
@keyframes wave {
  0%, 40%, 100% { translate: 0 0; }
  20% { translate: 0 -0.35em; }
}
@media (prefers-reduced-motion: reduce) {
  .end-title .wave-ch { animation: none; }
}

/* The Mind: green for a level passed or a win, red for a loss. */
/* The Mind's "Life Lost!": pops in, then shakes like the heart in the bar
   (heart-lost), around its centered spot. */
.end-title.life-lost { animation: end-in 300ms ease-out both, title-shake 900ms ease-out both; }
/* (--title-y: where it sits up or down, kept while it shakes.) */
@keyframes title-shake {
  0%, 80%, 100% { translate: -50% var(--title-y, 0); }
  20% { translate: calc(-50% - 4px) var(--title-y, 0); }
  35% { translate: calc(-50% + 4px) var(--title-y, 0); }
  50% { translate: calc(-50% - 3px) var(--title-y, 0); }
  65% { translate: calc(-50% + 2px) var(--title-y, 0); }
}
/* A smaller line under a title (Hard Mode's "Retrying Level"). */
.end-title .title-sub {
  display: block;
  margin-top: 0.15em;
  color: var(--felt-text);
  font-size: 0.55em;
  font-weight: 800;
  text-align: center;
}

.end-title.passed { color: #8ff0b0; }
.end-title.lost { color: #ff8f80; }

/* The Mind's meld: tendrils from each player who is ready to the pile
   (drawn on a canvas, see drawDrain in mind.js), a soft core where they meet,
   and a star flash on the card once they have all flowed in. */
/* Layered over the card outline in the middle, but under the meld text and
   the card range, which stay readable through it. */
.meld-drain { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; pointer-events: none; }
.meld-drain ~ .table-center :is(.table-below, .table-above) { z-index: 2; }

/* The meld finishing: the card outline in the middle flashes bright white
   and a four-pointed star glints from its center, turning a little as it
   grows and fades. Only transform and opacity animate, which phones draw
   smoothly. mind.js sets --late (a wait for the tendrils, or how far along
   a flash a redraw caught). */
.meld-star {
  --late: 0ms;
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 4;
  width: 0;
  height: 0;
  pointer-events: none;
}
.meld-star > * { position: absolute; will-change: transform, opacity; }
/* The card's silhouette, lit up. */
.star-card {
  left: calc(var(--pile-w) / -2);
  top: calc(var(--pile-w) * -0.7);
  width: var(--pile-w);
  height: calc(var(--pile-w) * 1.4);
  border-radius: 8px;
  background: rgb(255 255 255 / 0.9);
  box-shadow: 0 0 18px 6px rgb(255 255 255 / 0.85), 0 0 46px 14px rgb(255 245 220 / 0.55);
  animation: star-card 900ms ease-out var(--late) both;
}
@keyframes star-card {
  0% { opacity: 0; transform: scale(0.96); }
  15% { opacity: 1; transform: scale(1.04); }
  100% { opacity: 0; transform: scale(1); }
}
/* The star: two long thin rays crossing (a vertical one, and a horizontal
   one drawn by the ::after), bright in the middle and fading to their tips. */
.star-rays {
  --len: calc(var(--pile-w) * 2.6);
  left: calc(var(--len) / -2);
  top: calc(var(--len) / -2);
  width: var(--len);
  height: var(--len);
  animation: star-rays 1000ms cubic-bezier(0.2, 0.7, 0.3, 1) calc(var(--late) + 60ms) both;
}
.star-rays::before,
.star-rays::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  width: 6%;
  height: 100%;
  translate: -50% 0;
  border-radius: 50%;
  background: radial-gradient(ellipse at center, #fff 0%, rgb(255 255 255 / 0.9) 18%, rgb(255 240 210 / 0.35) 45%, transparent 70%);
}
.star-rays::after { rotate: 90deg; }
/* A smaller pair on the diagonals. */
.star-rays.small { --len: calc(var(--pile-w) * 1.4); rotate: 45deg; opacity: 0.8; animation-delay: calc(var(--late) + 120ms); }
@keyframes star-rays {
  0% { opacity: 0; transform: scale(0.1) rotate(-20deg); }
  20% { opacity: 1; transform: scale(1) rotate(0deg); }
  100% { opacity: 0; transform: scale(0.6) rotate(25deg); }
}

/* The hint under the pile waits for the "Level N" banner to go. */
/* The Mind's meld, until you've tapped: "Tap to Meld" in three big lines
   filling the card's outline, over the tendrils, fading in and out
   (mind.js meldPile delays it until the "Level N" banner has gone). */
.meld-tap {
  position: absolute;
  inset: 0;
  z-index: 2; /* over the tendrils' canvas */
  display: grid;
  align-content: center;
  justify-items: center;
  color: #fff;
  font-size: calc(var(--pile-w) * 0.35);
  font-weight: 900;
  line-height: 1.05;
  -webkit-text-stroke: calc(var(--pile-w) * 0.018) rgb(0 0 0 / 0.6);
  paint-order: stroke fill;
  text-shadow: 0 2px 8px rgb(0 0 0 / 0.5);
  pointer-events: none;
  animation: meld-tap 1.8s ease-in-out infinite both;
}
@keyframes meld-tap {
  0%, 100% { opacity: 0; }
  50% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .meld-tap { animation: none; }
}

@media (prefers-reduced-motion: reduce) {
  .meld-star > * { animation-duration: 1ms !important; }
}

/* The Mind: "Level 3", big over the pile as the level starts (fades away). */
.level-banner {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 3;
  margin: 0;
  translate: -50% -50%;
  color: #fff;
  font-size: calc(var(--pile-w) * 0.7);
  font-weight: 900;
  white-space: nowrap;
  pointer-events: none;
  -webkit-text-stroke: calc(var(--pile-w) * 0.03) rgb(0 0 0 / 0.6);
  paint-order: stroke fill;
  text-shadow: 0 4px 16px rgb(0 0 0 / 0.5);
}

.end-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  width: min(100%, 420px);
  margin: 0 auto;
  animation: end-in 300ms ease-out var(--since, 0ms) both;
}
/* Host: Play again and Back to lobby side by side; Leave under them,
   centered (on its own, for everyone else). */
/* Same size as the host's row of buttons, so both layouts match. */
.end-waiting {
  grid-column: 1 / -1;
  min-height: 48px;
  margin: 0;
  display: grid;
  place-items: center;
  color: var(--muted);
  font-weight: 600;
  text-align: center;
}
.end-leave { grid-column: 1 / -1; justify-self: center; width: calc(50% - 5px); }
.end-actions .button { min-height: 48px; }

@keyframes end-in {
  from { opacity: 0; scale: 0.9; }
}

@media (prefers-reduced-motion: reduce) {
  .end-title, .end-title.life-lost, .end-actions { animation: none; }
}

.game-actions h2 { font-size: 1.25rem; }



/* The table takes all the height left above your hand, and is measured
   (container-type) so everything on it can be sized from it. */
.game-body .table-area {
  flex: 1 1 0;
  min-height: 180px;
  container-type: size;
}

.game-body .my-hand { flex: none; margin-top: 0; }
/* Same height whether or not cards can be tapped, and even with no cards left. */
.game-body .hand,
.game-body .hand:has(button.hand-slot) {
  --lift-room: max(20px, var(--card-w) * 0.24); /* room for picked cards to lift */
  padding-top: var(--lift-room);
  min-height: calc(var(--lift-room) + var(--card-w) * 1.4 + 16px);
}

/* ---------- Stupid Head (stupidhead.js) ---------- */

/* The draw pile and the pile, the same size, side by side in the middle of
   the table: the gap between them is on the table's middle line. */
.sh-middle {
  display: flex;
  align-items: center;
  gap: clamp(10px, 4cqi, 22px);
}
.sh-deck {
  position: relative;
  --card-w: var(--pile-w);
  margin: 6px 0; /* as .pile-stack, so the two line up */
}
.sh-deck.gone { visibility: hidden; }
.sh-deck .pile-count { z-index: 5; box-shadow: 0 0 0 1.5px #000; }

/* In front of each seat: a face-up card on its face-down one, the face-down
   card peeking out toward the seat. */
.seat-cards .playing-card > .playing-card {
  left: 0;
  bottom: auto;
  top: -16%;
  width: 100%;
  rotate: none;
  transform-origin: 50% 50%;
}
.sh-gap { visibility: hidden; }

/* Yours can be tapped there: the ones you can tap right now are edged in
   the turn color, and face-up cards you pick slide out from your circle. */
.seat-cards .sh-tap {
  pointer-events: auto;
  padding: 0;
  font: inherit;
  cursor: pointer;
  touch-action: manipulation;
  transition: bottom 0.12s ease;
}
.seat-cards .sh-tap:not(:has(.playing-card)),
.seat-cards .sh-tap > .playing-card {
  box-shadow: 0 0 0 2px var(--turn), 0 1px 3px rgb(0 0 0 / 0.3);
}
.seat-cards .sh-tap.selected {
  --pick: calc(var(--card-w) * 0.35);
}
.seat-cards .sh-tap.selected > .playing-card {
  box-shadow: 0 0 0 3px var(--accent), 0 4px 10px rgb(0 0 0 / 0.3);
}
.seat-cards .sh-tap:focus-visible { outline: none; }
.seat-cards .sh-tap:focus-visible > .playing-card,
.seat-cards .sh-tap:focus-visible:not(:has(.playing-card)) { outline: 3px solid var(--accent); outline-offset: 2px; }

/* Ready, under the pile while swapping. */
.table-below .button.sh-action {
  width: min(100%, 220px);
  min-height: 44px;
  border-radius: 14px;
  background: color-mix(in srgb, var(--felt-2) 55%, black);
  box-shadow: inset 0 0 0 3px rgb(255 255 255 / 0.18), 0 4px 16px rgb(0 0 0 / 0.35);
  color: #fff;
  font-size: clamp(1rem, 3cqb, 1.4rem);
  font-weight: 900;
  letter-spacing: 0.03em;
}
.table-below .button.sh-action:disabled {
  background: var(--border);
  box-shadow: none;
  color: var(--muted);
}
