:root {
  --bg: #0f1115;
  --panel: #171a21;
  --panel-2: #1f242d;
  --text: #e6e8ee;
  --muted: #9aa3b2;
  --accent: #5cc8ff;
  --grid-bg: #11141a;
  --cell-bg: #1a1f28;
  --cell-bg-2: #1d222c;
  --cell-border: #232936;
  --food: #ff6b6b;
  --food-glow: rgba(255, 107, 107, 0.45);
  --hint: rgba(92, 200, 255, 0.18);
  --miss: rgba(255, 107, 107, 0.35);
  --player: #5cc8ff;
  --player-head: #b9e6ff;
  --opponent: #e040fb;
  --opponent-head: #f5b8ff;
  --bot-1: #ffb454;
  --bot-2: #b07cff;
  --bot-3: #6dd6a3;
  --bot-4: #ff7ab6;
  --bot-5: #f7d160;
  --cell-size: 32px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, sans-serif;
  min-height: 100vh;
}

header {
  padding: 16px 24px 0;
}
header h1 {
  margin: 0;
  font-size: 22px;
  letter-spacing: 0.5px;
}
.tagline {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: 13px;
}

main {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: 24px;
  padding: 16px 24px 32px;
  align-items: start;
}

#hud {
  display: flex;
  flex-direction: column;
  gap: 12px;
  position: sticky;
  top: 16px;
}

.panel {
  background: var(--panel);
  border: 1px solid var(--cell-border);
  border-radius: 10px;
  padding: 12px 14px;
}
.panel h2 {
  margin: 0 0 8px;
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
}
.panel ul, .panel ol {
  margin: 0;
  padding-left: 18px;
  font-size: 13px;
  line-height: 1.5;
}
.panel label {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 13px;
  margin-bottom: 8px;
  gap: 8px;
}
.panel input[type="number"], .panel select {
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  padding: 4px 6px;
  font-size: 13px;
  width: 90px;
}
/* Country override dropdown — country names are longer than the default
   90px select width can show, so widen it. The flex parent (.panel label)
   keeps the label hugging the left edge while the select grows. */
.panel select#country-override {
  width: 160px;
  max-width: 100%;
}
.panel button {
  width: 100%;
  margin-top: 6px;
  background: var(--accent);
  color: #07131c;
  border: 0;
  border-radius: 6px;
  padding: 8px;
  font-weight: 600;
  cursor: pointer;
}
.panel button:hover { filter: brightness(1.1); }
kbd {
  background: var(--panel-2);
  border: 1px solid var(--cell-border);
  border-bottom-width: 2px;
  border-radius: 4px;
  padding: 1px 5px;
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.legend.food {
  display: inline-block;
  background: var(--food);
  color: #2a0707;
  border-radius: 4px;
  padding: 0 4px;
  font-weight: 700;
}

#scoreboard {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.score-row {
  display: grid;
  grid-template-columns: 14px 1fr auto auto;
  align-items: center;
  gap: 8px;
  background: var(--panel-2);
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  padding: 6px 8px;
  font-size: 13px;
}
.score-row .swatch {
  width: 12px; height: 12px; border-radius: 3px;
}
.score-row .name { font-weight: 600; }
.score-row .meta { color: var(--muted); font-variant-numeric: tabular-nums; }
.score-row.dead { opacity: 0.5; text-decoration: line-through; }
.score-row.self {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Difficulty preset selector */
.diff-label {
  display: block;
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin-bottom: 6px;
}
.diff-btns {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  margin-bottom: 10px;
}
.diff-btn {
  padding: 7px 4px;
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  background: var(--panel-2);
  color: var(--muted);
  cursor: pointer;
  transition: background 120ms, color 120ms, border-color 120ms;
  text-align: center;
  line-height: 1;
}
.diff-btn:hover { border-color: #3a4050; color: var(--text); }
.diff-name { display: block; font-size: 12px; font-weight: 700; }
.diff-sub  { display: block; font-size: 10px; font-weight: 500; opacity: 0.75; margin-top: 3px; }
.diff-btn[data-diff="easy"].active   { background: rgba(109,214,163,0.15); border-color: #6dd6a3; color: #6dd6a3; }
.diff-btn[data-diff="medium"].active { background: rgba(255,180,84,0.15);  border-color: #ffb454; color: #ffb454; }
.diff-btn[data-diff="hard"].active   { background: rgba(255,107,107,0.15); border-color: #ff6b6b; color: #ff6b6b; }

.mode-toggle {
  display: flex;
  gap: 8px;
  margin-bottom: 8px;
}
.mode-toggle label {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 13px;
  cursor: pointer;
}
.hi-score {
  margin-top: 4px;
  font-size: 13px;
  color: var(--muted);
}
.hi-score b { color: var(--text); font-variant-numeric: tabular-nums; }

/* Per-difficulty bests panel — replaces the single "High score: N" row.
   Two-column compact layout (label | tabular-nums value); the row with
   the current overall max is bolded + accent-tinted so the player's PB
   is still visually obvious. Empty rows render "—" not "0". */
.hi-score-panel {
  margin-top: 6px;
  font-size: 13px;
  color: var(--muted);
}
.hi-score-title {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 2px;
}
.hi-score-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.hi-score-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 1px 0;
  font-variant-numeric: tabular-nums;
}
.hi-score-label {
  color: var(--muted);
}
.hi-score-val {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  min-width: 3ch;
  text-align: right;
}
.hi-score-row.hi-score-best .hi-score-label,
.hi-score-row.hi-score-best .hi-score-val {
  color: var(--accent);
  font-weight: 600;
}
.notice {
  margin: 8px 0 0;
  padding: 6px 8px;
  background: var(--panel-2);
  border-left: 3px solid var(--food);
  font-size: 12px;
  color: var(--muted);
  border-radius: 4px;
}
.cell.snake[data-self="1"] {
  outline: 1px solid #ffffff80;
  outline-offset: -2px;
}
#status.ok { color: var(--accent); }

#leaderboard {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.lb-empty {
  color: var(--muted);
  font-style: italic;
  font-size: 12px;
  padding: 4px 0;
}
.lb-row {
  display: grid;
  grid-template-columns: 18px 1fr auto auto auto auto;
  align-items: center;
  gap: 6px;
  padding: 4px 6px;
  background: var(--panel-2);
  border: 1px solid var(--cell-border);
  border-radius: 4px;
}
.lb-rank {
  color: var(--muted);
  text-align: right;
}
.lb-name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lb-meta {
  color: var(--muted);
}
.lb-score {
  color: var(--accent);
  font-weight: 700;
}
.lb-tag {
  font-size: 10px;
  font-weight: 700;
  padding: 1px 4px;
  border-radius: 3px;
  letter-spacing: 0.5px;
}
.lb-tag-multi {
  background: rgba(255,107,107,0.25);
  color: #ffb4b4;
}
.lb-tag-single {
  background: rgba(92,200,255,0.20);
  color: #a8def5;
}
.lb-ago {
  font-size: 11px;
}

/* Country flag — used in 24h leaderboard rows AND live MP scoreboard rows.
   Inline span before the player name; small right margin so the name
   doesn't crowd the flag. font-size pumped a touch so emoji-rendered
   flags don't disappear at the surrounding 12-13px name size. */
.flag {
  display: inline-block;
  margin-right: 4px;
  font-size: 1.05em;
  line-height: 1;
}

#board-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  position: relative; /* anchor for room overlay positioning */
}

/* Per-move countdown timer bar */
#timer-wrap {
  width: var(--grid-px-width, 400px);
  height: 5px;
  background: var(--panel-2);
  border-radius: 3px;
  overflow: hidden;
  margin-top: -6px; /* close the gap between grid and bar */
  transition: opacity 200ms;
}
#timer-wrap.no-timer {
  opacity: 0;
  pointer-events: none;
}
#timer-bar {
  height: 100%;
  width: 100%;
  background: var(--accent);
  border-radius: 3px;
}

#grid {
  display: grid;
  gap: 2px;
  padding: 8px;
  background: var(--grid-bg);
  border: 1px solid var(--cell-border);
  border-radius: 10px;
  user-select: none;
}

#fx-canvas {
  position: absolute;
  pointer-events: none;
  z-index: 5;
  border-radius: 10px;
}

/* Full-grid white flash on death */
#death-flash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: #ffffff;
  opacity: 0;
  z-index: 7;
  border-radius: 10px;
  mix-blend-mode: screen;
}
#death-flash.fire {
  animation: death-flash 380ms ease-out;
}
@keyframes death-flash {
  0%   { opacity: 0; }
  10%  { opacity: 0.7; }
  100% { opacity: 0; }
}

/* Dying snake body — visible briefly then fades */
.cell.snake.dying {
  background: var(--dying-color, var(--food)) !important;
  color: transparent !important;
  outline: none;
  animation: snake-dying 0.9s cubic-bezier(0.5, 0, 0.6, 1) forwards;
}
@keyframes snake-dying {
  0%   { opacity: 1; transform: scale(1); filter: brightness(1.4); }
  18%  { opacity: 1; transform: scale(1.12); filter: brightness(1.7); }
  100% { opacity: 0; transform: scale(0.55) rotate(-8deg); filter: brightness(0.6); }
}

/* Floating "YOU" tag above the local player's head (multiplayer onboarding) */
#you-tag {
  position: absolute;
  z-index: 10;
  background: var(--player);
  color: #07131c;
  font-weight: 800;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 6px;
  transform: translate(-50%, 0);
  pointer-events: none;
  white-space: nowrap;
  box-shadow: 0 4px 14px rgba(92, 200, 255, 0.55);
  transition: opacity 600ms ease-out;
  animation: you-tag-bob 1.6s ease-in-out infinite;
  opacity: 1;
}
#you-tag::after {
  content: "";
  position: absolute;
  bottom: -5px;
  left: 50%;
  transform: translateX(-50%);
  width: 0; height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 5px solid var(--player);
}
#you-tag.fading { opacity: 0; }
@keyframes you-tag-bob {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -3px; }
}

/* RTL tweaks (Arabic) — keep grid + score panels visually balanced */
html[dir="rtl"] .lobby-code-input,
html[dir="rtl"] .room-code-big {
  direction: ltr; /* room codes are always Latin */
  unicode-bidi: isolate;
}
html[dir="rtl"] kbd { direction: ltr; unicode-bidi: isolate; }

.cell {
  width: var(--cell-size);
  height: var(--cell-size);
  background: var(--cell-bg);
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: calc(var(--cell-size) * 0.55);
  font-weight: 600;
  color: #c8cfdb;
  position: relative;
  transition: background-color 80ms linear, transform 80ms linear;
}
.cell.alt { background: var(--cell-bg-2); }

.cell.hint {
  background: var(--hint);
  color: var(--player-head);
}

/* === Valid-move coaching ===================================================
   Highlight the (up to) 4 typeable cells orthogonal to the player's head and
   dim everything else. Helps new players locate the cells they can act on
   without scanning all 64 letters every tick — see HIGHLIGHT_VALID_KEY. The
   classes are added/cleared per render() in applyValidHighlights(); we never
   touch snake/food cells (they have their own visual identity already). */
.cell.is-valid {
  /* Subtle 1px ring + a touch of brightness. The combination — not just the
     glow alone — reads at a glance even on the alt-background cells. */
  box-shadow: 0 0 0 1px var(--accent);
  filter: brightness(1.1);
  transition: box-shadow 200ms ease, filter 200ms ease;
}
.cell.is-dim {
  /* Drop to ~55% — clearly secondary, but the letter is still readable so a
     player who's planning two moves ahead can still scan the board. */
  opacity: 0.55;
  transition: opacity 200ms ease;
}

.cell.first-pulse {
  background: var(--hint);
  color: var(--player-head);
  animation: first-pulse-glow 1.05s ease-in-out infinite;
  z-index: 1;
}
@keyframes first-pulse-glow {
  0%   { box-shadow: 0 0 0 0 var(--player-head); transform: scale(1); }
  55%  { box-shadow: 0 0 0 9px transparent;     transform: scale(1.07); }
  100% { box-shadow: 0 0 0 0 transparent;        transform: scale(1); }
}

.cell.miss-flash {
  background: var(--miss) !important;
}

/* === Letter-swap telegraphy ===============================================
   When letter stability is ON, the dedup repair occasionally STILL has to
   change a typeable cell's letter (collision the head + side cells alone
   can't absorb). To avoid the silent "underneath my fingers" feel that fast
   typists complained about, we paint a brief fade-shift on the cell so the
   change is visible. Duration matches LETTER_SWAP_MS in game.js (280ms).
   We touch only color/scale — no background changes — so the animation
   layers cleanly on top of any other cell state (alt, hint, is-valid). */
.cell.letter-swap {
  animation: letter-swap-anim 280ms ease-out;
}
@keyframes letter-swap-anim {
  0%   { color: var(--food); transform: scale(0.85); opacity: 0.55; }
  35%  { color: var(--food); transform: scale(1.08); opacity: 1; }
  100% { color: inherit;     transform: scale(1);    opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  /* Reduced-motion users still get a color flash so the change is visible,
     but no scale/opacity animation. The flash is brief and uses the food
     color (already used elsewhere as a "look here" cue). */
  .cell.letter-swap { animation: letter-swap-rm 280ms linear; }
  @keyframes letter-swap-rm {
    0%, 80% { color: var(--food); }
    100%    { color: inherit; }
  }
}

/* Food cells layer two visual signals on top of the amber background:
   1. Outward "pulse" glow ring — radiates --food-glow outward
   2. Inset bright border — 2px white, opacity pulses 0.6 → 1.0 → 0.6 at 1Hz
   The inset border is the disambiguator: a snake body can be assigned the
   same amber as food (see GH issue #43 — yellow snake vs yellow food), but
   the bright inset border is a non-color signal that food cells always
   carry, so they remain identifiable regardless of color collision. The
   inset shadow stays inside the cell box, so grid layout is preserved.
   Both effects compose into a single box-shadow list and animate together
   in one keyframe so they don't fight over the property. */
.cell.food {
  background: var(--food);
  color: #2a0707;
  box-shadow:
    0 0 0 0 var(--food-glow),
    inset 0 0 0 2px rgba(255, 255, 255, 0.6),
    inset 0 0 4px rgba(255, 255, 255, 0.35);
  animation: pulse 1.6s infinite;
}
@keyframes pulse {
  /* 1.6s cycle. Outward glow ring (slot 1) ramps then fades.
     Inner ring (slot 2) opacity pulses on a sine-ish 0.6 → 1.0 → 0.6 sweep
     covering ~1 full beat per food cycle (the keyframe is 1.6s, so the
     visible ring pulse is ~0.625 Hz — gentle and non-distracting). */
  0%   { box-shadow: 0 0 0 0 var(--food-glow),
                     inset 0 0 0 2px rgba(255, 255, 255, 0.6),
                     inset 0 0 4px rgba(255, 255, 255, 0.35); }
  50%  { box-shadow: 0 0 0 7px rgba(255, 107, 107, 0.18),
                     inset 0 0 0 2px rgba(255, 255, 255, 1.0),
                     inset 0 0 5px rgba(255, 255, 255, 0.55); }
  70%  { box-shadow: 0 0 0 10px rgba(255, 107, 107, 0),
                     inset 0 0 0 2px rgba(255, 255, 255, 0.85),
                     inset 0 0 4px rgba(255, 255, 255, 0.4); }
  100% { box-shadow: 0 0 0 0 rgba(255, 107, 107, 0),
                     inset 0 0 0 2px rgba(255, 255, 255, 0.6),
                     inset 0 0 4px rgba(255, 255, 255, 0.35); }
}

/* === Switcheroo heat predictor =============================================
   The dedup repair was the single most-common "letter changed under my
   fingers" complaint — not because it fired often (it doesn't), but because
   when it did the swap was silent. The heat predictor identifies cells the
   repair is ABOUT to re-roll three ticks ahead and paints them yellow →
   orange → red. The player can grab the cell for a tiered bonus before it
   changes; if they don't, the cell switches and they at least saw it coming.

   Color tones picked from the existing palette so the heat reads as part of
   the same world: yellow == --bot-5 (saturated gold), orange == --bot-1
   (warm ember), red == --food (the strongest "do something" cue). 200ms
   transitions between heat levels for smoothness. Red also pulses (1Hz)
   so it commands attention even at a glance.

   Z-order vs. food: heat-* paints background+border. food cells use a
   different background (red) AND animate the pulse via box-shadow — they
   never co-exist with heat (food is its own visual treatment per the
   design lock-in), so there's no rule conflict to resolve.

   Snake bodies: heat is cleared the moment the snake's head consumes the
   cell, so no body cell will ever carry a heat class. Defensive ordering:
   the renderer assigns heat-* BEFORE the snake class, and the snake
   background uses !important via [data-pid] selectors anyway, so a stale
   heat class wouldn't visibly leak. */

.cell.heat-3,
.cell.heat-2,
.cell.heat-1 {
  /* 200ms color transition between tiers feels smooth without losing
     the urgency of a tier flip. transform is included for the pulse
     animation on heat-1 to share a single transition contract. */
  transition: background-color 200ms ease-out, color 200ms ease-out;
}
.cell.heat-3 {
  /* Yellow — fresh threat. Soft, doesn't compete with the 4 typeable-cell
     glow (.is-valid uses --accent / blue) but is unmistakable next to the
     dim cells (.is-dim opacity 0.55). */
  background: rgba(247, 209, 96, 0.28);
  color: #f7d160;
}
.cell.heat-2 {
  /* Orange — heating up. Slightly brighter background + saturated text. */
  background: rgba(255, 180, 84, 0.42);
  color: #ffb454;
}
.cell.heat-1 {
  /* Red — last chance. Pulse animation calls attention without being
     dizzying (1Hz sine ease so the eye locks on). Color contrast is
     comparable to a food cell so the urgency reads clearly. */
  background: rgba(255, 107, 107, 0.45);
  color: #ff7ab6;
  font-weight: 700;
  animation: heat-pulse 1s ease-in-out infinite;
}
@keyframes heat-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 107, 107, 0.55); }
  50%      { box-shadow: 0 0 0 6px rgba(255, 107, 107, 0); }
}
@media (prefers-reduced-motion: reduce) {
  /* Skip the pulse but keep the color contrast — the threat still reads
     clearly without the kinetic component. */
  .cell.heat-1 { animation: none; }
}

.cell.snake {
  color: rgba(255,255,255,0.0);
  /* Smooth the per-segment head→tail brightness gradient so it eases as the
     snake moves. Inline opacity is set by render() in game.js. */
  transition: opacity 140ms linear;
}
.cell.snake.head {
  color: #0a0d12;
  font-weight: 800;
}
.cell.snake[data-pid="0"] { background: var(--player); }
.cell.snake.head[data-pid="0"] { background: var(--player-head); }
.cell.snake[data-pid="1"] { background: var(--bot-1); }
.cell.snake[data-pid="2"] { background: var(--bot-2); }
.cell.snake[data-pid="3"] { background: var(--bot-3); }
.cell.snake[data-pid="4"] { background: var(--bot-4); }
.cell.snake[data-pid="5"] { background: var(--bot-5); }

#status {
  min-height: 24px;
  font-size: 14px;
  color: var(--muted);
}
#status.gameover {
  color: var(--food);
  font-weight: 600;
}

/* === Reconnect badge ======================================================
   Tiny outline pill (style cribbed from .rg-beta-badge) anchored to the top-
   right of the board. Shown by game.js while retrying a dropped /room
   socket; swaps to .exhausted (red) after backoff is spent. */
#reconnect-badge {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 15;
  padding: 3px 8px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  line-height: 1.4;
  color: var(--food);
  background: rgba(242, 215, 76, 0.10);
  border: 1px solid var(--food);
  border-radius: 999px;
  pointer-events: none;
}
#reconnect-badge.exhausted {
  color: #ff6b6b;
  background: rgba(255, 107, 107, 0.12);
  border-color: #ff6b6b;
}

/* === Juice ================================================================ */

/* Screen shake on death */
@keyframes shake {
  0%,100% { transform: translate(0, 0) rotate(0deg); }
  12%  { transform: translate(-6px, -3px) rotate(-0.5deg); }
  25%  { transform: translate(5px, 3px) rotate(0.4deg); }
  37%  { transform: translate(-4px, 4px) rotate(-0.35deg); }
  50%  { transform: translate(4px, -3px) rotate(0.3deg); }
  62%  { transform: translate(-3px, 2px) rotate(-0.2deg); }
  75%  { transform: translate(3px, -2px) rotate(0.15deg); }
  87%  { transform: translate(-1px, 1px) rotate(-0.1deg); }
}
.board-shake { animation: shake 0.45s ease-out; }

/* Subtle "tic" shake for LOCAL player death only (paired with the existing
   heavier shake's sibling effects in triggerDeath). 80ms + 2-3px keeps it
   on the "I died" side of the line without crossing into earthquake. */
@keyframes deathShake {
  0%   { transform: translate(0, 0); }
  20%  { transform: translate(-2px, 1px); }
  40%  { transform: translate(2px, -1px); }
  60%  { transform: translate(-2px, 1px); }
  80%  { transform: translate(1px, -1px); }
  100% { transform: translate(0, 0); }
}
#board-wrap.is-dying { animation: deathShake 80ms ease-out; }

/* Food eat pop */
@keyframes eat-pop {
  0%   { transform: scale(1); }
  35%  { transform: scale(1.42); box-shadow: 0 0 0 7px rgba(185,230,255,0.45); }
  70%  { transform: scale(0.93); }
  100% { transform: scale(1); box-shadow: none; }
}
.cell.eat-pop {
  animation: eat-pop 0.28s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Snake head glow after eating */
@keyframes head-glow-anim {
  0%   { box-shadow: 0 0 0 4px rgba(185,230,255,0.9), 0 0 18px rgba(185,230,255,0.55); }
  100% { box-shadow: 0 0 0 0 rgba(185,230,255,0), 0 0 0 rgba(185,230,255,0); }
}
.cell.head-glow { animation: head-glow-anim 0.55s ease-out; }

/* Combo streak display */
#streak-display {
  min-height: 26px;
  font-size: 15px;
  font-weight: 700;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.5px;
  color: transparent;
  user-select: none;
}
#streak-display.active { color: #ffb454; }
#streak-display.hot {
  color: #ff6b6b;
  text-shadow: 0 0 10px rgba(255,107,107,0.7);
}
@keyframes streak-pop {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.25); }
  100% { transform: scale(1); }
}
#streak-display.bump { animation: streak-pop 0.18s ease-out; }

/* === Juice pass: per-keystroke micro-feedback ============================ */

/* (1) Cell flash + 1.05x scale on the cell the snake just landed on. The
   cell already has its color from the snake/head class — we layer a brief
   white-tinted box-shadow + scale on top, then ease back to neutral. */
@keyframes cell-land-anim {
  0%   { transform: scale(1.00); box-shadow: 0 0 0 0 rgba(255,255,255,0.0); filter: brightness(1); }
  35%  { transform: scale(1.05); box-shadow: 0 0 0 3px rgba(255,255,255,0.45); filter: brightness(1.18); }
  100% { transform: scale(1.00); box-shadow: 0 0 0 0 rgba(255,255,255,0.0); filter: brightness(1); }
}
.cell.cell-land {
  animation: cell-land-anim 110ms cubic-bezier(0.22, 1, 0.36, 1);
  z-index: 2;
}

/* (2) Floating "+10" pop on eat. DOM-elements live in #popups-layer
   (an aria-live=polite container inside #board-wrap). The layer itself
   is a transparent absolute overlay; individual popups position
   themselves relative to it. */
#popups-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 12;
}
.score-float {
  position: absolute;
  pointer-events: none;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: calc(var(--cell-size, 32px) * 0.85);
  font-weight: 800;
  color: var(--accent);
  text-shadow: 0 1px 0 rgba(0,0,0,0.45);
  transform: translate(-50%, -50%);
  animation: score-float-anim 700ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
  user-select: none;
  letter-spacing: 0.04em;
  white-space: nowrap;
  text-align: center;
  line-height: 1;
}
/* Combo streak echo: small "x4" badge under the "+10". Same animation,
   inherits the parent's color/fade/scale so it's visually one piece. */
.score-float-echo {
  display: block;
  font-size: 0.65em;
  font-weight: 700;
  margin-top: 2px;
  opacity: 0.85;
  letter-spacing: 0.06em;
}
@keyframes score-float-anim {
  0%   { opacity: 0;   transform: translate(-50%, -50%) scale(0.9); }
  20%  { opacity: 1;   transform: translate(-50%, -100%) scale(1.15); }
  60%  { opacity: 1;   transform: translate(-50%, -150%) scale(0.9); }
  100% { opacity: 0;   transform: translate(-50%, -180%) scale(0.9); }
}

/* "+100 PERFECT!" splash — bigger, gold, centered over the board.
   Pairs with (and slightly leads) the gold #room-result overlay. */
.score-float.perfect-splash {
  font-size: calc(var(--cell-size, 32px) * 1.6);
  font-weight: 900;
  color: #ffd24a;
  text-shadow:
    0 0 24px rgba(255, 210, 74, 0.7),
    0 0 60px rgba(255, 180, 50, 0.4),
    0 2px 0 rgba(0,0,0,0.5);
  /* Sits above normal popups but below the full PERFECT overlay (which
     uses position:fixed and its own stacking context). */
  z-index: 20;
  animation: perfect-splash-anim 1400ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes perfect-splash-anim {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
  14%  { opacity: 1; transform: translate(-50%, -50%) scale(1.2); }   /* 200ms in */
  29%  { opacity: 1; transform: translate(-50%, -50%) scale(1.0); }   /* settle */
  71%  { opacity: 1; transform: translate(-50%, -50%) scale(1.0); }   /* hold 800ms */
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.0); }   /* fade out 400ms */
}

/* (2b) Particle burst on eat (Visual C). DOM circles in the snake's color
   spawned by fx.js#particleBurst, anchored to #particles-layer (created
   lazily inside #board-wrap, sibling of #popups-layer). z-index 11 puts
   them ABOVE the cell letter / food border (z-index ~5) but BELOW the
   "+10" score popup layer (z-index 12). Each particle uses CSS custom
   properties --bx / --by to encode its outward target offset, then a
   single keyframe handles fly-out + fade. Self-cleans on animationend. */
#particles-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 11;
}
.burst-particle {
  position: absolute;
  pointer-events: none;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  will-change: transform, opacity;
  box-shadow: 0 0 6px currentColor;
  animation: burst-particle-anim 700ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes burst-particle-anim {
  0%   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  100% {
    opacity: 0;
    transform: translate(calc(-50% + var(--bx, 0px)),
                         calc(-50% + var(--by, 0px))) scale(0.4);
  }
}

/* (3) Wrong-letter head shake. Tiny ~3-4px wobble — a "no, try again"
   nudge, not punishment. Only the head cell shakes; the body sits still. */
@keyframes head-shake-anim {
  0%   { transform: translateX(0); }
  20%  { transform: translateX(-3px); }
  40%  { transform: translateX(3px); }
  60%  { transform: translateX(-2px); }
  80%  { transform: translateX(2px); }
  100% { transform: translateX(0); }
}
.cell.head-shake {
  animation: head-shake-anim 150ms ease-in-out;
  z-index: 3;
}

/* (4) Snake-body grow-bounce on eat. Applied to the new tail segment
   for a single tick of "I just got bigger" overshoot. */
@keyframes grow-bounce-anim {
  0%   { transform: scale(0); }
  55%  { transform: scale(1.20); }
  80%  { transform: scale(0.95); }
  100% { transform: scale(1); }
}
.cell.snake.grow-bounce {
  animation: grow-bounce-anim 250ms cubic-bezier(0.34, 1.56, 0.64, 1);
  transform-origin: center;
}

/* === Accessibility: prefers-reduced-motion =================================
   Skip every juice animation we just added (and the existing shake / pulses)
   and jump straight to the end state. JS-driven effects (sound layers,
   particles, slow-mo) check this preference live in fx.reducedMotion(). */
@media (prefers-reduced-motion: reduce) {
  .cell.cell-land,
  .cell.head-shake,
  .cell.snake.grow-bounce,
  .cell.eat-pop,
  .cell.head-glow,
  .cell.first-pulse,
  .cell.food,
  .board-shake,
  #board-wrap.is-dying,
  #streak-display.bump,
  .score-float,
  .burst-particle {
    animation: none !important;
  }
  /* Score-float: no rise / scale per spec. Static at the cell, fade in
     200ms then fade out 400ms. JS removes the element after ~600ms. */
  .score-float {
    transform: translate(-50%, -50%);
    animation: score-float-rm 600ms ease forwards !important;
  }
  .score-float.perfect-splash {
    transform: translate(-50%, -50%);
    animation: perfect-splash-rm 800ms ease forwards !important;
  }
  @keyframes score-float-rm {
    0%   { opacity: 0; }
    33%  { opacity: 1; }
    100% { opacity: 0; }
  }
  @keyframes perfect-splash-rm {
    0%   { opacity: 0; }
    25%  { opacity: 1; }
    75%  { opacity: 1; }
    100% { opacity: 0; }
  }
  /* Valid-move highlight: keep the visual distinction (the contrast is the
     onboarding signal — that's exactly what reduced-motion users still want)
     but snap rather than ease so there's no animated state change per tick. */
  .cell.is-valid,
  .cell.is-dim {
    transition: none;
  }
  /* Food-vs-snake disambiguation (GH issue #43): keep the inset bright
     border at full opacity so motion-sensitive users still get the non-color
     signal that distinguishes food from a same-color snake body. The outer
     glow ring is dropped (no animation, no static halo), but the inset
     border stays — that's the part that matters for distinguishability. */
  .cell.food {
    box-shadow:
      inset 0 0 0 2px rgba(255, 255, 255, 0.95),
      inset 0 0 4px rgba(255, 255, 255, 0.4) !important;
  }
}

/* Mute indicator + volume slider (sound controls block) */
.sound-controls {
  margin-top: 4px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
#mute-indicator {
  font-size: 11px;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}
#mute-indicator:hover { color: var(--text); }
#mute-indicator.muted { color: #ff6b6b; opacity: 0.8; }

.volume-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 11px;
  color: var(--muted);
}
.volume-label {
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: 10px;
  min-width: 22px;
}
/* Slider matches the panel's other inputs (panel-2 background, cell-border).
   Cross-browser styling needs explicit -webkit- and -moz- pseudo-elements. */
#volume-slider {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 4px;
  background: var(--panel-2);
  border: 1px solid var(--cell-border);
  border-radius: 4px;
  outline: none;
  cursor: pointer;
  padding: 0;
  margin: 0;
}
#volume-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--accent);
  border: 1px solid var(--cell-border);
  cursor: pointer;
}
#volume-slider::-moz-range-thumb {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--accent);
  border: 1px solid var(--cell-border);
  cursor: pointer;
}
#volume-slider:disabled,
#volume-slider.muted {
  opacity: 0.35;
  cursor: not-allowed;
}

/* Personal stats: toggle button (lives inside .sound-controls) */
.btn-stats-toggle {
  margin-top: 6px;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  padding: 5px 8px;
  font-size: 12px;
  cursor: pointer;
  text-align: left;
}
.btn-stats-toggle:hover { filter: brightness(1.15); }
.btn-stats-toggle[aria-expanded="true"] {
  border-color: var(--accent);
  color: var(--accent);
}

/* Personal stats: panel rows */
#personal-stats-panel .ps-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  font-size: 13px;
  padding: 4px 0;
  border-bottom: 1px solid rgba(35, 41, 54, 0.6);
}
#personal-stats-panel .ps-row:last-of-type { border-bottom: none; }
#personal-stats-panel .ps-label { color: var(--muted); }
#personal-stats-panel .ps-value {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--text);
}
/* Higher-specificity selector to override the default green .panel button
   accent — the reset button should read as low-key/destructive, not as a
   primary action. */
#personal-stats-panel .btn-stats-reset {
  margin-top: 10px;
  width: 100%;
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  padding: 6px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
}
#personal-stats-panel .btn-stats-reset:hover {
  color: #ff6b6b;
  border-color: #ff6b6b;
  filter: none;
}

/* --- Switcheroo achievements (PR: feat/switcheroo-achievements) -----------
   8 text-only milestone badges in a list inside the existing stats panel.
   No icon design — emoji + ✓/○ marker carry the unlocked/locked state. */
#personal-stats-panel .sw-achievements {
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid rgba(35, 41, 54, 0.6);
}
#personal-stats-panel .sw-achievements-h3 {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin: 0 0 8px 0;
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: 0.02em;
}
#personal-stats-panel .sw-achievements-counter {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  color: var(--muted);
  font-weight: 500;
}
#personal-stats-panel .sw-achievements-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
#personal-stats-panel .sw-ach-row {
  display: grid;
  grid-template-columns: 16px 1fr;
  align-items: baseline;
  gap: 6px;
  padding: 4px 0;
  font-size: 12.5px;
  line-height: 1.35;
}
#personal-stats-panel .sw-ach-mark {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 700;
  text-align: center;
}
#personal-stats-panel .sw-ach-row.is-unlocked .sw-ach-mark { color: var(--accent); }
#personal-stats-panel .sw-ach-row.is-locked   .sw-ach-mark { color: var(--muted); }
#personal-stats-panel .sw-ach-name { font-weight: 600; }
#personal-stats-panel .sw-ach-row.is-unlocked .sw-ach-name { color: var(--accent); }
#personal-stats-panel .sw-ach-row.is-locked   .sw-ach-name { color: var(--muted); }
#personal-stats-panel .sw-ach-desc {
  grid-column: 2 / 3;
  font-size: 11px;
  color: var(--muted);
  opacity: 0.85;
  margin-top: 1px;
}

/* Achievement unlock toast — fixed top-center overlay, fades in / sits / fades
   out over ACHIEVEMENT_TOAST_MS (2.5s in game.js). pointer-events: none so
   the toast never blocks gameplay; multiple toasts stack via flex column. */
#achievement-toast-layer {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 9999;
  display: flex;
  flex-direction: column;
  gap: 6px;
  pointer-events: none;
}
.achievement-toast {
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--accent);
  border-radius: 8px;
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 600;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.45);
  white-space: nowrap;
  animation: achievement-toast-anim 2500ms ease-out forwards;
}
@keyframes achievement-toast-anim {
  0%   { opacity: 0; transform: translateY(-8px); }
  10%  { opacity: 1; transform: translateY(0); }
  85%  { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-4px); }
}

/* Stats panel */
#stats-panel {
  display: flex;
  width: 100%;
  max-width: 760px;
  background: var(--panel);
  border: 1px solid var(--cell-border);
  border-radius: 10px;
  overflow: hidden;
}

.stats-col {
  flex: 1;
  padding: 14px 18px;
}

.stats-col + .stats-col {
  border-left: 1px solid var(--cell-border);
}

.stats-heading {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 10px;
}

.stat-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 5px 0;
  border-bottom: 1px solid rgba(35, 41, 54, 0.6);
}

.stat-row:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.stat-row:first-of-type {
  padding-top: 0;
}

.stat-label {
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
}

.stat-value {
  font-size: 16px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--text);
}

.stat-accent {
  color: var(--accent);
}

/* === Room snake colors ===================================================== */

.cell.snake[data-pid="room-self"]      { background: var(--player); }
.cell.snake.head[data-pid="room-self"] { background: var(--player-head); }
.cell.snake[data-pid="room-opp"]       { background: var(--opponent); }
.cell.snake.head[data-pid="room-opp"]  { background: var(--opponent-head); }

/* === Room lobby overlay ==================================================== */

/* display:flex in these rules overrides [hidden]'s default display:none */
#room-lobby[hidden], #room-result[hidden] { display: none; }

#room-lobby {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(11, 14, 21, 0.96);
  z-index: 20;
  border-radius: 10px;
}

.lobby-box {
  background: var(--panel);
  border: 1px solid var(--cell-border);
  border-radius: 12px;
  padding: 28px 28px 24px;
  min-width: 270px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.lobby-title {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  color: var(--text);
}

.lobby-btn {
  display: block;
  width: 100%;
  background: var(--accent);
  color: #07131c;
  border: 0;
  border-radius: 8px;
  padding: 10px 16px;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
  transition: filter 120ms;
}
.lobby-btn:hover { filter: brightness(1.12); }

.lobby-btn-cancel {
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--cell-border);
  margin-top: 4px;
}
.lobby-btn-cancel:hover { color: var(--text); filter: none; border-color: #3a4050; }

.lobby-btn-primary {
  background: linear-gradient(135deg, #5cc8ff 0%, #b07cff 100%);
  font-size: 16px;
  padding: 12px 16px;
  box-shadow: 0 6px 18px rgba(92,200,255,0.30);
}

.lobby-search-spinner {
  width: 32px;
  height: 32px;
  border: 3px solid var(--panel-2);
  border-top-color: var(--accent);
  border-radius: 50%;
  margin: 4px auto 8px;
  animation: lobby-spin 0.9s linear infinite;
}
@keyframes lobby-spin { to { transform: rotate(360deg); } }

#lobby-searching {
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: stretch;
}
#lobby-searching[hidden] { display: none; }

.lobby-or {
  font-size: 12px;
  color: var(--muted);
  margin: 2px 0;
}

.lobby-join-row {
  display: flex;
  gap: 8px;
  align-items: stretch;
}

.lobby-code-input {
  flex: 1;
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--cell-border);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 18px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-align: center;
  text-transform: uppercase;
}
.lobby-code-input::placeholder { letter-spacing: 0.1em; font-size: 13px; font-weight: 400; }
.lobby-code-input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }

.lobby-join-row .lobby-btn {
  flex: 0 0 auto;
  width: auto;
  padding: 8px 18px;
}

.lobby-err {
  color: var(--food);
  font-size: 12px;
  margin: 0;
  padding: 6px 8px;
  background: rgba(255,107,107,0.12);
  border-radius: 6px;
  border: 1px solid rgba(255,107,107,0.25);
}

.lobby-share-label {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
}

.room-code-big {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 42px;
  font-weight: 800;
  letter-spacing: 0.22em;
  color: var(--accent);
  padding: 6px 0;
  user-select: all;
}

.lobby-wait-msg {
  margin: 0;
  font-size: 13px;
  color: var(--muted);
  animation: lobby-pulse 1.8s ease-in-out infinite;
}
@keyframes lobby-pulse {
  0%,100% { opacity: 1; }
  50%      { opacity: 0.4; }
}

/* === Room result overlay (YOU WIN / YOU LOSE) ============================== */

#room-result {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(11, 14, 21, 0.93);
  z-index: 20;
  border-radius: 10px;
}

.result-box {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 36px 40px;
}

.result-title {
  font-size: 52px;
  font-weight: 900;
  letter-spacing: 0.04em;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  line-height: 1;
}
.result-win  { color: #6dd6a3; text-shadow: 0 0 36px rgba(109,214,163,0.55); }
.result-lose { color: var(--food); text-shadow: 0 0 36px rgba(255,107,107,0.55); }

.result-detail {
  font-size: 14px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  min-height: 20px;
}

/* =====================================================================
   retrogames1337 redesign overrides — appended 2026-04-25
   Phosphor green palette + hacker-cool typography. Last-rule-wins via
   :root re-declaration, plus targeted overrides for hardcoded colors.
   Game JS is untouched; all existing IDs and classes are preserved.
   ===================================================================== */

:root {
  /* palette — phosphor green on near-black, amber for next-target */
  --bg:           #0A0F0A;
  --panel:        #0F1612;
  --panel-2:      #14201A;
  --text:         #E8F2E8;
  --muted:        #9CAB9C;
  --accent:       #9BF24C;     /* was cyan, now phosphor green */
  --grid-bg:      #050805;
  --cell-bg:      #0F1612;
  --cell-bg-2:    #14201A;
  --cell-border:  #1F2A1F;
  --food:         #F2D74C;     /* was red, now amber word-pellet */
  --food-glow:    rgba(242, 215, 76, 0.45);
  --hint:         rgba(155, 242, 76, 0.18);
  --miss:         rgba(242, 76, 76, 0.30);
  --player:       #5BC93A;     /* was cyan, now Noodle body */
  --player-head:  #7DDC42;     /* was light cyan, now Noodle head */
  --opponent:     #F24CC9;     /* magenta, distinct from Noodle */
  --opponent-head:#FFA8E8;
  --bot-1:        #F2D74C;
  --bot-2:        #B07CFF;
  --bot-3:        #F2A4D8;
  --bot-4:        #5CD4F2;
  --bot-5:        #F29A4C;
  --font-mono:    ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
}

html, body {
  font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif;
}

/* portal breadcrumb at top of page */
.rg-portal-nav {
  max-width: 1200px;
  margin: 0 auto;
  padding: 16px 24px 0;
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--muted);
}
.rg-portal-back {
  color: var(--accent);
  text-decoration: none;
  transition: opacity 120ms ease;
}
.rg-portal-back:hover { opacity: 0.75; }
.rg-portal-arrow { display: inline-block; margin-right: 4px; }
.rg-portal-bread { color: var(--muted); }

/* H1 treatment — Sidewinder rebrand. The wordmark is now the new
   coiled-snake logo PNG (sidewinder-logo.png). The blinking .rg-cursor
   block stays beside it for character; the BETA badge keeps its existing
   styling. The legacy Type<accent>Snake</accent> markup is gone but
   .rg-accent / .rg-cursor styles are retained for the cursor + any
   downstream uses (e.g. typequest.html which still uses Type<accent>Quest). */
header { padding: 14px 24px 0; }
header h1 {
  font-family: var(--font-mono);
  font-size: 26px;
  font-weight: 500;
  letter-spacing: -0.01em;
  display: inline-flex;
  align-items: center;
  margin: 4px 0 0;
}
.sw-h1 {
  /* Sidewinder rebrand H1 — wordmark image + cursor + BETA badge in a row. */
  gap: 10px;
}
.sw-wordmark {
  /* The source PNG is square (720x720) with the snake + wordmark centered
     and ~25% vertical padding around it. We size the box generously so the
     actual visible artwork has presence — at 56px the snake was only ~35px
     tall and lost to the BETA badge. height auto preserves ratio. */
  display: block;
  height: 140px;
  width: auto;
  max-width: 560px;
  /* Subtle drop shadow gives the chrome wordmark a little depth on the
     dark background — purely cosmetic, can be tuned. */
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
}
@media (max-width: 600px) {
  .sw-wordmark { height: 96px; max-width: 90vw; }
}
.hero-pitch {
  /* The new positioning line: "Sidewinder — type to steer. Outpace yourself."
     Sits between the wordmark and the keyboard tagline. */
  margin: 8px 0 0;
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: 14px;
  letter-spacing: 0.2px;
  opacity: 0.95;
}
.rg-accent { color: var(--accent); }
.rg-cursor {
  display: inline-block;
  width: 9px;
  height: 18px;
  background: var(--accent);
  margin-left: 4px;
  animation: rg-cursor-blink 1.05s steps(2, jump-none) infinite;
}
@keyframes rg-cursor-blink {
  50% { opacity: 0; }
}

/* HUD panels — slight monospace flavor on the headings */
.panel h2 {
  font-family: var(--font-mono);
  letter-spacing: 0.8px;
  color: var(--accent);
  opacity: 0.85;
}

/* Buttons — monospace, no bold paint */
.panel button {
  font-family: var(--font-mono);
  letter-spacing: 0.3px;
  font-weight: 500;
}

/* Difficulty: medium = amber (was orange), hard stays red as danger */
.diff-btn { font-family: var(--font-mono); }
.diff-btn[data-diff="easy"].active   { background: rgba(155, 242, 76, 0.15); border-color: var(--accent); color: var(--accent); }
.diff-btn[data-diff="medium"].active { background: rgba(242, 215, 76, 0.15); border-color: var(--food);   color: var(--food); }
.diff-btn[data-diff="hard"].active   { background: rgba(242, 76, 76, 0.15);  border-color: #F24C4C;       color: #F24C4C; }

/* Leaderboard tag colors — amber for multi, green for single */
.lb-tag-multi  { background: rgba(242, 215, 76, 0.20); color: #F2E899; }
.lb-tag-single { background: rgba(155, 242, 76, 0.18); color: #C8F299; }
.lb-score      { color: var(--accent); }

/* Legend food block — dark amber text on amber bg */
.legend.food { color: #4A3A02; }

/* Score row "self" outline now uses Noodle head color */
.score-row.self { outline-color: var(--player-head); }

/* You-tag glow uses player color, which is now green */
#you-tag {
  background: var(--player);
  color: var(--bg);
  box-shadow: 0 4px 14px rgba(91, 201, 58, 0.55);
}
#you-tag::after { border-top-color: var(--player); }

/* === Sandbox / Practice difficulty button ================================== */

/* When the 4th button is present, the diff grid switches to 4 columns. The
   sandbox button gets its own muted-but-friendly visual treatment so it reads
   as "practice / training wheels" rather than another competitive tier. */
.diff-btns.diff-btns-4 {
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
}
.diff-btn-sandbox {
  /* dashed border hints "not a real difficulty" */
  border-style: dashed !important;
}
.diff-btn[data-diff="sandbox"].active {
  background: rgba(155, 242, 76, 0.08);
  border-color: var(--accent);
  color: var(--accent);
  border-style: solid !important;
}

/* === Coach overlay (first-visit 3-step tutorial) =========================== */

/* The coach overlay is intentionally non-blocking — pointer-events:none on
   the wrapper means clicks/keystrokes pass through to the board. The card
   itself re-enables events so the Skip link is still clickable. */
#coach-overlay[hidden] { display: none; }
#coach-overlay {
  position: absolute;
  inset: 0;
  z-index: 18;
  pointer-events: none;
  border-radius: 10px;
}
.coach-card {
  position: absolute;
  bottom: 14px;
  right: 14px;
  max-width: 260px;
  background: rgba(10, 15, 10, 0.92);
  border: 1px solid var(--accent);
  border-radius: 8px;
  padding: 12px 14px 10px;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.55), 0 0 18px rgba(155, 242, 76, 0.18);
  pointer-events: auto;
  font-family: var(--font-mono);
  color: var(--text);
  animation: coach-card-in 220ms ease-out;
}
@keyframes coach-card-in {
  0%   { opacity: 0; transform: translateY(8px); }
  100% { opacity: 1; transform: translateY(0); }
}
.coach-step-indicator {
  display: flex;
  gap: 5px;
  margin-bottom: 8px;
}
.coach-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--cell-border);
  transition: background 160ms;
}
.coach-dot.active { background: var(--accent); }
.coach-text {
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--text);
  margin-bottom: 8px;
}
.coach-skip {
  background: none;
  border: 0;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  padding: 2px 0;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: rgba(156, 171, 156, 0.4);
}
.coach-skip:hover { color: var(--accent); text-decoration-color: var(--accent); }

/* Pulse helpers — applied to grid cells while a coach step is active. The
   step-1 pulse puts a soft ring on the player head + neighbor letters so
   the user knows what to type. The step-2 pulse highlights the nearest food. */
.cell.coach-pulse-letter {
  box-shadow: 0 0 0 0 rgba(155, 242, 76, 0.55);
  animation: coach-pulse-letter 1.4s ease-out infinite;
  z-index: 2;
}
@keyframes coach-pulse-letter {
  0%   { box-shadow: 0 0 0 0 rgba(155, 242, 76, 0.55); }
  70%  { box-shadow: 0 0 0 10px rgba(155, 242, 76, 0); }
  100% { box-shadow: 0 0 0 0 rgba(155, 242, 76, 0); }
}
.cell.coach-pulse-food {
  box-shadow: 0 0 0 0 rgba(242, 215, 76, 0.7);
  animation: coach-pulse-food 1.1s ease-out infinite;
  z-index: 2;
}
@keyframes coach-pulse-food {
  0%   { box-shadow: 0 0 0 0 rgba(242, 215, 76, 0.7); transform: scale(1); }
  60%  { box-shadow: 0 0 0 14px rgba(242, 215, 76, 0); transform: scale(1.06); }
  100% { box-shadow: 0 0 0 0 rgba(242, 215, 76, 0); transform: scale(1); }
}

/* === Multiplayer explainer overlay ========================================= */

#mp-explainer[hidden] { display: none; }
#mp-explainer {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(11, 14, 21, 0.96);
  z-index: 21; /* above the lobby */
  border-radius: 10px;
  padding: 16px;
  overflow: auto;
}
.mp-explainer-box {
  background: var(--panel);
  border: 1px solid var(--cell-border);
  border-radius: 12px;
  padding: 24px 24px 20px;
  max-width: 560px;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 16px;
  text-align: center;
}
.mp-explainer-title {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 16px;
  font-weight: 600;
  color: var(--accent);
  letter-spacing: 0.02em;
}
.mp-explainer-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  text-align: left;
}
@media (max-width: 540px) {
  .mp-explainer-grid { grid-template-columns: 1fr; }
}
.mp-explainer-card {
  background: var(--panel-2);
  border: 1px solid var(--cell-border);
  border-radius: 8px;
  padding: 12px 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.mp-explainer-icon {
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: 700;
  color: var(--accent);
  line-height: 1;
}
.mp-explainer-name {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: 0.02em;
}
.mp-explainer-desc {
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted);
}

/* === Switcheroo 101 tutorial overlay (PR #46) =============================
   First-visit explainer for the heat / dedup-repair system shipped in
   PR #45. Reuses the same dark backdrop / centered card pattern as the
   #mp-explainer overlay so it reads as part of the same family. The heat
   demo (.sw-heat-demo) cycles through the SAME yellow/orange/red colors
   as the live .heat-3/.heat-2/.heat-1 cells (kept in sync by reusing the
   exact rgba values from the .cell.heat-* rules above), so the player
   learns to recognise the on-board treatment. */

#switcheroo-tutorial[hidden] { display: none; }
#switcheroo-tutorial {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(11, 14, 21, 0.96);
  z-index: 22; /* above lobby + mp-explainer; same family as result modals */
  border-radius: 10px;
  padding: 16px;
  overflow: auto;
}
.sw-tutorial-box {
  position: relative;
  background: var(--panel);
  border: 1px solid var(--accent);
  border-radius: 12px;
  padding: 22px 22px 18px;
  max-width: 460px;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 14px;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.55), 0 0 18px rgba(92, 200, 255, 0.18);
  font-family: var(--font-mono);
  color: var(--text);
  animation: sw-tutorial-in 220ms ease-out;
}
@keyframes sw-tutorial-in {
  0%   { opacity: 0; transform: translateY(8px); }
  100% { opacity: 1; transform: translateY(0); }
}
.sw-tutorial-close {
  position: absolute;
  top: 6px;
  right: 8px;
  background: none;
  border: 0;
  color: var(--muted);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
}
.sw-tutorial-close:hover { color: var(--accent); }
.sw-tutorial-title {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  color: var(--accent);
  letter-spacing: 0.02em;
  text-align: center;
}
.sw-tutorial-stepdots {
  display: flex;
  justify-content: center;
  gap: 6px;
}
.sw-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--cell-border);
  transition: background 160ms;
}
.sw-dot.active { background: var(--accent); }

.sw-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  align-items: center;
  min-height: 140px;
}
.sw-panel[hidden] { display: none; }
.sw-body {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text);
  text-align: center;
}

/* Panel 1 — letter shuffle visual */
.sw-visual {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
.sw-visual-shuffle .sw-mini-cell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border: 1px solid var(--cell-border);
  border-radius: 4px;
  background: var(--panel-2);
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 16px;
  color: var(--text);
}
.sw-mini-snake {
  background: var(--player) !important;
  color: #0a0d12 !important;
}
.sw-mini-arrow {
  border: 0 !important;
  background: transparent !important;
  color: var(--muted) !important;
  width: 22px !important;
  font-size: 18px !important;
}
.sw-mini-changed {
  animation: sw-mini-flip 1.6s ease-in-out infinite;
}
@keyframes sw-mini-flip {
  0%, 60% { background: rgba(247, 209, 96, 0.28); color: #f7d160; }
  100%    { background: var(--panel-2); color: var(--text); }
}

/* Panel 2 — heat warning. Cycles through yellow → orange → red → reset
   on a 3.6s loop using the SAME rgba values as the live .cell.heat-*
   classes so the demo color-matches what the player will see in-game. */
.sw-visual-heat {
  height: 64px;
}
.sw-heat-cell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 24px;
  background: var(--panel-2);
  color: var(--text);
}
.sw-heat-demo {
  animation: sw-heat-cycle 3.6s ease-in-out infinite;
}
@keyframes sw-heat-cycle {
  0%   { background: var(--panel-2);            color: var(--text); }
  10%  { background: rgba(247, 209, 96, 0.28);  color: #f7d160; }   /* heat-3 yellow */
  40%  { background: rgba(255, 180, 84, 0.42);  color: #ffb454; }   /* heat-2 orange */
  70%  { background: rgba(255, 107, 107, 0.45); color: #ff7ab6; box-shadow: 0 0 0 4px rgba(255, 107, 107, 0.35); } /* heat-1 red */
  90%  { background: var(--panel-2);            color: var(--text); box-shadow: none; }
  100% { background: var(--panel-2);            color: var(--text); box-shadow: none; }
}
@media (prefers-reduced-motion: reduce) {
  /* Mirrors the .cell.heat-1 reduced-motion rule above — keep contrast,
     drop the kinetic component. */
  .sw-heat-demo { animation: none; background: rgba(255, 107, 107, 0.45); color: #ff7ab6; }
  .sw-mini-changed { animation: none; }
}

/* Panel 3 — score table */
.sw-score-list {
  list-style: none;
  margin: 0;
  padding: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.sw-score-list li {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  background: var(--panel-2);
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  font-size: 12.5px;
}
.sw-score-swatch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 4px;
  flex-shrink: 0;
  font-size: 11px;
}
.sw-swatch-yellow { background: rgba(247, 209, 96, 0.28);  border: 1px solid #f7d160; }
.sw-swatch-orange { background: rgba(255, 180, 84, 0.42);  border: 1px solid #ffb454; }
.sw-swatch-red    { background: rgba(255, 107, 107, 0.45); border: 1px solid #ff7ab6; }
.sw-swatch-snipe  { background: transparent;               border: 0; color: #f7d160; font-weight: 700; }
.sw-score-label {
  flex: 1;
  color: var(--text);
}
.sw-score-pts {
  font-family: var(--font-mono);
  font-weight: 700;
  color: var(--accent);
  min-width: 44px;
  text-align: right;
}

/* Panel 4 — done */
.sw-visual-done { font-size: 32px; color: var(--accent); }
.sw-done-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border: 2px solid var(--accent);
  border-radius: 50%;
}

.sw-tutorial-nav {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
}
.sw-tutorial-nav-spacer { flex: 1; }
.sw-tutorial-skip {
  background: none;
  border: 0;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  padding: 6px 4px;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: rgba(156, 171, 156, 0.4);
}
.sw-tutorial-skip:hover { color: var(--accent); text-decoration-color: var(--accent); }
.sw-tutorial-btn {
  background: var(--panel-2);
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  padding: 7px 14px;
  cursor: pointer;
  transition: background 140ms, border-color 140ms;
}
.sw-tutorial-btn:hover:not(:disabled) {
  background: var(--panel);
  border-color: var(--accent);
}
.sw-tutorial-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.sw-tutorial-btn-next {
  background: var(--accent);
  color: #0a0d12;
  border-color: var(--accent);
  font-weight: 700;
}
.sw-tutorial-btn-next:hover:not(:disabled) {
  background: var(--player-head);
  border-color: var(--player-head);
}

/* Sidebar "?" re-open button — sits in the .sound-controls block next
   to the Stats and Feedback buttons. Visual weight matches them. */
.btn-tutorial-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: transparent;
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  padding: 5px 9px;
  cursor: pointer;
  transition: color 140ms, border-color 140ms;
}
.btn-tutorial-toggle:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* === BETA badge ============================================================
   Small uppercase amber pill that sits next to the Sidewinder wordmark in the
   H1. Outline style so it reads as an intentional label rather than a banner.
   Linkable to the GitHub issues tracker; CSS-only tooltip via [data-tip].
   ========================================================================= */
.rg-beta-badge {
  display: inline-block;
  margin-left: 10px;
  padding: 2px 7px;
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.12em;
  line-height: 1.4;
  color: var(--food);
  background: rgba(242, 215, 76, 0.08);
  border: 1px solid var(--food);
  border-radius: 999px;
  text-decoration: none;
  vertical-align: middle;
  position: relative;
  transition: background-color 120ms ease, color 120ms ease;
}
.rg-beta-badge:hover,
.rg-beta-badge:focus-visible {
  background: var(--food);
  color: #2A2202;
  outline: none;
}
.rg-beta-badge[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  top: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%) translateY(-2px);
  padding: 6px 10px;
  font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: normal;
  white-space: nowrap;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease, transform 120ms ease;
  z-index: 20;
}
.rg-beta-badge[data-tip]:hover::after,
.rg-beta-badge[data-tip]:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
@media (max-width: 480px) {
  .rg-beta-badge {
    margin-left: 8px;
    font-size: 9px;
    padding: 1px 6px;
  }
  /* On narrow screens the tooltip would clip; hide it and rely on the link */
  .rg-beta-badge[data-tip]::after { display: none; }
}

/* === Share scorecard ======================================================
   The "Share my run" button appears in the room-result overlay (multiplayer)
   and as a one-off row above the status line on single-player death. Distinct
   gradient so it reads as the primary post-game CTA. */

.lobby-btn-share {
  background: linear-gradient(135deg, #7DDC42 0%, #5cc8ff 100%);
  color: #07131c;
  font-weight: 800;
  letter-spacing: 0.02em;
  box-shadow: 0 6px 18px rgba(125,220,66,0.30);
}
.lobby-btn-share:hover { filter: brightness(1.10); }

#sp-share-row {
  display: flex;
  justify-content: center;
  margin: 8px auto 4px;
  max-width: 280px;
}
#sp-share-row[hidden] { display: none; }

#toast {
  position: fixed;
  left: 50%;
  bottom: 32px;
  transform: translateX(-50%);
  background: var(--panel-2, #131a2a);
  color: var(--text, #e8ffd6);
  border: 1px solid var(--accent, #7DDC42);
  border-radius: 8px;
  padding: 10px 16px;
  font-size: 13px;
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  box-shadow: 0 8px 24px rgba(0,0,0,0.45);
  z-index: 9999;
  pointer-events: none;
  opacity: 0;
  transition: opacity 180ms;
}
#toast[data-visible="1"] { opacity: 1; }
#toast[hidden] { display: none; }

/* =====================================================================
   PERFECT GAME overlay (Bug 2)
   Reuses the #room-result scaffold but swaps in gold/amber styling and
   a subtle pulse so it reads as a celebration rather than a death screen.
   ===================================================================== */

#room-result.result-perfect {
  background: radial-gradient(ellipse at center,
    rgba(40, 30, 8, 0.96) 0%,
    rgba(11, 14, 21, 0.97) 75%);
}

.result-perfect-title {
  font-size: 52px;
  font-weight: 900;
  letter-spacing: 0.05em;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  line-height: 1;
  color: #ffd24a;
  text-shadow:
    0 0 24px rgba(255, 210, 74, 0.7),
    0 0 60px rgba(255, 180, 50, 0.4);
  animation: perfect-pulse 1.6s ease-in-out infinite;
}

@keyframes perfect-pulse {
  0%, 100% { transform: scale(1);    opacity: 1; }
  50%      { transform: scale(1.04); opacity: 0.92; }
}

@media (prefers-reduced-motion: reduce) {
  .result-perfect-title { animation: none; }
}

/* =====================================================================
   PERFECT confetti burst (visual polish)
   Viewport-fixed layer that spawns ~180 small gold rectangles falling
   from the top, behind the gold #room-result overlay (which has its own
   stacking context inside #board-wrap). The container is pointer-events:
   none so it never intercepts clicks on the overlay buttons underneath.
   Reduced-motion fork lower in the file: drops to ~30 pieces, faster
   fall, no rotation.
   ===================================================================== */
.perfect-confetti-layer {
  position: fixed;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  /* PERFECT overlay sits at z-index 20 inside #board-wrap (a positioned
     ancestor), so it's actually trapped in that local stacking context.
     A fixed layer at z-index 50 here renders ABOVE the board content but
     the centered gold card still draws "on top" visually because we set
     opacity to ease into the card's brightness. We deliberately stay
     below the modal layers (toast / share-card live well above 50). */
  z-index: 50;
}
.perfect-confetti-piece {
  position: absolute;
  top: -20px;
  width: var(--cf-w, 6px);
  height: var(--cf-h, 10px);
  background: var(--cf-color, #ffd24a);
  border-radius: 1px;
  will-change: transform, opacity;
  animation: perfect-confetti-fall var(--cf-dur, 2800ms) cubic-bezier(0.25, 0.45, 0.5, 1) forwards;
  animation-delay: var(--cf-delay, 0ms);
  opacity: 0;
}
@keyframes perfect-confetti-fall {
  0%   { transform: translate3d(0, -20px, 0) rotate(0deg);                   opacity: 0; }
  8%   {                                                                      opacity: 1; }
  80%  {                                                                      opacity: 1; }
  100% { transform: translate3d(var(--cf-drift, 0px), 100vh, 0) rotate(var(--cf-rot, 540deg)); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .perfect-confetti-piece {
    animation: perfect-confetti-fall-rm var(--cf-dur, 1000ms) ease-out forwards !important;
  }
  @keyframes perfect-confetti-fall-rm {
    0%   { transform: translate3d(0, -20px, 0); opacity: 0; }
    20%  {                                       opacity: 1; }
    100% { transform: translate3d(0, 100vh, 0);  opacity: 0; }
  }
}

/* =====================================================================
   Multiplayer VS ribbon (Bug 3)
   Replaces the floating opponent name-tag that used to sit on top of a
   board cell. A short horizontal strip above the grid carries each
   player's name + a color swatch. Hidden by default; game.js toggles
   .has-vs on #board-wrap when an MP match starts and clears it on end.
   ===================================================================== */

#vs-ribbon {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 6px 10px;
  margin-bottom: 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text);
  background: rgba(15, 22, 18, 0.85);
  border: 1px solid rgba(125, 220, 66, 0.18);
  border-radius: 6px;
  min-height: 30px;
  box-sizing: border-box;
}

#board-wrap.has-vs #vs-ribbon { display: flex; }

.vs-player {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  white-space: nowrap;
  max-width: 40%;
  overflow: hidden;
  text-overflow: ellipsis;
}

.vs-player .vs-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: 0 0 auto;
}

.vs-player.vs-self .vs-name { color: var(--player); }
.vs-player.vs-other .vs-name { color: var(--opponent); }

.vs-divider {
  font-weight: 800;
  color: var(--muted);
  letter-spacing: 0.08em;
}

@media (max-width: 480px) {
  #vs-ribbon { font-size: 11px; gap: 8px; padding: 4px 6px; }
  .vs-player .vs-swatch { width: 8px; height: 8px; }
}

/* === Feedback / bug-report modal =========================================== */

/* Reuses .lobby-box for the inner panel (same border/padding as the room
   lobby). The outer overlay is fixed-position over the whole viewport so it
   works from any screen — lobby, gameplay, post-game. */
#feedback-modal[hidden] { display: none; }
#feedback-modal {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(11, 14, 21, 0.86);
  z-index: 60;
  padding: 16px;
}
.feedback-box {
  position: relative;
  min-width: 320px;
  max-width: 440px;
  width: 100%;
  text-align: left;
  gap: 10px;
}
.feedback-box .lobby-title { text-align: center; }
.feedback-box textarea {
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  padding: 10px;
  font-family: inherit;
  font-size: 14px;
  resize: vertical;
  min-height: 96px;
}
.feedback-box textarea:focus {
  outline: none;
  border-color: var(--accent);
}
.feedback-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 12px;
  color: var(--muted);
}
.feedback-field input {
  background: var(--panel-2);
  color: var(--text);
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  padding: 8px 10px;
  font-family: inherit;
  font-size: 14px;
}
.feedback-field input:focus {
  outline: none;
  border-color: var(--accent);
}
.feedback-help {
  color: var(--muted);
  font-style: italic;
  font-weight: 400;
}
.feedback-status {
  margin: 4px 0 0;
  text-align: center;
  font-size: 13px;
  min-height: 1em;
}
.feedback-status.is-ok    { color: var(--accent); }
.feedback-status.is-error { color: #ff7a90; }
.feedback-links {
  display: flex;
  justify-content: center;
  gap: 16px;
  font-size: 12px;
  margin-top: 4px;
}
.feedback-links a {
  color: var(--muted);
  text-decoration: none;
}
.feedback-links a:hover { color: var(--text); text-decoration: underline; }
#feedback-close {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 28px;
  height: 28px;
  padding: 0;
  font-size: 18px;
  line-height: 1;
  margin: 0;
}

/* Lobby-row trigger button. Sits in the .sound-controls block next to
   #btn-stats, so it inherits the same compact "secondary" look. */
.btn-feedback-toggle {
  background: var(--panel-2);
  color: var(--muted);
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  padding: 6px 10px;
  font-size: 12px;
  cursor: pointer;
  width: 100%;
  margin-top: 4px;
}
.btn-feedback-toggle:hover { color: var(--text); border-color: var(--accent); }
/* ----------------------------------------------- daily challenge panel + badge
   Daily Challenge: solo, locked Hard, one deterministic board per UTC day.
   Visually distinct from the main 24h leaderboard via a soft gold accent
   (matches the PERFECT bonus colour) so the panel reads as "the prize event"
   rather than another ordinary panel. */
.daily-panel {
  border-color: #4d3a13;
  background: linear-gradient(180deg, var(--panel) 0%, #1d180e 100%);
}
.daily-panel h2 { color: #f2d74c; }
/* `.panel button` (PR #21 sidebar styling) wins specificity over `.lobby-btn`,
   so the Daily Play button defaults to the plain accent green. Override here
   so the primary CTA shows the same gradient + shadow as the Quick Match /
   Create Room buttons in the lobby overlay. */
.daily-panel #btn-daily-play.lobby-btn-primary {
  background: linear-gradient(135deg, #5cc8ff 0%, #b07cff 100%);
  font-size: 15px;
  padding: 10px 14px;
  box-shadow: 0 4px 14px rgba(92,200,255,0.25);
}
.daily-panel #btn-daily-play {
  margin-top: 4px;
}
.daily-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 8px;
}
.daily-date { font-variant-numeric: tabular-nums; font-weight: 600; color: #f2d74c; }
.daily-resets { font-variant-numeric: tabular-nums; }
.daily-status {
  margin-top: 8px;
  font-size: 13px;
  color: var(--text);
  background: var(--panel-2);
  padding: 6px 8px;
  border-radius: 6px;
  border: 1px solid var(--cell-border);
}
.daily-status.played { color: #b9e6a3; }
.daily-status.practice { color: #ffb454; }
.daily-hint {
  margin: 8px 0 4px;
  font-size: 11px;
  color: var(--muted);
  font-style: italic;
}

/* Daily streak badge — small pill rendered in the Daily lobby, below the
   meta row and above the Play button. Two states:
     .zero   → muted, italic, "Play today to start a 🔥 streak"
     .active → warm orange-red gradient with a 🔥 prefix, count emphasised
   At 5+ days .hot enables a subtle pulse on the flame. Pure presentational
   — all the count math lives in game.js#bumpDailyStreak. */
.daily-streak {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  margin: 0 0 8px;
  padding: 4px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  border: 1px solid transparent;
  font-variant-numeric: tabular-nums;
}
.daily-streak.zero {
  color: var(--muted);
  background: var(--panel-2);
  border-color: var(--cell-border);
  font-style: italic;
  font-weight: 500;
}
.daily-streak.active {
  color: #ffd9b0;
  background: linear-gradient(135deg, rgba(255,107,107,0.18) 0%, rgba(255,180,84,0.18) 100%);
  border-color: rgba(255,107,107,0.45);
  text-shadow: 0 0 8px rgba(255,107,107,0.25);
}
/* 5+ day streak earns a soft pulsing flame. Uses the same animation
   timing-function as the food pulse so both UI moments feel consistent. */
@keyframes daily-streak-flame {
  0%, 100% { text-shadow: 0 0 6px rgba(255,107,107,0.45); }
  50%      { text-shadow: 0 0 14px rgba(255,180,84,0.7); }
}
.daily-streak.hot {
  animation: daily-streak-flame 1.6s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .daily-streak.hot { animation: none; }
}
.daily-lb {
  list-style: none;
  padding: 0;
  margin: 8px 0 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 12px;
}
.daily-lb .lb-row {
  display: grid;
  grid-template-columns: 22px 1fr auto;
  gap: 6px;
  align-items: center;
  padding: 2px 4px;
  border-radius: 4px;
}
.daily-lb .lb-row.me {
  background: rgba(242, 215, 76, 0.1);
  border: 1px solid rgba(242, 215, 76, 0.3);
}
.daily-lb .lb-rank { color: var(--muted); font-variant-numeric: tabular-nums; }
.daily-lb .lb-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.daily-lb .lb-score { font-variant-numeric: tabular-nums; color: #f2d74c; font-weight: 600; }
.daily-lb-expand {
  margin-top: 8px;
  background: transparent;
  color: #f2d74c;
  border: 1px solid #4d3a13;
}
.daily-lb-expand:hover { background: rgba(242, 215, 76, 0.08); }

/* Daily badge over the board — small floating chip so the player can always
   see at a glance whether the run is the Daily (and counts) or freeplay. */
#daily-badge {
  position: absolute;
  top: -28px;
  right: 0;
  padding: 4px 10px;
  background: rgba(242, 215, 76, 0.15);
  color: #f2d74c;
  border: 1px solid #4d3a13;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  pointer-events: none;
  z-index: 5;
}
@media (max-width: 700px) {
  #daily-badge { position: static; display: inline-block; margin-bottom: 6px; }
}

/* ----------------------------------------------- replays (lite) modal + button
   "Watch the #1" — a play-back of a saved daily run. The modal mirrors the
   #room-lobby / #mp-explainer pattern (full-screen dim, centered card) but
   sits at a higher z-index so it stacks above any open lobby. */

/* ▶ button injected into each daily-leaderboard row that has a stored replay.
   Positioned via a 4th implicit grid-template-columns slot on .daily-lb .lb-row
   — the existing rule sets `22px 1fr auto`; appending the btn naturally falls
   into a new column. We override the parent grid to add the explicit slot. */
.daily-lb .lb-row { grid-template-columns: 22px 1fr auto auto; }
.daily-lb .replay-btn {
  background: transparent;
  border: 1px solid rgba(155, 242, 76, 0.35);
  border-radius: 4px;
  color: var(--accent);
  font-size: 11px;
  line-height: 1;
  padding: 2px 6px;
  margin-left: 4px;
  cursor: pointer;
  font-family: var(--font-mono);
  transition: background-color 120ms, border-color 120ms;
}
.daily-lb .replay-btn:hover {
  background: rgba(155, 242, 76, 0.15);
  border-color: var(--accent);
}

#replay-modal[hidden] { display: none; }
#replay-modal {
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(5, 8, 5, 0.85);
  z-index: 100; /* above all in-board overlays (lobby/result are 20-21) */
  padding: 16px;
  overflow: auto;
}
.replay-box {
  background: var(--panel);
  border: 1px solid var(--accent);
  border-radius: 12px;
  padding: 14px 16px 16px;
  width: 100%;
  max-width: 440px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55), 0 0 24px rgba(155, 242, 76, 0.1);
}
.replay-topbar {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  justify-content: space-between;
}
.replay-header {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text);
  line-height: 1.35;
  flex: 1;
}
.replay-header .flag { margin-right: 4px; }
.replay-header .replay-perfect {
  color: #ffd24a;
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.04em;
  margin-left: 4px;
}
.replay-header .replay-final { color: var(--accent); }
.replay-close {
  background: transparent;
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  padding: 2px 8px;
  cursor: pointer;
  font-family: var(--font-mono);
  flex: 0 0 auto;
}
.replay-close:hover { color: var(--food); border-color: var(--food); }
.replay-board-wrap {
  display: flex;
  justify-content: center;
  align-items: center;
  background: var(--grid-bg);
  border: 1px solid var(--cell-border);
  border-radius: 8px;
  padding: 8px;
}
.replay-grid {
  display: grid;
  gap: 2px;
  font-family: var(--font-mono);
}
.replay-cell {
  width: 36px;
  height: 36px;
  background: var(--cell-bg);
  border-radius: 3px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 16px;
  font-weight: 600;
  color: var(--muted);
  text-transform: lowercase;
}
.replay-cell.alt { background: var(--cell-bg-2); }
.replay-cell.food {
  background: var(--food);
  color: #2A2202;
}
.replay-cell.snake {
  background: var(--player);
  color: var(--bg);
}
.replay-cell.snake.head {
  background: var(--player-head);
  color: var(--bg);
}
.replay-controls {
  display: flex;
  align-items: center;
  gap: 8px;
}
.replay-ctrl-btn {
  background: var(--panel-2);
  border: 1px solid var(--cell-border);
  border-radius: 6px;
  color: var(--accent);
  font-size: 14px;
  line-height: 1;
  padding: 6px 10px;
  cursor: pointer;
  font-family: var(--font-mono);
  min-width: 36px;
}
.replay-ctrl-btn:hover { background: rgba(155, 242, 76, 0.10); }
.replay-slider {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 4px;
  background: var(--panel-2);
  border: 1px solid var(--cell-border);
  border-radius: 4px;
  outline: none;
  cursor: pointer;
  padding: 0;
  margin: 0;
}
.replay-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  border: 1px solid var(--cell-border);
  cursor: pointer;
}
.replay-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  border: 1px solid var(--cell-border);
  cursor: pointer;
}
.replay-elapsed {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  min-width: 80px;
  text-align: right;
}
.replay-speeds {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--muted);
}
.replay-speeds-label { margin-right: 4px; }
.replay-speed-btn {
  background: var(--panel-2);
  border: 1px solid var(--cell-border);
  border-radius: 4px;
  color: var(--muted);
  font-size: 11px;
  padding: 3px 8px;
  cursor: pointer;
  font-family: var(--font-mono);
  font-weight: 600;
}
.replay-speed-btn:hover { color: var(--text); border-color: #3a4050; }
.replay-speed-btn.active {
  background: rgba(155, 242, 76, 0.15);
  border-color: var(--accent);
  color: var(--accent);
}
.replay-live-score {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
@media (max-width: 480px) {
  .replay-cell { width: 30px; height: 30px; font-size: 14px; }
  .replay-elapsed { min-width: 70px; font-size: 10px; }
}

/* === Letter-change debug overlay (?debug=letters) =========================
   Diagnostic-only. Activated by URL flag in game.js (window.__letterDebug).
   Strictly additive — only paints cells the renderer has tagged
   .debug-letterfx, and the corner stats box (#debug-letterfx-stats) is
   appended lazily. When the flag is OFF, none of these classes are ever
   applied and the box is never created, so layout is untouched.

   The outline uses inset box-shadow (rather than border) so cell sizing
   stays identical across debug-on / debug-off — important for replay
   parity if a debug session is ever recorded. Color is set per-event via
   --debug-color (one of the cause palette colors in game.js). */
.cell.debug-letterfx {
  /* 2px inset ring in the cause color, layered on top of any existing
     box-shadow (heat pulses, food glow). Position-relative so the label
     child can absolutely-position into the cell corner. */
  box-shadow: inset 0 0 0 2px var(--debug-color, #fff);
  position: relative;
}
.debug-letterfx-label {
  /* Tiny corner label per the spec: "OLD→NEW" + 3-char position tag in
     8px monospace. Positioned in the top-left corner so it doesn't
     overlap the cell letter (centered) or food glow (radial). */
  position: absolute;
  top: 1px;
  left: 2px;
  font-size: 8px;
  line-height: 1;
  font-family: var(--font-mono, monospace);
  font-weight: 700;
  pointer-events: none;
  /* Subtle dark backdrop so the label reads on light + dark cells. */
  text-shadow: 0 0 2px #000, 0 0 1px #000;
  letter-spacing: -0.3px;
}
#debug-letterfx-stats {
  /* Fixed corner overlay, top-right so it doesn't fight with the
     existing scoreboard (top-left) or popups layer (anchored to grid).
     z-index above the grid + popups but below modals. */
  position: fixed;
  top: 8px;
  right: 8px;
  z-index: 9999;
  background: rgba(15, 17, 24, 0.92);
  border: 1px solid #3a4050;
  border-radius: 6px;
  padding: 8px 10px;
  font-family: var(--font-mono, monospace);
  font-size: 11px;
  color: #d6dae4;
  min-width: 220px;
  pointer-events: none;
  font-variant-numeric: tabular-nums;
}
.debug-stats-title {
  font-weight: 700;
  margin-bottom: 4px;
  color: #9bf24c;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.debug-stats-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 1px 0;
}
.debug-stats-row b { color: #fff; }
.debug-stats-headline {
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px solid #3a4050;
  color: #ff7ab6;
}

/* === Changelog "What's new" pill (lobby sidebar) ============================
   Small outline-styled link rendered in the "Also try" panel, sibling to the
   TypeQuest button. Distinct from .lobby-btn (filled accent) so it reads as
   secondary navigation, not a play CTA. The .cl-pill-dot is a 6px red marker
   shown only when the latest changelog entry is unseen — the JS in
   index.html toggles its [hidden] attribute. Tooltip-free; the icon + label
   are self-explanatory. Compatible with the panel button cascade because
   it's an <a> not a <button>. */
.cl-pill {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin-top: 8px;
  padding: 8px 12px;
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--cell-border);
  border-radius: 999px;
  text-decoration: none;
  cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease, background-color 120ms ease;
}
.cl-pill:hover,
.cl-pill:focus-visible {
  color: var(--accent);
  border-color: var(--accent);
  background: rgba(92, 200, 255, 0.06);
  outline: none;
}
.cl-pill-icon {
  font-size: 13px;
  line-height: 1;
}
.cl-pill-text { line-height: 1.2; }
.cl-pill-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #F24C4C;
  box-shadow: 0 0 6px rgba(242, 76, 76, 0.7);
  animation: cl-pill-dot-pulse 1.6s ease-in-out infinite;
}
.cl-pill-dot[hidden] { display: none; }
@keyframes cl-pill-dot-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.55; transform: scale(0.85); }
}
@media (prefers-reduced-motion: reduce) {
  .cl-pill-dot { animation: none; }
}
