/* Western pixel-art theme. Loaded after styles.css, avatars.css and
   history-stats.css: those keep the layout and behaviour, this file owns the
   look. Square corners, hard 3px outlines, flat colours, hard drop shadows.
   The sprites (table, wall, chips, card backs, icons) are generated by
   src/client/pixel-art.ts into the --px-* custom properties. */

/* JetBrains Mono (OFL, public/fonts/jetbrains-mono-OFL.txt), self-hosted. The
   display face is the same font drawn at 80%: the display sizes below were set
   for a much narrower pixel font, and this keeps headings and buttons on one
   line without retuning every size. */
@font-face {
  font-family: "JetBrains Mono";
  src: url("/fonts/jetbrains-mono.woff2") format("woff2");
  font-weight: 100 800;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono Display";
  src: url("/fonts/jetbrains-mono.woff2") format("woff2");
  font-weight: 100 800;
  font-display: swap;
  size-adjust: 80%;
}

/* Titles keep the Jersey 10 pixel font (OFL, licence beside it). */
@font-face {
  font-family: "Jersey 10";
  src: url("/fonts/jersey-10.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
/* Player names and the table's lettering: Monocraft, a Minecraft-style pixel
   font (OFL), trimmed to the Latin letters and symbols the site uses. It
   runs large, so it is drawn at 82% to keep the sizes set for the old font. */
/* Rye (Sorkin Type, SIL Open Font License, see rye-OFL.txt): the card
   packs' Wild West "Saloon". */
@font-face {
  font-family: "Rye";
  src: url("/fonts/rye.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

/* Macondo (John Vargas Beltrán, SIL Open Font License, see macondo-OFL.txt):
   the Pharaoh Pack's title, in the rough, flared strokes of a hand on
   papyrus. */
@font-face {
  font-family: "Macondo";
  src: url("/fonts/macondo.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

/* Creepster (Font Diner, SIL Open Font License, see creepster-OFL.txt): the
   Spooky Event Pack's dripping horror-film title. */
@font-face {
  font-family: "Creepster";
  src: url("/fonts/creepster.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

@font-face {
  font-family: "Monocraft";
  src: url("/fonts/monocraft.woff") format("woff");
  font-weight: 400;
  font-display: swap;
  size-adjust: 82%;
}
@font-face {
  font-family: "Monocraft";
  src: url("/fonts/monocraft-semibold.woff") format("woff");
  font-weight: 600;
  font-display: swap;
  size-adjust: 82%;
}
@font-face {
  font-family: "Monocraft";
  src: url("/fonts/monocraft-bold.woff") format("woff");
  font-weight: 700;
  font-display: swap;
  size-adjust: 82%;
}

/* At the table, text is pixel lettering but numbers stay in JetBrains Mono:
   one family that takes its letters from Monocraft and its digits and money
   signs ($ % + , . / ½ × ⅓ −) from JetBrains Mono, so "Call $10" mixes the two
   letter by letter. Each weight has its own pair of faces with the same
   descriptors (and ranges that don't overlap), or browsers treat the halves
   as separate fonts and the digits fall back to the pixel font. */
@font-face {
  font-family: "Saloon Mix";
  src: url("/fonts/monocraft.woff") format("woff");
  font-weight: 400;
  font-display: swap;
  size-adjust: 82%;
  unicode-range: U+0000-0023, U+0026-002A, U+002D, U+003A-00BC, U+00BE-00D6, U+00D8-2152, U+2154-2211, U+2213-10FFFF;
}
@font-face {
  font-family: "Saloon Mix";
  src: url("/fonts/jetbrains-mono.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0030-0039, U+0024, U+0025, U+002B, U+002C, U+002E, U+002F, U+00BD, U+00D7, U+2153, U+2212;
  size-adjust: 90%;
}
@font-face {
  font-family: "Saloon Mix";
  src: url("/fonts/monocraft-semibold.woff") format("woff");
  font-weight: 600;
  font-display: swap;
  size-adjust: 82%;
  unicode-range: U+0000-0023, U+0026-002A, U+002D, U+003A-00BC, U+00BE-00D6, U+00D8-2152, U+2154-2211, U+2213-10FFFF;
}
@font-face {
  font-family: "Saloon Mix";
  src: url("/fonts/jetbrains-mono.woff2") format("woff2");
  font-weight: 600;
  font-display: swap;
  unicode-range: U+0030-0039, U+0024, U+0025, U+002B, U+002C, U+002E, U+002F, U+00BD, U+00D7, U+2153, U+2212;
  size-adjust: 90%;
}
@font-face {
  font-family: "Saloon Mix";
  src: url("/fonts/monocraft-bold.woff") format("woff");
  font-weight: 700;
  font-display: swap;
  size-adjust: 82%;
  unicode-range: U+0000-0023, U+0026-002A, U+002D, U+003A-00BC, U+00BE-00D6, U+00D8-2152, U+2154-2211, U+2213-10FFFF;
}
@font-face {
  font-family: "Saloon Mix";
  src: url("/fonts/jetbrains-mono.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
  unicode-range: U+0030-0039, U+0024, U+0025, U+002B, U+002C, U+002E, U+002F, U+00BD, U+00D7, U+2153, U+2212;
  size-adjust: 90%;
}

:root {
  --ink: #ece0c2;
  --muted: #a89272;
  --plum: #2a1d14;
  --plum-deep: #160f0a;
  --gold: #d9b05c;
  --gold-deep: #a67c32;
  --gold-dark: #6b4f1c;
  --red: #a8392a;
  --red-deep: #5e1c13;
  --green: #7c9a5a;
  --green-deep: #4a5e35;
  --blue: #5a7a96;
  --felt: #365436;
  --felt-deep: #253a24;
  --leather: #5e3820;
  --leather-deep: #3a2212;
  --card-ink: #1e1611;
  --card-red: #9a2d20;
  --line: rgba(236, 224, 194, 0.16);
  --panel: #2a1d14;
  --display: "JetBrains Mono Display", ui-monospace, monospace;
  --body: "JetBrains Mono", ui-monospace, monospace;
  --title: "Jersey 10", "JetBrains Mono Display", monospace;
  --name: "Monocraft", "JetBrains Mono", monospace;
  --mix: "Saloon Mix", "Monocraft", monospace;

  --outline: #120b07;
  --wood: #5e3e29;
  --wood-hi: #7a5236;
  --wood-lo: #2e1d12;
  --panel-rim: #4a3324;
  --well: #1a120c;
  --paper: #e9dcbc;
  --paper-shade: #c9b68c;
  --sage-text: #c9d6a8;
  --drop: 5px 5px 0 rgba(0, 0, 0, 0.45);
  /* Cut corners, one 3px step, for buttons. */
  --notch: polygon(
    0 3px, 3px 3px, 3px 0, calc(100% - 3px) 0, calc(100% - 3px) 3px, 100% 3px,
    100% calc(100% - 3px), calc(100% - 3px) calc(100% - 3px), calc(100% - 3px) 100%,
    3px 100%, 3px calc(100% - 3px), 0 calc(100% - 3px)
  );
  scrollbar-color: var(--gold-dark) var(--well);
  /* The sprites are small PNGs scaled up: keep their pixels hard-edged. */
  image-rendering: pixelated;
}

/* Pixel art has no round corners. */
*,
*::before,
*::after {
  border-radius: 0 !important;
}

body {
  font-weight: 500;
  /* No coding ligatures ("->", "!=") in game text. */
  font-variant-ligatures: none;
  background: var(--plum-deep);
}

/* A dark saloon wall of wooden planks. */
body::before {
  background: var(--px-wall) 0 0 / 240px 160px repeat, #251911;
}

/* At the table the room is seen from above, like the table: floorboards in
   the dim of a saloon at night, darkest at the edges of the screen. */
body[data-screen="table"]::before,
.entry-floor {
  background:
    radial-gradient(ellipse 85% 80% at 45% 48%, transparent 25%, rgb(6 4 2 / 0.88) 100%),
    linear-gradient(rgb(12 8 4 / 0.55), rgb(12 8 4 / 0.55)),
    var(--px-floor) 0 0 / 320px 150px repeat,
    #2f1f10;
  filter: brightness(0.7);
}

/* The floor round the table: the lamp's pool of light, and side
   tables whose candles flicker and throw a wavering glow on the boards. */
.saloon-room {
  display: none;
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  --cell: 4px;
}

body[data-screen="table"] .saloon-room {
  display: block;
  filter: brightness(0.7);
}

.table-light,
.side-table {
  position: absolute;
  translate: -50% -50%;
}

/* The lamp hanging over the table: a warm pool on the floor that wavers. */
.table-light {
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgb(255 180 100 / 0.2),
    rgb(255 150 70 / 0.1) 55%,
    transparent 100%
  );
  animation: candle-glow 5.1s ease-in-out infinite;
}

.side-table {
  width: calc(var(--cell) * 32);
  height: calc(var(--cell) * 32);
  background: var(--px-side-table) 0 0 / 100% 100% no-repeat;
  image-rendering: pixelated;
  filter: brightness(0.75);
}

/* The bar, tucked into the bottom-left corner, with a candle of its own. */
.saloon-bar {
  position: absolute;
  left: 0;
  bottom: 0;
  width: calc(var(--cell) * 84);
  height: calc(var(--cell) * 62);
  background: var(--px-bar) 0 0 / 100% 100% no-repeat;
  image-rendering: pixelated;
  filter: brightness(0.55);
}

.saloon-bar::before {
  content: "";
  position: absolute;
  z-index: 1;
  left: calc(var(--cell) * 4);
  top: calc(var(--cell) * 33);
  width: calc(var(--cell) * 5);
  height: calc(var(--cell) * 5);
  background: var(--px-flame) 0 0 / 100% 300% no-repeat;
  image-rendering: pixelated;
  animation: flame-frames 0.6s steps(3) infinite -0.35s;
}

.saloon-bar::after {
  content: "";
  position: absolute;
  left: calc(var(--cell) * 6.5);
  top: calc(var(--cell) * 35.5);
  width: 26rem;
  height: 26rem;
  translate: -50% -50%;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgb(255 205 130 / 0.32),
    rgb(255 165 85 / 0.16) 22%,
    rgb(225 125 55 / 0.07) 50%,
    transparent 100%
  );
  animation: candle-glow 3s ease-in-out infinite -0.8s;
}

/* The flame, on the wick in the middle of the table. */
.side-table::before {
  content: "";
  position: absolute;
  z-index: 1;
  left: calc(50% - var(--cell) * 3);
  top: calc(50% - var(--cell) * 3);
  width: calc(var(--cell) * 5);
  height: calc(var(--cell) * 5);
  background: var(--px-flame) 0 0 / 100% 300% no-repeat;
  image-rendering: pixelated;
  animation: flame-frames 0.54s steps(3) infinite;
}

/* The candle's light on the table and the floor round it. */
.side-table::after {
  content: "";
  position: absolute;
  left: calc(50% - var(--cell) * 0.5);
  top: calc(50% - var(--cell) * 0.5);
  width: 28rem;
  height: 28rem;
  translate: -50% -50%;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgb(255 205 130 / 0.36),
    rgb(255 165 85 / 0.18) 22%,
    rgb(225 125 55 / 0.08) 50%,
    transparent 100%
  );
  animation: candle-glow 3.3s ease-in-out infinite;
}

.side-table:nth-child(4)::before {
  animation-duration: 0.66s;
  animation-delay: -0.2s;
}

.side-table:nth-child(4)::after {
  animation-duration: 2.7s;
  animation-delay: -1.3s;
}

/* Halloween, through October (main.ts sets data-season): jack-o'-lanterns
   in place of the candles in the middle of the side tables and on the bar
   counter (the candles' glow stays, lighting them), cobwebs in the top
   corners, a spider bobbing on its thread and two bats flapping under the
   ceiling. */
.spooky {
  display: none;
  position: absolute;
  image-rendering: pixelated;
  background: 0 0 / 100% 100% no-repeat;
}

body[data-season="halloween"] .spooky {
  display: block;
}

body[data-season="halloween"] .side-table::before,
body[data-season="halloween"] .saloon-bar::before {
  display: none;
}

.pumpkin {
  z-index: 2;
  left: calc(50% - var(--cell) * 6);
  top: calc(50% - var(--cell) * 6.5);
  width: calc(var(--cell) * 12);
  height: calc(var(--cell) * 10.3);
  background-image: var(--px-pumpkin);
  filter: drop-shadow(0 0 calc(var(--cell) * 2) rgb(255 150 40 / 0.55));
  animation: pumpkin-glow 2.4s ease-in-out infinite;
}

/* The bar's pumpkin stands on the counter in place of its candle, centred
   on the candle's glow. */
.saloon-bar .pumpkin {
  left: calc(var(--cell) * 0.5);
  top: calc(var(--cell) * 30.3);
  width: calc(var(--cell) * 12);
  height: calc(var(--cell) * 10.3);
  animation-delay: -1.1s;
}

.side-table:nth-child(3) .pumpkin {
  animation-delay: -0.6s;
}

@keyframes pumpkin-glow {
  0%, 100% { filter: drop-shadow(0 0 calc(var(--cell) * 2) rgb(255 150 40 / 0.5)) brightness(1); }
  30% { filter: drop-shadow(0 0 calc(var(--cell) * 4) rgb(255 160 50 / 0.75)) brightness(1.15); }
  55% { filter: drop-shadow(0 0 calc(var(--cell) * 1.5) rgb(255 140 30 / 0.4)) brightness(0.95); }
  80% { filter: drop-shadow(0 0 calc(var(--cell) * 3.5) rgb(255 160 50 / 0.7)) brightness(1.1); }
}

.cobweb {
  top: 0;
  left: 0;
  width: calc(var(--cell) * 26 * 1.5);
  height: calc(var(--cell) * 26 * 1.5);
  background-image: var(--px-cobweb);
}

.cobweb.right {
  left: auto;
  right: 0;
  scale: -1 1;
}

/* The spider hangs on a thread from the ceiling and rises and drops. */
.spider {
  top: 0;
  left: 63%;
  width: calc(var(--cell) * 9);
  height: calc(var(--cell) * 7);
  background-image: var(--px-spider);
  translate: 0 calc(var(--cell) * 40);
  animation: spider-drop 7s ease-in-out infinite;
}

.spider::before {
  content: "";
  position: absolute;
  left: calc(50% - 0.5px);
  bottom: 100%;
  width: 1px;
  height: 100vh;
  background: rgb(221 216 204 / 0.45);
}

@keyframes spider-drop {
  0%, 100% { translate: 0 calc(var(--cell) * 34); }
  45% { translate: 0 calc(var(--cell) * 52); }
  55% { translate: 0 calc(var(--cell) * 50); }
}

.bat {
  top: calc(var(--cell) * 24);
  left: 14%;
  width: calc(var(--cell) * 11 * 1.5);
  height: calc(var(--cell) * 6 * 1.5);
  background: var(--px-bat) 0 0 / 100% 200% no-repeat;
  opacity: 0.8;
  animation:
    bat-flap 0.3s steps(1) infinite,
    bat-drift 9s ease-in-out infinite;
}

.bat.second {
  top: calc(var(--cell) * 30);
  left: 56%;
  scale: 0.75;
  animation-delay: -0.15s, -4s;
}

@keyframes bat-flap {
  50% { background-position: 0 100%; }
}

@keyframes bat-drift {
  0%, 100% { translate: 0 0; }
  25% { translate: calc(var(--cell) * 14) calc(var(--cell) * -4); }
  50% { translate: calc(var(--cell) * 6) calc(var(--cell) * 5); }
  75% { translate: calc(var(--cell) * -8) calc(var(--cell) * -2); }
}

@keyframes flame-frames {
  to { background-position: 0 150%; }
}

@keyframes candle-glow {
  0%, 100% { opacity: 0.85; scale: 1; }
  9% { opacity: 1; scale: 1.04; }
  17% { opacity: 0.78; scale: 0.97; }
  31% { opacity: 0.96; scale: 1.03; }
  44% { opacity: 0.86; scale: 1; }
  56% { opacity: 1; scale: 1.05; }
  64% { opacity: 0.74; scale: 0.96; }
  78% { opacity: 0.94; scale: 1.02; }
  90% { opacity: 0.82; scale: 0.99; }
}

@media (max-width: 760px) {
  .saloon-room {
    --cell: 2px;
  }

  .side-table::after {
    width: 15rem;
    height: 15rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .saloon-bar::before,
  .saloon-bar::after,
  .table-light,
  .side-table::before,
  .side-table::after,
  .spooky {
    animation: none;
  }
}

/* The lobby and the login screen: the swinging doors of a saloon at night,
   filling the screen, dimmed by about 85% under a veil of the room's darkest
   tone so the panels on top stay easy to read. */
body[data-screen="lobby"]::before,
body[data-screen="auth"]::before {
  background:
    linear-gradient(rgb(22 15 10 / 0.85), rgb(22 15 10 / 0.85)),
    var(--px-saloon) center / cover no-repeat,
    #160f0a;
}

/* Tall, narrow screens: the tall drawing of the doors, so covering the screen
   doesn't crop the doors down to the gap between them. */
@media (max-aspect-ratio: 4 / 5) {
  body[data-screen="lobby"]::before,
  body[data-screen="auth"]::before {
    background-image:
      linear-gradient(rgb(22 15 10 / 0.85), rgb(22 15 10 / 0.85)),
      var(--px-saloon-tall);
  }
}

/* The chandelier's candles flicker over the backdrop: frames of flames and
   soft halos (--px-saloon-flicker-*, drawn to the scene's size and laid over it
   the same way) swapped at uneven beats. */
body[data-screen="lobby"]::after,
body[data-screen="auth"]::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: var(--px-saloon-flicker-0) center / cover no-repeat;
  image-rendering: pixelated;
  pointer-events: none;
  animation: candle-flicker 1.9s steps(1, end) infinite;
}

@keyframes candle-flicker {
  0% { background-image: var(--px-saloon-flicker-0); }
  18% { background-image: var(--px-saloon-flicker-1); }
  31% { background-image: var(--px-saloon-flicker-2); }
  52% { background-image: var(--px-saloon-flicker-0); }
  64% { background-image: var(--px-saloon-flicker-2); }
  83% { background-image: var(--px-saloon-flicker-1); }
}

@media (max-aspect-ratio: 4 / 5) {
  body[data-screen="lobby"]::after,
  body[data-screen="auth"]::after {
    background-image: var(--px-saloon-tall-flicker-0);
    animation-name: candle-flicker-tall;
  }
}

@keyframes candle-flicker-tall {
  0% { background-image: var(--px-saloon-tall-flicker-0); }
  18% { background-image: var(--px-saloon-tall-flicker-1); }
  31% { background-image: var(--px-saloon-tall-flicker-2); }
  52% { background-image: var(--px-saloon-tall-flicker-0); }
  64% { background-image: var(--px-saloon-tall-flicker-2); }
  83% { background-image: var(--px-saloon-tall-flicker-1); }
}

@media (prefers-reduced-motion: reduce) {
  body[data-screen="lobby"]::after,
  body[data-screen="auth"]::after {
    animation: none;
  }
}

/* Walking between the lobby and a table (startWalkIn and startWalkOut in
   main.ts): the lobby's backdrop rebuilt over everything, as the room and the
   doors in layers, each door hinged at the screen's edge so it swings away
   from the view. The table stands between them: the real one under the scene
   with the room taken away, or a still copy on the room's floor. A still copy
   of the lobby's panels fades on top. */
.saloon-entry {
  position: fixed;
  inset: 0;
  z-index: 50;
  overflow: hidden;
  pointer-events: none;
}

.entry-floor {
  position: absolute;
  inset: 0;
}

.entry-doors {
  position: absolute;
  inset: 0;
  perspective: 60rem;
}

.entry-room,
.entry-door {
  position: absolute;
  inset: 0;
  background: var(--px-saloon-room) center / cover no-repeat;
  image-rendering: pixelated;
}

.entry-door {
  background-image: var(--px-saloon-doors);
}

.entry-door.left {
  clip-path: inset(0 50% 0 0);
  transform-origin: 0 50%;
}

.entry-door.right {
  clip-path: inset(0 0 0 50%);
  transform-origin: 100% 50%;
}

.entry-veil {
  position: absolute;
  inset: 0;
  background: rgb(22 15 10);
  opacity: 0.85;
}

@media (max-aspect-ratio: 4 / 5) {
  .entry-room {
    background-image: var(--px-saloon-tall-room);
  }

  .entry-door {
    background-image: var(--px-saloon-tall-doors);
  }
}

::selection {
  background: var(--gold);
  color: var(--outline);
}

/* ---------- Pixel icons ---------- */

.px-icon {
  display: inline-block;
  flex: none;
  width: 1.2em;
  height: 1.05em;
  background: center / contain no-repeat;
  vertical-align: -0.15em;
}

.px-icon[data-icon="book"] { background-image: var(--px-icon-book); }
.px-icon[data-icon="scroll"] { background-image: var(--px-icon-scroll); }
.px-icon[data-icon="trophy"] { background-image: var(--px-icon-trophy); }
.px-icon[data-icon="crown"] { background-image: var(--px-icon-crown); }
.px-icon[data-icon="eye"] { background-image: var(--px-icon-eye); }
.px-icon[data-icon="pencil"] { background-image: var(--px-icon-pencil); }
.px-icon[data-icon="bag"] { background-image: var(--px-icon-bag); }
.px-icon[data-icon="card-back"] { background-image: var(--px-icon-card-back); }
.px-icon[data-icon="sound"] { background-image: var(--px-icon-sound); }
.px-icon[data-icon="mute"] { background-image: var(--px-icon-mute); }
.px-icon[data-icon="sparkle"] { background-image: var(--px-icon-sparkle); }
.px-icon[data-icon="anger"] { background-image: var(--px-icon-anger); }
.px-icon[data-icon="bang"] { background-image: var(--px-icon-bang); }
.px-icon[data-icon="drop"] { background-image: var(--px-icon-drop); }
.px-icon[data-icon="ghost"] { background-image: var(--px-icon-ghost); }
.px-icon[data-icon="hat"] { background-image: var(--px-icon-hat); }
.px-icon[data-icon="bluff-card"] { background-image: var(--px-icon-bluff-card); }
.px-icon[data-icon="horseshoe"] { background-image: var(--px-icon-horseshoe); }

.px-suit {
  display: block;
  width: 0.92em;
  height: 1em;
}

/* ---------- Buttons ---------- */

.btn,
.actions button,
.table-card button,
#leave,
.out-leave {
  --face: var(--wood);
  --hi: var(--wood-hi);
  --side: var(--wood-lo);
  border: 0;
  background: var(--face);
  color: var(--ink);
  font-family: var(--display);
  font-size: 1.3rem;
  font-weight: 400;
  letter-spacing: 0.04em;
  line-height: 1;
  text-transform: uppercase;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.4);
  box-shadow: inset 0 0 0 3px var(--outline), inset 0 6px 0 var(--hi), inset 0 -6px 0 var(--side);
  clip-path: var(--notch);
  transition: filter 0.1s steps(1);
}

.btn:hover:not(:disabled),
.actions button:hover:not(:disabled),
.table-card button:hover:not(:disabled),
#leave:hover,
.out-leave:hover {
  filter: brightness(1.12);
  transform: none;
}

.btn:active:not(:disabled),
.actions button:active:not(:disabled),
.table-card button:active:not(:disabled),
#leave:active,
.out-leave:active {
  transform: translateY(3px);
  box-shadow: inset 0 0 0 3px var(--outline), inset 0 6px 0 var(--side);
}

.btn-gold,
.actions .act-raise,
.table-card button {
  --face: #c49a45;
  --hi: #e2bf6e;
  --side: #7c5a22;
  color: #2a1a08;
  text-shadow: 1px 1px 0 rgba(255, 240, 200, 0.35);
}

#leave,
.out-leave,
.actions .act-fold {
  --face: #8f2d20;
  --hi: #b0412c;
  --side: #561a12;
  color: #f1e4c6;
}

.actions .act-check,
.actions .act-call {
  --face: #5f7a4a;
  --hi: #7c9a5a;
  --side: #3a4c2b;
  color: #f1e4c6;
}

.actions .act-allin {
  --face: #2b2622;
  --hi: #46403a;
  --side: var(--outline);
  color: var(--gold);
}

.table-card .watch-table {
  --face: var(--wood);
  --hi: var(--wood-hi);
  --side: var(--wood-lo);
  color: var(--ink);
  text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.4);
}

#leave {
  font-size: 1.15rem;
}

.out-leave {
  padding: 0.5rem 0.9rem;
  font-size: 1.05rem;
}

.btn-big {
  font-size: 1.6rem;
}

#begin {
  animation: begin-blink 1.2s steps(1) infinite;
}

@keyframes begin-blink {
  50% { filter: brightness(1.18); }
}

/* Small square wooden buttons. */
.sit-out-btn,
.history-toggle,
.guide-close,
.history-close,
.week-step {
  border: 0;
  background: var(--wood);
  color: var(--ink);
  box-shadow: inset 0 0 0 3px var(--outline), inset 0 -4px 0 var(--wood-lo), inset 0 4px 0 var(--wood-hi);
}

.sit-out-btn:hover:not(:disabled),
.history-toggle:hover,
.guide-close:hover,
.history-close:hover,
.week-step:hover:not(:disabled) {
  background: var(--wood-hi);
  color: var(--ink);
}

.history-toggle {
  font-weight: 600;
  gap: 0.45rem;
}

.history-toggle[aria-expanded="true"],
.sit-out-btn.active {
  background: var(--gold-dark);
  color: var(--gold);
  box-shadow: inset 0 0 0 3px var(--outline), inset 0 4px 0 var(--wood-lo);
}

.history-toggle.unread::after,
.history-tabs button.unread::after {
  background: var(--red);
  box-shadow: 0 0 0 2px var(--outline);
}

/* ---------- Top bar ---------- */

.brand-chip {
  color: transparent;
  background: var(--px-chip-big) center / contain no-repeat;
  box-shadow: none;
  animation: none;
}

.big-chip {
  animation: coin-flip 1.2s steps(6) infinite;
}

@keyframes coin-flip {
  50% { transform: scaleX(0.15); }
}

.brand-name {
  font-size: 1.15rem;
  letter-spacing: 0.14em;
}

h1 {
  font-size: clamp(1.8rem, 3.6vw, 2.6rem);
  letter-spacing: 0.02em;
  text-shadow: 3px 3px 0 var(--outline);
}

#status {
  font-size: 1rem;
}

/* ---------- Wooden sign (marquee) ---------- */

/* The sign is made like the ones the players sit behind at the table
   (.seat-body): two weathered planks with a dark seam between them, pixel
   grain, worn edges and an iron nail in each corner. */
.marquee {
  background:
    var(--px-sign-nail) left 0.6rem top 0.55rem / 0.85rem 0.85rem no-repeat,
    var(--px-sign-nail) right 0.6rem top 0.55rem / 0.85rem 0.85rem no-repeat,
    var(--px-sign-nail) left 0.6rem bottom 0.55rem / 0.85rem 0.85rem no-repeat,
    var(--px-sign-nail) right 0.6rem bottom 0.55rem / 0.85rem 0.85rem no-repeat,
    var(--px-sign-grain) 0 0 / 144px 30px repeat,
    linear-gradient(
      180deg,
      #74502f 0 3px,
      transparent 3px calc(50% - 3px),
      #24160c calc(50% - 3px) calc(50% + 1px),
      #6c4729 calc(50% + 1px) calc(50% + 4px),
      transparent calc(50% + 4px) calc(100% - 5px),
      #3b2414 calc(100% - 5px)
    ),
    linear-gradient(180deg, #5c3b22 50%, #55361f 50%);
  image-rendering: pixelated;
  box-shadow: 0 0 0 3px var(--outline), inset 0 0 0 3px #2e1c10, var(--drop);
}

.marquee-title {
  font-size: clamp(3.8rem, 13vw, 8.4rem);
  line-height: 0.85;
  letter-spacing: 0.02em;
  color: var(--paper);
  /* A plain drop shadow like the lobby headings ("Open a table"). */
  text-shadow: 0.07em 0.07em 0 var(--outline);
}

.marquee-title span {
  animation: none;
}

.marquee-title span:last-child {
  color: var(--gold);
}

.marquee-small .marquee-title {
  font-size: clamp(3rem, 10vw, 5.4rem);
}

.marquee-sub {
  color: var(--paper);
  font-size: 1.15rem;
  text-shadow: 2px 2px 0 var(--outline);
}

.marquee-sub strong {
  color: var(--gold);
}

.marquee-cards .card:first-child,
.marquee-cards .card:last-child,
.marquee-cards-left .card:first-child,
.marquee-cards-left .card:last-child {
  transform: none;
}

.marquee-cards .card:last-child {
  margin-top: 1rem;
}

.marquee-cards .card {
  animation-timing-function: steps(2);
}

/* ---------- Panels ---------- */

.table-card,
.auth-card,
.history,
.leaders li,
#result,
.actions.your-turn,
.toast,
.banner {
  background: var(--panel);
  box-shadow: 0 0 0 3px var(--outline), inset 0 0 0 3px var(--panel-rim), var(--drop);
}

.lobby h3,
.history-tabs button,
.guide-head h3,
.dialog h3 {
  letter-spacing: 0.03em;
}

.lobby h3 {
  font-size: 1.9rem;
  color: var(--paper);
  text-shadow: 3px 3px 0 var(--outline);
}

/* The open-a-table form: felt in a brass frame, coloured and patterned per
   room: clubs on the Chuckwagon's green, spades on the Saloon's red,
   diamonds on the Riverboat's blue, dark red hearts on High Noon's black,
   and trophies on purple for tournaments. */
.new-table {
  --room: #5a2420;
  --room-edge: #3a1412;
  --room-felt: var(--px-felt-saloon);
  background: var(--room-felt) 0 0 / 60px 60px repeat, var(--room);
  box-shadow:
    0 0 0 3px var(--outline),
    inset 0 0 0 3px var(--gold-deep),
    inset 0 0 0 6px var(--room-edge),
    var(--drop);
}

.new-table[data-room="chuckwagon"] {
  --room: #2f4a2f;
  --room-edge: #1f331f;
  --room-felt: var(--px-felt-chuckwagon);
}

.new-table[data-room="riverboat"] {
  --room: #2a3f55;
  --room-edge: #1a2938;
  --room-felt: var(--px-felt-riverboat);
}

.new-table[data-room="high-noon"] {
  --room: #161210;
  --room-edge: #050403;
  --room-felt: var(--px-felt-high-noon);
}

.new-table[data-room="tournament"] {
  --room: #3a2c4e;
  --room-edge: #241a33;
  --room-felt: var(--px-felt-tournament);
}

/* Scheduling a tournament: the tournament's trophies, on gold. */
.new-table[data-room="scheduled"] {
  --room: #8a6a1e;
  --room-edge: #5e4512;
  --room-felt: var(--px-felt-scheduled);
}

/* Date and time pickers drawn in the theme's light ink. */
.schedule-when input {
  color-scheme: dark;
}

/* Cash and tournament options share one cell, which fits the mode shown, so
   the button sits right under its options (the buy-in slider for cash). */
.mode-options {
  display: grid;
}

.mode-options > .options {
  grid-area: 1 / 1;
  align-content: start;
}

.mode-options > [inert] {
  display: none;
}

.new-table .options {
  gap: 0.25rem;
  margin-bottom: 0.4rem;
}

/* Room for the longest note, so picking another entry fee doesn't resize. */
#tournament-note {
  min-height: 3lh;
}

/* A compact form at every size, so the button stays in view without
   scrolling. */
.new-table {
  padding: 1.1rem 1.25rem;
}

.new-table input:not([type="range"]) {
  margin-bottom: 0.6rem;
  padding-block: 0.5rem;
}

.new-table .mode-toggle {
  margin-bottom: 0.6rem;
}

.new-table .mode-toggle button {
  padding-block: 0.4rem;
}

.new-table h3 {
  margin-bottom: 0.5rem;
}

/* Short phones: a slimmer lobby sign leaves the form room for its button. */
@media (max-width: 760px) and (max-height: 700px) {
  .lobby .marquee {
    padding-block: 0.8rem;
  }

  .lobby .marquee-title {
    font-size: 2.4rem;
  }
}

/* "Open table · buy in $1,000" stays on one line, so the box doesn't grow. */
#start,
#schedule-submit {
  padding-inline: 0.6rem;
  font-size: 1.1rem;
  white-space: nowrap;
}

/* Every option is the same size, two by two: a cash room's name over its
   blinds, or a tournament's entry fee. */
.new-table .pills {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

.new-table .pills button {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 0.15rem;
  min-height: 2.6rem;
  padding: 0.25rem 0.4rem;
}

.mode-blurb {
  min-height: 0;
}

.mode-blurb,
#tournament-note {
  font-size: 0.78rem;
}

.pill-title {
  font-size: 1rem;
}

.pill-detail {
  font-size: 0.8rem;
  opacity: 0.8;
}

.new-table > p,
.mode-blurb,
.mode-toggle button,
.option-label,
.option-note,
.pills button {
  color: var(--sage-text);
}

.option-label strong {
  font-size: 1.4rem;
}

/* ---------- Fields, tabs, toggles ---------- */

.auth-card input,
.new-table input:not([type="range"]),
#table-search,
#scheduled-search,
.chat input:not([type="range"]) {
  border: 3px solid var(--outline);
  background: var(--well);
  color: var(--ink);
  font-family: var(--body);
  font-weight: 500;
  box-shadow: inset 3px 3px 0 rgba(0, 0, 0, 0.45);
}

.auth-card input::placeholder,
.new-table input::placeholder,
#table-search::placeholder,
#scheduled-search::placeholder,
.chat input:not([type="range"])::placeholder {
  color: rgba(236, 224, 194, 0.4);
}

.tabs,
.mode-toggle,
.lobby-tabs {
  background: var(--well);
  box-shadow: 0 0 0 3px var(--outline);
}

.tabs button,
.mode-toggle button,
.pills button,
.lobby-tabs button {
  font-family: var(--display);
  font-size: 1.2rem;
  font-weight: 400;
  letter-spacing: 0.04em;
  transition: none;
}

.pills button {
  background: var(--well);
  box-shadow: inset 0 0 0 2px rgba(236, 224, 194, 0.12);
}

.tabs button[aria-selected="true"],
.mode-toggle button[aria-checked="true"],
.pills button[aria-checked="true"],
.lobby-tabs button[aria-selected="true"] {
  background: var(--gold);
  color: #2a1a08;
  box-shadow: inset 0 -3px 0 #7c5a22, inset 0 0 0 2px var(--outline);
}

.auth-card label,
.auth-avatar legend,
.avatar-section-title {
  color: var(--muted);
  font-family: var(--display);
  font-size: 1.1rem;
  letter-spacing: 0.06em;
}

.form-error {
  color: #e0715a;
}

/* The login screen is pixel lettering throughout (numbers and money stay in
   JetBrains Mono), except the big title. */
#auth .marquee-sub,
#auth .auth-card,
#auth .auth-card * {
  font-family: var(--mix);
}

/* In the main menu too: the welcome under the sign, and every button's
   label, also on its copy floating away on the walk to a table. The card
   packs' buttons are lettered the same wherever they're opened. */
.lobby .marquee-sub,
body[data-screen="lobby"] button,
.saloon-entry[data-from="lobby"] button,
:is(.packs-dialog, .pack-opening) button {
  font-family: var(--mix);
}

/* The login form is an old printed form on the wanted poster's aged paper
   (the --px-poster 9-slice): a heading between ornaments, the two choices as
   tick boxes, the fields as ruled lines to write on in ink, and a red button
   like an ink stamp. */
#auth .auth-card {
  --form-ink: #2a1a0f;
  --form-ink-soft: #5a4128;
  --form-red: #8f2d20;
  border: 30px solid transparent;
  border-image: var(--px-poster) 10 fill / 30px round;
  padding: 0 0.6rem 0.2rem;
  background: none;
  color: var(--form-ink);
  image-rendering: pixelated;
  box-shadow: none;
  filter: drop-shadow(8px 8px 0 rgba(0, 0, 0, 0.45));
}

.auth-form-head {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin: 0;
  padding-bottom: 0.45rem;
  border-bottom: 3px double var(--form-ink);
  font-size: 1.35rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.auth-form-head::before,
.auth-form-head::after {
  content: "";
  flex: 1;
  height: 2px;
  background: var(--form-ink);
}

/* Log in / Create account: a tick box beside each, the chosen one crossed. */
#auth .tabs {
  display: flex;
  justify-content: center;
  gap: 1.6rem;
  padding: 0;
  background: none;
  box-shadow: none;
}

#auth .tabs button {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.25rem 0;
  background: none;
  color: var(--form-ink-soft);
  font-size: 1.05rem;
  box-shadow: none;
}

#auth .tabs button::before {
  content: "";
  flex: none;
  width: 1.05rem;
  height: 1.05rem;
  box-shadow: inset 0 0 0 2px var(--form-ink);
}

#auth .tabs button[aria-selected="true"] {
  background: none;
  color: var(--form-ink);
  box-shadow: none;
}

#auth .tabs button[aria-selected="true"]::before {
  background:
    linear-gradient(45deg, transparent 43%, var(--form-red) 43% 57%, transparent 57%),
    linear-gradient(-45deg, transparent 43%, var(--form-red) 43% 57%, transparent 57%);
}

#auth .tabs button:not([aria-selected="true"]):hover {
  color: var(--form-ink);
}

/* Each field: its name, then a ruled line to write on. */
#auth .auth-card label {
  grid-template-columns: auto minmax(0, 1fr);
  align-items: end;
  gap: 0.7rem;
  color: var(--form-ink);
  font-size: 1rem;
}

#auth .auth-card input {
  border: 0;
  border-bottom: 2px solid var(--form-ink);
  border-radius: 0;
  padding: 0.35rem 0.25rem 0.15rem;
  background: none;
  color: var(--form-ink);
  font-size: 1.1rem;
  box-shadow: none;
}

#auth .auth-card input:focus-visible {
  outline: none;
  border-bottom-color: var(--form-red);
  background: rgb(143 45 32 / 0.08);
}

#auth .auth-avatar legend,
#auth .avatar-section-title {
  color: var(--form-ink);
}

#auth .avatar-picker.strip .avatar-grid {
  box-shadow: 0 0 0 2px var(--form-ink);
}

#auth .form-error {
  color: var(--form-red);
}

/* The button: red like an ink stamp. */
#auth-submit {
  --face: #8f2d20;
  --hi: #b0412c;
  --side: #561a12;
  margin-top: 0.3rem;
  color: #f1e4c6;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.4);
}

#auth-submit:focus-visible {
  outline-color: var(--form-ink);
}

/* The fine print at the foot of the form. */
#auth .auth-hint {
  color: var(--form-ink-soft);
}

/* Sliders: a flat brass track and a pixel chip for the thumb, whose gold
   runs to the middle of the chip wherever it sits (--p is 0 to 1). */
.new-table input[type="range"],
.dialog input[type="range"],
.raise input[type="range"] {
  --thumb: 1.7rem;
  height: 12px;
  background: linear-gradient(90deg, var(--gold) calc(var(--thumb) / 2 + (100% - var(--thumb)) * var(--p, 0)), var(--well) 0);
  box-shadow: 0 0 0 3px var(--outline);
}

.new-table input[type="range"]::-webkit-slider-thumb,
.dialog input[type="range"]::-webkit-slider-thumb,
.raise input[type="range"]::-webkit-slider-thumb {
  width: var(--thumb, 1.7rem);
  height: var(--thumb, 1.7rem);
  background: var(--px-chip-gold) center / contain no-repeat;
  box-shadow: none;
}

.new-table input[type="range"]::-moz-range-thumb,
.dialog input[type="range"]::-moz-range-thumb,
.raise input[type="range"]::-moz-range-thumb {
  width: var(--thumb, 1.7rem);
  height: var(--thumb, 1.7rem);
  background: var(--px-chip-gold) center / contain no-repeat;
}

/* ---------- Lobby tables ---------- */

.mini-table {
  background: var(--px-table) center / 100% 100% no-repeat;
  box-shadow: none;
}

.mini-table .mini-name {
  color: var(--paper);
  font-size: 1.35rem;
  text-shadow: 2px 2px 0 var(--outline);
}

.mini-seat {
  width: 1.5rem;
  height: 1.5rem;
  border: 2px dashed rgba(236, 224, 194, 0.4);
  background: rgba(18, 11, 7, 0.45);
  color: #2a1a08;
  font-family: var(--display);
  font-size: 0.95rem;
  font-weight: 400;
}

.mini-seat.taken {
  background: var(--gold);
  box-shadow: 0 0 0 2px var(--outline);
}

.badge {
  background: var(--well);
  color: var(--muted);
  font-family: var(--display);
  font-size: 0.95rem;
  font-weight: 400;
  letter-spacing: 0.06em;
  box-shadow: inset 0 0 0 2px rgba(236, 224, 194, 0.12);
}

.badge.open,
.badge.cash { background: #26331d; color: #b4cc8c; }
.badge.live { background: #3a1912; color: #e88a74; }
.badge.tourney { background: #3a2c12; color: var(--gold); }
.badge.live::before { background: #e0715a; }

/* ---------- The table ---------- */

.rail {
  background: var(--px-table) center / 100% 100% no-repeat;
  box-shadow: none;
}

/* Each room's felt matches its swatch in the lobby. Swapping the sprites
   themselves keeps every rule that picks the wide or tall table working. */
.rail[data-room="chuckwagon"],
.mini-table[data-room="chuckwagon"] {
  --px-table: var(--px-table-chuckwagon);
  --px-table-tall: var(--px-table-tall-chuckwagon);
}

.rail[data-room="saloon"],
.mini-table[data-room="saloon"] {
  --px-table: var(--px-table-saloon);
  --px-table-tall: var(--px-table-tall-saloon);
}

.rail[data-room="riverboat"],
.mini-table[data-room="riverboat"] {
  --px-table: var(--px-table-riverboat);
  --px-table-tall: var(--px-table-tall-riverboat);
}

.rail[data-room="high-noon"],
.mini-table[data-room="high-noon"] {
  --px-table: var(--px-table-high-noon);
  --px-table-tall: var(--px-table-tall-high-noon);
}

.rail[data-room="tournament"],
.mini-table[data-room="tournament"] {
  --px-table: var(--px-table-tournament);
  --px-table-tall: var(--px-table-tall-tournament);
}

.felt {
  background: none;
  box-shadow: none;
}

.felt::before {
  display: none;
}

/* A scheduled tournament's name, printed faintly into the felt between the
   pot and the bottom seat, between two rules, under everything on the
   table. */
.felt-name {
  position: absolute;
  left: 50%;
  top: 73%;
  z-index: 0;
  display: flex;
  align-items: center;
  gap: 0.9rem;
  max-width: 62%;
  margin: 0;
  translate: -50% -50%;
  color: rgb(255 240 200 / 0.14);
  font-family: var(--title);
  font-size: 2.4rem;
  line-height: 1;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
}

.felt-name:empty {
  display: none;
}

.felt-name::before,
.felt-name::after {
  content: "";
  flex: none;
  width: 2rem;
  height: 3px;
  background: currentColor;
}

/* The tall table on phones has no room for it. */
@media (max-width: 760px) {
  .felt-name {
    display: none;
  }
}

.card-slot {
  border: 2px dashed rgba(236, 224, 194, 0.2);
}

.pot-amount {
  font-size: 2.9rem;
  letter-spacing: 0.02em;
  color: var(--gold);
  text-shadow: 3px 3px 0 var(--outline), -1px -1px 0 var(--outline);
}

.pot-amount::before {
  color: var(--paper);
  font-size: 1.25rem;
}

/* Side pots, under the main amount. */
.pot-label {
  margin-top: 0.25rem;
  color: var(--paper);
  font-family: var(--display);
  font-size: 1.25rem;
  letter-spacing: 0.02em;
  text-shadow: 2px 2px 0 var(--outline);
}

/* What just happened ("emotetest631 raises to $20."), in the players' name
   lettering on the felt just under the pot. It hangs below the board and pot rather than
   joining them, so those stay centred on the table. */
.center {
  top: 50%;
}

.center #result,
.seat #result {
  position: absolute;
  top: calc(100% + 0.35rem);
  left: 50%;
  translate: -50% 0;
  width: max-content;
  max-width: min(26rem, 80vw);
  min-height: 0;
  margin: 0;
  padding: 0;
  overflow: hidden;
  border-radius: 0;
  background: none;
  box-shadow: none;
  /* The same lettering as the players' names on their signs, with the
     amounts in JetBrains Mono. */
  color: var(--ink);
  font-family: var(--mix);
  font-size: 1.05rem;
  font-weight: 600;
  line-height: 1.15;
  text-align: center;
  text-shadow: 2px 2px 0 var(--outline);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.center #result:empty,
.seat #result:empty {
  visibility: hidden;
}

/* Under your own portrait (and its hand name), above the other seats. */
.seat #result {
  z-index: 4;
  font-size: 0.95rem;
}

/* ---------- Chips ---------- */

.chip {
  background: var(--px-chip-red) center / 100% 100% no-repeat;
  box-shadow: none;
}

.chip.white { background-image: var(--px-chip-white); }
.chip.red { background-image: var(--px-chip-red); }
.chip.green { background-image: var(--px-chip-green); }
.chip.black { background-image: var(--px-chip-black); }
.chip.purple { background-image: var(--px-chip-purple); }
.chip.gold { background-image: var(--px-chip-gold); }

.chip-stacks.pop .chip {
  animation-timing-function: steps(3);
}

/* On the table, chips are seen from the side (12 by 5 pixels), so bets, the
   pot and the players' piles stack into towers. Stacked chips share their
   outline row. The wallet, slider and win confetti keep the round chips. */
.chip-col .chip,
.chip.flying-chip {
  height: calc(var(--chip-size) * 5 / 12);
  border-radius: 0;
  background-size: 100% 100%;
  image-rendering: pixelated;
}

.chip-col .chip + .chip {
  margin-bottom: calc(var(--chip-size) / -12);
}

.bet {
  --chip-size: 1.2rem;
}

/* The pot keeps the height of its tallest tower (six chips), so the board
   above it never shifts as the pot grows. */
.pot-chips {
  --chip-size: 1.35rem;
  display: flex;
  align-items: flex-end;
  height: calc(var(--chip-size) * 25 / 12);
}

/* The amount stands on the same line as the bottom of the pot's towers. */
.center .pot {
  align-items: flex-end;
}

.chip.flying-chip {
  --chip-size: 1.1rem;
}

@media (max-width: 760px) {
  .bet {
    --chip-size: 0.9rem;
  }

  .pot-chips {
    --chip-size: 1rem;
  }
}

.chip-col .chip.white,
.chip.flying-chip.white { background-image: var(--px-chipside-white); }
.chip-col .chip.red,
.chip.flying-chip.red { background-image: var(--px-chipside-red); }
.chip-col .chip.green,
.chip.flying-chip.green { background-image: var(--px-chipside-green); }
.chip-col .chip.black,
.chip.flying-chip.black { background-image: var(--px-chipside-black); }
.chip-col .chip.purple,
.chip.flying-chip.purple { background-image: var(--px-chipside-purple); }
.chip-col .chip.gold,
.chip.flying-chip.gold { background-image: var(--px-chipside-gold); }

/* Pocket aces shown off: two six-shooters out from the sides of the portrait,
   firing a ricocheting volley (acesRicochet in main.ts). */
.ricochet {
  position: absolute;
  width: 0;
  height: 0;
}

.gun-holster,
.muzzle-flash,
.ricochet-bullet,
.ricochet-spark {
  position: absolute;
  left: 0;
  top: 0;
}

.gun-holster {
  transform-origin: 15% 70%;
}

.gun-holster:has(.left) {
  transform-origin: 85% 70%;
}

.gun {
  transform-origin: inherit;
  background: var(--px-gun-right) center / 100% 100% no-repeat;
  image-rendering: pixelated;
  filter: drop-shadow(2px 2px 0 rgba(0, 0, 0, 0.45));
}

.gun.left {
  background-image: var(--px-gun-left);
}

.muzzle-flash {
  width: 1.3rem;
  height: 1.3rem;
  background: var(--px-muzzle-flash) center / 100% 100% no-repeat;
  image-rendering: pixelated;
  opacity: 0;
}

.ricochet-bullet {
  width: 6px;
  height: 3px;
  background: #f4efe3;
  box-shadow: 0 0 0 1px #d9b05c;
  opacity: 0;
}

.muzzle-flash.ping {
  width: 0.9rem;
  height: 0.9rem;
}

.ricochet-spark {
  width: 5px;
  height: 5px;
  background: #d9b05c;
  box-shadow: 0 0 0 1px rgba(18, 11, 7, 0.6);
  opacity: 0;
}

.ricochet-spark.hot {
  background: #f4efe3;
}

/* ---------- Seats ---------- */

.seat-cards .card:first-child,
.seat-cards .card:last-child {
  rotate: 0deg;
}

.seat-cards .card:last-child {
  translate: 0 0.25rem;
}

.seat.you .seat-cards .card:last-child {
  translate: 0 -1.75rem;
}

/* Each player sits behind an old wooden sign: two weathered planks with a
   dark seam between them, pixel grain, worn edges and iron nails in the
   right-hand corners (the portrait covers the left). Same size as before. */
.seat-body {
  --sign-rim: var(--sign-style-rim, #2e1c10);
  background:
    var(--sign-nail, var(--px-sign-nail)) right 0.35rem top 0.3rem / 0.55rem 0.55rem no-repeat,
    var(--sign-nail, var(--px-sign-nail)) right 0.35rem bottom 0.3rem / 0.55rem 0.55rem no-repeat,
    var(--sign-dim, none),
    var(--sign-grain, var(--px-sign-grain)) 0 0 / 144px 30px repeat,
    linear-gradient(
      180deg,
      var(--plank-top, #74502f) 0 2px,
      transparent 2px calc(50% - 2px),
      var(--plank-seam, #24160c) calc(50% - 2px) calc(50% + 1px),
      var(--plank-seam-hi, #6c4729) calc(50% + 1px) calc(50% + 3px),
      transparent calc(50% + 3px) calc(100% - 4px),
      var(--plank-bottom, #3b2414) calc(100% - 4px)
    ),
    linear-gradient(180deg, var(--plank-a, #5c3b22) 50%, var(--plank-b, #55361f) 50%);
  image-rendering: pixelated;
  box-shadow: 0 0 0 3px var(--outline), inset 0 0 0 2px var(--sign-rim), var(--drop);
}

.seat.you .seat-body {
  --sign-rim: var(--gold-deep);
}

.seat-body .who,
.seat-body .stack {
  text-shadow: 2px 2px 0 var(--outline);
}

.avatar {
  background: var(--well);
  box-shadow: 0 0 0 3px var(--outline), 0 0 0 5px var(--panel-rim);
}

.who {
  font-weight: 600;
}

.who-you {
  color: var(--gold);
}

.stack {
  font-size: 1.55rem;
  line-height: 1;
  text-shadow: 2px 2px 0 var(--outline);
}

.stack.all-in {
  color: #e0715a;
}

@keyframes stack-up {
  30% { transform: scale(1.2); color: #b4cc8c; }
}

@keyframes stack-down {
  30% { transform: translateX(-3px); color: #e0715a; }
  60% { transform: translateX(3px); }
}

/* The dealer button: a round pixel disc with a pixel "D", same size as before. */
.dealer {
  border-radius: 0;
  background: var(--px-icon-dealer) center / 100% 100% no-repeat;
  color: var(--card-ink);
  font-family: var(--title);
  font-size: 1.05rem;
  line-height: 1;
  box-shadow: none;
  filter: drop-shadow(2px 2px 0 rgba(0, 0, 0, 0.45));
  image-rendering: pixelated;
  animation: none;
}

.made {
  background: var(--gold);
  color: #2a1a08;
  font-family: var(--display);
  font-size: 0.95rem;
  font-weight: 400;
  letter-spacing: 0.04em;
  box-shadow: 0 0 0 2px var(--outline), 3px 3px 0 rgba(0, 0, 0, 0.4);
  animation: pop-in 0.3s steps(3) both;
}

.made.sit-out-tag {
  background: var(--panel-rim);
  color: var(--ink);
}

/* Whose turn: a brass frame round the plate, and a blinking frame round the
   portrait. */
.seat.turn .seat-body {
  box-shadow: 0 0 0 3px var(--gold), 0 0 0 6px var(--outline), inset 0 0 0 2px var(--sign-rim), var(--drop);
  animation: none;
}

.seat.turn .avatar::after {
  inset: -7px;
  background: none;
  -webkit-mask: none;
  mask: none;
  box-shadow: 0 0 0 3px var(--gold);
  animation: turn-blink 0.9s steps(1) infinite;
}

@keyframes turn-blink {
  50% { opacity: 0; }
}

.seat.folded .seat-body,
.seat.folded .seat-cards {
  opacity: 0.5;
  filter: grayscale(0.85) brightness(0.85);
}

.seat.winner .seat-body {
  box-shadow: 0 0 0 3px var(--gold), 0 0 0 6px var(--outline), 0 0 0 9px rgba(217, 176, 92, 0.35);
  animation: winner 0.6s steps(3) 3;
}

.seat.winner .avatar::before {
  content: "";
  top: -1.25rem;
  width: 1.8rem;
  height: 1.2rem;
  background: var(--px-icon-crown) center / contain no-repeat;
  animation: pop-in 0.4s steps(3);
}

.empty-seat {
  border: 2px dashed rgba(236, 224, 194, 0.3);
  background: rgba(18, 11, 7, 0.35);
  color: rgba(236, 224, 194, 0.5);
  font-family: var(--display);
  font-size: 0.85rem;
  font-weight: 400;
}

/* Bet sizes: big brass numbers on a dark tag, readable at a glance. */
.bet-amount {
  padding: 0.2rem 0.5rem;
  background: rgba(18, 11, 7, 0.88);
  color: var(--gold);
  font-family: var(--display);
  font-size: 1.45rem;
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0.02em;
  text-shadow: 2px 2px 0 var(--outline);
  box-shadow: 0 0 0 2px var(--outline), 3px 3px 0 rgba(0, 0, 0, 0.45);
}

/* ---------- Speech and action tags ---------- */

/* Chat comes out of the speaker's portrait in a pixel balloon: square with
   notched corners, a stepped tail down to the portrait and a hard shadow.
   Seats on the right open their balloons to the left, and the top seat's
   hangs below with its tail pointing up. */
.bubble {
  --bubble-bg: var(--paper);
  --bubble-line: var(--outline);
  --px: 3px;
  left: calc(var(--avatar-x, 2rem) - 1rem);
  top: auto;
  bottom: calc(100% - var(--avatar-top, 0px) + 18px);
  translate: 0 0;
  border: 0;
  border-radius: 0;
  padding: 0.45rem 0.75rem;
  color: var(--outline);
  font-family: var(--body);
  font-weight: 600;
  transform-origin: 1rem calc(100% + 15px);
  box-shadow:
    var(--px) 0 0 0 var(--bubble-line),
    calc(var(--px) * -1) 0 0 0 var(--bubble-line),
    0 var(--px) 0 0 var(--bubble-line),
    0 calc(var(--px) * -1) 0 0 var(--bubble-line);
  filter: drop-shadow(4px 4px 0 rgba(0, 0, 0, 0.45));
  animation:
    bubble-in 0.36s steps(4) both,
    bubble-bob 2.6s steps(2) 0.36s infinite;
}

.bubble::before,
.bubble::after {
  top: 100%;
  bottom: auto;
  border: 0;
  translate: none;
}

/* The tail: an outline staircase, and the fill inside it reaching up over
   the balloon's bottom edge. */
.bubble::before {
  left: 0.8rem;
  width: 12px;
  height: 15px;
  background: var(--bubble-line);
  clip-path: polygon(0 0, 12px 0, 12px 6px, 9px 6px, 9px 9px, 6px 9px, 6px 12px, 3px 12px, 3px 15px, 0 15px);
}

.bubble::after {
  left: calc(0.8rem + 3px);
  width: 6px;
  height: 9px;
  background: var(--bubble-bg);
  clip-path: polygon(0 0, 6px 0, 6px 6px, 3px 6px, 3px 9px, 0 9px);
}

.seat[data-slot="4"] .bubble,
.seat[data-slot="5"] .bubble {
  left: auto;
  right: calc(100% - var(--avatar-x, 2rem) - 1rem);
  transform-origin: calc(100% - 1rem) calc(100% + 15px);
}

.seat[data-slot="4"] .bubble::before,
.seat[data-slot="5"] .bubble::before {
  left: auto;
  right: 0.8rem;
  scale: -1 1;
}

.seat[data-slot="4"] .bubble::after,
.seat[data-slot="5"] .bubble::after {
  left: auto;
  right: calc(0.8rem + 3px);
  scale: -1 1;
}

.seat[data-slot="3"] .bubble {
  top: calc(var(--avatar-bottom, 100%) + 18px);
  bottom: auto;
  transform-origin: 1rem -15px;
}

.seat[data-slot="3"] .bubble::before,
.seat[data-slot="3"] .bubble::after {
  top: auto;
  bottom: 100%;
  border: 0;
  translate: none;
  scale: 1 -1;
}

/* Your own balloon, to the left of your sign: the tail points right at your
   portrait. */
.seat.you .bubble.beside {
  left: auto;
  right: calc(100% - var(--body-left) + 18px);
  top: var(--avatar-top);
  bottom: auto;
  max-width: min(16rem, var(--room));
  transform-origin: calc(100% + 15px) 0.6rem;
}

.seat.you .bubble.beside::before {
  top: 0.35rem;
  left: 100%;
  right: auto;
  width: 15px;
  height: 12px;
  clip-path: polygon(0 0, 15px 0, 15px 3px, 12px 3px, 12px 6px, 9px 6px, 9px 9px, 6px 9px, 6px 12px, 0 12px);
}

.seat.you .bubble.beside::after {
  top: calc(0.35rem + 3px);
  left: 100%;
  right: auto;
  width: 9px;
  height: 6px;
  clip-path: polygon(0 0, 9px 0, 9px 3px, 6px 3px, 6px 6px, 0 6px);
}

.bubble.emoji {
  animation:
    bubble-in 0.36s steps(4) both,
    bubble-bob 2.6s steps(2) 0.36s infinite;
}

.bubble.shout {
  animation:
    bubble-in 0.36s steps(4) both,
    bubble-shake 0.45s steps(4) 0.36s 2,
    bubble-bob 2.6s steps(2) 1.26s infinite;
}

.bubble.shown {
  animation: bubble-bob 2.6s steps(2) infinite;
}

.bubble.leaving {
  animation: bubble-out 0.3s steps(3) forwards;
}

.bubble.tone-1 { --bubble-bg: #e8c477; }
.bubble.tone-2 { --bubble-bg: #b7c99a; }
.bubble.tone-3 { --bubble-bg: #e0a890; }
.bubble.tone-4 { --bubble-bg: #a9bccb; }
.bubble.tone-5 { --bubble-bg: #c7b2c4; }

.bubble.shout {
  font-size: 1.5rem;
}

/* What a player says when they act: a square speech balloon with a tail down
   to their seat. Folds are said quietly, raises in red, all-ins in gold. Every whisper has a data-label, so these match the
   base sheet's per-label colours and win. */
.whisper[data-label] {
  --say-bg: var(--paper);
  --say-ink: var(--outline);
  border-radius: 0;
  padding: 0.2rem 0.6rem 0.25rem;
  background: var(--say-bg);
  color: var(--say-ink);
  font-family: var(--body);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0;
  box-shadow: 0 0 0 2px var(--outline), 4px 4px 0 rgba(0, 0, 0, 0.45);
}

.whisper::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 100%;
  width: 10px;
  height: 7px;
  translate: -50% 1px;
  background: var(--outline);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}

.whisper::before {
  content: "";
  position: absolute;
  z-index: 1;
  left: 50%;
  top: 100%;
  width: 6px;
  height: 4px;
  translate: -50% -1px;
  background: var(--say-bg);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}

.whisper[data-label="Fold"] { --say-bg: #b3a68a; --say-ink: #3a2a1c; font-weight: 600; }
.whisper[data-label="Raise"] { --say-bg: #8f2d20; --say-ink: #f1e4c6; }
.whisper[data-label="AllIn"] { --say-bg: var(--gold); --say-ink: #5a1a10; font-size: 1.15rem; text-transform: uppercase; }

.win-float {
  color: #b4cc8c;
  font-size: 2rem;
  text-shadow: 3px 3px 0 var(--outline);
}

/* ---------- The deck ---------- */

/* A short stack of face-down cards on the felt, left of the board, that every
   card is dealt from. Each card sits a pixel up and to the left of the one
   under it. */
.board {
  position: relative;
}

.deck {
  position: absolute;
  top: 0;
  right: calc(100% + 1.1rem);
  width: var(--cw);
  height: calc(var(--cw) * 1.4);
  pointer-events: none;
  filter: drop-shadow(3px 3px 0 rgba(0, 0, 0, 0.35));
}

.deck .card {
  position: absolute;
  top: 0;
  left: 0;
}

.deck .card:nth-child(2) { translate: -1px -2px; }
.deck .card:nth-child(3) { translate: -2px -4px; }
.deck .card:nth-child(4) { translate: -3px -6px; }

/* Phones: the board nearly fills the felt, so a smaller deck closer in. */
@media (max-width: 760px) {
  .deck {
    right: calc(100% + 0.35rem);
    scale: 0.8;
    transform-origin: 100% 0;
  }
}

/* Cards in flight from the deck, and the dealt card showing when one lands:
   face-down cards just appear, face-up ones finish turning over. */
.card.flying-card {
  position: absolute;
  z-index: 6;
  margin: 0;
  pointer-events: none;
}

/* A dealt card is hidden until its flight lands, then simply there, at full
   strength (a fade-in here showed as a see-through card for a moment, more
   so on a slow device). */
.card.deal {
  animation: card-land 0.01s steps(1) backwards;
  animation-delay: var(--deal-delay, 0s);
}

.card.deal:not(.back) {
  animation: card-turn 0.2s steps(3) both;
  animation-delay: var(--deal-delay, 0s);
}

@keyframes card-land {
  from, to { opacity: 0; }
}

@keyframes card-turn {
  from { scale: 0 1; }
}

/* ---------- Stacks on the table ---------- */

/* Each player's chips behind, piled on the felt beside their sign (on the
   side facing the middle for the seats down the left, otherwise on the
   left, away from where their bets go). */
.stack-pile {
  --chip-size: 1.1rem;
  position: absolute;
  right: calc(100% + 0.5rem);
  bottom: 0.2rem;
  z-index: 1;
  display: flex;
  align-items: flex-end;
  gap: 0.12rem;
  pointer-events: none;
  filter: drop-shadow(2px 2px 0 rgba(0, 0, 0, 0.35));
}

.seat[data-slot="1"] .stack-pile,
.seat[data-slot="2"] .stack-pile {
  right: auto;
  left: calc(100% + 0.5rem);
}

/* Your sign sits low on the rail, so your pile stands up on the felt; the
   top seat's sign sits high, so its pile hangs from the sign's top edge. */
.seat[data-slot="0"] .stack-pile {
  bottom: 3.2rem;
}

.seat[data-slot="3"] .stack-pile {
  top: 0.4rem;
  bottom: auto;
}

/* The side players' bets slide in past their piles. */
@media (min-width: 761px) {
  .seat[data-slot="1"],
  .seat[data-slot="2"] {
    --bx: 11.5rem;
  }

  .seat[data-slot="4"],
  .seat[data-slot="5"] {
    --bx: -11.5rem;
  }
}

/* Phones are crowded: a smaller pile of at most two towers, tucked onto a
   corner of each side player's sign, clear of the pot, the cards and the line
   under the pot; yours and the top player's sit just outside the sign. */
@media (max-width: 760px) {
  .stack-pile {
    --chip-size: 0.65rem;
    gap: 0.08rem;
  }

  .stack-pile .chip-col:nth-child(n + 3) {
    display: none;
  }

  .seat .stack-pile {
    top: auto;
    right: 0.25rem;
    bottom: calc(100% - 0.3rem);
    left: auto;
    z-index: 2;
  }

  .seat[data-slot="4"] .stack-pile,
  .seat[data-slot="5"] .stack-pile {
    right: auto;
    left: 0.25rem;
  }

  /* The lower side seats hang theirs below the outer corner of the sign,
     clear of the line saying what just happened and of your seat. */
  .seat[data-slot="1"] .stack-pile,
  .seat[data-slot="5"] .stack-pile {
    top: calc(100% - 0.3rem);
    bottom: auto;
  }

  .seat[data-slot="1"] .stack-pile {
    right: auto;
    left: 0.25rem;
  }

  .seat[data-slot="5"] .stack-pile {
    right: 0.25rem;
    left: auto;
  }

  .seat[data-slot="0"] .stack-pile {
    right: calc(100% + 0.25rem);
    bottom: 0.2rem;
  }

  .seat[data-slot="3"] .stack-pile {
    top: 0.3rem;
    right: calc(100% + 0.25rem);
    bottom: auto;
  }
}

/* The shuffle emote: chips hop in and out of the towers in turn, and the
   towers rock towards each other, like a riffle. */
.seat.shuffling .stack-pile .chip {
  animation: chip-riffle 0.3s steps(2) var(--shuffle-delay, 0s) 7 both;
}

.seat.shuffling .stack-pile .chip:nth-child(even) {
  animation-delay: calc(var(--shuffle-delay, 0s) - 0.15s);
}

.seat.shuffling .stack-pile .chip-col:nth-child(odd) {
  animation: tower-rock 0.6s steps(2) var(--shuffle-delay, 0s) 3 both;
}

@keyframes chip-riffle {
  50% { translate: 0 -3px; }
}

@keyframes tower-rock {
  50% { translate: 2px 0; }
}

/* ---------- Characters in motion ---------- */

/* Emotes, reactions to their own actions and thinking. The frame never
   moves: only the character inside it does (the drawing's group, over its
   fixed backdrop), in whole pixels of the portrait. avatar-motion.ts sets the
   class and keeps the timing across redraws with a negative delay. */
.avatar .px-figure {
  transform-box: view-box;
  transform-origin: 50% 100%;
}

.avatar.motion-hop .px-figure { animation: av-hop 2.4s steps(1, end) both; }
.avatar.motion-laugh .px-figure { animation: av-laugh 1.9s steps(1, end) both; }
.avatar.motion-shake .px-figure { animation: av-shake 2.4s steps(1, end) both; }
.avatar.motion-droop .px-figure { animation: av-droop 2.4s steps(1, end) both; }
.avatar.motion-droop svg { animation: av-grey 2.4s ease-in-out both; }
.avatar.motion-jump .px-figure { animation: av-jump 2.4s steps(1, end) both; }
.avatar.motion-spook .px-figure { animation: av-spook 2.4s steps(1, end) both; }
.avatar.motion-bow .px-figure { animation: av-bow 2.4s steps(1, end) both; }
.avatar.motion-smirk .px-figure { animation: av-smirk 2.4s steps(1, end) both; }
.avatar.motion-lucky .px-figure { animation: av-lucky 2.4s steps(1, end) both; }
.avatar.motion-tip .px-figure { animation: av-tip 2s steps(1, end) both; }
.avatar.motion-fold .px-figure { animation: av-fold 1.1s steps(1, end) both; }
.avatar.motion-fold svg { animation: av-dim 1.1s ease-in-out both; }
.avatar.motion-knock .px-figure { animation: av-knock 0.8s steps(1, end) both; }
.avatar.motion-nod .px-figure { animation: av-nod 0.8s steps(1, end) both; }
.avatar.motion-lean .px-figure { animation: av-lean 1.1s steps(1, end) both; }
.avatar.motion-allin .px-figure { animation: av-allin 1.5s steps(1, end) both; }
.avatar.motion-think .px-figure { animation: av-think 2.4s steps(1, end) infinite; }
.avatar.motion-fiddle .px-figure { animation: av-fiddle 2.4s steps(1, end) both; }
.avatar.motion-breathe .px-figure { animation: av-breathe 2.4s steps(1, end) both; }
.avatar.motion-shuffle .px-figure { animation: av-shuffle 1.8s steps(1, end) both; }
.avatar.motion-yawn .px-figure { animation: av-yawn 1.8s steps(1, end) both; }
.avatar.motion-doze .px-figure { animation: av-doze 2.6s steps(1, end) both; }
.avatar.motion-peek .px-figure { animation: av-peek 1.5s steps(1, end) both; }

/* Offsets are in portrait pixels (the drawing is 16 across). */
@keyframes av-hop {
  0%, 20%, 40%, 60%, 100% { translate: 0 0; }
  8% { translate: 0 -2px; }
  28% { translate: 0 -2px; }
  48% { translate: 0 -1px; }
}

@keyframes av-laugh {
  0%, 20%, 40%, 60%, 80%, 100% { translate: 0 0; }
  10%, 30%, 50%, 70% { translate: 0 -1px; }
}

@keyframes av-shake {
  0%, 52%, 100% { translate: 0 0; }
  4%, 12%, 20%, 28%, 36%, 44% { translate: -1px 0; }
  8%, 16%, 24%, 32%, 40%, 48% { translate: 1px 0; }
}

@keyframes av-droop {
  0%, 90%, 100% { translate: 0 0; }
  12% { translate: 0 1px; }
  20%, 82% { translate: 0 2px; }
}

@keyframes av-grey {
  0%, 100% { filter: none; }
  15%, 80% { filter: saturate(0.45) brightness(0.8); }
}

@keyframes av-jump {
  0%, 24%, 100% { translate: 0 0; }
  4% { translate: 0 -2px; }
  10% { translate: 0 -3px; }
  18% { translate: 0 -1px; }
}

/* Boo!: ducks down, springs up tall, trembles, settles. */
@keyframes av-spook {
  0%, 62%, 100% { translate: 0 0; scale: 1; }
  5% { translate: 0 1px; }
  12% { translate: 0 2px; scale: 1 0.96; }
  19% { translate: 0 -3px; scale: 1.08; }
  25% { translate: 1px -2px; scale: 1.06; }
  30% { translate: -1px -2px; }
  35% { translate: 1px -2px; }
  40% { translate: -1px -2px; }
  45% { translate: 1px -1px; }
  52% { translate: 0 -1px; scale: 1.02; }
}

/* *tips hat*: a slow, low bow and back up. */
@keyframes av-bow {
  0%, 70%, 100% { translate: 0 0; }
  10% { translate: 0 1px; }
  20%, 45% { translate: 0 2px; }
  55% { translate: 0 1px; }
}

/* Bluff!: leans to one side, sways sly, leans back. */
@keyframes av-smirk {
  0%, 75%, 100% { translate: 0 0; }
  8%, 24%, 40% { translate: 1px 0; }
  16%, 32%, 48% { translate: 2px 0; }
  60% { translate: 1px -1px; }
}

/* Feelin' lucky!: rocks side to side, then a hop. */
@keyframes av-lucky {
  0%, 85%, 100% { translate: 0 0; }
  8%, 24% { translate: -1px 0; }
  16%, 32% { translate: 1px 0; }
  44% { translate: 0 -2px; }
  52% { translate: 0 -3px; }
  60% { translate: 0 -1px; }
}

@keyframes av-tip {
  0%, 45%, 100% { translate: 0 0; }
  10%, 30% { translate: -1px 0; }
  20% { translate: -1px -1px; }
}

@keyframes av-fold {
  0%, 85%, 100% { translate: 0 0; }
  20%, 70% { translate: 0 1px; }
}

@keyframes av-dim {
  0%, 100% { filter: none; }
  30%, 70% { filter: brightness(0.7) saturate(0.5); }
}

@keyframes av-knock {
  0%, 25%, 50%, 100% { translate: 0 0; }
  12%, 37% { translate: 0 1px; }
}

@keyframes av-nod {
  0%, 60%, 100% { translate: 0 0; }
  25% { translate: 0 1px; }
}

@keyframes av-lean {
  0%, 85%, 100% { translate: 0 0; }
  15%, 70% { translate: 0 -1px; }
}

@keyframes av-allin {
  0%, 70%, 100% { translate: 0 0; }
  6% { translate: 0 -2px; }
  14% { translate: 0 -3px; }
  24% { translate: 0 0; }
  30%, 40%, 50% { translate: -1px 0; }
  35%, 45%, 55% { translate: 1px 0; }
}

@keyframes av-fiddle {
  0%, 90%, 100% { translate: 0 0; }
  10%, 30%, 50%, 70% { translate: 0 1px; }
  20%, 40%, 60%, 80% { translate: 0 0; }
}

@keyframes av-think {
  0%, 100% { translate: 0 0; }
  50% { translate: 0 -1px; }
}

@keyframes av-breathe {
  0%, 50%, 100% { translate: 0 0; }
  20%, 30% { translate: 0 -1px; }
  70%, 80% { translate: 0 -1px; }
}

@keyframes av-shuffle {
  0%, 85%, 100% { translate: 0 0; }
  10%, 35% { translate: -1px 0; }
  50%, 75% { translate: 1px 0; }
}

@keyframes av-yawn {
  0%, 80%, 100% { translate: 0 0; }
  15%, 65% { translate: 0 -1px; }
}

@keyframes av-doze {
  0%, 85%, 100% { translate: 0 0; }
  25%, 50% { translate: 0 1px; }
  55%, 80% { translate: 0 2px; }
  86% { translate: 0 -1px; }
}

@keyframes av-peek {
  0%, 80%, 100% { translate: 0 0; }
  15%, 70% { translate: 1px -1px; }
}

/* Seat plates and portrait frames stay put: no bounce on a raise or a win. */
.seat.raising .seat-body,
.seat.winner .seat-body {
  animation: none;
}

/* The mark that pops up over the head with an emote. */
.emote-mark {
  position: absolute;
  z-index: 3;
  left: -1.1rem;
  top: -1.3rem;
  width: 2.2rem;
  height: 2.2rem;
  pointer-events: none;
  animation: mark-pop 2.4s ease-out both;
}

.emote-mark[data-icon="anger"] {
  animation: mark-pop 2.4s ease-out both, mark-throb 0.4s steps(2) infinite;
}

@keyframes mark-pop {
  0% { scale: 0; opacity: 0; }
  8% { scale: 1.3; opacity: 1; }
  14% { scale: 1; }
  85% { opacity: 1; translate: 0 -2px; }
  100% { opacity: 0; translate: 0 -8px; }
}

@keyframes mark-throb {
  50% { width: 1.8rem; height: 1.8rem; }
}

/* "…" in a little thought balloon while a player takes their time. */
.thought {
  position: absolute;
  z-index: 3;
  left: -2.1rem;
  top: -0.5rem;
  display: flex;
  gap: 3px;
  padding: 4px 5px;
  background: var(--paper);
  box-shadow: 0 0 0 2px var(--outline), 3px 3px 0 rgba(0, 0, 0, 0.4);
  pointer-events: none;
  animation: mark-pop-in 0.3s ease-out both;
}

.thought::after {
  content: "";
  position: absolute;
  right: -6px;
  bottom: -6px;
  width: 4px;
  height: 4px;
  background: var(--paper);
  box-shadow: 0 0 0 2px var(--outline);
}

.thought i {
  width: 4px;
  height: 4px;
  background: var(--outline);
  animation: thought-dot 1.2s steps(1) infinite;
}

.thought i:nth-child(2) { animation-delay: 0.3s; }
.thought i:nth-child(3) { animation-delay: 0.6s; }

@keyframes thought-dot {
  0% { opacity: 1; }
  50% { opacity: 0.25; }
}

@keyframes mark-pop-in {
  from { scale: 0; opacity: 0; }
}

/* The emote menu: your character pulling each face, over the chat bar. */
.chat {
  position: relative;
}

.emote-menu {
  position: absolute;
  z-index: 20;
  left: 0;
  bottom: calc(100% + 0.6rem);
  display: grid;
  grid-template-columns: repeat(3, auto);
  gap: 0.45rem;
  padding: 0.55rem;
  background: var(--panel);
  box-shadow: 0 0 0 3px var(--outline), inset 0 0 0 2px var(--panel-rim), var(--drop);
}

/* The chat-voices switch beside the emote button: just the speaker icon. */
.sound-toggle {
  padding-inline: 0.55rem;
}

.sound-toggle .px-icon {
  width: 1.35rem;
  height: 1.2rem;
  vertical-align: middle;
}

/* On desktop the bet sizes give up a little width rather than push the
   action box onto a second row. */
@media (min-width: 761px) and (min-height: 561px) {
  .raise {
    flex: 1 1 0;
    flex-wrap: nowrap;
  }

  .raise-picker {
    flex: 1 1 auto;
    min-width: 0;
  }

  /* They stretch to fill the row anyway, so little padding of their own. */
  .actions .presets button {
    padding-inline: 0.2rem;
  }
}

/* On phones the action box never grows wider than the screen: the six bet
   sizes share the row evenly (smaller text on the narrowest phones) instead
   of pushing it out sideways. */
@media (max-width: 760px) {
  .dock,
  .dock-stage {
    grid-template-columns: minmax(0, 1fr);
    min-width: 0;
  }

  .actions,
  .raise-picker,
  .presets {
    min-width: 0;
  }
}

@media (max-width: 420px) {
  .actions .presets button {
    font-size: 0.9rem;
  }
}

/* Narrow phones, and phones on their side (where the controls sit in a
   narrow column beside the table): the chat buttons get the first line and
   the chat field the whole second line, so it has room to type. */
@media (max-width: 520px), (max-height: 560px) and (min-width: 600px) {
  .chat {
    flex-wrap: wrap;
  }

  .chat input:not([type="range"]) {
    flex: 1 1 100%;
    min-width: 0;
  }

}

/* Phones at a table: the header shrinks to one slim row (table name, wallet,
   the two panel buttons and Cash out) so the table gets the room. The blinds
   are in every hand's history entry, and Log out is in the lobby. */
@media (max-width: 760px) {
  body[data-screen="table"] .topbar {
    flex-wrap: nowrap;
    gap: 0.5rem;
    padding: 0.4rem 0 0.3rem;
  }

  body[data-screen="table"] .brand {
    flex: 1 1 auto;
    min-width: 0;
  }

  body[data-screen="table"] .brand > div {
    min-width: 0;
  }

  body[data-screen="table"] .brand-chip,
  body[data-screen="table"] #status,
  body[data-screen="table"] #logout {
    display: none;
  }

  body[data-screen="table"] h1 {
    overflow: hidden;
    font-size: 1.15rem;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  body[data-screen="table"] .header-actions {
    flex: none;
    flex-wrap: nowrap;
    gap: 0.35rem;
  }

  /* Less air around the controls under the table. */
  body[data-screen="table"] .dock {
    gap: 0.35rem;
    padding-top: 0.15rem;
  }

  body[data-screen="table"] .chat {
    gap: 0.35rem;
  }
}

/* ---------- Table screen lettering ---------- */

/* Everything on the table screen (header, table, controls, side panel,
   dialogs and messages) is pixel lettering, with numbers in JetBrains Mono.
   The wanted poster and the card packs (their window and the view a pack
   opens in) are left out, so they look the same opened from the table as
   from the lobby, and the brand in the header keeps the lobby sign's
   lettering.
   Player names stay pure Monocraft, so a "145" in a name doesn't switch
   fonts; titles and card ranks keep Jersey 10; and the number displays (stacks,
   pot, bets, balance, raise amount) keep their JetBrains look. */
body[data-screen="table"] :is(.topbar, .room, dialog:not(.profile-dialog, .packs-dialog, .pack-opening), .toast) *,
body[data-screen="table"] :is(.topbar, .room, dialog:not(.profile-dialog, .packs-dialog, .pack-opening), .toast) *::before,
body[data-screen="table"] :is(.topbar, .room, dialog:not(.profile-dialog, .packs-dialog, .pack-opening), .toast) *::after,
body[data-screen="table"] #leave {
  font-family: var(--mix);
}

body[data-screen="table"] :is(.who, .history-name, .spectator-name, .chat-name, #username):not(:is(.packs-dialog, .pack-opening) *) {
  font-family: var(--name);
}

body[data-screen="table"] :is(h1, h3, h4, .card .rank, .dealer, .brand-name):not(:is(.packs-dialog, .pack-opening) *) {
  font-family: var(--title);
}

/* "Hand #9": its number in JetBrains like every other number. */
body[data-screen="table"] .history .history-hand h3 {
  font-family: var(--mix);
}

body[data-screen="table"] :is(.stack, .pot-amount, .bet-amount, #balance, .raise-amount, .raise-amount input) {
  font-family: var(--display);
}

/* Monocraft is drawn on a 9-pixel grid, so its letters only come out crisp
   at whole multiples of 9px. The table's buttons use exactly 18px (it is set
   at 82%, hence the division). The bet sizes keep their smaller size so five
   of them fit on one row, and on phones so do the chat-bar buttons. */
body[data-screen="table"] :is(.act-fold, .act-check, .act-call, .act-raise, .act-allin, .act-show) {
  font-size: calc(18px / 0.82);
  letter-spacing: 0;
}

@media (min-width: 761px) {
  body[data-screen="table"] .sit-out-btn {
    font-size: calc(18px / 0.82);
    letter-spacing: 0;
  }
}

/* Keyboard shortcuts: small key badges on the buttons (only where there is
   a keyboard and mouse), a ? button, and the list it opens. */
.key-hint,
.keys-dialog kbd {
  display: inline-block;
  min-width: 1.15em;
  padding: 0.05em 0.25em;
  background: var(--paper);
  color: var(--outline);
  font-family: var(--body);
  font-size: 0.7rem;
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  box-shadow: 0 0 0 2px var(--outline), 2px 2px 0 1px rgba(0, 0, 0, 0.4);
}

/* The buttons cut their pixel corners with a clip-path, which also clips
   their contents, so the badge sits just inside the top-left corner. */
.key-hint {
  position: absolute;
  top: 3px;
  left: 3px;
  z-index: 2;
  min-width: 0;
  padding: 0 0.2em;
  font-size: 0.56rem;
  line-height: 1.25;
  box-shadow: 0 0 0 1px var(--outline);
  opacity: 0.9;
  text-shadow: none;
  pointer-events: none;
}

/* Plain lettering for the badges, not the buttons' pixel text and shadow. */
body[data-screen="table"] kbd.key-hint {
  font-family: var(--body);
  font-weight: 700;
  text-shadow: none;
}

/* The bet sizes have plain corners (no clip-path), so their number badge can
   sit out on the corner without taking room from the label. */
.actions .presets button {
  clip-path: none;
}

.actions .presets .key-hint {
  top: -0.4rem;
  left: -0.4rem;
}

.actions button,
.emote-option {
  position: relative;
}

/* Phones and tablets (and narrow windows) never show keyboard hints. */
@media (hover: none), (pointer: coarse), (max-width: 760px) {
  .key-hint,
  .keys-button {
    display: none;
  }
}

.keys-button {
  padding-inline: 0.7rem;
}

.keys-dialog {
  width: min(46rem, calc(100vw - 2rem));
}

.keys-columns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1rem 1.6rem;
  padding: 0.4rem 0.2rem 0.2rem;
}

.keys-columns h4 {
  margin: 0 0 0.6rem;
  color: var(--gold);
  font-family: var(--title);
  font-size: 1.5rem;
  letter-spacing: 0.03em;
}

.keys-list {
  display: grid;
  gap: 0.55rem;
  margin: 0;
}

.keys-list div {
  display: grid;
  grid-template-columns: 7.2rem 1fr;
  gap: 0.8rem;
  align-items: baseline;
}

.keys-list dt {
  white-space: nowrap;
}

.keys-list dd {
  margin: 0;
  color: var(--ink);
  font-size: 0.95rem;
}

.emote-menu[hidden] {
  display: none;
}

.emote-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  min-width: 6.2rem;
  border: 0;
  padding: 0.45rem 0.5rem 0.4rem;
  background: var(--well);
  color: var(--paper);
  font-family: var(--body);
  font-size: 0.9rem;
  font-weight: 600;
  box-shadow: 0 0 0 2px var(--outline);
  cursor: pointer;
}

.emote-option:hover,
.emote-option:focus-visible {
  outline: none;
  background: #3a2819;
  box-shadow: 0 0 0 2px var(--gold);
}

.emote-face {
  font-size: 2.6rem;
  line-height: 0;
  box-shadow: 0 0 0 2px var(--outline);
}

.emote-face .avatar-art {
  display: block;
}

@media (prefers-reduced-motion: reduce) {
  .emote-mark,
  .thought,
  .thought i {
    animation: none;
  }
}

/* ---------- Cards ---------- */

.card {
  background: var(--paper);
  color: var(--card-ink);
}

.card {
  box-shadow:
    inset 0 0 0 2px var(--outline),
    inset -4px -4px 0 var(--paper-shade),
    3px 3px 0 rgba(0, 0, 0, 0.45);
}

.card.red {
  color: var(--card-red);
}

.card .rank {
  font-family: var(--display);
  font-size: calc(var(--cw) * 0.44);
  font-weight: 400;
}

.card .mini {
  font-size: calc(var(--cw) * 0.25);
  margin-top: 0.12em;
}

.card > .suit {
  margin-top: 22%;
  margin-left: 16%;
  font-size: calc(var(--cw) * 0.48);
}

.card.big > .suit {
  margin: 8% 0 0;
  font-size: calc(var(--cw) * 0.58);
}

/* A back's tile repeats at --back-size (a whole-card picture sets 100% and
   no-repeat). */
.card.back {
  background: var(--back-tile, var(--px-card-back)) var(--back-at, 0 0) / var(--back-size, calc(var(--cw) * 0.4) calc(var(--cw) * 0.4)) var(--back-repeat, repeat), var(--back-ground, #6b2118);
  box-shadow:
    inset 0 0 0 2px var(--outline),
    inset 0 0 0 calc(var(--back-frame, var(--cw) * 0.07) + 2px) var(--paper),
    inset 0 0 0 calc(var(--back-frame, var(--cw) * 0.07) + 4px) var(--outline),
    3px 3px 0 rgba(0, 0, 0, 0.45);
}

/* Cards that don't make the hand (kickers) step back. */
.guide-cards .card.guide-spare {
  opacity: 0.45;
  filter: grayscale(0.6);
}

/* ---------- Dock: result, actions, chat ---------- */

#result {
  font-size: 1.05rem;
}

.actions.your-turn {
  box-shadow: 0 0 0 3px var(--outline), inset 0 0 0 3px var(--gold-deep), var(--drop);
  animation: slide-up 0.3s steps(3);
}

.actions .presets button {
  --face: var(--well);
  --hi: #2a1d14;
  --side: var(--outline);
  font-family: var(--display);
  font-size: 1rem;
  text-shadow: none;
  box-shadow: inset 0 0 0 2px var(--outline), inset 0 3px 0 var(--hi);
}

.actions .presets button.active {
  --face: var(--gold-dark);
  color: var(--gold);
}

.raise-amount {
  background: var(--well);
  font-size: 1.3rem;
  box-shadow: 0 0 0 3px var(--outline), inset 0 0 0 2px var(--gold-deep);
}

.raise-amount:focus-within {
  box-shadow: 0 0 0 3px var(--outline), inset 0 0 0 2px var(--gold);
}

.raise-amount:has([aria-invalid="true"]) {
  box-shadow: 0 0 0 3px var(--outline), inset 0 0 0 2px #e0715a;
  color: #e88a74;
}

.sit-out-btn {
  font-family: var(--display);
  font-size: 1.05rem;
  font-weight: 400;
  letter-spacing: 0.04em;
}

.chat-name.tone-0 { color: var(--paper); }
.chat-name.tone-1 { color: #e8c477; }
.chat-name.tone-2 { color: #b7c99a; }
.chat-name.tone-3 { color: #e0a890; }
.chat-name.tone-4 { color: #a9bccb; }
.chat-name.tone-5 { color: #c7b2c4; }

.chat-name {
  font-weight: 700;
}

@keyframes panel-flash {
  from { box-shadow: inset 0 0 0 3px var(--gold); }
}

/* ---------- Spectators ---------- */

.spectators-label,
.spectator {
  background: rgba(18, 11, 7, 0.7);
  box-shadow: 0 0 0 2px var(--outline);
}

.spectators-label {
  font-family: var(--display);
  font-size: 0.95rem;
  font-weight: 400;
}

.spectator.you {
  box-shadow: 0 0 0 2px var(--outline), inset 0 0 0 2px var(--gold-deep);
}

/* ---------- History panel ---------- */

.history-head {
  border-bottom: 3px solid var(--outline);
}

.history-tabs button {
  font-size: 1.7rem;
}

.history-hand {
  background: #1f150f;
  box-shadow: 0 0 0 2px var(--outline), inset 0 0 0 2px rgba(236, 224, 194, 0.06);
}

.history-hand:first-child {
  box-shadow: 0 0 0 2px var(--outline), inset 0 0 0 2px var(--gold-deep);
}

.history-hand.arrive {
  animation: result-in 0.3s steps(3);
}

.history-hand h3 {
  font-size: 1.3rem;
}

.history-board {
  background: #2f4a2f;
  box-shadow: inset 0 0 0 2px #1f331f;
}

.history-label,
.history-none,
.odds-hint {
  color: var(--sage-text);
}

.history-label {
  font-family: var(--display);
  font-size: 0.9rem;
  font-weight: 400;
}

.history-line.you {
  background: rgba(217, 176, 92, 0.12);
  box-shadow: inset 3px 0 0 var(--gold);
}

.history-you {
  background: var(--gold);
  color: #2a1a08;
  font-family: var(--display);
  font-size: 0.8rem;
  font-weight: 400;
}

.history-line .net {
  background: var(--well);
  font-size: 1.05rem;
}

.history-line.up .net { background: #26331d; color: #b4cc8c; }
.history-line.down .net { background: #3a1912; color: #e88a74; }

/* Win chances in the history and over the seats (history-stats.css). */
.odds-chip {
  background: var(--well);
  box-shadow: 0 0 0 2px var(--outline);
}

.odds-chip.lead {
  background: var(--gold);
  color: #2a1a08;
  box-shadow: 0 0 0 2px var(--outline);
}

.odds-chip.tie {
  background: #7c9a5a;
  color: var(--outline);
  box-shadow: 0 0 0 2px var(--outline);
}

.odds-tie-label,
.odds-tie-note {
  color: #b4cc8c;
  text-shadow: none;
}

.odds-hint-icon {
  background: var(--well);
  box-shadow: inset 0 0 0 1px var(--sage-text);
}

.odds-bar {
  background: rgba(236, 224, 194, 0.07);
  box-shadow: inset -3px 0 0 rgba(236, 224, 194, 0.2);
}

.odds-lead .odds-bar {
  background: rgba(217, 176, 92, 0.18);
  box-shadow: inset -3px 0 0 var(--gold-deep);
}

.seat-odds {
  font-size: 1rem;
  filter: drop-shadow(3px 3px 0 rgba(0, 0, 0, 0.55));
}

.seat-odds .odds-chip:not(.lead, .tie) {
  background: rgba(18, 11, 7, 0.88);
  box-shadow: 0 0 0 2px var(--outline);
}

.has-odds [data-odds-street]:hover,
.has-odds [data-odds-street]:focus-visible,
.history-hand[data-odds="flop"] .history-street[data-street="flop"],
.history-hand[data-odds="turn"] .history-street[data-street="turn"],
.history-hand[data-odds="river"] .history-street[data-street="river"],
.history-hand[data-odds="preflop"] .history-label {
  outline-color: var(--gold);
}

/* ---------- Toast and celebration ---------- */

.toast {
  background: #8f2d20;
  color: #f1e4c6;
  box-shadow: 0 0 0 3px var(--outline), inset 0 0 0 3px #b0412c, var(--drop);
  animation: toast 0.3s steps(3);
}

.confetti {
  width: 1.2rem;
  height: 1.2rem;
  background: center / contain no-repeat;
}

.banner {
  box-shadow:
    0 0 0 3px var(--outline),
    inset 0 0 0 3px var(--gold-deep),
    inset 0 0 0 6px var(--outline),
    10px 10px 0 rgba(0, 0, 0, 0.5);
}

.banner .banner-emoji {
  width: 4rem;
  height: 3.4rem;
}

.banner .banner-title {
  color: var(--paper);
  font-size: clamp(2.6rem, 8vw, 4.4rem);
  text-shadow: 4px 4px 0 var(--outline);
}

.banner .banner-sub {
  font-size: 1.2rem;
}

/* ---------- Account bar ---------- */

.wallet {
  background: var(--well);
  box-shadow: 0 0 0 3px var(--outline), inset 0 0 0 2px var(--gold-deep);
}

#balance {
  font-size: 1.5rem;
  text-shadow: 2px 2px 0 var(--outline);
}

#username {
  font-weight: 600;
}

.me-avatar,
.avatar-preview {
  background: var(--well);
  box-shadow: 0 0 0 3px var(--outline), 0 0 0 6px var(--gold-deep), var(--drop);
}

.me-avatar:hover,
.me-avatar:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 0 0 3px var(--outline), 0 0 0 6px var(--gold), var(--drop);
}

.profile-avatar-edit {
  background-color: var(--gold);
  background-size: 75%;
  box-shadow: 0 0 0 2px var(--outline);
}

/* .px-icon would show it; only your own profile's avatar can be edited. */
.profile-avatar:disabled .profile-avatar-edit {
  display: none;
}

/* ---------- Avatar picker ---------- */

.avatar-option {
  background: var(--well);
  box-shadow: 0 0 0 2px var(--outline);
  transition: none;
}

.avatar-option:hover {
  transform: translateY(-2px);
  background: var(--well);
}

.avatar-option[aria-checked="true"] {
  transform: none;
  background: var(--well);
  box-shadow: 0 0 0 2px var(--outline), 0 0 0 5px var(--gold);
}

.avatar-picker.strip .avatar-grid {
  background: var(--well);
  padding: 0.45rem 0.6rem;
}

/* ---------- Leaderboard ---------- */

.leader-rank {
  font-size: 1.4rem;
  text-shadow: 2px 2px 0 var(--outline);
}

.leaders li.podium-1 .leader-rank { color: var(--gold); }
.leaders li.podium-2 .leader-rank { color: #c2bcae; }
.leaders li.podium-3 .leader-rank { color: #c9713d; }

.leaders li.me {
  box-shadow: 0 0 0 3px var(--outline), inset 0 0 0 3px var(--gold-deep), var(--drop);
}

.leaders li[role="button"]:hover,
.leaders li[role="button"]:focus-visible {
  background: #3a2819;
}

.leader-avatar {
  background: var(--well);
  box-shadow: 0 0 0 2px var(--outline);
}

.leaders li.me .leader-avatar {
  box-shadow: 0 0 0 2px var(--outline), 0 0 0 4px var(--gold-deep);
}

.leaders .leader-chips {
  font-size: 1.3rem;
  text-shadow: 2px 2px 0 var(--outline);
}

.week-chip {
  background: var(--well);
  box-shadow: 0 0 0 2px var(--outline);
}

.week-chip[aria-checked="true"] {
  background: var(--gold-dark);
  color: var(--gold);
  box-shadow: 0 0 0 2px var(--outline), inset 0 0 0 2px var(--gold);
}

.week-number {
  font-size: 1.25rem;
}

.week-chip.live .week-sub {
  color: #b4cc8c;
}

/* ---------- Dialogs ---------- */

.dialog {
  background: var(--panel);
  box-shadow:
    0 0 0 3px var(--outline),
    inset 0 0 0 3px var(--gold-deep),
    inset 0 0 0 6px var(--outline),
    10px 10px 0 rgba(0, 0, 0, 0.5);
}

.dialog[open] {
  animation: pop-in 0.25s steps(3);
}

.dialog::backdrop {
  background: rgba(10, 6, 4, 0.78);
  backdrop-filter: none;
}

.dialog h3 {
  font-size: 2rem;
  text-shadow: 3px 3px 0 var(--outline);
}

.dialog-amount {
  font-size: 3rem;
  text-shadow: 3px 3px 0 var(--outline);
}

.guide-head h3 {
  font-size: 1.8rem;
}

.guide-hand {
  background: #1f150f;
  box-shadow: 0 0 0 2px var(--outline), inset 0 0 0 2px rgba(236, 224, 194, 0.06);
}

.guide-place {
  font-size: 1.5rem;
}

.guide-name {
  font-weight: 700;
}

.guide-scroll h4 {
  font-size: 1.35rem;
}

/* ---------- Scheduled tournaments: the notice board ---------- */

/* A board of dark planks in a heavy wooden frame, with a notice nailed up
   for each tournament. */
.lobby-scheduled .table-list {
  grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: 1.4rem 1.2rem;
  padding: 1.6rem 1.5rem 1.8rem;
  background:
    var(--px-sign-grain) 0 0 / 144px 30px repeat,
    repeating-linear-gradient(
      180deg,
      #74502f 0 2px,
      #5c3b22 2px 45px,
      #24160c 45px 48px
    );
  image-rendering: pixelated;
  box-shadow:
    0 0 0 3px var(--outline),
    inset 0 0 0 3px #7a5236,
    inset 0 0 0 9px #2e1d12,
    inset 0 0 0 12px var(--outline),
    var(--drop);
}

.lobby-scheduled .lobby-empty {
  grid-column: 1 / -1;
  color: var(--paper);
  text-shadow: 2px 2px 0 var(--outline);
}

/* A notice on the wanted poster's aged paper (the --px-poster 9-slice, nailed
   at the corners): printed heading and terms, then a sign-up sheet. Every
   other one hangs a little lower, as if put up by hand. */
.notice {
  --notice-ink: #2a1a0f;
  --notice-ink-soft: #6a4d30;
  --notice-red: #8f2d20;
  --notice-pen: #283a5c;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  min-width: 0;
  border: 20px solid transparent;
  border-image: var(--px-poster) 10 fill / 20px round;
  padding: 0 0.3rem;
  color: var(--notice-ink);
  font-family: var(--mix);
  image-rendering: pixelated;
  filter: drop-shadow(5px 5px 0 rgba(0, 0, 0, 0.5));
}

.notice:nth-child(even) {
  translate: 0 0.5rem;
}

.notice-kicker {
  margin: 0;
  padding-inline: 1.6rem;
  color: var(--notice-red);
  font-size: 0.75rem;
  line-height: 1.1;
  letter-spacing: 0.14em;
  text-align: center;
  text-transform: uppercase;
}

.notice-name {
  margin: -0.25rem 0 0;
  padding-bottom: 0.3rem;
  border-bottom: 3px double var(--notice-ink);
  font-family: var(--title);
  font-size: 1.75rem;
  font-weight: 400;
  line-height: 1;
  text-align: center;
  /* One line whatever the name, so every notice is the same size; the full
     name shows on hover. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* When and what it costs, side by side. */
.notice-terms {
  display: flex;
  justify-content: space-between;
  gap: 0.8rem;
  margin: 0;
}

.notice-terms dt {
  color: var(--notice-ink-soft);
  font-size: 0.7rem;
  line-height: 1.2;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.notice-terms dd {
  margin: 0;
  overflow: hidden;
  font-size: 0.95rem;
  line-height: 1.2;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.notice-terms div {
  min-width: 0;
}

.notice-terms div:last-child {
  text-align: right;
}

/* The sign-up sheet: a line per seat in two columns, filled down the first
   (--rows lines, set per notice) and then the second, each name written on
   its line in pen beside the player's face. */
.notice-sheet {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: repeat(var(--rows, 3), auto);
  grid-auto-flow: column;
  gap: 0 0.7rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.notice-sheet li {
  display: flex;
  align-items: flex-end;
  gap: 0.35rem;
  min-width: 0;
  min-height: 1.6rem;
  padding: 0.1rem 0 0.15rem;
  border-bottom: 2px solid rgb(42 26 15 / 0.45);
}

.notice-face {
  flex: none;
  width: 1.2rem;
  height: 1.2rem;
  font-size: 1.2rem;
  background: var(--well);
  box-shadow: 0 0 0 2px var(--notice-ink);
}

.notice-signature {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  color: var(--notice-pen);
  font-size: 0.85rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.notice-sheet li.mine .notice-signature {
  font-weight: 700;
}

/* Already at the table: a small green dot after the name. */
.notice-here {
  flex: none;
  align-self: center;
  width: 0.45rem;
  height: 0.45rem;
  background: #4f7a32;
  box-shadow: 0 0 0 1px var(--notice-ink);
}

/* Signing up: red like an ink stamp, as on the login form. */
.notice .notice-sign {
  --face: #8f2d20;
  --hi: #b0412c;
  --side: #561a12;
  margin-top: 0.15rem;
  padding: 0.55rem 0.8rem;
  overflow: hidden;
  color: #f1e4c6;
  font-size: 1rem;
  text-overflow: ellipsis;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.4);
  white-space: nowrap;
}

/* Backing out: a small red ✕ in the top corner, clear of the nail. */
.notice-leave {
  position: absolute;
  top: 0;
  right: 0;
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  border: 0;
  padding: 0;
  background: var(--notice-red);
  color: #f1e4c6;
  font-size: 0.85rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 0 0 2px var(--notice-ink);
}

.notice-leave:hover {
  background: #b0412c;
}

/* ---------- Profile: the wanted poster ---------- */

/* Aged paper from the --px-poster 9-slice (burnt ragged edges, torn corners,
   a nail in each corner) with the player as the outlaw: dark ink on the
   paper, the avatar as the mugshot and the balances as the reward. The
   dialog itself is bare so only the poster's ragged shape shows. */
.profile-dialog {
  --poster-ink: #2a1a0f;
  --poster-ink-soft: #5a4128;
  --poster-red: #8f2d20;
  overflow: visible;
  background: none;
  box-shadow: none;
  color: var(--poster-ink);
}

.profile-card {
  border: 30px solid transparent;
  border-image: var(--px-poster) 10 fill / 30px round;
  padding: 0 0.4rem 0.2rem;
  image-rendering: pixelated;
  filter: drop-shadow(10px 10px 0 rgba(0, 0, 0, 0.5));
  scrollbar-color: #9c7c4b transparent;
}

/* Inside the poster's top-right corner, clear of the nail. */
.profile-close {
  top: 1.9rem;
  right: 1.9rem;
}

/* Kept clear of the close button on both sides, and as big as fits. */
/* WANTED in black at the top, DEAD OR ALIVE in red under the name: big
   poster letters struck a little off register like cheap wood type, each
   between two diamond ornaments. */
.profile-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.7rem;
  width: 100%;
  font-family: var(--title);
  font-size: clamp(1.5rem, 8vw, 2.1rem);
  line-height: 1;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  text-shadow: 2px 2px 0 rgba(42, 26, 15, 0.35);
}

.profile-banner::before,
.profile-banner::after {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  background: currentColor;
  rotate: 45deg;
  box-shadow: 2px 2px 0 rgba(42, 26, 15, 0.35);
}

.profile-wanted {
  /* Clear of the close button in the corner. */
  padding-inline: 2.6rem;
  color: var(--poster-ink);
  text-shadow: 2px 2px 0 rgba(143, 45, 32, 0.35);
}

.profile-alive {
  margin-top: 0.7rem;
  color: var(--poster-red);
}

/* The mugshot: the avatar on dark backing in a double-ruled frame. */
.profile-avatar {
  margin: 0.9rem 0 0.8rem;
  background: var(--well);
  box-shadow: 0 0 0 3px var(--poster-ink), 0 0 0 6px var(--paper), 0 0 0 9px var(--poster-ink);
}

.profile-avatar:not(:disabled):hover,
.profile-avatar:focus-visible {
  transform: none;
  box-shadow: 0 0 0 3px var(--poster-ink), 0 0 0 6px var(--paper), 0 0 0 9px var(--poster-red);
}

.profile-avatar-edit {
  box-shadow: 0 0 0 3px var(--poster-ink);
}

.dialog h3.profile-name {
  font-size: 2.6rem;
  line-height: 1;
  text-shadow: none;
}

.profile-status {
  color: var(--poster-ink-soft);
  font-size: 0.8rem;
}

.profile-loading {
  color: var(--poster-ink-soft);
}

/* The two balances side by side, ruled above and below. */
.profile-reward {
  border-block: 3px solid var(--poster-ink);
  padding: 0.5rem 0 0.6rem;
}

.profile-balance + .profile-balance {
  border-left: 2px dashed rgba(42, 26, 15, 0.45);
}

.profile-balance-label {
  font-family: var(--display);
  font-size: 0.95rem;
  letter-spacing: 0.06em;
}

.profile-balance-value {
  color: var(--poster-red);
  font-family: var(--title);
  font-size: 2.3rem;
  line-height: 1;
}

.profile-balance-note {
  color: var(--poster-ink-soft);
}

.profile-stat-label {
  color: var(--poster-ink-soft);
}

.profile-stat-value {
  font-weight: 700;
}

.profile-stat-value.up { color: #3f5531; }
.profile-stat-value.down { color: var(--poster-red); }

@media (max-width: 380px) {
  .dialog h3.profile-name {
    font-size: 2.2rem;
  }

  .profile-balance-value {
    font-size: 1.9rem;
  }
}

/* ---------- Pixel fonts for titles, player names and card ranks ---------- */

h1,
.brand-name,
.marquee-title,
.lobby h3,
.dialog h3,
.guide-head h3,
.history-hand h3,
.guide-scroll h4,
.card .rank {
  font-family: var(--title);
}

#username,
.who,
.leaders .leader-name,
.history-name,
.spectator-name,
.chat-name {
  font-family: var(--name);
}

/* ---------- Small screens ---------- */

@media (max-width: 760px), (max-height: 560px) {
  .tab-count:not(:empty) {
    background: var(--red);
    font-family: var(--display);
    font-size: 0.9rem;
    font-weight: 400;
  }
}

/* Phones in portrait: the table stands upright, so it gets the tall art. */
@media (max-width: 760px) {
  .rail {
    background-image: var(--px-table-tall);
  }

  /* The felt is only about 21rem across here, so the plates slim down and the
     seats stand in three bands: the top seats, the board and pot between the
     side seats, then you at the bottom with your cards resting on your plate. */
  .seat {
    width: 5.6rem;
  }

  .seat-body {
    padding: 0.25rem 0.25rem 0.3rem;
  }

  .avatar {
    width: 2.3rem;
    height: 2.3rem;
  }

  .avatar.is-character {
    font-size: 2.3rem;
  }

  .who.long {
    font-size: 0.68rem;
  }

  .stack {
    font-size: 1.3rem;
  }

  .bet-amount {
    padding: 0.15rem 0.35rem;
    font-size: 1.2rem;
  }

  .made {
    font-size: 0.8rem;
  }

  /* Bet tags lie flat and stay clear of the cards and the pot: beside the top
     seat and your plate, inward of the side seats. */
  .bet {
    flex-direction: row;
    gap: 0.2rem;
  }

  .seat[data-slot="0"] { left: 50%; top: 92%; --bx: 4.4rem; --by: 0.9rem; }

  .seat[data-slot="0"] .bet {
    translate: var(--bx) calc(-50% + var(--by));
  }

  .seat[data-slot="1"] { left: 14%; top: 80%; --bx: 5.2rem; --by: -2.3rem; }
  .seat[data-slot="2"] { left: 14%; top: 30%; --bx: 5.2rem; --by: -0.4rem; }
  .seat[data-slot="3"] { left: 50%; top: 8%; --bx: 5.3rem; --by: -0.6rem; }
  .seat[data-slot="4"] { left: 86%; top: 30%; --bx: -5.2rem; --by: -0.4rem; }
  .seat[data-slot="5"] { left: 86%; top: 80%; --bx: -5.2rem; --by: -2.3rem; }

  /* Your plate lies flat, portrait beside name and stack, and your cards sit
     right on it instead of floating above. */
  .seat.you {
    width: 8rem;
  }

  .seat.you .seat-body {
    flex-direction: row;
    max-width: 11rem;
    padding: 0.3rem 0.6rem 0.3rem 0.3rem;
  }

  .seat.you .seat-info {
    justify-items: start;
    width: auto;
    margin: 0 0 0 0.5rem;
    text-align: left;
  }

  .seat.you .who.long {
    font-size: 0.8rem;
  }

  .seat.you .seat-cards {
    height: 3.6rem;
  }

  .seat.you .seat-cards .card {
    --cw: 3.2rem;
    translate: 0 0;
  }

  .seat.you .seat-cards .card:last-child {
    translate: 0 -0.25rem;
  }

  .seat.you .made {
    max-width: none;
  }

  .seat.you .bubble {
    bottom: calc(100% - 0.3rem);
  }

  /* The board fills the gap between the side seats, with the pot above it,
     narrow enough to pass between plates. */
  .center {
    top: 50%;
    gap: 0.4rem;
  }

  .center .pot {
    order: -1;
  }

  /* Keeps its room once the pot is swept, so the board doesn't jump up. */
  .pot {
    gap: 0.4rem;
    min-height: 2.45rem;
  }

  .pot-chips {
    --chip-size: 0.9rem;
  }

  .pot-amount {
    font-size: 2.1rem;
  }

  .pot-amount::before {
    font-size: 1rem;
    letter-spacing: 0.04em;
  }

  .pot-label {
    font-size: 1rem;
  }

  /* Dock: every control is a comfortable thumb target, about 44px tall. */
  #result {
    padding: 0.25rem 0.8rem;
    font-size: 0.9rem;
  }

  .actions button {
    min-height: 3rem;
    padding: 0.35rem 0.5rem;
    font-size: 1.25rem;
  }

  /* Up to six presets share the row evenly instead of pushing it wider. */
  .presets {
    display: grid;
    grid-auto-columns: minmax(0, 1fr);
    grid-auto-flow: column;
    gap: 0.3rem;
  }

  .actions .presets button {
    min-width: 0;
    min-height: 2.75rem;
    padding: 0.2rem 0;
    font-size: 1.05rem;
    letter-spacing: 0;
  }

  .raise-row {
    min-height: 2.75rem;
  }

  .raise-amount {
    align-self: stretch;
  }

  .raise input[type="range"] {
    --thumb: 2.4rem;
  }

  .sit-out-btn,
  .chat input:not([type="range"]) {
    min-height: 2.75rem;
  }

  /* The history drawer's tabs and close button, big enough for a thumb. */
  .history-tabs button {
    min-height: 2.75rem;
  }

  .history-close {
    width: 2.75rem;
    height: 2.75rem;
  }

  /* Holds the dock at its full height between turns, so the table doesn't
     jump in size each time your turn comes round. */
  .actions {
    min-height: 11.1rem;
  }
}

/* Short phones: a tighter dock leaves the table more of the screen. */
@media (max-width: 760px) and (max-height: 740px) {
  .dock {
    gap: 0.35rem;
    padding-top: 0.15rem;
  }

  #result {
    min-height: 0;
    padding: 0.15rem 0.7rem;
    font-size: 0.8rem;
  }

  .actions,
  .actions.your-turn {
    gap: 0.4rem;
    padding: 0.45rem;
  }

  /* The full panel (five bet sizes, slider, Fold/Call/Raise), so the box and
     the table above it keep one size whether or not it is your turn. */
  .actions {
    min-height: 10rem;
  }

  .raise-picker {
    gap: 0.4rem;
  }

  .actions button {
    min-height: 2.6rem;
    font-size: 1.15rem;
  }

  .actions .presets button {
    min-height: 2.4rem;
    font-size: 1rem;
  }

  .raise-row {
    min-height: 2.4rem;
  }

  .sit-out-btn,
  .chat input:not([type="range"]) {
    min-height: 2.5rem;
  }
}

/* Phones on their side: a narrower table scales up further, so names, stacks
   and cards stay readable. Narrower phones on their side keep the upright
   table from the portrait rules. */
@media (max-height: 560px) and (min-width: 761px) {
  .rail {
    width: 46rem;
  }

  /* Pull the side seats in so their plates stay on the table. */
  .seat[data-slot="1"] { left: 13%; }
  .seat[data-slot="2"] { left: 12%; }
  .seat[data-slot="4"] { left: 88%; }
  .seat[data-slot="5"] { left: 87%; }
}

/* The dock beside the table pairs Fold with Call and keeps every raise preset
   on one row, so it all fits the height of a phone on its side. */
@media (max-height: 560px) and (min-width: 600px) {
  .dock {
    width: 20rem;
  }

  #result {
    min-height: 0;
    padding: 0.2rem 0.7rem;
    font-size: 0.85rem;
  }

  .actions,
  .actions.your-turn {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.4rem;
    padding: 0.5rem;
  }

  .actions {
    /* The full two-column panel, held between turns too. */
    min-height: 12rem;
  }

  .raise {
    display: contents;
  }

  .raise-picker,
  .actions .act-raise,
  .actions .act-allin,
  .actions .act-show {
    grid-column: 1 / -1;
  }

  .raise-picker {
    order: 0;
    gap: 0.4rem;
  }

  .actions button {
    min-height: 2.6rem;
    font-size: 1.15rem;
  }

  .presets {
    display: grid;
    grid-auto-columns: minmax(0, 1fr);
    grid-auto-flow: column;
    gap: 0.25rem;
  }

  .actions .presets button {
    min-width: 0;
    min-height: 2.3rem;
    padding-inline: 0;
    font-size: 0.9rem;
    letter-spacing: 0;
  }

  .raise-row {
    min-height: 2.3rem;
  }

  .raise input[type="range"] {
    --thumb: 2rem;
  }

  .sit-out-btn,
  .chat input:not([type="range"]) {
    min-height: 2.4rem;
  }
}

@media (max-width: 1100px), (max-height: 560px) {
  .history {
    background: var(--panel);
    box-shadow: 0 0 0 3px var(--outline), inset 0 0 0 3px var(--panel-rim), 10px 10px 0 rgba(0, 0, 0, 0.6);
  }
}

/* Narrow screens: at a table the header's buttons slim down to share one row,
   and wrap rather than run off the right edge on the narrowest phones. Log
   out, last in the row, stays rightmost. */
@media (max-width: 760px) {
  .header-actions {
    flex-wrap: wrap;
    justify-content: flex-end;
    row-gap: 0.4rem;
  }

  body[data-screen="table"] #leave {
    padding-inline: 0.5rem;
    font-size: 0.95rem;
  }

  body[data-screen="table"] .wallet {
    padding-inline: 0.4rem 0.6rem;
  }

  body[data-screen="table"] #balance {
    font-size: 1.2rem;
  }

  body[data-screen="table"] .history-toggle {
    padding-inline: 0.45rem;
  }
}

/* Six-seven shown off: the two hole cards do the 6-7, one up while the other
   comes down, like a pair of hands weighing it up (sixSeven in main.ts).
   --juggled sets it back by the time already spent, so a seat rebuilt
   mid-juggle carries on where it was. */
.seat.six-seven .seat-cards .card {
  animation: six-seven 400ms ease-in-out calc(var(--juggled, 0ms)) 6;
}

.seat.six-seven .seat-cards .card:last-child {
  animation-delay: calc(var(--juggled, 0ms) - 200ms);
}

@keyframes six-seven {
  25% { transform: translateY(-0.9rem); }
  75% { transform: translateY(0.4rem); }
}

/* Achievement styles (achievements.ts), set by data-back and data-sign on a
   seat or an achievements preview: card backs swap the tile and ground of .card.back, signs the
   planks, grain and nails of .seat-body. */
[data-back="back-saddle"] { --back-tile: var(--px-back-saddle); --back-ground: #f3f1ec; }
[data-back="back-midnight"] { --back-tile: var(--px-back-midnight); --back-ground: #16203a; }
[data-back="back-emerald"] { --back-tile: var(--px-back-emerald); --back-ground: #1a5234; }
[data-back="back-rattler"] { --back-tile: var(--px-back-rattler); --back-ground: #b98d55; }
[data-back="back-sheriff"] { --back-tile: var(--px-back-sheriff); --back-ground: #17130f; }
/* The card backs from the Event Pack (packs.ts), set the same way, with no
   paper border (--back-frame 0px), just the dark edge. Every data-back
   resets these first, so a back inside another's element isn't left with
   its settings. */
[data-back] { --back-size: initial; --back-repeat: initial; --back-at: initial; --back-frame: initial; }
[data-back="cobweb-back"] { --back-tile: var(--px-back-cobweb); --back-ground: #140d1a; --back-size: 100% 100%; --back-repeat: no-repeat; --back-frame: 0px; }
/* The house back in other colours, from the Standard and Jumbo Packs. */
[data-back="violet-back"] { --back-tile: var(--px-back-violet); --back-ground: #3e1f5e; }
[data-back="ember-back"] { --back-tile: var(--px-back-ember); --back-ground: #943c12; }
[data-back="forest-back"] { --back-tile: var(--px-back-forest); --back-ground: #243d1e; }
[data-back="crimson-back"] { --back-tile: var(--px-back-crimson); --back-ground: #8a1418; }
[data-back="charcoal-back"] { --back-tile: var(--px-back-charcoal); --back-ground: #262626; }

/* Portrait backdrops from packs (packs.ts), set by data-backdrop on a seat
   or a pack card: the character's own backdrop gives way to the pack's. */
[data-backdrop] .avatar-art.character svg > rect:first-child {
  fill-opacity: 0;
}

[data-backdrop="dusk-backdrop"] .avatar-art.character {
  background: linear-gradient(#3a2050 0%, #a8445a 55%, #f0a04a 100%);
}

[data-backdrop="wood-backdrop"] .avatar-art.character {
  background: repeating-linear-gradient(90deg, #7a4a26 0 0.18em, #6a3e1e 0.18em 0.2em, #8a5630 0.2em 0.36em, #6a3e1e 0.36em 0.38em);
}

[data-backdrop="desert-backdrop"] .avatar-art.character {
  background:
    radial-gradient(circle at 70% 38%, #fff0b0 0 0.12em, transparent 0.13em),
    linear-gradient(#f0b46a 0%, #f6d290 60%, #c8884a 60%, #b07438 100%);
}

[data-back="graveyard-back"] { --back-tile: var(--px-back-graveyard); --back-ground: #111111; --back-size: 100% 100%; --back-repeat: no-repeat; --back-frame: 0px; }

[data-sign="sign-pine"] {
  --sign-grain: var(--px-sign-grain-pine);
  /* Toned down so the cream name and gold stack read on the pale boards. */
  --sign-dim: linear-gradient(rgba(46, 26, 10, 0.35), rgba(46, 26, 10, 0.35));
  --plank-a: #c9a06a;
  --plank-b: #c09662;
  --plank-top: #e8cb98;
  --plank-seam: #7a5430;
  --plank-seam-hi: #dcb880;
  --plank-bottom: #94693c;
  --sign-style-rim: #6e4a26;
}

[data-sign="sign-ebony"] {
  --sign-grain: var(--px-sign-grain-ebony);
  --sign-nail: var(--px-sign-nail-silver);
  --plank-a: #221d1a;
  --plank-b: #1c1815;
  --plank-top: #3f3833;
  --plank-seam: #050404;
  --plank-seam-hi: #36302b;
  --plank-bottom: #0e0c0a;
  --sign-style-rim: #080706;
}

[data-sign="sign-redwood"] {
  --sign-grain: var(--px-sign-grain-redwood);
  --sign-nail: var(--px-sign-nail-copper);
  --plank-a: #7a2c1c;
  --plank-b: #6f2718;
  --plank-top: #a5523a;
  --plank-seam: #300d08;
  --plank-seam-hi: #94442e;
  --plank-bottom: #4a1610;
  --sign-style-rim: #2e0c06;
}

/* The Joker: planks painted green, with brass nails. */
[data-sign="sign-joker"] {
  --sign-grain: var(--px-sign-grain-joker);
  --sign-nail: var(--px-sign-nail-brass);
  --plank-a: #2f6e41;
  --plank-b: #2c6a3e;
  --plank-top: #5a9c6c;
  --plank-seam: #0c2a14;
  --plank-seam-hi: #4a8e5c;
  --plank-bottom: #1c4a2a;
  --sign-style-rim: #0f2a16;
}

/* Royal Flush (sign-brass, from the days it was a brass-trimmed plank):
   royal-blue damask in a polished gold frame, gold studs, and a small star
   that twinkles now and then, here and there on the boards, in steps to
   stay pixel-crisp. Straight Flush is the same sign in red. */
[data-sign="sign-brass"] {
  --sign-grain: var(--px-sign-grain-royal);
  --sign-nail: var(--px-sign-nail-royal);
  --sign-star: var(--px-royal-sparkle);
  --plank-a: #1d2a66;
  --plank-b: #18235a;
  --plank-top: #4a63c8;
  --plank-seam: #0a1034;
  --plank-seam-hi: #33479c;
  --plank-bottom: #0d1440;
  --sign-style-rim: #ffd75a;
}

[data-sign="sign-straight"] {
  --sign-grain: var(--px-sign-grain-straight);
  --sign-nail: var(--px-sign-nail-royal);
  --sign-star: var(--px-straight-sparkle);
  --plank-a: #6a1622;
  --plank-b: #5c121d;
  --plank-top: #c8404f;
  --plank-seam: #2a050a;
  --plank-seam-hi: #9c2a38;
  --plank-bottom: #3a0810;
  --sign-style-rim: #ffd75a;
}

:is([data-sign="sign-brass"], [data-sign="sign-straight"]) .seat-body {
  overflow: hidden;
  outline: 2px solid #b8862b;
  outline-offset: -5px;
}

/* The star: pops on, holds and is gone, in a different spot each time. */
:is([data-sign="sign-brass"], [data-sign="sign-straight"]) .seat-body::before {
  content: "";
  position: absolute;
  z-index: 3;
  width: 0.85rem;
  height: 0.85rem;
  background: var(--sign-star) center / 100% 100% no-repeat;
  image-rendering: pixelated;
  pointer-events: none;
  animation:
    royal-twinkle 4s steps(1) infinite,
    royal-wander 12s steps(1) infinite;
}

@keyframes royal-twinkle {
  0%, 80%, 100% { opacity: 0; scale: 0.5; }
  83% { opacity: 1; scale: 0.6; }
  86%, 92% { opacity: 1; scale: 1; }
  95% { opacity: 1; scale: 0.6; }
}

@keyframes royal-wander {
  0% { top: 0.15rem; right: 1.1rem; }
  33.333% { top: calc(100% - 1rem); right: 32%; }
  66.666% { top: 0.2rem; right: 55%; }
}

/* The name and stack stay above the star. */
:is([data-sign="sign-brass"], [data-sign="sign-straight"]) .seat-body > * {
  position: relative;
  z-index: 4;
}

/* Dead or Alive: plain mustard planks with iron nails and a dark rim, the
   stack and "(you)" in paper white so they read on it. */
[data-sign="sign-bounty"] {
  --sign-grain: var(--px-sign-grain-bounty);
  --sign-dim: linear-gradient(rgba(46, 30, 4, 0.38), rgba(46, 30, 4, 0.38));
  --plank-a: #b08a26;
  --plank-b: #a58022;
  --plank-top: #e2c460;
  --plank-seam: #4e380c;
  --plank-seam-hi: #cfae48;
  --plank-bottom: #6e5012;
  --sign-style-rim: #3e2a06;
}

[data-sign="sign-bounty"] :is(.stack, .who-you) {
  color: #fff4d6;
}

/* Collector: off-white planks, like a fresh deck's card stock, with dark ink
   for the name and stack so they read on it. */
[data-sign="sign-collector"] {
  --sign-grain: var(--px-sign-grain-collector);
  --sign-nail: var(--px-sign-nail-silver);
  --plank-a: #ece6d6;
  --plank-b: #e4ddcb;
  --plank-top: #fdfaf2;
  --plank-seam: #8a8170;
  --plank-seam-hi: #f6f1e4;
  --plank-bottom: #b8ae98;
  --sign-style-rim: #6e6656;
}

[data-sign="sign-collector"] .seat-body :is(.who, .stack, .who-you),
[data-sign="sign-collector"] .achievement-sign {
  color: #2e2418;
  text-shadow: 1px 1px 0 #fffdf6;
}

/* The Haunted: pumpkin-orange planks with iron nails and a dark rim, the
   stack and "(you)" in paper white so they read on it. */
[data-sign="sign-lantern"] {
  --sign-grain: var(--px-sign-grain-lantern);
  --sign-dim: linear-gradient(rgba(58, 24, 2, 0.38), rgba(58, 24, 2, 0.38));
  --plank-a: #d9701a;
  --plank-b: #cc6616;
  --plank-top: #ffa94a;
  --plank-seam: #5c2806;
  --plank-seam-hi: #f08f30;
  --plank-bottom: #8a400c;
  --sign-style-rim: #4a1f04;
}

[data-sign="sign-lantern"] :is(.stack, .who-you) {
  color: #fff4d6;
}

@media (prefers-reduced-motion: reduce) {
  :is([data-sign="sign-brass"], [data-sign="sign-straight"]) .seat-body::before {
    animation: none;
  }
}

/* ---------- Achievements ---------- */

/* The achievements and card packs buttons show in the lobby and at the
   table, so looks can be changed mid-game. */
body:is([data-screen="lobby"], [data-screen="table"]) .history-toggle.achievements-toggle:not([hidden]) {
  display: inline-flex;
}

/* The hand rankings, achievements and card packs windows: a board of dark
   planks in a heavy wooden frame, like the scheduled tournaments' notice
   board. */
.dialog.achievements-dialog {
  width: min(34rem, calc(100% - 1.5rem));
}

.dialog.guide-dialog {
  padding: 1.4rem 1.4rem 0.6rem;
  background:
    var(--px-sign-grain) 0 0 / 144px 30px repeat,
    repeating-linear-gradient(
      180deg,
      #74502f 0 2px,
      #5c3b22 2px 45px,
      #24160c 45px 48px
    );
  image-rendering: pixelated;
  box-shadow:
    0 0 0 3px var(--outline),
    inset 0 0 0 3px #7a5236,
    inset 0 0 0 9px #2e1d12,
    inset 0 0 0 12px var(--outline),
    10px 10px 0 rgba(0, 0, 0, 0.5);
}

/* Headings and notes on the planks get a hard shadow to stand out. */
.guide-dialog .guide-scroll h4,
.guide-dialog .guide-tips,
.guide-dialog .pack-note {
  text-shadow: 2px 2px 0 var(--outline);
}

.achievement-list {
  display: grid;
  gap: 0.55rem;
}

.achievement {
  display: grid;
  grid-template-columns: 5.6rem minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.8rem;
  padding: 0.55rem 0.7rem;
  background: #1f150f;
  box-shadow: 0 0 0 2px var(--outline), inset 0 0 0 2px rgba(236, 224, 194, 0.06);
}

.achievement.in-use {
  box-shadow: 0 0 0 2px var(--outline), inset 0 0 0 2px var(--gold-deep);
}

.achievement-text p {
  margin: 0;
}

.achievement-name {
  font-weight: 700;
}

.achievement-about {
  color: var(--muted, #b9a988);
  font-size: 0.85rem;
}

.achievement-action {
  min-width: 6.5rem;
  white-space: nowrap;
}

/* Locked: the preview greyed out, and a bar of hands played so far. */
.achievement.locked .achievement-preview {
  opacity: 0.45;
  filter: grayscale(0.8);
}

.achievement-progress {
  display: grid;
  gap: 0.25rem;
  min-width: 6.5rem;
  color: var(--muted, #b9a988);
  font-size: 0.75rem;
  text-align: center;
  white-space: nowrap;
}

.achievement-bar {
  height: 0.5rem;
  background: linear-gradient(90deg, var(--gold) calc(min(var(--done), 1) * 100%), #120b07 0);
  box-shadow: 0 0 0 2px var(--outline);
}

.achievement-preview {
  display: flex;
  justify-content: center;
}

.achievement-preview-back .card {
  --cw: 2.1rem;
}

.achievement-preview-back .card:first-child {
  margin-right: -0.7rem;
  rotate: -8deg;
}

.achievement-preview-back .card:last-child {
  rotate: 8deg;
}

.achievement-sign {
  width: 100%;
  padding: 0.55rem 1.1rem 0.55rem 0.4rem;
  overflow: hidden;
  font-size: 0.75rem;
  font-weight: 700;
  text-align: left;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-shadow: 2px 2px 0 var(--outline);
}

@media (max-width: 480px) {
  .achievement {
    grid-template-columns: 4.6rem minmax(0, 1fr);
  }

  .achievement-action,
  .achievement-progress {
    grid-column: 1 / -1;
  }
}

/* ---------- Card packs ---------- */

/* The card packs button while today's free pack is unclaimed: a warm gold
   glow that breathes, and the card icon bobbing. */
.history-toggle.pack-ready {
  color: var(--gold);
  animation: pack-glow 1.6s ease-in-out infinite;
}

.history-toggle.pack-ready .px-icon {
  animation: pack-bob 1.6s steps(4) infinite;
}

@keyframes pack-glow {
  0%, 100% {
    box-shadow: inset 0 0 0 3px var(--outline), inset 0 -4px 0 var(--wood-lo), inset 0 4px 0 var(--wood-hi), 0 0 0 2px var(--gold), 0 0 6px 1px rgba(217, 176, 92, 0.35);
  }
  50% {
    box-shadow: inset 0 0 0 3px var(--outline), inset 0 -4px 0 var(--wood-lo), inset 0 4px 0 var(--wood-hi), 0 0 0 2px var(--gold), 0 0 16px 5px rgba(217, 176, 92, 0.65);
  }
}

@keyframes pack-bob {
  0%, 100% { translate: 0 0; rotate: 0deg; }
  25% { translate: 0 -2px; rotate: -8deg; }
  75% { translate: 0 -2px; rotate: 8deg; }
}

@media (prefers-reduced-motion: reduce) {
  .history-toggle.pack-ready,
  .history-toggle.pack-ready .px-icon {
    animation: none;
  }

  .history-toggle.pack-ready {
    box-shadow: inset 0 0 0 3px var(--outline), inset 0 -4px 0 var(--wood-lo), inset 0 4px 0 var(--wood-hi), 0 0 0 2px var(--gold), 0 0 10px 3px rgba(217, 176, 92, 0.5);
  }
}

/* Face styles (packs.ts), on a player's hole cards and in the packs window.
   Each recolours the card: its paper, the shade along two edges, the ink of
   the black and red suits, and the outline. Gilded, the rare one, is a black
   card printed in gold. */
.card.face-sepia {
  --paper: #d8c094;
  --paper-shade: #b0925e;
  --card-ink: #4a2c16;
  --card-red: #8e3a18;
}

/* Azure: pale sky-blue paper with deep navy ink, kin to Sepia and Jade. */
.card.face-azure {
  --paper: #cfe0ef;
  --paper-shade: #93b0cc;
  --card-ink: #13304f;
  --card-red: #a63a3a;
}

.card.face-jade {
  --paper: #cfe2cf;
  --paper-shade: #98bc9c;
  --card-ink: #133824;
  --card-red: #a63a26;
}

/* Metal, from every pack: brushed grey steel, its fine grain running
   across, in hard bands of light and shade with a bright streak of shine
   slanting over it. */
.card.face-metal {
  --paper: #9a9da1;
  --paper-shade: #55585c;
  --outline: #2a2c2e;
  --card-ink: #161819;
  --card-red: #7a1c18;
  background:
    linear-gradient(125deg, transparent 0 26%, rgb(255 255 255 / 0.75) 26% 31%, rgb(255 255 255 / 0.35) 31% 37%, transparent 37% 44%, rgb(255 255 255 / 0.45) 44% 47%, transparent 47%),
    repeating-linear-gradient(0deg, rgb(255 255 255 / 0.16) 0 1px, rgb(0 0 0 / 0.12) 1px 2px),
    linear-gradient(#d8dadc 0 14%, #a6a9ad 14% 40%, #7c7f83 40% 62%, #5e6165 62% 78%, #b4b7ba 78% 90%, #8a8d91 90%);
}

/* Glass, from every pack: see-through, faintly tinted and frosted, with
   streaks of glare, its ink pale so it reads on whatever shows through. */
.card.face-glass {
  --paper: rgb(214 236 255 / 0.18);
  --paper-shade: transparent;
  --outline: transparent;
  --card-ink: #f2faff;
  --card-red: #ff9aa8;
  background:
    linear-gradient(130deg, rgb(255 255 255 / 0.45) 0 10%, transparent 10% 20%, rgb(255 255 255 / 0.22) 20% 24%, transparent 24% 70%, rgb(255 255 255 / 0.15) 70% 74%, transparent 74%),
    var(--paper);
  backdrop-filter: blur(1.5px);
}

.card.face-glass .corner,
.card.face-glass > .suit {
  filter: drop-shadow(1px 1px 0 rgb(10 30 50 / 0.85));
}

/* Another tinted paper, from the Standard and Jumbo Packs. */
.card.face-rose {
  --paper: #f0d0d4;
  --paper-shade: #c89aa2;
  --card-ink: #4a1a26;
  --card-red: #a8243a;
}

/* Spooky, from the Halloween Event Pack: old orange-tinted paper, faded
   brown-black ink, dust, scuffs and a stain. */
.card.face-spooky {
  --paper: #dc9248;
  --paper-shade: #8e5222;
  --card-ink: #2e1c10;
  --card-red: #7c2a12;
  --outline: #3a2412;
  /* The light orange outline round the rank. */
  --rank-edge: #ffb062;
  background: var(--px-card-dust) 0 0 / calc(var(--cw) * 0.95) calc(var(--cw) * 0.95) repeat, var(--paper);
  filter: saturate(0.85) contrast(0.95);
  /* Grimy, darkened edges from years of handling. */
  box-shadow:
    inset 0 0 0 2px var(--outline),
    inset 0 0 calc(var(--cw) * 0.22) rgb(70 36 12 / 0.55),
    inset -4px -4px 0 var(--paper-shade),
    3px 3px 0 rgba(0, 0, 0, 0.45);
}

/* The turned corner is only shown by court-card layouts. */
.card .corner.flipped {
  display: none;
}

/* Spooky's jack, queen and king are laid out like a real deck's court
   cards: a framed, double-headed portrait (a pumpkin-headed knave, a witch
   queen, a crowned vampire) between the rank and suit in the top left and
   again, turned, in the bottom right. The ace has a skull on crossed bones
   in the same frame. Numbered cards keep their suits. Traditional's jack,
   queen and king are laid out the same way. */
.card.face-spooky:not([data-group="num"]) > .suit,
.card.face-kemet:not([data-group="num"]) > .suit,
.card.face-traditional:is([data-group="J"], [data-group="Q"], [data-group="K"]) > .suit {
  position: absolute;
  left: 21%;
  top: 14.5%;
  width: 58%;
  height: 71%;
  margin: 0;
  background: center / contain no-repeat;
  image-rendering: pixelated;
}

.card.face-spooky:not([data-group="num"]) > .suit > *,
.card.face-kemet:not([data-group="num"]) > .suit > *,
.card.face-traditional:is([data-group="J"], [data-group="Q"], [data-group="K"]) > .suit > * {
  display: none;
}

/* Every Spooky card's rank ("10", "J", "A") is outlined in light orange. */
.card.face-spooky .corner .rank {
  text-shadow:
    1px 0 0 var(--rank-edge),
    -1px 0 0 var(--rank-edge),
    0 1px 0 var(--rank-edge),
    0 -1px 0 var(--rank-edge),
    1px 1px 0 var(--rank-edge),
    -1px -1px 0 var(--rank-edge),
    1px -1px 0 var(--rank-edge),
    -1px 1px 0 var(--rank-edge);
}

/* So is the suit under it on the jack, queen, king and ace (the numbered
   cards' suits stay plain). */
.card.face-spooky:not([data-group="num"]) .corner .mini {
  filter:
    drop-shadow(1px 0 0 var(--rank-edge))
    drop-shadow(-1px 0 0 var(--rank-edge))
    drop-shadow(0 1px 0 var(--rank-edge))
    drop-shadow(0 -1px 0 var(--rank-edge));
}

/* The corners keep the usual size, over the portrait's edge if need be. */
.card.face-spooky:not([data-group="num"]) .corner,
.card.face-kemet:not([data-group="num"]) .corner,
.card.face-traditional:is([data-group="J"], [data-group="Q"], [data-group="K"]) .corner {
  z-index: 1;
}

.card.face-spooky:not([data-group="num"]) .corner.flipped,
.card.face-kemet:not([data-group="num"]) .corner.flipped,
.card.face-traditional:is([data-group="J"], [data-group="Q"], [data-group="K"]) .corner.flipped {
  display: grid;
  top: auto;
  left: auto;
  right: 9%;
  bottom: 5%;
  rotate: 180deg;
}

.card.face-spooky[data-group="J"] > .suit { background-image: var(--px-spooky-jack); }
.card.face-spooky[data-group="Q"] > .suit { background-image: var(--px-spooky-queen); }
.card.face-spooky[data-group="K"] > .suit { background-image: var(--px-spooky-king); }
.card.face-spooky[data-group="A"] > .suit { background-image: var(--px-spooky-ace); }

/* Every Kemet card's rank ("2" to "10", "J", "Q", "K", "A") and the suit
   under it are outlined in white. */
.card.face-kemet .corner .mini {
  filter:
    drop-shadow(1px 0 0 #fff)
    drop-shadow(-1px 0 0 #fff)
    drop-shadow(0 1px 0 #fff)
    drop-shadow(0 -1px 0 #fff);
}

.card.face-kemet .corner .rank {
  text-shadow:
    1px 0 0 #fff,
    -1px 0 0 #fff,
    0 1px 0 #fff,
    0 -1px 0 #fff,
    1px 1px 0 #fff,
    -1px -1px 0 #fff,
    1px -1px 0 #fff,
    -1px 1px 0 #fff;
}

.card.face-kemet[data-group="J"] > .suit { background-image: var(--px-kemet-jack); }
.card.face-kemet[data-group="Q"] > .suit { background-image: var(--px-kemet-queen); }
.card.face-kemet[data-group="K"] > .suit { background-image: var(--px-kemet-king); }
.card.face-kemet[data-group="A"] > .suit { background-image: var(--px-kemet-ace); }

/* Kemet, from the Pharaoh Pack (kemet-art.ts): papyrus with its fibres
   showing, lapis ink and carnelian red, inside a gold and lapis border. The
   jack, queen, king and ace (Anubis, Isis, the Pharaoh and the Eye of Horus) are
   framed double-headed portraits at dusk over the pyramids, laid out like
   Spooky's. */
.card.face-kemet {
  --paper: #ecd9a4;
  --paper-shade: #bfa26a;
  --card-ink: #1f3f8a;
  --card-red: #b8321e;
  --outline: #1f3f8a;
  background: var(--px-kemet-papyrus) 0 0 / calc(var(--cw) * 0.95) calc(var(--cw) * 0.95) repeat, var(--paper);
  box-shadow:
    inset 0 0 0 2px var(--outline),
    inset 0 0 0 4px #e6bd62,
    inset -4px -4px 0 var(--paper-shade),
    3px 3px 0 rgba(0, 0, 0, 0.45);
}

.card.face-traditional[data-group="J"] > .suit { background-image: var(--px-traditional-jack); }
.card.face-traditional[data-group="Q"] > .suit { background-image: var(--px-traditional-queen); }
.card.face-traditional[data-group="K"] > .suit { background-image: var(--px-traditional-king); }

/* Traditional, from the Standard and Jumbo Packs: a classic deck's crisp
   white card, black and red ink, and its ace the plain big suit. */
.card.face-traditional {
  --paper: #faf8f2;
  --paper-shade: #cfc8b8;
  --card-ink: #141414;
  --card-red: #c8202a;
}

/* Pips: only the styles that lay numbered cards out like a real deck's show
   them. */
.card .card-pips {
  display: none;
}

/* Traditional's 2 to 10 and ace, after a vintage ten: old off-white card,
   a slim index in the top left and again turned in the bottom right, and
   inside a thin blue frame the pips in the classic layout (the lower ones
   turned), or the ace's one big suit. */
.card.face-traditional:is([data-group="num"], [data-group="A"]) {
  --paper: #ebe5d4;
  --paper-shade: #c8bea6;
  background:
    radial-gradient(circle at 78% 18%, rgb(150 130 90 / 0.12), transparent 30%),
    radial-gradient(circle at 22% 82%, rgb(150 130 90 / 0.1), transparent 34%),
    var(--paper);
}

.card.face-traditional[data-group="num"] > .suit {
  display: none;
}

.card.face-traditional[data-group="A"] .corner {
  top: 4%;
  left: 5%;
}

.card.face-traditional:is([data-group="num"], [data-group="A"]) .corner.flipped {
  display: grid;
  top: auto;
  left: auto;
  right: 5%;
  bottom: 4%;
  rotate: 180deg;
}

/* The 2 to 10 keep the usual corner (number and suit as big as any other
   style's, so they read in a hand), mirrored at the usual place; their pips
   narrow to fit between the two. */
.card.face-traditional[data-group="num"] .corner.flipped {
  right: 9%;
  bottom: 5%;
}

.card.face-traditional[data-group="A"] .corner .rank {
  font-size: calc(var(--cw) * 0.26);
}

.card.face-traditional[data-group="A"] .corner .mini {
  font-size: calc(var(--cw) * 0.18);
}

.card.face-traditional:is([data-group="num"], [data-group="A"])::after {
  content: "";
  position: absolute;
  inset: 17% 25%;
  box-shadow: 0 0 0 1px #2a4ab8;
  pointer-events: none;
}

/* No frame round the 2 to 10's pips. */
.card.face-traditional[data-group="num"]::after {
  content: none;
}

.card.face-traditional[data-group="num"] .card-pips {
  display: block;
  position: absolute;
  inset: 28% 40%;
}

.card.face-traditional[data-group="num"] .card-pip {
  position: absolute;
  translate: -50% -50%;
  font-size: calc(var(--cw) * 0.075);
  line-height: 0;
}

.card.face-traditional[data-group="num"] .card-pip.turned {
  rotate: 180deg;
}

/* The ace's one big suit, in the middle of the frame. */
.card.face-traditional[data-group="A"] > .suit {
  position: absolute;
  left: 50%;
  top: 50%;
  margin: 0;
  translate: -50% -50%;
  font-size: calc(var(--cw) * 0.36);
}

/* As on a real deck, the Ace of Spades is the one ace that's decorated: a
   big ornate spade full of flourishes. */
.card.face-traditional[data-group="A"][data-card-suit="s"] > .suit {
  width: 44%;
  height: 52%;
  background: var(--px-traditional-ace-spades) center / contain no-repeat;
  image-rendering: pixelated;
}

.card.face-traditional[data-group="A"][data-card-suit="s"] > .suit > * {
  display: none;
}


/* Dark (stored as gilded, its first name): near-black card, gold ink. */
.card.face-gilded {
  --paper: #1d1712;
  --paper-shade: #0c0906;
  --card-ink: #e8c066;
  --card-red: #f08060;
  --outline: #0a0705;
}

.packs-opener {
  display: grid;
  justify-items: center;
  gap: 0.6rem;
  padding: 0.8rem 0 1rem;
}

/* The packs side by side, sharing the window's width: the Standard Pack of
   the day, the Jumbo Pack of the week, the Pharaoh Pack of the day and, during
   its event, the Event Pack. */
.pack-offers {
  display: flex;
  justify-content: center;
  align-items: flex-end;
  gap: 1rem;
  width: 100%;
}

/* A pack, with its buttons underneath: OPEN, or the time left, and Buy. */
.pack-offer {
  display: flex;
  flex: 0 1 132px;
  flex-direction: column;
  align-items: center;
  gap: 0.9rem;
  min-width: 0;
  padding: 0.4rem 0;
}

.pack-take {
  width: 100%;
  min-width: 0;
  padding-inline: 0.3rem;
  white-space: nowrap;
}

/* The Standard Pack: a pixel-art foil booster (the booster sprite, 32×56
   sprite pixels) with the saloon's name, a fan of cards and
   the pack's name over it. Twinkles and a stepped gleam cross the foil.
   While today's pack is waiting it bobs and a hard gold outline pulses
   round it; hovering lifts it. */
.booster {
  position: relative;
  display: block;
  width: 6rem;
  height: 10.5rem;
  border: 0;
  padding: 0;
  background: var(--px-booster) 0 0 / 100% 100% no-repeat;
  image-rendering: pixelated;
  color: var(--ink);
  cursor: default;
  filter: drop-shadow(0.1875rem 0.1875rem 0 rgb(0 0 0 / 0.55));
  --logo: #e6bd62;
  --ribbon: #e6bd62;
  --ribbon-ink: #3a1a0c;
  --stamp: #b8452f;
  --stamp-ink: #fff2cf;
}

/* The Jumbo Pack's midnight blue with silver seals, and the Halloween Event
   Pack's night purple with orange. All packs are the same size. */
.booster.jumbo {
  background-image: var(--px-booster-jumbo);
  --stamp: #e6bd62;
  --stamp-ink: #1a1408;
}

/* The Event Pack's title in a dripping horror font, glowing. */
.booster.event .booster-logo {
  font-family: "Creepster", var(--display);
  font-size: 1.45rem;
  letter-spacing: 0.04em;
  text-transform: none;
  text-shadow:
    0.125rem 0 0 var(--outline),
    -0.125rem 0 0 var(--outline),
    0 0.125rem 0 var(--outline),
    0 -0.125rem 0 var(--outline),
    0.125rem 0.125rem 0 var(--outline),
    -0.125rem -0.125rem 0 var(--outline),
    0 0 0.625rem rgb(240 138 42 / 0.7);
}

.booster.event {
  background-image: var(--px-booster-event);
  --logo: #f08a2a;
  --ribbon: #f08a2a;
  --ribbon-ink: #1a0a04;
  --stamp: #f08a2a;
  --stamp-ink: #1a0a04;
}

/* The Pharaoh Pack: obsidian black foil with gold seals, like a
   sarcophagus, a winged sun spread over its fan of Kemet cards, gold
   ribbons and a turquoise stamp, and its title in gold, brushed as if on
   papyrus, glowing. */
.booster.pharaoh {
  background-image: var(--px-booster-pharaoh);
  --logo: #f2c84a;
  --ribbon: #f2c84a;
  --ribbon-ink: #16141c;
  --stamp: #2ac8b8;
  --stamp-ink: #06060a;
}

.booster.pharaoh .booster-logo {
  font-family: "Macondo", var(--title);
  font-size: 1.02rem;
  text-transform: uppercase;
  text-shadow:
    0.125rem 0 0 var(--outline),
    -0.125rem 0 0 var(--outline),
    0 0.125rem 0 var(--outline),
    0 -0.125rem 0 var(--outline),
    0.125rem 0.125rem 0 var(--outline),
    -0.125rem -0.125rem 0 var(--outline),
    0 0 0.625rem rgb(242 200 74 / 0.75);
}

/* The winged sun over the Pharaoh Pack's fan, spanning the front; the fan
   sits lower to leave it room. */
.booster.pharaoh .booster-fan {
  clip-path: inset(-5rem 0.125rem 0);
}

.booster.pharaoh .booster-fan .card {
  top: 1.15rem;
}

.booster-winged-sun {
  position: absolute;
  left: 0.125rem;
  right: 0.125rem;
  top: -0.1rem;
  height: 1.35rem;
  background: var(--px-winged-sun) center / 100% 100% no-repeat;
  image-rendering: pixelated;
}

/* Opened for now: still in full colour, just a little dimmer and still. */
.booster:is(:disabled, .spent) {
  opacity: 1;
  cursor: default;
  filter: drop-shadow(0.1875rem 0.1875rem 0 rgb(0 0 0 / 0.55)) brightness(0.85);
}

.booster.ready {
  cursor: pointer;
}

.booster:enabled {
  cursor: pointer;
}

.booster:enabled:hover {
  translate: 0 -0.375rem;
}

/* The front, between the seals and inside the pinstripe. */
.booster-body {
  position: absolute;
  z-index: 1;
  inset: 1.6875rem 0.4375rem 1.6875rem;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto 1fr auto;
  justify-items: center;
}

/* "Hold 'Em" on a little ribbon with notched ends. */
.booster-ribbon {
  position: relative;
  z-index: 1;
  padding: 0 0.5625rem 0.0625rem;
  background: var(--ribbon);
  color: var(--ribbon-ink);
  font-family: var(--title);
  font-size: 0.85rem;
  line-height: 1;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  clip-path: polygon(0 0, 100% 0, calc(100% - 0.3125rem) 50%, 100% 100%, 0 100%, 0.3125rem 50%);
}

/* The saloon's name, big, in a Wild West wanted-poster face with a thick
   dark outline and a drop. Every pack's is as tall, so the pictures under
   them line up. */
.booster-logo {
  display: flex;
  align-items: center;
  height: 1.45rem;
  margin-top: 0.125rem;
  color: var(--logo);
  font-family: "Rye", var(--display);
  font-size: 1.08rem;
  line-height: 1;
  text-transform: uppercase;
  text-shadow:
    0.125rem 0 0 var(--outline),
    -0.125rem 0 0 var(--outline),
    0 0.125rem 0 var(--outline),
    0 -0.125rem 0 var(--outline),
    0.125rem 0.125rem 0 var(--outline),
    -0.125rem -0.125rem 0 var(--outline),
    0.125rem -0.125rem 0 var(--outline),
    -0.125rem 0.125rem 0 var(--outline),
    0.1875rem 0.25rem 0 rgb(0 0 0 / 0.5);
}

/* The pack's picture: a fan of cards (real card faces) over a poker chip,
   or for the Event Pack the Spooky cards with bats behind. */
.booster-art {
  position: relative;
  align-self: stretch;
  width: 100%;
}

/* The fan tucks in behind the pinstripe down each side: it's cut off at
   the stripe's inner edge (a sprite pixel inside the front). */
.booster-fan {
  position: absolute;
  inset: 0;
  clip-path: inset(-5rem 0.125rem);
}

.booster-fan .card {
  --cw: 2.5rem;
  position: absolute;
  left: calc(50% - 1.25rem);
  top: 0.15rem;
  transform-origin: 50% 125%;
  rotate: calc((var(--i) - (var(--n) - 1) / 2) * 11deg);
  filter: drop-shadow(0.0625rem 0.0625rem 0 rgb(0 0 0 / 0.5));
}

.booster-chip {
  position: absolute;
  left: calc(50% - 1.25rem);
  top: 0;
  width: 2.5rem;
  height: 2.5rem;
  background: var(--px-chip-big) center / contain no-repeat;
  image-rendering: pixelated;
}

.booster-bat {
  position: absolute;
  left: 0.125rem;
  top: 0.125rem;
  width: 1.375rem;
  height: 0.75rem;
  background: var(--px-bat) 0 0 / 100% 200% no-repeat;
  image-rendering: pixelated;
}

.booster-bat.second {
  left: auto;
  right: 0;
  top: 0.875rem;
  scale: -0.8 0.8;
  background-position: 0 100%;
}

/* The pack's name on a gold ribbon with notched tails. */
.booster-name {
  position: relative;
  z-index: 1;
  padding: 0.0625rem 0.5rem 0.125rem;
  background: var(--ribbon);
  color: var(--ribbon-ink);
  font-family: var(--title);
  font-size: 0.76rem;
  line-height: 1;
  white-space: nowrap;
  clip-path: polygon(0 0, 100% 0, calc(100% - 0.375rem) 50%, 100% 100%, 0 100%, 0.375rem 50%);
  filter: drop-shadow(0 0.125rem 0 rgb(0 0 0 / 0.45));
}

/* The card count on a round stamp (a stepped pixel circle) printed on the
   wrapper in its lower corner: flat, over the cards but under the foil's
   gleam, edged in a thin dark line; the Jumbo Pack's a gold starburst. */
.booster-stamp {
  position: absolute;
  z-index: 2;
  right: 0.625rem;
  bottom: 2.75rem;
  width: 1.875rem;
  height: 1.875rem;
  filter:
    drop-shadow(0.0625rem 0 0 var(--outline))
    drop-shadow(-0.0625rem 0 0 var(--outline))
    drop-shadow(0 0.0625rem 0 var(--outline))
    drop-shadow(0 -0.0625rem 0 var(--outline));
}

.booster-stamp-face {
  display: grid;
  place-items: center;
  align-content: center;
  width: 100%;
  height: 100%;
  background: var(--stamp);
  color: var(--stamp-ink);
  line-height: 0.85;
  clip-path: polygon(30% 0, 70% 0, 70% 6%, 85% 6%, 85% 15%, 94% 15%, 94% 30%, 100% 30%, 100% 70%, 94% 70%, 94% 85%, 85% 85%, 85% 94%, 70% 94%, 70% 100%, 30% 100%, 30% 94%, 15% 94%, 15% 85%, 6% 85%, 6% 70%, 0 70%, 0 30%, 6% 30%, 6% 15%, 15% 15%, 15% 6%, 30% 6%);
}

.booster.jumbo .booster-stamp {
  right: 0.5625rem;
  bottom: 2.9375rem;
  width: 2.375rem;
  height: 2.375rem;
}

.booster.jumbo .booster-stamp-face {
  clip-path: polygon(50% 0, 61% 18%, 82% 10%, 80% 32%, 100% 40%, 84% 55%, 96% 75%, 74% 76%, 68% 98%, 50% 84%, 32% 98%, 26% 76%, 4% 75%, 16% 55%, 0 40%, 20% 32%, 18% 10%, 39% 18%);
}

.booster-stamp-count {
  font-family: var(--display);
  font-size: 0.9rem;
}

.booster-stamp-word {
  font-size: 0.4rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

/* A gleam crossing the foil in hard steps: a wide band and a thin one. */
.booster-shine {
  position: absolute;
  z-index: 3;
  inset: 1.3125rem 0.375rem;
  overflow: hidden;
  pointer-events: none;
}

.booster-shine::before {
  content: "";
  position: absolute;
  top: -30%;
  bottom: -30%;
  left: -70%;
  width: 60%;
  background: linear-gradient(
    90deg,
    transparent 0 25%,
    rgb(255 255 255 / 0.28) 25% 48%,
    transparent 48% 60%,
    rgb(255 255 255 / 0.18) 60% 68%,
    transparent 68%
  );
  rotate: 25deg;
  animation: booster-shine 3.6s steps(14) infinite;
}

@keyframes booster-shine {
  0%, 50% { translate: 0 0; }
  100% { translate: 16.25rem 0; }
}

/* Twinkles on the foil, in turn. */
.booster-sparkle {
  position: absolute;
  z-index: 3;
  width: 0.9375rem;
  height: 0.9375rem;
  background: var(--px-sparkle) 0 0 / 100% 100% no-repeat;
  image-rendering: pixelated;
  opacity: 0;
  pointer-events: none;
  animation: booster-twinkle 2.4s steps(1) infinite;
}

.booster-sparkle:nth-of-type(4) { left: 0.75rem; top: 4.5rem; }
.booster-sparkle:nth-of-type(5) { right: 0.875rem; top: 1.625rem; animation-delay: -0.8s; }
.booster-sparkle:nth-of-type(6) { right: 1rem; bottom: 3.25rem; animation-delay: -1.6s; }

@keyframes booster-twinkle {
  0%, 70%, 100% { opacity: 0; scale: 0.6; }
  76% { opacity: 1; scale: 0.6; }
  82% { opacity: 1; scale: 1; }
  88% { opacity: 0.8; scale: 0.6; }
}

@media (prefers-reduced-motion: reduce) {
  .booster-shine::before,
  .booster-sparkle {
    animation: none;
  }
}

.pack-note {
  margin: 0;
  color: var(--muted, #b9a988);
  font-size: 0.85rem;
  text-align: center;
}

.pack-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem 0.4rem;
}

/* A pack's five cards in a row; a Jumbo Pack's ten in two rows of five. */
.pack-row .pack-card {
  --cw: 4.2rem;
  width: 5.8rem;
}

.pack-row.wide {
  max-width: 31rem;
  margin: 0 auto;
}

/* A pack card turns over on a click: the back on one side, the face on the
   other, with its name and rarity underneath once it shows. */
.pack-card {
  --cw: 4.6rem;
  display: grid;
  justify-items: center;
  gap: 0.5rem;
  border: 0;
  padding: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  perspective: 40rem;
}

.pack-card.turned {
  cursor: default;
}

.pack-flip {
  position: relative;
  display: block;
  width: var(--cw);
  height: calc(var(--cw) * 1.4);
  transform-style: preserve-3d;
  transition: transform 0.6s cubic-bezier(0.3, 1.3, 0.5, 1);
}

.pack-card:not(.turned):hover .pack-flip {
  transform: translateY(-4px) rotate(-2deg);
}

.pack-card.turned .pack-flip {
  transform: rotateY(180deg);
}

.pack-side {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}

.pack-front {
  transform: rotateY(180deg);
}

.pack-label {
  display: grid;
  gap: 0.15rem;
  min-height: 2.4rem;
  font-size: 0.72rem;
  white-space: nowrap;
  text-align: center;
  opacity: 0;
  transition: opacity 0.3s 0.4s;
}

.pack-card.turned .pack-label {
  opacity: 1;
}

.pack-tier,
.pack-status {
  font-size: 0.62rem;
}

.pack-status {
  color: var(--muted, #b9a988);
}

.pack-status.fresh {
  color: var(--gold);
}

.tier-common { color: #c9bfa8; }
.tier-uncommon { color: #6fd08a; }
.tier-rare { color: #6fb2f0; }
.tier-epic { color: #c78af0; }
.tier-legendary { color: #ffb43c; text-shadow: 0 0 6px rgb(255 160 40 / 0.6); }

/* The rarities in a row, each with its chance per card underneath. */
.pack-odds {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  margin: 0;
  padding: 0.5rem 0.8rem;
  background: #1f150f;
  box-shadow: 0 0 0 2px var(--outline);
  list-style: none;
}

.pack-odds li {
  display: grid;
  justify-items: center;
  gap: 0.1rem;
  font-size: 0.8rem;
}

.pack-odds-chance {
  color: var(--muted, #b9a988);
  font-size: 0.75rem;
}

.packs-collection {
  display: grid;
  gap: 0.8rem;
  margin-top: 0.6rem;
}

.collection-group h4 {
  margin: 0 0 0.35rem;
  font-size: 1rem;
}

/* Every style of a group in even columns, as many to a row as fit. */
.collection-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(3.9rem, 1fr));
  gap: 0.45rem;
}

.collection-card {
  --cw: 3rem;
  display: grid;
  justify-items: center;
  gap: 0.3rem;
  border: 0;
  padding: 0.4rem;
  background: #1f150f;
  color: inherit;
  cursor: pointer;
  box-shadow: 0 0 0 2px var(--outline);
}

/* A card not found yet: face up but faded, its rarity still clear. */
.collection-card.missing .card,
.collection-card.missing .collection-caption {
  opacity: 0.45;
}

.collection-card:hover {
  box-shadow: 0 0 0 2px var(--gold-deep);
}

.collection-card.in-use {
  box-shadow: 0 0 0 2px var(--outline), inset 0 0 0 2px var(--gold);
}

.collection-caption {
  max-width: 100%;
  overflow: hidden;
  font-size: 0.68rem;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.collection-tier {
  margin-top: -0.2rem;
  font-size: 0.65rem;
  white-space: nowrap;
}


@media (prefers-reduced-motion: reduce) {
  .pack-flip {
    transition: none;
  }
}

/* Halloween: the little spider that now and then walks along the table's
   rail (railSpider in main.ts), its legs going as it goes. */
.rail-spider {
  position: absolute;
  left: 0;
  top: 0;
  width: 28px;
  height: 24px;
  background: var(--px-mini-spider) 0 0 / 100% 200% no-repeat;
  image-rendering: pixelated;
  filter: drop-shadow(1px 2px 0 rgb(0 0 0 / 0.45));
  animation: spider-legs 0.16s steps(1) infinite;
}

@keyframes spider-legs {
  50% { background-position: 0 100%; }
}

/* The big view of one card, over the card packs window: the card large,
   tilting towards the pointer with a sheen of light following it, and its
   name, rarity and Use button under it. */
/* What can be found in a pack (packs.ts showContents): a scrolling list
   over the window, by rarity from Common to Legendary, each with its
   chance in a pack and the cards themselves as in the collection. */
.pack-contents {
  position: absolute;
  inset: 0;
  z-index: 9;
  overflow: auto;
  display: grid;
  justify-items: center;
  align-items: start;
  padding: 1rem;
  background: rgb(14 8 5 / 0.9);
}

.pack-contents[hidden] {
  display: none;
}

.pack-contents-panel {
  display: grid;
  gap: 0.8rem;
  width: min(100%, 40rem);
  padding: 1rem 1.1rem 1.2rem;
  border: 3px solid var(--outline);
  background: var(--panel, #2a1a10);
  box-shadow: 0.25rem 0.25rem 0 rgb(0 0 0 / 0.5);
  text-align: left;
}

.pack-contents-title {
  margin: 0;
  color: var(--gold);
  font-family: var(--display);
  font-size: 1.3rem;
  font-weight: 400;
  text-shadow: 2px 2px 0 var(--outline);
}

.pack-contents-sub {
  margin: -0.5rem 0 0;
  opacity: 0.8;
  font-size: 0.8rem;
}

.pack-contents-tier h4 {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin: 0 0 0.4rem;
  padding-bottom: 0.2rem;
  border-bottom: 2px solid rgb(255 255 255 / 0.12);
  font-size: 0.95rem;
}

.pack-contents-chance {
  opacity: 0.8;
  font-size: 0.8rem;
  font-weight: 400;
}

.pack-contents-close {
  justify-self: center;
  min-width: 8rem;
}

.card-viewer {
  position: absolute;
  inset: 0;
  z-index: 10;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 1.2rem;
  padding: 1.5rem;
  background: rgb(14 8 5 / 0.9);
}

.card-viewer[hidden] {
  display: none;
}

/* Over the pack opening, which is only as tall as its cards, the viewer
   covers the whole window, so the big card fits. */
.pack-opening > .card-viewer {
  position: fixed;
  overflow: auto;
}

.viewer-stage {
  padding: 1.5rem;
  perspective: 60rem;
}

.viewer-tilt {
  --tilt-x: 0deg;
  --tilt-y: 0deg;
  position: relative;
  transform: rotateX(var(--tilt-x)) rotateY(var(--tilt-y));
  transform-style: preserve-3d;
  transition: transform 0.12s ease-out;
  filter: drop-shadow(6px 10px 0 rgb(0 0 0 / 0.5));
}

.viewer-tilt .card {
  --cw: min(11rem, 36vw);
}

/* Light catching the card where the pointer is. */
.viewer-glare {
  position: absolute;
  inset: 0;
  border-radius: calc(min(11rem, 36vw) * 0.06);
  background: radial-gradient(
    circle at var(--glare-x, 30%) var(--glare-y, 20%),
    rgb(255 255 255 / 0.32),
    rgb(255 255 255 / 0.08) 35%,
    transparent 60%
  );
  mix-blend-mode: overlay;
  pointer-events: none;
}

/* The players who have the card, just to its right, leaving the card
   centred; under it when the window is too narrow for both. */
.viewer-row {
  position: relative;
  display: grid;
  justify-items: center;
}

.viewer-owners {
  position: absolute;
  top: 50%;
  left: 100%;
  transform: translateY(-50%);
  width: 11rem;
  max-height: min(18rem, 60vh);
  overflow-y: auto;
}

.viewer-owners h4 {
  margin: 0 0 0.4rem;
  font-family: var(--display);
  font-size: 0.95rem;
  color: var(--ink);
}

.viewer-owners ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.35rem;
}

.viewer-owners li {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-width: 0;
  font-size: 0.85rem;
  color: var(--ink);
}

.viewer-owners li span:last-child {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.viewer-owners .avatar-art {
  font-size: 1.6rem;
}

.viewer-owners li.you {
  color: #f2d48a;
}

@media (max-width: 40rem) {
  .viewer-owners {
    position: static;
    transform: none;
    max-height: 30vh;
  }
}

.viewer-info {
  display: grid;
  justify-items: center;
  gap: 0.3rem;
  text-align: center;
}

.viewer-info p {
  margin: 0;
}

.viewer-name {
  font-family: var(--display);
  font-size: 1.3rem;
  color: var(--ink);
}

.viewer-actions {
  display: flex;
  gap: 0.6rem;
  margin-top: 0.5rem;
}

@media (prefers-reduced-motion: reduce) {
  .viewer-tilt {
    transition: none;
  }
}

/* ---------- Opening a pack ---------- */

/* Its own view over the dimmed screen: the pack's name, its cards face down
   to turn over, and Done. */
.pack-opening {
  width: min(44rem, calc(100% - 1rem));
  max-height: calc(100dvh - 1rem);
  overflow: visible auto;
  border: 0;
  padding: 1rem 0.5rem 1.4rem;
  background: none;
  color: var(--ink);
  text-align: center;
}

.pack-opening[open] {
  display: grid;
  justify-items: center;
  gap: 1rem;
}

.pack-opening::backdrop {
  background: rgb(8 5 3 / 0.88);
}

.pack-opening-title {
  margin: 0;
  color: var(--gold);
  font-family: var(--display);
  font-size: 1.5rem;
  font-weight: 400;
  text-shadow: 3px 3px 0 var(--outline);
}

.pack-opening .pack-row .pack-card {
  --cw: clamp(3.6rem, 11vw, 5.2rem);
  width: calc(var(--cw) * 1.35);
}

.pack-opening .pack-row.wide {
  max-width: none;
}

.pack-opening .pack-note {
  text-shadow: 2px 2px 0 var(--outline);
}

/* A pack just opened (packs.ts playIntro), about 1.8s before the cards
   fly: light rays turn behind the pack as it drops in from above and lands
   with a bounce (0-0.55s); it shakes harder and harder as a glow builds
   (0.55-1.45s); a bright line runs across its top (1.4-1.6s), and the top
   rips away in a flash, a ring and a burst of sparks (1.6s) as the window
   jolts and the rest of the pack is torn in two and fades; the cards shoot up out of it and fan down to their places (from
   1.8s), which can't be clicked until they've all landed. Gold for the
   Standard and Jumbo Packs, orange and violet for the Event Pack. */
.pack-intro {
  --fx: #ffd36a;
  --fx-2: #fff2cf;
  position: fixed;
  inset: 0;
  z-index: 3;
  overflow: hidden;
  pointer-events: none;
  animation: pack-intro-jolt 0.35s linear 1.6s;
}

.pack-intro[data-kind="pharaoh"] {
  --fx: #f2c84a;
  --fx-2: #2ac8b8;
}

.pack-intro[data-kind="event"] {
  --fx: #ff8a2a;
  --fx-2: #c78af0;
}

.pack-intro-pack {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 6rem;
  height: 10.5rem;
  margin: -5.25rem 0 0 -3rem;
  scale: 1.6;
  animation:
    pack-intro-in 0.55s cubic-bezier(0.3, 1.4, 0.5, 1) both,
    pack-intro-charge 0.9s linear 0.55s;
}

.pack-intro .booster {
  position: absolute;
  inset: 0;
  filter: drop-shadow(3px 3px 0 rgb(0 0 0 / 0.55));
}

.pack-intro-top {
  clip-path: inset(0 0 89% 0);
  animation: pack-intro-top 0.7s cubic-bezier(0.2, 0.7, 0.4, 1) 1.6s forwards;
}

/* The rest of the pack in two halves along a jagged tear down the middle,
   pulled apart as it opens and fading away. */
.pack-intro-left {
  clip-path: polygon(0 11%, 50% 11%, 46% 22%, 53% 33%, 45% 45%, 54% 57%, 46% 69%, 53% 81%, 47% 92%, 50% 100%, 0 100%);
  animation: pack-intro-left 0.75s cubic-bezier(0.2, 0.6, 0.4, 1) 1.65s forwards;
}

.pack-intro-right {
  clip-path: polygon(50% 11%, 100% 11%, 100% 100%, 50% 100%, 47% 92%, 53% 81%, 46% 69%, 54% 57%, 45% 45%, 53% 33%, 46% 22%);
  animation: pack-intro-right 0.75s cubic-bezier(0.2, 0.6, 0.4, 1) 1.65s forwards;
}

/* The glow round the pack, building as it shakes and flaring as it opens. */
.pack-intro-glow {
  position: absolute;
  inset: -60% -90%;
  background: radial-gradient(closest-side, var(--fx-2), var(--fx) 35%, transparent 70%);
  opacity: 0;
  animation: pack-intro-glow 2.3s ease-in both;
}

/* The bright line run across the pack's top before it rips. */
.pack-intro-rip {
  position: absolute;
  z-index: 3;
  left: 0;
  right: 0;
  top: 10%;
  height: 0.125rem;
  background: #fffbe8;
  box-shadow: 0 0 0.3rem 0.1rem var(--fx), 0 0 0.8rem 0.2rem var(--fx-2);
  transform-origin: left;
  scale: 0 1;
  animation: pack-intro-rip 0.5s ease-in 1.2s forwards;
}

/* Sparks: little squares thrown out from the tear, falling as they fade. */
.pack-intro-bits {
  position: absolute;
  z-index: 3;
  left: 50%;
  top: 10%;
}

.pack-intro-bit {
  position: absolute;
  width: var(--size);
  height: var(--size);
  margin: calc(var(--size) / -2);
  background: var(--fx);
  box-shadow: 0 0 0.25rem var(--fx);
  opacity: 0;
  animation: pack-intro-bit 0.95s cubic-bezier(0.15, 0.7, 0.4, 1) calc(1.6s + var(--wait)) forwards;
}

.pack-intro-bit.alt {
  background: var(--fx-2);
  box-shadow: 0 0 0.25rem var(--fx-2);
}

/* Light rays turning slowly behind everything, fading at the edges. */
.pack-intro-rays {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 60rem;
  height: 60rem;
  margin: -30rem 0 0 -30rem;
  background: repeating-conic-gradient(from 0deg, color-mix(in srgb, var(--fx) 55%, transparent) 0deg 7deg, transparent 7deg 22.5deg);
  mask-image: radial-gradient(closest-side, #000 15%, transparent 75%);
  -webkit-mask-image: radial-gradient(closest-side, #000 15%, transparent 75%);
  opacity: 0;
  animation:
    pack-intro-spin 9s linear infinite,
    pack-intro-rays 2.9s ease-out both;
}

/* The flash as the pack opens, over the whole window. */
.pack-intro-flash {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: radial-gradient(circle, #fffbe8, color-mix(in srgb, var(--fx) 70%, transparent) 35%, transparent 80%);
  opacity: 0;
  animation: pack-intro-flash 0.6s ease-out 1.6s;
}

/* A ring of light spreading out from the tear. */
.pack-intro-ring {
  position: absolute;
  left: 50%;
  top: calc(50% - 4.5rem);
  width: 40rem;
  height: 40rem;
  margin: -20rem 0 0 -20rem;
  background: radial-gradient(closest-side, transparent 82%, var(--fx-2) 86%, var(--fx) 90%, transparent 95%);
  opacity: 0;
  scale: 0.05;
  animation: pack-intro-ring 0.75s ease-out 1.6s;
}

.pack-opening.intro .pack-card {
  pointer-events: none;
}

/* Nor does the view clip the cards as they fly up past its top. */
.pack-opening.intro {
  overflow: visible;
}

/* The cards fly over the pack they come out of. */
.pack-opening.intro .pack-row {
  position: relative;
  z-index: 4;
}

.pack-opening.intro .pack-row .pack-card {
  animation: pack-fly 0.9s var(--fly-delay, 0s) both;
}

.pack-opening.intro .pack-note {
  opacity: 0;
}

@keyframes pack-intro-in {
  0% { opacity: 0; translate: 0 -70vh; scale: 1.1; rotate: -8deg; }
  40% { opacity: 1; }
  100% { opacity: 1; translate: 0 0; scale: 1.6; rotate: 0deg; }
}

/* Shaking harder and harder, swelling a little at the end. */
@keyframes pack-intro-charge {
  0% { rotate: 0deg; translate: 0 0; }
  10% { rotate: -1.5deg; }
  20% { rotate: 1.5deg; }
  30% { rotate: -2.5deg; translate: -1px 0; }
  40% { rotate: 2.5deg; translate: 1px 0; }
  50% { rotate: -4deg; translate: -2px 1px; }
  58% { rotate: 4deg; translate: 2px -1px; }
  66% { rotate: -5deg; translate: -3px 1px; scale: 1.64; }
  73% { rotate: 5deg; translate: 3px -1px; }
  80% { rotate: -6deg; translate: -3px 2px; scale: 1.68; }
  86% { rotate: 6deg; translate: 3px -2px; }
  92% { rotate: -6deg; translate: -4px 1px; scale: 1.72; }
  100% { rotate: 0deg; translate: 0 0; scale: 1.6; }
}

@keyframes pack-intro-glow {
  0%, 20% { opacity: 0; scale: 0.6; }
  68% { opacity: 0.55; scale: 1; }
  72% { opacity: 1; scale: 1.5; }
  100% { opacity: 0; scale: 1.2; }
}

@keyframes pack-intro-rip {
  80% { opacity: 1; scale: 1 1; }
  100% { opacity: 0; scale: 1 3; }
}

@keyframes pack-intro-top {
  0% { translate: 0 0; rotate: 0deg; opacity: 1; }
  100% { translate: 4rem -9rem; rotate: 70deg; opacity: 0; }
}

@keyframes pack-intro-left {
  0% { opacity: 1; translate: 0 0; rotate: 0deg; }
  100% { opacity: 0; translate: -3.5rem 2.5rem; rotate: -24deg; }
}

@keyframes pack-intro-right {
  0% { opacity: 1; translate: 0 0; rotate: 0deg; }
  100% { opacity: 0; translate: 3.5rem 2.5rem; rotate: 24deg; }
}

@keyframes pack-intro-bit {
  0% { opacity: 1; translate: 0 0; rotate: 0deg; }
  70% { opacity: 1; }
  100% { opacity: 0; translate: var(--dx) calc(var(--dy) + 3rem); rotate: var(--spin); }
}

@keyframes pack-intro-spin {
  to { rotate: 360deg; }
}

@keyframes pack-intro-rays {
  0% { opacity: 0; scale: 0.4; }
  18% { opacity: 0.45; scale: 0.8; }
  50% { opacity: 0.55; scale: 0.85; }
  58% { opacity: 1; scale: 1.15; }
  80% { opacity: 0.6; }
  100% { opacity: 0; scale: 1.3; }
}

@keyframes pack-intro-flash {
  0% { opacity: 0; }
  15% { opacity: 0.9; }
  100% { opacity: 0; }
}

@keyframes pack-intro-ring {
  0% { opacity: 1; scale: 0.05; }
  100% { opacity: 0; scale: 1; }
}

@keyframes pack-intro-jolt {
  0%, 100% { translate: 0 0; }
  15% { translate: -6px 4px; }
  30% { translate: 5px -4px; }
  45% { translate: -4px -2px; }
  60% { translate: 3px 3px; }
  80% { translate: -1px -1px; }
}

/* Each card shoots up out of the pack, spinning, to above it, then fans
   down and out to its place. */
@keyframes pack-fly {
  0% {
    opacity: 0;
    translate: var(--fly-x) calc(var(--fly-y) - 3rem);
    scale: 0.5;
    rotate: var(--fly-r);
    animation-timing-function: cubic-bezier(0.2, 0.8, 0.4, 1);
  }
  8% { opacity: 1; }
  38% {
    opacity: 1;
    translate: calc(var(--fly-x) + var(--fly-spread)) calc(var(--fly-y) - 7rem);
    scale: 1.15;
    rotate: calc(var(--fly-r) * 0.3);
    animation-timing-function: cubic-bezier(0.4, 0, 0.3, 1.25);
  }
  100% { opacity: 1; translate: 0 0; scale: 1; rotate: 0deg; }
}

/* The title takes the focus when the view opens, without a ring. */
.pack-opening-title:focus {
  outline: none;
}

/* A Legendary card's gold flash over everything. */
.pack-opening::after {
  content: "";
  position: fixed;
  inset: 0;
  background: radial-gradient(circle, rgb(255 230 160 / 0.9), rgb(255 170 60 / 0.5) 45%, transparent 75%);
  opacity: 0;
  pointer-events: none;
}

.pack-opening.flash::after {
  animation: pack-flash 0.9s steps(6) forwards;
}

@keyframes pack-flash {
  0% { opacity: 0; }
  15% { opacity: 1; }
  100% { opacity: 0; }
}

/* Flip effects, by rarity. The burst sits behind the card. */
.pack-card {
  position: relative;
}

/* Every card and label above every burst, so rays never cover a
   neighbouring card. (The cards themselves make no stacking context.) */
.pack-flip,
.pack-label {
  position: relative;
  z-index: 2;
}

.pack-burst {
  position: absolute;
  left: 50%;
  top: calc(var(--cw) * 0.7);
  width: 0;
  height: 0;
  pointer-events: none;
}

/* Rings: a pixel diamond that grows out and fades. */
.pack-burst::before,
.pack-burst::after {
  content: "";
  position: absolute;
  left: calc(var(--cw) * -0.8);
  top: calc(var(--cw) * -0.8);
  width: calc(var(--cw) * 1.6);
  height: calc(var(--cw) * 1.6);
  opacity: 0;
}

.pack-card.revealed[data-tier="rare"] .pack-burst::before,
.pack-card.revealed[data-tier="epic"] .pack-burst::before,
.pack-card.revealed[data-tier="legendary"] .pack-burst::before {
  border: 4px solid var(--burst);
  rotate: 45deg;
  animation: pack-ring 0.7s steps(7) forwards;
}

@keyframes pack-ring {
  0% { opacity: 1; scale: 0.3; }
  100% { opacity: 0; scale: 1.9; }
}

.pack-card[data-tier="rare"] { --burst: #6fb2f0; }
.pack-card[data-tier="epic"] { --burst: #c78af0; }
.pack-card[data-tier="legendary"] { --burst: #ffb43c; }

/* The turned card keeps a glow of its rarity. (On the face, not the flip:
   a filter there would flatten the turn.) */
.pack-card.revealed[data-tier="rare"] .pack-front,
.pack-card.revealed[data-tier="epic"] .pack-front,
.pack-card.revealed[data-tier="legendary"] .pack-front {
  filter: drop-shadow(0 0 6px var(--burst)) drop-shadow(0 0 2px var(--burst));
}

/* Epic and Legendary: rays turning behind the card. */
.pack-card.revealed[data-tier="epic"] .pack-burst::after,
.pack-card.revealed[data-tier="legendary"] .pack-burst::after {
  left: calc(var(--cw) * -1.3);
  top: calc(var(--cw) * -1.3);
  width: calc(var(--cw) * 2.6);
  height: calc(var(--cw) * 2.6);
  background: repeating-conic-gradient(from 0deg, var(--burst) 0deg 8deg, transparent 8deg 30deg);
  -webkit-mask: radial-gradient(circle, #000 25%, transparent 68%);
  mask: radial-gradient(circle, #000 25%, transparent 68%);
  animation:
    pack-rays-in 0.5s steps(5) forwards,
    pack-rays-spin 6s linear infinite;
}

@keyframes pack-rays-in {
  to { opacity: 0.45; }
}

@keyframes pack-rays-spin {
  to { rotate: 360deg; }
}

.pack-card.revealed[data-tier="legendary"] .pack-burst::after {
  animation:
    pack-rays-in-strong 0.5s steps(5) forwards,
    pack-rays-spin 4s linear infinite;
}

@keyframes pack-rays-in-strong {
  to { opacity: 0.7; }
}

/* Epic pops as it lands; Legendary pops bigger, then floats and pulses. */
.pack-card.revealed[data-tier="epic"] .pack-front {
  animation: pack-pop 0.5s steps(5);
}

.pack-card.revealed[data-tier="legendary"] .pack-front {
  animation:
    pack-pop-big 0.6s steps(6),
    pack-glow 2s ease-in-out 0.6s infinite;
}

.pack-card.revealed[data-tier="legendary"] .pack-flip {
  animation: pack-float 2s ease-in-out 0.6s infinite;
}

@keyframes pack-pop {
  40% { scale: 1.18; }
}

@keyframes pack-pop-big {
  40% { scale: 1.35; }
}

@keyframes pack-float {
  50% { translate: 0 -5px; }
}

@keyframes pack-glow {
  0%, 100% { filter: drop-shadow(0 0 6px #ffb43c) drop-shadow(0 0 2px #ffb43c); }
  50% { filter: drop-shadow(0 0 16px #ffd27a) drop-shadow(0 0 4px #ffb43c); }
}

/* Before a Legendary card turns: it shakes and its glow builds. */
.pack-card.charging .pack-flip {
  animation: pack-charge 0.75s steps(10);
}

.pack-card.charging .pack-back {
  animation: pack-charge-glow 0.75s steps(10) forwards;
}

@keyframes pack-charge {
  20% { translate: -2px 0; }
  40% { translate: 2px -1px; }
  60% { translate: -3px 1px; }
  80% { translate: 3px 0; }
}

@keyframes pack-charge-glow {
  0% { filter: drop-shadow(0 0 0 #ffb43c); }
  50% { filter: drop-shadow(0 0 8px #ffb43c); }
  100% { filter: drop-shadow(0 0 20px #fff2c0) brightness(1.3); }
}

/* Sparks flying out from the card. */
.pack-spark {
  position: absolute;
  left: -3px;
  top: -3px;
  width: 6px;
  height: 6px;
  background: var(--burst);
  box-shadow: 0 0 6px var(--burst);
  animation: pack-spark 1s steps(10) forwards;
}

.pack-card[data-tier="legendary"] .pack-spark:nth-child(even) {
  background: #fff2c0;
}

@keyframes pack-spark {
  0% { translate: 0 0; opacity: 1; }
  100% { translate: var(--dx) var(--dy); opacity: 0; }
}

/* The rarity under a Legendary card glows too. */
.pack-card.revealed[data-tier="legendary"] .pack-tier {
  animation: pack-legend-text 1.4s steps(4) infinite;
}

@keyframes pack-legend-text {
  50% { color: #fff2c0; text-shadow: 0 0 8px #ffb43c; }
}

@media (prefers-reduced-motion: reduce) {
  .pack-card .pack-flip,
  .pack-card .pack-front,
  .pack-burst::before,
  .pack-burst::after,
  .pack-card .pack-tier,
  .pack-opening.flash::after {
    animation: none !important;
  }
}

/* Phones: the packs side by side, smaller, and every style of a rank
   in one row. */
@media (max-width: 520px) {
  .pack-offers {
    gap: 0.5rem;
  }

  .pack-offer {
    flex: 1 1 0;
    min-width: 0;
  }

  .booster {
    zoom: 0.72;
  }

  .pack-take {
    width: 100%;
    min-width: 0;
    padding-inline: 0.3rem;
  }

  .pack-odds {
    padding-inline: 0.4rem;
  }

  .pack-odds li {
    font-size: 0.68rem;
  }

  .collection-cards {
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 0.3rem;
  }

  .collection-card {
    --cw: 2.4rem;
    padding: 0.3rem 0.15rem;
  }

  .collection-caption,
  .collection-tier {
    font-size: 0.56rem;
  }
}

/* Sending some of your weekly balance to the player on the poster: a button
   under their record, opening an amount and Send. */
.profile-gift {
  display: grid;
  justify-items: center;
  margin-top: 0.9rem;
  padding-top: 0.8rem;
  border-top: 3px solid var(--poster-ink);
}

.profile-gift-form {
  display: grid;
  justify-items: center;
  gap: 0.45rem;
  width: 100%;
}

.profile-gift-form[hidden] {
  display: none;
}

.profile-gift-label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--poster-ink);
  font-weight: 700;
}

.profile-gift-amount {
  width: 8rem;
  padding: 0.35rem 0.5rem;
  border: 0;
  background: #fff6e0;
  color: var(--poster-ink);
  font: inherit;
  box-shadow: inset 0 0 0 2px var(--poster-ink);
}

.profile-gift-note {
  margin: 0;
  color: var(--poster-ink-soft);
  font-size: 0.8rem;
  text-align: center;
}

.profile-gift-actions {
  display: flex;
  gap: 0.5rem;
}

/* Read out but not shown. */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Card Dust: the total at the top of Card Packs, the Buy buttons, and what a
   duplicate turned into. The icon is a little heap of it. */
.px-icon[data-icon="dust"] {
  background-image: var(--px-icon-dust);
  image-rendering: pixelated;
}

.packs-dust {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  margin: 0 auto 0 1rem;
  color: var(--ink);
  font-size: 0.95rem;
  text-shadow: 2px 2px 0 var(--outline);
}

.packs-dust strong {
  color: #e8d9ff;
}

.pack-buy {
  width: 100%;
  min-width: 0;
  padding-inline: 0.3rem;
  font-size: 0.8rem;
  white-space: nowrap;
}

.pack-dust {
  color: #e8d9ff;
  font-size: 0.62rem;
}

/* ---------- Card backs and all-in animations from packs ---------- */

/* As pack and collection cards they're the size of every other card: a card
   back is the back itself. */

/* An all-in animation's card: a still of it on the paper (a ghost over the
   glow, two bats) and "All In" underneath. */
.pack-extra-allin {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: inherit;
}

.pack-extra-allin > * {
  position: absolute;
  image-rendering: pixelated;
}

.pack-extra-allin .haunt-glow {
  left: 10%;
  top: 12%;
  width: 80%;
  height: 57%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgb(255 214 120 / 0.85), rgb(255 150 40 / 0.35) 60%, transparent);
}

.pack-extra-allin .haunt-ghost {
  left: 25%;
  top: 18%;
  width: 50%;
  height: calc(var(--cw) * 0.55);
  background: var(--px-ghost) 0 0 / 100% 200% no-repeat;
  animation: ghost-flap 0.5s steps(1) infinite, ghost-bob 2s ease-in-out infinite;
}

.pack-extra-allin .haunt-bat {
  left: 6%;
  top: 8%;
  width: 34%;
  height: calc(var(--cw) * 0.34 * 6 / 11);
  background: var(--px-bat) 0 0 / 100% 200% no-repeat;
  animation: bat-flap 0.3s steps(1) infinite;
}

.pack-extra-allin .haunt-bat.second {
  left: auto;
  right: 6%;
  top: 22%;
  scale: -0.8 0.8;
  animation-delay: -0.15s;
}

.pack-extra-label {
  left: 0;
  right: 0;
  bottom: 9%;
  text-align: center;
  font-family: var(--display);
  font-size: calc(var(--cw) * 0.2);
  color: var(--card-ink);
  text-transform: uppercase;
  text-shadow:
    1px 0 0 var(--rank-edge),
    -1px 0 0 var(--rank-edge),
    0 1px 0 var(--rank-edge),
    0 -1px 0 var(--rank-edge);
}

@keyframes ghost-flap {
  50% { background-position: 0 100%; }
}

@keyframes ghost-bob {
  50% { translate: 0 calc(var(--cw) * -0.05); }
}

/* The Haunted All-In at the table, over the seat (allin-effects.ts): the
   night closing in, a glow, three ghosts rising, bats bursting away. */
.haunt-night {
  position: fixed;
  inset: 0;
  z-index: 60;
  pointer-events: none;
  background: radial-gradient(circle, transparent 35%, rgb(24 8 34 / 0.8));
  animation: haunt-night 2.6s ease-out forwards;
}

.allin-fx {
  position: fixed;
  z-index: 61;
  width: 0;
  height: 0;
  pointer-events: none;
}

.allin-fx > * {
  position: absolute;
  image-rendering: pixelated;
}

.allin-haunted .haunt-glow {
  left: -7rem;
  top: -7rem;
  width: 14rem;
  height: 14rem;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgb(255 190 90 / 0.8), rgb(255 120 20 / 0.35) 55%, transparent);
  animation: haunt-glow 2.2s ease-out forwards;
}

.allin-haunted .haunt-ghost {
  --x: calc((var(--i) - 1) * 4.2rem);
  left: calc(var(--x) - 1.6rem);
  top: -1.6rem;
  width: 3.2rem;
  height: 3.5rem;
  opacity: 0;
  background: var(--px-ghost) 0 0 / 100% 200% no-repeat;
  animation:
    ghost-flap 0.4s steps(1) infinite,
    haunt-rise 2.2s ease-out calc(var(--i) * 0.18s) forwards;
}

.allin-haunted .haunt-bat {
  left: -1.1rem;
  top: -0.6rem;
  width: 2.2rem;
  height: 1.2rem;
  background: var(--px-bat) 0 0 / 100% 200% no-repeat;
  opacity: 0;
  animation:
    bat-flap 0.25s steps(1) infinite,
    haunt-bat 1.7s ease-out forwards;
}

@keyframes haunt-night {
  0% { opacity: 0; }
  15%, 65% { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes haunt-glow {
  0% { opacity: 0; scale: 0.3; }
  20% { opacity: 1; scale: 1.1; }
  70% { opacity: 0.8; scale: 1; }
  100% { opacity: 0; scale: 1.2; }
}

@keyframes haunt-rise {
  0% { opacity: 0; translate: 0 2rem; scale: 0.5; }
  20% { opacity: 0.95; }
  50% { translate: 0.8rem -4rem; scale: 1; }
  75% { opacity: 0.8; translate: -0.8rem -7rem; }
  100% { opacity: 0; translate: 0 -10rem; scale: 1.1; }
}

@keyframes haunt-bat {
  0% { opacity: 0; translate: 0 0; scale: 0.4; }
  10% { opacity: 1; }
  80% { opacity: 1; }
  100% { opacity: 0; translate: var(--dx) var(--dy); scale: 1.3; }
}

/* The Wild West Showdown's card: a low sun and wind streaking across
   it, on Sepia paper, with "All In" underneath. */
.pack-extra-allin .show-sun {
  left: 18%;
  top: 8%;
  width: 64%;
  height: calc(var(--cw) * 0.64);
  border-radius: 50%;
  background: radial-gradient(closest-side, #ffe08a, #f0a83c 55%, rgb(240 168 60 / 0) 100%);
}

.pack-extra-allin .show-gust {
  left: 8%;
  top: 30%;
  width: 60%;
  height: 2px;
  background: linear-gradient(90deg, transparent, rgb(255 248 228 / 0.9) 40%, transparent);
  animation: gust-still 2.4s ease-in-out infinite;
}

.pack-extra-allin .show-gust.second {
  left: 30%;
  top: 42%;
  width: 55%;
  animation-delay: -0.8s;
}

.pack-extra-allin .show-gust.third {
  left: 14%;
  top: 54%;
  width: 45%;
  animation-delay: -1.6s;
}

/* An emote's and a backdrop's card: the player's character (pulling the
   emote's face, with its mark) in a framed portrait. */
.pack-extra-allin .extra-portrait {
  left: 20%;
  top: 14%;
  width: 60%;
  height: calc(var(--cw) * 0.6);
  overflow: hidden;
  font-size: calc(var(--cw) * 0.6);
  line-height: 0;
  box-shadow: 0 0 0 2px var(--card-ink);
}

.pack-extra-allin .extra-mark {
  left: 6%;
  top: 4%;
  width: 34%;
  height: calc(var(--cw) * 0.34);
  animation: ghost-bob 2s ease-in-out infinite;
}

/* A Card Dust card: a heap, or an overflowing pouch. */
.pack-extra-allin .extra-dust {
  left: 12%;
  top: 10%;
  width: 76%;
  height: calc(var(--cw) * 0.76);
  background: center / 100% 100% no-repeat;
  image-rendering: pixelated;
}

.extra-dust[data-amount="25"] { background-image: var(--px-dust-25); }
.extra-dust[data-amount="125"] { background-image: var(--px-dust-125); }

.viewer-where {
  margin: 0;
  color: var(--muted, #b9a988);
  font-size: 0.75rem;
}

.viewer-note {
  margin: 0;
  color: var(--muted, #b9a988);
  font-size: 0.8rem;
}

.pack-extra-label.long {
  bottom: 7%;
  padding: 0 4%;
  font-size: calc(var(--cw) * 0.13);
  line-height: 1.15;
}

@keyframes gust-still {
  0%, 100% { opacity: 0.2; translate: 10% 0; }
  50% { opacity: 1; translate: -10% 0; }
}

/* The Wild West Showdown at the table (allin-effects.ts): the standoff.
   The screen drains to sepia and black bars close in, the light narrows
   onto the seat, wind streaks and dust blow across, and a tumbleweed
   drifts by. */
.show-film {
  position: fixed;
  inset: 0;
  z-index: 60;
  overflow: hidden;
  pointer-events: none;
  background: rgb(120 76 30 / 0.28);
  backdrop-filter: sepia(0.85) saturate(0.7);
  animation: show-film 3.2s ease-out forwards;
}

.show-film::before,
.show-film::after {
  content: "";
  position: absolute;
  z-index: 2;
  left: 0;
  right: 0;
  height: 14vh;
  background: #050302;
  animation: show-bar-top 3.2s ease-in-out forwards;
}

.show-film::before { top: 0; }

.show-film::after {
  bottom: 0;
  animation-name: show-bar-bottom;
}

/* The light closing in: a ring of dark round a clear middle on the seat,
   shrinking, its edge blurred soft. */
.show-spot {
  position: absolute;
  width: 60rem;
  height: 60rem;
  translate: -50% -50%;
  border-radius: 50%;
  box-shadow: 0 0 0 200vmax rgb(16 8 2 / 0.72);
  filter: blur(28px);
  animation: show-spot 3.2s ease-in-out forwards;
}

.show-gust {
  position: absolute;
  right: -20rem;
  height: 2px;
  border-radius: 1px;
  background: linear-gradient(90deg, transparent, rgb(255 246 222 / 0.75) 35%, rgb(255 246 222 / 0.2) 80%, transparent);
  opacity: 0;
  animation: show-gust 1s linear both;
}

.show-speck {
  position: absolute;
  right: -1rem;
  width: 3px;
  height: 3px;
  background: rgb(222 188 132 / 0.85);
  opacity: 0;
  animation: show-speck 2s linear both;
}

.show-weed.rolling {
  position: absolute;
  right: -5rem;
  bottom: calc(14vh + 0.4rem);
  width: 3.4rem;
  height: 3.4rem;
  background: var(--px-tumbleweed) center / 100% 100% no-repeat;
  image-rendering: pixelated;
  animation: weed-roll 2.8s linear 0.2s forwards;
}

@keyframes show-film {
  0% { opacity: 0; }
  14%, 88% { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes show-bar-top {
  0% { translate: 0 -100%; }
  30%, 88% { translate: 0 0; }
  100% { translate: 0 -100%; }
}

@keyframes show-bar-bottom {
  0% { translate: 0 100%; }
  30%, 88% { translate: 0 0; }
  100% { translate: 0 100%; }
}

@keyframes show-spot {
  0% { width: 90rem; height: 90rem; }
  65% { width: 16rem; height: 16rem; }
  88% { width: 14rem; height: 14rem; }
  100% { width: 90rem; height: 90rem; }
}

@keyframes show-gust {
  0% { opacity: 0; translate: 0 0; }
  15% { opacity: 1; }
  100% { opacity: 0; translate: calc(-100vw - 30rem) 0.6rem; }
}

@keyframes show-speck {
  0% { opacity: 0; translate: 0 0; }
  10% { opacity: 1; }
  50% { translate: -50vw -1.2rem; }
  100% { opacity: 0; translate: -102vw 0.8rem; }
}

@keyframes weed-roll {
  0% { translate: 0 0; rotate: 0deg; }
  20% { translate: -20vw -1.8rem; }
  32% { translate: -32vw 0; }
  52% { translate: -52vw -1.2rem; }
  64% { translate: -64vw 0; }
  82% { translate: -82vw -0.6rem; }
  100% { translate: calc(-100vw - 10rem) 0; rotate: -5turn; }
}
