/* The site's own font, the same on every device (never the phone's or
   computer's): Inter for text, and for the few symbols it lacks, small
   cuts of Noto Sans Symbols 2 (suits, shapes, ✓) and Noto Sans Math (∞, ⋮,
   ←, Ω), all in client/fonts. Each face claims every weight, so the browser
   never makes up a bold of its own. font-display: block: no flash of some
   other font while it loads. */
@font-face {
  font-family: "BBG";
  src: url("/fonts/inter-latin-wght.woff2") format("woff2");
  font-weight: 100 900;
  font-display: block;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "BBG";
  src: url("/fonts/noto-sans-symbols-2-subset.woff2") format("woff2");
  font-weight: 100 900;
  font-display: block;
  unicode-range: U+2660-2667, U+25CF, U+25B2, U+25BC, U+25C6, U+2605, U+2713;
}
@font-face {
  font-family: "BBG";
  src: url("/fonts/noto-sans-math-subset.woff2") format("woff2");
  font-weight: 100 900;
  font-display: block;
  unicode-range: U+221E, U+22EE, U+2190, U+2192, U+03A9;
}

/* --u: one design pixel (see ui/scale.js). Every size here is so many of
   them, so a game is the same picture on every screen, only bigger or
   smaller. A pixel outside a game; the script sets it in one. */
:root { --u: 1px; --box-w: 100vw; --box-h: 100vh; }
/* (Text sizes too: never the browser's own text size, so nothing changes
   from one device to another.) */
html { font-size: calc(16 * var(--u)); }

/* The colours: one dark look, whatever the device is set to. */
: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; }
/* (Buttons and text boxes don't take the page's font unless told to; and
   the browser's own size for them, 13.33px, scaled like everything else.
   :where(): any class still sets its own.) */
:where(button, input, select, textarea) { font-family: inherit; font-size: calc(13.333 * var(--u)); }

/* 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: calc(1 * var(--u));
  height: calc(1 * var(--u));
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* An emoji from the site's own set (ui/emoji.js): text-sized. */
.emoji { display: inline-block; width: 1em; height: 1em; aspect-ratio: 1; vertical-align: -0.125em; }
/* (Drawn into the page, see ui/emoji.js: its picture fills it.) */
span.emoji > svg { display: block; width: 100%; height: 100%; }

/* 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: "BBG", sans-serif;
  font-variant-emoji: text; /* (♥ and the like as our font draws them, never the phone's emoji) */
  font-synthesis: none;
  /* 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);
  /* (The title lines up with the page below: its 16px in from the page's
     edge, wherever the page sits.) */
  --page-gap: max(calc(16 * var(--u)), (100% - (var(--page-cols) * 343 + (var(--page-cols) - 1) * 12 + 32) * var(--u)) / 2 + 16 * var(--u));
  padding: calc(calc(14 * var(--u)) + env(safe-area-inset-top, 0px)) max(var(--page-gap), env(safe-area-inset-right, 0px)) calc(14 * var(--u)) max(var(--page-gap), env(safe-area-inset-left, 0px));
}

.brand {
  color: var(--on-accent);
  font-weight: 700;
  font-size: calc(18.4 * var(--u));
  text-decoration: none;
}

/* The page: the phone's width (375 design px, padding included), in the
   middle of anything wider. Pages with a list of cards (the game list) or
   two halves (the lobby) take more columns where they fit: --page-cols
   columns of the phone's 343, 12 apart, out of the --cols that fit the
   screen (ui/scale.js; at most 3). Each column is the phone's exact width,
   so a card looks the same however many sit side by side. */
:root { --cols: 1; --page-cols: 1; }
html:has(main .game-list) { --page-cols: var(--cols); }
html:has(main .lobby-grid) { --page-cols: var(--cols); }
main {
  max-width: calc((var(--page-cols) * 343 + (var(--page-cols) - 1) * 12 + 32) * var(--u));
  margin: 0 auto;
  padding: calc(20 * var(--u)) max(calc(16 * var(--u)), env(safe-area-inset-right, 0px)) calc(96 * var(--u)) max(calc(16 * var(--u)), env(safe-area-inset-left, 0px));
}

h1 { font-size: calc(24 * var(--u)); margin: 0 0 calc(16 * var(--u)); }

/* 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;
  /* (As many phone-wide columns as the page has: see main.) */
  grid-template-columns: repeat(var(--page-cols), minmax(0, calc(343 * var(--u))));
  gap: calc(12 * var(--u));
}
/* Beside a game list, everything else on the page keeps to one column. */
:is(main, .room-content):has(> .game-list) > :not(.game-list) { max-width: calc(343 * var(--u)); }

/* The lobby: one part after another, as on a phone. Where more columns
   fit, the room and game at the top and the buttons at the bottom stay one
   phone-wide column in the middle, and the players and spectators lists
   spread into the columns (as the game list does). */
.lobby-grid { display: block; }
/* The lobby's captions (Game, Starting mission, Players, Spectators), centered. */
.lobby-grid h2 { text-align: center; }
.lobby-grid > :is(.lobby-top, .lobby-bottom) { max-width: calc(343 * var(--u)); margin-inline: auto; }
/* The game and its options: one under the other on a phone; side by side
   where two columns fit (the game alone in the middle if it has none). */
.lobby-settings { max-width: calc(343 * var(--u)); margin-inline: auto; }
html:not([data-cols="1"]) .lobby-settings {
  max-width: none;
  display: grid;
  grid-template-columns: repeat(auto-fit, calc(343 * var(--u)));
  justify-content: center;
  column-gap: calc(12 * var(--u));
}
/* Side by side, the options as tall as the game (if its description wraps). */
html:not([data-cols="1"]) .lobby-settings > * { display: flex; flex-direction: column; }
html:not([data-cols="1"]) .lobby-settings .game-pick { flex: 1; }
.lobby-grid .player-list {
  grid-template-columns: repeat(var(--page-cols), minmax(0, calc(343 * var(--u))));
  justify-content: center;
}

.game-card {
  position: relative;
  display: flex;
  align-items: center;
  gap: calc(16 * var(--u));
  padding: calc(16 * var(--u));
  background: var(--surface);
  border: calc(1 * var(--u)) solid var(--border);
  border-radius: calc(12 * var(--u));
  color: inherit;
  text-decoration: none;
}

.game-card:active { transform: scale(0.98); }
.game-card:focus-visible { outline: calc(3 * var(--u)) solid var(--accent); outline-offset: calc(2 * var(--u)); }
/* (As a button: the lobby's game row and the list to pick from.) */
button.game-card { width: 100%; font: inherit; text-align: left; cursor: pointer; }
button.game-card:disabled { opacity: 0.5; cursor: default; transform: none; }
.game-card.current { border-color: var(--accent); box-shadow: inset 0 0 0 calc(1 * var(--u)) var(--accent); }

/* The lobby's game row, under its "Game" heading: the game's name over its
   blurb, and a chevron (for the host, who taps it to pick another). */
.lobby-heading { margin-top: calc(4 * var(--u)); }
.game-card.game-pick { margin: calc(8 * var(--u)) 0 calc(16 * var(--u)); padding: calc(12 * var(--u)) calc(16 * var(--u)); }
.game-pick .game-info { flex: 1; min-width: 0; display: grid; gap: calc(2 * var(--u)); }
.game-pick-chevron { font-size: calc(25.6 * var(--u)); line-height: 1; color: var(--muted); }
/* A choice (e.g. The Crew's mission): the same row, without an icon. */
.game-card.choice-pick { padding: calc(12 * var(--u)) calc(16 * var(--u)); min-height: calc(82 * var(--u)); }

/* Picking from a choice's list once a game is over: the list over the
   whole screen, scrolling on its own. */
.choice-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: max(calc(16 * var(--u)), env(safe-area-inset-top, 0px)) calc(16 * var(--u)) max(calc(24 * var(--u)), env(safe-area-inset-bottom, 0px));
  background: var(--bg);
}
.choice-overlay > * { max-width: calc(560 * var(--u)); margin-left: auto; margin-right: auto; }

.game-icon {
  flex: none;
  width: calc(56 * var(--u));
  height: calc(56 * var(--u));
  display: grid;
  place-items: center;
  border-radius: calc(10 * var(--u));
  background: var(--bg);
  font-size: calc(25.6 * var(--u));
  font-weight: 800;
}

.game-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: calc(8 * var(--u)); }

/* 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: calc(8 * var(--u));
}

.game-name { font-weight: 700; font-size: calc(17.6 * var(--u)); }
/* One line; cut off with "…" if a screen is too narrow for it. */
.game-meta {
  overflow: hidden;
  color: var(--muted);
  font-size: calc(14.4 * var(--u));
  text-overflow: ellipsis;
  white-space: nowrap;
}

.game-players {
  flex: none;
  padding: calc(2 * var(--u)) calc(10 * var(--u));
  border-radius: calc(999 * var(--u));
  background: var(--bg);
  color: var(--muted);
  font-size: calc(12.8 * var(--u));
  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: calc(12 * var(--u));
}

.game-actions { margin-top: calc(24 * var(--u)); }

/* Cards: drawn with HTML and CSS, sized by --card-w. */
:root { --card-w: calc(74 * var(--u)); }

.playing-card {
  position: relative;
  display: block;
  flex: none;
  width: var(--card-w);
  aspect-ratio: 5 / 7;
  border: calc(1 * var(--u)) solid var(--card-edge);
  /* (In step with the card's size, so a big card on a tablet is as round
     as a small one on a phone: 8px on a 74px card.) */
  border-radius: max(calc(3 * var(--u)), calc(var(--card-w) * 0.108));
  background: var(--card-face);
  color: var(--card-black);
  box-shadow: 0 calc(1 * var(--u)) calc(3 * var(--u)) 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: calc(1 * var(--u));
  font-size: max(calc(12 * var(--u)), calc(var(--card-w) * 0.235));
  line-height: 1;
}

/* The same in the bottom right corner, turned upside down, as on a real
   card: still there when the top left is covered. */
.card-corner.end {
  top: auto;
  left: auto;
  right: calc(var(--card-w) * 0.075);
  bottom: calc(var(--card-w) * 0.075);
  rotate: 180deg;
}

.card-pip {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: calc(var(--card-w) * 0.45);
  line-height: 1;
}

/* A card back (every game): its stripes and white edge are sized from the
   card, so a back looks the same at any size, just scaled (a card shrinking
   or growing as it flies keeps its stripes). The white edge is an inset
   shadow, --back-edge: anything else giving a back a shadow (an outline
   when it's picked) has to keep it, or the stripes spread to the edge. */
.playing-card.back {
  --back-edge: inset 0 0 0 max(calc(1.5 * var(--u)), var(--card-w) * 0.07) var(--card-face);
  background: repeating-linear-gradient(45deg, var(--card-back) 0 calc(var(--card-w) * 0.18), var(--card-back-2) calc(var(--card-w) * 0.18) calc(var(--card-w) * 0.36));
  box-shadow: var(--back-edge), 0 calc(1 * var(--u)) calc(3 * var(--u)) 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: calc(4 * var(--u)) calc(4 * var(--u)) calc(12 * var(--u));
  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 */
  /* (Not stretched to the row's height: a button centers what's in it, so a
     tappable card would sit a little lower than the same card not tappable,
     and the hand would shift down whenever it became your turn.) */
  align-self: flex-start;
}

/* 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 calc(3 * var(--u)) var(--accent), 0 calc(4 * var(--u)) calc(10 * var(--u)) rgb(0 0 0 / 0.3);
}
.hand-slot.selected .playing-card.back { box-shadow: var(--back-edge), 0 0 0 calc(3 * var(--u)) var(--accent), 0 calc(4 * var(--u)) calc(10 * var(--u)) rgb(0 0 0 / 0.3); }

button.hand-slot:focus-visible { outline: none; }
button.hand-slot:focus-visible .playing-card { outline: calc(3 * var(--u)) solid var(--accent); outline-offset: calc(2 * var(--u)); }

.hand:has(button.hand-slot) { padding-top: calc(20 * var(--u)); }

@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; }

/* A card flying into your hand, or a bundle of them (e.g. a pile picked
   up): a copy over the whole game screen, under pop-ups (.modal-backdrop),
   so the hand row (which scrolls, so cuts off what's outside it) can't
   hide it (hand-motion.js). */
.playing-card.hand-ghost,
.bundle-ghost {
  position: fixed;
  z-index: 15;
  margin: 0;
  pointer-events: none;
}
.bundle-ghost > .pile-stack { margin: 0; }
/* (The empty pile's outline stays on the table; only the cards fly.) */
.bundle-ghost > .pile-stack::before { display: none; }

.my-hand { margin-top: calc(20 * var(--u)); }
.my-hand h2 { margin-bottom: calc(8 * var(--u)); }

/* 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: calc(37.73 * var(--u)); /* seat circles */
  --pile-w: calc(58.31 * var(--u)); /* cards in the middle */
  --t-font: calc(12.8 * var(--u)); /* text on the felt */
  position: relative;
  width: 100%;
  height: 100%;
}

.table.crowded { --avatar: calc(30.87 * var(--u)); }

/* 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 + calc(26 * var(--u))); /* room above the top row for a speech bubble */
  --felt-bottom: calc(var(--avatar) * 0.5 + calc(6 * var(--u)) + 1.25 * max(calc(11.2 * var(--u)), var(--avatar) * 0.3));
}
.table-felt {
  position: absolute;
  inset: var(--felt-top) calc(var(--avatar) * 0.825 + calc(2 * var(--u))) var(--felt-bottom);
  border-radius: calc(28 * var(--u));
  background: radial-gradient(ellipse at center, var(--felt-1), var(--felt-2));
  box-shadow: inset 0 0 0 calc(5 * var(--u)) 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: calc(6 * var(--u)); /* 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: calc(2 * var(--u)) solid var(--seat-edge);
  font-weight: 700;
  font-size: calc(var(--avatar) * 0.42);
  line-height: 1;
}

/* Your circle is a button for emotes (table.js): it looks the same. */
button.seat-avatar.seat-emote {
  padding: 0;
  font-family: inherit;
  cursor: pointer;
  touch-action: manipulation;
}
button.seat-avatar.seat-emote:focus-visible { outline: calc(3 * var(--u)) solid var(--accent); outline-offset: calc(3 * var(--u)); }

/* A circle you can tap to pick that player (table.js seat.onPick): a
   pulsing gold ring. */
button.seat-avatar.seat-pick {
  padding: 0;
  font-family: inherit;
  cursor: pointer;
  touch-action: manipulation;
  pointer-events: auto;
  animation: pick-pulse 1.1s ease-in-out infinite;
}
@keyframes pick-pulse {
  0%, 100% { box-shadow: 0 0 0 calc(3 * var(--u)) var(--bg), 0 0 0 calc(5 * var(--u)) var(--turn), 0 0 calc(4 * var(--u)) calc(5 * var(--u)) color-mix(in srgb, var(--turn) 45%, transparent); }
  50% { box-shadow: 0 0 0 calc(3 * var(--u)) var(--bg), 0 0 0 calc(8 * var(--u)) var(--turn), 0 0 calc(8 * var(--u)) calc(8 * var(--u)) color-mix(in srgb, var(--turn) 45%, transparent); }
}
button.seat-avatar.seat-pick:active { scale: 0.94; }
@media (prefers-reduced-motion: reduce) {
  button.seat-avatar.seat-pick { animation: none; box-shadow: 0 0 0 calc(3 * var(--u)) var(--bg), 0 0 0 calc(6 * var(--u)) var(--turn); }
}

/* An emote showing on someone's circle: the emoji, a bit bigger than the
   circle, over it (and its letter). */
.seat-emote-shown {
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%; /* (centered on the circle, though it's wider) */
  z-index: 3;
  font-size: calc(var(--avatar) * 0.95);
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
  filter: drop-shadow(0 calc(2 * var(--u)) calc(4 * var(--u)) rgb(0 0 0 / 0.35));
}
/* Popping in, then at once floating up, fading out only at the very end
   (table.js sets the timing). Opacity and the float have keyframes of
   their own, so the float keeps one smooth pace. */
@keyframes emote-float {
  0% { opacity: 0; transform: translateY(0) scale(0.6); }
  9% { transform: translateY(0) scale(1); animation-timing-function: ease-out; }
  9%, 82% { opacity: 1; }
  100% { opacity: 0; transform: translateY(calc(var(--avatar) * -1.1)) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  @keyframes emote-float {
    0%, 100% { opacity: 0; }
    9%, 82% { opacity: 1; }
  }
}

/* The list of emotes: on the page, over everything, just above your
   circle (table.js places it). */
.emote-picker {
  position: fixed;
  z-index: 1000;
  display: grid;
  grid-template-columns: repeat(4, calc(44 * var(--u)));
  gap: calc(4 * var(--u));
  padding: calc(8 * var(--u));
  border-radius: calc(14 * var(--u));
  background: var(--surface);
  border: calc(1 * var(--u)) solid var(--border);
  box-shadow: 0 calc(8 * var(--u)) calc(24 * var(--u)) rgb(0 0 0 / 0.35);
}
.emote-picker.opening { animation: emote-picker-in 140ms ease-out both; }
.emote-picker[hidden] { display: none; }
@keyframes emote-picker-in { from { opacity: 0; scale: 0.92; } }
.emote-choice {
  width: calc(44 * var(--u));
  height: calc(44 * var(--u));
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: calc(10 * var(--u));
  background: transparent;
  font-size: calc(25.6 * var(--u));
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
}
.emote-choice:active { background: color-mix(in srgb, var(--accent) 20%, transparent); }
.emote-choice:focus-visible { outline: calc(2 * var(--u)) solid var(--accent); }

.seat-spot.me .seat-avatar {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.seat-spot.away .seat-avatar { opacity: 0.45; }

/* A circle filled with a colour (table.js seat.fill, e.g. Euchre's teams). */
.seat-spot.filled .seat-avatar {
  background: var(--seat-fill);
  border-color: var(--seat-edge); /* (the usual edge, yours too) */
  color: #fff;
}

/* Someone with nothing left to play, or ready (seat.done): a green check in
   their circle in place of their letter. */
.seat-check {
  display: grid;
  place-items: center;
  width: 62%;
  aspect-ratio: 1;
  color: var(--done-ring);
}
.seat-check svg { width: 100%; height: 100%; display: block; }

/* The spectators watching a player (table.js ghostsEl): a little ghost
   each, circling their circle, bobbing as it goes, always upright. Behind
   the player's circle (and its card count) and their name, never taking a
   tap. */
.seat-ghosts {
  position: absolute;
  top: calc(var(--avatar) / 2);
  left: 50%;
  width: 0;
  height: 0;
  z-index: 1;
  pointer-events: none;
}
.ghost-fly,
.ghost-spread,
.ghost-orbit,
.ghost-up { position: absolute; left: 0; top: 0; }
.ghost-orbit { animation: ghost-orbit 9s linear infinite; }
@keyframes ghost-orbit {
  from { transform: rotate(0deg) translateX(calc(var(--avatar) * 0.8)) rotate(0deg); }
  to { transform: rotate(360deg) translateX(calc(var(--avatar) * 0.8)) rotate(-360deg); }
}
.ghost {
  position: absolute;
  left: 0;
  top: 0;
  width: calc(var(--avatar) * 0.56 * var(--ghost-size, 1)); /* (smaller when more crowd one circle) */
  translate: -50% -50%;
  animation: ghost-bob 1.4s ease-in-out infinite alternate;
}
/* (Everything a ghost does moves it as a whole, never re-laying out the
   page or redrawing it each frame, so a crowd of them stays light on a
   phone: the shadow is part of its picture, not a filter.) */
.ghost-orbit,
.ghost { will-change: transform; }
.ghost svg { display: block; width: 100%; height: auto; }
/* (The layer of the spectators' emotes, over the circle and the name: a
   stand-in for each ghost, the size of one, carrying only its emote.) */
.seat-ghosts.ghost-emotes { z-index: 2; }
.ghost-emotes .ghost { aspect-ratio: 40 / 48; }
/* A spectator's emote, just over their ghost's head (table.js
   ghostEmoteEl), going round and bobbing with it: as a player's emote, only
   smaller and lifting less far. */
.ghost-emote {
  position: absolute;
  left: 50%;
  bottom: 92%;
  font-size: calc(var(--avatar) * 0.42);
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
}
@keyframes ghost-emote-float {
  0% { opacity: 0; transform: translateX(-50%) translateY(0) scale(0.6); }
  9% { transform: translateX(-50%) translateY(0) scale(1); animation-timing-function: ease-out; }
  9%, 82% { opacity: 1; }
  100% { opacity: 0; transform: translateX(-50%) translateY(calc(var(--avatar) * -0.55)) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  @keyframes ghost-emote-float {
    0%, 100% { opacity: 0; transform: translateX(-50%); }
    9%, 82% { opacity: 1; transform: translateX(-50%); }
  }
}
@keyframes ghost-bob {
  from { transform: translateY(calc(var(--avatar) * -0.04)); }
  to { transform: translateY(calc(var(--avatar) * 0.04)); }
}
@media (prefers-reduced-motion: reduce) {
  .ghost-orbit, .ghost { animation: none; }
  .ghost-orbit { transform: translate(calc(var(--avatar) * 0.6), calc(var(--avatar) * -0.6)); }
}

/* 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 calc(3 * var(--u)) var(--bg), 0 0 0 calc(6 * var(--u)) 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 calc(3 * var(--u)) var(--bg), 0 0 0 calc(5 * var(--u)) var(--turn-ring),
      0 0 calc(4 * var(--u)) calc(5 * var(--u)) color-mix(in srgb, var(--turn-ring) 45%, transparent);
  }
  50% {
    box-shadow: 0 0 0 calc(3 * var(--u)) var(--bg), 0 0 0 calc(7 * var(--u)) var(--turn-ring),
      0 0 calc(6 * var(--u)) calc(7 * var(--u)) 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: calc(-6 * var(--u));
  display: inline-flex;
  align-items: center;
  gap: calc(2 * var(--u));
  padding: calc(1 * var(--u)) calc(5 * var(--u)) calc(1 * var(--u)) calc(3 * var(--u));
  border-radius: calc(999 * var(--u));
  background: var(--fg);
  color: var(--bg);
  font-size: max(calc(9.92 * var(--u)), calc(var(--avatar) * 0.26));
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}

.seat-count .mini-back { width: 0.7em; height: 1em; }
/* The dealer (table.js seat.dealer): a crown with a D on it, tilted on the
   top left of their circle. */
.seat-crown {
  position: absolute;
  left: -22%;
  top: -30%;
  z-index: 1;
  width: 60%;
  rotate: -22deg;
  line-height: 0;
  pointer-events: none;
}
.seat-crown svg { width: 100%; height: auto; overflow: visible; }
/* (The Crew's commander's cap: a little bigger, as it's squatter.) */
.seat-cap { width: 72%; left: -26%; top: -24%; }
.seat-spot[data-label="above-left"] .seat-cap { left: auto; right: -26%; }
/* (With the name off the circle's top left, the crown sits on its top right.) */
.seat-spot[data-label="above-left"] .seat-crown { left: auto; right: -22%; rotate: 22deg; }
/* Sitting this one out (table.js seat.sitting). */
.seat-spot.sitting .seat-avatar { opacity: 0.4; }
/* 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 calc(1.5 * var(--u)) #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 calc(1 * var(--u)) calc(3 * var(--u)) rgb(0 0 0 / 0.3);
  transform-origin: 50% calc(100% + var(--inner) + var(--pick, 0px));
  rotate: var(--angle, 0deg);
}
.seat-cards .playing-card.back { box-shadow: var(--back-edge), 0 calc(1 * var(--u)) calc(3 * var(--u)) rgb(0 0 0 / 0.3); }
/* A square table (table.js layout "square"): the cards in a straight row
   instead, moved along it (--row-x, --row-y). */
.table.square .seat-cards .playing-card { translate: var(--row-x, 0px) var(--row-y, 0px); }
/* The cards at the seats: the same cards, only smaller (all in proportion,
   no smallest size for the corner numbers). */
.seat-cards .card-corner { font-size: calc(var(--card-w) * 0.235); }

/* 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: calc(700 * var(--u));
  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 calc(1 * var(--u)) calc(4 * var(--u)) 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 calc(2 * var(--u)) var(--danger-strong), 0 0 calc(12 * var(--u)) 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 calc(2 * var(--u)) var(--danger-strong), 0 0 calc(10 * var(--u)) 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);
    /* (Small where it starts: its corners as round as the cards there.) */
    border-radius: calc(max(calc(3 * var(--u)), var(--card-w) * 0.108 * var(--from-scale, 1)) / var(--from-scale, 1));
  }
}
/* 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: calc(-1 * var(--u));
  width: auto;
  transform: none;
}
@keyframes fly-face-out {
  from { transform: perspective(calc(500 * var(--u))) rotateY(0deg); }
  to { transform: perspective(calc(500 * var(--u))) rotateY(90deg); visibility: hidden; }
}
@keyframes fly-back-in {
  from { transform: perspective(calc(500 * var(--u))) rotateY(-90deg); }
  to { transform: perspective(calc(500 * var(--u))) rotateY(0deg); }
}

/* A seat's name, in a spot of its own by where the seat is (table.js sets
   data-label: above or below the circle; centered on it, or out to its
   left or right), clear of any cards fanned toward the middle. --lx moves
   it back onto the table if it would stick out past the side. */
.seat-label {
  position: absolute;
  z-index: 1; /* (over a ghost circling the seat; the cards there still go over it) */
  max-width: calc(var(--avatar) * 2.5);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: max(calc(11.2 * var(--u)), calc(var(--avatar) * 0.3));
  line-height: 1.25; /* the felt leaves exactly this much room (see .table) */
  font-weight: 600;
  top: calc(var(--avatar) + calc(6 * var(--u)));
  left: 50%;
  translate: calc(-50% + var(--lx, 0px)) 0;
}
.seat-spot[data-label="above-center"] .seat-label { top: 0; translate: calc(-50% + var(--lx, 0px)) calc(-100% - calc(6 * var(--u))); }
/* Anywhere else: off the circle's outer corner, diagonally (above or
   below, left or right), out past the felt's edge rather than on it. */
/* (Below: under the card count, which sits on the circle's lower right.) */
.seat-spot:is([data-label$="-left"], [data-label$="-right"]) .seat-label { top: calc(var(--avatar) + calc(6 * var(--u))); }
.seat-spot:is([data-label="above-left"], [data-label="above-right"]) .seat-label { top: calc(var(--avatar) * -0.05); }
.seat-spot[data-label$="-left"] .seat-label { left: auto; right: calc(50% + var(--avatar) * 0.3); text-align: right; }
.seat-spot[data-label$="-right"] .seat-label { left: calc(50% + var(--avatar) * 0.3); }
.seat-spot[data-label="above-left"] .seat-label,
.seat-spot[data-label="above-right"] .seat-label { translate: var(--lx, 0px) -100%; }
.seat-spot[data-label="below-left"] .seat-label,
.seat-spot[data-label="below-right"] .seat-label { translate: var(--lx, 0px) 0; }

/* Cards laid in a straight row in front of a seat (table.js seat.row):
   square to the table's edge, toward the middle, as if laid down facing
   that player. The row is drawn upright, centered just past the circle,
   then turned around the circle's center for the seat's side. Along the
   top, as anywhere, just past the circle (the name is above it there). */
.seat-row {
  position: absolute;
  top: calc(var(--avatar) / 2);
  left: 50%;
  width: 0;
  height: 0;
  pointer-events: none;
  --card-w: calc(var(--avatar) * 0.7);
  --row-turn: 0deg;
  --row-gap: calc(8 * var(--u));
}
.seat-spot[data-side="left"] .seat-row { --row-turn: 90deg; }
.seat-spot[data-side="right"] .seat-row { --row-turn: -90deg; }
.seat-spot[data-side="top"] .seat-row { --row-turn: 180deg; }
.seat-row-cards {
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  align-items: flex-start;
  transform-origin: 0 0;
  transform: rotate(var(--row-turn)) translate(-50%, calc(-100% - var(--avatar) / 2 - var(--row-gap)));
}
.seat-row-cards > .playing-card {
  flex: none;
  width: var(--card-w);
}
.seat-row-cards > .playing-card + .playing-card { margin-left: calc(var(--card-w) * -0.45); }
/* A card joining the end of a row shifts the rest over by half of what it
   adds (the row stays centered): they start where they were and slide. */
@keyframes eu-make-room {
  from { translate: calc(var(--card-w) * 0.275) 0; }
}

/* The tricks a player has won (ui/trick.js wonStackEl), among the cards in front of a
   seat: a stack of face-down cards, each a little further out than the one under it (--pick). */
.seat-cards .won-stack .playing-card { box-shadow: var(--back-edge), 0 calc(1 * var(--u)) calc(2 * var(--u)) rgb(0 0 0 / 0.35); }
/* (The count: a bubble like a task's order token, in the middle.) */
/* (Always upright, however the stack is turned.) */
.won-stack .won-stack-n {
  rotate: calc(-1 * var(--angle, 0deg));
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  min-width: 1.5em;
  padding: 0 0.3em;
  border-radius: calc(999 * var(--u));
  background: #1d2433;
  color: #fff;
  font-size: max(calc(9.6 * var(--u)), calc(var(--card-w) * 0.3));
  font-weight: 700;
  line-height: 1.5;
  text-align: center;
  box-shadow: 0 0 0 calc(1.5 * var(--u)) #fff;
}

/* The cards fanning out from the circle when they're laid down, one after
   another. 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; }
}
@media (prefers-reduced-motion: reduce) {
  .seat-spot.fanning .seat-cards .playing-card { 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: calc(6 * var(--u)) 0; }

.pile-empty {
  width: var(--pile-w);
  aspect-ratio: 5 / 7;
  border: calc(2 * var(--u)) dashed var(--felt-rim);
  border-radius: calc(var(--pile-w) * 0.108);
}

/* 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: calc(1 * var(--u)) 0.45em;
  border-radius: calc(999 * var(--u));
  background: var(--fg);
  color: var(--bg);
  font-size: max(calc(11.2 * var(--u)), 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 calc(1.5 * var(--u)) #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: calc(205.8 * var(--u));
  display: grid;
  justify-items: center;
  gap: calc(6.86 * var(--u));
}

.table-below { top: calc(100% + calc(6.86 * var(--u))); }

/* 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% + calc(6.86 * var(--u)));
  left: 50%;
  translate: -50% 0;
  width: calc(205.8 * var(--u));
  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: calc(14 * var(--u));
  background: none;
  font: inherit;
  cursor: pointer;
  touch-action: manipulation;
}

.pile-button:focus-visible { outline: calc(3 * var(--u)) solid var(--turn); outline-offset: calc(2 * var(--u)); }

/* 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 calc(2 * var(--u)) var(--turn)) drop-shadow(0 0 calc(4 * var(--u)) var(--turn)); }
  to { filter: drop-shadow(0 0 calc(3 * var(--u)) var(--turn)) drop-shadow(0 0 calc(14 * var(--u)) var(--turn)); }
}

@media (prefers-reduced-motion: reduce) {
  .pile-button.ready .pile-stack,
  .pile-button.ready .pile-empty {
    animation: none;
    filter: drop-shadow(0 0 calc(3 * var(--u)) var(--turn)) drop-shadow(0 0 calc(10 * var(--u)) 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: calc(6 * var(--u)) 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: calc(2 * var(--u)) dashed var(--felt-rim);
  border-radius: calc(var(--pile-w) * 0.108);
}
.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 calc(3 * var(--u)) var(--card-face), 0 0 0 calc(2 * var(--u)) var(--turn), 0 calc(1 * var(--u)) calc(3 * var(--u)) rgb(0 0 0 / 0.25);
}
.pile-stack .playing-card.latest.back { box-shadow: var(--back-edge), 0 0 0 calc(2 * var(--u)) var(--turn), 0 calc(1 * var(--u)) calc(3 * var(--u)) 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 calc(3 * var(--u)) var(--card-face), 0 0 0 calc(2 * var(--u)) var(--turn), 0 calc(1 * var(--u)) calc(3 * var(--u)) rgb(0 0 0 / 0.25);
}
.pile-stack .playing-card.latest.fly-turn > .fly-face {
  box-shadow: 0 0 0 calc(2 * var(--u)) var(--turn), 0 calc(1 * var(--u)) calc(3 * var(--u)) 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 calc(2 * var(--u)) calc(8 * var(--u)) rgb(0 0 0 / 0.5);
}


/* Between the table and your hand: the BS! button's slot (also The
   Mind's shuriken buttons), centered, as wide as the felt is on the
   reference phone and no wider, however wide the table gets (--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: calc(48 * var(--u));
  width: calc(276.8 * var(--u));
  max-width: calc(100% - 2 * var(--felt-inset, calc(16 * var(--u))));
  margin: 0 auto;
}

/* 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: calc(16 * var(--u));
  background: color-mix(in srgb, var(--felt-2) 55%, black);
  box-shadow: inset 0 0 0 calc(3 * var(--u)) rgb(255 255 255 / 0.18), 0 calc(4 * var(--u)) calc(16 * var(--u)) rgb(0 0 0 / 0.35);
  color: #fff;
  font-size: calc(26.23 * var(--u));
  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 calc(2 * var(--u)) calc(6 * var(--u)) 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: calc(3 * var(--u));
  --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 calc(4 * var(--u)) calc(12 * var(--u)) rgb(0 0 0 / 0.35);
  font-size: calc(15.09 * var(--u));
  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: calc(700 * var(--u));
}

/* 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-gather). */
.reveal-card {
  --in-row: translate(-50%, -50%)
    translate(calc((var(--i) - var(--mid)) * var(--step, 0px)), var(--drop, 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: max(calc(3 * var(--u)), calc(var(--card-w) * 0.108));
  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 calc(3 * var(--u)) var(--danger-strong), 0 0 calc(18 * var(--u)) 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 calc(3 * var(--u)) #2fa35b, 0 0 calc(14 * var(--u)) #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). */
/* The row gathering back onto the pile, each card to its spot there (the
   top one on top again), before it's all picked up as one bundle; then the
   bundle (pickup.js) takes over from all of it. */
@keyframes reveal-gather {
  from { transform: var(--in-row); z-index: var(--zd, auto); }
  10% { z-index: var(--zp, auto); }
  to { transform: translate(-50%, -50%) translate(var(--px), var(--py)) rotate(var(--pr)); z-index: var(--zp, auto); }
}
@keyframes reveal-unhop {
  to { translate: 0 0; }
}
@keyframes reveal-gone {
  to { visibility: hidden; }
}

.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: calc(294.98 * var(--u)); /* 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: calc(50 * var(--u));
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: calc(4 * var(--u));
}

.mini-back {
  width: calc(11 * var(--u));
  height: calc(15 * var(--u));
  border-radius: calc(2 * var(--u));
  background: repeating-linear-gradient(45deg, var(--card-back) 0 calc(2 * var(--u)), var(--card-back-2) calc(2 * var(--u)) calc(4 * var(--u)));
}

/* Buttons and forms: everything tappable is at least 48px tall. */
.button {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: calc(48 * var(--u));
  padding: 0 calc(28 * var(--u));
  border: none;
  border-radius: calc(999 * var(--u));
  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: calc(2 * var(--u)) solid var(--accent);
}

.button:disabled {
  background: var(--border);
  color: var(--muted);
  cursor: default;
}

.button:focus-visible,
.link-button:focus-visible,
.input:focus-visible {
  outline: calc(3 * var(--u)) solid var(--accent);
  outline-offset: calc(2 * var(--u));
}

.link-button {
  min-height: calc(44 * var(--u));
  padding: 0 calc(8 * var(--u));
  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: calc(16 * var(--u)) auto 0; }

h2 { font-size: calc(17.6 * var(--u)); margin: 0; }

.card {
  background: var(--surface);
  border: calc(1 * var(--u)) solid var(--border);
  border-radius: calc(12 * var(--u));
  padding: calc(16 * var(--u));
}

.form {
  display: flex;
  flex-direction: column;
  gap: calc(10 * var(--u));
  margin-bottom: calc(16 * var(--u));
}

.label { font-weight: 600; }

/* 16px or larger, or iPhones zoom in when the field is tapped. */
.input {
  width: 100%;
  min-height: calc(48 * var(--u));
  padding: 0 calc(14 * var(--u));
  border: calc(1 * var(--u)) solid var(--border);
  border-radius: calc(10 * var(--u));
  background: var(--bg);
  color: var(--fg);
  font: inherit;
  font-size: calc(16.8 * var(--u));
}

.code-input {
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-weight: 700;
}

.row { display: flex; gap: calc(8 * var(--u)); }
.row .input { flex: 1; min-width: 0; }

.join-form {
  display: flex;
  flex-direction: column;
  gap: calc(10 * var(--u));
  margin-bottom: calc(28 * var(--u));
}

/* The home page's two sections: "Join By Room Code:" and "Or Host A Game:". */
.home-heading { font-size: calc(20.8 * var(--u)); margin: 0 0 calc(12 * var(--u)); }

.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: calc(10 * var(--u));
  margin: calc(6 * var(--u)) 0 0;
  padding: 0;
  border: none;
}
.option { display: flex; align-items: flex-start; gap: calc(12 * var(--u)); cursor: pointer; }
.option-box { flex: none; width: calc(22 * var(--u)); height: calc(22 * var(--u)); margin: calc(1 * var(--u)) 0 0; accent-color: var(--accent); }
.option-text { display: flex; flex-direction: column; gap: calc(2 * var(--u)); }
.option-name { font-weight: 600; }
.option-help { color: var(--muted); font-size: calc(13.6 * var(--u)); line-height: 1.35; }
/* In the lobby, for everyone but the host: shown, but greyed out. */
.options:disabled .option { cursor: default; opacity: 0.55; }
:is(.room-content, .lobby-options) > .options { margin-bottom: calc(12 * var(--u)); }

/* 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: calc(16 * var(--u)) max(calc(16 * var(--u)), env(safe-area-inset-right, 0px)) calc(16 * var(--u)) max(calc(16 * var(--u)), env(safe-area-inset-left, 0px));
  background: rgb(0 0 0 / 0.5);
}

.modal {
  width: min(100%, calc(360 * var(--u)));
  display: grid;
  gap: calc(12 * var(--u));
  box-shadow: 0 calc(12 * var(--u)) calc(32 * var(--u)) rgb(0 0 0 / 0.35);
}

.modal h2 { font-size: calc(19.2 * var(--u)); }
.modal p { margin: 0; color: var(--muted); }
.modal-actions { display: grid; grid-template-columns: 1fr 1fr; gap: calc(10 * var(--u)); margin-top: calc(4 * var(--u)); }

.button.danger-button { background: var(--danger-strong); color: #fff; }
.form-error:empty { display: none; }

/* Why everyone's back in the lobby (room.js syncNotice): a card with a
   strip of table felt across its top; it pops up. */
.notice-backdrop { animation: notice-fade 200ms ease-out both; }
.notice-modal {
  overflow: hidden;
  padding-top: 0;
  text-align: center;
  justify-items: center;
  animation: notice-pop 260ms cubic-bezier(0.3, 1.4, 0.5, 1) both;
}
.notice-modal > .button { width: 100%; }
/* A question (ui/popup.js askConfirm): Cancel and the button that does it,
   side by side. */
.confirm-actions {
  justify-self: stretch;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: calc(10 * var(--u));
}

/* Something refused (ui/popup.js showToast): a short line on a soft dark
   background in the strip along the top of the screen (the game bar's,
   clear of the table and its seats), fading away on its own. Over
   everything, but it never takes a tap. */
.toast {
  position: fixed;
  top: calc(5 * var(--u));
  left: 50%;
  z-index: 40;
  translate: -50% 0;
  width: max-content;
  max-width: calc(330 * var(--u));
  padding: calc(6 * var(--u)) calc(14 * var(--u));
  border-radius: calc(999 * var(--u));
  background: rgb(14 20 17 / 0.9);
  box-shadow: 0 0 0 calc(1 * var(--u)) rgb(255 255 255 / 0.12), 0 calc(6 * var(--u)) calc(18 * var(--u)) rgb(0 0 0 / 0.4);
  color: var(--fg);
  font-size: calc(12.8 * var(--u));
  font-weight: 600;
  line-height: 1.3;
  text-align: center;
  text-wrap: balance;
  pointer-events: none;
}
.notice-felt {
  justify-self: stretch;
  display: grid;
  place-items: center;
  height: calc(22 * var(--u));
  margin: 0 calc(-16 * var(--u));
  background: radial-gradient(ellipse at center, var(--felt-1), var(--felt-2));
  border-bottom: calc(4 * var(--u)) solid var(--felt-rim);
}
@keyframes notice-fade { from { opacity: 0; } }
@keyframes notice-pop { from { opacity: 0; scale: 0.85; } }
@media (prefers-reduced-motion: reduce) {
  .notice-backdrop, .notice-modal { animation: none; }
}

.notice {
  margin: 0;
  padding: calc(10 * var(--u)) calc(12 * var(--u));
  border-radius: calc(8 * var(--u));
  background: var(--bg);
}

.muted { color: var(--muted); }
.small { font-size: calc(14.4 * var(--u)); }
.center { text-align: center; }

/* Room */
.eyebrow {
  margin: 0;
  text-align: center;
  color: var(--muted);
  font-size: calc(13.6 * var(--u));
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.room-code {
  margin: calc(4 * var(--u)) 0 calc(16 * var(--u));
  text-align: center;
  font-size: calc(56 * var(--u)); /* (what the phone got from 15% of its width, capped) */
  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: calc(10 * var(--u)); }

.how-to-play-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(8 * var(--u));
}
.how-to-play ul { margin: 0; padding-left: 1.1em; display: grid; gap: calc(10 * var(--u)); line-height: 1.45; }

/* Copies the game link (the link itself isn't shown). */
.invite-button { width: 100%; margin-bottom: calc(24 * var(--u)); }

.player-list {
  list-style: none;
  margin: calc(10 * var(--u)) 0 calc(20 * var(--u));
  padding: 0;
  display: grid;
  gap: calc(8 * var(--u));
}

.player {
  display: flex;
  align-items: center;
  gap: calc(10 * var(--u));
  min-height: calc(56 * var(--u));
  padding: calc(8 * var(--u)) calc(12 * var(--u));
  background: var(--surface);
  border: calc(1 * var(--u)) solid var(--border);
  border-radius: calc(12 * var(--u));
}

.player.empty {
  border-style: dashed;
  background: transparent;
  color: var(--muted);
}

.avatar {
  flex: none;
  width: calc(36 * var(--u));
  height: calc(36 * var(--u));
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 700;
}

.player.empty .avatar { background: var(--border); }

/* The host can tap rows to swap a player and a spectator: the one tapped
   first is outlined. */
.player.swappable { cursor: pointer; }
.player.picked { border-color: var(--accent); box-shadow: inset 0 0 0 calc(1 * var(--u)) var(--accent); }
.swap-hint { margin: calc(4 * var(--u)) 0 0; font-size: calc(13.6 * var(--u)); }
/* Moving yourself to the other section: an open spot, highlighted. */
button.player.join-row {
  width: 100%;
  font: inherit;
  font-weight: 600;
  text-align: left;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: calc(1 * var(--u)) dashed var(--accent);
  cursor: pointer;
}
button.player.join-row .avatar { background: var(--accent); color: var(--on-accent); }

/* Spectating a game: whose view it is, with arrows to the others, on a
   slim bar over the table. Nothing else can be tapped (but the end-of-game
   buttons). */
.watch-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(6 * var(--u));
  margin: 0 auto calc(4 * var(--u));
  padding: calc(2 * var(--u));
  border-radius: calc(999 * var(--u));
  background: var(--surface);
  border: calc(1 * var(--u)) solid var(--border);
  width: fit-content;
  max-width: 100%;
}
.watch-arrow {
  width: calc(36 * var(--u));
  height: calc(32 * var(--u));
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: calc(999 * var(--u));
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: calc(22.4 * var(--u));
  line-height: 1;
  cursor: pointer;
  touch-action: manipulation;
}
.watch-arrow:active { background: var(--bg); }
.watch-name { min-width: 8em; text-align: center; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.watch-eyebrow { font-weight: 500; color: var(--muted); }
.game-body.spectating > :not(.watch-bar, .end-slot) { pointer-events: none; }
/* (But the end-of-game buttons are theirs, wherever the game puts them.) */
.game-body.spectating .end-actions { pointer-events: auto; }
/* (And the circle of the player they watch: where their emotes come from,
   shown on their ghost.) */
.game-body.spectating .seat-emote { pointer-events: auto; }
/* (And the hand scrolls for them, its cards still not theirs to tap: a
   swipe on a card is a swipe on the hand.) */
.game-body.spectating .hand { pointer-events: auto; }
.game-body.spectating .hand > * { pointer-events: none; }

.player-name {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.pill {
  flex: none;
  padding: calc(2 * var(--u)) calc(10 * var(--u));
  border-radius: calc(999 * var(--u));
  background: var(--bg);
  font-size: calc(12.8 * var(--u));
  font-weight: 600;
}

.pill.away { background: var(--away-bg); color: var(--away-fg); }

.actions { display: grid; gap: calc(12 * var(--u)); }
.actions .card { display: grid; gap: calc(12 * var(--u)); }
.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: calc(12 * var(--u)) max(calc(16 * var(--u)), env(safe-area-inset-right, 0px)) calc(calc(12 * var(--u)) + env(safe-area-inset-bottom, 0px)) max(calc(16 * var(--u)), 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: calc(-9999 * var(--u)); }

/* 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 {
  display: flex;
  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;
  /* The game's box (ui/scale.js): the screen, or the middle of it past
     21:9. */
  width: var(--box-w);
  max-width: none;
  height: var(--box-h);
  margin: auto;
  display: flex;
  flex-direction: column;
  padding-top: calc(calc(4 * var(--u)) + env(safe-area-inset-top, 0px));
  padding-bottom: calc(calc(8 * var(--u)) + 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: calc(17.05 * var(--u));
}

/* 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: calc(-10 * var(--u)); }

/* 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: calc(-4 * var(--u)) 0; }
  35% { translate: calc(4 * var(--u)) 0; }
  50% { translate: calc(-3 * var(--u)) 0; }
  65% { translate: calc(2 * var(--u)) 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: calc(4 * var(--u));
  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 calc(4 * var(--u)) calc(16 * var(--u)) 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 calc(4 * var(--u)) calc(10 * var(--u)) 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 calc(3 * var(--u)) color-mix(in srgb, var(--star) 70%, white);
  color: #fff;
}
.button.star-button.voted:disabled .bs-fill { opacity: 1; }
/* The vote: Yes and No side by side, filling the slot. */
.star-answers { display: grid; grid-template-columns: 1fr 1fr; gap: calc(10 * var(--u)); width: 100%; height: 100%; }
.button.star-answer { min-height: 0; height: 100%; font-weight: 800; }
.button.star-answer.yes { background: var(--star); color: #fff; }
.button.star-answer.no { background: color-mix(in srgb, var(--felt-2) 55%, black); color: #fff; }
@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(calc(44 * var(--u)), 2.4em);
  height: max(calc(44 * var(--u)), 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: calc(3 * var(--u)) solid var(--accent); outline-offset: calc(2 * var(--u)); }

.menu-list {
  position: absolute;
  top: calc(100% + calc(4 * var(--u)));
  right: 0;
  z-index: 5;
  min-width: calc(190 * var(--u));
  padding: calc(6 * var(--u));
  display: grid;
  border: calc(1 * var(--u)) solid var(--border);
  border-radius: calc(12 * var(--u));
  background: var(--surface);
  box-shadow: 0 calc(8 * var(--u)) calc(24 * var(--u)) rgb(0 0 0 / 0.2);
}

.menu-item {
  min-height: calc(48 * var(--u));
  padding: 0 calc(14 * var(--u));
  border: none;
  border-radius: calc(8 * var(--u));
  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: calc(3 * var(--u)) solid var(--accent); outline-offset: calc(-3 * var(--u)); }
.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: calc(65.17 * var(--u));
  --hand-card-w: var(--card-w); /* (the same, kept where --card-w is set again for smaller cards) */
  container-type: size;
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: calc(8 * var(--u));
}

/* 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% + calc(8.58 * var(--u)));
  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 calc(2 * var(--u)) calc(6 * var(--u)) 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: calc(8 * var(--u)); }
.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(calc(20 * var(--u)), var(--card-w) * 0.24) + var(--card-w) * 1.4 + calc(16 * var(--u)));
  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% - calc(4 * var(--u))) var(--title-y, 0); }
  35% { translate: calc(-50% + calc(4 * var(--u))) var(--title-y, 0); }
  50% { translate: calc(-50% - calc(3 * var(--u))) var(--title-y, 0); }
  65% { translate: calc(-50% + calc(2 * var(--u))) 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: calc(var(--pile-w) * 0.108);
  background: rgb(255 255 255 / 0.9);
  box-shadow: 0 0 calc(18 * var(--u)) calc(6 * var(--u)) rgb(255 255 255 / 0.85), 0 0 calc(46 * var(--u)) calc(14 * var(--u)) 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 calc(2 * var(--u)) calc(8 * var(--u)) 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 calc(4 * var(--u)) calc(16 * var(--u)) rgb(0 0 0 / 0.5);
}

.end-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: calc(10 * var(--u));
  width: min(100%, calc(420 * var(--u)));
  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: calc(48 * var(--u));
  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% - calc(5 * var(--u))); }
.end-actions .button { min-height: calc(48 * var(--u)); }
/* With a Choose (e.g. mission) button too: two rows of two (Next and
   Choose, then Back to lobby and Leave). */
.end-actions.with-choice .end-leave { grid-column: auto; justify-self: stretch; width: auto; }
.end-actions.with-choice .button { padding-inline: calc(8 * var(--u)); white-space: nowrap; }

@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: calc(20 * var(--u)); }



/* 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: calc(180 * var(--u));
  container-type: size;
  /* Over your hand: cards played from it fly out over it (it would
     otherwise hide them where they start, e.g. under a face-down card). */
  position: relative;
  z-index: 1;
}

.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(calc(20 * var(--u)), 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 + calc(16 * var(--u)));
}

/* A card someone else draws, flying face down from where it was drawn into
   their circle (play.js drawToSeat). Shown even once the draw pile it
   leaves is empty. */
/* A card drawn into someone's circle (play.js drawToSeat): on the page, over
   everything on the table. */
.playing-card.draw-fly {
  position: fixed;
  z-index: 15;
  margin: 0;
  visibility: visible;
  pointer-events: none;
}

/* ---------- 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: calc(13.72 * var(--u));
}
.sh-deck {
  isolation: isolate; /* (its count stays over its card, not over cards flying past) */
  position: relative;
  --card-w: var(--pile-w);
  margin: calc(6 * var(--u)) 0; /* as .pile-stack, so the two line up */
}
.sh-deck.gone { visibility: hidden; }
/* The draw pile used up: the pile slides over to the middle of the table
   (stupidhead.js deckGone). The draw pile's spot stays, empty, so nothing
   else on the table changes size (the cards fanned at the seats are sized
   from it). */
.sh-middle.no-deck > .pile {
  animation: sh-pile-center 600ms ease-in-out var(--deck-late, 0ms) both;
}
/* Cards flying into the pile pass over the cards fanned at the seats. (The
   slide above makes the pile a layer of its own, which would otherwise keep
   them under the seats.) */
.sh-middle > .pile { z-index: 4; }
@keyframes sh-pile-center {
  to { translate: calc(-0.5 * (var(--pile-w) + calc(13.72 * var(--u)))) 0; }
}
.sh-deck .pile-count { z-index: 5; box-shadow: 0 0 0 calc(1.5 * var(--u)) #000; }
/* (The pile has its stack's 6px margin inside it, so its count goes up by
   that much to line up with the draw pile's.) */
.sh-middle .pile > .pile-count { bottom: calc(calc(6 * var(--u)) - 0.4em); }

/* 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 calc(2 * var(--u)) var(--turn), 0 calc(1 * var(--u)) calc(3 * var(--u)) 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 calc(3 * var(--u)) var(--accent), 0 calc(4 * var(--u)) calc(10 * var(--u)) rgb(0 0 0 / 0.3);
}
.seat-cards .sh-tap > .playing-card.back { box-shadow: var(--back-edge), 0 0 0 calc(2 * var(--u)) var(--turn), 0 calc(1 * var(--u)) calc(3 * var(--u)) rgb(0 0 0 / 0.3); }
.seat-cards .sh-tap.selected > .playing-card.back { box-shadow: var(--back-edge), 0 0 0 calc(3 * var(--u)) var(--accent), 0 calc(4 * var(--u)) calc(10 * var(--u)) 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: calc(3 * var(--u)) solid var(--accent); outline-offset: calc(2 * var(--u)); }

/* Ready, under the pile while swapping. */
.table-below .button.sh-action {
  width: min(100%, calc(220 * var(--u)));
  min-height: calc(44 * var(--u));
  border-radius: calc(14 * var(--u));
  background: color-mix(in srgb, var(--felt-2) 55%, black);
  box-shadow: inset 0 0 0 calc(3 * var(--u)) rgb(255 255 255 / 0.18), 0 calc(4 * var(--u)) calc(16 * var(--u)) rgb(0 0 0 / 0.35);
  color: #fff;
  font-size: calc(16 * var(--u));
  font-weight: 900;
  letter-spacing: 0.03em;
}
.table-below .button.sh-action:disabled {
  background: var(--border);
  box-shadow: none;
  color: var(--muted);
}

/* A special card's sign in its top right corner (cards.js cardEl's mark;
   Stupid Head's 7, 8 and 10). */
.card-mark {
  position: absolute;
  top: calc(var(--card-w) * 0.075);
  right: calc(var(--card-w) * 0.075);
  width: max(calc(10 * var(--u)), calc(var(--card-w) * 0.24));
  aspect-ratio: 1;
  line-height: 0;
}
.card-mark svg { width: 100%; height: 100%; display: block; }

/* Stupid Head: a 3 on the pile fades to just its outline (what counts is
   under it): its face, rank and suit fade away completely, leaving only
   its border. Once it has landed it fades slowly (.sh-clearing); one
   already on the pile is simply an outline (.sh-clear). */
.pile-stack .playing-card.sh-clear {
  background-color: transparent;
  color: transparent;
  border-color: rgb(255 255 255 / 0.85);
  box-shadow: none;
}
@keyframes sh-clear {
  to { background-color: transparent; color: transparent; border-color: rgb(255 255 255 / 0.85); box-shadow: none; }
}

/* Stupid Head: a pile burnt (a 10, or four of a kind) flares up and is gone. */
@keyframes pile-burn {
  0% { opacity: 1; scale: 1; filter: none; }
  25% { opacity: 1; scale: 1.06; filter: brightness(1.25) drop-shadow(0 0 calc(10 * var(--u)) #ffb347) drop-shadow(0 0 calc(22 * var(--u)) #ff6a00); }
  100% { opacity: 0; scale: 1.25; filter: brightness(1.6) blur(calc(6 * var(--u))) drop-shadow(0 0 calc(26 * var(--u)) #ff6a00); }
}
@media (prefers-reduced-motion: reduce) {
  .pile-stack > [style*="pile-burn"] { animation-duration: 1ms !important; }
}

/* Stupid Head, your hand empty: your face-up cards where your hand was,
   each with its face-down card peeking out under it (stupidhead.js
   tableHandEl); a face-down card alone once its face-up one is played. */
.sh-pair {
  position: relative;
  isolation: isolate; /* so the face-down card can sit behind the face-up one */
  display: block;
  width: var(--card-w);
}
.sh-pair > .playing-card.back {
  position: absolute;
  inset: 0;
  z-index: -1;
  /* (At most what fits in the room under the hand's cards, so the hand
     doesn't grow or scroll up and down on big screens.) */
  translate: 0 min(12%, calc(10 * var(--u)));
}
.hand.sh-table-hand { overflow-y: hidden; }
.sh-pair.alone > .playing-card.back {
  position: relative;
  z-index: auto;
  translate: none;
}
/* Picking a face-up card lifts and outlines only it, not the face-down card
   under it. */
.hand-slot.selected .sh-pair:not(.alone) > .playing-card.back {
  transform: none;
  box-shadow: var(--back-edge), 0 calc(1 * var(--u)) calc(3 * var(--u)) rgb(0 0 0 / 0.25);
}

/* Stupid Head, before play: the switcheroo sign between your hand and your
   face-up cards (stupidhead.js swapHandEl), in a narrower slot of its own. */
.hand-slot:has(.sh-swap-sign) {
  flex: 0 0 calc(var(--card-w) * 0.6);
  min-width: calc(var(--card-w) * 0.6);
  height: calc(var(--card-w) * 1.4); /* (centered on the cards beside it) */
  display: grid;
  place-items: center;
  cursor: default;
}
/* (The card before it keeps its full width, so it doesn't cover the sign.) */
.hand-slot:has(+ .hand-slot .sh-swap-sign) { flex-shrink: 0; }
.sh-swap-sign {
  display: block;
  width: calc(var(--card-w) * 0.42);
  aspect-ratio: 1;
  color: var(--muted);
}
.sh-swap-sign svg { width: 100%; height: 100%; display: block; }

/* A card flying from in front of a seat (play.js fromEl): small cards there
   don't show the center of their face (or a special card's sign), so it
   fades in on the way. */
.playing-card.from-seat .card-pip,
.playing-card.from-seat .card-mark {
  animation: seat-face-in 700ms ease-in var(--fly-delay, 0ms) both;
}
@keyframes seat-face-in {
  from { opacity: 0; }
}
/* (A card played from in front of a seat, holding its spot unseen while it
   flies from there: stupidhead.js tableCards.) */
.seat-cards .sh-leaving { visibility: hidden; }

/* Stupid Head: your face-up card picked in your hand while swapping, lit
   at your seat too. */
.seat-cards .playing-card.sh-picked { box-shadow: 0 0 0 calc(2 * var(--u)) var(--accent), 0 calc(2 * var(--u)) calc(6 * var(--u)) rgb(0 0 0 / 0.35); }
/* Your card played from where your hand was: its copy at your seat fades. */
.seat-cards .sh-fading { animation: sh-fade 400ms ease-out both; }
@keyframes sh-fade {
  to { opacity: 0; }
}
/* Your face-up and face-down cards sliding away from where your hand was
   (stupidhead.js slideTableHandAway): a copy over the page. */
.hand.sh-hand-leaving {
  position: fixed;
  z-index: 15;
  margin: 0;
  pointer-events: none;
  overflow: visible;
}
/* (On your own circle, which is green, a lighter green.) */
.seat-spot.me .seat-check { color: #8ff0b4; }

/* ---------- Euchre (euchre.js) ---------- */

/* The middle: the deck while picking trump, then the trick, each card
   nudged toward whoever played it. (Offsets are a share of the card's own
   size.) */
.eu-trick-cards { position: absolute; inset: 0; }
.eu-trick-cards.collecting { z-index: 5; } /* over the seats it flies past */

/* The back laid on the stack as it turns over: over every card in it (the
   last one played is raised while it flies in). */
.eu-trick-cards > .trick-back,
.crew-trick-cards > .trick-back { z-index: 10; }
.eu-trick-cards > .eu-at-bottom { --dx: 0%; --dy: 34%; }
.eu-trick-cards > .eu-at-top { --dx: 0%; --dy: -34%; }
.eu-trick-cards > .eu-at-left { --dx: -60%; --dy: 0%; }
.eu-trick-cards > .eu-at-right { --dx: 60%; --dy: 0%; }
.eu-center .pile-stack { --card-w: var(--pile-w); }
/* (Room above and below for the cards nudged up and down, so the text
   above and below the middle stays clear of them.) */
.eu-center { margin: calc(var(--pile-w) * 0.5) 0; }
.eu-suit-red { color: var(--card-red); }
/* The hand's result over the middle: the winning team on top, in its
   colour (a lighter tint, to read on the felt), waving if it's your team and
   shaking if it's the other; how many points under it, plain. */
/* (The whole block centered in the middle of the table, both ways.) */
.end-title.eu-result {
  bottom: auto;
  top: 50%;
  translate: -50% -50%;
  display: grid;
  justify-items: center;
  text-align: center;
  color: var(--felt-text);
}
.end-title.eu-result.blue .eu-result-team { color: #b4cdf2; }
.end-title.eu-result.red .eu-result-team { color: #f2b0ad; }
/* (The other team's: shaking, not waving.) */
.end-title.eu-result.bad .eu-result-team { animation: eu-shake 900ms ease-out 300ms both; }
.eu-result-points { font-size: 0.7em; }
@keyframes eu-shake {
  0%, 80%, 100% { translate: 0 0; }
  20% { translate: calc(-4 * var(--u)) 0; }
  35% { translate: calc(4 * var(--u)) 0; }
  50% { translate: calc(-3 * var(--u)) 0; }
  65% { translate: calc(2 * var(--u)) 0; }
}
/* (No empty-pile outline in the middle: there's no pile in Euchre...) */
.eu-center .pile-stack::before { display: none; }
/* ...except when it's your turn to lead (nothing played yet): then the
   empty spot glows gold, like anything else you can tap. */
.eu-center.pile-button .eu-trick:not(:has(.playing-card))::before {
  display: block;
  border: calc(3 * var(--u)) solid var(--turn);
}
.eu-center.pile-button .eu-trick:not(:has(.playing-card)) { animation: pile-glow 1.2s ease-in-out infinite alternate; }


/* Cards you can't play right now (you have to follow suit). */
.hand .playing-card.eu-cant { filter: brightness(0.7) saturate(0.6); }
/* Stupid Head: cards you can't play right now (they don't go on the pile). */
.hand .playing-card.cant-play { filter: brightness(0.7) saturate(0.6); }

/* Picking trump, under the turned card (euchre.js bidPanel): the suits and
   Pass, all the same size, with "Choose a suit" (or "Go alone?") under
   them. The middle of the table lifts to make room (.eu-lifted). */
.eu-panel {
  --btn: calc(var(--pile-w) * 0.62);
  --btn-h: calc(var(--pile-w) * 0.7);
  gap: calc(6 * var(--u));
}
.table-stack.eu-lifted { --eu-lift: calc((var(--btn-h, calc(var(--pile-w) * 0.7)) + calc(6 * var(--u)) + var(--t-font) * 1.3) / 2 + calc(6 * var(--u))); }
@keyframes eu-lift {
  from { translate: 0 0; }
  to { translate: 0 calc(-1 * var(--eu-lift)); }
}
@keyframes eu-drop {
  from { translate: 0 calc(-1 * var(--eu-lift)); }
  to { translate: 0 0; }
}
.eu-buttons {
  display: flex;
  justify-content: center;
  gap: calc(var(--pile-w) * 0.2); /* (room for each one's gold edge) */
  height: var(--btn-h);
}
.eu-panel-text {
  color: var(--felt-text);
  font-size: var(--t-font);
  font-weight: 700;
  line-height: 1.3;
  text-align: center;
  text-wrap: balance;
}
.button.eu-bid {
  flex: 0 0 var(--btn);
  min-width: 0;
  height: 100%;
  min-height: 0;
  padding: 0;
  border-radius: calc(10 * var(--u));
  background: var(--card-face);
  box-shadow: inset 0 0 0 calc(2 * var(--u)) rgb(0 0 0 / 0.15), 0 calc(3 * var(--u)) calc(10 * var(--u)) rgb(0 0 0 / 0.3);
  color: #1a1a1a;
  font-size: calc(var(--btn) * 0.62);
  font-weight: 900;
  line-height: 1;
}
.button.eu-bid.red { color: var(--card-red); }
/* What you can tap right now: a gold edge. */
.button.eu-bid:not(:disabled) { box-shadow: 0 0 0 calc(3 * var(--u)) var(--turn), 0 calc(3 * var(--u)) calc(10 * var(--u)) rgb(0 0 0 / 0.3); }
.button.eu-bid.eu-word { font-size: max(calc(11.52 * var(--u)), calc(var(--btn) * 0.28)); }
.button.eu-bid:disabled {
  background: color-mix(in srgb, var(--felt-2) 70%, white);
  box-shadow: none;
  color: rgb(255 255 255 / 0.55);
}
/* Asking whether to go alone, in place of the suits: it pops up. */
.eu-panel.asking > * { animation: eu-pop 220ms cubic-bezier(0.3, 1.4, 0.5, 1) both; }
@keyframes eu-pop {
  from { opacity: 0; scale: 0.85; }
}

/* Trump and the score sit in the room there already is above the table,
   beside the top seat: trump on the left (the suit, and "Trump, picked by
   X" beside it), the score on the right, their outer edges lined up with a
   full hand of cards (5). */
.eu-trump,
.eu-scores {
  position: absolute;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  height: calc(var(--felt-top) - calc(8 * var(--u)));
}
.eu-trump {
  left: max(calc(4 * var(--u)), 50% - calc(139.9 * var(--u)));
  gap: calc(8 * var(--u));
  pointer-events: none;
}
.eu-scores {
  right: max(calc(4 * var(--u)), 50% - calc(139.9 * var(--u)));
  /* (Both boxes as wide as the wider one.) */
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  gap: calc(6 * var(--u));
}
.eu-trump-chip {
  display: grid;
  place-items: center;
  height: 100%;
  aspect-ratio: 5 / 6;
  border-radius: calc(10 * var(--u));
  background: var(--card-face);
  box-shadow: inset 0 0 0 calc(2 * var(--u)) rgb(0 0 0 / 0.15), 0 calc(3 * var(--u)) calc(10 * var(--u)) rgb(0 0 0 / 0.3);
  color: #1a1a1a;
  font-size: calc(20.8 * var(--u));
  font-weight: 900;
  line-height: 1;
}
.eu-trump-chip.red { color: var(--card-red); }
/* The card the dealer picked up: its rank over its suit. */
.eu-trump-chip.taken { place-content: center; gap: 0.08em; font-size: calc(16 * var(--u)); }
.eu-trump-rank { font-size: 0.85em; }
.eu-trump-label {
  display: grid;
  color: var(--fg);
  line-height: 1.1;
  white-space: nowrap;
}
.eu-trump-word { font-size: calc(15.2 * var(--u)); font-weight: 800; }
.eu-trump-by { font-size: calc(10.88 * var(--u)); font-weight: 600; opacity: 0.8; }
/* The score: Us in blue, Them in red. */
.eu-score {
  height: 100%;
  /* (Square, or wider for a long score: 500's -250.) */
  min-width: calc(var(--felt-top) - calc(8 * var(--u)));
  padding: 0 calc(6 * var(--u));
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: calc(12 * var(--u));
  color: #fff;
  line-height: 1;
  box-shadow: 0 calc(4 * var(--u)) calc(12 * var(--u)) rgb(0 0 0 / 0.3);
}
.eu-score-points { font-size: calc(17.6 * var(--u)); font-weight: 900; font-variant-numeric: tabular-nums; }
/* (A long score, like 500's −250: smaller, so the boxes stay clear of the
   top seat's name.) */
.eu-score.long { padding: 0 calc(4 * var(--u)); }
.eu-score.long .eu-score-points { font-size: calc(14.4 * var(--u)); }

@keyframes eu-fade-out {
  to { opacity: 0; visibility: hidden; }
}
@keyframes eu-fade-in {
  from { opacity: 0; }
}
.button.eu-bid.chosen.red:disabled { color: var(--card-red); }

/* ---------- 500 (fivehundred.js) ---------- */

/* The Joker (fivehundred.js jokerEl): a struck-through J in the corners
   (the height of a rank there), a jester's hat in the middle. */
.playing-card.fh-joker { color: #7b4fc9; }
.fh-joker-index { display: block; width: 0.68em; height: 1em; }
.fh-joker-index svg { display: block; width: 100%; height: 100%; }
.playing-card.fh-joker .fh-joker-hat svg { display: block; width: calc(var(--card-w) * 0.8); height: auto; }

/* Bidding, under the kitty (fivehundred.js bidPanel): four rows of
   buttons the width of the panel, trick counts, suits, then Pass, Misère,
   Open misère and Bid, then Scoreboard, in Euchre's button style. The middle lifts by half
   of all that (.fh-lifted). */
.fh-panel {
  --btn: calc((205.8 * var(--u) - 4 * calc(8 * var(--u))) / 5);
  --btn-h: calc(var(--pile-w) * 0.55);
  --sb-h: calc(var(--pile-w) * 0.42); /* the Scoreboard row, slimmer */
  /* (Close under the kitty: up into the room kept below the middle for a
     trick's nudged cards, which there are none of while bidding.) */
  top: calc(100% - var(--pile-w) * 0.5 + calc(2 * var(--u)));
}
.fh-row {
  display: grid;
  gap: calc(8 * var(--u));
  width: 100%;
  height: var(--btn-h, calc(var(--pile-w) * 0.7));
}
.fh-row-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.fh-row-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.fh-row-1 { grid-template-columns: 1fr; height: var(--sb-h); width: 50%; }
/* Scoreboard: something to look at, not a bid, so quieter: an outline on
   the felt, no gold edge. */
.fh-row > .button.eu-bid.fh-scoreboard {
  background: rgb(255 255 255 / 0.08);
  box-shadow: inset 0 0 0 calc(1.5 * var(--u)) rgb(255 255 255 / 0.35);
  color: var(--felt-text);
}
.fh-row > .button.eu-bid.fh-scoreboard:active { background: rgb(255 255 255 / 0.16); }
.fh-row > .button.eu-bid { width: 100%; }
/* (And a little more, so the buttons stay clear of the bubble over your
   own circle at the bottom.) */
.table-stack.fh-lifted { --eu-lift: calc((3 * var(--pile-w) * 0.55 + var(--pile-w) * 0.42 + 4 * calc(6 * var(--u)) + var(--t-font) * 1.3 - var(--pile-w) * 0.5) / 2 + calc(46 * var(--u))); }
/* What you've picked so far: gold. */
.button.eu-bid.chosen:not(:disabled) { background: var(--turn); }
/* (The words on the bottom row, smaller to fit: "Open Misère" on two lines.) */
.fh-row-4 > .button.eu-bid.eu-word { font-size: calc(10.4 * var(--u)); }
.fh-two-lines { display: block; line-height: 1.05; }

/* The kitty's spot, empty once it's taken: glowing gold while you pick
   your discards (as Euchre's empty trick does on your lead). */
.eu-center.pile-button .pile-stack.fh-empty::before {
  display: block;
  border: calc(3 * var(--u)) solid var(--turn);
}
.eu-center.pile-button .pile-stack.fh-empty { animation: pile-glow 1.2s ease-in-out infinite alternate; }

/* The contract chip: a misère's M (or OM) a little smaller. */
.eu-trump-chip.fh-misere { font-size: calc(12 * var(--u)); }
.eu-trump-chip.fh-misere.open { font-size: calc(9.6 * var(--u)); line-height: 1.1; }
.eu-trump-chip .fh-nt { font-size: 0.8em; }
/* The Joker leading with no trumps: the suit it leads, in a badge on its
   top right. */
.fh-joker-as {
  position: absolute;
  top: calc(var(--card-w) * -0.12);
  right: calc(var(--card-w) * -0.12);
  display: grid;
  place-items: center;
  width: calc(var(--card-w) * 0.42);
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--card-face);
  color: #1a1a1a;
  font-size: calc(var(--card-w) * 0.26);
  line-height: 1;
  box-shadow: 0 0 0 calc(var(--card-w) * 0.03) #7b4fc9, 0 calc(1 * var(--u)) calc(3 * var(--u)) rgb(0 0 0 / 0.4);
}
.fh-joker-as.red { color: var(--card-red); }
.end-title.eu-result.fh-passed { color: var(--felt-text); }

/* The table of points (fivehundred.js pointsTableEl): the grid in the
   room's pop-up. */
.fh-board h2 { margin: 0; }
.fh-grid {
  display: grid;
  grid-template-columns: auto repeat(5, minmax(0, 1fr));
  gap: calc(4 * var(--u));
  width: 100%;
}
.fh-cell {
  display: grid;
  place-items: center;
  height: calc(28 * var(--u));
  border-radius: calc(6 * var(--u));
  background: var(--bg);
  font-size: calc(13.6 * var(--u));
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.fh-cell.head { background: none; color: var(--muted); font-size: calc(15.2 * var(--u)); padding: 0 calc(4 * var(--u)); }
.fh-cell.head.red { color: var(--card-red); }
.fh-cell.high { box-shadow: inset 0 0 0 calc(2 * var(--u)) var(--turn); }
.fh-cell.made { background: var(--done-ring); color: #0e2216; }
.fh-cell.set { background: var(--danger-strong); color: #fff; }
.fh-miseres {
  display: grid;
  grid-template-columns: auto 1fr auto 1fr;
  align-items: center;
  gap: calc(6 * var(--u));
  width: 100%;
}
.fh-misere-name { font-size: calc(13.6 * var(--u)); font-weight: 700; color: var(--muted); }
.fh-got { display: flex; justify-content: center; gap: calc(24 * var(--u)); }
.fh-got-team { display: grid; justify-items: center; gap: calc(2 * var(--u)); }
.fh-got-name { font-size: calc(13.6 * var(--u)); font-weight: 800; }
.fh-got-points { font-size: calc(22.4 * var(--u)); font-weight: 900; font-variant-numeric: tabular-nums; }
.fh-got-points.plus { color: var(--done-ring); }
.fh-got-points.minus { color: var(--danger-strong); }

/* ---------- The Crew (crew.js) ---------- */

/* The cards: each colour (and its shape, see crew.js) in its own ink. */
.playing-card.crew-B { color: #2f6fdc; }
.playing-card.crew-G { color: #23905a; }
.playing-card.crew-P { color: #d1407f; }
.playing-card.crew-Y { color: #c08a00; }
.playing-card.crew-R { color: #2a2a2a; background: #f3f0e6; }
.playing-card.crew-card .card-pip { font-size: calc(var(--card-w) * 0.55); font-weight: 800; }

/* A task card: unlike the playing cards, a solid block of its colour with
   the number and shape in white, so it can't be mistaken for a card shown
   (which stays white). */
.playing-card.crew-task { color: #fff; border-color: rgb(0 0 0 / 0.25); }
.playing-card.crew-task.crew-B { background: #2f6fdc; }
.playing-card.crew-task.crew-G { background: #23905a; }
.playing-card.crew-task.crew-P { background: #d1407f; }
.playing-card.crew-task.crew-Y { background: #c08a00; }
.playing-card.crew-task .card-corner,
.playing-card.crew-task .card-pip { text-shadow: 0 calc(1 * var(--u)) calc(2 * var(--u)) rgb(0 0 0 / 0.3); }

/* A task: its order token in a dark badge on the top right; a check once
   done (and dimmed); red for the one the mission failed on. */
.playing-card.crew-task .crew-token {
  position: absolute;
  top: calc(-6 * var(--u));
  right: calc(-6 * var(--u));
  min-width: 1.5em;
  padding: 0 0.3em;
  border-radius: calc(999 * var(--u));
  background: #1d2433;
  color: #fff;
  font-size: max(calc(9.6 * var(--u)), calc(var(--card-w) * 0.3));
  line-height: 1.5;
  text-align: center;
  box-shadow: 0 0 0 calc(1.5 * var(--u)) #fff;
}
.playing-card.crew-task.done { filter: brightness(0.8); } /* (still its colour) */
/* A task done: the check a player's circle gets when they're done (a
   green check in a dark circle), in the middle of the card. */
.playing-card.crew-task .crew-check {
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  rotate: calc(-1 * var(--angle, 0deg)); /* (upright, however the card is turned) */
  display: grid;
  place-items: center;
  width: calc(var(--card-w) * 0.7);
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--seat-bg);
  border: calc(var(--card-w) * 0.04) solid var(--seat-edge);
  box-shadow: 0 calc(1 * var(--u)) calc(3 * var(--u)) rgb(0 0 0 / 0.4);
}
.playing-card.crew-zero.failed,
.playing-card.crew-task.failed { box-shadow: 0 0 0 calc(3 * var(--u)) var(--danger-strong), 0 0 calc(10 * var(--u)) var(--danger-strong); }

/* Mission 5: in front of whoever must win no tricks, a dark card with a
   big 0 (crew.js zeroEl). */
.playing-card.crew-zero { background: #2a2f3a; color: #fff; border-color: #000; }
.playing-card.crew-zero .card-pip { font-size: calc(var(--card-w) * 0.6); font-weight: 900; }
/* Mission 5: "How do you feel?" in the middle: Good and Bad, as Euchre's
   buttons. */
.crew-feel { display: grid; justify-items: center; }

/* A card shown (communication): a blue radio token at its top (highest),
   middle (only) or bottom (lowest) edge. In your hand, that card is
   outlined in the same blue. */
.playing-card.crew-shown .crew-radio,
.playing-card.crew-moon .crew-radio {
  position: absolute;
  left: 50%;
  width: calc(var(--card-w) * 0.38);
  aspect-ratio: 1;
  translate: -50% -50%;
  border-radius: 50%;
  background: #3fa7ff;
  border: calc(2 * var(--u)) solid #fff;
  box-shadow: 0 calc(1 * var(--u)) calc(4 * var(--u)) rgb(0 0 0 / 0.4);
}
.crew-radio.top { top: 0; }
.crew-radio.only { top: 50%; }
.crew-radio.bottom { top: 100%; }
/* Used (crew.js usedMoonEl): in the middle of the moon card, red; moving
   there from where it was on the card shown, as that card is played. */
.playing-card.crew-moon .crew-radio.used { top: 50%; background: #e53935; }
.crew-radio.used.from-top { animation: radio-used-top 500ms ease-in-out both; }
.crew-radio.used.from-only { animation: radio-used-only 500ms ease-in-out both; }
.crew-radio.used.from-bottom { animation: radio-used-bottom 500ms ease-in-out both; }
@keyframes radio-used-top { from { top: 0; background: #3fa7ff; } }
@keyframes radio-used-only { from { background: #3fa7ff; } }
@keyframes radio-used-bottom { from { top: 100%; background: #3fa7ff; } }
/* A card flying between the table, your hand and the seats (ui/fly.js
   flyGhost; The Crew, Hanabi): on the page, over everything. */
.playing-card.fly-ghost {
  position: fixed;
  z-index: 15;
  margin: 0;
  pointer-events: none;
  inset: auto;
  bottom: auto;
}

/* The tasks to choose from, in a row in the middle (tap one on your turn). */
.crew-tasks {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: calc(8 * var(--u));
  max-width: calc(315.56 * var(--u));
  --card-w: calc(var(--pile-w) * 0.8);
}
.crew-task-pick {
  padding: 0;
  border: 0;
  background: none;
  border-radius: max(calc(3 * var(--u)), calc(var(--card-w) * 0.108));
  cursor: pointer;
  touch-action: manipulation;
}
.crew-task-pick .playing-card { box-shadow: 0 0 0 calc(3 * var(--u)) var(--turn), 0 calc(2 * var(--u)) calc(8 * var(--u)) rgb(0 0 0 / 0.3); }
.crew-task-pick:active { transform: scale(0.96); }

/* The trick, each card nudged toward whoever played it (as Euchre's; crew.js
   sets --dx and --dy from the seat's direction). */
.crew-trick-cards { position: absolute; inset: 0; }
.crew-trick-cards.collecting { z-index: 5; }
.crew-center .pile-stack { --card-w: var(--pile-w); }
.crew-center { margin: calc(var(--pile-w) * 0.5) 0; }
.crew-center .pile-stack::before { display: none; }
.crew-center.pile-button .crew-trick:not(:has(.playing-card))::before {
  display: block;
  border: calc(3 * var(--u)) solid var(--turn);
}
.crew-center.pile-button .crew-trick:not(:has(.playing-card)) { animation: pile-glow 1.2s ease-in-out infinite alternate; }
/* The mission's result, in the middle of the table. */
.end-title.crew-result { bottom: auto; top: 50%; translate: -50% -50%; }
/* (Above the trick a mission failed on, which stays to be seen.) */
.end-title.crew-result.over-trick { top: auto; bottom: calc(100% + var(--pile-w) * 0.55); translate: -50% 0; }
/* The moon card: black with a grey full moon, in front of each seat until its
   player shows a card (then in their hand in that card's place). Yours
   glows once you've picked a card you may show. */
.playing-card.crew-moon {
  background: #111;
  border-color: #000;
}
.playing-card.crew-moon .crew-moon-art { padding: 0; }
/* In your hand, for the card you've shown: that card's number and colour
   (brighter, on the black) over a faded moon. */
.playing-card.crew-moon.held .crew-moon-art { position: absolute; inset: 0; display: grid; place-items: center; opacity: 0.4; }
.playing-card.crew-moon.held .card-pip:not(.crew-moon-art) { text-shadow: 0 0 calc(4 * var(--u)) #000; }
.playing-card.crew-moon.held.crew-B { color: #6aa0ff; }
.playing-card.crew-moon.held.crew-G { color: #4fd18e; }
.playing-card.crew-moon.held.crew-P { color: #ff6fae; }
.playing-card.crew-moon.held.crew-Y { color: #ffc93a; }
.crew-moon-art svg { width: calc(var(--card-w) * 0.82); height: auto; display: block; }
.crew-slot-tap { pointer-events: auto; padding: 0; border: 0; background: none; cursor: pointer; touch-action: manipulation; }
.crew-slot-tap .crew-moon { pointer-events: auto; }
.crew-slot-tap.ready .crew-moon {
  box-shadow: 0 0 0 calc(2 * var(--u)) #3fa7ff;
  animation: slot-glow 1.2s ease-in-out infinite alternate;
}
@keyframes slot-glow {
  from { box-shadow: 0 0 calc(4 * var(--u)) #3fa7ff; }
  to { box-shadow: 0 0 calc(16 * var(--u)) #3fa7ff; }
}

/* ---------- Hanabi (hanabi.js) ---------- */

/* The colours: each card is solid in its colour, with its number in the
   ink that reads on it (hanabi.js hbCardEl). */
.hb-R { --hb-c: #d8343a; --hb-ink: #fff; }
.hb-Y { --hb-c: #e9b40f; --hb-ink: #2b2200; }
.hb-G { --hb-c: #2f9d58; --hb-ink: #fff; }
.hb-B { --hb-c: #2f6fdc; --hb-ink: #fff; }
.hb-W { --hb-c: #f3f3ef; --hb-ink: #34363a; }
.playing-card.hb-card {
  background: var(--hb-c);
  color: var(--hb-ink);
  border-color: rgb(0 0 0 / 0.3);
}
.playing-card.hb-card .card-pip {
  font-size: calc(var(--card-w) * 0.62);
  font-weight: 900;
}
/* (The colour's shape in the corner: in proportion at every size.) */
.playing-card .card-corner.hb-sym { font-size: calc(var(--card-w) * 0.24); }
/* (A card flying from a seat keeps its number all the way: it shows there.) */
.playing-card.hb-card.from-seat .card-pip { animation: none; }
/* (The card played from a seat holds its spot, unseen, while it flies.) */
.seat-cards .hb-leaving { visibility: hidden; }

/* Someone else's card they've been told about: a white edge. */
.seat-cards .playing-card.hb-clued { box-shadow: 0 0 0 max(calc(1.5 * var(--u)), var(--card-w) * 0.05) #fff, 0 calc(1 * var(--u)) calc(3 * var(--u)) rgb(0 0 0 / 0.3); }
/* What a card's player has been told (hanabi.js boxesOf): dark boxes as
   round as the card, tucked into its corners on the edge toward the middle
   of the table, a margin (--m) from its edges: the number on the left, the
   colour on the right. Each holds --n items one under another, a quarter
   of the card's height (less the margins) each, so four reach the far
   corner. In proportion to the card, the same margin from its top and its
   side; what's in them turned as the card is, centered on what shows of it
   (text-box). A card at the top
   turned to face you has its edge toward the middle at its bottom. */
.hb-tok {
  --m: calc(var(--card-w) * 0.05);
  --slot: calc((var(--card-w) * 1.4 - var(--m) * 2) / 4);
  position: absolute;
  top: var(--m);
  z-index: 2;
  width: calc(var(--card-w) * 0.42);
  height: calc(var(--slot) * var(--n, 1));
  border-radius: max(calc(3 * var(--u)), calc(var(--card-w) * 0.108));
  background: #15181d;
  box-shadow: 0 0 0 max(calc(0.5 * var(--u)), var(--card-w) * 0.012) rgb(255 255 255 / 0.35);
  overflow: hidden;
}
.hb-tok-num { left: var(--m); }
.hb-tok-col { right: var(--m); }
.seat-spot[data-side="top"] .hb-tok { top: auto; bottom: var(--m); }
.hb-tok-item {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(var(--slot) * var(--i, 0));
  height: var(--slot);
  display: grid;
  place-items: center;
  color: #fff;
  font-size: calc(var(--card-w) * 0.25);
  font-weight: 900;
  line-height: 1;
  text-box: trim-both cap alphabetic;
}
.hb-tok-col .hb-tok-item { color: var(--hb-c); }
/* Growing or shrinking to its new number of items (from --from-n), and an
   item moving up (from --d places further down), as a clue rules one out. */
.hb-tok.hb-resize { animation: hb-tok-size 450ms cubic-bezier(0.3, 0.7, 0.3, 1) both; }
@keyframes hb-tok-size { from { height: calc(var(--slot) * var(--from-n)); } }
.hb-tok-item.hb-shift { animation: hb-tok-shift 450ms cubic-bezier(0.3, 0.7, 0.3, 1) both; }
@keyframes hb-tok-shift { from { translate: 0 calc(var(--slot) * var(--d)); } }
.hb-tok.hb-going-box { animation: hb-going-box 600ms ease-in both; }
/* A seat a clue's tokens are on their way to (hanabi.js flyClue): over the
   other seats, so nothing covers them. */
.seat-spot.hb-clued-to { z-index: 5; }
.seat-cards .playing-card.hb-token-to { z-index: 3; }

/* What a clue just said fades in (the tab under someone's card; on your
   own card, its colour or number). hanabi.js times it from the clue. */
.hb-news { animation: hb-news-in 600ms ease-out both; }
@keyframes hb-news-in { from { opacity: 0; } }
/* The cards a clue points to light up for a moment (hanabi.js
   --touch-delay: since the clue). */
.seat-cards .playing-card.hb-touched { animation: hb-touch 1200ms ease-out var(--touch-delay, 0ms) both; }
@keyframes hb-touch {
  0% { box-shadow: 0 0 0 max(calc(1.5 * var(--u)), var(--card-w) * 0.05) #fff, 0 0 0 rgb(255 255 255 / 0); }
  25% { box-shadow: 0 0 0 calc(var(--card-w) * 0.1) #fff, 0 0 calc(var(--card-w) * 0.5) rgb(255 255 255 / 0.9); }
  100% { box-shadow: 0 0 0 max(calc(1.5 * var(--u)), var(--card-w) * 0.05) #fff, 0 0 0 rgb(255 255 255 / 0); }
}
/* Your own cards at your seat (hanabi.js myRow): on your turn, tap one to
   pick it: it lifts toward the middle, outlined. */
.seat-cards button.playing-card.hb-mine {
  padding: 0;
  font: inherit;
  cursor: pointer;
  touch-action: manipulation;
  pointer-events: auto;
  transition: bottom 0.12s ease;
}
.seat-cards .playing-card.hb-mine.selected {
  --pick: calc(var(--card-w) * 0.3);
  box-shadow: var(--back-edge), 0 0 0 calc(2.5 * var(--u)) var(--accent), 0 calc(3 * var(--u)) calc(8 * var(--u)) rgb(0 0 0 / 0.3);
}
.seat-cards button.playing-card.hb-mine:focus-visible { outline: calc(2.5 * var(--u)) solid var(--accent); outline-offset: calc(2 * var(--u)); }
.seat-cards .playing-card.hb-mine.hb-touched,
.hand-slot .playing-card.hb-mine.hb-touched { animation: hb-touch-mine 1200ms ease-out var(--touch-delay, 0ms) both; }
@keyframes hb-touch-mine {
  0%, 100% { box-shadow: var(--back-edge), 0 0 0 rgb(255 255 255 / 0); }
  25% { box-shadow: var(--back-edge), 0 0 0 calc(3 * var(--u)) #fff, 0 0 calc(18 * var(--u)) rgb(255 255 255 / 0.9); }
}

/* Your own cards (hanabi.js myCardEl): a back, its colour filling it once
   you know it; what you know, or what it could still be, in its boxes. */
.hb-mine .hb-fill {
  position: absolute;
  inset: calc(var(--card-w) * 0.07);
  border-radius: calc(var(--card-w) * 0.05);
  background: var(--hb-c);
  color: var(--hb-ink);
}
.hb-mine .hb-fill .card-corner { top: calc(var(--card-w) * 0.01); left: calc(var(--card-w) * 0.02); }
@keyframes hb-going-box { to { opacity: 0; } }
.hb-going { animation: hb-going 900ms ease-in both; }
@keyframes hb-going { to { opacity: 0; scale: 0.4; } }

/* The middle (hanabi.js centerEl): the fireworks in a row, under them the
   deck, the tokens and the discards. Cards here are smaller than a pile's
   (--hb-w), so five fit across. */
.hb-center {
  --hb-w: calc(var(--pile-w) * 0.5);
  gap: calc(10 * var(--u));
}
.hb-center .pile-stack { --card-w: var(--hb-w); width: var(--hb-w); margin: 0; }
/* (Its text clear of the columns of cards at the side seats.) */
.hb-center > .table-above { width: calc(var(--hb-w) * 6.2); }
.hb-center.hb-tall > .table-above { width: calc(var(--hb-w) * 4.4); }
/* The buttons at the end, over the deck, tokens and discards (which fade
   away under them); on a tall screen one above another, to fit between the
   hands at the sides. */
.hb-supply { position: relative; }
.hb-supply.hb-ended > :not(.hb-end) { animation: hb-fade-away 300ms ease-in var(--since, 0ms) both; visibility: hidden; }
@keyframes hb-fade-away { from { opacity: 1; visibility: visible; } to { opacity: 0; } }
.hb-end { position: absolute; top: 0; left: 50%; translate: -50% 0; z-index: 5; }
.hb-end .end-actions { width: calc(var(--hb-w) * 6.4); gap: calc(8 * var(--u)); }
.hb-end .end-actions .button { min-height: calc(36 * var(--u)); font-size: calc(13 * var(--u)); padding-inline: calc(6 * var(--u)); white-space: nowrap; }
.hb-tall .hb-end .end-actions { grid-template-columns: 1fr; width: calc(var(--hb-w) * 3.6); }
.hb-tall .hb-end .end-leave, .hb-tall .hb-end .end-waiting { width: auto; justify-self: stretch; }
.hb-fireworks { display: flex; gap: calc(var(--hb-w) * 0.18); }
/* On a tall screen (hanabi.js shape) the middle stacks up, narrow enough
   for the hands at the sides: bigger cards, the fireworks in three rows (two,
   two, then one). */
.hb-center.hb-tall {
  --hb-w: calc(var(--pile-w) * 0.605);
  top: calc(10 * var(--u)); /* (its text clear of the top player's cards) */
}
.hb-center.hb-tall .hb-fireworks {
  flex-wrap: wrap;
  justify-content: center;
  row-gap: calc(var(--hb-w) * 0.18);
  width: calc(var(--hb-w) * 2.18);
}
.hb-center.hb-tall .hb-supply {
  display: grid;
  grid-template-columns: auto auto;
  grid-template-areas: "tokens tokens" "deck discards";
  justify-content: center;
  justify-items: center;
  gap: calc(10 * var(--u)) calc(14 * var(--u));
}
/* (Then, on its own row under the tokens, the deck and the discards.) */
.hb-tall .hb-supply > .hb-tokens { grid-area: tokens; }
.hb-tall .hb-supply > .hb-deck { grid-area: deck; }
.hb-tall .hb-supply > .hb-discards { grid-area: discards; }
.hb-fireworks.pile-button { border-radius: calc(10 * var(--u)); }
.hb-slot { display: block; }
/* A firework not started: its outline and shape, faintly, in its colour. */
.hb-center .pile-stack.hb-empty::before { border-color: color-mix(in srgb, var(--hb-c) 55%, transparent); }
.hb-empty-sym {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--hb-c);
  opacity: 0.45;
  font-size: calc(var(--hb-w) * 0.42);
}
/* With a card picked, the fireworks light up as one: a single gold border
   round all five (a card goes on its own colour's firework, wherever you
   tap), not each on its own. */
.hb-fireworks::after {
  content: "";
  position: absolute;
  inset: calc(-6 * var(--u));
  border: calc(2.5 * var(--u)) solid var(--turn);
  border-radius: calc(12 * var(--u));
  opacity: 0;
  pointer-events: none;
  transition: opacity 150ms ease;
}
.hb-fireworks.pile-button.ready::after { opacity: 1; animation: hb-glow 1.2s ease-in-out infinite alternate; }
@keyframes hb-glow {
  from { box-shadow: 0 0 calc(3 * var(--u)) var(--turn), inset 0 0 calc(3 * var(--u)) var(--turn); }
  to { box-shadow: 0 0 calc(14 * var(--u)) var(--turn), inset 0 0 calc(8 * var(--u)) var(--turn); }
}
.hb-fireworks.pile-button.ready .pile-stack { animation: none; }
.hb-fireworks.pile-button.ready .pile-stack::before { border-color: var(--felt-rim); }
.hb-fireworks.pile-button.ready .pile-stack.hb-empty::before { border-color: color-mix(in srgb, var(--hb-c) 55%, transparent); }
@media (prefers-reduced-motion: reduce) {
  .hb-fireworks.pile-button.ready::after { animation: none; box-shadow: 0 0 calc(8 * var(--u)) var(--turn); }
}
.hb-discards.pile-button.ready .pile-stack.hb-empty::before { border-color: var(--turn); }
.hb-supply { display: flex; align-items: center; justify-content: center; gap: calc(18 * var(--u)); }
.hb-supply .pile-count { font-size: max(calc(10 * var(--u)), calc(var(--hb-w) * 0.27)); }
.hb-discards { padding: 0; border: 0; background: none; font: inherit; cursor: pointer; touch-action: manipulation; }

/* The clue tokens (blue, lit while there) and fuses (flames, dark once
   burnt). A token's change is drawn by its ::after, timed by --d. */
.hb-tokens { display: grid; justify-items: center; gap: calc(6 * var(--u)); }
.hb-clues { display: grid; grid-template-columns: repeat(4, auto); gap: calc(4 * var(--u)); }
.hb-clue {
  position: relative;
  width: calc(13 * var(--u));
  aspect-ratio: 1;
  border-radius: 50%;
  background: rgb(0 0 0 / 0.35);
  box-shadow: inset 0 0 0 calc(1.5 * var(--u)) rgb(255 255 255 / 0.18);
}
.hb-clue::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #a9dcff, #2f7fe0 70%);
  box-shadow: 0 0 0 calc(1.5 * var(--u)) #0d2a4d;
  opacity: 0;
}
.hb-clue.lit::after { opacity: 1; }
.hb-clue.hb-spend::after { animation: hb-spend 650ms ease-in var(--d, 0ms) both; }
@keyframes hb-spend {
  from { opacity: 1; scale: 1; }
  30% { opacity: 1; scale: 1.3; }
  to { opacity: 0; scale: 0.6; }
}
.hb-clue.hb-regain::after { animation: hb-regain 500ms cubic-bezier(0.3, 1.4, 0.5, 1) var(--d, 0ms) both; }
@keyframes hb-regain { from { opacity: 0; scale: 0.3; } }
.hb-fuses { display: flex; gap: calc(5 * var(--u)); }
.hb-fuse { display: block; width: calc(15 * var(--u)); line-height: 0; }
.hb-fuse .emoji { width: 100%; height: auto; }
.hb-fuse.burnt .emoji { opacity: 0.22; }
.hb-fuse.hb-burning .emoji { animation: hb-burn 900ms ease-out var(--d, 0ms) both; }
@keyframes hb-burn {
  from { opacity: 1; scale: 1; }
  30% { opacity: 1; scale: 1.7; }
  to { opacity: 0.22; scale: 1; }
}

/* Panels over the middle (giving a clue, the discards): they fade in and
   out (hanabi.js overlayStyle). */
.hb-panel {
  position: absolute;
  z-index: 5;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  display: grid;
  justify-items: center;
  gap: calc(8 * var(--u));
  padding: calc(10 * var(--u)) calc(12 * var(--u));
  border: 0;
  border-radius: calc(14 * var(--u));
  background: color-mix(in srgb, var(--felt-2) 88%, black);
  box-shadow: inset 0 0 0 calc(2 * var(--u)) var(--felt-rim), 0 calc(6 * var(--u)) calc(20 * var(--u)) rgb(0 0 0 / 0.45);
  color: var(--felt-text);
  font: inherit;
}
@keyframes hb-panel-in { from { opacity: 0; scale: 0.92; } }
@keyframes hb-panel-out { to { opacity: 0; scale: 0.92; } }
.hb-panel.eu-panel { --btn: calc(var(--pile-w) * 0.46); --btn-h: calc(var(--pile-w) * 0.52); }
.hb-panel .eu-buttons { gap: calc(var(--pile-w) * 0.11); }
/* (The clue panel is always the same size, room for every colour, whichever
   it offers.) */
.hb-panel.eu-panel { width: calc(var(--btn) * 5 + var(--pile-w) * 0.11 * 4); }
.button.eu-bid.hb-color-btn:not(:disabled) { background: var(--hb-c); color: var(--hb-ink); font-size: calc(var(--btn) * 0.5); }
.button.eu-bid.hb-color-btn:disabled { font-size: calc(var(--btn) * 0.5); }
.button.hb-cancel { min-height: calc(34 * var(--u)); padding: 0 calc(22 * var(--u)); }
/* The discards, by colour (tap to close). */
.hb-list { cursor: pointer; touch-action: manipulation; }
.hb-list-rows { display: grid; gap: calc(5 * var(--u)); justify-items: start; }
.hb-list-row { display: flex; align-items: center; gap: calc(3 * var(--u)); min-height: calc(25.2 * var(--u)); }
.hb-list-row .playing-card { --card-w: calc(18 * var(--u)); }
.hb-list-row .card-corner.hb-sym { display: none; }
.hb-list-none { color: var(--hb-c); opacity: 0.5; font-size: calc(14 * var(--u)); }

/* The result, above the fireworks, clear of the side seats' columns; a
   loss shakes. */
.end-title.hb-result { font-size: calc(var(--t-font) * 1.3); }
.end-title.hb-result.lost { animation: end-in 300ms ease-out var(--since, 0ms) both, title-shake 900ms ease-out calc(var(--since, 0ms) + 300ms) both; }

@media (prefers-reduced-motion: reduce) {
  .hb-news, .hb-going, .seat-cards .playing-card.hb-touched, .hand-slot .playing-card.hb-mine.hb-touched { animation-duration: 1ms; }
}
