:root {
  --bg: #14121a;
  --panel: #1e1b26;
  --accent: #b0453e;
  --accent2: #d9a441;
  /* Sampled directly from the actual eye artwork (public/assets/branding/maker-eyes.png,
     menu-intro.png) — the dominant iris/glow pixel there is rgb(153,3,4), not the
     more muted --accent brick-red already used for general danger/attack accents
     elsewhere. Used ONLY for the "Submit an Issue" menu button (v6.0.4, direct
     request: "red border color and red text that matches the red of the Maker
     eyes") so that one button reads as a distinct, deliberate accent rather than
     just another --accent use. */
  --maker-eyes-red: #990304;
  --text: #ede7d9;
  --muted: #9a93a6;
  /* v5.0.69: pre-game (lobby/room) sizes that scale with monitor WIDTH, not
     height. Everything else in the app is sized off `html`'s own vh-driven
     font-size (see the `html { font-size: clamp(...vh...) }` rule further
     down), which tracks viewport HEIGHT - correct for the in-game UI, which
     must fit a fixed-height viewport without scrolling, but wrong for these
     two horizontally-laid-out pre-game screens: on a wide-but-not-tall
     monitor the Create/Join cards and hero-rail portraits stayed pinned to
     one small size no matter how much wider the window got, since nothing
     about their size was ever a function of width. --lobby-card-w is the
     single source of truth for that width-driven scale, referenced by both
     the card/box max-widths AND (via --hero-rail-w below) the hero-rail's
     own width + position formula, so every piece grows together and the
     rail never drifts from the cards it's supposed to flank.
     v5.0.70: --hero-rail-w was an INDEPENDENT clamp() with its own, faster
     vw-rate than --lobby-card-w, so at most widths the rail actually grew
     past the card box it's supposed to flank (e.g. at 1920px wide: a 307px
     rail image next to a 288px card) - the two just happened to share a
     variable-based formula, not an actual proportional relationship. Now
     derived directly FROM --lobby-card-w at a fixed ratio (150/280, the
     original mockup's own rail-to-card proportion), with a max() floor so
     it never drops below the original 150px once the rails are visible at
     all (they're fully hidden below the 1280px breakpoint regardless). This
     keeps the portraits a smaller accent alongside the cards at every
     width, the way the approved mockup had them, instead of competing with
     the cards for visual weight. Quality note (source art is 500x750px,
     see public/assets/identity/*.jpg) no longer applies in practice - at
     this ratio the rail never grows past ~215px even at --lobby-card-w's
     400px cap, well under the no-upscale ceiling on any display. */
  --lobby-card-w: clamp(240px, 15vw, 400px);
  --hero-rail-w: max(150px, calc(var(--lobby-card-w) * 0.536));
  /* Single source of truth for the four rarity-tier text colors, shared by the High
     Scores leaderboard's tier-tinted player names (.highscore-list li.rarity-<tier>
     .hs-player) AND the High Scores description's tier-odds clause
     (.hs-note-tier-<tier>) - direct request that the description colors match the
     leaderboard's own banner colors, so both read off the same four values instead
     of two copies of the same hex codes drifting apart over time. --rarity-mythical
     was changed from a gold (#ffcf7a) to an orange/red (direct bug report: the gold
     read too close to Legendary's own gold) - the MYTHICAL banner chip itself
     (.rarity-flag-mythical) already spans red-to-gold-to-red, so this text color
     leans into the banner's red end rather than its gold middle. */
  --rarity-rare: #8fb4dd;
  --rarity-veryrare: #c6a6f2;
  --rarity-legendary: #f0c26b;
  --rarity-mythical: #ff5a36;
}
* { box-sizing: border-box; }

/* No text/image selection anywhere in the game (PC and mobile) — it's a game UI, not a
   document, so drag-selecting text or long-pressing an image to "save"/select is just
   noise. Text INPUTS stay selectable/editable (name, room code). Images can't be dragged
   out either. */
* {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;   /* no iOS long-press "save image / copy" menu */
}
input, textarea, [contenteditable="true"] {
  -webkit-user-select: text;
  -moz-user-select: text;
  user-select: text;
}
img {
  -webkit-user-drag: none;
  -khtml-user-drag: none;
  user-drag: none;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  -webkit-tap-highlight-color: transparent;  /* no grey tap flash on mobile */
}
#app { max-width: 720px; margin: 0 auto; padding: 24px 16px 64px; }
header { text-align: center; margin-bottom: 24px; }
header h1 { font-family: "Oswald", "Segoe UI", sans-serif; font-weight: 700; font-size: clamp(1.5rem, 3.4vh, 3rem); letter-spacing: 0.08em; text-transform: uppercase; color: var(--accent2); margin-bottom: 4px; }
.menu-art {
  display: block;
  width: 100%;
  max-width: clamp(300px, 34vh, 720px); /* scales with the monitor like the rest of the UI */
  margin: 10px auto 2px;
}

/* --- Cinematic intro banner ------------------------------------------------
 * The five Heroes under the Maker's gaze, composed from the game's own art.
 * Full-bleed across the top of the menu; its own art fades to the page colour
 * at every edge, so no hard seam is needed. Plays once on load: a slow fade-up
 * with a faint settle, so opening the site feels like a title card. Respects
 * reduced-motion, and is removed from flow entirely in-game (.hidden).
 * v5.0.70: overflow is now `visible` (was `hidden`) - a circular glow sized
 * to reach the (much wider than tall) eyes necessarily bleeds past this
 * tightly-cropped strip's own top/bottom; see the ::before comment below for
 * the full why. `visible` lets it fade naturally instead of getting clipped
 * into a hard-edged box; verified live it doesn't collide with the title
 * text below. */
.menu-intro {
  width: 100%;
  /* v5.0.76: raised from 922px (itself a 25% reduction from an earlier 1230px) by
     request - now serves a sharpened 2x (2880x600) source image (see
     tools/sharpen-menu-intro-2x.py and the <img> tag's own comment in index.html)
     instead of the original 1440x300 PNG, so this larger size still downscales
     on a standard display and only mildly upscales on a 2x/Retina one, rather
     than upscaling the original art directly. */
  max-width: 1300px;   /* the load-up splash eyes. #app is full-width on the
                          menu so this centres via margin auto; mobile stays full-width (viewport < cap). */
  margin: 0 auto -12px;
  overflow: visible;
  line-height: 0;
  pointer-events: none;
  opacity: 0;
  position: relative;
  animation: menu-intro-in 1600ms ease-out 120ms forwards;
}
/* v5.0.70: the banner PNG (public/assets/branding/menu-intro.png) previously
   had its own baked-in reddish "hue" fill fading across the ENTIRE 1440x300
   canvas - a canvas 4.8x wider than it is tall, so that fill was always a
   flattened oval, never a circle, no matter how a CSS mask tried to reshape
   it (v5.0.66, v5.0.68). tools/strip-eyes-hue-background.py stripped that
   fill out of the source art entirely (the untouched original is kept at
   public/assets/branding/_hue-originals/menu-intro.png) - the PNG now
   contains ONLY the flame/eye artwork on full transparency, nothing else.
   The glow is rebuilt here instead, as an actual circle: a square
   ::before layer (equal width/height via aspect-ratio, so its shape can't
   be stretched by the wide/short container the way the old ellipse mask
   was), centered exactly on the eyes (they sit almost perfectly centered in
   the source art - measured centre 720/1440, 150.5/300, i.e. 50%/50.2% -
   and .menu-intro has no other content, so its box IS the image's rendered
   box and 50%/50% lands on the eyes here too), filled with a true `circle`
   radial-gradient (the `circle` keyword forces equal x/y radii by
   definition, unlike `ellipse` which stretches to match whatever box it's
   in - this is what actually guarantees "a perfect circle" regardless of
   the container's own aspect ratio, rather than just tuning percentages on
   an ellipse and hoping). Most intense (highest alpha) at dead center,
   fading smoothly through zero well before the ::before box's own edge, so
   there's no hard boundary to be seen at any size.
   v5.0.71: doubled from 180% to 360% per request - the whole square (and
   therefore the circle drawn inside it) is now twice the size, still
   centered on the eyes via the unchanged top/left:50% + translate(-50%,-50%)
   below. Because the gradient's color stops are percentages OF this box,
   doubling the box automatically stretches the same fade curve over twice
   the distance - the hue now dissolves to transparent much further out from
   the eyes than before, rather than just being a bigger flat blob. */
.menu-intro::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  /* Sized off the container's HEIGHT (not width) so its radius can be tuned
     against a container whose width varies (responsive) but whose aspect
     ratio - and therefore height-as-a-fraction-of-width - never does. 360%
     of that height (v5.0.71: doubled from 180%) puts the circle's radius at
     ~38% of the container's WIDTH - the glow now reaches well past the eyes
     themselves before fading out, per the v5.0.71 "grow it and dissolve
     further out" request, rather than hugging tight to their own edge. */
  height: 360%;
  aspect-ratio: 1 / 1;
  transform: translate(-50%, -50%);
  /* v5.0.71: explicit `closest-side` sizing (was the default `farthest-corner`
     when no size keyword was given). With `farthest-corner`, 100% means "the
     distance to this square box's own CORNER" - which is ~41% farther out
     than the distance to a straight EDGE (a square's half-diagonal vs. its
     half-side). That mismatch meant the old 88%-transparent stop finished
     fading well before the corners but was still ~15% opaque at the moment
     it hit the box's own top/bottom/left/right edges - background-clip stops
     painting exactly at the box edge, so that residual opacity read as a
     faint hard-edged square outline around the circle. `closest-side` makes
     100% = the distance to the NEAREST edge in every direction (all four are
     equal here since the box is a forced square), so the 88%-transparent
     stop now finishes fading with margin to spare before reaching ANY edge
     of the box, corners included (a gradient holds its final color past its
     last stop, so corners past the closest-side radius are simply already
     fully transparent) - no more square-shaped cutoff at any edge. */
  background: radial-gradient(circle closest-side at center, rgba(50, 31, 46, 0.92) 0%, rgba(50, 31, 46, 0.6) 35%, rgba(50, 31, 46, 0.22) 62%, transparent 88%);
  pointer-events: none;
  z-index: 0;
}
.menu-intro img {
  width: 100%;
  height: auto;
  display: block;
  position: relative;
  z-index: 1;
}
.menu-intro.hidden { display: none !important; }

@keyframes menu-intro-in {
  from { opacity: 0; transform: translateY(-14px) scale(1.03); filter: brightness(0.5) saturate(0.7); }
  60%  { opacity: 1; }
  to   { opacity: 1; transform: translateY(0) scale(1); filter: brightness(1) saturate(1); }
}
@media (prefers-reduced-motion: reduce) {
  .menu-intro { animation: menu-intro-fade 900ms ease-out forwards; }
  @keyframes menu-intro-fade { from { opacity: 0; } to { opacity: 1; } }
}

/* The intro banner already features the Maker's eyes large, so the small eyes
   image beneath it would be a redundant second pair. While the banner is
   showing, hide that small art and let the "THE MAKER" wordmark sit in normal
   flow just under the banner (the same treatment used in-game). */
#menu-intro:not(.hidden) ~ header #menu-art { display: none; }
/* Room below the wordmark/tagline on the menu so the welcome line is never
   clipped by the header's tight in-game padding. */
#menu-intro:not(.hidden) ~ header { padding-bottom: 14px; }
#menu-intro:not(.hidden) ~ header .title-overlay {
  position: static;
  transform: none;
  text-shadow: 0 2px 10px rgba(0, 0, 0, 0.9);
  font-size: 1.5rem;
  margin-top: 0;
  margin-bottom: 2px;
}
/* With the banner showing, keep the welcome line snug beneath the wordmark (and
   therefore close to the eyes above it) — and clearly readable. This overrides
   the compact in-game tagline size (#app > header .tagline) that would otherwise
   shrink it on the menu too. */
#menu-intro:not(.hidden) ~ header .tagline {
  margin-top: 6px;
  font-size: clamp(1rem, 1.7vh, 1.35rem);
  line-height: 1.35;
}
#menu-intro:not(.hidden) ~ header .credit-line {
  font-size: clamp(0.75rem, 1.15vh, 1rem);
  line-height: 1.3;
  margin-top: 3px;
}
/* The eyes' back-hue is baked into the banner PNG, so on its own it stops at the
   image's lower edge. This soft radial wash CONTINUES that same dark-purple hue
   downward, behind the title / tagline / credit line, so the glow reads as one
   field that flows past the banner instead of being cut off at the image border.
   Menu-only (gated on the intro being visible) and painted behind both the banner
   art and the text (z-index:-1), so it never sits on top of anything readable. */
#menu-intro:not(.hidden) ~ header { position: relative; }
#menu-intro:not(.hidden) ~ header::before {
  content: "";
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: -24px;
  width: min(860px, 96vw);
  height: 156px;
  z-index: -1;
  pointer-events: none;
  /* Peak sits at the banner's lower edge so it meets the PNG's own lower fade,
     then tapers DOWN behind the title / tagline below it and OUT to the sides -
     a soft ellipse (explicit radii), so there is no rectangular box or hard edge
     and it dissolves to the page background before the menu cards. */
  background: radial-gradient(ellipse 340px 84px at 50% 44px,
    rgba(60, 37, 55, 0.50) 0%,
    rgba(46, 29, 43, 0.34) 40%,
    rgba(30, 23, 36, 0.16) 62%,
    rgba(20, 18, 26, 0) 84%);
}
/* Mobile, direct request: "match the PC main menu - a circular background hue that is
   centered. Remove all other background hues for this artwork." On a phone the banner's
   own box is much shorter relative to its width (full-bleed, no side margins, edge-to-edge
   with the title right underneath - desktop has generous room on every side instead), so
   this second wash - meant only to softly continue the circle's fade past the image's own
   lower edge - ends up overlapping the circle itself almost immediately instead of
   extending past it, reading as a second, separate hue rather than one clean centered
   circle. Desktop is untouched (the wash there does what its own comment says: continues
   the fade into genuinely empty space below the image, not into the circle itself). */
html.mobile #menu-intro:not(.hidden) ~ header::before { background: none; }
.tagline { color: var(--muted); font-size: clamp(0.8rem, 1.4vh, 1.3rem); margin: 0 0 4px; }
.hidden { display: none !important; }
.card {
  background: var(--panel);
  border: 1px solid #33303e;
  border-radius: 10px;
  padding: 16px;
  margin-bottom: 16px;
}
input {
  display: block;
  width: 100%;
  padding: 8px 10px;
  margin: 8px 0;
  border-radius: 6px;
  border: 1px solid #45414f;
  background: #100e15;
  color: var(--text);
}
button {
  background: var(--accent);
  color: #fff;
  border: none;
  padding: 10px 16px;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
  margin-right: 8px;
  margin-top: 4px;
  /* Every button in the app is already fully custom-styled (background, border,
     padding, border-radius) below, but without this reset iOS Safari can still
     layer its OWN native control chrome underneath a small custom button - live
     on a real iPhone this showed up as a visible oval/pill halo around the
     Spectating card's small circular (✕) close button, and could just as
     easily be inflating other compact buttons' real tap height past their own
     CSS padding. Reset at the shared base rule rather than patching each
     button that happens to get reported, since every button is equally exposed
     to the same native-chrome quirk. */
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
}
button:hover { filter: brightness(1.1); }
button:disabled { opacity: 0.4; cursor: not-allowed; }
/* The "READY" button — the one call to action of the setup phase, so it's
   deliberately loud: a bright gold gradient, bigger and bolder, uppercase, with
   a soft pulsing glow that draws the eye. Falls back to a quiet look while disabled
   (facing split not yet valid). */
/* Sizing lives on the BASE selector (applies to disabled OR enabled alike) so the
   button never jumps size when it becomes valid — only its colour/glow do. Sized to
   match the footprint the old, longer "Ready to Start Game" label used to fill
   (that label was previously scaled down 15% just to fit it); now that the label
   is the single short word "READY", there's room to make the button itself
   bigger AND bump the font size to match, instead of shrinking it down. */
.start-game-btn {
  font-size: 1.2rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  /* The label is a single short word ("READY") set directly in uppercase in the
     markup, so line-height:1 keeps it tightly, evenly centred vertically inside
     the button's padding rather than inheriting a taller default line box that
     left it looking off-centre. */
  line-height: 1;
  padding: 12px 28px;
  border-radius: 8px;
  /* Breathing room top + bottom so the glow isn't clipped by #turn-controls'
     overflow when the button sits flush at the container's bottom edge. */
  margin: 4px auto 12px;
}
.start-game-btn:not(:disabled) {
  /* v5.0.75: was a 3-stop vertical gradient (light gold -> --accent2 -> dark
     bronze), giving every gold button in the game a glossy, embossed-pill look.
     Per request, changed to a flat fill in the SAME gold as "THE MAKER" title
     text (header h1 / .title-overlay both use var(--accent2)) for a more matte
     finish. All 5 gold buttons in the game shared this exact gradient string
     and were changed together, here and at: .lobby-start-btn (Start Game),
     #lobby-screen .card button (Create/Join Room), .orient-btn-primary (the
     face-up orientation prompt), and .player-count-pill-active (selected
     player-count pill) - so they stay visually identical to each other. */
  background: var(--accent2);
  color: #2a1f08;
  /* A TIGHT glow (spread kept under the 12px bottom margin) so it stays within the
     clipped play-area column instead of being cut off at the edge. */
  box-shadow: 0 0 0 2px rgba(255, 233, 170, 0.5), 0 0 8px 1px rgba(217, 164, 65, 0.5);
  animation: start-game-pulse 1.5s ease-in-out infinite;
}
.start-game-btn:not(:disabled):hover {
  filter: brightness(1.06);
  animation-play-state: paused;
  box-shadow: 0 0 0 3px rgba(255, 243, 200, 0.75), 0 0 11px 2px rgba(217, 164, 65, 0.75);
}
@keyframes start-game-pulse {
  0%, 100% { box-shadow: 0 0 0 2px rgba(255, 233, 170, 0.45), 0 0 6px 1px rgba(217, 164, 65, 0.4); }
  50%      { box-shadow: 0 0 0 2px rgba(255, 233, 170, 0.65), 0 0 10px 2px rgba(217, 164, 65, 0.7); }
}
/* The lobby's "Start Game" button — the one thing a host needs to find and press
   to actually begin, so it gets the same loud gold treatment as "Ready to Start
   Game" in-game, scaled up further: full-width, a much bigger tap target, and a
   pulsing glow so it's unmistakable even among the room's other cards/controls. */
.lobby-start-btn {
  display: block;
  width: 100%;
  text-align: center;
  /* v5.0.68: bottom margin matched to .card's own 16px margin-bottom (was
     10px) so the gap down to the player-list box below it is the SAME as
     the gap between that box and Total Players below IT - was 10px vs 16px,
     visibly uneven.
     v5.0.69: top margin zeroed (was 16px) - .invite-row above now owns that
     gap entirely with its own margin-bottom, so the two stayed 16px and
     16px stacked into a 32px gap here instead of the 16px used everywhere
     else on this screen. Single-sided margins throughout this stack (room
     code h2 -> invite row -> Start Game -> seat list) is what keeps every
     gap the same 16px. */
  margin: 0 0 16px;
  /* Vertical padding trimmed down from the original 18px so the button's height
     tracks its own font size more closely, rather than towering well past it. */
  padding: 14px 20px;
  font-size: 1.35rem;
  font-weight: 800;
  letter-spacing: 0.03em;
  line-height: 1;
  border-radius: 12px;
  background: var(--accent2);
  color: #2a1f08;
  box-shadow: 0 0 0 2px rgba(255, 233, 170, 0.5), 0 0 14px 2px rgba(217, 164, 65, 0.55), 0 6px 18px rgba(0, 0, 0, 0.4);
  animation: start-game-pulse 1.5s ease-in-out infinite;
}
.lobby-start-btn:hover {
  filter: brightness(1.06);
  animation-play-state: paused;
  box-shadow: 0 0 0 3px rgba(255, 243, 200, 0.75), 0 0 18px 3px rgba(217, 164, 65, 0.8), 0 6px 18px rgba(0, 0, 0, 0.4);
}
.lobby-start-btn.hidden { display: none; }
/* Discouraged action button — e.g. Draw Loot for a Hero once the Maker is out in
   the open, or Draw/Move/Play-Action once the player has started assigning attack
   dice (Attack is the move now). A LIGHT red (a washed-out tint of the usual hard
   red) so it reads as "not what you want" while staying fully usable — distinct
   from both the solid-red primary buttons and the grey Leave Room button. */
.btn-discouraged {
  background: #cf8f8b;
  color: #3a1c19;
  border: 1px solid #b0453e;
}
.btn-discouraged:hover { filter: none; background: #d9a29e; color: #2a1310; }
/* A plain grey button — used for the dead player's "Leave Room" so it reads as a
   quiet, secondary action rather than a bright call to action. */
.btn-grey {
  background: #3a3742;
  color: var(--muted);
  border: 1px solid #55515f;
}
.btn-grey:hover { filter: none; background: #454150; color: var(--text); }
.error { color: #e2665f; min-height: 1.2em; }
.note { color: var(--muted); font-size: 0.85rem; }
#seat-list { list-style: none; padding: 0; }
#seat-list li { padding: 6px 0; border-bottom: 1px solid #2b2733; }
/* v5.0.66: now the first child inside .seat-list-card (a bordered box like
   Total Players below it, was a bare heading floating in the page flow) -
   the card's own padding provides the top breathing room, so this no longer
   needs its own 14px top margin on top of that. */
.seat-list-title { margin: 0 0 8px; }
/* Placeholder rows for seats nobody's taken yet — visibly quieter than a real
   player's row (dimmer text, no bold), so it reads as "not filled yet" rather
   than a real teammate. The first joining human effectively takes "Bot #1"'s
   spot: it falls off the top of this placeholder run and everything below
   renumbers, with no special-case code needed for that. */
#seat-list li.seat-bot-placeholder { color: var(--muted); }
/* Your own row gets the emoji-picker inline; a light highlight so it's easy to
   spot which row is "you" among the others. */
#seat-list li.seat-mine { color: var(--accent2); }
.emoji-picker {
  display: inline-block;
  /* A fully custom-drawn arrow (appearance:none + our own tiny SVG chevron)
     instead of relying on the browser's native <select> arrow — the native
     one reserves an inconsistent (and sometimes larger-than-expected) amount
     of space across browsers/platforms, which is exactly what was cramping
     the emoji glyph against it before. This way the reserved space for the
     arrow is exact and guaranteed, on every browser. */
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  width: 57px; /* was 44px - about 30% wider, to give the emoji glyph more room. The
                  arrow (background-size/position below) is untouched — it's sized and
                  positioned independently of this width, so it stays exactly the same. */
  margin: 0 0 0 6px;
  /* Generous vertical padding AND a minimum height, well beyond what a
     "typical" emoji glyph needs. Real color-emoji fonts (Apple Color Emoji,
     Segoe UI Emoji, Noto Color Emoji) render considerably TALLER and more
     detailed than this dev environment's fallback glyphs, so testing here
     alone understates how tight the box actually is in a real browser — this
     is deliberately oversized rather than tuned to a specific glyph's exact
     height, so it stays safe across whichever emoji font actually renders it. */
  padding: 6px 16px 6px 5px;
  min-height: 34px;
  font-size: 0.95rem;
  line-height: 1.5;
  border-radius: 6px;
  border: 1px solid #45414f;
  background-color: #100e15;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='8' height='6' viewBox='0 0 8 6'><path d='M0 0 L4 6 L8 0 Z' fill='%23a39ec0'/></svg>");
  background-repeat: no-repeat;
  background-position: right 4px center;
  background-size: 8px 6px;
  color: var(--text);
  vertical-align: middle;
}
/* No margin-left here — the templates already put exactly one literal space
   before these spans (see the player-row-name / seat-list templates in
   app.js); adding CSS margin on top of that space was a double gap. */
.seat-emoji, .player-emoji { margin-left: 0; }
/* Player-count buttons: allow a clean two-line wrap (the 3-player one carries a
   "(Beta)" tag on its own line) and keep them the same height so the row stays even. */
#player-count-selector button {
  white-space: normal;
  line-height: 1.15;
  text-align: center;
  vertical-align: top;
  min-height: 2.6em;
}
#player-count-selector .beta-tag {
  display: block;
  font-size: 0.72em;
  font-weight: 600;
  opacity: 0.72;
  letter-spacing: 0.02em;
}
/* Each player's info is its own clearly-divided pane: a subtle raised card with a border and a
   gap between rows, rather than a faint hairline separator. */
.player-row {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 8px 10px;
  margin-bottom: 7px;
  background: rgba(255, 255, 255, 0.035);
  border: 1px solid #423d4e;
  border-radius: 9px;
}
.player-row:last-child { margin-bottom: 0; }
.player-row-name {
  font-weight: 600;
  font-size: 1.05rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.player-row-name small { font-weight: 400; color: var(--muted); }
.player-row-name small.you-tag { color: var(--accent2); font-weight: 600; }
/* HP text + status chips on one line, vertically CENTRED together. Was inline flow, where
   the larger/bolder HP text and the middle-aligned chips didn't line up (the chips sat a
   touch low). Flex + align-items:center puts them on the same centre line. */
.player-row-meta { display: flex; align-items: center; flex-wrap: wrap; font-size: 0.85em; color: var(--muted); }
.player-hp { font-size: 0.98rem; font-weight: 700; }
.player-hp-bar {
  height: 6px;
  background: #2b2733;
  border-radius: 3px;
  overflow: hidden;
  margin: 3px 0 1px;
}
.player-hp-bar-fill {
  height: 100%;
  width: 100%;
  border-radius: 3px;
  background: var(--accent2);
  /* Glides between values in step with the narrated HP counter (refreshPlayerHp). */
  transition: width 0.45s ease, background-color 0.45s ease;
}
.player-row.dead .player-hp-bar-fill { background: var(--muted) !important; }
/* The active player's pane keeps the plain card background — its NAME, HP, and the turn
   arrow go gold (via color + the arrow ::after), and now the row's own border does too, so
   whose turn it is reads at a glance even before the arrow catches the eye. Deliberately
   thin (the existing 1px border width, just recoloured) — kept subtle so it doesn't compete
   with the much heavier 2-3px "Your Turn" glow around the Play Area itself. */
.player-row.current { color: var(--accent2); border-color: var(--accent2); }
.player-row.current .player-row-meta { color: var(--accent2); opacity: 0.85; }

/* Whose turn it is: a leftward arrow pinned to the top-right of the current player's info row,
   pointing back across the row at them. ~3/4 of the big play-area arrow it replaced (~3.15rem).
   Shown on BOTH desktop and mobile. It is ABSOLUTELY positioned (out of the flex flow) for two
   reasons: (1) it no longer reserves a column that shortened the health bar — the bar now spans
   the full row width; (2) it sits up at the name line, clear of the bar at the bottom of the row.
   The translateX nudge animation still applies to an absolute element. */
.player-row.current .player-row-top { position: relative; }
.player-row.current .player-row-top::after {
  content: "\25C2";            /* ◂ */
  position: absolute;
  right: 2px;
  top: -4px;                   /* sit up at the name line, lower tip clear of the bottom health bar */
  line-height: 1;
  font-size: 3.15rem;
  color: var(--accent2);
  text-shadow: 0 0 10px rgba(224, 184, 58, 0.4);
  pointer-events: none;        /* never intercept clicks on the row */
  animation: turn-arrow-nudge 1.4s ease-in-out infinite;
}
/* Mobile RIGHT-handed: the name/HP sit on the LEFT (opposite the right-side cards), so the turn arrow
   stays on the RIGHT pointing LEFT (◂) — same as desktop, just clamped to top:0 so the mobile row's
   overflow:hidden doesn't clip the glyph. */
html.mobile .player-row.current .player-row-top::after { top: 0; }
@keyframes turn-arrow-nudge {
  0%, 100% { transform: translateX(0); opacity: 0.7; }
  50%      { transform: translateX(-4px); opacity: 1; }
}
@keyframes turn-arrow-nudge-right {
  0%, 100% { transform: translateX(0); opacity: 0.7; }
  50%      { transform: translateX(4px); opacity: 1; }
}
/* LEFT-HANDED: name/HP sit on the RIGHT (opposite the left-side cards), so the turn arrow moves to
   the LEFT of the row and points RIGHT (▸) at the player, with the rightward wiggle. */
html.mobile.left-handed .player-row.current .player-row-top::after {
  content: "\25B8";            /* ▸ right-pointing */
  right: auto;
  left: 2px;
  animation-name: turn-arrow-nudge-right;
}
@media (prefers-reduced-motion: reduce) {
  .player-row.current .player-row-top::after { animation: none; opacity: 0.9; }
}
.player-row.dead { text-decoration: line-through; color: var(--muted); }

.player-card-thumbs {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 4px;
  margin-top: 4px;
}
.player-card-thumbs:empty { margin-top: 0; }
.player-card-thumb {
  width: 40px;
  height: 58px;
  object-fit: cover;
  /* Unified card-corner formula — this "thumbnail" now looks exactly the same,
     corner-wise, as inventory card art, its hover-magnify preview, and the
     identity card. */
  border-radius: 8px;
  border: 1px solid #45414f;
}
.player-card-thumb.face-down-thumb { opacity: 0.85; cursor: default; }

/* --- Hover-to-enlarge: just a cursor + subtle lift hint here. The actual
   enlarged preview is a separate floating element (#card-hover-preview,
   positioned via JS in app.js) rather than an in-place CSS transform —
   an in-place transform gets clipped by any scrolling ancestor (the hand
   row, the players panel, etc.) and can't be kept on-screen near a viewport
   edge, both of which a floating fixed-position element sidesteps cleanly. --- */
.hoverable-card-art { cursor: pointer; }

#card-hover-preview {
  position: fixed;
  z-index: 3000;
  pointer-events: none;
  display: none;
}
#card-hover-preview.visible { display: block; }
#card-hover-preview img {
  display: block;
  /* Unified card-corner formula — the magnified hover preview looks exactly
     the same, corner-wise, as inventory card art and the identity card. */
  border-radius: 36px;
  border: 2px solid #45414f;
  box-shadow: 0 14px 44px rgba(0, 0, 0, 0.8);
}
#card-hover-preview-text {
  display: none;
  max-width: 240px;
  padding: 10px 12px;
  background: var(--panel);
  color: var(--text);
  border: 1px solid #45414f;
  border-radius: 8px;
  font-size: 0.85rem;
  line-height: 1.35;
  box-shadow: 0 14px 44px rgba(0, 0, 0, 0.8);
}
.hand-card {
  display: inline-block;
  background: #100e15;
  border: 1px solid #45414f;
  border-radius: 8px;
  padding: 8px 10px;
  margin: 4px;
  font-size: 0.85rem;
  min-width: 140px;
  vertical-align: top;
}
.hand-card small { color: var(--muted); display: block; margin-top: 4px; }
/* Click-arrow reorder (a click alternative to dragging) removed — drag now works at any
   time on both PC and mobile, so a separate control was redundant. */
/* Background + border hue for each card type, matching that type's title
   text color on the actual card art (weapon=green, spell=blue,
   extra_die=white, maker_only=brown, action=red). */
.hand-card.weapon { background: #142a1c; border-color: #3c8f52; }
.hand-card.spell { background: #15202f; border-color: #3f7dc9; }
.hand-card.extra_die { background: #26262a; border-color: #d8d8d8; }
.hand-card.maker_only { background: #2a1f14; border-color: #a06a35; }
.hand-card.action { background: #2a1416; border-color: #b0453e; }
select { padding: 6px; border-radius: 6px; background: #100e15; color: var(--text); border: 1px solid #45414f; margin: 4px 4px 4px 0; }
.weapon-rows { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-start; margin-top: 2px; }
.weapon-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  border: 1px solid #2b2733;
  border-radius: 6px;
  flex: 0 0 auto;
  flex-wrap: nowrap;
}
.weapon-row-name { font-weight: 600; white-space: nowrap; }
.weapon-row button {
  padding: 4px 10px;
  margin: 0;
  min-width: 30px;
}
.dice-count { min-width: 44px; text-align: center; }
/* Power toggle: its own full-width row with the checkbox and label aligned,
   so the Attack button (which follows it) sits on the next line rather than
   being pulled up alongside an inline label. */
.power-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 12px 0 4px;
  cursor: pointer;
  line-height: 1.3;
}
.power-toggle input {
  margin: 0;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
}
button.selected { outline: 2px solid var(--accent2); }
.identity-art {
  display: block;
  max-width: 160px;
  /* FIXED pixel radius, deliberately NOT a percentage/clamp() — percentage-
     based border-radius (including clamp() with a % term) was confirmed, via
     live pixel-level screenshot testing, to silently fail to render on real
     card-art elements in this app's actual deployed context (corners came out
     perfectly square) even though the exact same declaration worked fine in
     isolated test pages and on the hover-magnify preview specifically (whose
     img gets an explicit JS-set pixel width, not a CSS %-based one — the
     likely trigger, though the precise engine mechanism wasn't pinned down).
     A plain fixed px value, live-tested the same way, rendered correctly
     every time. Every card-art context in the app now uses a fixed value
     sized proportionally for ITS typical rendered size (small thumbnails get
     a small radius, the large hover-preview gets a large one) rather than a
     single formula computed live — see the sibling rules (.card-art,
     .hand-row .card-art, #card-hover-preview img, .player-identity-card,
     etc.) for the same treatment at their own sizes. The old per-context
     clip-path insets are separately gone too - they existed only to hide a
     white-ring export defect on 7 card PNGs, which is now fixed directly in
     those source files (see tools/bake-card-corner-fix.py). */
  border-radius: 22px;
  margin-bottom: 8px;
}
.card-art {
  display: block;
  width: 100%;
  max-width: 140px;
  border-radius: 18px;
  margin-bottom: 6px;
}
.card-art.reaction-art { max-width: 100px; }
.board-svg { display: block; width: 100%; max-width: 340px; margin: 0 auto 12px; }
.board-art { display: block; max-width: 100%; border-radius: 8px; margin-bottom: 10px; }
.card-option-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; justify-content: center; }
.card-option {
  display: flex;
  flex-direction: column;
  align-items: center;
  background: #100e15;
  border: 1px solid var(--accent);
  border-radius: 8px;
  padding: 8px;
  cursor: pointer;
  color: var(--text);
  min-width: 100px;
}
.card-option:hover { filter: brightness(1.15); }
.card-option .card-art { max-width: 100px; margin-bottom: 4px; }
.card-option span { font-size: 0.8rem; text-align: center; }
/* In the popups and the turn-control prompts, choice tiles share the row and
   shrink to fit (up to 5 discard cards) rather than wrapping/overflowing, so the
   whole choice stays on one screen. Art is still hover-to-magnify. */
#overlay-panels .card-option-row,
#turn-controls .card-option-row { flex-wrap: nowrap; gap: 5px; margin-top: 4px; width: 100%; }
#overlay-panels .card-option,
#turn-controls .card-option { flex: 0 1 108px; min-width: 0; max-width: 108px; padding: 4px; }
#overlay-panels .card-option .card-art,
#turn-controls .card-option .card-art { max-width: 100%; width: 100%; margin-bottom: 2px; }
#overlay-panels .card-option span,
#turn-controls .card-option span { font-size: 0.7rem; line-height: 1.1; overflow-wrap: anywhere; }
.reaction-panel { border-color: var(--accent); background: #241a1a; }
/* Obtain-a-card orientation prompt: face-up is the DEFAULT, so its button is the
   loud primary (gold) and gets keyboard focus, while "Keep face-down" is a quiet
   secondary the player has to reach for deliberately. */
.orient-btn-primary {
  background: var(--accent2);
  color: #2a1f08;
  font-weight: 800;
  border: none;
}
.orient-btn-primary:focus-visible { outline: 2px solid #fff3d6; outline-offset: 2px; }
.orient-btn-secondary {
  background: transparent;
  border: 1px solid #55505f;
  color: var(--muted);
  font-weight: 600;
}
.orient-btn-secondary:hover { color: var(--text); border-color: var(--accent2); }
/* Incoming-hit header. The unblockable version of this prompt is only on screen
   for about three seconds, so it has to read at a glance rather than be read. */
.reaction-hit {
  font-family: "Oswald", "Segoe UI", sans-serif;
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1.15;
  text-align: center;
  color: var(--accent);
  margin: 8px 0 2px;
}
/* Says plainly that there's nothing to decide, so nobody sits waiting for buttons
   that aren't coming. */
.reaction-noblock {
  text-align: center;
  font-size: 1rem;
  font-weight: 600;
  color: var(--text);
  opacity: 0.85;
  margin: 0 0 2px;
}
.reaction-panel .hp-transition { margin: 2px 0 8px; text-align: center; }
/* The "You obtained a card!" prompt card. Desktop shows the full card (art + name);
   mobile hides the art and shows just the name (see mobile.css) since the player
   already saw the card when they drew it. */
.hand-card.obtain-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  margin: 6px auto;
}
#log-list { max-height: 200px; overflow-y: auto; font-size: 0.85rem; color: var(--muted); }
#log-list div { padding: 3px 0; border-bottom: 1px dashed #2b2733; }

/* ======================================================================
   Fit-to-viewport game screen layout (no page scrolling required).
   ====================================================================== */
html { font-size: clamp(12px, 1.5vh, 22px); }
html, body { height: 100%; }
body { overflow: hidden; }
#app {
  height: 100vh;
  width: 100%;
  max-width: none;
  padding: 0;
  display: flex;
  flex-direction: column;
}
#app > header {
  flex: 0 0 auto;
  margin-bottom: 0;
  padding: 2px 14px 1px;
  text-align: center;
}
.title-art-wrap { position: relative; display: inline-block; max-width: 460px; width: 100%; }
.title-overlay {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  margin: 0;
  white-space: nowrap;
  font-size: 1.65rem;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 1), 0 0 9px rgba(0, 0, 0, 1), 0 0 4px rgba(0, 0, 0, 1);
}
/* In-game the eyes art is hidden, so the title falls back to normal flow — keep
   it small and tight so the play/inventory panes get the space. */
.menu-art.hidden + .title-overlay { position: static; transform: none; text-shadow: none; font-size: 1rem; }
#app > header h1 { font-family: "Oswald", "Segoe UI", sans-serif; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; font-size: 1rem; margin: 0; }
.version-tag { text-transform: none; letter-spacing: normal; font-weight: 400; font-size: 0.62em; color: var(--muted); }
/* v5.0.67: on the room lobby screen, drop the version tag onto its own
   centered line below "THE MAKER" instead of trailing inline beside the
   title text - the in-game compact header (where every extra line of height
   is at a premium) keeps the original inline placement. display:block is
   enough to center it: it inherits text-align:center from the header, and a
   block-level span defaults to full container width, so its own text
   centers the same as the title above it. */
body:has(#room-screen:not(.hidden)) .version-tag {
  display: block;
  margin-top: 4px;
  font-size: 0.42em;
}
/* v5.0.68: the MAIN menu is different again - the version tag is pulled out
   of the title entirely and re-shown as its own small fixed badge in the
   bottom-left corner (mirroring the audio settings gear, bottom-right,
   ~28px/1rem - see .audio-controls). It's the same #version-tag DOM node the
   fetch() below always keeps up to date; position:fixed just relocates it
   visually and pulls it out of the header's normal flow, so nothing else
   needs to hide it there. */
body:has(#lobby-screen:not(.hidden)) .version-tag {
  position: fixed;
  left: 12px;
  bottom: 12px;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  /* A pill rather than a hard 28x28 square - "(v5.1)" is a few characters of
     text, not one glyph like the gear, so it needs width to breathe. Height
     and padding still match .audio-controls exactly for the same visual
     weight/footprint in the opposite corner. */
  height: 28px;
  min-width: 28px;
  padding: 6px 10px;
  border-radius: 999px;
  background: rgba(20, 18, 26, 0.72);
  border: 1px solid #3a3545;
  backdrop-filter: blur(4px);
  font-size: 0.75rem;
  white-space: nowrap;
  /* Direct request: clicking this pill opens Patch Notes (see openPatchNotesFromVersionTag
     in app.js, wired only while this exact scope's condition - the main menu showing - is
     true, so the cursor/hover feedback here never lies about what a click will do). */
  cursor: pointer;
}
body:has(#lobby-screen:not(.hidden)) .version-tag:hover { background: rgba(20, 18, 26, 0.9); border-color: var(--accent2); }
#app > header .tagline { font-size: 0.62rem; margin: 0; line-height: 1.05; color: var(--muted); }

#lobby-screen, #room-screen {
  overflow-y: visible;
  padding: 8px 16px 12px;
  /* v5.0.65: NOT flex:1 anymore. Growing to fill the remaining height below
     the header only centered THIS screen's own content within the leftover
     space under the header - the menu-intro eyes banner + header (title/
     tagline/credit line) stayed pinned to the very top the whole time, so
     the page as a whole still read as "hugging the top" even though this
     screen's own children were technically centered in their own box.
     Centering the WHOLE group (eyes banner, header text, cards, buttons) is
     now done one level up, on #app itself (see body:has(...) #app rule
     below) - this screen just sizes to its natural content height so the
     group's total height is what gets centered. */
}
/* Hero rails: flanking hero-identity portraits on the main menu and room lobby
   only (concept mockup, v5.0.61/62 rounds - sizing/glow matched to the
   approved mockup screenshots in the v5.0.63 round). Fixed to the viewport
   edges rather than laid out inside #app, so they never affect #app's own
   max-width/centering or any in-game layout math. Gated to appear ONLY while
   #lobby-screen or #room-screen is the visible screen (body:has(...) -
   already relied on elsewhere in this file, e.g. the tutorial-screen header
   hide above), and only on windows wide enough to clear the centered content
   column without overlap; the html.mobile rule below additionally
   force-hides them on real touch devices per the project's own
   (pointer: coarse)+(hover: none) mobile detection (mobile.js), independent
   of window width.
   v5.0.66: position is now `calc(50% - 481px)` instead of a flat px inset
   from the viewport edge. A flat inset put the rails a FIXED distance from
   the edge, so on any monitor wider than the ~1280px breakpoint the centered
   card column (which stays a constant ~582px wide, not viewport-relative)
   just drifted further and further from the rails as the window widened -
   at the project's own reference resolution (2531px) that gap was over
   600px. Anchoring the rails to the viewport CENTER instead - half the
   lobby's max card-row width (582/2 = 291px) plus the rail's own width
   (150px) plus a fixed 40px clearance = 481px - keeps a constant ~40px gap
   to the card row at every width once the rails are visible.
   v5.0.69: the fixed 291px/150px in that formula are now var(--lobby-card-w)
   (+ half the row's own 22px gap) and var(--hero-rail-w), so the offset
   recomputes automatically as those two vars scale with viewport width
   instead of staying frozen at the old mockup's fixed sizes - the rail
   still keeps the same ~40px clearance from the card row at every width,
   it's just that "the card row" and "the rail" are both bigger now on a
   wide monitor. Room-screen's single-column content is narrower than the
   lobby's two-card row this formula is based on, so room-screen gets
   slightly MORE clearance, never less - verified live at the breakpoint
   boundary (1280px), 1920x1080, and the project's reference resolution. */
.hero-rail {
  display: none;
  position: fixed;
  top: 0;
  bottom: 0;
  width: var(--hero-rail-w);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 40px;
  pointer-events: none;
  z-index: 1;
}
.hero-rail-left { left: calc(50% - var(--lobby-card-w) - 11px - var(--hero-rail-w) - 40px); }
.hero-rail-right { right: calc(50% - var(--lobby-card-w) - 11px - var(--hero-rail-w) - 40px); }
.hero-rail-img {
  width: var(--hero-rail-w);
  aspect-ratio: 2 / 3; /* matches the source art's real 500x750 pixel ratio */
  height: auto;
  object-fit: cover;
  border-radius: 14px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
}
/* Each portrait gets a soft blurred glow matching its own hero color, per the
   mockup - a wide, low-opacity colored box-shadow reads as a halo without
   needing an extra glow element. */
.hero-rail-img--blue   { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 46px 10px rgba(79, 143, 217, 0.55); }
.hero-rail-img--green  { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 46px 10px rgba(90, 191, 96, 0.55); }
.hero-rail-img--orange { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 46px 10px rgba(224, 138, 52, 0.55); }
.hero-rail-img--purple { box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6), 0 0 46px 10px rgba(155, 89, 182, 0.55); }
@media (min-width: 1280px) {
  body:has(#lobby-screen:not(.hidden)) .hero-rail,
  body:has(#room-screen:not(.hidden)) .hero-rail {
    display: flex;
  }
}
html.mobile .hero-rail { display: none !important; }
/* Pre-game facelift (v5.0.64 round): bigger title + eyes art, and Create/Join +
   the 3 bonus buttons laid out in rows, matching the approved mockup. Scoped to
   #lobby-screen/#room-screen only (via body:has(), the same pattern already used
   for the hero rails above) so the in-game header stays in its tight
   fit-to-viewport form - this only loosens things up pre-game, where there's
   room to spare. Specificity note: these need the #app > header h1 shape (an ID
   selector) to out-rank the compact #app > header h1 { font-size: 1rem; } rule
   above, since .title-overlay's own font-size loses to that ID rule otherwise. */
body:has(#lobby-screen:not(.hidden)) #app > header,
body:has(#room-screen:not(.hidden)) #app > header {
  padding: 20px 14px 8px;
}
body:has(#lobby-screen:not(.hidden)) #app > header h1,
body:has(#room-screen:not(.hidden)) #app > header h1 {
  font-size: clamp(1.9rem, 4.4vh, 3.1rem);
}
body:has(#lobby-screen:not(.hidden)) #app > header .tagline,
body:has(#room-screen:not(.hidden)) #app > header .tagline {
  font-size: clamp(0.85rem, 1.7vh, 1.15rem);
  /* v5.0.68: capped so this line can't grow wider than the card row below it
     and reach into the (now much closer, v5.0.66 round) hero-rail zone at
     mid-range viewport widths - unconstrained, this paragraph would wrap to
     whatever width #app > header naturally had (nearly the full viewport),
     well past the card row's own width.
     v5.0.72: that cap was a flat 580px - fine at the v5.0.68 mockup's fixed
     card size, but --lobby-card-w (v5.0.69) now scales the actual card row
     width WITH the viewport, and the 580px cap didn't scale with it. At the
     project's own 2522x1246 reference resolution (and wider) the card row
     is already ~780px+ wide with room to spare, but this line was still
     force-wrapped to 2 lines at exactly the same 580px as a cramped
     1280px-wide window. Switched to `calc(2 * var(--lobby-card-w) + 22px)` -
     the exact same footprint as the two-card row itself (each card's own
     max-width, plus their 22px gap, see .menu-cards-row) - so this cap
     scales in lockstep with the row beneath it instead of going stale, and
     the line only wraps when it's genuinely too long for that width, not
     because of a leftover fixed number. Verified live at 1280/1440/1920/
     2522/3440px: single line everywhere the natural text width already fit
     under the old cap, AND now also single line at 2522px and 3440px where
     it previously force-wrapped despite the row having plenty of room. */
  max-width: calc(2 * var(--lobby-card-w) + 22px);
  margin-left: auto;
  margin-right: auto;
}
body:has(#lobby-screen:not(.hidden)) .menu-art,
body:has(#room-screen:not(.hidden)) .menu-art {
  max-width: clamp(340px, 32vh, 580px);
}
/* v5.0.84: the "Welcome to The Maker..." tagline now shows on the title
   screen (#lobby-screen) only, per direct request - every other menu (room
   lobby, Rarity Guide, High Scores, Card Compendium) and the in-game header
   hide it. The tutorial screen already hides its ENTIRE header (see the
   #app:has(#tutorial-screen...) rule further down), so nothing extra is
   needed there. This only hides the line itself - the enlarge/width-cap
   rule just above still harmlessly matches #room-screen too, it just has
   nothing visible left to size. */
body:has(#room-screen:not(.hidden)) #app > header .tagline,
body:has(#rarity-screen:not(.hidden)) #app > header .tagline,
body:has(#highscores-screen:not(.hidden)) #app > header .tagline,
body:has(#compendium-screen:not(.hidden)) #app > header .tagline,
body:has(#game-screen:not(.hidden)) #app > header .tagline {
  display: none;
}

/* v6.0.29 (direct request): in-game only, the title/version and the credit
   line move to the far right of the screen instead of sitting centered over
   the middle of the viewport - but the two lines stay CENTERED RELATIVE TO
   EACH OTHER as a group (direct follow-up correction: a first pass just
   flipped header's text-align to "right," which instead flush-aligned each
   line's own right edge against the header's right edge - visually a
   "ratchet," since the title and credit line are different lengths, not the
   same centered-group look the original layout had, just relocated).
   Scoped to #game-screen so every other menu (lobby, room, Rarity Guide,
   High Scores, Compendium) keeps its original centered-in-the-middle header
   unchanged.
   The trick: header's own text-align stays "center" (unchanged, inherited
   from the base #app > header rule above) - centering is still what
   positions each line's actual glyphs. What moves is the BOX each line
   centers itself within: .title-art-wrap and .credit-line are each given
   the identical max-width (460px, matching .title-art-wrap's own pre-
   existing cap) and pushed flush against the header's right edge with
   margin-left:auto (a block-level element with an explicit width and
   margin-left:auto pushes itself to its container's right edge - the
   standard CSS "right-align a fixed-width block" pattern). Because both
   boxes share the exact same width AND right edge, they also share the same
   LEFT edge - so they occupy the identical horizontal span, and each line's
   own text-align:center (inherited) centers it within that shared span
   exactly as it did before, just shifted from spanning the whole header to
   spanning only its rightmost 460px.
   .title-art-wrap needs display:block (overriding its normal inline-block,
   which the lobby/room screens still need for the absolutely-positioned
   title-over-hero-art effect) because auto margins only push BLOCK-level
   boxes - inline-block ignores margin-left:auto. Safe to override here:
   in-game the hero art (.menu-art) is hidden and .title-overlay itself
   already switches to position:static (see .menu-art.hidden + .title-overlay
   above), so nothing in-game depends on title-art-wrap's relative/
   inline-block positioning context any more.
   Mobile is unaffected either way: it hides this entire header in-game
   already (html.mobile.in-game #app > header { display: none } in
   mobile.css).
   This is also what makes the chat bar's old title/credit clamp in
   syncChatBarPosition() (app.js) unnecessary and removed this same round -
   see that function's comment for why moving the title clear of the play
   area's column mostly (not entirely - a narrower-desktop edge case is
   handled there too) removes the overlap risk the clamp existed to
   prevent.
   Implementation (second pass, direct correction from a first attempt that
   used a hardcoded 460px shared width - see the paragraph above): a bare
   `margin-left:auto` push needs the two lines to share one common width so
   their centers (not just one edge) line up, and a fixed 460px guess (borrowed
   from .title-art-wrap's own pre-existing cap, sized for the much bigger
   lobby/room title, not this compact in-game one) put a misleadingly wide
   gap between the text and the true right edge. CSS Grid solves this without
   any hardcoded number: header becomes a single-column grid (no
   grid-template-columns declared - the standard "auto-stacks into one
   intrinsically-sized column" trick), so that one column's width is set by
   the WIDER of the two lines' own natural (max-content) widths, and BOTH
   grid items stretch to fill that exact shared width (Grid's default
   justify-items/justify-self:stretch) - whichever line is narrower is then
   genuinely centered (inherited text-align:center, unchanged) within that
   shared width, not just sitting in its own separately-sized box. Only
   justify-content:end is needed to push that one (auto-sized, narrower than
   the header) column over to the header's right edge. .tagline is a grid
   child too but display:none in-game (see above), so it's excluded from
   grid placement entirely and doesn't add a stray column/row. */
body:has(#game-screen:not(.hidden)) #app > header {
  display: grid;
  justify-content: end;
}
/* Clears .title-art-wrap's own width:100%/max-width:460px (sized for the
   lobby/room screens' bigger title-over-hero-art layout) so its grid-item
   width comes purely from its intrinsic content (the h1's own text) instead
   - see the comment above for why a fixed width defeats the whole point
   here. display:block isn't strictly required (CSS "blockifies" any grid
   item regardless of its own inline-block display), kept for clarity. */
body:has(#game-screen:not(.hidden)) #app > header .title-art-wrap {
  display: block;
  width: auto;
  max-width: none;
  margin: 0;
}
body:has(#game-screen:not(.hidden)) #app > header .credit-line {
  margin: 0;
}
/* v5.0.65: center the WHOLE pre-game group - the .menu-intro eyes banner,
   the header (title/tagline/credit line), and the active screen's cards/
   buttons - as one block within the viewport, instead of only centering
   the active screen's own content in the leftover space below a
   top-pinned header. #app is already `display:flex; flex-direction:column`
   and 100vh tall (see the fit-to-viewport rule above); scoping
   justify-content here (rather than on #lobby-screen/#room-screen) is what
   lets the eyes banner + header move down together with the cards instead
   of staying glued to the very top. `safe center` falls back to top-aligned
   if the group is ever taller than the viewport; overflow-y: auto (moved
   here from the individual screens) is the scroll fallback for that case. */
body:has(#lobby-screen:not(.hidden)) #app,
body:has(#room-screen:not(.hidden)) #app {
  justify-content: safe center;
  overflow-y: auto;
}
/* Lobby: shrink the Create/Join panes and center them on screen. */
#lobby-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: safe center;
}
/* Create / Join sit side-by-side in a row on screens wide enough (mockup
   facelift, v5.0.64 round); wraps back to a stacked column once the two cards
   (plus gap) no longer fit, so narrow/mobile widths need no separate rule.
   align-items: stretch (v5.0.65 round) so both cards share the SAME height
   regardless of which has more fields (Join has one extra input) - .card is
   plain block flow internally (not itself a flex column), so its heading and
   fields still stack from the top of that taller box rather than centering
   within the extra space. */
.menu-cards-row {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  align-items: stretch;
  gap: 22px;
  width: 100%;
}
#lobby-screen .card {
  width: 100%;
  /* v5.0.69: was clamp(240px, 25vh, 280px) - driven by viewport HEIGHT, which
     is nearly always tall enough on an ordinary desktop to hit the 280px cap
     regardless of window WIDTH, so a wide-but-not-tall monitor never grew
     these past 280px no matter how much horizontal room was free. Switched
     to var(--lobby-card-w) (width-driven, see :root) and raised the cap to
     400px so these actually respond to a wider screen; the hero rails'
     position formula above reads this same variable so the two stay in
     sync. */
  max-width: var(--lobby-card-w);
  /* v5.0.66: zero out the base .card { margin-bottom: 16px }. Vertical
     spacing between the card row and the buttons row below is now owned
     entirely by .menu-actions-row's own margin-top, so the two rows can sit
     tightly packed instead of stacking two separate top/bottom margins. */
  margin-bottom: 0;
  /* v5.0.74: was plain block flow (per the align-items: stretch comment on
     .menu-cards-row above) - both cards get stretched to the SAME height
     (Join is naturally taller, one extra room-code field), but block flow
     just stacks the Create card's heading/field/button from the top and
     leaves the leftover height as dead space below the button, so "Create
     Room" sat noticeably higher than "Join Room" and the single "Your name"
     field sat pinned to the top instead of level with Join's own fields.
     Flex column + the .card-fields wrapper below fixes both: the heading
     and button stay fixed-size at the top/bottom, and the fields wrapper
     (flex: 1) claims exactly the leftover space in between and centers
     itself within it - so both buttons land on the same row regardless of
     which card has more fields, and a lone field centers on the shared
     midline instead of hugging the top. */
  display: flex;
  flex-direction: column;
}
/* v5.0.74: wraps each card's input(s) so they can be vertically centered as
   a group within the leftover space between the heading and the button
   (see the .card flex-column comment above), instead of the fields just
   sitting wherever block flow happens to stack them. flex: 1 is what makes
   the wrapper actually claim that leftover space - the card with more
   fields (Join, no slack to center within) ends up unchanged, since its
   fields already fill the wrapper edge-to-edge. */
.card-fields {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* Main menu: centre everything inside the Create / Join panes — the heading, the
   name / room-code fields, and the action button — so the whole lobby reads as a
   centred column rather than left-aligned text in a centred box. */
#lobby-screen .card { text-align: center; }
#lobby-screen .card input {
  text-align: center;
  /* v5.0.74: was relying on the base `input` rule's fixed 8px 10px padding
     and the browser UA default font-size (~13.3px) - a fixed height
     regardless of how large --lobby-card-w has scaled the surrounding card,
     so at wide monitors these fields looked cramped/small next to the now-
     much-bigger card and its already-scaled button (see
     `#lobby-screen .card button`'s own font-size formula just below, same
     pattern reused here). font-size now scales with --lobby-card-w (same
     0.0556 multiplier as the button, so field text and button text track
     each other 1:1); padding switched to em units so it grows right along
     with the font-size instead of staying flat. */
  font-size: calc(var(--lobby-card-w) * 0.0556);
  padding: 0.6em 0.75em;
}
#lobby-screen .card button {
  display: block;
  width: 100%;
  margin: 8px auto 0;
  /* v5.0.68: matched to the Start Game button's own treatment (gradient +
     glow + pulse), per direct request - was a flat solid gold. Was also gold
     to match the mockup (previously the same red as in-game action buttons)
     - scoped to the lobby's Create/Join buttons only, not the base `button`
     rule other screens still use.
     v5.0.70: font-size explicitly tied to var(--lobby-card-w) - was
     unset here, so it fell all the way back to the browser's plain UA
     default button font (~13.3px, fixed regardless of anything on the
     page), same at every viewport width even as the button itself grew
     from ~200px to ~370px wide once these scaled with monitor width
     (v5.0.69 round). The multiplier (0.0556) is calibrated so the text
     comes out the same ~13.3px as before at the card's floor width (240px,
     small windows) and grows proportionally from there - literally scaling
     WITH the button, not independently of it. Padding switched to em units
     (was a fixed 10px 16px from the base `button` rule) so it scales
     alongside the font-size automatically instead of needing its own
     separate formula.
     v5.0.72: vertical padding trimmed 0.75em -> 0.45em per request ("reduce
     the height... to cleanly fit the text") - 0.75em top+bottom was adding
     more empty space above/below the label than the button's own pill shape
     needed, especially once the font-size scaling above made these buttons
     noticeably wider without their height following suit. 0.45em matches
     the snugger, font-tracking fit already established on .lobby-start-btn
     (see its own "trimmed down... so the button's height tracks its own
     font size more closely" comment) rather than introducing a new ratio.
     Horizontal padding (1.2em) is untouched - only the height changed. */
  font-size: calc(var(--lobby-card-w) * 0.0556);
  padding: 0.45em 1.2em;
  font-weight: 800;
  letter-spacing: 0.03em;
  border-radius: 12px;
  background: var(--accent2);
  color: #2a1f08;
  box-shadow: 0 0 0 2px rgba(255, 233, 170, 0.5), 0 0 14px 2px rgba(217, 164, 65, 0.55), 0 6px 18px rgba(0, 0, 0, 0.4);
  animation: start-game-pulse 1.5s ease-in-out infinite;
}
#lobby-screen .card button:hover {
  filter: brightness(1.06);
  animation-play-state: paused;
  box-shadow: 0 0 0 3px rgba(255, 243, 200, 0.75), 0 0 18px 3px rgba(217, 164, 65, 0.8), 0 6px 18px rgba(0, 0, 0, 0.4);
}
/* How To Play / Card Compendium / Stats for Nerds sit in a row too (mockup
   facelift, v5.0.64 round) - overrides the base .tutorial-open-btn's
   width:100%/margin:14px auto 0 (still relied on elsewhere it might appear)
   with auto-width flex items plus row-level gap/margin instead. */
.menu-actions-row {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin-top: 8px; /* v5.0.66: tightened from 14px so the 3 buttons pack close under the cards */
}
.menu-actions-row .tutorial-open-btn {
  width: auto;
  max-width: none;
  margin: 0;
  flex: 0 1 auto;
  /* Smaller than the base size (v5.0.65 round) so all 3 comfortably fit the
     same overall width as the Create/Join row above, on one line, without
     wrapping on ordinary desktop widths. */
  padding: 8px 14px;
  font-size: 0.88rem;
}
/* Short desktop/laptop windows: tighten the menu so the whole thing (both cards,
   How to play, High scores, and any "room not found" error) fits without needing
   to scroll. Kicks in only when the window is short. */
@media (min-width: 700px) and (max-height: 860px) {
  #menu-art { max-height: 12vh; }
  .tagline { font-size: 0.9rem; margin: 3px 0 0; }
  .credit-line { margin: 2px 0 0; }
  #lobby-screen { gap: 7px; padding: 6px 16px 8px; }
  #lobby-screen .card { padding: 10px 16px; }
  #lobby-screen .card h2 { margin: 0 0 6px; }
  #lobby-screen input { margin: 5px 0; }
  #lobby-screen .card button { margin-top: 4px; }
  #lobby-screen .tutorial-open-btn { margin-top: 5px; }
}
@media (min-width: 700px) and (max-height: 700px) {
  header { padding: 4px 16px !important; }
  .title-art-wrap { max-height: 12vh; overflow: hidden; }
  #menu-art { max-height: 9vh; }
  .title-overlay { font-size: 1.4rem; }
  .tagline { font-size: 0.82rem; margin: 2px 0 0; }
  .credit-line { font-size: 0.7rem; margin: 2px 0 0; }
  #lobby-screen { gap: 5px; padding: 2px 16px 6px; }
  #lobby-screen .card { padding: 7px 16px; }
  #lobby-screen .card h2 { margin: 0 0 3px; font-size: 1.02rem; }
  #lobby-screen input { margin: 3px 0; padding: 6px 10px; }
  #lobby-screen .card button { padding: 6px 12px; }
  #lobby-screen .tutorial-open-btn { padding: 6px 12px; margin-top: 3px; }
}

/* Fifth main-menu button, its own row BELOW .menu-actions-row's four (direct
   request) - kept as a separate row rather than a 5th flex item in that row so
   it always sits centered on its own line under the other four regardless of
   how many of them wrap.
   v6.0.23: a second button ("Get Launch Updates") joined it in this same row
   (see .email-signup-btn below) - gap/flex-wrap added so the two sit side by
   side with breathing room on wide screens and stack cleanly on narrow ones. */
.menu-secondary-row {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 10px;
  width: 100%;
  margin-top: 10px;
}
/* Mobile-only heading for the button-block grouping (direct request) - see
   the h2.menu-group-title elements in index.html for "Explore the Dungeon"
   (.menu-actions-row) and "Other" (.menu-secondary-row). Hidden here (and on
   desktop generally, where these rows stay a plain button row, not a
   card-like block); mobile.css un-hides it once the row itself gets the
   card background/border treatment. Same hide-on-desktop/show-on-mobile
   pattern already used for .chat-panel-hint-mobile above. */
.menu-group-title { display: none; }
/* Same dark-fill/outlined shape as .tutorial-open-btn, but in --maker-eyes-red
   instead of --accent2 gold - a deliberately different accent so this button
   reads as its own thing (reporting a problem), not just a fifth menu link.
   The BORDER stays --maker-eyes-red; the label text is --muted light grey
   (direct follow-up request), matching the muted body-text tone used
   elsewhere rather than the loud red repeated in both border and text. */
.report-issue-btn {
  margin: 0;
  width: auto;
  max-width: clamp(280px, 30vh, 520px);
  padding: 8px 14px;
  font-size: 0.88rem;
  font-weight: 700;
  background: #100e15;
  color: var(--muted);
  border: 1px solid var(--maker-eyes-red);
  border-radius: 8px;
  cursor: pointer;
}
.report-issue-btn:hover { background: var(--maker-eyes-red); color: #14121a; }

/* --- "Submit an Issue" overlay: a true fixed modal, sibling of #app, popping
   up ON TOP of the main menu (direct request) rather than swapping the menu
   out for another full .screen the way How To Play / Card Compendium / Stats
   for Nerds do. Same dimmed-backdrop + centered-card language as the in-game
   #overlay-panels (reaction/reveal prompts), but its own element since this
   one needs to work from the main menu, before any game/socket state exists. --- */
.issue-report-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(8, 7, 11, 0.92);
  padding: 16px;
}
.issue-report-overlay.hidden { display: none; }
.issue-report-card {
  background: var(--panel);
  border: 1px solid var(--maker-eyes-red);
  border-radius: 10px;
  padding: 18px 20px;
  width: 100%;
  max-width: 480px;
  max-height: 90vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  text-align: left;
  animation: modal-pop-in 0.15s ease-out;
}
.issue-report-card h2 { margin: 0 0 4px; font-size: 1.3rem; }
.issue-report-card label {
  font-size: 0.85rem;
  color: var(--muted);
  margin-top: 10px;
  margin-bottom: 3px;
}
.issue-report-card select,
.issue-report-card textarea {
  width: 100%;
  padding: 8px 10px;
  border-radius: 6px;
  border: 1px solid #45414f;
  background: #100e15;
  color: var(--text);
  font-family: inherit;
  font-size: 0.92rem;
  margin: 0;
}
.issue-report-card select:disabled { opacity: 0.5; cursor: not-allowed; }
.issue-report-card textarea { resize: vertical; min-height: 90px; }
.issue-report-status { margin: 10px 0 0; font-size: 0.85rem; }
.issue-report-status.error { color: #ff7a70; }
.issue-report-status.success { color: #8fd694; }
.issue-report-status.hidden { display: none; }
.issue-report-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}
.issue-report-actions button { margin: 0; }
#issue-report-cancel { background: #33303e; }

/* "Get Launch Updates" button (v6.0.23, direct request) - same dark-fill/
   outlined shape and sizing as .report-issue-btn right above (they share the
   row), but --accent2 gold instead of --maker-eyes-red: this is a positive
   marketing call-to-action, not a warning/report button, so it borrows the
   game's normal gold accent instead of the red reserved for issue-reporting.
   Text stays var(--muted) at rest like its neighbor, for the same understated
   look, and inverts to a solid gold fill with dark text on hover. */
.email-signup-btn {
  margin: 0;
  width: auto;
  max-width: clamp(280px, 30vh, 520px);
  padding: 8px 14px;
  font-size: 0.88rem;
  font-weight: 700;
  background: #100e15;
  color: var(--muted);
  border: 1px solid var(--accent2);
  border-radius: 8px;
  cursor: pointer;
}
.email-signup-btn:hover { background: var(--accent2); color: #14121a; }

/* --- "Get Launch Updates" overlay: identical structure/behavior to the
   "Submit an Issue" overlay above (fixed modal, sibling of #app, dimmed
   backdrop, centered card), just re-skinned in gold to match its button. --- */
.email-signup-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(8, 7, 11, 0.92);
  padding: 16px;
}
.email-signup-overlay.hidden { display: none; }
.email-signup-card {
  background: var(--panel);
  border: 1px solid var(--accent2);
  border-radius: 10px;
  padding: 18px 20px;
  width: 100%;
  max-width: 480px;
  max-height: 90vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  text-align: left;
  animation: modal-pop-in 0.15s ease-out;
}
/* The physical box photo. Direct follow-up request: square, not the original
   rectangular crop - aspect-ratio: 1 / 1 (not a fixed height, which never
   actually produced a square against the card's own variable width) forces
   the box to stay square at any card width, and object-fit: cover crops the
   source photo to fill it without distortion. Fixed PIXEL border-radius, not
   a percentage or clamp() formula - see the project's card-art corner-radius
   lesson (percentage/clamp() radii have silently failed to render on real
   elements in this app before; a flat px value is the fix that stuck). */
.email-signup-box-photo {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 8px;
  margin: 0 0 12px;
  display: block;
}
.email-signup-card h2 { margin: 0 0 4px; font-size: 1.3rem; }
.email-signup-card .note { color: var(--muted); font-size: 0.88rem; margin: 0 0 4px; }
.email-signup-card label {
  font-size: 0.85rem;
  color: var(--muted);
  margin-top: 10px;
  margin-bottom: 3px;
}
.email-signup-card input {
  width: 100%;
  padding: 8px 10px;
  border-radius: 6px;
  border: 1px solid #45414f;
  background: #100e15;
  color: var(--text);
  font-family: inherit;
  font-size: 0.92rem;
  margin: 0;
  box-sizing: border-box;
}
.email-signup-status { margin: 10px 0 0; font-size: 0.85rem; }
.email-signup-status.error { color: #ff7a70; }
.email-signup-status.success { color: #8fd694; }
.email-signup-status.hidden { display: none; }
.email-signup-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}
.email-signup-actions button { margin: 0; }
#email-signup-cancel { background: #33303e; }
/* Submit defaults to the app's base red button (var(--accent)) like every
   other button, but that reads as a warning color here - this is a positive
   sign-up action, not a report. Give it the same gold as its opening button
   instead, so the whole flow (button -> overlay -> submit) stays gold, not
   red, end to end. */
#email-signup-submit { background: var(--accent2); color: #14121a; }
#email-signup-submit:hover { filter: brightness(1.08); }

/* Room / player-count screen: shrink and center to match the lobby. */
#room-screen {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: safe center;
}
#room-screen > * {
  width: 100%;
  /* v5.0.69: was clamp(300px, 32vh, 560px) - same height-driven cap issue as
     #lobby-screen .card above (see its comment), switched to width-driven
     and the cap raised so the room code / seat list / total-players boxes
     also grow on a wide monitor instead of staying a fixed size regardless
     of window width. */
  max-width: clamp(300px, 18vw, 620px);
}
/* "Room" reads gold like the rest of the branding; the code itself stays the
   normal text color so it's the part that visually reads as "the data". */
/* v5.0.69: explicit 16px bottom margin (top left as the browser default,
   unrelated to this) - was an unset UA-default h2 margin (~17px at this
   screen's usual root font-size) stacking with .invite-row's own top
   margin to put more space above "Copy invite link" than the other gaps in
   this stack get. Matches the same 16px every other gap on this screen now
   uses.
   v5.0.72: top margin now explicit too (was left as the browser UA default,
   ~17-20px at this screen's font-size) - per request, to bring "Room CODE"
   closer to "Created by Mitch St.Peter" above it. Combined with the
   header's own 8px bottom padding and #room-screen's 8px top padding, the
   UA default was stacking into a noticeably bigger gap here than anywhere
   else on this screen; 4px brings the visual gap in line with the other
   16px-ish gaps in this stack without touching them. */
#room-screen h2 { color: var(--accent2); margin-top: 4px; margin-bottom: 16px; }
#room-code { color: var(--text); }

#game-screen.screen {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 6px 12px 8px;
  gap: 6px;
  overflow: hidden;
}

/* In-game panels (Play Area, Log, Players, Dungeon, Inventory, Identity) use a darker, more
   neutral gray than the lobby panels, so the game screen reads darker and moodier. Redefining
   --panel scoped to #game-screen cascades to every panel and the reaction/overlay cards inside
   it, without touching the lobby/menu. */
#game-screen { --panel: #16151b; }
.panel {
  background: var(--panel);
  border: 1px solid #33303e;
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 0;
}
.panel h3 { font-size: 0.95rem; margin: 0 0 6px; }
/* All pane titles ("Play Area", "Dungeon Rooms", "Your Inventory", "Log", etc.)
   in a subdued dark gray. */
.panel h3,
.panel-title,
.play-area-head h3,
.play-area h3,
.log-panel h3,
.hand-panel-head h3 { color: #6b6675; }

/* --- Two-column game grid: turn tracker + log on the left, everything
   else (identity, board, hand, turn controls) on the right. --- */
.game-grid {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  gap: 10px;
}
.game-left-col {
  flex: 0 0 clamp(250px, 25%, 600px);
  container-type: size; /* so player rows can scale to fit all 6 in the column height */
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 0;
}
.game-right-col {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 0;
}

/* --- Left column: compact turn tracker (fixed-ish height) + log filling
   the rest, both scrolling internally if needed. --- */
.players-panel {
  flex: 0 1 auto;
  min-height: 0;
  overflow-y: auto;       /* scrollable so a full 6-player table can show full-size cards... */
  scrollbar-width: none;  /* ...with no visible scrollbar */
  padding: 6px 10px;
}
.players-panel::-webkit-scrollbar { width: 0; }
.players-panel .player-row { padding: 7px 9px; font-size: 0.78rem; }
/* Player-info cards sized ~2x the old compact size so they're clearly readable,
   with larger names/HP. The battle log gives up the room (see .log-panel
   min-height) and the pane scrolls invisibly for a full 6-player table. */
.players-panel .player-identity-card { height: 72px; }
/* Maker-Only cards render at exactly the inventory-thumbnail size so the two
   sides of .player-row-bottom line up as one row of same-sized cards. */
.players-panel .player-perm-card { height: clamp(38px, 8cqh, 76px); width: auto; }
.players-panel .player-card-thumb { width: clamp(26px, 5.5cqh, 52px); height: clamp(38px, 8cqh, 76px); }
.players-panel .player-row-name { font-size: 1.15rem; }
.players-panel .player-hp { font-size: 1.05rem; }
.log-panel {
  flex: 1 1 0; /* fill the space LEFT after the player pane, based on that space — not on how many log lines exist */
  min-height: 3.1em; /* shrink down to just the title + the latest (highlighted) line; player cards keep their full size */
  overflow: hidden; /* the inner #log-list scrolls, not the panel */
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
}
.log-panel h3 { margin: 0 0 4px; font-size: 0.85rem; flex: 0 0 auto; cursor: pointer; }
/* Hovering the log expands it over the player-info panel so the full log is
   readable, then collapses back when the mouse leaves. */
.game-left-col { position: relative; }
#log-panel.log-expanded {
  position: absolute;
  inset: 0;
  z-index: 60;
  background: var(--panel);
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.75);
}
/* v5.0.69: margin is single-sided (bottom only) now, matching the same
   "one element owns the whole gap" convention used by .lobby-start-btn and
   .seat-list-card below - was `4px 0 10px` (top AND bottom), which stacked
   with the room-code h2's own margin-bottom to produce a bigger, uneven gap
   above this row than the ones below it. */
.invite-row { display: flex; align-items: center; gap: 8px; margin: 0 0 16px; flex-wrap: wrap; }
#log-panel.log-expanded #log-list { max-height: none; }
#log-list { max-height: none; flex: 1 1 auto; min-height: 0; overflow-y: auto; }
/* Hide the battle-log scrollbar until the pane is expanded (it stays scrollable). */
#log-panel, #log-list { scrollbar-width: none; }
#log-panel::-webkit-scrollbar, #log-list::-webkit-scrollbar { width: 0; }
#log-panel.log-expanded, #log-panel.log-expanded #log-list { scrollbar-width: thin; scrollbar-color: var(--accent2) rgba(255, 255, 255, 0.05); }
#log-panel.log-expanded::-webkit-scrollbar, #log-panel.log-expanded #log-list::-webkit-scrollbar { width: 8px; }
#log-panel::-webkit-scrollbar-thumb, #log-list::-webkit-scrollbar-thumb { background: linear-gradient(180deg, var(--accent2), var(--accent)); border-radius: 6px; }
#log-panel::-webkit-scrollbar-track, #log-list::-webkit-scrollbar-track { background: transparent; }

/* --- Right column top row: dice + board (the identity panel now lives in the
   bottom row instead). Given a real height budget rather than shrink-wrapping,
   so the board can actually scale up to use the space; the dice panel takes a
   fixed slice on the left and the board fills the rest. --- */
.game-top-row {
  flex: 0 0 56vh;
  min-height: 320px;
  display: flex;
  align-items: stretch;
  gap: 10px;
}

/* --- Game chat (PC): a compact bar showing the latest human chat message.
   position:fixed takes it OUT of .game-right-col's document flow entirely, so
   it can never bump .game-top-row (or anything else) down regardless of where
   #chat-bar actually lives in the DOM - it doesn't need to be a sibling of
   .game-top-row for its on-screen position to line up with anything. Instead,
   top/left/width are all set live by syncChatBarPosition() in app.js: left
   matches #dice-panel's own left edge, width matches the Play Area's width
   EXACTLY (direct request, v6.0.29 - see that function's comment for why the
   old narrower clamp against the in-game title/credit line's text is gone),
   and top vertically centers the bar against the shared <header>'s title+
   credit line group. (v6.0.24 briefly moved this down next to the Identity
   panel for easier phone reach, but that was reverted in v6.0.25 - that
   reposition was only ever meant for mobile's separate chat bubble, see
   mobile.css/mobile.js for .chat-bubble / positionMobileChatBubble().) ---- */
.chat-bar {
  position: fixed;
  z-index: 45;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 60px;
  padding: 7px 12px;
  background: var(--panel);
  border: 1px solid #33303e;
  border-radius: 8px;
  cursor: pointer;
  user-select: none;
}
.chat-bar:hover { border-color: #45414f; }
.chat-bar-icon { flex: 0 0 auto; font-size: 1rem; line-height: 1; }
.chat-bar-latest {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.85rem;
  color: var(--muted);
  text-align: left;
}
.chat-bar-latest.has-message { color: var(--text); }
/* PERSISTENT unread state (direct follow-up request - replaced an earlier
   one-shot flash-then-fade): the whole bar stays this faded yellow for as
   long as there's an unread message waiting, not just a brief flash. This
   was originally the SAME identical rgba as the mobile chat bubble's own
   .unread class; a later direct request ("less loud") softened the PC bar
   specifically, so the two have deliberately diverged - .chat-bubble.unread
   in this file keeps the original stronger values since the user scoped the
   complaint to PC. The new alpha (0.45 / 0.35) sits between that original
   0.85/0.6 and the 0.35 one-shot .chat-row-new/.log-highlight-fade peak used
   elsewhere in the app, so the bar still reads as clearly highlighted without
   the blaring solid-fill look. Cleared only by actually opening the chat
   (click or the T hotkey both route through openChatPanel() - see app.js),
   never a timer, so it can't fade back to normal while still genuinely
   unread. */
.chat-bar.unread {
  background: rgba(217, 164, 65, 0.45);
  box-shadow: 0 0 8px rgba(217, 164, 65, 0.35);
}
/* Right-aligned hotkey reminder. Originally shown only while the bar was
   unread; direct follow-up request made it a standing reminder instead - the
   closed/collapsed bar should always tell the player they can press T to open
   chat, not just when there's a new message waiting. .chat-bar-latest is
   flex:1 1 auto and always consumes the bar's full slack width (its own text
   is truncated with ellipsis instead of growing), so this sibling naturally
   lands flush against the bar's right edge rather than needing its own
   margin-left:auto. Muted by default so it doesn't compete with the actual
   message text; still brightens along with the rest of the bar via
   .chat-bar.unread's own color rules when a message is waiting. */
.chat-bar-hint {
  display: inline;
  flex: 0 0 auto;
  font-size: 0.72rem;
  color: var(--muted);
  white-space: nowrap;
}
.chat-bar.unread .chat-bar-hint {
  color: var(--text);
}

/* --- Game chat (expanded overlay): covers the Play Area exactly, the same
   inset:0-over-a-positioned-ancestor trick #log-panel.log-expanded uses for
   its own column (.play-area is already position:relative). Higher z-index
   than #overlay-panels (20) so it's always reachable, even mid-reaction.
   Direct request: semi-transparent (not solid var(--panel), which fully hid
   the board/dice/turn controls behind it) so a player actively chatting can
   still see the Play Area moving behind the panel and not miss anything -
   same rgba(--panel)+blur "frosted glass" treatment already used for the
   corner version-tag pill (see #app > header .version-tag above). Applies to
   BOTH platforms unchanged: mobile.css only overrides this rule's position/
   z-index/border-radius (to track #dice-panel's rect once reparented), never
   its background, so one change here covers PC and mobile alike. --- */
.chat-panel {
  position: absolute;
  inset: 0;
  z-index: 70;
  background: rgba(30, 27, 38, 0.82);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border-radius: 10px;
  box-shadow: 0 12px 36px rgba(0, 0, 0, 0.75);
  display: flex;
  flex-direction: column;
  padding: 10px 12px;
}
.chat-panel.hidden { display: none; }
.chat-panel-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 6px;
}
.chat-panel-head h3 { margin: 0; }
.chat-panel-close {
  flex: 0 0 auto;
  background: none;
  border: none;
  color: var(--muted);
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
  padding: 4px 6px;
}
.chat-panel-close:hover { color: var(--text); }
/* PC-only hotkey reminder (direct request) - hidden on mobile, see
   html.mobile .chat-panel-hint in mobile.css, since T/Escape are PC-only. */
.chat-panel-hint {
  flex: 0 0 auto;
  margin: 0 0 6px;
  font-size: 0.72rem;
  color: var(--muted);
}
/* MOBILE-only counterpart to .chat-panel-hint above (direct request) - same
   look, but hidden on desktop by default; mobile.css un-hides it (and hides
   the PC one in its place, mirroring the existing split). */
.chat-panel-hint-mobile {
  display: none;
  flex: 0 0 auto;
  margin: 0 0 6px;
  font-size: 0.72rem;
  color: var(--muted);
}
.chat-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-right: 4px;
  margin-bottom: 8px;
}
.chat-row { font-size: 0.85rem; line-height: 1.3; overflow-wrap: anywhere; }
.chat-row-self .chat-row-name { color: var(--accent2); }
.chat-row-name { font-weight: 600; margin-right: 4px; }
.chat-row-text { color: var(--text); }
/* Direct request: a message not seen since the player's last click into chat
   gets a one-shot highlight, the SAME faded yellow as the bar/bubble's
   persistent .unread and the Battle Log's log-new (identical rgba). Holds at
   full color for exactly 1 second (direct follow-up request - the original
   cut of this held full color for only 25% of a 1s total, so it read as
   barely-there/already-fading almost immediately), then fades out quickly
   over a short 0.3s tail. See renderChatList() for how "unseen" is determined
   (lastOpenedChatSeq, captured before it's advanced). No JS timer needed to
   remove the class - the animation just ends transparent and stays that way
   (animation-fill-mode: forwards). */
@keyframes chat-row-highlight-fade {
  0% { background: rgba(217, 164, 65, 0.35); }
  77% { background: rgba(217, 164, 65, 0.35); } /* ~1s of a 1.3s total */
  100% { background: transparent; }
}
.chat-row.chat-row-new {
  animation: chat-row-highlight-fade 1.3s ease-out forwards;
  border-radius: 4px;
  padding-left: 4px;
}
.chat-empty { color: var(--muted); font-size: 0.85rem; }
.chat-form {
  flex: 0 0 auto;
  display: flex;
  gap: 6px;
}
.chat-input {
  flex: 1 1 auto;
  min-width: 0;
  padding: 7px 10px;
  border-radius: 6px;
  border: 1px solid #45414f;
  background: var(--bg);
  color: var(--text);
  font-size: 0.85rem;
}
.chat-send-btn { flex: 0 0 auto; padding: 7px 14px; font-size: 0.85rem; }

/* --- Game chat (mobile bubble): hidden on desktop; mobile.css shows this and
   hides #chat-bar instead. Positioned absolute against .play-area (its
   nearest positioned ancestor - .play-area-scroll, the actual DOM parent on
   mobile, is itself position:static, so this resolves against the fixed
   .play-area box beneath it, unclipped - see skipIds in mobile.js). --- */
.chat-bubble {
  display: none;
}
.chat-bubble.unread {
  background: rgba(217, 164, 65, 0.85);
  box-shadow: 0 0 10px rgba(217, 164, 65, 0.6);
}

/* Mobile-only "Rules" quick-access button (v6.0.24) - hidden on desktop like
   .chat-bubble right above; mobile.css shows it in that button's old
   top-right corner spot. */
.mobile-rules-btn {
  display: none;
}

.identity-panel {
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* Shared reference height for the identity card art and the revealed
     Maker-Only cards (which render at 75% of it — see .identity-perm-card).
     Sized in container units so it scales with the identity PANE, not the monitor. */
  --id-card-h: clamp(80px, 64cqmin, 300px);
}
.identity-body {
  flex: 1 1 auto;
  min-height: 0;
  container-type: size; /* components below size to THIS pane via cq units */
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap; /* keep the dice + card on one row so they don't wrap and overflow the pane */
  align-items: center;
  justify-content: center;
  gap: clamp(6px, 3cqmin, 16px);
  overflow: hidden;
}
.panel-title { flex: 0 0 auto; text-align: left; margin: 0 0 6px; }
.identity-panel .identity-art {
  flex: 0 1 auto;
  min-width: 0;
  height: var(--id-card-h);
  max-height: 100%;
  max-width: 100%;
  width: auto;
  object-fit: contain;
  margin: 0;
}
/* Direct bug report: on mobile this card reads with "oddly large" corners.
   The shared .identity-art rule's border-radius is a fixed 22px, calibrated
   for its ~160px typical max-width elsewhere - but in THIS pane the card
   scales down with --id-card-h (clamp(80px, 64cqmin, 300px)), and a phone's
   cramped identity-body row (shared with the health/attack dice, all on one
   forced-nowrap line) routinely sits near that 80px floor, i.e. ~53px wide
   at the identity art's native 2:3 aspect. 22px on a ~53px-wide card is
   ~40% of its width - way past the ~14-15% every other card-art context in
   the app targets (see .player-identity-card's 8px/54px-height pairing, the
   closest reference point at a similar small size). A fixed px override
   (not a %/clamp() formula - see .identity-art's own comment for why that
   fails to even RENDER rounded on a real device) sized for this context's
   much smaller mobile footprint. */
html.mobile .identity-panel .identity-art { border-radius: 10px; }
.identity-stats {
  display: contents; /* let the health die, attack die and identity art all be equal-gap peers of .identity-body */
}
.stat-die-wrap { display: flex; flex-direction: column; align-items: center; flex: 0 1 auto; min-width: 0; }
.stat-die { width: clamp(44px, 38cqmin, 180px); height: clamp(44px, 38cqmin, 180px); max-width: 100%; display: block; }
.stat-die-label { font-size: clamp(0.62rem, 4cqmin, 1rem); color: var(--muted); margin-top: 4px; }
.maker-true-hp { color: var(--accent2); font-weight: 600; }

.board-panel {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 200px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.board-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: row;
  gap: 14px;
  align-items: stretch;
  overflow: hidden;
}
.board-square-wrap {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.board-square-wrap .board-svg {
  display: block;
  max-width: 100%;
  max-height: 100%;
}
.board-side-col {
  flex: 0 0 136px;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: safe center;
  overflow-y: auto;
  gap: 6px;
}
.board-panel .board-meta { font-size: 0.72rem; color: var(--muted); text-align: center; }
.deck-col { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.deck-stack { display: flex; flex-direction: column; align-items: center; }
.deck-back { width: auto; height: var(--deck-h, 120px); max-width: 100%; border-radius: 8px; border: 1px solid #45414f; display: block; }
/* v5.0.76: larger, all-caps by request - was 0.62/0.64rem, normal weight,
   mixed case. text-transform handles the caps so the underlying "Loot: N" /
   "Event: N" / "Discard: N" strings (and their JS templates) don't need to
   change. v5.0.76 also bumped font-weight to 800 for visual weight at this
   size, but that read as too bold in practice - reverted to normal weight
   (v5.0.81) while keeping the larger size/caps/letter-spacing from that round.
   v5.1: shrunk ~20% again (direct request) - one shared rule, so this covers
   both PC and mobile with no separate override needed. */
.deck-count { font-size: 0.72rem; font-weight: 400; letter-spacing: 0.02em; text-transform: uppercase; color: var(--muted); margin-top: 2px; text-align: center; }
.deck-count-text { font-size: 0.74rem; font-weight: 400; letter-spacing: 0.02em; text-transform: uppercase; color: var(--muted); text-align: center; }
.deck-gap-before { margin-top: 4px; padding-top: 3px; border-top: 1px solid #33303e; }

/* --- Hand: single row, 3 face-up slots then 2 face-down slots, all the
   same size — matches MAX_FACE_UP/MAX_FACE_DOWN so it never wraps. --- */
/* --- Hand: single row, 3 face-up slots then 2 face-down slots, all the
   same size — matches MAX_FACE_UP/MAX_FACE_DOWN so it never wraps. Fixed
   max-height + height-capped art keeps this from ballooning to fill the
   screen (card art is portrait-oriented, so width-only capping let the
   browser blow the row up to the images' full natural height). --- */
/* Middle row: Your Inventory (left, ~half) and Identity (right, ~half). Given
   a generous height (the Your Turn window needs less now that Attack lives in
   its own column) so every card's art, text, and buttons show without any
   scrolling. */
.game-mid-row {
  flex: 1 1 auto;
  min-height: 175px;
  display: flex;
  gap: 8px;
  align-items: stretch;
}
#hand-panel {
  flex: 2 1 0;
  min-width: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.hand-panel-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  margin-bottom: 4px;
}
.hand-panel-head h3 { margin: 0; }
.hand-panel-head button { margin: 0; padding: 2px 9px; font-size: 0.7rem; }
/* The inventory status strip (health-bar mirror + attack-dice xN) is a MOBILE-only affordance;
   on desktop the Identity pane already shows both, so it stays hidden here. */
.inv-status { display: none; }
#hand-panel .hand-row { flex: 1 1 auto; min-height: 0; }
.hand-row {
  display: flex;
  gap: 5px;
  align-items: stretch;
}
.hand-row .hand-card {
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
  margin: 0;
  padding: 4px 5px;
  font-size: 0.64rem;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center; /* center the (non-stretched) art + title in the card */
  container-type: inline-size; /* so card art corners can scale to the card's width */
}
.hand-row .hand-card > strong,
.hand-row .hand-card > small,
.hand-row .hand-card > button,
.hand-row .hand-card > .note { flex: 0 0 auto; }
/* The Flip Up / Flip Down toggle sits ABOVE the card art — centered in the card,
   with a small gap below it. The "Use Heal" button sits just under it (still above
   the art), also centered. Both lead the flex column, ahead of the art. */
.hand-row .hand-card > .rearrange-btn,
.hand-row .hand-card > .heal-btn { align-self: center; margin: 0 0 4px; }
/* The poisoned Heal/Cleric Staff tap target (replaces the old permanent "Heal
   blocked (Dart Gun)" note - see showPoisonBlockedToast in app.js) reads as
   dimmed/inert rather than the real action button's vivid accent color, so it
   doesn't look like a working "Use Heal" button that just happens to do nothing
   when pressed - tapping it still explains why, via the small popup. */
.heal-btn-blocked {
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-dim, #a39ec0);
  font-weight: 500;
}
.heal-btn-blocked:hover { filter: brightness(1.3); }
.hand-card-title {
  font-size: 0.95rem;
  line-height: 1.1;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  text-align: center;
  display: block;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin: 2px 0;
}
#hand-panel.hide-rearrange .rearrange-btn { display: none !important; }
.hand-row .hand-card.face-down-slot { border-left: 2px solid #5c5468; }
/* A dead player's "Your Inventory" pane holds the Spectating banner + Leave Room
   button (the Play Area stays free for the live game). Centred in the empty pane. */
.spectating-pane {
  position: relative;
  flex: 1 1 100%;
  min-width: 0;
  margin: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  text-align: center;
  padding: 12px 16px;
  /* A solid card so it stays readable even where the inventory panel itself is
     transparent (mobile) and other content sits behind it. */
  background: var(--panel, #17141d);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 10px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
}
.spectating-pane .prompt-command { margin: 0; }
.spectating-pane .prompt-hint { margin: 0; max-width: 52ch; }
.spectating-pane .btn-grey { margin-top: 4px; }
/* The collapse (✕) button — small, corner-anchored, doesn't compete with the
   Leave Room button for attention. Mobile-only: on mobile this card sits over the
   precious, always-visible frozen "Your Inventory" strip, so collapsing it to
   free that space matters there. On desktop the inventory panel has plenty of
   room regardless, so the collapse control is hidden entirely (see mobile.css
   for the override that shows it back on mobile). */
.spectating-pane .spectating-close {
  display: none;
  position: absolute;
  /* Explicit margin reset: the generic `button` rule's `margin-right: 8px;
     margin-top: 4px` still applies here (this rule never touched margin), and
     since this button is position:absolute, that margin shifts it away from
     its own top/right offset - pushing the X noticeably off the actual corner
     instead of sitting flush in it. */
  margin: 0;
  top: 4px;
  right: 4px;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  color: var(--text-dim, #a39ec0);
  font-size: 0.7rem;
  line-height: 1;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.spectating-pane .spectating-close:hover { background: rgba(255, 255, 255, 0.16); color: var(--text, #eee); }
/* Collapsed: just the (smaller) Leave Room button — no title, no hint text, no
   reopen control (dismissing this card is one-way, see deadSpectatingPaneHtml),
   minimal padding, so it reads as a compact bar rather than a dialogue box. */
.spectating-pane.collapsed {
  flex-direction: row;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 6px 10px;
}
.spectating-pane.collapsed .btn-grey {
  margin-top: 0;
  margin-right: 0;
  /* height: 1lh's worth of line box + this padding only - no extra min-height
     anywhere else applies here, so the button is exactly as tall as its own
     text needs, not the full-size Leave Room button's stature. */
  padding: 4px 12px;
  font-size: 0.78rem;
  line-height: 1.2;
}
/* Inventory cards awaiting a forced choice (discard / facing fix / swap) — tap
   to pick. These prompts block the turn, so the tappable cards are deliberately
   loud: a hard bright ring hugging the border (reads on any card-type colour,
   including red Action cards), then two softer halos behind it. The halo is kept
   under ~40px because #hand-panel is `overflow: hidden` and would clip a wider
   one on the row's end cards. */
.hand-card.needs-selection {
  position: relative;
  z-index: 3;
  border: 2px solid #ff8a8a !important;
  cursor: pointer;
  animation: pulse-red 1.15s ease-in-out infinite;
}
.hand-card.needs-selection:hover {
  filter: brightness(1.25);
  animation-play-state: paused;
  box-shadow:
    0 0 0 3px #ffdcdc,
    0 0 22px 4px rgba(255, 120, 120, 0.95),
    0 0 44px 10px rgba(255, 80, 80, 0.45) !important;
}
/* Cards you *can't* tap recede, so the glow has something to glow against.
   Without this a red Action card camouflages a red halo. */
.hand-row:has(.needs-selection) .hand-card:not(.needs-selection) {
  opacity: 0.4;
  filter: grayscale(0.65) brightness(0.75);
  transition: opacity 0.2s ease, filter 0.2s ease;
}
/* The card being swapped (the swap SOURCE) is greyed out too — it's not a tap
   target, so it recedes with the other non-selectable cards rather than drawing a
   bright ring. It keeps the .swap-source class only so JS/tests can find it. */
.select-hint {
  display: block;
  width: fit-content;
  max-width: 100%;
  margin: 3px auto 0;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(224, 85, 85, 0.92);
  color: #fff3f3;
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.swap-source .select-hint { background: rgba(217, 164, 65, 0.92); color: #1a1408; }
/* The small "NEW" banner on whichever card just pushed the player over the 5-card hand
   limit (see newCardId in presentGame / .new-card in renderHandCard's discard branch,
   app.js) - a distinct gold pill (not the discard prompt's own red select-hint color,
   so it doesn't read as another selection affordance) sitting above the card, same
   slot as a Flip/Use button would use. */
/* Desktop-sized larger than the original 0.62rem/1px 7px (request: easier to spot at
   a glance on a big screen where the card itself renders much bigger than on mobile).
   mobile.css pins this back down to the original small size for the phone layout,
   where the compact hand-card has far less room to spare. */
.new-card-banner {
  display: block;
  width: fit-content;
  max-width: 100%;
  margin: 0 auto 4px;
  padding: 3px 11px;
  border-radius: 999px;
  background: var(--accent2);
  color: #1a1408;
  font-size: 0.95rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  white-space: nowrap;
}
/* A slight highlight ring on the card itself too, so it stands out even before reading
   the banner text - subtle enough not to compete with the red "needs-selection" glow
   every discard-prompt card already carries. */
.hand-card.new-card { box-shadow: 0 0 0 2px var(--accent2), 0 0 10px rgba(217, 164, 65, 0.5); }
/* On the Attack screen a not-yet-armed weapon's "Click to add a die" hint sits ABOVE
   the art (in the flip-button slot), matching where the target dropdown sits on an
   armed weapon. Applies on BOTH desktop and mobile. */
.assignable-weapon .select-hint { order: -1; margin: 0 auto 4px; text-align: center; }
@keyframes pulse-red {
  0%, 100% {
    box-shadow:
      0 0 0 1px rgba(255, 170, 170, 0.45),
      0 0 10px 1px rgba(224, 85, 85, 0.6),
      0 0 24px 4px rgba(224, 85, 85, 0.25);
  }
  50% {
    box-shadow:
      0 0 0 3px rgba(255, 214, 214, 0.95),
      0 0 20px 3px rgba(255, 106, 106, 0.95),
      0 0 40px 9px rgba(255, 90, 90, 0.45);
  }
}
/* A weapon that can currently take an unassigned attack die pulses a soft GREEN glow -
   the same "notice me" pulse-red gets for a card needing a required action
   (needs-selection), just green because this one is an invitation ("you could tap
   this") rather than a demand ("you must tap this"). Applied in renderHandCard
   whenever canAssignDie is true AND at least one die is still unassigned in the pool
   (see awaitingDieClass/attackDiceRemaining) - stops the instant the pool empties. */
.weapon-awaiting-die {
  animation: pulse-green 1.15s ease-in-out infinite;
}
@keyframes pulse-green {
  0%, 100% {
    box-shadow:
      0 0 0 1px rgba(170, 255, 190, 0.45),
      0 0 10px 1px rgba(85, 200, 120, 0.6),
      0 0 24px 4px rgba(85, 200, 120, 0.25);
  }
  50% {
    box-shadow:
      0 0 0 3px rgba(214, 255, 224, 0.95),
      0 0 20px 3px rgba(106, 220, 140, 0.95),
      0 0 40px 9px rgba(90, 210, 130, 0.45);
  }
}
@media (prefers-reduced-motion: reduce) {
  .hand-card.needs-selection {
    animation: none;
    box-shadow:
      0 0 0 3px rgba(255, 214, 214, 0.9),
      0 0 22px 4px rgba(255, 106, 106, 0.85);
  }
  .weapon-awaiting-die {
    animation: none;
    box-shadow:
      0 0 0 3px rgba(214, 255, 224, 0.9),
      0 0 22px 4px rgba(106, 220, 140, 0.85);
  }
}
.hand-row .card-art {
  flex: 0 1 auto; /* don't stretch taller than the image, or object-fit letterboxes it and the rounded corners land on the transparent box instead of the art */
  min-height: 0;
  max-width: 100%;
  width: auto;
  height: auto;
  max-height: 100%; /* scale to the card (pane), not the viewport */
  object-fit: contain;
  margin: 0 auto 3px;
  border-radius: 18px; /* fixed px (see .identity-art's comment for why not a %/clamp() formula) - sized to match this card's own typical rendered width */
}
/* Card name scales with the card's own width so it always fits (never clipped/overflowing). */
.hand-row .hand-card > strong {
  display: block;
  font-size: clamp(0.5rem, 13cqw, 0.92rem);
  line-height: 1.1;
  overflow-wrap: anywhere;
  flex: 0 0 auto;
}

/* Permanent Maker-Only cards: shown small in the Identity pane (for the Maker)
   and under a revealed Maker's name in the player-info pane, both with the
   shared hover magnifier. */
.identity-permanents {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
  justify-content: center;
  min-width: 0;
}
/* Maker-Only cards shown in the Identity pane after the Maker reveals: sized
   to 75% of the identity card (they share --id-card-h with .identity-art). */
.identity-perm-card {
  height: calc(var(--id-card-h) * 0.75);
  max-height: 100%;
  width: auto;
  /* Proportional (~15% of the card's own box), matching every other card
     representation in the app. */
  border-radius: 16px;
  border: 1px solid #45414f;
  display: block;
}
.player-row-top { display: flex; align-items: flex-start; gap: 8px; }
.player-row-main { flex: 1 1 auto; min-width: 0; }
.player-identity-card {
  height: 54px;
  width: auto;
  /* Unified card-corner formula — the identity card for the player looks
     exactly the same, corner-wise, as inventory card art, its hover-magnify
     preview, and the small player-row card thumbnails. */
  border-radius: 8px;
  border: 1px solid #45414f;
  display: block;
  flex: 0 0 auto; /* revealed identity card, to the left of name/HP */
}
.player-perm-cards {
  display: flex;
  flex-wrap: wrap;
  gap: 3px;
  justify-content: flex-start;
  margin-top: 4px;   /* sits UNDER the name/HP text, left-aligned */
}
.player-perm-card {
  height: 52px;
  width: auto;
  flex: 0 0 auto;   /* keep intrinsic width so a 3rd card isn't
                       squished to a sliver when the inventory thumbs compete for the
                       narrow player-pane width, hiding the Maker's 3rd Maker-Only card */
  /* Unified card-corner formula — same reasoning as .player-identity-card above. */
  border-radius: 8px;
  border: 1px solid #45414f;
  display: block;
}

/* Bottom row: the Your Turn window, expanded to fill the freed space with a
   two-column body (actions left, attack builder right) and End Turn pinned at
   the bottom, so nothing needs scrolling in the common case. */
.game-bottom-row {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  gap: 8px;
}
#turn-controls {
  position: relative;
  flex: 0 0 auto;
  max-height: 50%;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;   /* everything in the play area is centered (prompts, buttons, the Power toggle, "No more actions…", Cancel) */
  text-align: center;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 0 16px;       /* side margins so long prompt text wraps inside the pane instead of running edge-to-edge */
}
/* When an Action-card form (Destroy / Pickpocket) is open, End Turn leaves its
   bottom-pinned (margin-top:auto) slot and flows AFTER the form so the two can never
   land on top of each other; the box just grows and scrolls. */
#turn-controls.form-open .end-turn-btn { margin-top: 12px; }
/* An OBVIOUS "there's more below - scroll" cue for the height-capped, scrolling
   controls box: a fade + a bouncing chevron stuck to the bottom edge, shown only
   while there's content below the fold (JS toggles is-scrollable / at-scroll-bottom
   in updateTurnScrollCue). The negative top margin overlays the cue on the last strip
   of content so the cue itself never adds scroll height.
   v5.0.75: was a SINGLE ::after carrying both the fade background AND the "⌄" glyph,
   with the bounce animation on that one element - so the whole grey fade block visibly
   bounced along with the arrow, per request. Split into two pseudo-elements sharing the
   same sticky-bottom position: ::before is now the static fade background (no
   animation, so the block itself never moves) and ::after is just the arrow glyph,
   which keeps the bounce. Both boxes also shrank from a flat 24px to 18px - "only
   thick enough to contain the full range of the down arrow movement" - the arrow's
   own 14px line-height plus its 2px translateY bounce only needs about 18px of
   headroom, not the old 24px sized back when the fade and the arrow were one box. */
#turn-controls.is-scrollable::before {
  content: "";
  position: sticky;
  bottom: 0;
  align-self: stretch;
  flex: 0 0 auto;
  display: block;
  height: 18px;
  margin-top: -18px;
  pointer-events: none;
  background: linear-gradient(to top, var(--panel) 30%, rgba(20, 17, 26, 0));
  opacity: 1;
  transition: opacity 0.2s ease;
}
#turn-controls.is-scrollable::after {
  content: "⌄";
  position: sticky;
  bottom: 0;
  align-self: stretch;
  flex: 0 0 auto;
  height: 18px;
  line-height: 14px;
  margin-top: -18px;
  text-align: center;
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--accent2);
  pointer-events: none;
  opacity: 1;
  transition: opacity 0.2s ease;
  animation: turn-scroll-nudge 1.4s ease-in-out infinite;
}
#turn-controls.at-scroll-bottom::before,
#turn-controls.at-scroll-bottom::after { opacity: 0; }
#turn-controls.at-scroll-bottom::after { animation: none; }
@keyframes turn-scroll-nudge {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(2px); }
}
/* Discard / facing-fix button lists: 3 across, wrapping to new rows, so a long
   list never runs off the bottom of the pane. */
.discard-grid {
  width: 100%;
  align-self: stretch;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
  margin-top: 4px;
}
.discard-grid button { margin: 0; width: 100%; }
/* Your Turn flow: a row with the action buttons on the left (Attack rightmost
   among them) and the dice-allocation / target settings filling the blank space
   to the right — these stay on the right and never wrap below the buttons. End
   Turn is pinned to the bottom. */
/* Selection-screen turn actions: a grid where the buttons flex to fill the
   width and wrap to new rows, so every button stays visible and reasonably
   sized no matter how many there are. */
.turn-grid {
  align-self: stretch;
  width: 100%;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 6px;
  margin: 6px 0;
}
.turn-grid button {
  margin: 0;
  width: 100%;
  min-height: 42px;
  white-space: normal;
}
/* PC: the "Your Turn" action buttons (Draw / Move / Play <Action> / Attack) are
   noticeably TALLER and LARGER-FONT than the End Turn button, which stays a quieter
   secondary. Mobile keeps its own compact sizing. */
html:not(.mobile) .turn-grid button {
  min-height: 58px;
  font-size: 1.08rem;
  font-weight: 700;
}
html:not(.mobile) #turn-controls .end-turn-btn:not(.end-turn-btn-tall) {
  min-height: 38px;
  font-size: 0.85rem;
  font-weight: 600;
  padding: 8px 16px;
}
#attack-builder, #action-builder { align-self: stretch; width: 100%; min-width: 0; }
/* Hovering the faded-out (disabled) Attack button flashes the "assign your
   dice" message white, then fades it back — a nudge toward what's blocking the
   attack. Class-driven (added on mouseenter, removed on animationend) so the
   flash always plays to completion even if the pointer leaves mid-animation. */
#attack-builder p.assign-note-flashing {
  animation: assign-note-flash 1s ease-out;
}
@keyframes assign-note-flash {
  0% { color: #ffffff; text-shadow: 0 0 10px rgba(255, 255, 255, 0.9); }
  55% { color: #ffffff; text-shadow: 0 0 10px rgba(255, 255, 255, 0.55); }
  100% { color: inherit; text-shadow: none; }
}
#attack-builder .note { text-align: center; margin: 0 auto 4px; }
#attack-builder .weapon-rows { max-width: 100%; }
#attack-builder .weapon-row { justify-content: flex-start; flex-wrap: wrap; max-width: 100%; min-width: 0; }
#attack-builder .weapon-row-name { white-space: normal; overflow-wrap: anywhere; }
#attack-builder .weapon-row select { max-width: 100%; }
#attack-builder .power-toggle { justify-content: center; }
.free-trapped-row { align-self: stretch; width: 100%; display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 6px; }
.free-trapped-row button { margin: 0; width: 100%; }
#turn-controls .end-turn-btn { flex: 0 0 auto; align-self: stretch; width: 100%; margin-top: auto; }

/* Weapon cards you can click to assign a die. */
.hand-card.has-assign { position: relative; }
/* "Heal blocked (Dart Gun)", "can't go face-down (Exposed)" and friends. */
.hand-card .note { display: block; width: 100%; text-align: center; line-height: 1.25; }
/* The "can't go face-down (Exposed)" hint sits at the top of every card while Exposed — give
   it breathing room so it isn't crammed against the card border and the art below it. */
.hand-card .flip-blocked-note { padding: 4px 6px; margin: 2px 0 8px; line-height: 1.35; }
.weapon-target-overlay {
  position: absolute;
  top: 4px;
  left: 38px;
  right: 4px;
  z-index: 3;
}
.weapon-target-overlay select {
  width: 100%;
  font-size: 0.65rem;
  padding: 2px 4px;
  border-radius: 4px;
  background: rgba(16, 14, 21, 0.94);
  color: var(--text);
  border: 1px solid var(--accent2);
}
.assignable-weapon { cursor: pointer; }
.assignable-weapon:hover { outline: 2px solid var(--accent2); outline-offset: -2px; }
.weapon-dice-badge {
  position: static;
  z-index: 2;
  width: 34px;
  height: 34px;
  flex: 0 0 34px;
  padding: 0;
  border-radius: 6px;
  background: transparent;
  cursor: pointer;
}
.weapon-dice-badge .badge-die { width: 100%; height: 100%; display: block; }

/* --- Weapon dice assignment -------------------------------------------------
   Dice are assigned by clicking a face-up weapon's ART (both desktop and mobile) -
   there is no separate "die +" button. Once a weapon holds dice it turns into a
   controls tile - count, target dropdown, "＋ Add die", "Clear dice" - that keeps
   the SAME footprint as a normal card, so the card area doesn't change size when you
   start assigning. On DESKTOP the weapon art fills the tile as an opaque backdrop
   and the controls float OVER it; on MOBILE the art is hidden and controls stack. */

/* Same flex sizing as a normal card, so the weapon card doesn't shrink/grow when it
   flips into the assigning tile. It's a flex column like a normal card (centred), so
   the title LEADS at the top - aligned with the neighbours' "Flip" buttons - and the
   art+controls slot fills below it, in line with the neighbours' art. */
.hand-row .hand-card.has-dice {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* On PC the weapon title is HIDDEN on the dice-assignment tile (the card art already
   shows the name, so the extra label looked out of place). The element is KEPT as an
   invisible spacer band, though: it holds the same top slot the neighbours' "Flip"
   button holds (~35px tall + an 11px top gap to match their centred layout), so the
   art slot below still starts on the neighbours' art line. Mobile re-shows it (there
   the art is hidden, so the label is the only name). */
.weapon-assign-title {
  order: 0;
  flex: 0 0 auto;
  align-self: center;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 35px;
  margin: 11px 0 4px;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 0 6px rgba(0, 0, 0, 0.8);
  visibility: hidden;       /* PC: spacer only - text hidden (mobile re-shows it) */
}
.hand-row .hand-card.has-dice .hand-card-title { display: flex; }
/* The art + controls occupy the slot BELOW the title, filling the rest of the tile
   so the backdrop art lines up with the neighbours' art. */
.weapon-assign-slot {
  order: 1;
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
}
/* The weapon art fills the slot as an opaque backdrop; hovering it previews the
   full card. Hidden on mobile (see mobile.css). */
.weapon-assign-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  margin: 0;
  z-index: 0;
  border-radius: 10px;
}
.weapon-assign-compact {
  position: relative;
  z-index: 1;               /* float the controls OVER the art */
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 100%;
  height: 100%;
  padding: 8px 6px;
  border-radius: 10px;
}
/* ON YOUR TURN: the controls sit at the TOP of the tile, sized to the controls
   (flex:0 0 auto) - NOT the whole tile - so the weapon art shows at FULL size below
   them. There is NO backdrop veil behind the controls; the (solid) buttons carry their
   own fill, and the weapon art shows in the gaps around them. */
.weapon-assign-compact.assigning {
  flex: 0 0 auto;
  height: auto;
  justify-content: flex-start;
  gap: 4px;
  padding: 4px 8px 6px;
  background: none;
}
/* A drop shadow lifts a weapon that's carrying remembered dice off the inventory row
   while it's NOT your turn, so the "armed" weapon(s) stand out at a glance between
   turns - even though the die itself isn't drawn until your turn. The weapon renders
   as a normal card here, so this is keyed on `.dice-resting` alone. Raised above the
   neighbours so the shadow isn't clipped by them. */
.hand-row .hand-card.dice-resting {
  position: relative;
  z-index: 2;
  box-shadow: 0 5px 14px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(0, 0, 0, 0.35);
}
/* Narrow the controls so they sit WITHIN the card art (not out to the tile edge), and
   make them taller / easier to hit. Only in the on-turn assigning state. */
.weapon-assign-compact.assigning .weapon-assign-btns,
.weapon-assign-compact.assigning .weapon-target-select { width: 80%; }
.weapon-assign-compact.assigning .weapon-assign-btns { gap: 3px; }
/* Compact buttons + dropdown (and a tighter cluster above) so the controls take less
   vertical room - leaving the card's damage tiers (dice pips + damage) visible on every
   weapon during dice assignment. */
.weapon-assign-compact.assigning .weapon-add-die { padding: 6px 4px; font-size: 0.72rem; }
.weapon-assign-compact.assigning .weapon-clear-dice { padding: 5px 4px; font-size: 0.66rem; }
.weapon-assign-compact.assigning select { padding: 5px 8px; font-size: 0.72rem; }
/* A light shadow fade behind each control lifts it off the (veil-less) weapon art -
   the dice pip, the "xN" multiplier, both buttons, and the target dropdown. */
.weapon-assign-compact.assigning .weapon-dice-badge.inline,
.weapon-assign-compact.assigning .weapon-add-die,
.weapon-assign-compact.assigning .weapon-clear-dice {
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.75);
}
.weapon-assign-compact.assigning .weapon-target-select {
  box-shadow: 0 0 0 2px rgba(224, 138, 60, 0.18), 0 6px 20px rgba(0, 0, 0, 0.75);
}
.weapon-assign-compact.assigning .weapon-dice-mult {
  text-shadow: 0 3px 11px rgba(0, 0, 0, 0.95);
}
.weapon-assign-btns { display: flex; flex-direction: column; gap: 4px; width: 100%; }
.weapon-add-die {
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 6px 4px;
  font-size: 0.72rem;
  font-weight: 700;
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  border: 1px solid #b0453e;
  cursor: pointer;
}
.weapon-add-die:hover { filter: brightness(1.08); }
.weapon-add-die .wadb-die { width: 14px; height: 14px; }

.weapon-dice-badge.inline {
  position: static;
  top: auto;
  left: auto;
  flex: 0 0 auto;
  width: 50px;
  height: 50px;
  box-shadow: none;
}
/* A single die badge shows the pip count, with a "×N" multiplier beside it when
   more than one die is assigned. Keep them together on one centred row. */
.weapon-dice-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
}
.weapon-dice-mult {
  font-weight: 700;
  font-size: 1rem;
  color: var(--accent2, #c8a24a);
  line-height: 1;
}
/* Per-weapon "Clear dice" button (brings the card art back). */
.weapon-clear-dice {
  width: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 5px 4px;
  font-size: 0.66rem;
  line-height: 1.1;
  min-height: 0;
  /* Solid fill so the button reads cleanly over the weapon art (no backdrop veil). */
  background: rgb(58, 52, 74);
  border: 1px solid rgba(255, 255, 255, 0.28);
  color: #f0edf5;
  border-radius: 6px;
  cursor: pointer;
}
.weapon-clear-dice:hover { filter: brightness(1.1); }

/* === Attack screen (v4.1) ================================================== */
/* PC: the assigned dice sit ON the weapon card art, centred, wrapping after ~3.
   Clicking the card adds a die; clicking a die removes it; the target dropdown sits
   under the dice. */
.hand-row .hand-card.attack-on-art {
  position: relative;
  cursor: pointer;
  overflow: visible;   /* let the enlarged dice overlay pop past the card edge, unclipped */
}
/* The art is the SAME contained image as a weapon with no dice — it must NOT enlarge
   or shrink when dice go on/off. It keeps the normal .hand-row .card-art sizing
   (object-fit: contain); the dice ride on top as an overlay. */
.attack-art-wrap {
  position: relative;
  flex: 0 1 auto;
  min-height: 0;
  max-width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 4px;
}
.attack-art-wrap .card-art { margin: 0; }
/* Dice overlay: centered on the art, wrapping after ~3, never resizing the art. */
.assigned-dice-cluster {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-content: center;
  gap: 6px;
  /* The cluster spans most of the art width and caps out, so the dice wrap after ~3
     and the group scales with the card without running off it. */
  width: 88%;
  max-width: 240px;
  pointer-events: none;
  filter: drop-shadow(0 1px 4px rgba(0,0,0,0.55)); /* lift the dice off the art */
}
.assigned-dice-cluster > * { pointer-events: auto; }
.assigned-die {
  /* Scale with the card art (cqw, via .hand-card's container-type) but STAY roughly the
     size of the play-area pool dice (76px): a clamp caps the growth on wide cards and
     floors it on narrow ones, so a die is never bigger than a pool token. */
  width: clamp(44px, 40cqw, 76px);
  height: clamp(44px, 40cqw, 76px);
  cursor: pointer;
  filter: drop-shadow(0 2px 5px rgba(0,0,0,0.7));
  transition: transform 0.1s ease;
}
.assigned-die:hover { transform: scale(1.14); }
/* PC drag-BETWEEN-weapons: an already-placed die is grabbable too. */
.assigned-die { cursor: grab; }
.assigned-die:active { cursor: grabbing; }
/* The die being dragged off a weapon leaves a dimmed placeholder, same treatment as
   a pool die mid-drag, so the "lifted" one reads clearly against the ones staying put. */
.assigned-die-dragging { opacity: 0.28; filter: grayscale(0.7); transform: scale(0.82); }
/* While ANY die (pool or on-weapon) is mid-drag, quiet the other on-weapon dice a touch
   so the eye follows the one actually moving — mirrors the pool's own dimming. */
body.dragging-attack-die .assigned-die:not(.assigned-die-dragging) { opacity: 0.75; }
body.dragging-attack-die .assigned-die:hover { transform: none; } /* no hover pop mid-drag */
.assigned-die .pool-die-svg { width: 100%; height: 100%; display: block; }
.assigned-die .pool-die-svg rect { fill: #ffffff; }   /* white die pops on the parchment art */
.assigned-die-more {
  font-weight: 800; font-size: 0.95rem; color: #fff;
  text-shadow: 0 1px 3px #000; align-self: center;
}
/* On the Attack screen the target dropdown sits ABOVE the art, in the same slot the
   Flip buttons occupy on adjacent cards — so the art below it lines up (and stays the
   exact same size) as every neighbouring card. It leads the flex column and matches
   the flip button's box (align + bottom gap). */
.attack-on-art .weapon-target-select {
  flex: 0 0 auto;
  align-self: center;
  order: -1;                 /* lead the card, ahead of the art */
  width: 90%; max-width: 150px; box-sizing: border-box;
  margin: 0 0 4px;
  min-height: 34px;          /* match the flip button's height so the art lines up */
  background: rgb(34, 30, 46);
  color: #f4f1f8;
  border: 2px solid var(--accent2);
  border-radius: 5px;
  padding: 6px 8px;
  font-size: 0.72rem; font-weight: 600;
  text-align: center;
  text-align-last: center;   /* centre the selected player's name in the closed select */
  box-shadow: 0 2px 6px rgba(0,0,0,0.45);
}
.attack-on-art .weapon-target-select option { text-align: center; }

/* The dice POOL in the play area: one token per available die. Left-to-assign stay
   solid; already-placed ones fade. */
.attack-dice-pool {
  display: flex; flex-wrap: wrap;
  justify-content: center; align-items: center;
  gap: 7px;
  margin: 8px 0 12px;
  width: 100%;
}
.pool-die { width: 76px; height: 76px; display: inline-block; flex: 0 0 auto; }
.pool-die .pool-die-svg { width: 100%; height: 100%; display: block; }
.pool-die-used { opacity: 0.3; }   /* an assigned die fades out of the pool */
/* PC drag-to-assign: a still-to-place die can be dragged onto a weapon. */
.pool-die-draggable { cursor: grab; transition: transform 0.1s ease, filter 0.1s ease; }
.pool-die-draggable:hover { transform: translateY(-2px) scale(1.06); filter: drop-shadow(0 3px 5px rgba(0,0,0,0.35)); }
.pool-die-draggable:active { cursor: grabbing; }
/* The die being dragged leaves a clean, dimmed "empty slot" placeholder in the pool
   (a faint dashed ring) rather than a half-there token. */
.pool-die-dragging { opacity: 0.28; filter: grayscale(0.7); }
.pool-die-dragging .pool-die-svg { transform: scale(0.82); transition: transform 0.12s ease; }
/* The crisp standalone die that follows the cursor mid-drag (set as the drag image). */
.pool-die-ghost {
  position: fixed;
  top: -1000px; left: -1000px;
  width: 66px; height: 66px;
  pointer-events: none;
  filter: drop-shadow(0 5px 9px rgba(0, 0, 0, 0.55));
}
.pool-die-ghost .pool-die-svg { width: 100%; height: 100%; display: block; }
/* While a die is in hand, quiet the OTHER pool dice so the eye follows the drag. */
body.dragging-attack-die .pool-die-draggable:not(.pool-die-dragging) { opacity: 0.6; }
body.dragging-attack-die .pool-die-draggable:hover { transform: none; filter: none; } /* no hover pop mid-drag */
/* A weapon lit up as a valid drop target while a die is dragged over it — a clean gold
   glow-frame that lifts slightly (no jittery dashed outline). */
.assignable-weapon.drag-over-die {
  outline: none;
  box-shadow: inset 0 0 0 3px var(--accent2), 0 0 0 3px rgba(217, 164, 65, 0.35), 0 8px 20px rgba(0, 0, 0, 0.45);
  transform: translateY(-3px) scale(1.02);
  transition: transform 0.1s ease, box-shadow 0.1s ease;
}

/* Attack-screen buttons: the confirm (Attack) is the loud primary; Clear / Cancel are
   quieter secondaries. The confirm is deliberately TALL, and sits well clear of the
   "Use Power" checkbox above it. */
.attack-confirm-btn {
  display: block;
  width: 100%;
  margin: 16px auto 0;   /* room between it and the Use Power toggle */
  min-height: 56px;
  padding: 15px 16px;
  font-size: 1.08rem;
  font-weight: 700;
}
#attack-builder .power-toggle { margin-bottom: 2px; }
.attack-screen-btns { display: flex; gap: 8px; justify-content: center; margin-top: 8px; width: 100%; }
.attack-clear-btn, .attack-cancel-btn {
  flex: 1 1 0;
  margin: 0;   /* drop the default button margins so the row's edges line up exactly
                  with the full-width Attack button above (all centred as one group) */
  background: rgba(60, 54, 74, 0.92);
  border: 1px solid rgba(255, 255, 255, 0.2);
  color: var(--text, #e8e6f0);
}
.attack-clear-btn:hover, .attack-cancel-btn:hover { filter: brightness(1.1); }
/* Pickpocket / Destroy assignment screen: stack the target + card pickers full-width, with
   breathing room above the Confirm button (which reuses .attack-confirm-btn). */
/* Player-assignment and the card being taken/destroyed sit SIDE BY SIDE (horizontal), which
   saves vertical space — especially on mobile. Each select sizes to its OWN content (so option
   text like "1st face-down" is never clipped) and the row wraps only if it can't fit. */
.action-assign { display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px; margin: 8px 0 0; }
.action-assign select { flex: 0 0 auto; width: auto; min-width: 0; max-width: 100%; padding: 10px 12px; font-size: 1rem; }
.weapon-assign-compact select {
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 6px 8px;
  font-size: 0.8rem;
  font-weight: 600;
  border-radius: 5px;
  /* Solid fill so the target dropdown reads cleanly over the weapon art. */
  background: rgb(34, 30, 46);
  color: #f4f1f8;
  border: 2px solid var(--accent2);
  box-shadow: 0 0 0 2px rgba(224, 138, 60, 0.18);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.play-area {
  flex: 0 0 44%;
  min-width: 360px;
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-height: 0;
}
/* "Your turn" gold border — thicker, with an inner + outer gold glow that gently
 *  breathes so it draws the eye without shouting. */
.play-area.my-turn {
  box-shadow: inset 0 0 0 3px var(--accent2), inset 0 0 16px rgba(224, 184, 58, 0.3), 0 0 12px rgba(224, 184, 58, 0.22);
  animation: my-turn-glow 2.2s ease-in-out infinite;
}
@keyframes my-turn-glow {
  0%, 100% { box-shadow: inset 0 0 0 3px var(--accent2), inset 0 0 14px rgba(224, 184, 58, 0.26), 0 0 10px rgba(224, 184, 58, 0.18); }
  50%      { box-shadow: inset 0 0 0 4px var(--accent2), inset 0 0 28px rgba(224, 184, 58, 0.52), 0 0 24px rgba(224, 184, 58, 0.42); }
}
@media (prefers-reduced-motion: reduce) {
  .play-area.my-turn { animation: none; box-shadow: inset 0 0 0 3px var(--accent2), inset 0 0 16px rgba(224, 184, 58, 0.32), 0 0 12px rgba(224, 184, 58, 0.24); }
}
/* Mobile: dim the Your-Turn border — a thin gold edge with a faint, gentle glow. The
   full desktop pulse is too intense on a small screen. */
html.mobile .play-area.my-turn {
  box-shadow: inset 0 0 0 2px var(--accent2), inset 0 0 7px rgba(224, 184, 58, 0.16);
  animation: my-turn-glow-mobile 2.6s ease-in-out infinite;
}
@keyframes my-turn-glow-mobile {
  0%, 100% { box-shadow: inset 0 0 0 2px var(--accent2), inset 0 0 5px rgba(224, 184, 58, 0.12); }
  50%      { box-shadow: inset 0 0 0 2px var(--accent2), inset 0 0 11px rgba(224, 184, 58, 0.24); }
}
@media (prefers-reduced-motion: reduce) {
  html.mobile .play-area.my-turn { animation: none; box-shadow: inset 0 0 0 2px var(--accent2), inset 0 0 7px rgba(224, 184, 58, 0.16); }
}
.play-area h3 { align-self: flex-start; flex: 0 0 auto; }
.play-area-head {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  position: relative; /* containing block for the centred YOUR TURN badge */
}
.play-area-head h3 { margin: 0; }
/* Centre the YOUR TURN badge over the top of the play area (both desktop and
   mobile). A full-width, click-through layer holds the badge dead-centre so it no
   longer rides in the top-right corner. The badge keeps its own pop animation
   because the centring lives on this wrapper, not the badge. */
.turn-badge-center {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  justify-content: center;
  pointer-events: none;
  z-index: 2;
}
/* On PC, the YOUR TURN badge sits in the TOP-RIGHT of the play area instead of centred.
   Mobile keeps it centred (the base rule above). */
html:not(.mobile) .turn-badge-center { justify-content: flex-end; padding-right: 4px; }
#turn-badge:not(:empty) {
  background: var(--accent2);
  color: #1a1826;
  font-weight: 600;      /* simmered down a notch from 700 */
  padding: 3px 12px;
  border-radius: 12px;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  opacity: 0.9;          /* slightly dimmer so the gold pill is a touch less blaring */
  animation: stage-pop 0.4s ease-out;
}
/* The "screen": animated log messages, the dice roll, and drawn Event cards. */
.play-stage {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  overflow: hidden;
  border-bottom: 1px solid #33303e;
  padding: 0 14px 8px;   /* side margins so long stage prompts wrap cleanly, not edge-to-edge */
  margin-bottom: 8px;
}
.stage-message {
  flex: 0 0 auto;
  width: 100%;
  text-align: center;
  font-size: 1.5rem;
  font-weight: 600;
  min-height: 1.4em;
  color: var(--text);
  overflow-wrap: break-word;
}
.stage-message:empty { display: none; }
.stage-hidden { display: none !important; }
.stage-message-animate { animation: stage-pop 0.45s ease-out; }
/* The "Your Turn!" prompt breathes slowly from white toward near-black to draw
   the eye gently. Runs the entrance pop first, then loops the soft pulse. */
.stage-message.your-turn-pulse {
  animation: stage-pop 0.45s ease-out, your-turn-pulse 3s ease-in-out 0.45s infinite;
}
@keyframes your-turn-pulse {
  0%, 100% { color: #ffffff; }
  50% { color: #2a2730; }
}
@keyframes stage-pop {
  0% { opacity: 0; transform: translateY(-10px) scale(0.94); }
  60% { transform: translateY(0) scale(1.03); }
  100% { opacity: 1; transform: none; }
}
@keyframes event-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
.stage-message.msg-attack { color: var(--accent); }
.stage-message.msg-heal { color: #5bbf6a; }
.stage-message.msg-event { color: var(--accent2); }
.stage-message.msg-death { color: #e0736a; }
/* Prompt colour-coding by category (banners, event card, and log entries). */
.msg-spell { color: #5b8dd9 !important; }   /* spell use — blue */
.msg-damage { color: #983831 !important; }  /* damage to players — dark red */
.msg-action { color: #ef5a52 !important; }  /* action cards — bright red */
.msg-event { color: #a274d6 !important; }   /* Events — purple */
.msg-maker { color: #b07d43 !important; }   /* Maker-Only cards — brown */
.msg-heal { color: #5bbf6a !important; }    /* gaining health — green */
.msg-weapon { color: #2f8a4c !important; }  /* weapon special abilities — dark green */
.msg-death { color: #9a93a6 !important; }   /* death — gray */
.msg-reveal { color: #e0b83a !important; }  /* Maker reveal, Room of Truth, victory — golden */
.stage-message.msg-reveal { color: var(--accent2); }
.stage-message.msg-turn { color: var(--accent2); font-size: 1.7rem; letter-spacing: 0.04em; }
.hp-flavor {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  margin-top: 10px;
  font-size: 2rem;
  font-weight: 700;
  color: var(--text);
}
.hp-flavor .hp-old, .hp-flavor .hp-arrow { color: var(--muted); }
.hp-die-glyph { width: 56px; height: 56px; display: block; }
.hp-transition { color: var(--accent2); font-weight: 600; }
.hp-transition strong { color: var(--accent); }
.dice-roll-area {
  flex: 0 1 auto;
  min-height: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.stage-event {
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  text-align: center;
}
.stage-event:empty { display: none; }
/* The whole reveal (drawer line + card art + name + effect) must fit inside the
   Play Area pane. The art is capped well below the pane height and the fonts are
   trimmed so neither the top "drew an Event" line nor the effect text below get
   clipped; overflow scrolls as a last resort. The art stays hover-magnifiable. */
.stage-event { overflow: hidden; max-height: 100%; padding: 2px 4px; }
.stage-event img { flex: 0 1 auto; min-height: 0; max-width: 100%; max-height: 40vh; width: auto; height: auto; object-fit: contain; border-radius: 18px; border: 1px solid #45414f; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6); animation: event-fade-in 0.35s ease-out; }
/* Event reveal: with the name/effect text removed, let the card art grow to fill the play-area
   height (object-fit keeps its aspect). Scoped to .event-art-fill so the shared card-use banner
   art keeps its smaller cap. */
.stage-event img.event-art-fill { flex: 1 1 auto; max-height: 100%; }
/* Destroy permanently removes another player's card from the game, so its
   card-use banner gets a heavier reaction than the plain fade-in every other
   action card (Foreshadow/Pickpocket/Rummage) gets: a violent little shake plus
   a hot red flash on the art itself, and a cracked-glass overlay across the
   whole stage. The art shown is the DESTROYED card's own art (not Destroy's) -
   see showCardArtBanner's artOverrideName param and DESTROY_TARGET_RE in
   app.js - so the shake/crack reads as "this specific card got ripped apart",
   not as the Destroy action card itself being damaged. Class is toggled in
   showCardArtBanner (public/app.js) and always cleared the moment anything
   else takes over the stage. Shown to every player, including whoever played
   it (Destroy is deliberately excluded from the "own action" fast-path that
   skips the banner for the other 3 action cards — see OWN_ACTION_FAST_RE). */
.stage-destroy-fx { position: relative; }
.stage-destroy-fx img.hoverable-card-art {
  animation: event-fade-in 0.35s ease-out, stage-destroy-shake 0.55s ease-out, stage-destroy-flash 0.65s ease-out;
}
.stage-destroy-fx::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cpath d='M100 4 L90 44 L118 60 L82 96 L112 118 L66 156 L100 196' stroke='white' stroke-width='4' fill='none' opacity='0.9' stroke-linejoin='round'/%3E%3Cpath d='M100 4 L90 44 L118 60 L82 96 L112 118 L66 156 L100 196' stroke='black' stroke-width='1.4' fill='none' opacity='0.55' stroke-linejoin='round'/%3E%3Cpath d='M90 44 L58 52 M118 60 L150 68 M82 96 L48 100 M112 118 L146 128' stroke='white' stroke-width='2.5' fill='none' opacity='0.75' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 55% 85%;
  opacity: 0;
  animation: stage-destroy-crack 0.65s ease-out;
}
@keyframes stage-destroy-shake {
  0% { transform: translate(0, 0) rotate(0deg); }
  10% { transform: translate(-7px, 3px) rotate(-1.8deg); }
  22% { transform: translate(6px, -4px) rotate(1.8deg); }
  34% { transform: translate(-5px, 4px) rotate(-1.2deg); }
  46% { transform: translate(5px, -3px) rotate(1.2deg); }
  58% { transform: translate(-3px, 2px) rotate(-0.6deg); }
  72% { transform: translate(2px, -1px) rotate(0.4deg); }
  86% { transform: translate(-1px, 1px) rotate(0deg); }
  100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes stage-destroy-flash {
  0% { filter: brightness(1) saturate(1); }
  10% { filter: brightness(2.3) saturate(0.2) drop-shadow(0 0 26px rgba(255, 60, 60, 0.95)); }
  28% { filter: brightness(1.3) saturate(0.7) drop-shadow(0 0 12px rgba(255, 60, 60, 0.55)); }
  100% { filter: brightness(1) saturate(1); }
}
@keyframes stage-destroy-crack {
  0% { opacity: 0; }
  12% { opacity: 1; }
  55% { opacity: 0.8; }
  100% { opacity: 0; }
}
.stage-event .note { max-width: 100%; font-size: 0.95rem; flex: 0 0 auto; }
.stage-event strong { font-size: 1.15rem; }
#dice-caption { text-align: center; margin-bottom: 10px; min-height: 1.2em; font-size: 1.25rem; }
.dice-cap-line { line-height: 1.4; }
.dice-cap-main { font-weight: 600; color: var(--text); }
.dice-cap-sub { color: var(--muted); font-size: 0.9em; }
.you-highlight { color: var(--accent2); font-weight: 700; }
.dice-and-weapon {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  flex-wrap: nowrap;  /* don't stack vertically — that made the tall weapon art clip out of the pane */
  width: 100%;
  max-height: 100%;
  min-height: 0;
}
#dice-display { min-width: 0; }
.dice-weapon-art {
  width: auto;
  height: auto;
  max-height: 33vh; /* ~150% of the old 22vh — bigger weapon card during the dice roll */
  max-width: 55%;   /* room for the larger card beside the dice */
  flex: 0 1 auto;
  min-width: 0;
  border-radius: 18px;
  border: 1px solid #45414f;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.55);
}
.dice-weapon-art.hidden { display: none !important; }
#dice-display {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  align-items: center;
  align-content: center;
}
.die-roll {
  width: var(--die-size, 110px);
  height: var(--die-size, 110px);
  display: block;
  filter: drop-shadow(0 3px 5px rgba(0, 0, 0, 0.5));
}
.die-roll.rolling { animation: die-shake 0.15s linear infinite; }
@keyframes die-shake {
  0% { transform: rotate(-6deg); }
  50% { transform: rotate(6deg); }
  100% { transform: rotate(-6deg); }
}
.turn-emphasis {
  color: var(--accent2);
  font-size: 0.85rem;
  margin: 0 0 8px;
  text-align: center;
}
.turn-emphasis strong { text-decoration: underline; }
/* Reminder shown under a faded Attack button when you hold only a face-down weapon. */
/* Centre the flip hint vertically between the turn buttons and the End Turn button. The End Turn
   button sits at the bottom via margin-top:auto, so margin-top:auto here splits any spare space
   evenly above and below the hint. The matching margin-bottom:6px cancels the turn-grid's own 6px
   bottom margin, so the gaps stay symmetric in BOTH the spare case AND the compact case (where the
   autos collapse to 0 and the hint would otherwise hug the End Turn button). */
.attack-flip-hint { text-align: center; margin: auto 0 6px; color: var(--accent2); font-size: 0.82rem; }
/* End Turn: always black fill with a red border, in both the normal and the
   tall (nothing-left-to-do) variant. Hover brightens the border rather than
   flooding the button red. */
.end-turn-btn {
  display: block;
  margin-top: 14px;
  background: #100e15;
  border: 1px solid var(--accent);
  color: var(--text);
}
.end-turn-btn { transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease; }
.end-turn-btn:hover {
  background: #33161a;
  border-color: #ff7a70;
  color: #fff;
  box-shadow: 0 0 0 1px #ff7a70, 0 0 18px 2px rgba(224, 85, 85, 0.55), 0 0 44px 8px rgba(224, 85, 85, 0.22);
  text-shadow: 0 0 10px rgba(255, 170, 170, 0.7);
}
.end-turn-btn:active { transform: translateY(1px); }
@media (prefers-reduced-motion: reduce) { .end-turn-btn { transition: none; } }

/* --- Overlay panels (reactions / reveal prompts / choices): a true
   centered modal overlay, fixed over the whole viewport with a dimmed
   backdrop. Removed from normal document flow entirely (position: fixed)
   so it never pushes, shrinks, or shifts anything else on screen — it just
   appears on top and disappears once resolved. --- */
#overlay-panels {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  background: rgba(8, 7, 11, 0.92);
  padding: 10px;
  overflow: auto;
}
#overlay-panels:not(:empty) { display: flex; }
/* Popups are confined to the Play Area pane, so their content must scale to fit
   it rather than spilling past its edges. The card is capped to the pane's
   height and scrolls internally if a rare case still overflows; art and fonts
   are sized down from the old full-viewport values so the common popups fit
   without any clipping or scrolling. */
#overlay-panels .card {
  margin-bottom: 0;
  width: 100%;
  max-width: 100%;
  max-height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: safe center; /* centers when it fits; falls back to top so buttons stay reachable */
  gap: 3px;
  overflow-y: auto; /* last-resort scroll so action buttons are never clipped out of reach */
  text-align: center;
  font-size: 1rem;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.65);
  animation: modal-pop-in 0.15s ease-out;
}
#overlay-panels .card p:not(.prompt-command):not(.prompt-hint):not(.reaction-hit):not(.reaction-noblock) { font-size: 0.95rem; flex: 0 0 auto; margin: 1px 0; line-height: 1.25; }
#overlay-panels .card p.reaction-hit,
#overlay-panels .card p.reaction-noblock { flex: 0 0 auto; }
#overlay-panels .card p.prompt-command,
#overlay-panels .card p.prompt-hint { flex: 0 0 auto; }
#overlay-panels .card .note { margin: 1px 0; }
#overlay-panels .card h2 { font-size: 1.4rem; flex: 0 0 auto; }
#overlay-panels .card button { margin: 6px 6px 0; font-size: 0.95rem; padding: 7px 14px; }
/* Buttons, notes and step-groups keep their size; the card art is the flexible
   element that shrinks so the whole prompt fits and the buttons stay on-screen.
   Art stays hover-magnifiable, so shrinking it small is fine. */
#overlay-panels .card .reaction-art,
#overlay-panels .card > .card-art,
#overlay-panels .card > img.hoverable-card-art {
  margin-left: auto; margin-right: auto;
  flex: 0 1 auto; min-height: 0;
  max-width: 90%; max-height: 42vh;
  width: auto; height: auto; object-fit: contain;
}
/* Card art wrapped in a .hand-card (the "you obtained a card" and starting-hand
   popups): make the wrapper a shrinkable flex column so the image inside scales
   down to leave room for the buttons, rather than pushing them off the pane. */
#overlay-panels .card .hand-card {
  display: flex; flex-direction: column; align-items: center;
  flex: 0 1 auto; min-height: 0; min-width: 0; overflow: hidden;
  width: auto; max-width: 100%; margin: 4px auto;
}
#overlay-panels .card .hand-card .card-art {
  flex: 0 1 auto; min-height: 0;
  max-width: 100%; max-height: 30vh;
  width: auto; height: auto; object-fit: contain; margin-bottom: 4px;
}
#overlay-panels .card #card-orientation-main-step,
#overlay-panels .card #card-orientation-swap-step { flex: 0 0 auto; width: 100%; }
#overlay-panels .card .discard-grid,
#overlay-panels .card .card-option-row { flex: 0 0 auto; }

/* Choice popups (Foreshadow, card claim) held their cards at a hard 108px inside a
   full-width panel: three postage stamps stranded in a wide empty box, with the
   printed card text far too small to read. The panel now shrinks to its contents
   and the cards grow to fill it. Excludes the starting-hand popup, which is tuned
   separately below. */
#overlay-panels .card:has(> .card-option-row):not(#starting-orientation-panel) {
  width: fit-content;
  min-width: min(100%, 320px);
  max-width: min(100%, 680px);
  padding: 16px 12px;
}
#overlay-panels .card:not(#starting-orientation-panel) > .card-option-row {
  gap: 10px;
  margin-top: 12px;
}
#overlay-panels .card:not(#starting-orientation-panel) > .card-option-row .card-option {
  /* The basis must be the size we WANT, not 0 — with `flex-basis: 0` each tile
     collapses to its min-content width (~120px) and the row never fills the
     panel, which is exactly what made three cards look marooned in a wide box.
     Shrink still applies, so five discard options fall back toward 118px. */
  flex: 1 1 clamp(118px, 23vh, 180px);
  max-width: clamp(118px, 23vh, 180px);
  padding: 8px 6px;
  gap: 2px;
  /* .card-option is a <button>, so `#overlay-panels .card button { margin: 6px 6px 0 }`
     was quietly taking 12px off every tile — 36px across three of them. */
  margin: 0;
}
#overlay-panels .card:not(#starting-orientation-panel) > .card-option-row .card-art {
  max-height: 40vh;
  margin-bottom: 5px;
}
#overlay-panels .card:not(#starting-orientation-panel) > .card-option-row .card-option span {
  font-size: 0.82rem;
  line-height: 1.15;
}

/* The starting-hand popup always shows exactly 3 cards and must never wrap them
   to a second row. The card row absorbs whatever pane height is left after the
   title/note/Ready button, and the 3 cards + their art scale down within it, so
   the whole window always fits the Play Area and the Ready button is never
   pushed off-screen. (High-specificity selectors so they beat the general
   overlay rules above.) */
#starting-orientation-panel { max-width: 100%; }
#overlay-panels #starting-orientation-panel .card-option-row {
  flex: 1 1 auto;
  flex-wrap: nowrap;
  min-height: 0;
  gap: 6px;
  width: 100%;
  align-items: stretch;
}
#overlay-panels #starting-orientation-panel .card-option-row .hand-card {
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
  margin: 0;
  justify-content: flex-start;
}
#overlay-panels #starting-orientation-panel .card-option-row .hand-card .card-art {
  flex: 1 1 auto;
  min-height: 0;
  max-width: 100%;
  max-height: 100%;
  width: 100%;
  height: auto;
  object-fit: contain;
  margin-bottom: 2px;
}
#overlay-panels #starting-orientation-panel .card-option-row .hand-card strong { font-size: 0.68rem; overflow-wrap: anywhere; line-height: 1.1; }
#overlay-panels #starting-orientation-panel .card-option-row .hand-card .rearrange-btn { font-size: 0.86rem; padding: 6px 12px; margin-top: 5px; }
@keyframes modal-pop-in {
  from { transform: scale(0.94); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

/* "Your turn!" toast: shares the modal card look but is non-blocking (floats
   for 3s then fades). Its position (top/left/width) is set in JS so it lands
   directly on the seam between the board and the "Your inventory" panel; the
   card is translated up by half its height to straddle that line. */
#turn-toast {
  position: fixed;
  z-index: 1100;
  display: flex;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s ease;
}
#turn-toast.visible { opacity: 1; }
#turn-toast .turn-toast-card {
  width: auto;
  min-width: 440px;
  max-width: 96vw;
  margin: 0;
  padding: 32px 44px;
  transform: translateY(-50%);
  text-align: center;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.65);
  animation: modal-pop-in 0.15s ease-out;
}
#turn-toast .turn-toast-card h2 {
  margin: 0;
  font-size: 2.6rem;
  color: var(--accent2);
  font-family: "Oswald", "Segoe UI", sans-serif;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* Small, non-blocking popup for a brief explanation (currently just the poisoned
   Heal/Cleric Staff tap - see showPoisonBlockedToast in app.js). Deliberately much
   smaller/quieter than #turn-toast above - this is a passing tap-triggered aside,
   not an announcement. Created once and reused (the element persists, only its
   text and .visible class change), so repeat taps never pile up duplicate nodes. */
.mini-toast {
  position: fixed;
  left: 50%;
  bottom: 14%;
  transform: translateX(-50%) translateY(8px);
  z-index: 1200;
  padding: 8px 16px;
  border-radius: 999px;
  background: rgba(20, 18, 26, 0.94);
  border: 1px solid #3a3545;
  color: #eee;
  font-size: 0.85rem;
  max-width: 90vw;
  text-align: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}
.mini-toast.visible { opacity: 1; transform: translateX(-50%) translateY(0); }

/* --- Card backs: real art, shown at full strength (previously washed out
   to near-invisibility by a 0.6 opacity fade, which read as "blank"). --- */
.card-art-back { opacity: 1; border: 1px solid #45414f; border-radius: 18px; }

/* --- Log line highlight: newly-arrived entries glow briefly then fade on
   their own via animation, so no JS timer is needed to remove the class. --- */
@keyframes log-highlight-fade {
  0% { background: rgba(217, 164, 65, 0.35); }
  40% { background: rgba(217, 164, 65, 0.35); }
  100% { background: transparent; }
}
#log-list div.log-new {
  animation: log-highlight-fade 1.5s ease-out forwards;
  border-radius: 4px;
  padding-left: 4px;
}

/* --- Victory screen: full-screen finale when a side wins. Themed red for the
   Maker (with the eyes art) and gold for the Heroes. --- */
.victory-screen {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  /* When the content (e.g. the Maker-reveal card with all its facts) is taller
     than the screen, scroll it rather than clipping the top and bottom. */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: radial-gradient(ellipse at center, rgba(20, 18, 26, 0.86), rgba(8, 7, 11, 0.96));
  animation: victory-fade-in 0.4s ease-out;
}
/* Tall content: don't vertically-center (that hides the ends behind the top/bottom
   edges when it overflows) — let it flow from the top and scroll. Short content
   still sits comfortably because of the auto margins on the card. */
.victory-screen > * { margin-top: auto; margin-bottom: auto; }
.victory-screen.maker-win { background: radial-gradient(ellipse at center, rgba(60, 20, 18, 0.9), rgba(8, 7, 11, 0.97)); }
.victory-screen.heroes-win { background: radial-gradient(ellipse at center, rgba(46, 40, 20, 0.9), rgba(8, 7, 11, 0.97)); }
.victory-card {
  text-align: center;
  max-width: 480px;
  width: 100%;
  animation: modal-pop-in 0.35s ease-out;
}
/* v5.0.76: matches the identity/Maker-reveal cards' new height-driven scaling
   (see .identity-intro-card above) - both now grow with the viewport height
   instead of sitting at one flat size regardless of screen. Scaled down from
   that card's numbers for this image's much wider ~2.2:1 aspect (maker-eyes.png
   is 503x227) and this screen's own narrower, untouched 480px .victory-card, so
   it grows with the screen without overflowing the card sideways. */
.victory-art {
  height: clamp(90px, 22vh, 170px);
  width: auto;
  max-width: 86%;
  display: block;
  margin: 0 auto 8px;
}
.victory-emblem { font-size: 5rem; line-height: 1; margin-bottom: 8px; }
.victory-title {
  font-family: "Oswald", "Segoe UI", sans-serif;
  font-weight: 700;
  font-size: 2.8rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin: 8px 0 6px;
}
.maker-win .victory-title { color: var(--accent); text-shadow: 0 0 24px rgba(176, 69, 62, 0.6); }
.heroes-win .victory-title { color: var(--accent2); text-shadow: 0 0 24px rgba(217, 164, 65, 0.55); }
.victory-sub { color: var(--muted); font-size: 1rem; margin-bottom: 20px; }
.victory-btn { font-size: 1rem; padding: 12px 22px; }
.victory-btn-alt { background: transparent; border: 1px solid #55505f; margin-top: 8px; }
/* Match-awards screen (after the victory card) */
.victory-card.stats-card { max-width: 600px; width: 94%; max-height: 88vh; overflow-y: auto; }
/* Freeze "Back to menu" at the bottom of the awards card so it's always reachable
   without scrolling the (often long) awards list to the end. The stats-card is the
   scroll box (max-height + overflow-y:auto), so position:sticky bottom pins the
   footer to its lower edge. A short gradient fade lets the last award scroll under it. */
.stats-card .stats-card-footer {
  position: sticky;
  bottom: 0;
  margin-top: 10px;
  padding: 10px 0 4px;
  background: linear-gradient(to top, var(--bg) 62%, rgba(20, 18, 26, 0));
  /* Without an explicit z-index, this sticky footer defaults to z-index:auto and
     can be painted OVER by anything with a positive z-index that scrolls near it
     — including a .rarity-flag badge (z-index: 2), which pokes up above its own
     award card and can end up overlapping "Back to menu" while scrolling. Higher
     than any award-area z-index keeps the footer always on top. */
  z-index: 5;
}
.stats-card .stats-card-footer .victory-btn { margin-top: 0; }
.stats-card .victory-title { font-size: 1.8rem; margin-bottom: 6px; }
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
  gap: 10px;
  margin: 14px 0 6px;
  text-align: left;
}
.stat-award {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid #33303e;
  border-radius: 8px;
  padding: 9px 11px;
}
.stat-title { font-weight: 700; font-size: 1.02rem; color: #e0b83a; line-height: 1.15; }
.stat-player { font-weight: 600; font-size: 0.92rem; color: var(--text); margin-top: 1px; }
.stat-category { font-size: 0.66rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.03em; margin-top: 5px; }
.stat-detail { font-size: 0.8rem; color: #b8b2c2; margin-top: 3px; }
@keyframes victory-fade-in { from { opacity: 0; } to { opacity: 1; } }

/* Death prompt: one-time spectate/leave choice when the viewer's player dies. */
.death-prompt {
  position: fixed;
  inset: 0;
  z-index: 1900;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(8, 7, 11, 0.72);
  animation: victory-fade-in 0.3s ease-out;
}
.death-prompt-card {
  background: var(--panel);
  border: 1px solid #45414f;
  border-radius: 12px;
  padding: 24px 28px;
  max-width: 420px;
  text-align: center;
  box-shadow: 0 16px 48px rgba(0, 0, 0, 0.6);
}
.death-prompt-card h2 {
  margin: 0 0 8px;
  color: var(--accent);
  font-family: "Oswald", "Segoe UI", sans-serif;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.death-prompt-card p { color: var(--muted); margin-bottom: 18px; }
.death-prompt-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }

.free-trapped-row { margin-top: 10px; margin-bottom: 12px; }

.event-space { cursor: help; }

/* Top-center, non-blocking turn banner (used by the 10s no-moves-left auto-end). */
/* (The old absolute-positioned .turn-banner for the end-turn warning has been
   replaced by the shared .play-countdown bar in #play-countdowns — see the
   "Play-area countdown bars" section — so both time-limit bars are identical.) */

/* Host-left countdown: viewport-fixed, above everything except the reveal
   screens, because the room is about to disappear from under the player. */
#host-grace-banner {
  position: fixed;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2500;
  padding: 10px 20px;
  border-radius: 8px;
  font-size: 1rem;
  text-align: center;
  max-width: 92vw;
  background: #4a1717;
  color: #ffd9d9;
  border: 1px solid var(--accent);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.6);
}
.hand-row .hand-card > strong,
.hand-row .hand-card .hand-card-title { display: none; }

/* --- Play Area messages centered, wider Clear-dice, bigger idle warning,
   and the bot-takeover "Return to game" inventory button. --- */
#turn-controls { text-align: center; }        /* center hints/warnings in the Play Area */
/* ...which on its own did nothing. #turn-controls is a flex COLUMN with
   align-items: flex-start, so every child shrink-wraps to its own text and pins
   left; centring text inside a box that already hugs the text is invisible. The
   turn prompts ("Discard 1 card" / "Flip a card face-up" and their hints) must
   stretch to the column's width before text-align has anything to centre within.
   Only direct children: #attack-builder and the button rows manage their own
   width, and stretching those would change their layout. */
#turn-controls > .prompt-command,
#turn-controls > .prompt-hint,
#turn-controls > .note {
  align-self: stretch;
  width: 100%;
}
#clear-dice-slot button { padding: 5px 30px; min-width: 150px; }  /* longer Clear dice button */


/* Face-down inventory cards only show the generic back (no printed name), so
   keep their text title visible so you can tell your own face-down cards apart. */
.hand-row .hand-card.face-down-slot > strong,
.hand-row .hand-card.face-down-slot .hand-card-title { display: block; }

/* End-of-game Match Awards, organized into readable category sections. */
.stats-groups { display: flex; flex-direction: column; gap: 18px; width: 100%; }
.stat-group { width: 100%; }
.stat-group-title {
  font-size: 1.05rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent2);
  margin: 0 0 8px;
  padding-bottom: 5px;
  border-bottom: 1px solid #3a3646;
  text-align: left;
}

/* Per-award icon on each Match Award card. */
.stat-award .stat-icon {
  font-size: 1.7rem;
  line-height: 1;
  margin-bottom: 5px;
}

/* --- Themed scrollbars -----------------------------------------------------
   Wherever a pane runs out of vertical room the browser falls back to its own
   grey scrollbar, which looks like an office widget bolted onto a dungeon. This
   is the game's dark/gold treatment, applied globally so EVERY scrollable pane
   (awards, tutorial, lobby, player info, battle log, rules, prompts, the page
   itself) matches without needing to be listed one by one.

   Panes that deliberately hide their scrollbar (the players pane; the collapsed
   battle log) override this with their own rules further up — a class selector
   beats the universal one, so they stay hidden. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--accent2) rgba(255, 255, 255, 0.05);
}
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track {
  background: rgba(255, 255, 255, 0.04);
  border-radius: 8px;
  margin: 6px 0;
}
*::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, var(--accent2), var(--accent));
  border-radius: 8px;
  border: 2px solid rgba(20, 18, 26, 0.85);
}
*::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, #e6b653, var(--accent));
}
*::-webkit-scrollbar-corner { background: transparent; }

/* Host-only "End game" button, tucked into the Dungeon Rooms panel header.
   height + flex-centering (direct follow-up request, matching .rules-quick-btn
   right below): an EXPLICIT shared height on both buttons, rather than relying
   on each one's own natural content height, is what actually guarantees they
   render pixel-identical - "End game"'s plain text and Rules' emoji+text line
   have different natural font-metric heights (especially in a real browser
   with a color-emoji font, which this project's headless test browser can't
   fully show - see the project's own testing-discipline notes on this), so
   letting each button size itself would always risk a mismatch. */
.host-end-btn {
  float: right;
  margin: -2px 0 0 8px;
  padding: 3px 12px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #ffd9d5;
  background: #5c2320;
  border: 1px solid var(--accent);
  border-radius: 6px;
  cursor: pointer;
}
.host-end-btn:hover { background: var(--accent); color: #fff; }

/* "Rules" quick-access button (v6.0.24) - same float-right/sizing as
   .host-end-btn right above (they share the same panel-title, see
   presentGame() in app.js), but a neutral gold instead of the warning red,
   since this isn't a destructive action. Comes AFTER .host-end-btn in DOM
   order, and float:right stacks later siblings to the LEFT of earlier ones -
   so on a host's screen End game stays rightmost with Rules just to its left
   ("next to" it, per direct request); for a non-host (no End game button at
   all) Rules is the only thing floated right, landing in that exact same
   rightmost corner End game would otherwise occupy. */
.rules-quick-btn {
  float: right;
  margin: -2px 0 0 8px;
  padding: 3px 12px;
  /* Same EXPLICIT height as .host-end-btn right above, not just matching
     padding/font-size - see the comment on .host-end-btn for why an explicit
     shared height (not each button's own natural content height) is what
     actually keeps the two the exact same size (direct follow-up request).
     inline-flex + line-height:1: the book emoji's glyph box runs taller than
     the "RULES" text's own line box in real browsers (the project's headless
     test browser has no color-emoji font and understates this), which pushed
     the text off-center vertically under plain block/inline text-centering.
     Flexbox centers the whole line - emoji and text together - against the
     button's true padding box regardless of font metrics. */
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  background: #100e15;
  border: 1px solid var(--accent2);
  border-radius: 6px;
  cursor: pointer;
}
.rules-quick-btn:hover { background: var(--accent2); color: #14121a; }

/* When End Turn is the only real option left (nothing but rearranging cards),
   make it a big, obvious target. */
.end-turn-btn.end-turn-btn-tall {
  min-height: 92px;
  font-size: 1.25rem;
  font-weight: 700;
  background: #100e15;              /* keep the black fill at the larger size */
  border: 1px solid var(--accent);  /* and the red border */
  color: var(--text);
}
.end-turn-btn.end-turn-btn-tall:hover { background: #17131b; border-color: #d4574e; color: #fff; }

/* Revealed Maker's permanent Maker-Only cards sit on the same line as the
   inventory thumbnails to their right, rather than on their own row. */
/* Player-info card row (PC): every player's inventory thumbnails sit right-aligned,
   and a revealed Maker's Maker-Only cards sit on the LEFT of the same row, top-
   aligned with the thumbnails and rendered at the same size. Both sides wrap, so
   all three Maker-Only cards (Toughness/Enrage/Dexterity) still fit alongside a
   full hand. (Mobile keeps these stacked — see mobile.css — since a phone row is
   too narrow to hold both sides.) */
.player-row-bottom {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
}
.player-row-bottom .player-perm-cards { margin-top: 4px; flex: 0 0 auto; justify-content: flex-start; flex-wrap: wrap; }
.player-row-bottom .player-card-thumbs { margin-top: 4px; flex: 1 1 auto; justify-content: flex-end; flex-wrap: wrap; }

/* Rummage discard list: rows are ranked strongest-first and tinted by card type,
   matching the type's colour on the card art and in the inventory (weapon=green,
   spell=blue, extra_die=white, maker_only=brown, action=red). A styled list is
   used rather than a native <select> because browsers ignore per-option colours. */
.discard-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 40vh;
  overflow-y: auto;
  margin: 6px 0 10px;
  padding-right: 4px;
  scrollbar-width: thin;
  scrollbar-color: var(--accent2) transparent;
}
/* Rummage: plain centred buttons. The list is still ranked strongest-first —
   the order says so without a caption, and without a stray radio dial pinned to
   the far left of a right-aligned row. */
.rummage-form { text-align: center; }
.rummage-form .note { text-align: center; margin: 0 auto 10px; max-width: 46ch; }
.rummage-confirm { display: block; margin: 12px auto 0; min-width: 190px; }

.discard-opt {
  /* Title on TOP, type BELOW — stacked, both centred and free to wrap, so a long
     name ("Golden Musket", "Dagger of Thieves", "Spike-Toed Boots") never gets
     clipped in its grid cell. Was a single baseline row, which cut names off. */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  width: 100%;
  min-height: 62px;
  margin: 0 0 5px;
  padding: 9px 10px;
  border-radius: 6px;
  border: 1px solid transparent;
  cursor: pointer;
  font-size: 0.9rem;
  text-align: center;
  font-family: inherit;
}
.discard-opt:hover { filter: brightness(1.3); }
.discard-opt.selected { border-color: var(--accent2); box-shadow: 0 0 0 1px var(--accent2) inset; }
.discard-name { font-weight: 600; line-height: 1.15; white-space: normal; overflow-wrap: anywhere; }
.discard-type { opacity: 0.7; font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.04em; white-space: nowrap; }

.discard-opt.weapon     { color: #6ec98a; background: #142a1c; }
.discard-opt.spell      { color: #7fb0e8; background: #15202f; }
.discard-opt.extra_die  { color: #eaeaea; background: #26262a; }
.discard-opt.maker_only { color: #d59a5c; background: #2a1f14; }
.discard-opt.action     { color: #ef7a72; background: #2a1416; }

/* Dungeon Monster: the die value needed to survive, shown above the rolling dice. */
.dice-target {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--accent2);
  letter-spacing: 0.03em;
  margin: 0 0 6px;
  text-align: center;
}
.dice-target .dice-target-pip {
  display: inline-block;
  min-width: 1.9em;
  padding: 1px 8px;
  margin: 0 2px;
  border-radius: 6px;
  background: #3a2a10;
  border: 1px solid var(--accent2);
  color: #ffe6b3;
}

/* ========================= TUTORIAL ========================= */
/* Opens from the main menu; leaves at any time via the Exit button. */

.tutorial-open-btn {
  /* auto left/right margins CENTRE the button and cancel the base `button`
     rule's `margin-right: 8px`, which otherwise nudged these 3 menu buttons
     (How To Play / Card Compendium / Rarity Guide) a few px left of centre. */
  margin: 14px auto 0;
  width: 100%;
  max-width: clamp(280px, 30vh, 520px);
  padding: 10px 18px;
  font-size: 1rem;
  font-weight: 700;
  background: #100e15;
  color: var(--accent2);
  border: 1px solid var(--accent2);
  border-radius: 8px;
  cursor: pointer;
}
.tutorial-open-btn:hover { background: var(--accent2); color: #14121a; }

#tutorial-screen {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
  padding: 0;
  overflow: hidden;
}
#tutorial-screen.hidden { display: none; }
/* v6.0.24: when the tutorial is opened from an in-progress game (the new
   in-game "Rules" quick-access button - see .rules-quick-btn / openTutorial()
   in tutorial.js), it renders as a true full-viewport overlay ON TOP of the
   still-running, never-hidden game screen instead of the normal in-flow menu
   swap above. position:fixed here escapes #app's max-width:720px constraint
   entirely (fixed positioning's containing block is the viewport, not a
   non-positioned ancestor), which is exactly what's wanted - full-bleed,
   opaque, so nothing of the board shows through underneath. Closing it just
   removes this class and un-hides #tutorial-screen's normal flow position;
   the game itself was never touched. */
body.tutorial-overlay-mode #tutorial-screen {
  position: fixed;
  inset: 0;
  z-index: 55;
  background: var(--bg);
}

/* When the tutorial is open, reclaim the vertical space the banner and masthead
   take at the top of the menu — the tutorial then fills the whole viewport, so
   more of each chapter is visible before any scrolling. (The tutorial has its
   own titled top bar, so the main header is redundant here anyway.) */
#app:has(#tutorial-screen:not(.hidden)) > .menu-intro,
#app:has(#tutorial-screen:not(.hidden)) > header { display: none; }

.tutorial-topbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px clamp(12px, 3vw, 30px);
  border-bottom: 1px solid #2f2b3a;
  background: #17151f;
}
.tutorial-topbar h2 { margin: 0; font-size: clamp(1rem, 2.2vh, 1.5rem); }
.tutorial-topbar-sub { color: var(--accent2); }
/* Desktop: centre the How To Play "Exit" button on the page (title above it),
   matching the compendium's centred back button. Mobile keeps its own layout. */
html:not(.mobile) .tutorial-topbar { flex-direction: column; justify-content: center; gap: 8px; }

/* Direct request: while the Rules overlay covers a live game (see
   body.tutorial-overlay-mode) and the player's sound effects are muted,
   there's otherwise no way to notice it's become their turn - the board
   is fully covered and there's no audio cue either. Same gold "your turn"
   color language as .play-area.my-turn elsewhere in the app, as a solid
   full-width banner instead of a border glow (the border glow's inset
   shadows are designed to show against a dark box - invisible against an
   already-gold background - so this gets its own bespoke pulse instead of
   reusing my-turn-glow directly). Hidden by default (see
   #tutorial-turn-alert.hidden below); shown/hidden by
   updateTutorialTurnAlert() in tutorial.js, which polls only while the
   overlay is actually open. */
.tutorial-turn-alert {
  flex: 0 0 auto;
  margin: 0;
  padding: 8px 16px;
  text-align: center;
  font-weight: 700;
  font-size: 0.95rem;
  color: #14121a;
  background: var(--accent2);
  animation: tutorial-turn-alert-pulse 1.8s ease-in-out infinite;
}
.tutorial-turn-alert.hidden { display: none; }
@keyframes tutorial-turn-alert-pulse {
  0%, 100% { box-shadow: 0 0 10px rgba(224, 184, 58, 0.35); }
  50%      { box-shadow: 0 0 22px rgba(224, 184, 58, 0.75); }
}
@media (prefers-reduced-motion: reduce) {
  .tutorial-turn-alert { animation: none; box-shadow: 0 0 12px rgba(224, 184, 58, 0.4); }
}

.tutorial-progress { flex: 0 0 auto; height: 3px; background: #2a2634; }
.tutorial-progress-fill {
  height: 100%;
  width: 0;
  background: linear-gradient(90deg, var(--accent), var(--accent2));
  transition: width 0.28s ease;
}

/* v5.0.68: the chapter list moved from a left sidebar to a horizontal tab
   row at the top of the screen (matching the Rarity Guide / Card Compendium
   tab style), so .tutorial-main is now just the page area - no more sidebar
   column, so no more phantom spacer column either. */
.tutorial-main {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
}

.tutorial-nav {
  flex: 0 0 auto;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  padding: 8px 12px;
  border-bottom: 1px solid #2f2b3a;
  background: #17151f;
}
.tut-nav-item {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  text-align: left;
  font-size: 0.8rem;
  color: var(--muted);
  background: #221f2b;
  border: 1px solid #45414f;
  border-radius: 999px;
  cursor: pointer;
}
.tut-nav-item:hover { border-color: var(--accent2); color: var(--text); }
.tut-nav-item.active { background: #3a2a10; border-color: #8a6a28; color: #f0c26b; font-weight: 700; }
.tut-nav-item.done .tut-nav-title { opacity: 0.75; }
.tut-nav-icon { font-size: 1rem; flex: 0 0 auto; }
.tut-nav-title { flex: 0 0 auto; }

/* The page and the two arrows that flank it. */
.tutorial-page-area {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: stretch;
}
.tut-side-btn {
  flex: 0 0 auto;
  align-self: center;
  /* Centre the glyph explicitly so the finish "✓" (whose bounding box differs from
     the "‹"/"›" chevrons) sits dead-centre rather than off to one side. */
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 clamp(4px, 1vw, 16px);
  width: 46px;
  height: 76px;
  font-size: 2rem;
  line-height: 1;
  font-weight: 700;
  color: var(--muted);
  background: #14121a;
  border: 1px solid #45414f;
  border-radius: 10px;
  cursor: pointer;
  transition: color 0.14s ease, border-color 0.14s ease, box-shadow 0.14s ease, transform 0.1s ease;
}
.tut-side-btn:hover:not(:disabled) {
  color: var(--accent2);
  border-color: var(--accent2);
  box-shadow: 0 0 18px rgba(217, 164, 65, 0.35);
}
.tut-side-btn:active:not(:disabled) { transform: scale(0.96); }
.tut-side-btn:disabled { opacity: 0.22; cursor: default; }
.tut-side-btn.tut-side-finish { color: var(--accent2); border-color: var(--accent2); }
/* The footer holds only the keyboard hint now. */
.tutorial-footer { justify-content: center; }

/* The prose column is centred rather than pinned to the left of the pane —
   with a 20% nav sidebar, left-aligned text sat well off the screen's middle. */
.tut-page { max-width: 760px; margin: 0 auto; text-align: center; }
.tut-page .tut-chapter-head { justify-content: center; text-align: left; }
/* Lists still read left-to-right; they're centred as a block, not as text. */
.tut-page ul,
.tut-page ol { display: inline-block; text-align: left; margin-left: 0; }
/* Prose that sits BESIDE something (an icon, a card) reads left-aligned; only the
   page's own flow is centred. .tut-gain and .tut-recap were centring their
   sentences next to a left-hand icon, unlike .tut-callout right beside them. */
.tut-page .tut-callout,
.tut-page .tut-split-art-body,
.tut-page .tut-gain div,
.tut-page .tut-recap div { text-align: left; }

.tutorial-body {
  flex: 1 1 auto;
  min-width: 0;
  overflow-y: auto;
  padding: clamp(14px, 2.4vh, 30px) clamp(8px, 1.5vw, 24px);
  scrollbar-width: thin;
  scrollbar-color: var(--accent2) transparent;
}
.tutorial-body::-webkit-scrollbar { width: 9px; }
.tutorial-body::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, var(--accent2), var(--accent));
  border-radius: 8px; border: 2px solid #14121a;
}

.tut-chapter-head { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
.tut-chapter-icon { font-size: clamp(1.8rem, 4vh, 2.8rem); }
.tut-chapter-step { margin: 0; font-size: 0.75rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); }
.tut-chapter-title { margin: 2px 0 0; font-size: clamp(1.3rem, 3.2vh, 2.1rem); color: var(--accent2); }
.tut-chapter-body { max-width: 900px; }
.tut-chapter-body p { line-height: 1.6; margin: 0 0 12px; }
.tut-lead { font-size: 1.08rem; color: var(--text); }
.tut-note { font-size: 0.86rem; color: var(--muted); }
.tut-h4 { margin: 18px 0 8px; color: var(--accent2); }

.tut-list { line-height: 1.7; margin: 0 0 12px; padding-left: 20px; }
.tut-list li { margin-bottom: 4px; }

.tut-split { display: flex; flex-wrap: wrap; gap: 12px; margin: 14px 0; }
.tut-box {
  flex: 1 1 260px;
  padding: 12px 14px;
  background: #1c1926;
  border: 1px solid #34303f;
  border-radius: 10px;
}
.tut-box h4 { margin: 0 0 6px; color: var(--text); }
.tut-box p, .tut-box ul { margin: 0; font-size: 0.92rem; }
.tut-box-hero { border-color: #3f7dc9; }
.tut-box-hero h4 { color: #7fb0e8; }
.tut-box-maker { border-color: var(--accent); }
.tut-box-maker h4 { color: #ef7a72; }

.tut-callout {
  margin: 14px 0;
  padding: 11px 14px;
  background: #1f1a1a;
  border-left: 3px solid var(--accent);
  border-radius: 0 8px 8px 0;
  font-size: 0.94rem;
  line-height: 1.6;
}
.tut-callout-key { background: #211d12; border-left-color: var(--accent2); }

.tut-demo {
  margin: 16px 0;
  padding: 14px;
  background: #191622;
  border: 1px dashed #45414f;
  border-radius: 10px;
}
.tut-demo-hint { margin: 0 0 10px; font-size: 0.85rem; color: var(--accent2); }
.tut-result { min-height: 1.4em; margin: 10px 0 0; font-size: 0.94rem; }
.tut-warn { color: #ef7a72; }
.tut-hero { color: #7fb0e8; }
.tut-maker { color: #ef7a72; }

.tut-mini-btn, .tut-primary-btn {
  padding: 8px 16px;
  font-weight: 700;
  color: var(--text);
  background: #100e15;
  border: 1px solid var(--accent);
  border-radius: 7px;
  cursor: pointer;
}
.tut-mini-btn:hover { background: var(--accent); color: #fff; }
.tut-primary-btn { padding: 12px 26px; font-size: 1.05rem; background: var(--accent); color: #fff; }
.tut-primary-btn:hover { filter: brightness(1.15); }

/* Cards + dice */
/* Rows of items sit in a centred column, so they centre too. Without this a
   pair of 114px cards hugged the left edge of a 760px page, 260px off centre. */
.tut-card-row { display: flex; flex-wrap: wrap; gap: 12px; margin: 14px 0; justify-content: center; }
.tut-card { margin: 0; width: clamp(90px, 12vh, 140px); text-align: center; }
.tut-card img { width: 100%; border-radius: 16px; border: 1px solid #45414f; display: block; }
.tut-card figcaption { margin-top: 5px; font-size: 0.76rem; color: var(--muted); }
.tut-card-fallback {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 2 / 3; padding: 8px; font-size: 0.8rem; font-weight: 700;
  background: #26222f; border: 1px solid #45414f; border-radius: 10px;
}
.tut-card-flip { animation: modal-pop-in 0.3s ease-out; }
.tut-flip-row { display: flex; justify-content: center; }
#tut-identity-slot { cursor: pointer; }

.tut-hand { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; align-items: flex-start; }
.tut-hand-card {
  width: clamp(72px, 9vh, 108px);
  padding: 0; background: transparent; border: 2px solid transparent;
  border-radius: 10px;
  display: flex; flex-direction: column; align-items: center;
  cursor: grab;
  touch-action: none; /* the pointer drag owns the gesture; don't let it scroll */
  transform-origin: center;
}
/* No pointer-events:none here (unlike a plain illustration figure) — this art
   needs to be hoverable/tappable for its magnified preview, exactly like the
   real hand-card's art. Pointer events on it still bubble up to the card's own
   drag listeners below, so this doesn't interfere with dragging. */
.tut-hand-card img { width: 100%; border-radius: 14px; display: block; }
.tut-hand-card.up { border-color: #3c8f52; }
.tut-hand-card.down { border-color: #6a6478; }
.tut-hand-card:hover { filter: brightness(1.2); }
.tut-hand-label { display: block; margin-top: 3px; font-size: 0.7rem; color: var(--muted); }

/* Dragging to reorder, as in the real inventory: a lift/tilt, a gold glow, and
   the rest of the hand dims so the card you're holding reads clearly against it. */
.tut-hand-card.tut-dragging {
  cursor: grabbing; z-index: 2; transition: none;
  box-shadow: 0 0 0 2px #f2c96a, 0 0 20px 3px rgba(242, 201, 106, 0.5), 0 12px 30px rgba(0, 0, 0, 0.7);
}
.tut-hand.tut-reordering .tut-hand-card:not(.tut-dragging) { opacity: 0.62; }

/* Where it will land — a lit gold bar beside the neighbouring card, same idea as
   the real inventory's drop-before/drop-after (simplified: no animation loop). */
.tut-hand-card.tut-drop-before,
.tut-hand-card.tut-drop-after { opacity: 1 !important; }
.tut-hand-card.tut-drop-before { box-shadow: -8px 0 0 -1px #f2c96a, -8px 0 16px 2px rgba(242, 201, 106, 0.7); }
.tut-hand-card.tut-drop-after  { box-shadow: 8px 0 0 -1px #f2c96a, 8px 0 16px 2px rgba(242, 201, 106, 0.7); }

/* The card that asked to swap, and the cards it may legally trade with - styled
   to match the REAL inventory's swap prompt exactly (see .hand-card.needs-selection
   / .hand-row:has(.needs-selection) in this file), not a tutorial-only look-alike:
   candidates get the same red pulsing ring (reusing the shared pulse-red keyframe),
   and every other card - INCLUDING the source card itself, which isn't a tap target
   any more than the others - recedes into the same dimmed/greyscale treatment. The
   source keeps the .tut-swap-source class only so JS/tests can find it, exactly like
   the real .swap-source - it carries no border/glow of its own. */
.tut-hand-card.tut-swap-target {
  position: relative;
  z-index: 3;
  border: 2px solid #ff8a8a !important;
  cursor: pointer;
  animation: pulse-red 1.15s ease-in-out infinite;
}
.tut-hand-card.tut-swap-target:hover {
  filter: brightness(1.25);
  animation-play-state: paused;
  box-shadow:
    0 0 0 3px #ffdcdc,
    0 0 22px 4px rgba(255, 120, 120, 0.95),
    0 0 44px 10px rgba(255, 80, 80, 0.45) !important;
}
.tut-hand:has(.tut-swap-target) .tut-hand-card:not(.tut-swap-target) {
  opacity: 0.4;
  filter: grayscale(0.65) brightness(0.75);
  transition: opacity 0.2s ease, filter 0.2s ease;
}

.tut-flip-btn {
  margin-bottom: 4px; padding: 3px 8px;
  font: inherit; font-size: 0.68rem; font-weight: 700;
  color: var(--text); background: #100e15;
  border: 1px solid #55505f; border-radius: 6px; cursor: pointer;
}
.tut-flip-btn:hover { background: var(--accent2); color: #14121a; border-color: var(--accent2); }

/* Three things to try; each ticks off once done. */
.tut-goals {
  list-style: none; padding: 0; margin: 12px 0 0;
  display: flex; flex-wrap: wrap; gap: 6px 18px;
  font-size: 0.85rem; color: var(--muted);
}
.tut-goals li { display: flex; align-items: center; gap: 6px; }
.tut-tick { width: 13px; height: 13px; border: 1px solid #55505f; border-radius: 4px; display: inline-block; }
.tut-goals li.done { color: #8fd39f; }
.tut-goals li.done .tut-tick { background: #3c8f52; border-color: #3c8f52; }
.tut-good { color: #8fd39f; }

.tut-dice-row { display: flex; gap: 10px; justify-content: center; }
.tut-die { width: clamp(44px, 7vh, 72px); height: clamp(44px, 7vh, 72px); }
.tut-die-best { filter: drop-shadow(0 0 7px var(--accent2)); }
.tut-die-dim { opacity: 0.4; }
.tut-roll-area { display: flex; flex-direction: column; align-items: center; gap: 8px; margin-bottom: 10px; }

.tut-tiers { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0 4px; justify-content: center; }
.tut-tier {
  padding: 7px 12px; background: #1c1926; border: 1px solid #34303f;
  border-radius: 8px; font-size: 0.9rem;
}
.tut-tier-roll {
  display: inline-block; min-width: 2em; margin-right: 6px; padding: 1px 6px;
  text-align: center; font-weight: 700; color: #ffe6b3;
  background: #3a2a10; border: 1px solid var(--accent2); border-radius: 5px;
}
.tut-tier-miss .tut-tier-roll { color: #ffd9d5; background: #3a1a18; border-color: var(--accent); }

.tut-types { display: flex; flex-direction: column; gap: 7px; margin: 12px 0; }
.tut-type { padding: 9px 12px; border-radius: 8px; border-left: 3px solid; background: #1c1926; }
.tut-type h4 { margin: 0 0 3px; font-size: 0.95rem; }
.tut-type h4 span { font-size: 0.72rem; font-weight: 400; opacity: 0.6; text-transform: uppercase; letter-spacing: 0.06em; }
.tut-type p { margin: 0; font-size: 0.88rem; color: var(--muted); }
.tut-type.weapon { border-left-color: #3c8f52; } .tut-type.weapon h4 { color: #6ec98a; }
.tut-type.spell { border-left-color: #3f7dc9; } .tut-type.spell h4 { color: #7fb0e8; }
.tut-type.extra_die { border-left-color: #d8d8d8; } .tut-type.extra_die h4 { color: #eaeaea; }
.tut-type.action { border-left-color: var(--accent); } .tut-type.action h4 { color: #ef7a72; }
.tut-type.maker_only { border-left-color: #a06a35; } .tut-type.maker_only h4 { color: #d59a5c; }

.tut-action-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.tut-action {
  flex: 1 1 130px; padding: 11px 8px; font-weight: 700; font-size: 0.92rem;
  color: var(--text); background: #100e15; border: 1px solid #45414f;
  border-radius: 8px; cursor: pointer;
}
.tut-action:hover { border-color: var(--accent2); }
.tut-action.selected { background: #251f2e; border-color: var(--accent2); color: var(--accent2); }
.tut-detail { margin-top: 12px; min-height: 4.5em; font-size: 0.92rem; line-height: 1.6; color: var(--muted); }
.tut-detail h4 { margin: 0 0 5px; color: var(--accent2); }
.tut-detail p { margin: 0 0 6px; }

.tut-gains { display: flex; flex-direction: column; gap: 9px; margin: 12px 0; }
.tut-gain { display: flex; gap: 11px; align-items: flex-start; padding: 10px 12px; background: #1c1926; border: 1px solid #34303f; border-radius: 9px; }
.tut-gain > span { font-size: 1.4rem; }
.tut-gain div { font-size: 0.9rem; line-height: 1.5; }


.tut-recap { display: flex; flex-direction: column; gap: 7px; margin: 14px 0; }
.tut-recap div { display: flex; gap: 11px; align-items: center; padding: 9px 12px; background: #1c1926; border-radius: 8px; font-size: 0.93rem; }
.tut-recap span { font-size: 1.15rem; }
.tut-finish { margin-top: 22px; text-align: center; }

.tutorial-footer {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px clamp(12px, 3vw, 30px);
  border-top: 1px solid #2f2b3a; background: #17151f;
}
.tut-nav-btn {
  padding: 9px 20px; font-weight: 700; color: var(--text);
  background: #100e15; border: 1px solid #45414f; border-radius: 8px; cursor: pointer;
}
.tut-nav-btn:hover:not(:disabled) { border-color: var(--accent2); color: var(--accent2); }
.tut-nav-btn:disabled { opacity: 0.35; cursor: default; }
.tut-nav-next { border-color: var(--accent2); color: var(--accent2); }
.tutorial-hint { font-size: 0.78rem; color: var(--muted); }
/* The bottom exit button is a mobile-only affordance (see mobile.css) - on
   desktop the existing centered top button (#tutorial-exit) is already easy
   to reach, so this stays hidden there rather than showing a redundant
   second exit control. */
#tutorial-exit-bottom { display: none; }

/* v5.0.68: the old icon-only collapse (this used to shrink the sidebar to a
   54px icon strip on narrow desktop windows) is gone now that .tutorial-nav
   is a horizontal row - flex-wrap on .tutorial-nav already handles narrow
   widths by wrapping tabs onto additional rows, full titles intact. */

/* Action prompts: a short, bold command with a quiet one-line hint beneath.
   The glowing cards themselves carry the "Tap to discard" micro-hint, so the
   prompt only has to say WHAT to do, not how. */
/* ---- The one prompt voice -----------------------------------------------
   Every prompt and popup speaks the same way: a command line, then a hint line.
   The discard prompt already did this; the others rolled their own
   <p><strong>Title</strong></p> and a .note, so the game had three different
   treatments for the same idea. They all use these two classes now.

   Note the explicit overrides below: `#overlay-panels .card p` has specificity
   (1,1,1) and was quietly beating `.prompt-command` (0,1,0), so an identical
   prompt rendered at 1.35rem in the turn controls and 1rem inside a popup. */
.prompt-command {
  margin: 0 0 4px;
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: 0.01em;
  color: var(--text);
}
.prompt-hint {
  margin: 0 0 8px;
  font-size: 1.05rem;
  font-weight: 400;
  line-height: 1.4;
  color: var(--muted);
}
#overlay-panels .card .prompt-command,
#turn-controls .prompt-command,
.play-stage .prompt-command {
  font-size: 1.35rem;
  font-weight: 700;
  line-height: 1.25;
  margin: 0 0 4px;
  color: var(--text);
}
#overlay-panels .card .prompt-hint,
#turn-controls .prompt-hint,
.play-stage .prompt-hint {
  font-size: 1.05rem;
  font-weight: 400;
  line-height: 1.4;
  margin: 0 0 8px;
  color: var(--muted);
}
/* The played-card banner's caption is a hint, and is colour-coded by message
   type (.msg-* carry !important), so only the metrics come from here. */
#stage-event .prompt-hint { margin: 6px 0 0; max-width: 42ch; text-align: center; }
/* The revealed-Maker "Enhanced Maker turn: …" hint is a long single sentence that must sit on ONE
   line. Make #turn-controls a query container and size this hint's font to the container's own width
   (cqi) so the sentence always fits one line — from a wide desktop play area down to a narrow phone.
   clamp() keeps it readable; nowrap + ellipsis are the safety net below the floor. */
#turn-controls { container-type: inline-size; }
#turn-controls .enhanced-turn-hint {
  white-space: nowrap;
  font-size: clamp(0.5rem, 2.45cqi, 1.05rem);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
}
#stage-event .prompt-command { margin: 4px 0; font-size: 1.15rem; }

/* Anything left carrying .note in a prompt surface gets the hint's metrics, so
   a stray one can never look like a different typeface again. */
#overlay-panels .card .note,
#turn-controls .note,
.play-stage .note,
.death-prompt-card .note {
  font-size: 0.85rem;
  font-weight: 400;
  line-height: 1.35;
  color: var(--muted);
}
/* ...with ONE exception. The assign-dice warning is the note that explains why
   the player's Attack is blocked, so it must not read as quiet grey body text.
   `.attack-warning` (0,1,0) already asked for red back at its own rule, but the
   unification above is `#turn-controls .note` (1,1,0) and silently outranked it,
   repainting the warning muted — the rule looked correct in isolation and lost
   the cascade. Restated here, after and more specific, so it actually wins. */
#turn-controls .note.attack-warning,
#overlay-panels .card .note.attack-warning,
.play-stage .note.attack-warning {
  color: var(--accent);
  font-weight: 600;
}
/* Buttons in prompt surfaces share one size too. */
#overlay-panels .card button,
#turn-controls .end-turn-btn,
.victory-btn { font-family: inherit; }

/* Tutorial: wide illustration (the board) and a card-beside-text layout. */
.tut-wide { margin: 14px 0; }
.tut-wide img {
  width: 100%;
  max-height: 34vh;
  object-fit: contain;
  border-radius: 14px;
  border: 1px solid #45414f;
  background: #17151f;
  display: block;
}
.tut-wide figcaption { margin-top: 6px; font-size: 0.78rem; color: var(--muted); text-align: center; }
/* The live in-game board SVG shown in the "The Dungeon" tutorial page. Fit it to
   the figure like an image, in the same framed box. The board is wide, so it
   scales down to fit and scrolls horizontally only if truly necessary. */
.tut-board-svg-wrap {
  width: 100%;
  border-radius: 10px;
  border: 1px solid #45414f;
  background: #17151f;
  padding: 10px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.tut-board-svg-wrap .board-svg {
  width: 100%;
  max-width: 100%;
  max-height: 34vh;
  height: auto;
  margin: 0 auto;
  display: block;
}

.tut-split-art { display: flex; flex-direction: column; gap: 16px; align-items: center; margin: 12px 0; justify-content: center; }
.tut-split-art .tut-card { flex: 0 0 auto; margin: 0 auto; }
.tut-split-art-body { flex: 0 0 auto; width: 100%; max-width: 420px; min-width: 0; }

/* --- Identity reveal + Maker reveal screens ------------------------------- */
/* Both reuse .victory-screen / .victory-card, so they inherit the same dimmed
   full-viewport overlay, emblem, title and button treatment. */

/* v5.0.73: was `clamp(120px, 20vh, 200px)` - height-driven (vh) and capped at a
   flat 200px regardless of monitor WIDTH, so on a large desktop this card sat
   far smaller than the lobby's own responsively-scaled cards (--lobby-card-w,
   up to 400px) or the in-game hand-row cards, which is what the side-by-side
   screenshot flagged. Switched to a width-driven (vw) clamp so it actually
   grows with the monitor like everything else in the pre-game/reveal chrome.
   v5.0.76: that width-driven clamp (24vw, capped at 460px) worked out to roughly
   690px TALL on a typical desktop (460px * the art's 3:2 aspect) - taller than
   most viewports leave room for once the title/facts/button beneath it are
   counted, so the card ended up "maximizing" the screen and scrolling. Switched
   the driving axis to height instead, targeting ~50% of the viewport height, so
   the art scales WITH the screen without dominating it. The new 420px ceiling
   (and its ~280px-wide companion at the 3:2 aspect) both sit comfortably under
   the source identity art's native 500x750px, so every size here is a
   downscale - never the upscale-blur risk the old width-driven ceiling flagged. */
.identity-intro-card {
  height: clamp(160px, 50vh, 420px);
  width: auto;
  border-radius: clamp(8px, 1.4vh, 16px);
  border: 1px solid #45414f;
  /* `auto` is load-bearing: .victory-card centres its children with
     text-align, which has no effect on a fixed-width block element. */
  margin: 4px auto 14px;
  display: block;
}

/* v5.0.73: the identity-reveal and Maker-reveal cards now grow WITH the
   enlarged art above (was a flat 480px, inherited unscoped from the shared
   base .victory-card - and this screen's own flat 520px) so a wide monitor
   doesn't end up with 460px-wide art sitting inside a narrower-than-that
   480px card, and so the "One of the others is the Maker..." sub-line only
   wraps when it genuinely needs to rather than being squeezed by a stale
   fixed width. Scoped by screen id, NOT a change to the shared bare
   .victory-card rule, so the end-game victory/defeat screen (which also uses
   .victory-card, unscoped) keeps its existing narrower sizing untouched. */
#identity-intro-screen .victory-card { max-width: clamp(480px, 44vw, 640px); width: 94%; }
/* Mobile, direct request: the initial identity-reveal screen's card art was still showing
   SHARP corners on a real phone. Same root cause and same fix already proven for the later
   mid-game Maker-reveal screen (see the html.mobile #maker-reveal-screen .identity-intro-card
   rule and its comment further down) - the shared base .identity-intro-card rule above uses a
   vh-based clamp() for its border-radius, which is confirmed to render rounded in this
   environment's headless Chromium but SQUARE on a real iPhone; only a fixed pixel value
   reliably rounds on a real device. This screen (unlike the Maker-reveal one) has no overflow
   problem to solve - just title, art, one sub-line and one button - so it needs nothing beyond
   the radius fix; the shared height/width sizing is left untouched. 12px roughly matches what
   the clamp() was already resolving to at common phone heights, just as a real fixed value
   instead of a formula that only coincidentally looks right in this test environment. */
html.mobile #identity-intro-screen .identity-intro-card { border-radius: 12px; }
.maker-reveal-card { max-width: clamp(480px, 44vw, 640px); width: 94%; }
/* Wraps the Maker-reveal screen's art (see showMakerRevealScreen in app.js). On desktop this
   is a no-op - `display: contents` makes the wrapper generate no box of its own, so the
   .identity-intro-card <img> inside behaves exactly as if it were still a direct child of
   .maker-reveal-card, unaffected by this wrapper's mere existence in the DOM. Only the
   html.mobile override further down (see its own comment) turns this wrapper into an actual
   flex item - that's a mobile-only sizing fix, this screen's desktop layout never had the bug
   it addresses. */
.maker-reveal-art-wrap { display: contents; }
/* Smaller than the base .victory-title (used elsewhere for the identity-intro and
   end-game victory screens, which stay full-size) - the eye emblem that used to sit
   above this title is gone, so the title itself is the top of the card now and reads
   better at a lower weight/size than a screen-topping emblem+title pair needed. */
.maker-reveal-title { font-size: 1.9rem; margin: 0 0 10px; }
.maker-reveal-facts {
  display: flex;
  flex-direction: column;
  gap: 7px;
  width: 100%;
  margin: 4px 0 14px;
  text-align: left;
}
.maker-reveal-facts > div {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 8px 12px;
  background: rgba(0, 0, 0, 0.28);
  border: 1px solid #4a3230;
  border-radius: 8px;
}
.maker-reveal-facts span { font-size: 1.25rem; flex: 0 0 auto; }
.maker-reveal-facts p { margin: 0; font-size: 0.92rem; line-height: 1.45; }
/* 15s auto-advance countdown under the Fight / Let-them-come button. Same shrinking-bar
   language as the in-play countdowns, sized for the reveal card. */
.maker-reveal-countdown { margin: 12px auto 0; max-width: 260px; }
.maker-reveal-countdown-bar {
  height: 5px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.16);
  overflow: hidden;
}
.maker-reveal-countdown-bar > span {
  display: block;
  height: 100%;
  width: 100%;
  border-radius: 999px;
  background: var(--accent2);
  transition: width 0.1s linear;
}
.maker-reveal-countdown-text {
  margin: 4px 0 0;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: center;
  color: var(--muted);
}
.maker-reveal-countdown-urgent .maker-reveal-countdown-bar > span { background: var(--accent); }
.maker-reveal-countdown-urgent .maker-reveal-countdown-text { color: var(--accent); }
.maker-reveal-waiting {
  margin: 6px 0 4px;
  color: var(--muted);
  font-size: 0.95rem;
  animation: waiting-pulse 1.8s ease-in-out infinite;
}
@keyframes waiting-pulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .maker-reveal-waiting { animation: none; opacity: 0.85; } }
.maker-reveal-close {
  margin: 0 0 16px;
  font-size: 0.95rem;
  font-style: italic;
  color: var(--accent2);
}

/* Mobile, direct request: the Maker-reveal screen (title + art + sub-line/culprit-name +
   4 facts rows + close-line + button + countdown) was sized for desktop and easily ran
   taller than a phone's viewport - the shared .identity-intro-card art alone claims up to
   50vh, leaving too little room for everything under it, so the player had to scroll the
   whole screen just to find the confirm button. This is a `html.mobile`-scoped override
   ONLY (the shared base rules above, and harness/identity-maker-reveal-card-scale.js which
   pins their desktop values, are untouched) that shrinks every piece so the full card fits
   inside a real phone viewport without scrolling. Scoped by #maker-reveal-screen (not the
   bare shared classes) so the identity-intro screen and the end-game victory/defeat screens,
   which reuse some of these same classes, keep their existing sizing. Verified live at
   iPhone-SE-sized viewports (the shortest common target) with the confirm button visible
   with no scroll needed; extra headroom left over the calculated fit versus a bare-minimum
   squeeze, both because real facts text can wrap differently than assumed and because this
   environment's headless browser understates real emoji glyph height (see project testing
   notes) - so real devices have more margin, not less, than what was measured here. */
html.mobile #maker-reveal-screen.victory-screen { padding: 10px; }
/* v5.1 follow-up (direct request): a fixed small clamp on the art left headroom unused on
   taller phones - the ask is for the art to grow to fill whatever room is actually left over,
   on THIS device, after every other component has taken the space it needs, not just avoid
   overflowing. Switched the card to a flex column with an explicit height (not auto - a flex
   item can't be told to "grow into leftover space" unless the container's own size is fixed,
   otherwise the container just grows to wrap its content and there's no leftover to give):
   height = the padded screen's own available space (100dvh minus the 10px+10px padding above).
   Every text element below keeps its natural (tightened) size and flex-shrink:0, so they are
   never the ones who give up space - only the art (flex: 1 1 0, min-height:0) is elastic,
   claiming 100% of whatever's left. object-fit:contain scales the actual artwork as large as
   the resulting box allows while preserving its aspect ratio (letterboxing on whichever axis
   the box is wider/taller than the art needs, rather than distorting it). */
html.mobile #maker-reveal-screen .maker-reveal-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  height: calc(100dvh - 20px);
  max-height: 96dvh; /* small buffer vs. the calc() above, in case dvh itself rounds differently */
  overflow-y: auto;  /* safety net only - the art shrinks toward 0 long before this could trigger */
}
/* Direct bug report (with a real-device photo): "the rounded corner bounding box is not
   shrunk down enough width-wise to affect the card art properly." Root cause was the old
   `.identity-intro-card` mobile rule using `width: 100%; object-fit: contain;` while ALSO
   being the flex item that flex-grow stretches to fill leftover height (`flex: 1 1 0`) -
   stretching the <img> element's own BOX to the full card width, then relying on
   object-fit to letterbox the actual 500x750 (2:3) art down to fit that flex-resolved
   height. object-fit only scales the VISIBLE PIXELS; it does not shrink the element's own
   border box, which is what border-radius clips to. So the rounded corners landed on the
   wide, mostly-empty outer box, while the narrower letterboxed art sitting inside it kept
   its own plain square corners - visibly unrounded, exactly as reported, with empty gaps
   on either side.
   FOUR follow-up attempts to derive the img's on-screen box from its own aspect ratio, all
   pure CSS, were each LIVE-MEASURED in a real mobile viewport (getBoundingClientRect(), not
   a static CSS read - see project testing discipline) and none of them worked: plain
   `width: auto`; an explicit `aspect-ratio: 500 / 750` alongside it; `position: absolute`
   with `height: 100%; width: auto;` (chosen because an absolutely positioned replaced
   element is supposed to use the ordinary, unambiguous CSS2 10.3.7 sizing algorithm); and a
   flex-centered wrap with the img's own `width`/`height` still left for the browser to
   auto-derive. Every one of them measured the img's rendered box matching its CONTAINER's
   box, not the image's real 0.6667 (500/750) aspect ratio - this engine simply does not
   reliably derive a replaced element's cross-axis size from its intrinsic ratio in any of
   these configurations, contrary to what the CSS2 spec text would predict. Worse, the
   wrap's own box isn't even guaranteed to be exactly 2:3 itself (its width comes from the
   card's own layout, its height from flex-grow into leftover space) - so even a correct
   auto-derivation on ONE axis alone wouldn't guarantee a letterbox-free fit; the fit needs
   to check BOTH axes and bind to whichever is tighter.
   The fix that actually stuck abandons CSS-only sizing entirely: `sizeMakerRevealArt()` in
   app.js measures `.maker-reveal-art-wrap`'s real resolved box (after it has flex-grown to
   fill the leftover space below the title/facts/button) and computes the largest 500:750
   box that fits inside it - exactly the "fit inside a box, preserve aspect ratio" job
   object-fit: contain claims to do, just computed explicitly in JS against the real
   resolved box instead of trusted to letterbox an oversized element. It then sets that
   width/height as explicit inline pixel styles on the <img> itself - no CSS auto-sizing
   involved at any point, so none of the ambiguity above applies. The wrap is just an
   ordinary flex container (`display: flex; align-items: center; justify-content: center;`)
   that centers the now-exactly-sized img inside whatever leftover space it doesn't fill;
   the img itself carries the fixed-pixel border-radius directly (same proven "fixed px,
   never a vh/clamp()/percentage formula" pattern as every other card-art radius in this
   app) since its own box now matches the visible art exactly, with zero letterboxing to
   clip around. Confirmed live via a magenta-background + full-page-screenshot +
   getBoundingClientRect() crop (the project's own documented reliable method, since
   .screenshot()'s own per-element clip region has separate quirks) - see
   harness/mobile-identity-intro-card-radius.js and the round's summary doc for the actual
   measurement. */
html.mobile #maker-reveal-screen .maker-reveal-art-wrap {
  display: flex; /* overrides the base .maker-reveal-art-wrap's `display: contents` - this
    wrap must generate a real box here: it's both the flex item that grows to fill leftover
    height, AND the flex container that centers the now-fixed-size <img> inside it. No
    overflow/radius needed here - the img's own box now matches the visible art exactly,
    so there's nothing left for a wrap-level clip to do. */
  align-items: center;
  justify-content: center;
  flex: 1 1 0;
  min-height: 0;
  width: 100%;
  margin: 2px auto 6px;
}
html.mobile #maker-reveal-screen .identity-intro-card {
  /* width/height are set inline in px by sizeMakerRevealArt() in app.js, computed as the
     largest 500:750 box that fits inside the wrap's real resolved box - see the block
     comment above for why this is JS, not CSS. */
  display: block;
  margin: 0;
  border-radius: 14px; /* FIXED pixel radius, not a vh/clamp() formula - see the block comment above */
  max-width: 100%;  /* safety net only, in case sizeMakerRevealArt() hasn't run yet */
  max-height: 100%; /* same */
}
html.mobile #maker-reveal-screen .maker-reveal-title { flex: 0 0 auto; font-size: 1.2rem; margin: 0 0 5px; }
html.mobile #maker-reveal-screen .victory-sub { flex: 0 0 auto; font-size: 0.8rem; margin-bottom: 8px; }
html.mobile #maker-reveal-screen .culprit-name { font-size: clamp(1.3rem, 4.5dvh, 2rem); }
html.mobile #maker-reveal-screen .culprit-tail { font-size: 0.75rem; }
html.mobile #maker-reveal-screen .maker-reveal-facts { flex: 0 0 auto; width: 100%; gap: 4px; margin: 2px 0 8px; }
html.mobile #maker-reveal-screen .maker-reveal-facts > div { padding: 5px 9px; gap: 8px; }
html.mobile #maker-reveal-screen .maker-reveal-facts span { font-size: 1rem; }
html.mobile #maker-reveal-screen .maker-reveal-facts p { font-size: 0.76rem; line-height: 1.3; }
html.mobile #maker-reveal-screen .maker-reveal-close { flex: 0 0 auto; margin: 0 0 8px; font-size: 0.8rem; }
html.mobile #maker-reveal-screen .victory-btn { flex: 0 0 auto; padding: 10px 20px; font-size: 0.95rem; }
html.mobile #maker-reveal-screen .maker-reveal-countdown { flex: 0 0 auto; margin: 8px auto 0; }

/* Fade the reveal screen out, and the game screen in behind it. */
.screen-fading-out { animation: screen-fade-out 0.4s ease-in forwards; pointer-events: none; }
@keyframes screen-fade-out {
  from { opacity: 1; transform: scale(1); }
  to   { opacity: 0; transform: scale(1.03); }
}
.screen-fading-in { animation: screen-fade-in 0.85s ease-out; }
@keyframes screen-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}


/* ==========================================================================
   v2.21 additions
   ========================================================================== */

/* --- Status chips beside a player's HP ---------------------------------- */
/* Every condition here is cleared server-side the moment it lifts, so the chip
   disappears on the next broadcast — free a trapped player and it's gone. */
.status-badges { display: inline-flex; flex-wrap: wrap; gap: 4px; margin-left: 8px; vertical-align: middle; }
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid transparent;
  cursor: help;
}
.status-badge span { font-size: 0.72rem; }
.status-trapped { background: #3a2a10; color: #f0c26b; border-color: #8a6a28; }
.status-skip    { background: #2a1416; color: #ef8a82; border-color: #8a3a34; }
.status-poison  { background: #1c2f22; color: #7fd39a; border-color: #35714a; }
.status-exposed { background: #2a2035; color: #c0a3e8; border-color: #5b4482; }
.status-eliminated { background: #3a1a12; color: #f0966b; border-color: #8a4a28; }
/* Trapped is the one that needs someone else to act, so it asks to be noticed. */
.status-trapped { animation: trapped-pulse 1.8s ease-in-out infinite; }
@keyframes trapped-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(240, 194, 107, 0); }
  50%      { box-shadow: 0 0 0 3px rgba(240, 194, 107, 0.22); }
}
@media (prefers-reduced-motion: reduce) { .status-trapped { animation: none; } }

/* --- Battle log: where your last turn ended ----------------------------- */
.log-turn-divider {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 7px 0 5px;
  color: var(--accent2);
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  opacity: 0.75;
}
.log-turn-divider::before,
.log-turn-divider::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--accent2), transparent);
}
.log-turn-divider span { flex: 0 0 auto; }

/* --- Maker reveal: name the culprit ------------------------------------- */
.maker-culprit { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.culprit-name {
  font-family: Oswald, "Segoe UI", sans-serif;
  font-weight: 700;
  font-size: clamp(2rem, 7vh, 3.6rem);
  line-height: 1.05;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #ff6b60;
  text-shadow: 0 0 10px rgba(224, 85, 85, 0.9), 0 0 34px rgba(224, 85, 85, 0.55);
  animation: culprit-slam 0.5s cubic-bezier(0.2, 1.4, 0.4, 1) both;
}
.culprit-tail { font-size: 0.95rem; color: var(--muted); letter-spacing: 0.02em; }
@keyframes culprit-slam {
  0%   { transform: scale(1.7); opacity: 0; filter: blur(6px); }
  100% { transform: scale(1);   opacity: 1; filter: blur(0); }
}
@media (prefers-reduced-motion: reduce) { .culprit-name { animation: none; } }

/* --- Dragging a card to rearrange your inventory ------------------------ */
/* Without these the browser drags the card ART (a native image drag) and fires
   pointercancel, killing the reorder gesture before it starts. */
/* `grab` only when a grab is actually possible — off-turn the hand is read-only. */
.hand-row .hand-card { user-select: none; -webkit-user-select: none; cursor: default; }
#hand-panel.can-rearrange .hand-row .hand-card { cursor: grab; }
.hand-row .hand-card img { -webkit-user-drag: none; user-drag: none; }
.hand-row .hand-card.needs-selection { cursor: pointer; }
.hand-row .hand-card { transition: box-shadow 0.12s ease, margin 0.14s ease, opacity 0.14s ease; }

/* The card in your hand. Lifted, tilted, ringed in gold, and see-through — so the
   position it would land at (the insertion bar / the card being displaced) stays
   visible underneath it while you're actively deciding where to drop it. */
.hand-card.dragging {
  z-index: 20;
  cursor: grabbing;
  transform-origin: center;
  transition: none;
  opacity: 0.5;
  box-shadow:
    0 0 0 2px #f2c96a,
    0 0 26px 4px rgba(242, 201, 106, 0.5),
    0 18px 44px rgba(0, 0, 0, 0.82);
}
/* Everything you're not holding recedes, so the insertion bar has contrast. */
.hand-row.reordering .hand-card:not(.dragging) { opacity: 0.62; }

/* Where it will land. A 3px gold edge was invisible against a card border that is
   already 2px and coloured; this is a lit bar with a gap opened beside it, so the
   slot you're dropping into is unmistakable. */
.hand-card.drop-before,
.hand-card.drop-after {
  opacity: 1 !important;
  animation: drop-bar 0.85s ease-in-out infinite;
}
.hand-card.drop-before { margin-left: 15px; }
.hand-card.drop-after  { margin-right: 15px; }
@keyframes drop-bar {
  0%, 100% {
    box-shadow: -0px 0 0 0 transparent,
                inset 0 0 0 1px rgba(242, 201, 106, 0.3);
  }
  50% { box-shadow: inset 0 0 0 1px rgba(242, 201, 106, 0.55); }
}
/* The bar itself: drawn as a spread shadow so #hand-panel's overflow can't clip
   a pseudo-element out of existence. */
.hand-card.drop-before {
  box-shadow: -8px 0 0 -1px #f2c96a, -8px 0 20px 2px rgba(242, 201, 106, 0.8);
  animation: drop-bar-left 0.85s ease-in-out infinite;
}
.hand-card.drop-after {
  box-shadow: 8px 0 0 -1px #f2c96a, 8px 0 20px 2px rgba(242, 201, 106, 0.8);
  animation: drop-bar-right 0.85s ease-in-out infinite;
}
@keyframes drop-bar-left {
  0%, 100% { box-shadow: -8px 0 0 -1px #f2c96a, -8px 0 14px 1px rgba(242, 201, 106, 0.55); }
  50%      { box-shadow: -9px 0 0 -1px #ffe1a3, -9px 0 26px 5px rgba(242, 201, 106, 0.95); }
}
@keyframes drop-bar-right {
  0%, 100% { box-shadow: 8px 0 0 -1px #f2c96a, 8px 0 14px 1px rgba(242, 201, 106, 0.55); }
  50%      { box-shadow: 9px 0 0 -1px #ffe1a3, 9px 0 26px 5px rgba(242, 201, 106, 0.95); }
}
@media (prefers-reduced-motion: reduce) {
  .hand-card.drop-before, .hand-card.drop-after { animation: none; }
}

/* --- Bot doodle (5s hover on an AI's name) ------------------------------ */
/* The card itself is drawn inside the SVG (parchment, ink, serif type — see
   bot-doodle.js), so the wrapper adds nothing but a shadow and a lazy tilt, as
   though it had been tossed on the table. */
.bot-doodle {
  position: fixed;
  z-index: 3000;
  width: 178px;
  pointer-events: none;
  opacity: 0;
  transform: translateY(6px) rotate(-2deg);
  transition: opacity 0.18s ease, transform 0.18s ease;
  filter: drop-shadow(0 12px 26px rgba(0, 0, 0, 0.7));
}
.bot-doodle.visible { opacity: 1; transform: translateY(0) rotate(-2deg); }
.bot-doodle-art { display: block; }
.bot-doodle-art svg { display: block; width: 100%; height: auto; }

/* Owner-supplied art (assets/bot-doodles/<name>.png) has no card around it, so
   the wrapper draws one to match. */
.bot-doodle.has-art {
  background: #d4bc8d;
  border: 3px solid #2a2118;
  border-radius: 11px;
  padding: 9px 9px 6px;
}
.bot-doodle-art img { width: 100%; height: auto; border-radius: 4px; display: block; }
.bot-doodle-cap {
  margin-top: 5px;
  text-align: center;
  font-family: Georgia, "Times New Roman", serif;
  font-weight: 700;
  font-size: 0.95rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #100c08;
}
.bot-doodle-cap span {
  display: block;
  font-weight: 400;
  font-style: italic;
  text-transform: none;
  font-size: 0.66rem;
  letter-spacing: 0;
  opacity: 0.62;
}


/* ==========================================================================
   v2.25 — end-screen summary, records, high scores, tutorial arrows
   ========================================================================== */

/* --- Whole-game figures, above the awards ------------------------------- */
.game-summary { margin: 0 0 22px; width: 100%; }
.summary-grid {
  display: grid;
  /* Exactly ten figures, so pin five columns and let it fall to two tidy rows
     rather than letting auto-fit strand three cells on a second line. */
  grid-template-columns: repeat(5, minmax(0, 1fr));
  /* Row gap + top margin leave clearance for the rarity banners, which now sit fully ABOVE each
     cell (reaching ~18px up). 22px between rows keeps a second-row banner off the first row's cells. */
  gap: 22px 10px;
  margin-top: 20px;
}
.summary-cell {
  position: relative; /* anchor for the rarity banner */
  background: #17151f;
  border: 1px solid #2f2b3a;
  border-radius: 10px;
  padding: 10px 6px;
  text-align: center;
}
.summary-icon { font-size: 1.2rem; line-height: 1; margin-bottom: 4px; }
.summary-value {
  font-family: "Oswald", "Segoe UI", sans-serif;
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--accent2);
  line-height: 1.1;
}
.summary-label {
  font-size: 0.68rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-top: 2px;
}

/* --- A record broken this game ------------------------------------------ */
.record-banner {
  margin: 0 0 14px;
  padding: 8px 16px;
  border-radius: 999px;
  display: inline-block;
  background: #3a2a10;
  color: #f0c26b;
  border: 1px solid #8a6a28;
  font-weight: 700;
  letter-spacing: 0.03em;
}
/* Direct request: a separate, cooler-toned banner for records that only beat
   this game's own player count, not every size (see .stat-record-size and
   .record-flag-size above for the matching per-award treatment). Two banners
   can appear together (an all-time record AND a size-only record, for two
   different awards, in the same game) - each has its own bottom margin so
   they stack cleanly rather than needing a shared wrapper. */
.record-banner.record-banner-size {
  background: #16232d;
  color: #9fc6e3;
  border-color: #3d5a70;
}
.stat-award.stat-record,
.highscore-card.stat-record {
  border-color: var(--accent2);
  box-shadow: 0 0 0 1px var(--accent2) inset, 0 0 22px rgba(217, 164, 65, 0.28);
}
/* Direct request: a #1 finish that's only a record for THIS player count (not
   also the all-time best across every size) gets its own middle tier - a real
   record, so it still gets a colored border and glow like .stat-record, just
   a cooler, quieter tone than the gold all-time treatment, and dialed back
   from it (a smaller glow) so gold still reads as the bigger deal. Sits
   between .stat-record (gold, all-time) and .stat-topfive (plain muted
   outline, no glow at all) in visual weight. */
.stat-award.stat-record-size,
.highscore-card.stat-record-size {
  border-color: #7fa8c9;
  box-shadow: 0 0 0 1px #7fa8c9 inset, 0 0 14px rgba(127, 168, 201, 0.22);
}
/* Made the persistent top 5, but didn't take #1 - a real deal, so it still
   gets its own border, just quieter than .stat-record's gold glow: a plain
   muted-silver outline with no box-shadow bloom. Deliberately less exciting,
   per the design intent of splitting this off from "New record" at all. */
.stat-award.stat-topfive,
.highscore-card.stat-topfive {
  border-color: var(--muted);
}
.stat-award { position: relative; }
.record-flag {
  position: absolute;
  top: -9px;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  padding: 2px 9px;
  border-radius: 999px;
  background: var(--accent2);
  color: #1a1826;
  font-size: 0.58rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
/* The size-specific record flag (see .stat-record-size above) - same shape as
   the gold .record-flag, recolored to the same cooler blue-silver tone so the
   two are never mistaken for each other at a glance. */
.record-flag.record-flag-size {
  background: #7fa8c9;
  color: #14212c;
}
.topfive-flag {
  position: absolute;
  top: -9px;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  padding: 2px 9px;
  border-radius: 999px;
  background: #3a3745;
  color: var(--muted);
  border: 1px solid var(--muted);
  font-size: 0.58rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

/* --- All-time high scores ----------------------------------------------- */
/* One award at a time, flanked by arrows, with a chip strip to jump. The old
   auto-fit grid squeezed each tile to ~240px and `overflow-wrap: anywhere` then
   broke short names one letter per line. */
#highscore-screen { z-index: 2600; }
.hs-card { max-width: 640px; }

.hs-viewer {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin-top: 10px;
}
.hs-arrow {
  flex: 0 0 auto;
  width: 42px;
  height: 74px;
  padding: 0;
  font-size: 1.9rem;
  line-height: 1;
  font-weight: 700;
  color: var(--muted);
  background: #14121a;
  border: 1px solid #45414f;
  border-radius: 10px;
  cursor: pointer;
  transition: color 0.14s ease, border-color 0.14s ease, box-shadow 0.14s ease, transform 0.1s ease;
}
.hs-arrow:hover { color: var(--accent2); border-color: var(--accent2); box-shadow: 0 0 18px rgba(217, 164, 65, 0.3); }
.hs-arrow:active { transform: scale(0.96); }

.hs-award {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  background: #17151f;
  border: 1px solid #2f2b3a;
  /* v5.1: bigger box to match the Card Compendium's card scale (.comp-card's
     18px padding / 12px radius) - direct request ("think card compendium
     scale"). */
  border-radius: 14px;
  padding: 20px 22px 18px;
  text-align: center;
}
.hs-award-icon { font-size: 2.2rem; line-height: 1; margin-bottom: 6px; }
.hs-award-title {
  margin: 0;
  font-family: "Oswald", "Segoe UI", sans-serif;
  /* v5.1: bumped from 1.2rem, matching .comp-text h4's 1.3rem card-name scale
     plus a touch more since this title carries a whole award category. */
  font-size: 1.4rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--accent2);
}
.hs-award-cat { margin: 4px 0 12px; font-size: 0.92rem; color: var(--muted); }

.highscore-list { list-style: none; margin: 0; padding: 0; text-align: left; }
.highscore-list li {
  display: grid;
  /* A fixed rank column, then the name, a SHORT fixed leader, and the detail -
     all sized to their own content (v5.1 - was `1fr` on the leader column,
     which stretched to fill the row and stranded the name and stat at
     opposite edges; direct request to pull them back close together). With
     every column auto/fixed width, the grid's default left-anchoring keeps
     the whole rank+name+detail cluster compact - any leftover row width just
     becomes blank space after it instead of being the gap BETWEEN them. */
  /* The detail column gets a shrinkable minmax(0, auto), not a bare auto - on a
     narrow box a long detail string (e.g. "Survived to the very end") is
     allowed to give up width and wrap to a second line instead of forcing the
     whole row (and the page) wider than the box - a pre-existing overflow on
     narrow mobile widths that v5.1's size bump made noticeably worse (see
     harness/highscores-detail-wrap.js). */
  grid-template-columns: 1.8em auto auto minmax(0, auto);
  /* Grid's default justify-content (normal, which resolves to stretch) would
     otherwise divide the row's leftover width EQUALLY across the three auto
     columns, re-inflating the leader and detail gaps right back out - this is
     the actual fix that makes the auto/auto/auto columns pack tight instead
     of stretching to fill the row. */
  justify-content: start;
  gap: 12px;
  align-items: baseline;
  padding: 10px 2px;
  border-bottom: 1px dashed #2b2733;
  /* v5.1: bumped again, closer to the Compendium's card-name scale (direct request). */
  font-size: 1.2rem;
}
.highscore-list li:last-child { border-bottom: none; }
/* Direct request (v5.1): #1 no longer gets a colored name just for being #1 -
   only a rarity tier (see "Rarity banners on the High Scores leaderboard"
   below) earns colored text now. Still bold, so rank 1 remains visually
   distinct at a glance, just without implying it's something rarer than it
   is when it's merely the current best-known score. */
.highscore-list li.is-best .hs-player { font-weight: 700; }
.highscore-list li.hs-empty { opacity: 0.38; }
.hs-rank { color: var(--muted); font-variant-numeric: tabular-nums; }
/* All-Time tab only: each entry can come from a different player-count game,
   so it's tagged with which one (e.g. "5p") right after the name - small and
   muted so it reads as a footnote, not a second stat. No grid column of its
   own; it's just extra inline content inside .hs-player's existing auto-width
   cell (see highScoreRowHtml's `showCount` param), so the per-size tabs (which
   never pass it) are completely unaffected. */
.hs-count-badge { color: var(--muted); font-weight: 400; font-size: 0.7em; }
.hs-player {
  color: var(--text);
  font-weight: 600;
  /* Names are capped at 15 characters everywhere (input maxlength + server), so
     the leaderboard should always show them in full on one line - this column
     must NOT shrink below that (unlike .hs-detail, which deliberately gives up
     width - see the grid comment above). Dropping min-width back to its grid-item
     default (auto, i.e. the text's own natural width) is what actually guarantees
     that: an explicit `min-width: 0` here previously let this column get squeezed
     under pressure from the other columns, and since white-space stayed nowrap
     with overflow:visible (no clipping, no wrap), a long name just silently
     spilled into the leader-line/detail columns instead of visibly failing -
     caught live once names were allowed past 10 characters (v5.1). */
  white-space: nowrap;
  overflow: visible;
}
/* A short dotted tick between the name and its stat - just enough to read as
   connective tissue at close range, not a bridge across a wide gap (v5.1:
   shrunk from a row-spanning `1fr` to a small fixed width, now that the name
   and stat sit right next to each other). Sits on the text baseline
   (translateY-equivalent margin nudges it up off the descender line). */
.hs-leader {
  align-self: end;
  height: 1px;
  width: 20px;
  margin: 0 0 0.5em;
  background-image: linear-gradient(to right, transparent, #4a465a 15%, #4a465a 85%, transparent);
}
.highscore-list li.hs-empty .hs-leader { background-image: linear-gradient(to right, transparent, #37333f 15%, #37333f 85%, transparent); }
/* white-space stays normal (not nowrap) so a long detail string wraps to a
   second line, within the shrinkable minmax(0, auto) column above, rather
   than forcing the row (and the page) wider than its box on a narrow screen. */
.hs-detail { color: var(--muted); font-size: 1.05rem; text-align: right; overflow-wrap: break-word; min-width: 0; }

.hs-counter {
  margin: 10px 0 6px;
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.hs-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 5px;
  max-width: 100%;
  margin-bottom: 12px;
}
.hs-chip {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  min-height: 0;
  padding: 0;
  font-size: 0.95rem;
  line-height: 1;
  border-radius: 8px;
  background: #100e15;
  border: 1px solid #2f2b3a;
  cursor: pointer;
  filter: grayscale(0.7);
  opacity: 0.62;
  transition: opacity 0.12s ease, filter 0.12s ease, border-color 0.12s ease;
}
.hs-chip:hover { opacity: 1; filter: none; }
.hs-chip.active { opacity: 1; filter: none; border-color: var(--accent2); box-shadow: 0 0 0 1px var(--accent2) inset; }

.hs-footer { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; width: 100%; }
/* Global high-scores: pick a game size first, then browse that size's records. */
.hs-pick-label { text-align: center; color: var(--muted); margin: 4px 0 10px; }
.hs-count-grid { display: flex; flex-direction: column; gap: 10px; align-items: stretch; margin-bottom: 14px; }
.hs-count-btn { width: 100%; }
.hs-count-label { text-align: center; color: var(--accent2); font-weight: 700; margin: 0 0 8px; }
.hs-award .record-flag { top: -10px; }
.hs-award .topfive-flag { top: -10px; }

/* --- Glyphs on the turn-action buttons ---------------------------------- */


/* --- Caption above a magnified card ------------------------------------- */
/* Used by the Event pile ("Last Event"). Hidden unless the hovered element
   carries data-preview-label. */
#card-hover-preview-label {
  display: none;
  margin: 0 auto 6px;
  font-family: "Oswald", "Segoe UI", sans-serif;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: center;
  /* Sat as bare gold text directly on whatever the card art happened to be
     behind it, which on a light or busy card was close to unreadable. A filled
     chip gives it its own surface so it reads against any art. */
  color: #f4d894;
  background: #3a2a10;
  border: 1px solid #8a6a28;
  border-radius: 999px;
  padding: 3px 12px;
  width: fit-content;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
}


/* --- Audio controls ------------------------------------------------------- */
/* Bottom-left so they never collide with the turn controls or the card hover
   preview (both of which live right/centre). Deliberately unobtrusive: this is
   a setting you touch once, not part of the game. */
.audio-controls {
  position: fixed;
  right: 12px;
  bottom: 12px;
  left: auto;
  z-index: 60;
  display: flex;
  flex-direction: row-reverse; /* gear on the right; panel opens leftward, into the screen */
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  border-radius: 999px;
  background: rgba(20, 18, 26, 0.72);
  border: 1px solid #3a3545;
  backdrop-filter: blur(4px);
}
.audio-controls button {
  all: unset;
  position: relative;
  cursor: pointer;
  width: 28px;
  height: 28px;
  line-height: 28px;
  text-align: center;
  border-radius: 50%;
  font-size: 0.95rem;
}
.audio-controls button:hover { background: rgba(255, 255, 255, 0.12); }
.audio-controls button.audio-off { opacity: 0.55; }
/* Gear button: always-visible entry point; the settings live in a popover. */
.audio-gear { font-size: 1.05rem; }
.audio-panel {
  display: flex;
  align-items: center;
  gap: 6px;
}
/* The volume slider ran at the browser's default ~129px, so the usable 0-100
   range was squeezed into about 120 pixels — roughly one step per pixel, with a
   ~16px thumb on top of it. Longer track = the same 100 steps spread over more
   travel, which is what actually makes fine adjustment possible. */
#audio-vol {
  width: 190px;
  max-width: 46vw;
  margin: 0;
  cursor: pointer;
}
.audio-panel[hidden] { display: none; }
/* When open, the panel sits inline next to the gear (pill grows to fit). On the
   mobile in-scroll placement it simply appears in flow beneath the gear. */
.audio-controls.audio-open { padding-right: 10px; }
/* A clear diagonal slash across a muted icon (both music and sfx), drawn in CSS
   so it renders identically everywhere instead of relying on a combining glyph. */
.audio-controls button.audio-off::after {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 26px; height: 2.5px;
  background: #e2544b;
  border-radius: 2px;
  transform: translate(-50%, -50%) rotate(-45deg);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.6);
  pointer-events: none;
}
.audio-controls input[type="range"] {
  width: 78px;
  margin: 0;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
}
/* Custom track + thumb so the handle slides flush to both ends (the native
   accent-color slider insets the thumb, so it can't reach 0% / 100%). */
.audio-controls input[type="range"]::-webkit-slider-runnable-track {
  height: 5px; border-radius: 3px;
  background: #4a4550;
}
.audio-controls input[type="range"]::-moz-range-track {
  height: 5px; border-radius: 3px;
  background: #4a4550;
}
.audio-controls input[type="range"]::-moz-range-progress {
  height: 5px; border-radius: 3px; background: var(--accent2, #c8a24a);
}
.audio-controls input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent2, #c8a24a);
  margin-top: -4.5px; /* centre the 14px thumb on the 5px track */
  border: none;
}
.audio-controls input[type="range"]::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent2, #c8a24a); border: none;
}
/* The victory screen sits above everything; don't float controls over it. */
body:has(#victory-screen) .audio-controls { display: none; }

@media (max-width: 720px) {
  .audio-controls { left: 8px; bottom: 8px; padding: 5px 6px; }
  .audio-controls input[type="range"] { width: 54px; }
}


/* ==========================================================================
   Mobile block gate
   --------------------------------------------------------------------------
   Mobile support is disabled for now. An inline script in index.html adds
   .mobile-blocked to <html> on a phone/tablet and unhides #mobile-block. When
   that class is present we hide the entire app and show only the notice, so no
   game code ever renders on an unsupported device.
   ========================================================================== */
#mobile-block { display: none; }

html.mobile-blocked, html.mobile-blocked body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  background: var(--bg);
}
html.mobile-blocked #app { display: none !important; }
html.mobile-blocked #mobile-block {
  display: flex !important;
  position: fixed;
  inset: 0;
  z-index: 100000;
  align-items: center;
  justify-content: center;
  padding: 28px;
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(176, 69, 62, 0.28), transparent 60%),
    var(--bg);
  text-align: center;
}
.mobile-block-inner { max-width: 520px; }
.mobile-block-icon {
  width: 72px;
  height: 116px;
  margin: 0 auto 22px;
  border: 4px solid var(--accent2);
  border-radius: 14px;
  position: relative;
  box-shadow: 0 0 28px rgba(217, 164, 65, 0.35);
}
/* A "no" bar struck across the phone glyph. */
.mobile-block-icon::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 118px;
  height: 4px;
  background: var(--accent);
  transform: translate(-50%, -50%) rotate(-45deg);
  box-shadow: 0 0 12px rgba(176, 69, 62, 0.6);
}
/* The phone's home button dot. */
.mobile-block-icon::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 8px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--accent2);
  transform: translateX(-50%);
}
html.mobile-blocked #mobile-block h1 {
  font-family: "Oswald", "Segoe UI", sans-serif;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent2);
  font-size: clamp(1.5rem, 6vw, 2.2rem);
  line-height: 1.2;
  margin: 0 0 10px;
}
html.mobile-blocked #mobile-block p {
  color: var(--text);
  font-size: clamp(1.1rem, 4.5vw, 1.5rem);
  margin: 0;
}


/* "Take no cards" — the decline option on the defeat-loot prompt. Muted and
   set apart from the card thumbnails above it, so taking loot stays the obvious
   default and declining is a deliberate, secondary choice. */
.claim-decline-btn {
  margin-top: 10px;
  background: transparent;
  border: 1px solid #55505f;
  color: var(--muted);
}
.claim-decline-btn:hover {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text);
  border-color: #6a6478;
}


/* Lobby "Back to main menu" — a calm, secondary action set below Start game.
   Muted outline so it never competes with the primary button, but still clearly
   tappable. */
/* Centre everything in the room lobby — the room code heading, the seat list, each card's
   text and the collapsed Custom Settings / Developer dropdowns — so the whole screen reads
   as one centred column rather than left-aligned text inside centred boxes. */
#room-screen { text-align: center; }
#room-screen #seat-list li { text-align: center; }
#room-screen .dev-summary { text-align: center; }
/* Centre the checkbox rows too (they're left-aligned flex rows by default). */
#room-screen .dev-toggle { justify-content: center; text-align: center; }

/* Start Game and Back-to-main-menu are direct children of the centred #room-screen column
   (width:100% up to the shared max-width), but buttons carry a global `margin-right: 8px`,
   which offsets a full-width button ~4px left of the cards above them. Zero the side margins
   so both buttons line up dead-centre with every other element in the column. */
#room-screen #start-btn, #room-screen #leave-room-btn { margin-left: 0; margin-right: 0; }

/* Player-count buttons stack VERTICALLY, centred, each a consistent width, with the fill
   note full-width beneath them. (They used to sit inline and wrap into a 2x2 / 3+1 block.) */
#player-count-selector {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
}
/* Player-count pills: large 4/5/6 buttons in a single row (mockup facelift,
   v5.0.64 round - brings back the button row the dropdown above replaced,
   per direct request; with 3-player normally disabled this is usually only
   3 buttons, so it doesn't cost the vertical space the old 4-button stack
   did). The active count gets the same gold gradient as the Start Game
   button so it reads as "the one that's picked" at a glance. */
.player-count-pills {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  width: 100%;
}
.player-count-pill {
  min-width: 64px;
  padding: 12px 18px;
  font-size: 1.3rem;
  font-weight: 700;
  text-align: center;
  border-radius: 10px;
  border: 1px solid #55505f;
  background: var(--panel);
  color: var(--accent2);   /* match the gold of the "THE MAKER" title */
  cursor: pointer;
}
.player-count-pill:hover:not(:disabled) { border-color: var(--accent2); }
.player-count-pill-active {
  background: var(--accent2);
  color: #2a1f08;
  border-color: #c8912f;
}
.player-count-pill:disabled { opacity: 0.35; cursor: not-allowed; }
#player-count-selector p, #player-count-selector .note {
  width: 100%;
  margin: 6px 0 0;
}

/* A setting = a checkbox label plus a (?) info button that opens the description.
   The rows sit centred in the box; the (?) is a small round badge that reuses the
   shared card-preview overlay (hover on desktop, tap on mobile) to show its text. */
#room-screen .setting-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 6px 0;
}
#room-screen .setting-row .dev-toggle { margin: 0; }
.setting-info {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid #55505f;
  background: rgba(0, 0, 0, 0.25);
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  user-select: none;
}
.setting-info:hover, .setting-info:focus { color: var(--text); border-color: var(--accent2); background: rgba(224, 184, 58, 0.18); outline: none; }

/* MOBILE lobby: centre the Copy-invite button, and LEFT-align the checkbox settings
   (they read more naturally as a left-aligned list on a narrow screen). The boxes
   themselves stay centred as blocks. */
/* Centre the Copy-invite button on BOTH desktop and mobile. The button carries a global
   side margin; zero it inside the centred invite row so its box sits truly centred. */
#room-screen .invite-row { justify-content: center; }
#room-screen .invite-row > button { margin-left: 0; margin-right: 0; }
html.mobile #room-screen .setting-row { justify-content: center; text-align: center; }
html.mobile #room-screen .setting-row .dev-toggle { justify-content: center; text-align: center; }

/* Handedness (layout orientation) picker — MOBILE ONLY (the layout it toggles is a phone layout).
   Two mutually-exclusive checkboxes, side by side, defaulting to Right-handed. */
#handedness-select { display: none; }               /* hidden on desktop */
html.mobile #handedness-select { display: block; text-align: center; }
.handedness-row { display: flex; flex-direction: row; justify-content: center; flex-wrap: wrap; gap: 18px; margin-top: 6px; }
.handedness-opt { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; font-size: 0.9rem; }
.handedness-opt input { width: auto; display: inline-block; margin: 0; }

.leave-room-btn {
  display: block;
  /* Center like every other room-screen child — via the column's align-items:center,
     NOT margin:auto. Auto horizontal margins on a flex item center it across the full
     container width (past the scrollbar), leaving it a few px off from the Start button
     and the panels above it. margin-top only keeps the spacing without that drift. */
  margin-top: 10px;
  background: transparent;
  border: 1px solid #55505f;
  color: var(--muted);
}
.leave-room-btn:hover {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text);
  border-color: #6a6478;
}


/* Creator credit, small and understated, directly under the welcome tagline. */
.credit-line {
  color: var(--muted);
  opacity: 0.85;
  font-size: clamp(0.65rem, 1.05vh, 0.95rem);
  letter-spacing: 0.02em;
  margin: 2px 0 0;
}
/* In the compact in-game header the tagline shrinks; keep the credit proportional. */
#app > header .credit-line { font-size: 0.55rem; margin: 0; line-height: 1.05; }

/* --- Rarity flags on the awards screen -------------------------------------
 * How unusual a result is, graded against simulated play at the same player
 * count (server/game/rarity.js). Four tiers, each visually louder than the last,
 * so the eye sorts them without reading: Rare is a quiet slate chip, Mythical
 * pulses. Shares .record-flag's geometry so the two can never disagree about
 * placement — they occupy the same slot and only one is ever rendered.
 * ------------------------------------------------------------------------- */
.rarity-flag {
  position: absolute;
  /* Sit the banner ABOVE the cell's top edge (its bottom lands ~1px over the border) so it never
     overlaps or touches the icon/value/label inside. translateY(-50%) centres it on top:-9px, which
     lifts its whole body out of the cell instead of letting it hang down onto the content. */
  top: -9px;
  left: 50%;
  transform: translate(-50%, -50%);
  white-space: nowrap;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  z-index: 2;
}
/* Rare — cool slate/blue: noticeable, not celebratory. ~2 per game. */
.rarity-flag-rare       { background: #3d5a80; color: #dbe9f7; border: 1px solid #5d82ad; }
/* Very rare — violet. */
.rarity-flag-veryrare   { background: #5b3a8e; color: #ece0ff; border: 1px solid #8560c4; }
/* Legendary — amber, the same family as the old record flag. ~1 in 4 games. */
.rarity-flag-legendary  { background: #b8791a; color: #fff6e2; border: 1px solid #f0c26b;
                          box-shadow: 0 0 10px rgba(240, 194, 107, 0.5); }
/* Mythical — red-gold, and the only one that moves. ~1 in 25 games. */
.rarity-flag-mythical   { background: linear-gradient(90deg, #a01b2b, #d9a441, #a01b2b);
                          color: #fff; border: 1px solid #ffd98a;
                          box-shadow: 0 0 14px rgba(217, 164, 65, 0.75);
                          animation: rarity-mythical-pulse 1.6s ease-in-out infinite; }
@keyframes rarity-mythical-pulse {
  0%, 100% { box-shadow: 0 0 10px rgba(217, 164, 65, 0.55); }
  50%      { box-shadow: 0 0 20px rgba(217, 164, 65, 0.95); }
}
/* Card body echoes the tier, so a flagged award reads as a unit. */
.stat-award.rarity-rare      { border-color: #5d82ad; box-shadow: 0 0 0 1px #3d5a80 inset; }
.stat-award.rarity-veryrare  { border-color: #8560c4; box-shadow: 0 0 0 1px #5b3a8e inset, 0 0 16px rgba(133, 96, 196, 0.25); }
.stat-award.rarity-legendary { border-color: #f0c26b; box-shadow: 0 0 0 1px #b8791a inset, 0 0 22px rgba(240, 194, 107, 0.3); }
.stat-award.rarity-mythical  { border-color: #ffd98a; box-shadow: 0 0 0 1px #a01b2b inset, 0 0 26px rgba(217, 164, 65, 0.45); }
/* The "This game" stat tiles carry the SAME coloured rarity border as the award
   banners, so a rare stat reads as special at a glance — not just via its flag. */
.summary-cell.rarity-rare      { border-color: #5d82ad; box-shadow: 0 0 0 1px #3d5a80 inset; }
.summary-cell.rarity-veryrare  { border-color: #8560c4; box-shadow: 0 0 0 1px #5b3a8e inset, 0 0 16px rgba(133, 96, 196, 0.25); }
.summary-cell.rarity-legendary { border-color: #f0c26b; box-shadow: 0 0 0 1px #b8791a inset, 0 0 22px rgba(240, 194, 107, 0.3); }
.summary-cell.rarity-mythical  { border-color: #ffd98a; box-shadow: 0 0 0 1px #a01b2b inset, 0 0 26px rgba(217, 164, 65, 0.45); }

@media (prefers-reduced-motion: reduce) {
  .rarity-flag-mythical { animation: none; }
}

/* --- Rarity legend --------------------------------------------------------
 * Explains the rarity badges, but ONLY the tiers actually handed out this game.
 * Sits below the game-summary strip and above the individual awards. Reuses the
 * exact .rarity-flag-<tier> chips from the awards, with .rarity-flag-inline
 * flipping them from the awards' absolute "pinned to a card" placement to normal
 * in-flow layout so each chip sits beside its explanation.
 * ------------------------------------------------------------------------- */
.rarity-legend {
  width: 100%;
  margin: 4px 0 2px;
  padding: 12px 14px;
  border: 1px solid #2b2733;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.03);
}
.rarity-legend-title {
  margin: 0 0 10px;
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: left;
}
.rarity-legend-items { display: flex; flex-direction: column; gap: 9px; }
.rarity-legend-item { display: flex; align-items: center; gap: 10px; }
.rarity-flag-inline {
  position: static;
  transform: none;
  left: auto;
  top: auto;
  flex: 0 0 auto;
  min-width: 92px;
  text-align: center;
}
.rarity-legend-blurb { font-size: 0.82rem; color: var(--text); line-height: 1.3; }

/* --- Rarity banners on the High Scores leaderboard --------------------------
 * Direct request: a leaderboard entry that clears the SAME classify() tiers as
 * the awards screen (run client-side against /api/rarity-thresholds - see
 * classifyEntryClient in app.js) gets a matching banner next to its stat, plus
 * a tinted/bolded name, so a truly exceptional high score reads as one at a
 * glance rather than just as "#1". .rarity-flag-row is a compact variant of
 * .rarity-flag-inline above (same static-position base, reused via a shared
 * class on the same element) - a dense leaderboard row can't afford that
 * class's 92px min-width or the awards screen's absolute "pinned to card"
 * placement. Player-name tint intentionally reuses the SAME tier hex palette
 * as .rarity-flag-<tier>/.stat-award.rarity-<tier> above so the two screens'
 * color language never drifts apart. */
.rarity-flag-row {
  min-width: 0;
  margin-left: 6px;
  padding: 1px 7px;
  font-size: 0.56rem;
  vertical-align: middle;
}
.highscore-list li.rarity-rare      .hs-player { color: var(--rarity-rare); }
.highscore-list li.rarity-veryrare  .hs-player { color: var(--rarity-veryrare); }
.highscore-list li.rarity-legendary .hs-player { color: var(--rarity-legendary); font-weight: 700; }
.highscore-list li.rarity-mythical  .hs-player { color: var(--rarity-mythical); font-weight: 700; text-shadow: 0 0 8px rgba(232, 90, 54, 0.6); }

/* The High Scores screen's description line (#highscores-note, built by
 * highscoresNoteHtml() in app.js) bakes its "Rare ~10%, Very rare ~5%, ..." tier-odds
 * clause onto its own line (a <br> before it), with each tier's name + percentage
 * colored to match that SAME tier's leaderboard/banner color above - direct request.
 * Shares the --rarity-<tier> custom properties above, not its own hex copies. */
.hs-note-tier { font-weight: 600; }
.hs-note-tier-rare      { color: var(--rarity-rare); }
.hs-note-tier-veryrare  { color: var(--rarity-veryrare); }
.hs-note-tier-legendary { color: var(--rarity-legendary); }
.hs-note-tier-mythical  { color: var(--rarity-mythical); }

/* --- Reaction window countdown -------------------------------------------
 * The visible half of the 10-second auto-take (server: startReactionCountdown).
 * A player who walks away can't freeze the game — the window expires and the hit
 * simply lands — so the table needs to SEE that clock running, or the resolution
 * looks like the game acting on its own.
 *
 * Deliberately in normal flow above the buttons, never overlaid: this is a
 * warning about a decision, so it must not obstruct the decision. pointer-events
 * is off as a belt-and-braces guarantee that it can never eat a click meant for
 * Shield / Mirror / Take the hit, whatever the layout does at narrow widths.
 * ------------------------------------------------------------------------- */
/* --- Play-area countdown bars ---------------------------------------------
 * ONE component, used by both time-limit bars — the reaction window and the
 * end-turn auto-end — so they are visually identical (the request: "exact same
 * style so everything is consistent"). Both mount into #play-countdowns, which
 * sits centred in the play-area header between the "Play Area" title and the
 * "YOUR TURN" badge.
 * ------------------------------------------------------------------------- */
.play-countdowns {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  pointer-events: none; /* purely informational — never sits between a tap and a button */
  /* Lift the timer bar ABOVE the reaction popup (#overlay-panels is absolute,
     inset:0, z-index:20 with a dark backdrop that would otherwise hide the header
     bar). The reaction countdown must stay visible while the "take the hit" popup
     is up — that's the whole point of it. */
  position: relative;
  z-index: 30;
}
.play-countdown {
  width: 100%;
  max-width: 240px;
}
/* Centre-bottom slot of the play area for the end-turn ("Ending turn in Xs") timer.
   Sits below the turn controls, centred, with a little breathing room. Empty (no
   padding) when no bar is mounted. pointer-events:none so it never eats a click. */
.play-footer {
  flex: 0 0 auto;
  display: flex;
  justify-content: center;
  align-items: flex-end;
  pointer-events: none;
}
.play-footer:not(:empty) { padding: 6px 0 2px; }
.play-countdown-bar {
  height: 4px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  overflow: hidden;
}
.play-countdown-bar > span {
  display: block;
  height: 100%;
  width: 100%;
  border-radius: 999px;
  background: var(--accent2);
  transition: width 0.1s linear;
}
.play-countdown-text {
  margin: 2px 0 0;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: center;
  color: var(--muted);
  white-space: nowrap;
}
/* Last few seconds: the bar and label go red. No flashing — the window is
   already tense, and prefers-reduced-motion users would lose the cue anyway. */
.play-countdown-urgent .play-countdown-bar > span { background: var(--accent); }
.play-countdown-urgent .play-countdown-text { color: var(--accent); }

/* The reaction ("take the hit") countdown sits directly ON TOP of the dark-red
   reaction popup, so it has to read against it — the shared gold bar and its faint
   track washed out over the popup, and the urgent RED blended into the popup's red
   border. Give the reaction bar its own dark chip and brighter, contrasting colours
   so it's always legible over the popup. */
#reaction-countdown {
  background: rgba(6, 5, 9, 0.9);
  padding: 6px 12px 7px;
  border-radius: 12px;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.22), 0 4px 14px rgba(0, 0, 0, 0.45);
}
#reaction-countdown .play-countdown-bar {
  height: 6px;
  background: rgba(255, 255, 255, 0.28);
}
#reaction-countdown .play-countdown-bar > span { background: #ffd36b; } /* bright gold vs the red popup */
#reaction-countdown .play-countdown-text { color: #ffe6a8; }
#reaction-countdown.play-countdown-urgent .play-countdown-bar > span { background: #ff6a4d; } /* bright orange-red, distinct from the popup's muted red border */
#reaction-countdown.play-countdown-urgent .play-countdown-text { color: #ff9a86; }
/* The reaction countdown now lives INSIDE the reaction popup, in its own slot just
   above the Shield / Mirror / Take-the-hit buttons — centred, with a little breathing
   room below it before the buttons. pointer-events:none so it can't eat a tap. */
.reaction-countdown-slot {
  display: flex;
  justify-content: center;
  margin: 4px 0 10px;
  pointer-events: none;
}
.reaction-countdown-slot #reaction-countdown { margin: 0; }

/* --- Card compendium ------------------------------------------------------
 * Every Loot and Event card, readable outside a game. Scrolls in one column so
 * a phone gets the same list as a desktop, just narrower.
 * ------------------------------------------------------------------------ */
.compendium-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.compendium-head h2 { margin: 0; }
/* Freeze the head (which holds "← Back to Main Menu") at the top of the screen so
   it stays reachable no matter how far the cards / rarity rows are scrolled. The
   head is the first child of the screen's own scroll box (#compendium-screen /
   #rarity-screen are overflow-y:auto), so position:sticky pins it there. The
   negative side margins + matching padding let the solid bar span edge-to-edge so
   scrolling content passes cleanly underneath it. */
/* Rarity Guide: freeze the WHOLE header — title, Back button AND the player-count filters — at the
   top of the scroll box so the filters follow the reader as they scroll. The head + controls live in
   one .rarity-sticky wrapper; it takes over the sticky-bar treatment (background, edge-to-edge span,
   shadow) so the head inside it is just plain flow. */
#rarity-screen .rarity-sticky,
#highscores-screen .rarity-sticky {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--bg);
  margin: 0 -16px;
  padding: 6px 16px 8px;
  box-shadow: 0 6px 10px -6px rgba(0, 0, 0, 0.6);
}
/* Card Compendium (v5.0.68): the category tabs (Weapons/Spells/Actions/etc.)
   now live in the same sticky wrapper as the head, so they stay pinned at
   the top with "← Back to Main Menu" instead of scrolling away with the
   cards - same treatment as .rarity-sticky above, just for this screen. Kept
   as its own rule (not combined with .rarity-sticky above) so each screen's
   sticky bar is independently greppable/testable. */
#compendium-screen .compendium-sticky {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--bg);
  margin: 0 -16px;
  padding: 6px 16px 8px;
  box-shadow: 0 6px 10px -6px rgba(0, 0, 0, 0.6);
}
#rarity-screen .compendium-head,
#compendium-screen .compendium-head,
#highscores-screen .compendium-head {
  position: static;
  margin: 0;
  padding: 0;
  box-shadow: none;
  background: none;
}
/* On a phone the whole PAGE scrolls (mobile.css makes menu screens overflow:visible),
   but the base rule above gives these two screens overflow-y:auto, which would make
   position:sticky bind to a box that never actually scrolls — so the head would drift
   away with the page. Hand the scroll back to the page here so the sticky head pins to
   the viewport top, keeping "← Back to Main Menu" frozen on screen while cards scroll. */
html.mobile:not(.in-game) #compendium-screen,
html.mobile:not(.in-game) #rarity-screen,
html.mobile:not(.in-game) #highscores-screen {
  overflow: visible;
}
/* Desktop: centre the "Back to Main Menu" button on the page (title above it),
   rather than tucking it in the top-right corner. Mobile keeps its own layout. */
html:not(.mobile) .compendium-head { flex-direction: column; justify-content: center; gap: 10px; }
/* Rarity Guide / Weapon Analysis screen only (v5.0.66 round): center the Back
   button ABOVE the centered tab group, rather than stacking in DOM order
   (tabs, then button) like the general column rule above would - DOM order
   here is tabs-then-button (see index.html), so `column-reverse` flips the
   visual order to button-on-top/tabs-below without touching the markup.
   Overrides the general rule above via a more specific selector;
   #compendium-screen keeps its own (DOM-order) stacked treatment untouched. */
html:not(.mobile) #rarity-screen .compendium-head {
  flex-direction: column-reverse;
  justify-content: center;
  gap: 10px;
}
/* The compendium screen fills the remaining app height and scrolls internally, so a
   large tab (the 20 Weapons) can be scrolled up and down instead of being clipped by
   the app's fixed-height, overflow-hidden shell. */
#compendium-screen {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* No top padding: the sticky head bar (Rarity Guide / Card Compendium title + Back
     button) pins to the very top of this scroll box. An 8px top pad left a thin strip
     BETWEEN the page header and the sticky bar where scrolling content (tier chips, row
     edges) bled through — the "gap above the title/button". Zero here seats the bar flush
     against the header so content passes cleanly underneath it instead. */
  padding: 0 16px 28px;
}
.compendium-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 4px; }
/* Card Compendium only (v5.0.66): center the category tabs (Weapons / Spells
   / Actions / etc.) under the Back to Main Menu button, matching the mockup.
   Scoped to #compendium-screen so #rarity-main-tabs (Rarity Guide / Weapon
   Analysis) is untouched - that one centers as a group via its parent
   .compendium-head's own column+center rule instead. */
#compendium-screen .compendium-tabs { justify-content: center; }
/* v5.0.67: center the description paragraph under the category tabs, matching
   #rarity-note's own centered/capped-width treatment on the Rarity Guide /
   Weapon Analysis screen - was left-aligned, spanning the full screen width. */
#compendium-note { max-width: 720px; margin: 8px auto 4px; text-align: center; }
.comp-tab {
  margin: 0;
  padding: 6px 12px;
  font-size: 0.8rem;
  border-radius: 999px;
  background: #221f2b;
  border: 1px solid #45414f;
  color: var(--muted);
  cursor: pointer;
}
.comp-tab:hover { border-color: var(--accent2); color: var(--text); }
.comp-tab-active { background: #3a2a10; border-color: #8a6a28; color: #f0c26b; }
.comp-tab-n { opacity: 0.65; font-size: 0.72rem; }

/* --- Rarity guide screen -------------------------------------------------
 * Read-anytime reference of the tier thresholds behind every end-of-game
 * banner. Reuses the compendium's scrolling shell and the canonical tier
 * colours (.rarity-flag-*), so the chips look like the banners they explain. */
#rarity-screen,
#highscores-screen {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  /* No top padding: the sticky head bar (Rarity Guide / Card Compendium title + Back
     button) pins to the very top of this scroll box. An 8px top pad left a thin strip
     BETWEEN the page header and the sticky bar where scrolling content (tier chips, row
     edges) bled through — the "gap above the title/button". Zero here seats the bar flush
     against the header so content passes cleanly underneath it instead. */
  padding: 0 16px 28px;
}
.rarity-controls { display: flex; justify-content: center; margin: 8px 0 0; }
.rarity-size-tabs { display: flex; flex-wrap: wrap; gap: 6px; }
.rar-size-tab {
  margin: 0; padding: 6px 14px; font-size: 0.8rem; border-radius: 999px;
  background: #221f2b; border: 1px solid #45414f; color: var(--muted); cursor: pointer;
}
.rar-size-tab:hover { border-color: var(--accent2); color: var(--text); }
.rar-size-tab-active { background: #3a2a10; border-color: #8a6a28; color: #f0c26b; }
#rarity-note,
#highscores-note { max-width: 760px; margin: 8px auto 4px; text-align: center; }
.rarity-body { margin-top: 10px; max-width: 900px; margin-left: auto; margin-right: auto; }
.rar-section { margin: 0 0 22px; }
.rar-section-title {
  margin: 0 0 8px; font-size: 1.05rem; color: var(--text);
  border-bottom: 1px solid #33303e; padding-bottom: 5px;
}
.rar-group { margin: 0 0 12px; }
.rar-group-title {
  margin: 10px 0 6px; font-size: 0.78rem; text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--accent2); opacity: 0.9;
}
.rar-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; background: var(--panel); border: 1px solid #33303e;
  border-radius: 10px; padding: 9px 12px; margin: 0 0 7px;
}
.rar-row-head { display: flex; flex-direction: column; gap: 1px; min-width: 200px; flex: 1 1 220px; }
.rar-row-name { font-weight: 600; color: var(--text); }
.rar-row-desc { font-size: 0.8rem; color: var(--muted); }
.rar-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.rar-chip {
  display: inline-flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 4px 10px; border-radius: 8px; font-size: 0.72rem; line-height: 1.15;
  border: 1px solid #45414f; min-width: 68px;
}
.rar-chip-name { font-weight: 700; font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.02em; white-space: nowrap; }
.rar-chip-val { font-variant-numeric: tabular-nums; font-size: 0.82rem; }
/* Second line inside a chip: the swing requirement for Dead Eye (accuracy % on top, "& N+ swings"
   below), so each tier shows the full "hit-rate AND swings" bar that actually warrants it. */
.rar-chip-sub { font-size: 0.6rem; font-weight: 600; opacity: 0.85; white-space: nowrap; margin-top: 1px; }
/* Average column: the mean value from the sims, shown next to the tier thresholds. Styled as a
   neutral (non-tier) chip so it reads as its own column without competing with the tier colours. */
.rar-avg {
  display: inline-flex; flex-direction: column; align-items: center; gap: 1px;
  padding: 4px 10px; border-radius: 8px; min-width: 60px; flex: 0 0 auto;
  background: #23202d; border: 1px solid #3a3547; color: var(--muted);
}
.rar-avg-label { font-weight: 700; font-size: 0.6rem; text-transform: uppercase; letter-spacing: 0.03em; }
.rar-avg-val { font-variant-numeric: tabular-nums; font-size: 0.82rem; color: var(--text); }
.rar-chip.rar-rare      { background: #3d5a80; color: #dbe9f7; border-color: #5d82ad; }
.rar-chip.rar-veryrare  { background: #5b3a8e; color: #ece0ff; border-color: #8560c4; }
.rar-chip.rar-legendary { background: #b8791a; color: #fff6e2; border-color: #f0c26b; }
.rar-chip.rar-mythical  { background: linear-gradient(90deg, #a01b2b, #d9a441, #a01b2b); color: #fff3d6; border-color: #ffd98a; }
/* Mobile: STACK every award/stat row the same way — head (name + description) on top,
   the tier chips in a full-width row beneath. On the narrow phone layout the default
   space-between row only wrapped the chips below when there were enough of them to
   overflow; a one- or two-tier award (e.g. The Fatemonger, The Mastermind) instead kept
   its single chip inline on the right, so those rows looked structurally different from
   the four-tier rows. Forcing a column makes the chips sit below the head for ALL rows. */
html.mobile .rar-row { flex-direction: column; align-items: stretch; gap: 8px; }
html.mobile .rar-row-head { flex: none; min-width: 0; }
/* Mobile: the Avg cell sits on its own line under the head (above the tier chips), left-aligned. */
html.mobile .rar-avg { align-self: flex-start; flex-direction: row; gap: 6px; padding: 3px 9px; }
/* Mobile uses the SAME fixed four-column grid as desktop (below), so every tier
   sits in its own column and Mythical is ALWAYS the 4th column — a Rare+Mythical
   award leaves columns 2 & 3 empty instead of sliding Mythical left into column 2,
   which is what made the thresholds look mis-aligned. 1fr columns + shrunk chips
   fit the phone width (the head is stacked above per the .rar-row rule). */
html.mobile .rar-chips {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
  justify-content: stretch;
}
html.mobile .rar-chips .rar-chip { width: 100%; min-width: 0; padding: 3px 4px; }
html.mobile .rar-chip .rar-chip-name { font-size: 0.54rem; letter-spacing: 0; }
html.mobile .rar-chip .rar-chip-val  { font-size: 0.72rem; }
html.mobile .rar-chip.rar-rare      { grid-column: 1; }
html.mobile .rar-chip.rar-veryrare  { grid-column: 2; }
html.mobile .rar-chip.rar-legendary { grid-column: 3; }
html.mobile .rar-chip.rar-mythical  { grid-column: 4; }
/* PC: lay the chips out as a fixed FOUR-COLUMN grid, one column per tier, so every
   tier lines up vertically down the list. Each chip is placed into its own tier's
   column by its tier class, and missing tiers leave that column empty — so a lone
   Legendary (e.g. The Self-Preservationist) sits under the other Legendary chips
   instead of being pushed to the far-right where the Mythicals are. Fixed column
   widths + the chips block sitting at the row's right edge keep the columns aligned
   across adjacent rows. (Mobile keeps the stacked, left-aligned layout above.) */
html:not(.mobile) .rar-chips {
  display: grid;
  grid-template-columns: repeat(4, 100px);
  gap: 6px;
  justify-content: end;
}
html:not(.mobile) .rar-chips .rar-chip { width: 100%; min-width: 0; }
html:not(.mobile) .rar-chip.rar-rare      { grid-column: 1; }
html:not(.mobile) .rar-chip.rar-veryrare  { grid-column: 2; }
html:not(.mobile) .rar-chip.rar-legendary { grid-column: 3; }
html:not(.mobile) .rar-chip.rar-mythical  { grid-column: 4; }

/* High Scores tab (reverted from the Rarity Guide - see app.js's High Scores
 * section for the "why"): one card per award, reusing .rar-section/.rar-group
 * for the outer scaffolding (same visual language as the other two tabs) and
 * .highscore-list/.hs-rank/.hs-player/.hs-detail/.is-best/.hs-empty from the
 * standalone high-score viewer for the ranked rows themselves, so a ranked
 * list looks identical whether it's reached from here or from the end-of-game
 * "🏅 High Scores" button. */
.hsc-row {
  background: var(--panel); border: 1px solid #33303e; border-radius: 10px;
  padding: 10px 12px; margin: 0 0 10px;
}
.hsc-row-head { display: flex; align-items: flex-start; gap: 10px; margin: 0 0 6px; }
.hsc-row-icon { font-size: 1.3rem; line-height: 1.2; flex: none; }
.hsc-list { margin-top: 2px; }
/* v5.0.83: the mobile ".rar-row-head { flex: none; min-width: 0; }" reset above
   was written for #rarity-screen's .rar-row, which switches to a STACKED column
   on mobile (align-items: stretch then forces .rar-row-head to the row's full
   width, so "flex: none" is harmless there). .hsc-row-head never stacks - it's
   an icon + text row at every viewport - so that same reset left .rar-row-head
   sized to its unwrapped text content instead of filling the remaining width
   after the icon, and long descriptions (Dead Eye, The Last Stand) ran off the
   right edge instead of wrapping. Override with a more specific selector so the
   text column actually grows/shrinks to fill the row and wraps within it. */
html.mobile .hsc-row-head .rar-row-head { flex: 1 1 auto; min-width: 0; }

/* --- Weapon Analysis tab (Stats for Nerds) --------------------------------
   Same section/group scaffolding as the Rarity Guide (.rar-section/.rar-group
   reused as-is), but each entry is a horizontal bar row instead of a chip
   row - the natural shape for "one weapon, one score, ranked within its
   tier" rather than "one award, several tier thresholds side by side". */
.wpn-analysis-note { margin: 0 0 14px; }
.wpn-row { display: flex; align-items: flex-start; gap: 10px; margin: 0 0 10px; }
.wpn-tier-badge {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 0.95rem;
  border: 1px solid;
}
.wpn-row-main { flex: 1 1 auto; min-width: 0; }
.wpn-row-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.wpn-row-name { font-weight: 600; color: var(--text); }
.wpn-row-score { font-variant-numeric: tabular-nums; font-size: 0.82rem; color: var(--muted); white-space: nowrap; }
.wpn-bar-track { margin: 4px 0 14px; height: 8px; border-radius: 999px; background: rgba(255, 255, 255, 0.08); overflow: hidden; }
.wpn-bar-fill { height: 100%; border-radius: 999px; }
/* Per-identity Hero/Maker bars under the main tier-score bar - smaller and
   labeled so they read as a breakdown of the score above, not a second
   independent ranking. Hero uses the app's existing hero-blue accent; Maker
   uses its existing danger-red accent, matching how identity is colored
   everywhere else (identity cards, the revealed-Maker badge, etc.). v5.0.66:
   margin bumped 2px -> 4px (top bar's own bottom margin also grew 3px -> 8px)
   - the MAKER label was reading as visually touching/clipping into the
     combined-score bar above it with only ~3px of true whitespace between
     them; there's real breathing room now. */
.wpn-role-bar { display: flex; align-items: center; gap: 8px; margin: 5px 0; }
/* v5.0.68: widened from 38px - "MAKER" (6 uppercase letters + letter-spacing)
   was measuring right at the edge of the old 38px box on some renders, close
   enough to read as touching the bar beside it. 46px gives real slack. */
.wpn-role-label { flex: none; width: 46px; font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); }
/* min-width: 0 (not the flex-item default of auto) so this can actually shrink
   below its own content's intrinsic size instead of forcing the row wider than
   its container - the guard against the bar ever overlapping/pushing past the
   fixed-width label or value beside it. */
.wpn-bar-track-sm { flex: 1 1 auto; min-width: 0; margin: 0; height: 5px; }
.wpn-bar-fill-hero { background: #4f8fd9; }
.wpn-bar-fill-maker { background: #c0453e; }
.wpn-role-value { flex: none; font-variant-numeric: tabular-nums; font-size: 0.68rem; color: var(--muted); white-space: nowrap; min-width: 3.4em; text-align: right; }
/* Same warm-gold-to-cool-muted progression the rarity chips use for
   Rare -> Mythical, applied here to S -> F so the two tabs read as one
   consistent visual language even though the shapes (chip vs. bar) differ. */
.wpn-tier-s          { background: linear-gradient(90deg, #a01b2b, #d9a441, #a01b2b); border-color: #ffd98a; color: #fff3d6; }
.wpn-tier-badge.wpn-tier-s { background: #b8791a; }
.wpn-tier-a          { background: #5b3a8e; border-color: #8560c4; color: #ece0ff; }
.wpn-tier-b          { background: #3d5a80; border-color: #5d82ad; color: #dbe9f7; }
.wpn-tier-c          { background: #2d6a4f; border-color: #4f9c78; color: #dcf5e9; }
.wpn-tier-d          { background: #6b5a3a; border-color: #a08454; color: #f0e2c8; }
.wpn-tier-f          { background: #5a3535; border-color: #8a5252; color: #f2dede; }

/* --- Patch Notes tab (Stuff for Nerds) ---------------------------------------
   A flat chronological list (no tiers/groups to sort into, unlike the Rarity
   Guide or Weapon Analysis above), so it gets its own simple row - a version
   tag on the left, the summary filling the rest, matching .rar-row's overall
   card language (same background/border/radius) without borrowing its
   two-column chip layout, which doesn't fit a paragraph of prose. */
.patch-notes-list { display: flex; flex-direction: column; gap: 7px; }
.patch-note-row {
  display: flex; align-items: baseline; gap: 12px; background: var(--panel);
  border: 1px solid #33303e; border-radius: 10px; padding: 9px 12px;
}
/* Version + date stack together as one fixed-width left column (was just the version tag
   alone, before the v6.0.14 date backfill) - the date is a best-effort backfill (plenty of
   older entries have none, see patch-notes-data.js), so it's only ever rendered when
   present and this column's height/alignment doesn't depend on it being there. */
.patch-note-head {
  flex: none; min-width: 4.2em; display: flex; flex-direction: column; gap: 1px;
}
.patch-note-version { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--accent2); }
.patch-note-date { font-size: 0.7rem; color: var(--muted); }
.patch-note-summary { flex: 1 1 auto; min-width: 0; font-size: 0.85rem; color: var(--text); line-height: 1.35; }
/* Mobile: the version/date column reads better stacked above its summary than
   squeezed into a fixed-width column beside several lines of wrapped text. */
html.mobile .patch-note-row { flex-direction: column; align-items: stretch; gap: 3px; }
html.mobile .patch-note-head { min-width: 0; flex-direction: row; gap: 8px; align-items: baseline; }

.compendium-body {
  margin-top: 10px;
  display: grid;
  /* v5.0.66: single column, one card per row (was a multi-column auto-fill
     grid) - the list scrolls vertically through each category instead of
     wrapping into a tiled grid. Capped max-width + centered so a single
     enlarged card doesn't stretch edge-to-edge on a wide monitor. */
  grid-template-columns: 1fr;
  max-width: 720px;
  margin-left: auto;
  margin-right: auto;
  gap: 16px;
  align-content: start;
}
.comp-card {
  display: flex;
  gap: 18px;
  background: var(--panel);
  border: 1px solid #33303e;
  border-radius: 12px;
  /* Enlarged (v5.0.66) to fit the new one-card-per-row layout - was 10px. */
  padding: 18px;
}
/* Every card is now a button that opens the full-card viewer. */
.comp-card-selectable { cursor: pointer; transition: border-color 0.15s ease, transform 0.1s ease, box-shadow 0.15s ease; }
.comp-card-selectable:hover,
.comp-card-selectable:focus-visible {
  border-color: var(--accent2);
  box-shadow: 0 0 0 1px var(--accent2), 0 4px 14px rgba(0, 0, 0, 0.4);
  outline: none;
}
.comp-card-selectable:active { transform: scale(0.99); }
/* Full-card viewer: a centered overlay showing one card's art + description, with
   left/right arrows to move through the current category. */
.comp-viewer {
  position: fixed; inset: 0; z-index: 120;
  display: flex; align-items: center; justify-content: center;
  gap: clamp(8px, 3vw, 28px);
  padding: 20px;
  background: rgba(6, 5, 9, 0.86);
  backdrop-filter: blur(3px);
}
.comp-viewer-stage-wrap { display: flex; flex-direction: column; align-items: center; gap: 10px; max-height: 92vh; }
.comp-viewer-stage {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  background: var(--panel);
  border: 1px solid var(--accent2);
  border-radius: 14px;
  padding: 18px;
  /* FIXED width (not shrink-to-fit) so the viewer is the SAME size for every card —
     a long note wraps within this width instead of stretching the whole panel wider
     than a card with a short description. */
  width: min(440px, 86vw);
  max-height: 88vh;
  overflow-y: auto;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
}
.comp-viewer-art {
  width: auto; max-width: 100%;
  max-height: 56vh;
  border-radius: 24px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.5);
}
.comp-viewer-desc { width: 100%; text-align: center; overflow-wrap: anywhere; }
.comp-viewer-desc h3 { margin: 0 0 8px; color: var(--accent2); font-size: 1.15rem; }
.comp-viewer-desc .comp-tiers { display: inline-block; text-align: left; }
.comp-viewer-desc .comp-effect,
.comp-viewer-desc .comp-note { font-size: 0.9rem; line-height: 1.5; }
.comp-viewer-counter { font-size: 0.8rem; color: var(--muted); letter-spacing: 0.05em; }
.comp-viewer-nav {
  flex: 0 0 auto;
  width: 54px; height: 54px; margin: 0;
  border-radius: 50%;
  background: rgba(30, 27, 38, 0.9);
  color: var(--accent2);
  border: 1px solid var(--accent2);
  font-size: 2rem; line-height: 1; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.comp-viewer-nav:hover { background: var(--accent2); color: #2a1f08; }
.comp-viewer-close {
  position: absolute; top: 14px; right: 16px;
  width: 40px; height: 40px; margin: 0; padding: 0;
  border-radius: 50%;
  background: rgba(30, 27, 38, 0.9);
  color: var(--text); border: 1px solid #55515f;
  font-size: 1.1rem; cursor: pointer;
}
.comp-viewer-close:hover { background: #45414f; }
@media (max-width: 560px) {
  .comp-viewer { gap: 4px; padding: 10px; }
  /* On a phone the row (arrow · card · arrow) is wider than the screen, so the
     arrows were clipped off the edges. Float them absolutely so they OVERLAP the
     card's left/right edges instead of taking their own column — the card can then
     use the full width and both arrows stay on-screen and tappable. */
  .comp-viewer-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 44px; height: 44px; font-size: 1.6rem;
    z-index: 2;
    background: rgba(30, 27, 38, 0.94);
  }
  .comp-viewer-prev { left: 6px; }
  .comp-viewer-next { right: 6px; }
}
/* Enlarged (v5.0.66) to fit the new one-card-per-row layout - was 84px. */
.comp-art {
  width: 150px;
  flex: 0 0 150px;
  /* Scaled up with the enlarged single-column layout (v5.0.66) - was 12px at
     the old 84px width; 150px calls for a bigger radius to read the same
     proportionally, matching .identity-art's 22px at 160px (see
     card-art-white-corner-fix.js, which pins this exact value). */
  border-radius: 20px;
  align-self: flex-start;
}
.comp-art-missing {
  height: 210px;
  display: flex; align-items: center; justify-content: center;
  text-align: center; font-size: 0.85rem; padding: 4px;
  background: #100e15; border: 1px dashed #45414f; color: var(--muted);
}
.comp-text { min-width: 0; flex: 1 1 auto; }
.comp-text h4 { margin: 0 0 8px; font-size: 1.3rem; color: var(--accent2); }
.comp-copies { margin-left: 6px; font-size: 0.85rem; color: var(--muted); font-weight: 400; }
.comp-tiers { margin: 0; padding-left: 0; list-style: none; }
.comp-tiers li { font-size: 1rem; line-height: 1.5; color: var(--text); }
.comp-roll {
  display: inline-block; min-width: 32px; margin-right: 6px;
  padding: 2px 6px; border-radius: 5px;
  background: #100e15; border: 1px solid #45414f;
  font-weight: 700; text-align: center; color: #f0c26b;
}
.comp-tiers em { color: var(--muted); font-style: normal; }
.comp-effect { margin: 0; font-size: 1rem; line-height: 1.5; color: var(--text); }
.comp-note { margin: 8px 0 0; font-size: 0.85rem; line-height: 1.45; color: var(--muted); }
/* Developer panel in the room screen (host only). */
.dev-tools { border-color: #45414f; }
.dev-tools h3 { color: var(--muted); }
.dev-toggle { display: flex; align-items: center; gap: 8px; font-size: 0.85rem; cursor: pointer; }
.dev-toggle input { width: auto; display: inline-block; margin: 0; }
/* Type icon in the compendium tabs. Sits slightly larger than the label so it
   reads as an icon rather than punctuation, and never shrinks the hit area. */
.comp-tab-icon { margin-right: 5px; font-size: 0.92rem; line-height: 1; }

/* --- Rummage with a big discard pile --------------------------------------
 * This produced TWO scrollbars. `.discard-list` scrolls at max-height 40vh
 * (360px on a 900px-tall window), but it lives inside #turn-controls, which is
 * itself `max-height: 50%; overflow-y: auto` — about 260px. A 360px list inside
 * a 260px pane means BOTH overflow, so the player got a scrollbar inside a
 * scrollbar. Worse, the Confirm button sits after the list: having scrolled the
 * inner list to find a card, you then had to scroll the OUTER pane to reach the
 * button, with no hint it was there.
 *
 * The fix is to stop the list asking for a fixed height it can't have, and let
 * it take whatever the pane actually has left instead. The form becomes a flex
 * column that fills the pane; the list flexes into the leftover space and is the
 * only thing that scrolls; Confirm is pinned below it and always on screen.
 * min-height: 0 throughout because a flex item's default min-height:auto refuses
 * to shrink below its content — which is exactly what re-creates the overflow.
 * ------------------------------------------------------------------------- */
#action-builder.has-rummage {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.rummage-form {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1 1 auto;
}
.rummage-form .discard-list {
  flex: 1 1 auto;
  min-height: 0;
  max-height: none; /* was 40vh — taller than the pane it lives in */
  /* Bounding the list to the pane leaves it only ~150px tall, which is four rows
     of a pile that can hold thirty-plus cards — technically one scrollbar, still
     miserable to read. The pane is short but WIDE, so spend the width: a grid
     fits five cards per row instead of one, cutting a seven-screen scroll to
     about one. Falls back to a single column on a phone, where it was already. */
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  align-content: start;
}
.rummage-form .rummage-confirm { flex: 0 0 auto; margin-top: 0; }

/* Developer panel: a <details> so it's one quiet line until clicked. It sits in
   the middle of a normal player's room setup, so it shouldn't shout. */
.dev-tools { padding: 0; }
.dev-tools > .dev-summary {
  cursor: pointer;
  padding: 10px 12px;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--muted);
  list-style: none;
  user-select: none;
}
/* Replace the default triangle with one that follows the open state. */
.dev-tools > .dev-summary::-webkit-details-marker { display: none; }
.dev-tools > .dev-summary::before { content: "▸ "; display: inline-block; width: 1em; }
.dev-tools[open] > .dev-summary::before { content: "▾ "; }
.dev-tools > .dev-summary:hover { color: var(--text); }
.dev-tools .dev-body { padding: 0 12px 12px; }
.dev-tools .dev-body .note { margin-bottom: 0; }

/* Custom-settings-selected warning (direct request): sits right under the
   Start Game button (v6.0.22 round - previously sat under the <details> panel
   at the bottom of the room screen), so it's visible immediately rather than
   only after scrolling down and even if the host collapses the panel after
   turning a setting on. Uses --accent (the same brick-red already used for
   the attack-blocked warning) rather than a new color, and stays out of
   normal .note's muted-grey treatment. */
.custom-settings-warning {
  max-width: 42ch;
  /* Direct request: equalize the gaps above/below this note (and the bots
     note below) with every other gap in this screen's stack (room code h2 ->
     invite row -> Start Game -> seat list, all 16px - see .lobby-start-btn's
     own comment). Was `6px auto 12px` - since adjacent block margins
     COLLAPSE rather than sum, that made the gap up to Start Game's own 16px
     bottom margin collapse to 16px, but the gap down to the seat-list card
     below (0 top margin there) collapse to only 12px - visibly uneven. Zeroing
     the top margin here (Start Game's bottom margin already owns that gap)
     and matching the bottom margin to 16px makes both gaps equal. */
  margin: 0 auto 16px;
  padding: 8px 12px;
  border: 1px solid var(--accent);
  border-radius: 8px;
  color: var(--accent);
  font-size: 0.82rem;
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
}
/* Bots-in-game note (direct request): same bordered-box treatment as the
   custom-settings warning right below it, but --accent2 (gold) instead of
   --accent (red) - this isn't warning of a lost feature, just a friendly
   nudge toward playing with people. */
.bots-in-game-note {
  max-width: 42ch;
  /* Direct request: same equalized-gap fix as .custom-settings-warning right
     above - was `6px auto 12px`, collapsing to a 16px gap up to Start Game's
     own margin but only 12px down to the seat-list card below (its top
     margin is 0). 0/16 makes both gaps the same 16px used everywhere else in
     this screen's stack. */
  margin: 0 auto 16px;
  padding: 8px 12px;
  border: 1px solid var(--accent2);
  border-radius: 8px;
  color: var(--accent2);
  font-size: 0.82rem;
  font-weight: 600;
  line-height: 1.35;
  text-align: center;
}
