/* Snertz — game.css
   theme.css supplies the design tokens (--bg, --surface-1, --accent,
   etc.) — same base every other game here uses. Header/room-pill/
   player-rail/confirm-dialog/toast below are the same patterns Raid and
   Reporter already established for a room-based (not seats-around-a-
   table) game, reused near-verbatim since Snertz is architecturally in
   that same family. Everything from the board layout down is new,
   specific to Snertz's own mechanic: no turns, no shared board — every
   player has their own private tableau, and the only shared surface is
   the foundations area in the middle. */

/* ── Fluid card sizing ──────────────────────────────────────────────────
   Card width used to jump between two fixed sizes at two breakpoints
   (52px by default, 44px under 480px, 36px under 360px) — three flat
   sizes with a hard edge between them, not "dynamic." Worse, each fixed
   size only actually fit .my-board-row's content down to a specific
   viewport width, so a phone narrower than whatever that breakpoint's
   author had in mind still overflowed and forced .my-board-row's own
   overflow-x:auto to kick in, showing a scrollbar nobody wanted.
   --card-w replaces all of that with one continuous formula: it shrinks
   smoothly with viewport width instead of stepping between fixed sizes,
   and every screen this needs to support fits without ever touching
   that scrollbar fallback (which stays in place purely as a backstop
   for anything narrower than any real phone, not as the normal case).

   The formula itself comes directly from .my-board-row's own layout,
   not a guess: that row holds 7 card-widths (Nerts pile + 4 tableau
   piles + stock + waste, since stock/waste stack in a column rather
   than sitting side by side) plus 2 .my-board-row gaps (8px each,
   fixed — see .my-board-row below) and 3 .tableau-row gaps (6px each,
   fixed — see .tableau-row below), inside .my-board-panel's own 0.9rem
   padding and #main-area's 0.75rem padding on each side. Solving
   "7×card-w + fixed gaps + fixed padding = 100vw" for card-w gives the
   14.28vw (i.e. 100vw \u00f7 7) term below; the constant folds in every
   fixed width in that equation plus a small deliberate safety margin,
   so real browser rounding can never tip it over into overflow. Verified
   empirically afterward across viewport widths from 280px to 900px —
   see the conversation this shipped in — not just trusted algebraically.
   Bounded to never exceed 52px (the original fixed desktop size, still
   correct once a screen is wide enough that shrinking further would
   only look small for no reason) or shrink below 32px (smaller than
   that stops being comfortably tappable, regardless of how much room
   the calc() would otherwise claim). */
:root {
  --card-w: clamp(32px, calc(14.28vw - 13.3px), 52px);
}

.game-subtitle {
  text-align: center;
  color: var(--text-mid);
  font-size: 0.9rem;
  margin: -1.25rem 0 1.5rem;
}

/* ── Header ─────────────────────────────────────────────────────────── */
.game-header {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.9rem 1.25rem;
  background: var(--surface-1);
  border-bottom: 1px solid var(--border);
}
/* Chat/Settings/Share sit together as one right-aligned group rather
   than being spread apart by justify-content: space-between. See the
   identical comment in every other card game's game.css. */
.game-header .tbl-icon-row { margin-left: auto; }
/* Leave link — same bordered-chip look as every other card game's
   Leave button (.tbl-header-back in the shared table-header.css),
   using Snertz's own --border/--text-mid/--accent/--text tokens
   since Snertz keeps its own self-contained copy of the design
   tokens rather than depending on games.lightlol.com/shared
   cross-origin (see this game's theme.css header comment). Replaces
   the old plain-text .back-home-link this button used to carry (that
   class, and the scoped .game-header override that actually styled
   it, have both been removed — the join screen's own separate fixed
   pill-shaped back link in theme.css is untouched and still called
   that). */
.tbl-header-back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: none;
  border: 1px solid var(--border);
  color: var(--text-mid);
  border-radius: 8px;
  padding: 0.4rem 0.8rem;
  font-size: 0.75rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
  text-decoration: none;
}
.tbl-header-back:hover { border-color: var(--accent); color: var(--text); }
.tbl-header-back .tbl-icon { width: 16px; height: 16px; }
.room-code-pill {
  display: inline-flex;
  align-items: center;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.35rem 0.9rem;
  font-size: 0.85rem;
  color: var(--text-mid);
}
.room-code-pill span {
  /* Was relying on inline baseline alignment next to the "Room" text,
     which put the badge's bottom near the text baseline rather than its
     visual center — with only 0.05rem of vertical padding, that read as
     the badge sitting low and crowding the pill's bottom edge instead of
     looking centered inside it. The pill above is now a flex row with
     align-items: center, which centers this against "Room" properly
     regardless of any padding/line-height quirks; the padding here is
     also a little more generous top/bottom so the badge itself doesn't
     look flattened now that it's not fighting the baseline for space. */
  display: inline-block;
  background: var(--accent);
  color: var(--on-accent);
  font-weight: 800;
  letter-spacing: 0.05em;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  margin-left: 0.2rem;
}
.icon-btn {
  width: 38px;
  height: 38px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text-mid);
  font-size: 16px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative; /* anchors .chat-unread-badge to the button itself, not some ancestor */
}
.icon-btn:hover { border-color: var(--accent); color: var(--text); }

/* Header icon buttons (.tbl-icon-btn/.tbl-icon/.tbl-icon-row) now come
   from the shared games.lightlol.com/shared/table-header.css, same as
   every other card game. The one thing Snertz adds: the chat button's
   unread badge (further below) anchors to its button, so the button
   needs to be a positioning context. */
.tbl-icon-btn { position: relative; }

/* ── Player rail — running totals live here instead of a plain name/dot,
   since "who's ahead" matters continuously in Snertz, not just at the
   end of a round. ───────────────────────────────────────────────────── */
.player-rail {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--border);
}
.player-chip {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0.3rem 0.7rem 0.3rem 0.4rem;
  font-size: 0.8rem;
  color: var(--text-mid);
}
.player-chip.is-me { border-color: var(--accent); color: var(--text); }
.player-chip.is-disconnected { opacity: 0.4; }
.player-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.player-name { font-weight: 600; }
.player-score {
  background: var(--surface-2);
  border-radius: 999px;
  padding: 0.05rem 0.5rem;
  font-weight: 800;
  font-size: 0.78em;
  color: var(--text);
}
.player-remove-btn {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 0.85em;
  line-height: 1;
  cursor: pointer;
  padding: 0 0.1rem;
}
.player-remove-btn:hover { color: var(--danger); }

/* ── Team chip (player rail) — two members sharing one chip and one
   score badge, since a team's totalScore is literally the same number
   mirrored onto both (see finishRound on the server). ─────────────── */
.team-chip { gap: 0.3rem; padding-right: 0.6rem; }
.team-member { display: flex; align-items: center; gap: 0.3rem; }
.team-member.is-me .player-name { color: var(--text); font-weight: 800; }
.team-member.is-disconnected { opacity: 0.4; }
.team-link { font-size: 0.75em; }
.role-tag {
  font-size: 0.62em;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  background: var(--surface-2);
  border-radius: 999px;
  padding: 0.02rem 0.4rem;
}
.difficulty-tag.difficulty-easy { color: #5c8a5c; }
.difficulty-tag.difficulty-medium { color: #b8933a; }
.difficulty-tag.difficulty-hard { color: var(--danger); }

/* ── Main area ──────────────────────────────────────────────────────── */
#main-area {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 1rem 0.75rem 2rem;
  gap: 1rem;
}
.lobby-card { max-width: 560px; width: 100%; }
.lobby-controls { display: flex; flex-direction: column; gap: 1.1rem; align-items: center; }
.target-score-select {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  justify-content: center;
}
.target-score-option {
  padding: 0.6rem 1.1rem;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  font-family: var(--font-body);
  font-weight: 700;
  cursor: pointer;
}
.target-score-option.selected { border-color: var(--accent); background: var(--surface-1); box-shadow: 0 0 0 2px rgba(255, 228, 56, 0.35); }
.target-score-option.small { padding: 0.4rem 0.75rem; font-size: 0.8rem; }
/* Tap to Place, when there's no bot in the room to require it — same
   grayed-out treatment .snertz-btn:disabled already uses elsewhere, so
   "disabled" reads the same way everywhere on this page. A disabled
   button can't be .selected at the same time in practice (the server
   won't let autoPlace be true without a bot — see autoPlaceActive), but
   this still wins over .selected's border/shadow if that ever changed. */
.target-score-option:disabled { opacity: 0.4; cursor: default; box-shadow: none; }
.bot-add-row { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; justify-content: center; }
.bot-difficulty-select { display: flex; gap: 0.35rem; }
.mode-preview { text-align: center; color: var(--text-mid); font-size: 0.85rem; }
.snertz-btn.small { padding: 0.4rem 0.8rem; font-size: 0.8rem; }
.icon-btn.small { width: 30px; height: 30px; font-size: 13px; }

/* ── Teams panel (lobby) — host pairs two humans into one shared-deck
   team. Solo rows and formed-team rows share the same card so the
   whole roster reads as one list, not two disconnected sections. ──── */
.teams-panel {
  width: 100%;
  max-width: 560px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 0.8rem 1rem;
  margin-top: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.team-pair-row, .solo-player-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  padding: 0.4rem 0.5rem;
  border-radius: 8px;
  background: var(--surface-2);
}
.team-pair-row { justify-content: center; flex-wrap: wrap; }
.solo-player-row { justify-content: space-between; }
.solo-player-row.selected { outline: 2px solid var(--accent); }

/* ── Ready-up panel — shared shape with .teams-panel above (same card,
   same row treatment) so the lobby doesn't read as two unrelated widget
   styles stacked on top of each other. ───────────────────────────────── */
.ready-panel {
  width: 100%;
  max-width: 560px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 0.8rem 1rem;
  margin-top: 0.75rem;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  align-items: center;
}
.ready-rows { width: 100%; display: flex; flex-direction: column; gap: 0.4rem; }
.ready-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
  padding: 0.4rem 0.5rem;
  border-radius: 8px;
  background: var(--surface-2);
}
.ready-row.is-disconnected { opacity: 0.55; }
.ready-name { flex: 1; }
.ready-status { font-size: 0.78em; color: var(--text-muted); font-weight: 700; }
.ready-status.is-ready { color: #5c8a5c; }
.ready-status.away { font-style: italic; }
.ready-countdown {
  font-size: 0.9rem;
  font-weight: 700;
  color: var(--text);
  text-align: center;
}
.ready-countdown #ready-countdown-secs { color: var(--accent); font-size: 1.1em; }
/* Same shape as the mid-round .bury-vote-banner (see below) — one
   consistent "someone proposed, here's who's agreed" pattern wherever a
   unanimous vote shows up in this game. */
.extend-vote-banner {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  background: var(--surface-2);
  border: 1px solid var(--accent);
  border-radius: 10px;
  padding: 0.5rem 0.75rem;
  font-size: 0.82rem;
}
.extend-vote-actions { display: flex; gap: 0.5rem; flex-shrink: 0; }
.snertz-btn {
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: 999px;
  padding: 0.8rem 1.6rem;
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.1s ease;
}
.snertz-btn:hover:not(:disabled) { background: var(--accent-hover); }
.snertz-btn:active:not(:disabled) { transform: scale(0.98); }
.snertz-btn:disabled { opacity: 0.4; cursor: default; }
.snertz-btn.secondary { background: var(--surface-2); color: var(--text); }
.snertz-btn.secondary:hover:not(:disabled) { background: var(--surface-1); }

/* ── Playing card face — same cream/red look as every other game's cards
   (blackjack's .pcard) so a card reads the same visual "language"
   wherever it appears on the site. Sized bigger here than the other
   games' hand cards since these need to be comfortably drag-targets on
   a phone screen.
   Two layouts are supported — "Card style" in Settings. Unlike the
   card-back deck styles below (a pure CSS reskin, driven by an
   attribute), cardHtml() in game.html decides which ONE of these two
   layouts to actually build for a given card, so only ever one of them
   exists in the DOM at a time — there's nothing for this stylesheet to
   conditionally show or hide, just two plain, always-on styles for
   whichever markup shows up.
     "readable" (default) — big rank + suit, dead center. The most
       legible option at a glance or on a small phone screen, and how
       every card looked before "indexed" existed.
     "indexed" — a real card's layout: small rank+suit in the top-left
       corner (mirrored bottom-right) with one big suit glyph centered.
       Reads as more authentic, and uniquely, the corner index survives
       being partially covered in a cascaded tableau pile — the
       trade-off is it's a smaller mark to read at a glance than
       "readable"'s. ───────────────────────────────────────────────── */
.snz-card {
  width: var(--card-w);
  height: calc(var(--card-w) * 1.42); /* matches the original 52x74 aspect ratio */
  border-radius: 6px;
  background: #f8f6f0;
  color: #111;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: calc(var(--card-w) * 0.32);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
  flex-shrink: 0;
  position: relative;
  user-select: none;
  touch-action: none;
}
.snz-card.red { color: #c0392b; }

/* "readable" */
.snz-card .snz-rank { font-size: calc(var(--card-w) * 0.308); line-height: 1; }
.snz-card .snz-suit { font-size: calc(var(--card-w) * 0.375); line-height: 1; margin-top: 2px; }

/* "indexed" — sized and positioned to sit fully inside the strip a
   cascaded tableau card leaves exposed (var(--card-w) * 0.34 — see
   .tableau-pile .snz-card below and getTableauCascadeOffset in
   game.html), with margin to spare across the fluid size range that
   strip now scales through — that's what makes the corner specifically
   (not just "small text somewhere") the one part of a stacked card that
   stays visible under the next one. */
.snz-corner-index {
  position: absolute;
  font-size: 0.6rem;
  font-weight: 800;
  line-height: 1;
}
.snz-corner-top { top: 3px; left: 5px; }
.snz-corner-bottom { bottom: 3px; right: 5px; transform: rotate(180deg); }
.snz-center-suit {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: calc(var(--card-w) * 0.523);
  line-height: 1;
}

.snz-card.face-down {
  background: repeating-linear-gradient(45deg, var(--accent), var(--accent) 4px, #8a5c10 4px, #8a5c10 8px);
  border: 1px solid rgba(255, 255, 255, 0.15);
}
/* Deck back styles — picked from the Settings modal below. "classic" has
   no attribute value of its own, it's just the unqualified rule above.
   .mini and full-size cards share this one selector in snertz (see
   cardHtml in game.html), so nautical's glyph needs a size split. */
/* "Nautical" — the DEFAULT back — spelled out explicitly. Every game used
   to leave the default as its own unqualified face-down rule (yellow
   stripes here, grey in 5 Crowns, navy in poker), so the picker's
   Nautical swatch never matched what the cards actually drew, in any
   game. shared/table-theme.js always writes a valid id to
   [data-deck-style], so this rule is what a fresh visitor sees. */
[data-deck-style="nautical"] .snz-card.face-down {
  background: radial-gradient(circle at 50% 42%, #234b6e 0%, #0f2438 100%);
  border: 1px solid #3f6f99;
  display: flex; align-items: center; justify-content: center;
  color: #ffd700; font-size: 17px;
}
[data-deck-style="nautical"] .snz-card.face-down::after { content: '✦'; }
[data-deck-style="nautical"] .snz-card.mini.face-down { font-size: 11px; }
[data-deck-style="emerald"] .snz-card.face-down {
  background:
    repeating-linear-gradient(45deg, rgba(255,255,255,0.08) 0, rgba(255,255,255,0.08) 3px, transparent 3px, transparent 14px),
    linear-gradient(160deg, #0f4a3a, #06261e);
  border: 1px solid #c9a227;
  display: flex; align-items: center; justify-content: center;
  color: #c9a227; font-size: 17px;
}
[data-deck-style="geo"] .snz-card.face-down {
  background-color: #101114;
  background-image:
    linear-gradient(135deg, #1c1e24 25%, transparent 25%),
    linear-gradient(225deg, #1c1e24 25%, transparent 25%),
    linear-gradient(45deg, #1c1e24 25%, transparent 25%),
    linear-gradient(315deg, #1c1e24 25%, transparent 25%);
  background-position: 0 0, 8px 0, 8px -8px, 0 8px;
  background-size: 16px 16px;
  border-color: #565b66;
  display: flex; align-items: center; justify-content: center;
  color: #9aa4b8; font-size: 17px;
}
[data-deck-style="sunset"] .snz-card.face-down {
  background: radial-gradient(circle at 50% 60%, #ffb35c 0%, #e8586b 45%, #5a2a6e 100%);
  border-color: rgba(255, 255, 255, 0.5);
  display: flex; align-items: center; justify-content: center;
  color: #fff4dc; font-size: 17px;
}
[data-deck-style="deco"] .snz-card.face-down {
  background-color: #17140f;
  background-image: repeating-conic-gradient(from 0deg at 50% 120%, #17140f 0deg 8deg, #8a5a2a 8deg 10deg);
  border: 2px solid #b9812f;
  display: flex; align-items: center; justify-content: center;
  color: #e2b45a; font-size: 17px;
}
[data-deck-style="frost"] .snz-card.face-down {
  background:
    repeating-linear-gradient(60deg, rgba(255,255,255,0.5) 0 1px, transparent 1px 10px),
    repeating-linear-gradient(-60deg, rgba(255,255,255,0.5) 0 1px, transparent 1px 10px),
    linear-gradient(160deg, #cfe8f5, #8fb9d6);
  border: 1px solid #ffffff;
  display: flex; align-items: center; justify-content: center;
  color: #ffffff; font-size: 17px;
}
[data-deck-style="velvet"] .snz-card.face-down {
  background:
    repeating-linear-gradient(45deg, rgba(0,0,0,0.25) 0 1px, transparent 1px 10px),
    repeating-linear-gradient(-45deg, rgba(0,0,0,0.25) 0 1px, transparent 1px 10px),
    radial-gradient(circle at 50% 45%, #8a1a2a, #4a0d16);
  border: 1px solid #d4af37;
  display: flex; align-items: center; justify-content: center;
  color: #d4af37; font-size: 17px;
}
[data-deck-style="slate"] .snz-card.face-down {
  background: linear-gradient(160deg, #2b2f36, #16181c);
  border-color: #4a4f58;
  display: flex; align-items: center; justify-content: center;
  color: #c7a55a; font-family: Georgia, serif; font-weight: bold; font-size: 17px;
}
[data-deck-style="camo"] .snz-card.face-down {
  background-color: #24361f;
  background-image:
    radial-gradient(ellipse 14px 9px at 20% 30%, #3c5230 40%, transparent 41%),
    radial-gradient(ellipse 17px 10px at 75% 20%, #17240f 40%, transparent 41%),
    radial-gradient(ellipse 15px 10px at 30% 75%, #17240f 40%, transparent 41%),
    radial-gradient(ellipse 18px 12px at 80% 70%, #3c5230 40%, transparent 41%);
  border-color: rgba(0, 0, 0, 0.3);
  display: flex; align-items: center; justify-content: center;
  color: #d8d8b0; font-size: 17px;
}
/* Mini variant (see the "classic" comment above) needs the same glyph-size split as nautical, for every glyph-bearing style. */
[data-deck-style="emerald"] .snz-card.mini.face-down,
[data-deck-style="geo"] .snz-card.mini.face-down,
[data-deck-style="sunset"] .snz-card.mini.face-down,
[data-deck-style="deco"] .snz-card.mini.face-down,
[data-deck-style="frost"] .snz-card.mini.face-down,
[data-deck-style="velvet"] .snz-card.mini.face-down,
[data-deck-style="slate"] .snz-card.mini.face-down,
[data-deck-style="camo"] .snz-card.mini.face-down { font-size: 11px; }
[data-deck-style="emerald"] .snz-card.face-down::after { content: '✦'; }
[data-deck-style="geo"] .snz-card.face-down::after { content: '◇'; }
[data-deck-style="sunset"] .snz-card.face-down::after { content: '●'; }
[data-deck-style="deco"] .snz-card.face-down::after { content: '▲'; }
[data-deck-style="frost"] .snz-card.face-down::after { content: '❄'; }
[data-deck-style="velvet"] .snz-card.face-down::after { content: '◆'; }
[data-deck-style="slate"] .snz-card.face-down::after { content: 'L'; }
[data-deck-style="camo"] .snz-card.face-down::after { content: '★'; }
.snz-card.empty-slot {
  background: transparent;
  border: 2px dashed var(--border-strong);
  box-shadow: none;
}
.snz-card.playable { cursor: grab; }
.snz-card.playable:active { cursor: grabbing; }
.snz-card.mini { width: 32px; height: 46px; border-radius: 4px; font-size: 0.68rem; }
.snz-card.mini .snz-rank { font-size: 0.66rem; }
.snz-card.mini .snz-suit { font-size: 0.78rem; margin-top: 1px; }
.snz-card.mini .snz-corner-index { font-size: 0.44rem; }
.snz-card.mini .snz-corner-top { top: 2px; left: 3px; }
.snz-card.mini .snz-corner-bottom { bottom: 2px; right: 3px; }
.snz-card.mini .snz-center-suit { font-size: 1rem; }

/* A tiny count badge pinned to a pile's corner (Nerts pile, stock, any
   pile deep enough that "how many are under here" matters more than
   what they are). */
.snz-count-badge {
  position: absolute;
  bottom: -6px;
  right: -6px;
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  color: var(--text);
  font-size: 0.65rem;
  font-weight: 800;
  min-width: 1.4em;
  text-align: center;
  border-radius: 999px;
  padding: 0.05rem 0.3rem;
}

/* ── Foundations — the one shared surface everyone plays into. Wraps
   freely since the number of piles is unbounded (any player's Ace can
   start a new one, even a second pile of a suit that's already open) —
   real Snertz gets genuinely cluttered here and that's part of the
   game, not a bug to design around. ─────────────────────────────────── */
.foundations-panel {
  width: 100%;
  max-width: 900px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 0.8rem;
}
.foundations-label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: 0.5rem;
  text-align: center;
}
.foundations-area {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: center;
  min-height: 80px;
  border-radius: 10px;
  transition: background 0.12s ease;
}
.foundations-area.drag-over { background: rgba(255, 228, 56, 0.12); }
.foundation-pile { position: relative; }
.foundation-pile.drag-over .snz-card { box-shadow: 0 0 0 3px var(--accent), 0 2px 6px rgba(0, 0, 0, 0.35); }
/* One-shot flash the instant a foundation pile's top card actually
   changes (see renderFoundations in game.html for how "actually" is
   decided). A freshly-inserted element with an animation on it always
   plays from the start, so simply including this class only on the
   piles that changed is enough — no JS timers needed to add or remove
   it. Pure box-shadow + a touch of scale, nothing that shifts layout. */
@keyframes foundationJustPlayed {
  0%   { box-shadow: 0 0 0 0 rgba(255, 214, 51, 0.85), 0 2px 6px rgba(0, 0, 0, 0.35); transform: scale(1.08); }
  60%  { box-shadow: 0 0 0 9px rgba(255, 214, 51, 0), 0 2px 6px rgba(0, 0, 0, 0.35); }
  100% { box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35); transform: scale(1); }
}
.foundation-pile.just-played .snz-card { animation: foundationJustPlayed 0.55s ease-out; }
/* A completed pile (see renderFoundations — the King landed, face-down
   from here on) keeps a quiet permanent ring instead of no marker at
   all, so "this one's finished" reads as an accomplishment rather than
   as the pile having gone missing or broken. */
.foundation-pile.is-complete .snz-card { box-shadow: 0 0 0 2px var(--accent), 0 2px 6px rgba(0, 0, 0, 0.35); }
.foundations-empty-hint {
  color: var(--text-muted);
  font-size: 0.8rem;
  align-self: center;
  padding: 1.2rem 0;
}

/* ── Opponents strip — read-only mini-boards, scrolls horizontally on
   narrow screens rather than wrapping, so a long row of them never pushes
   anything else around. Sits below my own board now (see
   renderPlayingBoard) rather than between it and the foundations, so
   other people's cards aren't visually in the way of the one drag path
   that actually matters: my board up to the shared foundations. ────── */
.opponents-strip {
  width: 100%;
  max-width: 900px;
  display: flex;
  gap: 0.6rem;
  overflow-x: auto;
  padding-bottom: 0.2rem;
}
.opponent-board {
  flex-shrink: 0;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 0.6rem 0.7rem;
  min-width: 220px;
}
.opponent-board.is-finished { opacity: 0.6; }
.opponent-board-name {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-weight: 700;
  font-size: 0.82rem;
  margin-bottom: 0.4rem;
}
.opponent-board-row { display: flex; gap: 0.35rem; align-items: flex-end; }
.opponent-tableau { display: flex; gap: 0.3rem; }

/* ── My board — the interactive area. Nerts pile on the left (the thing
   you're racing to empty), four tableau columns in the middle (most of
   the width — this is where the actual decision-making happens),
   stock/waste on the right, same left-to-right reading order as a
   physical Snertz deal in front of you. ─────────────────────────────── */
.my-board-panel {
  width: 100%;
  max-width: 900px;
  background: var(--surface-1);
  border: 2px solid var(--border-strong);
  border-radius: 16px;
  padding: 0.9rem;
}
.my-board-label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-muted);
  margin-bottom: 0.6rem;
  text-align: center;
}
/* Late-join notice — shown in place of an empty board when someone joins
   mid-round (see renderMyBoard). Everyone else's boards are already live
   at this point, so a plain gray status-line read as background noise
   and got missed. Accent color plus a continuous pulse (unlike the
   one-shot flashes above) keeps it noticeable for as long as it's the
   only thing in the panel. */
.status-line.join-notice {
  color: var(--accent);
  font-weight: 700;
  animation: joinNoticePulse 1.3s ease-in-out infinite;
}
@keyframes joinNoticePulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.5; transform: scale(1.04); }
}
.my-board-row {
  display: flex;
  gap: 8px; /* fixed — folded into the --card-w formula's constant term above */
  align-items: flex-start;
  justify-content: center;
  /* Was flex-wrap: wrap — which let the browser's own greedy wrapping
     algorithm decide where this row broke onto a second line, and that
     decision is sensitive to exact pixel width. On most phones, Nerts +
     tableau fit on one line together and only stock/waste wrapped down;
     on a narrower phone, Nerts alone stopped fitting next to the
     tableau row too and got bumped onto its own line ABOVE it instead —
     a different arrangement from every other device, not a smaller
     version of the same one. Nerts/tableau/stock stay in one row on
     every screen now, full stop; --card-w (see the top of this file)
     shrinks everything fluidly enough that real phones never actually
     need to fall back on anything else, and overflow-x here is only a
     backstop for anything narrower than any real phone, so it scrolls
     a few pixels sideways in that unlikely case instead of ever
     rearranging into a different layout again.
  */
  flex-wrap: nowrap;
  overflow-x: auto;
  padding-bottom: 0.2rem;
}
.nerts-pile-slot, .stock-slot, .waste-slot { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; }
.stock-waste-slot { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; }
/* display:inline-block + max-width matter here, not just cosmetic — a
   plain inline span has no width to constrain in the first place, so
   "Snertz Pile" (the longest of these captions) was rendering at its
   full one-line width and forcing .nerts-pile-slot wider than the card
   sitting right below it, no matter how small --card-w got. That's what
   was causing a few pixels of real overflow on .my-board-row even after
   the card itself had shrunk enough to fit — the card was never the
   widest thing in its own column, the label above it was. The font-size
   scaling with --card-w too (not fixed) is what actually keeps this
   working at the smallest sizes: a fixed font-size meant "SNERTZ" as one
   unbreakable word (no space to wrap at) stayed wider than the box even
   after max-width capped the box itself, so the letters visually spilled
   out past their own container instead of ever truly overflowing the
   row's layout width — not a scrollbar, but not right either. Scaling
   the font down with the card keeps that one word inside its own box at
   every size actually tested; overflow-wrap is only a backstop in case
   some longer word ever ends up in this class that the numbers here
   weren't checked against. */
.pile-caption {
  display: inline-block;
  max-width: var(--card-w);
  overflow-wrap: break-word;
  font-size: calc(var(--card-w) * 0.2);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  text-align: center;
}
.role-locked { opacity: 0.55; }
.role-locked .pile-caption { font-style: italic; }

.bury-propose-btn { align-self: center; }
.bury-vote-banner {
  /* Was display:flex with flex-wrap — text and buttons sat side by side
     on a wide screen but stacked onto two rows on a narrower one, so
     this banner's own height (not just whether it exists at all) varied
     by device width. Always stacked now, on every width, so the one
     fixed height .bury-banner-area reserves below actually is the same
     shape every time, not just the same rough total. */
  width: 100%;
  max-width: 900px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  background: var(--surface-1);
  border: 1px solid var(--accent);
  border-radius: 12px;
  padding: 0.6rem 0.9rem;
  font-size: 0.85rem;
  text-align: center;
}
.bury-vote-actions { display: flex; gap: 0.5rem; }
/* Sits last in the race layout now (under the opponents' strip — see
   renderPlayingBoard in game.html), so the fixed min-height this used
   to reserve is gone: it existed so the foundations and boards below
   it wouldn't jump when a bury vote started or resolved, and nothing
   is below it anymore. */
.bury-banner-area {
  width: 100%;
  max-width: 900px;
  margin-top: 0.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
}

.tableau-row {
  display: flex;
  gap: 6px; /* fixed — folded into the --card-w formula's constant term above */
}
.tableau-pile {
  width: var(--card-w);
  /* --n (set inline in game.html's renderMyTableauPile) is the pile's
     current card count; the height/cascade math here always matches
     .tableau-pile .snz-card's own top offset below since both are
     driven off the same --card-w, rather than two breakpoint-specific
     numbers that could silently drift apart from each other. */
  min-height: calc(var(--card-w) * 1.42 + (var(--n, 1) - 1) * var(--card-w) * 0.34);
  position: relative;
  border-radius: 8px;
}
.tableau-pile.drag-over { box-shadow: 0 0 0 3px var(--accent); border-radius: 8px; }
.tableau-pile .snz-card {
  position: absolute;
  top: 0;
  left: 0;
}
/* Cascading stack — each card after the first in a pile is nudged down
   so the whole run is readable at a glance, same visual idea as a
   Klondike tableau column. Generated inline per-card as --i (see
   game.html's renderTableauPile) since the offset depends on that
   card's position within its own pile, not something a static nth-
   child rule could express cleanly against a pile of unbounded length. */
.tableau-pile .snz-card { top: calc(var(--i, 0) * var(--card-w) * 0.34); }
.tableau-pile-spacer { width: var(--card-w); height: calc(var(--card-w) * 1.42); }

.stock-back { position: relative; }

/* ── Drag ghost — the floating card(s) that follow the pointer while
   dragging (see game.html's pointer-events drag implementation; native
   HTML5 drag-and-drop has spotty mobile/touch support, which matters a
   lot here since this site is meant to work well on a phone). Built as
   a plain wrapper div with one or more .snz-card children rather than a
   single cloned card, since dragging a whole tableau run now needs to
   show every card in that group, cascaded the same way the tableau
   itself displays them (see getTableauCascadeOffset in game.html, which
   measures the real gap live rather than assuming this breakpoint's
   numbers). ──── */
.drag-ghost {
  position: fixed;
  z-index: 900;
  pointer-events: none;
  transform: scale(1.08) rotate(-2deg);
  /* drop-shadow (not box-shadow) so the shadow follows the actual visible
     card shapes inside this transparent wrapper, not the wrapper's own
     invisible bounding box — matters once the wrapper is taller than one
     card for a multi-card group. */
  filter: drop-shadow(0 8px 20px rgba(0, 0, 0, 0.5));
}
.drag-ghost .snz-card { position: absolute; left: 0; }
.drag-source-hidden { visibility: hidden; }

/* ── Tap-to-play mode's "no" — see shakeCard in game.html. A tapped card
   with nowhere legal to go gets a quick shake instead of the tap
   silently doing nothing, which would read as broken in a mode whose
   whole point is "tap it and watch it move." Plain transform, no layout
   shift, so it can't jostle neighboring cards in the same cascaded pile. */
@keyframes tapInvalidShake {
  10%, 90% { transform: translateX(-1px); }
  20%, 80% { transform: translateX(2px); }
  30%, 50%, 70% { transform: translateX(-4px); }
  40%, 60% { transform: translateX(4px); }
}
.tap-invalid { animation: tapInvalidShake 0.4s; }

/* ── Round-results / match-over scoreboard ──────────────────────────── */
.scoreboard { display: flex; flex-direction: column; gap: 0.5rem; width: 100%; max-width: 560px; }
.scoreboard-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  background: var(--surface-2);
  border-radius: 10px;
  padding: 0.6rem 0.9rem;
}
.scoreboard-row.is-winner { border: 1px solid var(--accent); }
.scoreboard-rank { width: 1.5rem; color: var(--text-muted); font-weight: 700; }
.scoreboard-name { flex: 1; font-weight: 600; }
.scoreboard-detail { font-size: 0.78rem; color: var(--text-mid); text-align: right; }
.scoreboard-total {
  font-weight: 800;
  font-size: 0.95rem;
  min-width: 2.4em;
  text-align: right;
}
.match-winner-banner {
  text-align: center;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.1rem;
  color: var(--on-accent);
  background: var(--accent);
  border-radius: 12px;
  padding: 0.8rem 1rem;
  width: 100%;
  max-width: 560px;
}

/* ── Confirm dialogs — Leave Game / End Game for Everyone. Same pattern
   as every other game here. ─────────────────────────────────────────── */
.confirm-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.8);
  z-index: 600;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.confirm-overlay.show { display: flex; }
.confirm-box {
  width: 100%;
  max-width: 340px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 28px 24px;
  text-align: center;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
}
.confirm-title { font-family: var(--font-display); color: var(--text); font-size: 18px; margin: 0 0 10px; }
.confirm-body { color: var(--text-mid); font-size: 13px; line-height: 1.5; margin: 0 0 24px; }
.confirm-actions { display: flex; gap: 10px; }
.confirm-btn { flex: 1; padding: 12px; font-family: var(--font-body); font-size: 14px; font-weight: 700; border: 0; border-radius: 8px; cursor: pointer; }
.confirm-btn.cancel { background: var(--surface-2); color: var(--text-mid); }
.confirm-btn.danger { background: var(--danger); color: #fff; }
.confirm-btn.danger:hover { background: var(--danger-deep); }

/* Settings menu, section labels and the card-back picker now come from
   the shared games.lightlol.com/shared/table-theme.css / .js. */

/* ── Chat modal — same confirm-box shell as Settings/Leave/End Game
   above, just wider and with a scrolling message list + input row
   instead of a picker or a warning. Only ever shown while
   gameState.chatEnabled is true (see render() and hasBots on the
   server) — a room with all real players never sees the button that
   opens this at all. ─────────────────────────────────────────────── */
.chat-box { max-width: 420px; }
.chat-messages {
  max-height: 40vh;
  min-height: 120px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.6rem;
  margin-bottom: 0.8rem;
  text-align: left;
}
.chat-line { font-size: 0.85rem; line-height: 1.35; }
.chat-author { font-weight: 700; margin-right: 0.35rem; }
.chat-line.is-me .chat-author { color: var(--accent) !important; }
.chat-text { color: var(--text); word-break: break-word; }
.chat-input-row { display: flex; gap: 0.5rem; margin-bottom: 12px; }
.chat-input-row input {
  flex: 1;
  min-width: 0; /* lets the input actually shrink in a flex row instead of overflowing the box */
  padding: 0.6rem 0.7rem;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 0.9rem;
}
/* The little "something new" dot on the header's chat button — see
   updateChatUnreadBadge in game.html for when it shows. The dot itself
   stays solid and still; the ::after pseudo-element is what actually
   pulses, an expanding-and-fading ring behind it (the classic "ping"
   notification pattern) rather than the dot itself growing/shrinking,
   so there's always a crisp, readable dot at rest and motion is purely
   an attention cue layered behind it. No extra DOM element needed for
   the ring — .icon-btn has no overflow:hidden, so it's free to expand
   past the dot's own tiny box without getting clipped. */
.chat-unread-badge {
  position: absolute;
  top: 3px;
  right: 3px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--danger);
  border: 1px solid var(--surface-2);
}
.chat-unread-badge::after {
  content: '';
  position: absolute;
  inset: -1px; /* match the dot's own box, roughly, accounting for its border */
  border-radius: 50%;
  background: var(--danger);
  animation: chatUnreadPing 1.6s cubic-bezier(0, 0, 0.2, 1) infinite;
}
@keyframes chatUnreadPing {
  0% { transform: scale(1); opacity: 0.6; }
  75%, 100% { transform: scale(2.4); opacity: 0; }
}
/* ── Toast ──────────────────────────────────────────────────────────── */
.toast {
  position: fixed;
  bottom: 1.5rem;
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  padding: 0.7rem 1.1rem;
  font-size: 0.85rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 800;
  max-width: 90vw;
  text-align: center;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
