/* Arcade scoreboard.
 *
 * The first pass read the accessibility brief as a reason to be quiet, and
 * quiet reads as boring to anyone under twenty. But none of the constraints
 * actually call for restraint: arcade cabinets, scoreboards and gig posters are
 * all enormous, high-contrast and loud. Saturated colour is MORE visible, not
 * less, and motion costs legibility nothing.
 *
 * So: every accessibility rule from the last pass survives, and the boldness
 * gets spent on colour, motion and one display face.
 *
 * Rules of the house:
 *   - Nothing smaller than 1rem. The base is ~19px, rem throughout, so the
 *     system font-size setting actually multiplies it.
 *   - Every tap target is at least 60px tall.
 *   - Colour never carries meaning alone; there is always a word or a shape.
 *   - Body text clears 7:1. Accents used as text clear 4.5:1 at large sizes.
 *   - Everything that moves is switched off under prefers-reduced-motion.
 */

@font-face {
  font-family: 'Atkinson';
  src: url('/static/fonts/atkinson-400.woff2?v=2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Atkinson';
  src: url('/static/fonts/atkinson-700.woff2?v=2') format('woff2');
  font-weight: 700;
  font-display: swap;
}
/* Bungee is a signage face: built to be read across a room, which is exactly
   this brief, and quirky enough to carry all the personality on its own. Used
   only for the room code and game titles, never for reading. */
@font-face {
  font-family: 'Bungee';
  src: url('/static/fonts/bungee.woff2?v=2') format('woff2');
  font-weight: 400;
  font-display: swap;
}

:root {
  /* Tells the browser we handle both schemes ourselves. Without it Chrome's
     auto-dark-mode force-inverts the light theme and quietly destroys contrast:
     the Wavelength target marker came out near-black on a darkened card and
     vanished entirely. It also makes native controls match. */
  color-scheme: dark light;

  --ground: #0b0b12;
  --card: #17172180;
  --card-solid: #171721;
  --ink: #f7f7fb;           /* 16:1 on card */
  --ink-soft: #a8a8c0;      /* 8.4:1 on card */
  --rule: #2e2e40;
  --rule-bold: #f7f7fb;

  /* Risograph inks. Loud on purpose. */
  --hot: #ff2e88;
  --volt: #00e5a0;
  --amber: #ffc400;
  --violet: #7b5bff;

  /* Decorative-only inks. These stay bright in both themes: the accent tokens
     above get darkened for text contrast in light mode, which turned the
     Wavelength track olive. A gradient bar carries no text, so it keeps the
     saturated versions. */
  --vivid-1: #7b5bff;
  --vivid-2: #ff2e88;
  --vivid-3: #ffc400;
  --vivid-4: #00e5a0;

  /* The display gradient: the wordmark, the room code, the reveal headline
     and Wavelength's number wear it, and it carries text, so light mode
     redefines both stops rather than inheriting accents darkened for body
     copy. */
  --display-1: var(--hot);
  --display-2: var(--amber);

  --glow: 0 0 0 3px #0b0b12, 0 0 24px -4px var(--hot);
  --radius: 14px;
  --tap: 3.75rem;           /* 60px at the ~19px base */
  /* The reaction pill: a 2.4rem button, .3rem of padding each side and a 2px
     border. The page ends this much plus a gap above its own foot, so the
     last row of a scrolled page clears the pill instead of sitting under it. */
  --pill: 3.2rem;
}

@media (prefers-color-scheme: light) {
  :root {
    --ground: #efedf7;
    --card: #ffffffcc;
    --card-solid: #ffffff;
    --ink: #101018;
    --ink-soft: #4b4b63;    /* 7.9:1 on white */
    --rule: #d8d5ea;
    --rule-bold: #101018;
    --hot: #d6006e;         /* 5.6:1 on white */
    --volt: #00795a;        /* 5.2:1 */
    --amber: #9a6b00;       /* 5.1:1 */
    --violet: #5a3ce0;
    /* Loud, and still legible: the accents darkened for body text turn the
       wordmark brown, so the display gradient keeps its own pink into violet.
       5.1:1 and 5.7:1 on the page ground, 6.3:1 at the middle of the ramp. */
    --display-1: #c40068;
    --display-2: #5a3ce0;
    --glow: 0 0 0 3px #efedf7, 0 0 24px -6px var(--hot);
  }
}

* { box-sizing: border-box; }

html { font-size: 119%; }   /* ~19px, and system scaling still multiplies it */

body {
  margin: 0;
  min-height: 100dvh;
  display: grid;
  /* An explicit track, not `justify-items: center`. Centred items are sized to
     max-content, so under Android font scaling one wide child grew the track
     past the viewport and the page scrolled sideways. A bounded track cannot. */
  grid-template-columns: min(30rem, 100%);
  justify-content: center;
  align-items: start;
  padding: 1rem 1rem calc(env(safe-area-inset-bottom, 0px) + var(--pill) + 1.5rem);
  color: var(--ink);
  font-family: 'Atkinson', system-ui, sans-serif;
  line-height: 1.55;
  -webkit-text-size-adjust: 100%;
  /* No pull-to-refresh. A round opens scrolled to the top, which is exactly
     where that gesture arms, and a reload mid-round costs whatever was typed
     or whichever bird was in the air. Ordinary scrolling is untouched: this
     only stops the overscroll chaining to the browser's own UI. */
  overscroll-behavior-y: contain;
  background:
    radial-gradient(120% 80% at 12% -10%, color-mix(in oklab, var(--vivid-1) 40%, transparent), transparent 60%),
    radial-gradient(100% 70% at 100% 0%, color-mix(in oklab, var(--vivid-2) 28%, transparent), transparent 55%),
    var(--ground);
  background-attachment: fixed;
}

.card {
  width: min(30rem, 100%);
  background: var(--card);
  backdrop-filter: blur(14px);
  border: 2px solid var(--rule);
  border-radius: var(--radius);
  padding: 1.25rem 1.25rem 1.5rem;
}

/* ---------- header ---------- */

/* Wraps because the header cannot shrink below its own words: at Android font
   scaling the code, the mute chip and the status chip together outrun a phone. */
.row { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: .75rem; }

/* The code is the invite, but styled like the wordmark it read as
   decoration: a tiny label says what those four letters are for. */
.code-label {
  display: block;
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

h1, .wordmark {
  margin: 0;
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: 2.1rem;
  letter-spacing: .06em;
  line-height: 1.1;
  background: linear-gradient(92deg, var(--display-1), var(--display-2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.status {
  font-size: .95rem;
  font-weight: 700;
  color: var(--ink-soft);
  white-space: nowrap;
}
/* Never colour alone: the word says it, the dot only repeats it. */
.status::before {
  content: "";
  display: inline-block;
  width: .65rem; height: .65rem;
  margin-right: .4rem;
  border-radius: 50%;
  background: var(--ink-soft);
}
.status.live { color: var(--volt); }
.status.live::before { background: var(--volt); box-shadow: 0 0 10px var(--volt); }

/* A quiet chip, not a full-width button: it shares the header with the code
   and the connection status. min-height keeps the 60px tap rule; the word
   carries the state, per the colour-never-alone rule. */
button.mute {
  width: auto;
  min-height: var(--tap);
  margin: 0 0 0 auto;
  padding: .3rem .8rem;
  font-size: .95rem;
  white-space: nowrap;
  color: var(--ink-soft);
  background: transparent;
  border-color: var(--rule);
}
button.mute[aria-pressed="true"] { opacity: .55; }

/* Quiet full-width rows at the card's foot; the header stays glanceable
   state and has no room for more chips at phone width. */
button.abandon {
  min-height: var(--tap);
  margin-top: 1.1rem;
  font-size: .95rem;
  color: var(--ink-soft);
  background: transparent;
}

a.home {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: var(--tap);
  margin-top: 1.1rem;
  font-size: .95rem;
  font-weight: 700;
  color: var(--ink-soft);
  border: 2px solid var(--rule);
  border-radius: 10px;
  text-decoration: none;
}

/* ---------- codenames ---------- */

.cn-grid {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: .3rem;
  margin: .5rem 0;
}
/* Deliberately under the tap floor and the 1rem text floor: 25 tiles must
   share a phone screen, and a mistap is recoverable table drama, not a lost
   turn on a hidden timer. */
button.cn-tile {
  width: auto;
  min-height: 2.6rem;
  margin: 0;
  padding: .15rem .2rem;
  font-size: .68rem;
  overflow-wrap: anywhere;
  line-height: 1.15;
  border-radius: 8px;
}
button.cn-tile:disabled { opacity: 1; cursor: default; }
button.cn-red { background: color-mix(in oklab, var(--hot) 45%, transparent); border-color: var(--hot); }
button.cn-blue { background: color-mix(in oklab, #2277ee 45%, transparent); border-color: #2277ee; }
button.cn-neutral { opacity: .45; }
button.cn-assassin { background: #000; border-color: var(--rule-bold); color: var(--ink); }
/* The spymaster's key: a coloured edge, not a fill, so revealed and merely
   known read differently at a glance. */
button.cn-key-red { border-color: var(--hot); border-width: 3px; }
button.cn-key-blue { border-color: #2277ee; border-width: 3px; }
button.cn-key-assassin { border-color: var(--rule-bold); border-width: 3px; border-style: dashed; }
button.cn-key-neutral { border-style: dotted; }

/* ---------- toast ---------- */

/* One transient line of feedback, above the reaction pill. Every refused or
   failed action lands here; a tap that silently does nothing reads as a
   broken app. */
#toast {
  position: fixed;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 4.2rem);
  left: 50%;
  transform: translateX(-50%) translateY(.5rem);
  max-width: min(90vw, 26rem);
  padding: .6rem 1rem;
  background: var(--card-solid);
  color: var(--ink);
  border: 2px solid var(--rule-bold);
  border-radius: var(--radius);
  text-align: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s, transform .2s;
  z-index: 10;
}
#toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
@media (prefers-reduced-motion: reduce) {
  #toast { transition: none; transform: translateX(-50%); }
}

/* Startable-but-short: dimmed like a disabled button, still tappable so the
   tap can explain itself with the toast. */
button.dim { opacity: .4; box-shadow: none; }

/* ---------- reactions ---------- */

/* display: flex on the ID out-specifies the browser's [hidden] rule, so the
   attribute needs its own harder rule or the bar floats over the lobby's
   Start button while "hidden". */
#react-bar[hidden] { display: none; }
/* A drawing board owns the bottom of the screen: its tools sit exactly where
   the pill floats. */
body:has(.board) #react-bar { display: none; }

#react-bar {
  position: fixed;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 10px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  /* At Android font scaling six emoji outrun the viewport, and a fixed pill
     wider than the screen is clipped on both sides. */
  max-width: calc(100vw - 24px);
  gap: .3rem;
  padding: .3rem .5rem;
  background: color-mix(in oklab, var(--card-solid) 88%, transparent);
  border: 2px solid var(--rule);
  border-radius: 999px;
  z-index: 9;
}
/* Under the tap floor on purpose: six of these share a pill with the whole
   card below it, and a mistap costs an emoji. */
button.react {
  width: 2.4rem;
  flex: 1 1 0;
  min-width: 0;
  min-height: 2.4rem;
  margin: 0;
  padding: 0;
  font-size: 1.3rem;
  background: transparent;
  border: none;
}
button.react:active { transform: scale(1.4); }

.floater {
  position: fixed;
  bottom: 4.5rem;
  font-size: 2.2rem;
  pointer-events: none;
  z-index: 8;
  animation: float-up 1.8s ease-out forwards;
}
@keyframes float-up {
  from { transform: translateY(0) scale(.8); opacity: 1; }
  60%  { opacity: 1; }
  to   { transform: translateY(-42vh) scale(1.5) rotate(8deg); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .floater { animation-duration: .01s; }
}

/* ---------- heads up ---------- */

/* Read across a room, off a forehead: as big as the viewport allows. */
.headsup-word {
  margin: 0;
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: clamp(2.2rem, 15vmin, 5rem);
  line-height: 1.1;
  text-align: center;
  overflow-wrap: anywhere;
}
button.zone { width: 100%; min-height: 5.5rem; font-size: 1.1rem; }
button.zone-got { background: color-mix(in oklab, var(--volt) 22%, transparent); }
button.zone-pass { background: color-mix(in oklab, var(--amber) 18%, transparent); }

/* The holder's takeover: the zones are the whole screen, halved, so a blind
   tap can't miss in either orientation. The word floats over the seam,
   inert to taps. */
.headsup-hold {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  background: var(--ground);
}
/* backdrop-filter makes .card the containing block for fixed descendants,
   which would trap the takeover inside the card. Lift it while holding. */
body:has(.headsup-hold) .card { backdrop-filter: none; }
.headsup-hold button.zone {
  flex: 1;
  min-height: 0;
  margin: 0;
  border-radius: 0;
  font-size: 1.15rem;
  display: flex;
  justify-content: center;
}
/* Labels hug the outer edges, clear of the word floating over the seam. */
.headsup-hold .zone-pass { align-items: flex-start; padding-top: max(1rem, env(safe-area-inset-top)); }
.headsup-hold .zone-got { align-items: flex-end; padding-bottom: max(1rem, env(safe-area-inset-bottom)); }
/* The host's escape hatch rides the takeover's corner: small, out of the
   zones' way, and armed by a first tap (the label says so) before a second
   tap ends the round. */
.headsup-hold .hold-exit {
  position: absolute;
  top: max(.4rem, env(safe-area-inset-top));
  right: .4rem;
  width: auto;
  min-height: 0;
  margin: 0;
  padding: .4rem .7rem;
  font-size: 1.05rem;
  color: var(--ink-soft);
  background: color-mix(in oklab, var(--ground) 70%, transparent);
  z-index: 1;
}
.headsup-hold .hold-exit.armed { color: var(--hot); border-color: var(--hot); }

.headsup-center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  padding: 0 .5rem;
  text-align: center;
  pointer-events: none;
}
/* On one shared phone this screen is the whole table's timer. */
.headsup-clock {
  margin: .2rem 0 0;
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: 1.5rem;
  color: var(--ink-soft);
}
.headsup-clock.low { color: var(--hot); }

/* ---------- spaceteam ---------- */

.panel { display: grid; gap: .6rem; margin-top: .6rem; }
.control {
  border: 2px solid var(--rule);
  border-radius: 10px;
  padding: .5rem .7rem .7rem;
}
.control-name { margin: 0 0 .3rem; font-weight: 700; text-transform: uppercase; font-size: 1rem; }
.ctl-row { display: flex; flex-wrap: wrap; gap: .4rem; }
button.ctl {
  width: auto;
  flex: 1;
  min-height: 2.8rem;   /* four controls must fit one screen; the row is wide */
  margin: 0;
  padding: .3rem .8rem;
  font-size: 1rem;
}
button.ctl.active { border-color: var(--volt); color: var(--volt); }

/* ---------- caption this ---------- */

img.meme {
  display: block;
  width: 100%;
  max-width: 420px;
  margin: .5rem auto 0;
  border-radius: 12px;
  background: #f7f2ea;
}

/* ---------- game emblems ---------- */

/* One 64px pixel sprite per game, the sprite language settled in cairn-table
   wearing this app's inks. The tile ground is fixed dark like the uno suits:
   the sprites bake this exact colour in, and an arcade screen stays dark in a
   bright kitchen. Pixelated rendering keeps the chunky pixels crisp at any
   display size. */
img.pix {
  width: 2.6rem;
  height: 2.6rem;
  flex-shrink: 0;
  border-radius: 8px;
  background: #0b0b12;
  image-rendering: pixelated;
}
img.pix-lg { width: 3.6rem; height: 3.6rem; }
.pick-row { display: flex; align-items: center; gap: .75rem; }
.pick-row p { margin: .5rem 0; }

/* Card art: Love Letter portraits and Friday hazards, same pixel language.
   The portrait tile stays dark inside the light card face on purpose; it
   reads as an inset painting. */
.ll-card img.card-art { width: 3.2rem; height: 3.2rem; }
img.uno-back {
  width: auto;
  height: 3.9rem;
  border-radius: 12px;
  background: #0b0b12;
  image-rendering: pixelated;
  flex-shrink: 0;
}

/* ---------- one screen, one job ---------- */

/* While a round runs, the play surface owns the first screenful: the card
   stretches to the viewport and the reference shelf (how to play, the room
   roster) is pushed to start at the fold instead of peeking in beside the
   question. The 5rem is the body's own vertical padding. A gentle snap lets
   a swipe land the shelf cleanly; proximity, not mandatory, so a game board
   taller than the screen still scrolls freely.

   The bottom padding is the room the reaction pill needs. It floats fixed over
   the foot of the page, so the card ends this much above the page's own foot
   and the last row scrolls clear of it. The demo bar gets the same. */
body.playing .card {
  display: flex;
  flex-direction: column;
  min-height: calc(100dvh - 5rem);
  padding-bottom: 6rem;
}
body.playing #game {
  display: flex;
  flex-direction: column;
  flex: 1;
}
/* The guarantee is on the stage, not the shelf: a margin-top auto gap only
   opens when the card is shorter than the screen, and it usually is not.
   15rem approximates everything above the stage (body and card padding, the
   header row, headline, timer), so the shelf starts at or just past the
   fold; a stage taller than that pushes it further, never less. No scroll
   snapping: measured on real phones it grabs the scroll and the boundary
   guess drifts with device chrome and font scale. The layout separation
   alone does the job. */
body.collecting #stage {
  min-height: calc(100dvh - 15rem);
}
body.collecting #howto {
  margin-top: auto;
}

/* Mid-round the header is reference rather than the invite, so the label goes,
   the code shrinks and the status keeps the same row. The code stays legible:
   it is still how a latecomer gets in. */
body.playing .row { gap: .5rem; }
body.playing .code-label { display: none; }
body.playing h1 { font-size: 1.2rem; }
body.playing .status { font-size: .85rem; }

/* The reveal headline is the room's own line, so it wears the wordmark. Sized
   to run to two lines at phone width, not more. */
#headline.wordmark { font-size: clamp(1.15rem, 4.6vw, 1.55rem); }

/* ---------- demo mode: the ghost table ---------- */

/* The replay shows a real round through the real renderer; the chrome that
   would act on a live room hides. The home link stays: the demo is a
   destination page now, and the way back must be visible. */
body.demo #react-bar,
body.demo #host-controls,
body.demo #abandon,
body.demo .share { display: none; }
/* No ticking clock either: a countdown resetting every frame reads as
   something happening that the viewer is missing. */
body.demo #timer { display: none; }
/* Inputs are scenery in a replay: untappable, so no phone keyboard pops
   over a form nobody can submit. Buttons stay live; they step the frames. */
body.demo #stage input,
body.demo #stage textarea,
body.demo #stage select { pointer-events: none; }
body.demo .card { padding-bottom: 6rem; }

#demo-bar {
  position: fixed;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 10px);
  left: 12px;
  right: 12px;
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .5rem .7rem;
  background: var(--card-solid);
  border: 2px solid var(--rule);
  border-radius: var(--radius);
  /* Above every in-game layer, including Heads up's fullscreen takeover:
     a demo whose captions and arrows are buried is not a demo. */
  z-index: 30;
}
/* Heads up hands the holder the whole screen. In a replay it yields the
   strip the caption bar lives in, so the takeover and the teaching are
   both readable at once. The zone labels hug the outer edges in play, to
   stay clear of the word over the seam; in a replay the lower one would
   hide behind the caption, so both centre in their half instead. */
body.demo .headsup-hold { inset: 0 0 5.5rem 0; }
body.demo .headsup-hold .zone-pass,
body.demo .headsup-hold .zone-got { align-items: center; padding: 0; }
/* The caption is the teacher: it reads before anything else in the bar. */
#demo-say { flex: 1; font-size: 1.1rem; line-height: 1.35; font-weight: 700; }
#demo-count { font-size: .85rem; color: var(--ink-soft); white-space: nowrap; }
#demo-bar button {
  width: auto;
  min-height: 2.8rem;
  margin: 0;
  padding: .2rem .9rem;
  font-size: 1.2rem;
}
/* Forward wears the app's one primary-action look, always: the way onward
   should never need hunting for. */
#demo-bar #demo-fwd {
  min-width: 3.4rem;
  font-size: 1.5rem;
  color: #fff;
  background: linear-gradient(100deg, var(--hot), var(--violet));
  border-color: transparent;
}
/* And it breathes until the first step teaches the rhythm. */
#demo-bar button.demo-nudge { animation: pulse 1.6s ease-in-out infinite; }

/* A shelf row says what the game is while still closed: the name at full
   weight, the seat count and the one-line blurb quiet under it. */
.shelf-entry {
  display: flex;
  flex-direction: column;
  gap: .1rem;
  min-width: 0;
  font-weight: 400;
}
.shelf-name { font-weight: 700; }

/* The shelf's watch button. */
button.demo-watch { color: var(--volt); }
a.demo-link {
  display: inline-block;
  margin-top: .5rem;
  font-weight: 700;
  color: var(--volt);
}

/* ---------- one night ---------- */

/* Your card, always at the top of every screen: it is the one thing the
   whole round hangs on, and it is the thing you are about to lie about. */
.on-card {
  display: grid;
  gap: .25rem;
  margin: .75rem 0;
  padding: .9rem 1rem;
  border: 2px solid var(--rule-bold);
  border-radius: var(--radius);
  background: color-mix(in oklab, var(--ink) 8%, transparent);
}
.on-role {
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: 1.6rem;
  line-height: 1.1;
}
.on-sub { font-size: 1rem; color: var(--ink-soft); line-height: 1.35; }
/* The two cards that are not on the village's side get their own colour, so
   a glance at your own screen never mistakes which way you are pointing. */
.on-card.on-werewolf, .on-card.on-minion { border-color: var(--hot); }
.on-werewolf .on-role, .on-minion .on-role { color: var(--hot); }
.on-card.on-tanner { border-color: var(--amber); }
.on-tanner .on-role { color: var(--amber); }

/* ---------- friday ---------- */

/* The HUD: one thin wrapping row of chips, so the decision below it stays
   above the fold. The stage dots map the three passes plus the pirates to
   the house inks; the word beside them says it too. */
.fri-strip { display: flex; flex-wrap: wrap; gap: .3rem; margin: .15rem 0 .35rem; }
/* Reference, not the decision: the type holds the 1rem floor and the box
   around it gives back every pixel it can, so the hazards below stay on the
   screen. */
.fri-chip {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .1rem .55rem;
  line-height: 1.35;
  border: 2px solid var(--rule);
  border-radius: 999px;
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink-soft);
}
.fri-life.low { color: var(--hot); border-color: var(--hot); }
.fri-max { font-weight: 400; font-size: .85em; }
.fri-dot {
  width: .7rem;
  height: .7rem;
  border-radius: 50%;
  border: 2px solid var(--rule);
}
.fri-dot.done { background: var(--rule); }
/* The pirates are different in kind, not just later; the dot says so
   before you get there. */
.fri-dot.fri-pirate { border-style: dashed; }
.fri-dot.fri-green.here { background: var(--volt); border-color: var(--volt); }
.fri-dot.fri-yellow.here { background: var(--amber); border-color: var(--amber); }
.fri-dot.fri-red.here { background: var(--hot); border-color: var(--hot); }
.fri-dot.fri-pirate.here { background: var(--violet); border-color: var(--violet); }

/* The scoreboard: your total against the hazard's number, in the display
   face. The green number is the word "ahead" for anyone who reads colour;
   crossing the target is also the only moment the left number can equal or
   pass the right one, so the state reads without it. */
.fri-versus { display: flex; align-items: center; gap: 1rem; margin: .75rem 0; }
.fri-score { display: grid; justify-items: center; gap: .15rem; }
.fri-num {
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: 2.4rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.fri-num.ahead { color: var(--volt); }
.fri-vs { font-size: 1rem; font-weight: 700; color: var(--ink-soft); }
.fri-lbl {
  font-size: .95rem;
  color: var(--ink-soft);
  max-width: 8.5rem;
  text-align: center;
  line-height: 1.15;
}

/* A hazard on offer: the card's two labelled sides, fight and prize. */
button.fri-offer {
  display: flex;
  align-items: center;
  gap: .75rem;
  text-align: left;
}
.fri-offer-col { display: grid; gap: .25rem; }
.fri-offer-name { font-weight: 700; font-size: 1.15rem; }
.fri-line {
  display: flex;
  gap: .5rem;
  align-items: baseline;
  font-size: 1rem;
  font-weight: 400;
  color: var(--ink-soft);
}
.fri-tag {
  font-size: .8rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--volt);
  min-width: 3rem;
}
.fri-line.prize .fri-tag { color: var(--amber); }

/* A card's skill rides the card. The newest flip pops; the rest hold still. */
.ll-card .fri-skill {
  font-size: .7rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  opacity: .75;
}
.fri-flip { animation: stamp .32s cubic-bezier(.2, 1.4, .4, 1) both; }

/* The fight hand runs long (a red-pass fight can flip eight cards), so its
   cards are compact: three to a phone row instead of two, and Stop stays
   on screen. */
.fri-hand .ll-card { min-width: 5.2rem; padding: .55rem .4rem; }
.fri-hand .ll-rank { font-size: 1.4rem; }

/* A skill's use button carries its one-line definition. */
button.fri-use { display: grid; gap: .15rem; justify-items: center; }

/* ---------- Onirim ---------- */

/* Colour is the whole game here, so it gets real colour — but the location
   name is always printed on the card too, because colour alone may not carry
   meaning and four of these are being told apart at a glance.

   All four are darkened to clear 4.5:1 against the white card text. The
   obvious versions of these hues (#1e9a52 green, #2f7fe0 blue) land at
   3.6-4.0:1, which is fine for the big symbol and not fine for the location
   name under it, and the name is the part that carries the meaning. */
.or-red { --suit: #de2f2f; }
.or-blue { --suit: #2073d8; }
.or-green { --suit: #1a8547; }
.or-brown { --suit: #9b6a37; }
/* A nightmare has no colour of its own, and without a suit the card computes
   to transparent: white text on a white card in light mode. */
.or-night { --suit: #2a2a34; }
.ll-card.or-card {
  min-width: 5.4rem; padding: .6rem .45rem;
  background: var(--suit); color: #fff;
  border: 3px solid transparent;
}
.or-symbol { font-family: 'Bungee', system-ui, sans-serif; font-size: 1.15rem; }
/* The last card in the row is the one the golden rule is about. */
.ll-card.or-last { border-color: var(--ink); }
.or-line { gap: .35rem; margin: .5rem 0 .25rem; }
.or-row { margin: .5rem 0; }

.or-doors { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; }
.or-door {
  width: .85rem; height: 1.3rem; border-radius: 3px;
  border: 2px solid var(--rule); background: transparent;
}
.or-door.open { border-color: var(--suit); background: var(--suit); }
.or-run { border-color: var(--volt); color: var(--volt); }
button.or-btn { background: var(--suit); color: #fff; border-color: var(--suit); }

/* ---------- For Northwood! ---------- */

/* Same 4.5:1 floor as the Onirim colours above, for the same reason: the suit
   name under the value is small text. */
.nw-c { --suit: #c1512c; }
.nw-f { --suit: #c8417c; }
.nw-l { --suit: #418341; }
.nw-e { --suit: #5d6dcf; }
.ll-card.nw-card {
  min-width: 4.6rem; padding: .5rem .4rem;
  background: var(--suit); color: #fff;
}
.nw-card .ll-rank { font-size: 1.5rem; }

/* The map. Eight tiles that have to fit a phone across two rows, so each one
   carries only its number, its worth, and who lives there. */
.nw-fiefs {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: .4rem; margin: .6rem 0;
}
.nw-fief {
  display: grid; gap: .1rem; justify-items: center; text-align: center;
  padding: .5rem .25rem; border-radius: 12px;
  border: 2px solid var(--rule); background: var(--card);
  color: var(--ink); font-size: .8rem; line-height: 1.15;
  min-height: var(--tap);
}
button.nw-fief { width: 100%; margin: 0; text-transform: none; letter-spacing: 0; }
.nw-num { font-family: 'Bungee', system-ui, sans-serif; font-size: 1.4rem; line-height: 1; }
.nw-stars { color: var(--amber); font-size: .7rem; letter-spacing: -1px; }
.nw-ruler { font-weight: 700; }
.nw-fief.nw-here { border-color: var(--hot); box-shadow: 0 0 18px -6px var(--hot); }
.nw-fief.nw-won { border-color: var(--volt); }
.nw-fief.nw-won .nw-num { color: var(--volt); }
.nw-fief.nw-lost { opacity: .45; }
.nw-fief.nw-lost .nw-num { text-decoration: line-through; }

.nw-said { display: flex; align-items: center; gap: .75rem; margin: .75rem 0; }
/* Hitting the number exactly is the win, so the tally says so the moment it
   is true rather than only at the end of the visit. */
.nw-tally.nw-on { border-color: var(--volt); color: var(--volt); }
button.nw-spent { opacity: .4; }
.card-btn.nw-chosen .ll-card { outline: 3px solid var(--volt); outline-offset: 2px; }
button.nw-btn { background: var(--suit); color: #fff; border-color: var(--suit); }

/* ---------- house-rule toggles ---------- */

#mode-options { display: grid; gap: .3rem; margin-top: .6rem; }
#mode-options .opt {
  display: flex; align-items: center; gap: .6rem;
  min-height: 2.4rem;         /* the whole label row is the tap target */
  font-size: 1rem; color: var(--ink-soft);
}
#mode-options input {
  width: 1.4rem; height: 1.4rem; margin: 0;
  accent-color: var(--volt);
}

/* ---------- draw telephone ---------- */

/* The drawing surface reads as paper: the one deliberately light panel in a
   dark app, because ink drawings need the contrast. */
.board canvas,
svg.paper {
  display: block;
  width: 100%;
  max-width: 420px;
  aspect-ratio: 1;
  margin: .5rem auto;
  background: #f7f2ea;
  border-radius: 12px;
  touch-action: none;   /* a stroke must never scroll the page */
}
svg.paper { touch-action: auto; }

/* Swatches and tools sit under the 60px tap floor: twelve inks plus undo and
   clear must share the paper's 420px width, and a mistap here costs a colour
   change, not a turn. Both rows wrap: everything here is rem-sized, so a
   large system font setting (which this stylesheet deliberately honours)
   otherwise shoves the row off the right edge of a phone. Measured: 618px
   demanded in a 308px slot at a 1.3x font scale. */
.tools { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem;
         max-width: 420px; margin: 0 auto; }
.swatches { display: flex; flex-wrap: wrap; gap: .4rem; flex: 1 1 auto; }
button.swatch {
  width: 2.2rem;    /* wraps to two rows of six at 420px max-width */
  min-height: 2.2rem;
  margin: 0;
  padding: 0;
  border: 2px solid var(--rule);
  border-radius: 50%;
}
button.swatch.active { border-color: var(--rule-bold); transform: scale(1.15); }
button.tool {
  width: auto;
  min-height: 2.4rem;
  margin: 0;
  padding: .2rem .7rem;
  font-size: .95rem;
  color: var(--ink-soft);
  background: transparent;
}

/* ---------- install nudge ---------- */

.install-banner {
  position: fixed;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 12px);
  left: 12px; right: 12px;
  display: flex; align-items: center; gap: .75rem;
  padding: .85rem 2.6rem .85rem 1rem;
  background: var(--card-solid);
  border: 2px solid var(--rule);
  border-radius: 12px;
  z-index: 10;
}
.install-text { flex: 1; }
.install-title { font-weight: 700; }
.install-sub { font-size: .95rem; color: var(--ink-soft); margin-top: 2px; }
.install-share { display: inline-flex; color: var(--volt); vertical-align: middle; }
button.install-btn { width: auto; margin: 0; padding: .5rem 1.1rem; }
button.install-close {
  position: absolute; top: 0; right: 0;
  width: 2.4rem; min-height: 2.4rem; margin: 0; padding: 0;
  background: transparent; border: none;
  color: var(--ink-soft); font-size: 1.3rem;
}

/* ---------- type ---------- */

.prompt {
  font-size: clamp(1.7rem, 7vw, 2.3rem);
  line-height: 1.15;
  font-weight: 700;
  margin: 1rem 0 .5rem;
  animation: rise .28s ease both;
}

.letter {
  display: inline-block;
  margin: 0 0 .5rem;
  padding: .15rem .7rem;
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: 1.4rem;
  color: var(--ground);
  background: var(--amber);
  border-radius: 8px;
}

.clue {
  font-size: 1.4rem;
  line-height: 1.35;
  margin: .75rem 0;
  padding: .6rem .9rem;
  border-left: 5px solid var(--volt);
  background: color-mix(in oklab, var(--volt) 12%, transparent);
  border-radius: 0 8px 8px 0;
}

.muted { color: var(--ink-soft); }
.small { font-size: 1rem; }
.error { color: var(--hot); font-weight: 700; }

/* A ballot you're part of: present but veiled, unreadable on purpose. Not a
   button, because there is nothing here to tap. */
.vote-veiled {
  margin-top: .85rem;
  padding: .7rem 1rem;
  min-height: var(--tap);
  display: flex;
  align-items: center;
  font-size: 1.2rem;
  font-weight: 700;
  background: color-mix(in oklab, var(--ink) 10%, transparent);
  border: 2px solid var(--rule);
  border-radius: 10px;
  filter: blur(4px) saturate(.5);
}

/* The referee leaning in, once: a first-timer's line above the stage. */
.hint {
  margin: .5rem 0 .75rem;
  padding: .5rem .8rem;
  font-size: 1rem;
  line-height: 1.4;
  border-left: 4px solid var(--violet);
  background: color-mix(in oklab, var(--violet) 12%, transparent);
  border-radius: 0 8px 8px 0;
}

.section {
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: .95rem;
  letter-spacing: .06em;
  color: var(--ink-soft);
  margin: 2rem 0 .25rem;
  padding-bottom: .4rem;
  border-bottom: 2px solid var(--rule);
}

/* ---------- timer ---------- */

.timer { margin: .5rem 0 1rem; }
.timer-digits {
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: 2.4rem;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.timer-track {
  height: .8rem;
  margin-top: .45rem;
  background: var(--rule);
  border-radius: 999px;
  overflow: hidden;
}
.timer-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--vivid-4), var(--vivid-3));
  transition: width .25s linear;
}
.timer.low .timer-digits { color: var(--hot); animation: pulse .8s ease-in-out infinite; }
.timer.low .timer-fill { background: var(--hot); }

/* ---------- controls ---------- */

label {
  display: block;
  margin: 1.25rem 0 .35rem;
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink-soft);
}

input[type=text], input:not([type]), select {
  width: 100%;
  min-height: var(--tap);
  padding: .7rem .9rem;
  font-family: inherit;
  font-size: 1.2rem;          /* >=16px or iOS zooms the page on focus */
  color: var(--ink);
  background: color-mix(in oklab, var(--ink) 8%, transparent);
  border: 2px solid var(--rule);
  border-radius: 10px;
}
input:focus, select:focus { border-color: var(--hot); outline: none; box-shadow: var(--glow); }

button {
  width: 100%;
  min-height: var(--tap);
  margin-top: .85rem;
  padding: .7rem 1rem;
  font-family: inherit;
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--ink);
  background: color-mix(in oklab, var(--ink) 10%, transparent);
  border: 2px solid var(--rule);
  border-radius: 10px;
  cursor: pointer;
  transition: transform .06s ease;
}

button.primary {
  color: #fff;
  background: linear-gradient(100deg, var(--hot), var(--violet));
  border-color: transparent;
  box-shadow: 0 6px 0 color-mix(in oklab, var(--violet) 60%, #000);
}
button.primary:active { transform: translateY(6px); box-shadow: none; }
button:not(.primary):active { transform: translateY(2px); }
button:disabled { opacity: .4; box-shadow: none; cursor: not-allowed; }
/* A tap in flight. Card buttons keep full opacity while they are disabled, so
   the pulse is the only thing that says the tap landed and is on its way. */
[aria-busy="true"] { animation: pulse .8s ease-in-out infinite; outline: 3px solid var(--amber); outline-offset: 2px; }

:focus-visible { outline: 4px solid var(--amber); outline-offset: 3px; }

/* ---------- roster ---------- */

ul { list-style: none; margin: .5rem 0 0; padding: 0; }

li {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: .75rem;
  padding: .75rem .1rem;
  font-size: 1.15rem;
  border-bottom: 1px solid var(--rule);
}

/* The wait list: who the room is still on, under the wait line. Tighter than
   the roster because it is reference, not the screen's subject. The word says
   the state; the colour only repeats it. */
.wait-roster { margin-top: .4rem; }
.wait-roster li { padding: .45rem .1rem; font-size: 1.05rem; }
.wait-roster .who { font-weight: 700; }
.wait-roster .state { font-size: 1rem; color: var(--ink-soft); }
.wait-roster .ready .state { color: var(--volt); }
.wait-roster .away { opacity: .55; }

li.gone { opacity: .5; }
li.gone span:first-child::after {
  content: " — away";
  color: var(--ink-soft);
  font-size: .95rem;
}

/* The signature: a score chip that stamps down like a dabber on a card. Scores,
   guess pins and points are all the same gesture, which is what ties five
   different games into one thing. */
.dab {
  display: inline-grid;
  place-items: center;
  min-width: 2.6rem;
  height: 2.6rem;
  padding: 0 .55rem;
  border-radius: 999px;
  background: linear-gradient(140deg, var(--hot), var(--violet));
  color: #fff;
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: 1.1rem;
  font-variant-numeric: tabular-nums;
}
.dab.zero {
  background: none;
  color: var(--ink-soft);
  border: 2px dashed var(--rule);
}
.dab.done { background: linear-gradient(140deg, var(--volt), #0aa); color: #04231c; }
.dab.stamp { animation: stamp .32s cubic-bezier(.2, 1.4, .4, 1) both; }

.mark {
  display: inline-block;
  width: 1.1rem; height: 1.1rem;
  margin-left: .45rem;
  border-radius: 50%;
  background: var(--volt);
  box-shadow: 0 0 10px color-mix(in oklab, var(--volt) 70%, transparent);
  animation: stamp .3s cubic-bezier(.2, 1.4, .4, 1) both;
}

/* ---------- wavelength scale ---------- */

.scale { margin: 1.25rem 0; }
.scale.pinned { margin-bottom: 3.25rem; }
.ends {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: 1.05rem;
  font-weight: 700;
  margin-bottom: .5rem;
}
.ends span:last-child { text-align: right; }

.track {
  position: relative;
  height: 2.1rem;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--vivid-1), var(--vivid-2), var(--vivid-3), var(--vivid-4));
}
/* The single most important mark in the game: where the answer actually was.
   A thin line vanished against the rainbow track, so this is a hard-outlined
   bar with the number sitting above it, readable over any colour beneath. */
.target {
  position: absolute; top: -.55rem;
  width: 8px; height: calc(100% + 1.1rem);
  background: var(--ink);
  outline: 3px solid var(--ground);
  border-radius: 4px;
  transform: translateX(-50%);
  animation: drop .3s ease both;
}
.target span {
  position: absolute;
  bottom: calc(100% + .35rem);
  left: 50%;
  transform: translateX(-50%);
  padding: .1rem .5rem;
  background: var(--ink);
  color: var(--ground);
  border-radius: 6px;
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: .95rem;
  line-height: 1.4;
}
/* Room for the number chip, which sits above the track and would otherwise
   land on top of the left-hand spectrum label. */
.scale.has-target .ends { margin-bottom: 2.1rem; }
.pin {
  position: absolute; top: 2.4rem;
  transform: translateX(-50%);
  display: grid; place-items: center;
  width: 2.1rem; height: 2.1rem;
  border-radius: 50%;
  background: var(--card-solid);
  border: 3px solid var(--ink);
  color: var(--ink);
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: .95rem;
  animation: drop .35s cubic-bezier(.2, 1.3, .4, 1) both;
}

/* Steppers, because a bare range input is the least forgiving control there is
   for imprecise hands. The slider still works; it just isn't the only way. */
.guess {
  display: grid;
  grid-template-columns: var(--tap) 1fr var(--tap);
  align-items: center;
  gap: .75rem;
  margin: .75rem 0;
}
.guess button { width: var(--tap); margin: 0; font-size: 1.7rem; }
.guess-value {
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: 2.6rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
  background: linear-gradient(92deg, var(--display-1), var(--display-2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
input[type=range] {
  width: 100%;
  height: var(--tap);
  margin: 0;
  accent-color: var(--hot);
}

/* ---------- misc ---------- */

.rules {
  margin: .75rem 0 1rem 1.4rem;
  padding: 0;
  font-size: 1.05rem;
  line-height: 1.6;
}
.rules li { display: list-item; border: 0; padding: .25rem 0; font-size: inherit; }

/* What the box would have told you: counts, cutoffs and clocks. Set quieter
   than the rules, because a player reads the rules to start and reads these
   to get better. */
.tips-head {
  margin: 1rem 0 .25rem;
  font-size: .95rem;
  font-weight: 600;
  color: var(--ink-soft);
}
.tips {
  margin: 0 0 1rem 1.4rem;
  padding: 0;
  font-size: .95rem;
  line-height: 1.55;
  color: var(--ink-soft);
}
/* Bulleted, because the reset strips markers and a tip runs to two or three
   lines on a phone: without them, where one ends and the next starts is a
   guess. */
.tips li {
  display: list-item;
  list-style-type: disc;
  border: 0;
  padding: .2rem 0;
  font-size: inherit;
}

details { margin: 1.25rem 0 0; border-top: 1px solid var(--rule); }
summary {
  min-height: var(--tap);
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--volt);
  cursor: pointer;
  list-style: none;
}
summary::-webkit-details-marker { display: none; }
/* An explicit caret: a bold line with no affordance reads as a heading, and
   nobody taps a heading. */
summary::before {
  content: "▸";
  font-size: 1.2rem;
  transition: transform .15s ease;
}
details[open] summary::before { transform: rotate(90deg); }

.share {
  float: right;
  font-size: 1rem;
  letter-spacing: 0;
  font-weight: 700;
  color: var(--volt);
  cursor: pointer;
  padding: .25rem 0;
}

.divider { display: flex; align-items: center; gap: 1rem; margin: 1.5rem 0; color: var(--ink-soft); }
.divider::before, .divider::after { content: ""; flex: 1; height: 2px; background: var(--rule); }

.rejected { text-decoration: line-through; opacity: .6; }

/* Staggered reveal. The reveal is the emotional peak of every one of these
   games and it used to arrive all at once, flat. */
.reveal-row { animation: rise .3s ease both; }

@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
@keyframes stamp {
  from { opacity: 0; transform: scale(1.9) rotate(-12deg); }
  to   { opacity: 1; transform: none; }
}
@keyframes drop {
  from { opacity: 0; transform: translateX(-50%) translateY(-14px); }
  to   { opacity: 1; transform: translateX(-50%); }
}
@keyframes pulse {
  50% { opacity: .45; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }
}

textarea {
  width: 100%;
  padding: .7rem .9rem;
  font-family: inherit;
  font-size: 1.2rem;
  line-height: 1.5;
  color: var(--ink);
  background: color-mix(in oklab, var(--ink) 8%, transparent);
  border: 2px solid var(--rule);
  border-radius: 10px;
  resize: vertical;
}
textarea:focus { border-color: var(--hot); outline: none; box-shadow: var(--glow); }

/* The pass-the-phone interstitial takes the whole card: it is the only thing
   standing between shared-phone play and no secrecy at all. */
#handoff { text-align: center; padding: 1.5rem 0 .5rem; }
#handoff .prompt { margin: .25rem 0 1.5rem; }


/* ---------- uno ---------- */

/* Card colours are fixed, not themed: red is red in a dark car and a bright
   kitchen. Every card carries its name as text too, so the colour is never
   the only thing telling you what it is. */
.c-r { --suit: #e02020; }
.c-y { --suit: #e8a800; }
.c-g { --suit: #14934a; }
.c-b { --suit: #1668d6; }
.c-w { --suit: #2a2a34; }

.pile { display: flex; align-items: center; gap: 1rem; margin: 1rem 0 .5rem; }
.pile-meta { display: grid; gap: .35rem; }

.uno-card {
  display: grid;
  place-items: center;
  min-width: 4.6rem;
  padding: 1.1rem .6rem;
  border-radius: 12px;
  background: var(--suit);
  color: #fff;
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: 1.5rem;
  line-height: 1;
  box-shadow: inset 0 0 0 4px #ffffff2e;
}

.chip {
  padding: .2rem .7rem;
  border-radius: 999px;
  background: var(--suit);
  color: #fff;
  font-weight: 700;
  font-size: 1rem;
}

.hand {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin: 1rem 0 .5rem;
}
.card-btn {
  width: auto;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 3px solid transparent;
  background: none;
  box-shadow: none;
  border-radius: 14px;
}
.card-btn:not(:disabled) { border-color: var(--ink); }
.card-btn:disabled { opacity: 1; }
/* An unplayable card stays readable but obviously out of play, and it is dimmed
   AND labelled by the disabled state rather than colour alone. */
.card-btn.dud .uno-card { opacity: .32; }
.card-btn:not(:disabled):active { transform: translateY(3px); }

/* Lobby filter chips. Toggles, so they carry an on state that reads without
   colour alone: the border thickens and the fill inverts. */
.filters { display: flex; flex-wrap: wrap; gap: .4rem; margin: .4rem 0 .6rem; }
.chip-toggle {
  width: auto;
  min-height: 0;
  margin: 0;
  padding: .45rem .8rem;
  border: 2px solid var(--rule);
  border-radius: 999px;
  background: none;
  box-shadow: none;
  color: var(--ink-soft);
  font-size: .95rem;
}
.chip-toggle.on {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--ground);
}
.chip-toggle:not(:disabled):active { transform: translateY(2px); }

/* The narrowing fold. Closed, it costs one row, so the picker, Start and the
   roster fit the host's first screen. */
#narrow { margin: .4rem 0 .8rem; border-top: 0; }

/* The table-size slider. A full-width track with a big thumb, because this is
   dragged with a thumb on a moving phone. */
.size-slider {
  width: 100%;
  margin: .1rem 0 .6rem;
  accent-color: var(--ink);
  min-height: 1.75rem;
}

/* Love Letter. Two cards at most, so they can be big: the rank does the work
   at a glance and the name carries the effect people are still learning. */
.ll-card {
  display: grid;
  gap: .35rem;
  place-items: center;
  min-width: 7.5rem;
  padding: 1rem .7rem;
  border-radius: 12px;
  background: var(--ink);
  color: var(--ground);
}
.ll-rank {
  font-family: 'Bungee', system-ui, sans-serif;
  font-size: 1.8rem;
  line-height: 1;
}
.ll-name { font-size: .85rem; text-align: center; line-height: 1.15; }
.ll-hand, .ll-reveal { display: flex; flex-wrap: wrap; gap: .6rem; margin: 1rem 0 .5rem; }
.ll-slot { display: grid; gap: .3rem; place-items: center; }
.card-btn.dud .ll-card { opacity: .32; }
/* The destroy cost sits under the card, so a card you cannot afford
   still says why. */
.ll-btn { display: grid; gap: .25rem; justify-items: center; }
.ll-table { list-style: none; padding: 0; margin: 0 0 .5rem; }
/* A card that is actively hurting you reads red, not just negative. */
.ll-card.fri-bad { background: var(--hot); color: #fff; }
.colours .card-btn .ll-card { width: 100%; }

.colours { display: grid; gap: .6rem; margin: 1rem 0; }
.colours .card-btn { width: 100%; }
.colours .card-btn .uno-card { width: 100%; font-size: 1.2rem; padding: .9rem; }

/* Flap. The canvas is the game, so it gets the width and nothing crowds it. */
.flap-wrap {
  display: flex;
  justify-content: center;
  margin: .5rem 0;
  touch-action: manipulation;
}
.flap-wrap canvas {
  border-radius: 10px;
  border: 1px solid var(--rule);
  cursor: pointer;
  /* The canvas keeps its own touches, the way the drawing paper does: a tap
     that drifts a few pixels is a tap, not a scroll, and flapping is nothing
     but taps. Scoped to the canvas rather than the page, so everything around
     it still scrolls normally. `manipulation` is not enough here, since it
     only drops double-tap zoom and still lets a touch pan. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

/* Monkey Island Mode Stage Styles */
.monkey-banner {
  text-align: center;
  margin-bottom: 0.75rem;
}
.monkey-banner h2 {
  font-family: 'Bungee', sans-serif;
  font-size: 1.2rem;
  color: var(--amber);
  margin: 0;
}

.monkey-player-wrap {
  position: relative;
  width: 100%;
  max-width: 640px;
  margin: 0 auto 1rem auto;
  aspect-ratio: 8 / 5;
  background: #000;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
  border: 2px solid var(--rule);
  touch-action: none;
}

.monkey-iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.monkey-expand-btn {
  position: absolute;
  bottom: 8px;
  right: 8px;
  opacity: 0.75;
  font-size: 0.8rem;
  padding: 0.25rem 0.5rem;
  min-height: unset;
  z-index: 10;
}
.monkey-expand-btn:hover {
  opacity: 1;
}

.monkey-watch-panel {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  padding: 1.5rem;
  text-align: center;
  background: var(--card-solid);
}

.monkey-helm-card, .monkey-notes-card {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 1rem;
  margin-bottom: 1rem;
}

.monkey-helm-card h3, .monkey-notes-card h3 {
  margin: 0 0 0.5rem 0;
  font-size: 1.1rem;
  color: var(--volt);
}

.notes-list-container {
  max-height: 180px;
  overflow-y: auto;
  margin-bottom: 0.75rem;
  padding-right: 0.25rem;
}

.notes-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.notes-list li {
  padding: 0.4rem 0.6rem;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 6px;
  margin-bottom: 0.35rem;
  font-size: 0.95rem;
  word-break: break-word;
}

.notes-list li strong {
  color: var(--amber);
}

