/* ============================================================
   Daily District — styles
   ============================================================ */

/* Barlow loaded via <link> in index.html — no @import needed here */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  height: 100%;
  max-height: 100dvh;
  overflow: hidden;
}

.leaflet-control-attribution { display: none !important; }

:root {
  /* CMU brand colors — constant across themes */
  --cmu-red:        #C41230;
  --cmu-red-dark:   #941120;
  --cmu-sky:        #007BC0;
  --cmu-navy:       #182C4B;
  --cmu-gold:       #FDB515;
  --cmu-green:      #009647;
  --cmu-gray:       #6D6E71;
  --cmu-steel:      #E0E0E0;

  /* ---- Light mode (default) ---- */
  --bg:             #f5f5f3;
  --surface:        #ffffff;
  --surface-alt:    #f3f4f6;
  --border:         #d1d5db;
  --text:           #1a1a1a;
  --text-secondary: #374151;
  --muted:          #6b7280;
  --muted-border:   #c4c9d4;

  /* semantic colors — theme-aware */
  --correct-bg:     #dcfce7;
  --correct-border: #86efac;
  --correct-text:   #166534;
  --wrong-bg:       #fef2f2;
  --wrong-border:   #fca5a5;
  --wrong-text:     #991b1b;
  --fact-bg:        #fdf4f5;
  --banner-bg:      #fef9c3;
  --banner-border:  #fde047;

  --accent:         var(--cmu-red);
  --accent-hover:   var(--cmu-red-dark);
  --green:          var(--cmu-green);
  --red:            var(--cmu-red);
  --yellow:         var(--cmu-gold);
  --radius:         8px;
  --shadow:         0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.08);
  --shadow-lg:      0 10px 25px rgba(0,0,0,0.15);
  --header-h:       52px;   /* header height; modals reserve this so the menu bar stays reachable */
  --menubar-w:      900px;  /* .header-inner's content width — #postgame-ribbon's inner row matches it */
  --postgame-ribbon-h: 0px; /* measured by _syncPostgameRibbonHeight(); 0 when no game is over */
  /* Arrow glyph used as a mask inside CMU-style red buttons (.Button--alt). */
  --cmu-arrow:      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h12M12 6l6 6-6 6' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* ============================================================
   DARK MODE
   ============================================================ */
/* Applied when system prefers dark AND user hasn't forced light */
@media (prefers-color-scheme: dark) {
  body:not(.light-mode) {
    --bg:             #111213;
    --surface:        #1a1a1b;
    --surface-alt:    #252526;
    --border:         #3a3a3c;
    --text:           #ffffff;
    --text-secondary: #d1d5db;
    --muted:          #818384;
    --muted-border:   #4a4a4c;
    --correct-bg:     #143620;
    --correct-border: #2a7a4e;
    --correct-text:   #86efac;
    --wrong-bg:       #321010;
    --wrong-border:   #7a2020;
    --wrong-text:     #fca5a5;
    --fact-bg:        #1e1e1f;
    --banner-bg:      #332c00;
    --banner-border:  #665800;
    --shadow:         0 1px 3px rgba(0,0,0,0.4), 0 1px 2px rgba(0,0,0,0.3);
    --shadow-lg:      0 10px 25px rgba(0,0,0,0.5);
    /* Slightly lighter red so it reads on dark surfaces */
    --accent:         #e8314a;
    --accent-hover:   #C41230;
  }
}
/* Applied when user explicitly toggled dark */
body.dark-mode {
  --bg:             #111213;
  --surface:        #1a1a1b;
  --surface-alt:    #252526;
  --border:         #3a3a3c;
  --text:           #ffffff;
  --text-secondary: #d1d5db;
  --muted:          #818384;
  --muted-border:   #4a4a4c;
  --correct-bg:     #143620;
  --correct-border: #2a7a4e;
  --correct-text:   #86efac;
  --wrong-bg:       #321010;
  --wrong-border:   #7a2020;
  --wrong-text:     #fca5a5;
  --fact-bg:        #1a2535;
  --banner-bg:      #332c00;
  --banner-border:  #665800;
  --shadow:         0 1px 3px rgba(0,0,0,0.4), 0 1px 2px rgba(0,0,0,0.3);
  --shadow-lg:      0 10px 25px rgba(0,0,0,0.5);
  /* Slightly lighter red so it reads on dark surfaces */
  --accent:         #e8314a;
  --accent-hover:   #C41230;
}

body {
  font-family: 'Barlow', 'Segoe UI', system-ui, -apple-system, sans-serif;
  background: var(--bg);
  color: var(--text);
  /* Fixed to the *visible* viewport (dvh, not vh — vh ignores mobile browser
     toolbars and would make the body taller than the screen, pushing game
     content past the header). Header + main are the two flex rows below. */
  height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
/* Prevent page scroll when any modal is open */
body:has(.modal:not(.hidden)) { overflow: hidden; }

/* ---- SVG icon system ---- */
.icon {
  display: inline-block;
  width: 1.1em;
  height: 1.1em;
  vertical-align: middle;
  flex-shrink: 0;
}
/* Inline icon inside btn-icon buttons */
.btn-icon .icon,
.btn-icon svg {
  width: 18px;
  height: 18px;
  display: block;
}
/* Clue/fact panel icon */
.clue-icon-svg {
  width: 1rem;
  height: 1rem;
  display: block;
  color: var(--accent);
}
.clue-icon-svg.locked { color: var(--muted-border); }
/* Guess history row icons */
.guess-icon-svg {
  width: 1.1rem;
  height: 1.1rem;
  display: block;
  flex-shrink: 0;
}
.guess-row.correct .guess-icon-svg { color: var(--green); }
.guess-row.wrong   .guess-icon-svg { color: var(--red); }
/* State boundary SVG for state guesses */
.guess-icon-state-slot {
  width: 1.5rem;
  height: 1.5rem;
  display: inline-flex;
  flex-shrink: 0;
  position: relative;
}
.guess-icon-state-slot .state-svg-container {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.guess-icon-state-slot svg {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.guess-row.correct .guess-icon-state-slot svg path {
  stroke: var(--green) !important;
}
.guess-row.wrong .guess-icon-state-slot svg path {
  stroke: var(--red) !important;
}
/* Hot/cold hint icons */
.hint-icon {
  width: 0.85rem;
  height: 0.85rem;
  display: inline-block;
  vertical-align: middle;
  margin-right: 2px;
}
.guess-hint.hot  .hint-icon { color: #c97600; }
.guess-hint.cold .hint-icon { color: #0a4e8a; }

/* ---- Header ---- */
header {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  /* First of the two flex rows: fixed height, never shrinks. main fills the rest.
     Stays at the top of the document flow so game content always sits below it. */
  flex: 0 0 auto;
  position: sticky;
  top: 0;
  /* Above all modals (max z-index 320) so the menu bar stays visible & tappable
     even over the game-over / result modal on mobile. */
  z-index: 600;
}
.header-inner {
  max-width: var(--menubar-w);
  margin: 0 auto;
  padding: 0 14px;
  height: 52px;
  position: relative;            /* anchor for the absolutely-centered title */
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.header-left {
  display: flex;
  align-items: center;
  gap: 6px;
}
header h1 {
  /* Absolutely centered on the menu bar at all times, regardless of how wide the
     left (icons) or right (timer/account/donate) groups are. */
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  pointer-events: none;          /* let clicks fall through; the button re-enables itself */
  font-size: 1.35rem;
  font-weight: 800;
  letter-spacing: -0.5px;
  display: flex;
  align-items: center;
  gap: 8px;
  justify-content: center;
  white-space: nowrap;
}
.title-home-btn { pointer-events: auto; }
.title-home-btn {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  display: inherit;
  align-items: inherit;
  gap: inherit;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  white-space: inherit;
}
/* Usually a <button>, but an <a> on the pre-launch teaser (no game JS to intercept the
   click) — the global link-underline rule further down (`a:not(...)`) would otherwise
   draw its hover underline across the whole logo+wordmark lockup. This selector's
   specificity (0,2,2) beats that rule's (0,2,1) regardless of source order. */
header .header-inner a.title-home-btn {
  text-decoration: none;
  background: none;
  padding-bottom: 0;
}
.game-logo {
  width: 44px;
  height: auto;
  flex-shrink: 0;
  display: block;
}
.beta-badge {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  background: var(--cmu-gold);
  color: #1a1a1a;
  padding: 2px 6px;
  border-radius: 6px;
  vertical-align: middle;
  line-height: 0.9;
}
.beta-label { font-size: 0.5rem; }
.beta-version { font-size: 0.5rem; }
.header-right {
  display: flex;
  align-items: center;
  gap: 6px;
  justify-content: flex-end;
  min-width: 0;
}
/* Header action pills — Sign in + Donate: identical size, outlined red, fill on
   hover (matches #gameover-new-map-btn). Collapse to icon circles on narrow screens
   (see the mobile media query). */
.donate-btn,
.header-signin-btn {
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 34px;
  min-width: 92px;
  padding: 0 14px;
  border-radius: 999px;
  border: 2px solid var(--cmu-red);
  background: transparent;
  color: var(--cmu-red);
  text-decoration: none;
  font-size: 0.8rem;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  flex-shrink: 0;
  transition: background 0.15s, color 0.15s;
}
.donate-btn:hover,
.header-signin-btn:hover { background: var(--cmu-red); color: #fff; }
.hdr-btn-icon { width: 17px; height: 17px; flex: 0 0 auto; display: none; } /* shown only in circle mode */
.timer {
  font-size: 0.95rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  background: var(--surface-alt);
  color: var(--text);
  padding: 4px 10px;
  border-radius: 20px;
  min-width: 68px;
  display: flex;
  align-items: center;
  gap: 5px;
}
.timer.running { color: var(--accent); }

/* Inline timer shown on mobile, inside guess-counter row */
.timer-inline {
  display: none;
  align-items: center;
  gap: 3px;
  font-size: 0.8rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  margin-left: auto;
}
.btn-icon {
  background: none;
  border: 1px solid var(--border);
  border-radius: 50%;
  width: 34px;
  height: 34px;
  flex: 0 0 auto;          /* never shrink in the header flex row — stay uniform */
  min-width: 34px;
  min-height: 34px;
  font-size: 1rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text);
  transition: background 0.15s;
}
.btn-icon:hover { background: var(--surface-alt); }

/* ---- First-visit "Settings is here" callout ---- */
.settings-hint {
  position: fixed;
  z-index: 1200;
  max-width: 240px;
  padding: 8px 12px;
  border-radius: 9px;
  background: var(--accent);
  color: #fff;
  font-size: 0.8rem;
  font-weight: 600;
  line-height: 1.3;
  box-shadow: 0 8px 24px rgba(0,0,0,0.28);
  cursor: pointer;
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.settings-hint.show { opacity: 1; transform: translateY(0); }
.settings-hint::before {
  content: '';
  position: absolute;
  top: -5px;
  left: 18px;
  width: 11px;
  height: 11px;
  background: inherit;
  border-radius: 2px;
  transform: rotate(45deg);
}
.settings-hint-gear { margin-right: 2px; }
@keyframes settings-gear-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.2); }
}
#settings-btn.hint-pulse { color: var(--accent); }
#settings-btn.hint-pulse svg {
  transform-origin: center;
  animation: settings-gear-pulse 1.1s ease-in-out 3;
}

/* ---- Main layout ---- */
main {
  max-width: 900px;
  margin: 0 auto;
  padding: 16px;
  /* #postgame-ribbon (fixed, above) doesn't reserve space in normal flow on its own —
     add that space here so #archive-badge/#gameover-modal (its flex children) don't
     render underneath it. 0px outside game-over, so normally a no-op. */
  padding-top: calc(16px + var(--postgame-ribbon-h));
  width: 100%;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
  overflow: hidden;
}
/* Tighten gap when the game-over banner is showing */
main:has(#already-played-banner:not(.hidden)) {
  gap: 6px;
}

/* Game section: fills remaining main height so the banner above doesn't push it off-screen */
#game-section {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  overflow: hidden;
}

#map {
  flex: 0 0 35%;
  min-height: 0;
  transition: flex-basis 0.3s ease, opacity 0.3s ease;
}

#map-panel {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* ---- Hint cards + guess counter container ---- */
#game-controls {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
  /* Reserve the hint-row height up front (cards = 52px + 8px hint-bar padding) so the
     panel doesn't resize once the hint-bar fills in / the first clue reveals. */
  min-height: 60px;
}

#hint-bar {
  display: flex;
  gap: 8px;
  padding: 6px 0 2px;
  overflow-x: hidden;
  overflow-y: visible;
  scrollbar-width: none;
}
#hint-bar::-webkit-scrollbar { display: none; }
/* Hard mode: replaces the clue cards with a muted note. */
.hint-hard-note {
  flex: 1;
  text-align: center;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--muted);
  padding: 9px 12px;
  border: 1px dashed var(--border);
  border-radius: 8px;
}

/* Collapsed by default: previous/locked clues shrink to a tappable icon strip; only
   the latest clue expands to fill the remaining horizontal space (see .latest below).
   This keeps the whole bar fitting the width with the current hint big and readable. */
.hint-card {
  flex: 0 0 44px;
  min-width: 44px;
  /* Match the revealed (label+value) card height so locked cards reserve the same space.
     Otherwise the row is short while every card is locked, then jumps taller the moment
     the first clue reveals (after the first guess), shifting #map-panel down. */
  min-height: 52px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  transition: opacity 0.3s, border-color 0.3s, min-height 0.2s ease;
  box-shadow: var(--shadow);
  cursor: default;
}

.hint-card:first-child { margin-left: 0; }
.hint-card.revealed { overflow: hidden; }

.hint-card.locked {
  background: var(--surface-alt);
  opacity: 0.5;
}

.hint-card.revealed {
  background: var(--surface);
  animation: hintReveal 0.35s ease;
}

.hint-card.revealed.latest {
  border-color: var(--accent);
}

/* The current (latest) clue grows to fill all remaining width so it's big and legible;
   collapsed cards keep their icon-strip width set on .hint-card above. Applies at all
   viewport sizes (the mobile media query only fine-tunes the collapsed width). */
.hint-card.latest {
  flex: 1 1 0;
  min-width: 0;
}
.hint-card.locked .hint-card-header,
.hint-card.revealed:not(.latest):not(.expanded) .hint-card-header {
  justify-content: center;
}
.hint-card.locked .hint-card-label,
.hint-card.locked .hint-card-val,
.hint-card.revealed:not(.latest):not(.expanded) .hint-card-label,
.hint-card.revealed:not(.latest):not(.expanded) .hint-card-val {
  display: none;
}
.hint-card.revealed:not(.latest) { cursor: pointer; }

@keyframes hintReveal {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

.hint-card-header {
  display: flex;
  align-items: center;
  gap: 5px;
}

.hint-card-icon {
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  color: var(--accent);
  display: flex;
  align-items: center;
}
.hint-card.locked .hint-card-icon { color: var(--muted); }

.hint-card-label {
  font-size: 0.62rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--accent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hint-card.locked .hint-card-label { color: var(--muted); }

.hint-card-val {
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--text);
  line-height: 1.3;
  overflow: hidden;
}

.hint-card-val {
  position: relative;
}

.hint-card-val > span {
  display: inline-block;
  white-space: nowrap;
}


@keyframes hintScroll {
  0%,  15% { margin-left: 0; }
  85%, 100% { margin-left: var(--scroll-dist, 0px); }
}

/* ── Mobile hint bar ─────────────────────────────────────────── */
@media (max-width: 640px) {
  /* Collapse/expand behaviour is universal (above); on mobile tighten the collapsed
     icon strip (it only shows a centered icon) so the latest clue keeps its full title
     instead of ellipsis-truncating. */
  .hint-card {
    flex: 0 0 26px;
    min-width: 26px;
    padding: 8px 3px;
  }
  .hint-card.latest { flex: 1 1 0; min-width: 0; padding: 8px 10px; }
}
/* Very narrow phones: shrink the icon strip further so long clue titles still fit. */
@media (max-width: 400px) {
  .hint-card { flex: 0 0 22px; min-width: 22px; padding: 8px 2px; }
  .hint-card.latest { flex: 1 1 0; min-width: 0; padding: 8px 8px; }
}

/* ---- Banner ---- */
.banner {
  background: var(--banner-bg);
  border: 1px solid var(--banner-border);
  border-radius: var(--radius);
  padding: 10px 16px;
  font-size: 0.9rem;
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.banner-actions {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
}
.banner button {
  background: var(--accent);
  color: white;
  border: none;
  border-radius: 4px;
  padding: 5px 12px;
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 600;
  white-space: nowrap;
}
#banner-new-map-btn {
  background: transparent;
  color: var(--accent);
  border: 1.5px solid var(--accent);
}
#banner-new-map-btn:hover { background: var(--accent); color: white; }
.banner-dismiss {
  background: transparent;
  color: var(--muted);
  border: 1.5px solid var(--border);
}
.banner-dismiss:hover { color: var(--text); border-color: var(--muted); }

/* ---- Map ---- */
#map {
  width: 100%;
  background: var(--bg);
  isolation: isolate;
  position: relative;
  z-index: 0;
}

/* ---- Clues panel ---- */
#hints-clues-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 8px;
}

/* Revealed facts — prominent */
.clue-item.revealed {
  display: grid;
  grid-template-columns: 1.4rem 1fr;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  background: var(--fact-bg);
  border-radius: 8px;
  border-left: 3px solid var(--accent);
  animation: clue-in 0.35s ease;
}
.clue-item.revealed .clue-icon {
  display: flex;
  align-items: center;
  justify-content: center;
}
.clue-item.revealed .clue-text {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--text);
  line-height: 1.3;
}
.clue-item.revealed .clue-label {
  font-size: 0.7rem;
  font-weight: 500;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  display: block;
  margin-bottom: 1px;
}

/* Locked facts — minimal */
.clue-item.locked {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  color: var(--muted-border);
  font-size: 0.78rem;
}
.clue-item.locked .clue-icon { opacity: 0.4; }
.clue-item.locked .clue-text { color: var(--muted-border); }

@keyframes clue-in {
  from { opacity: 0; transform: translateX(-6px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* ---- Guess section ---- */
#guesses-section.active {
  align-items: stretch;  /* override center so rows go full width */
  gap: 10px;
}
#guess-history {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.guess-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 12px;
  border-radius: 6px;
  font-size: 0.9rem;
  font-weight: 600;
}
.guess-row.correct {
  background: var(--correct-bg);
  border: 1px solid var(--correct-border);
  color: var(--correct-text);
}
.guess-row.wrong {
  background: var(--wrong-bg);
  border: 1px solid var(--wrong-border);
  color: var(--wrong-text);
}
.guess-row .guess-icon  { display: flex; align-items: center; }
.guess-row .guess-label { flex: 1; }
.guess-hint {
  font-size: 0.75rem;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 20px;
  white-space: nowrap;
  letter-spacing: 0.03em;
}
.guess-hint.hot {
  background: #fff3cd;
  color: #7a4f00;
  border: 1px solid #ffc107;
}
.guess-hint.cold {
  background: #e7f3ff;
  color: #0a4e8a;
  border: 1px solid #90c8f5;
}
#guess-remaining {
  font-size: 0.85rem;
  color: var(--muted);
  text-align: center;
}
/* State confirmed badge */

@keyframes shake {
  0%,100% { transform: translateX(0); }
  20%      { transform: translateX(-6px); }
  40%      { transform: translateX(6px); }
  60%      { transform: translateX(-4px); }
  80%      { transform: translateX(4px); }
}
.shake { animation: shake 0.4s ease; }

@keyframes flashCorrect {
  0%   { box-shadow: 0 0 0 0 rgba(34,197,94,0); }
  30%  { box-shadow: 0 0 0 8px rgba(34,197,94,0.55); }
  100% { box-shadow: 0 0 0 14px rgba(34,197,94,0); }
}
@keyframes flashWrong {
  0%,100% { transform: translateX(0); box-shadow: 0 0 0 0 rgba(220,38,38,0); }
  15%     { transform: translateX(-5px); box-shadow: 0 0 0 6px rgba(220,38,38,0.45); }
  30%     { transform: translateX(5px); }
  45%     { transform: translateX(-4px); }
  60%     { transform: translateX(4px); }
  75%     { transform: translateX(-2px); }
}

/* Live state fills fade over the static grey basemap — true dimming on a pending guess and
   a smooth fade-out when a state is eliminated. Only the live layer's state paths animate. */
#us-ref-map .layer-states path[data-abbr] { transition: fill-opacity 0.22s ease; }
/* Offshore callouts dim the same way (group opacity) during a pending guess / elimination. */
#us-ref-map .us-ref-callouts g[data-abbr] { transition: opacity 0.22s ease; }

/* Game-over loss: shake the whole map-ref area to signal "you didn't get it" */
@keyframes gameoverLossShake {
  0%, 100% { transform: translateX(0); }
  15%      { transform: translateX(-7px) rotate(-0.6deg); }
  30%      { transform: translateX(7px)  rotate(0.6deg); }
  45%      { transform: translateX(-5px); }
  60%      { transform: translateX(5px); }
  75%      { transform: translateX(-2px); }
}
#district-tiles.gameover-loss-shake svg,
#gameover-map-wrap.gameover-loss-shake svg { animation: gameoverLossShake 0.5s ease 0.65s; }

/* Game-over win: pulse ring around the map-ref area to celebrate the correct pick */
@keyframes gameoverWinPulse {
  0%   { box-shadow: 0 0 0 0 rgba(196,18,48,0); }
  40%  { box-shadow: 0 0 0 10px rgba(196,18,48,0.35); }
  100% { box-shadow: 0 0 0 18px rgba(196,18,48,0); }
}
#district-tiles.gameover-win-pulse,
#gameover-map-wrap.gameover-win-pulse { animation: gameoverWinPulse 0.7s ease 0.65s; }

/* Correct district tile: gold pop */
@keyframes tileCorrectPop {
  0%   { transform: scale(1);    fill: #C41230; }
  20%  { transform: scale(1.7);  fill: #FDB515; }
  55%  { transform: scale(1.45); fill: #FDB515; }
  100% { transform: scale(1.55); fill: #FDB515; }
}
circle.tile-correct-pop {
  transform-box: fill-box;
  transform-origin: center;
  animation: tileCorrectPop 0.65s cubic-bezier(0.34,1.56,0.64,1) forwards;
  filter: drop-shadow(0 0 5px rgba(253,181,21,0.9));
}
.tile-correct-check {
  animation: tileCheckFadeIn 0.12s ease 0.08s both;
}
@keyframes tileCheckFadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Wrong district tile: subtle shake */
@keyframes tileWrongShake {
  0%, 100% { transform: translateX(0); }
  18%      { transform: translateX(-2px); }
  36%      { transform: translateX(2px); }
  54%      { transform: translateX(-1.5px); }
  72%      { transform: translateX(1.5px); }
  88%      { transform: translateX(-0.5px); }
}
circle.tile-wrong-shake {
  transform-box: fill-box;
  transform-origin: center;
  animation: tileWrongShake 0.35s ease;
  fill: #c41230 !important;
}

/* Optimistic press: instant feedback on tap while the /guess request is in flight, so
   the click doesn't feel laggy. A ripple/sonar ping radiates from the tapped tile and
   the other tiles dim — no recolouring of the tile itself. Replaced by the pop/shake
   once the server replies. */
@keyframes tileRipple {
  0%   { transform: scale(0.85); opacity: 0.6; }
  70%  { opacity: 0.12; }
  100% { transform: scale(2.8);  opacity: 0; }
}
circle.tile-ripple {
  fill: none;
  stroke: var(--red, #C41230);
  stroke-width: 2.5;
  vector-effect: non-scaling-stroke;
  transform-box: fill-box;
  transform-origin: center;
  animation: tileRipple 0.85s ease-out infinite;
  pointer-events: none;
}
/* While a tile's guess is in flight, dim the other (un-tapped) tiles. The tiles carry an
   inline opacity from the build/fade code, so !important is needed to override it. */
#us-ref-map g.district-tile { transition: opacity 0.18s ease; }
#us-ref-map.tiles-pinging g.district-tile:not(.tile-active) { opacity: 0.4 !important; }

/* State pick: the non-selected states dim while the /guess is in flight (opacity set
   inline via D3); smooth the transition. */
#us-ref-map svg path[data-abbr] { transition: opacity 0.15s ease; }

/* ---- Result section ---- */
/* ---- Result modal ---- */
/* Unlike the other modals (capped at a small centered 480px card), the result modal takes
   the full page height (not the generic centered-card sizing) so it lines up with the
   game-over screen behind it and leaves room for #postgame-ribbon above + the desktop
   side-ad rails beside it. It keeps the standard dark scrim (inherited from .modal) so the
   game screen behind it is dimmed like every other modal; the side-ad rails are lifted
   above the scrim (see .side-ad z-index below) so they stay bright/clickable in the gutters
   rather than the scrim having to carve a hole around them. */
#result-modal { align-items: stretch; }
.modal-content.result-modal-content {
  max-width: 900px;
  width: 100%;
  height: 100%;
  max-height: 100%;
  border-radius: 0;
  box-shadow: none;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 0;          /* tabs + panes handle their own padding */
}
/* On desktop the tabs/stats content doesn't need anywhere near the map's full width —
   stretching it to match `main` just left everything reading unnecessarily wide and
   sparse. Narrow it right down; the freed-up space becomes padding either side (and,
   further out, the fixed side-ad rails — see .side-ad below). Only this content card
   narrows: #postgame-ribbon above it is unaffected, still spanning the full menu-bar
   width per --menubar-w. */
@media (orientation: landscape) and (min-width: 600px) {
  .modal-content.result-modal-content { max-width: 500px; }
}
/* Wraps every tab-pane + the shared footer (ad/donate/feedback) as one flex column, so
   the footer shows below whichever tab is active instead of only inside one pane. */
.result-tabs-content {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}
.result-tab-pane.active {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}
/* Two labeled tab groups ("Today's District" | "Lifetime Statistics"), centred and
   split by a vertical divider so the two halves read as distinct. */
.result-modal-tabs {
  display: flex;
  justify-content: center;
  gap: 0;
  border-bottom: 2px solid var(--border);
  padding: 14px 20px 0;
}
.result-tab-group {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.result-tab-group:first-child  { padding-right: clamp(18px, 6vw, 56px); }
.result-tab-group + .result-tab-group {
  padding-left: clamp(18px, 6vw, 56px);
  border-left: 1.5px solid var(--border);
}
.result-tab-group-label {
  text-align: center;
  font-size: 0.64rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--muted);
  margin-bottom: 3px;
  white-space: nowrap;
}
.result-tab-group-btns {
  display: flex;
  justify-content: center;
  gap: 4px;
}
.result-tab-btn {
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  padding: 8px 14px;
  margin-bottom: -2px;
  font-size: 0.88rem;
  font-weight: 700;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
  transition: color 0.15s, border-color 0.15s;
}
.result-tab-btn:hover {
  color: var(--text);
  border-bottom-color: var(--muted);   /* mouseover line */
}
.result-tab-btn.active,
.result-tab-btn.active:hover {
  color: var(--cmu-red);
  border-bottom-color: var(--cmu-red);
}
.result-tab-pane {
  display: none;
  padding: 8px 20px 20px;
}
/* Shared footer (ad/donate/feedback) — lives outside the tab-panes now (shown under every
   tab), so it needs its own copy of the horizontal padding the panes used to provide. */
.result-modal-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  flex-shrink: 0;
  padding: 0 20px 16px;
}

/* District shape preview in result modal (both tabs) */
#result-district-preview,
#census-district-preview {
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface-alt);
  border: 1px solid var(--border);
  flex: 0 0 clamp(130px, 27vh, 240px);
}
/* Inset the result preview so it reads as embedded in the modal (result modal has 0 padding). */
#result-district-preview { margin: 12px 12px 0; }
.district-preview-svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* Answer reveal block */
.result-answer {
  text-align: center;
  line-height: 1.2;
}
.result-answer-code {
  display: block;
  font-size: clamp(1.4rem, 4vw, 2.2rem);
  font-weight: 900;
  letter-spacing: -0.02em;
  color: var(--text);
}
.result-time-line {
  font-size: 0.88rem;
  color: var(--muted);
  text-align: center;
  margin-top: 6px;
  padding: 6px 14px;
  background: var(--surface-alt);
  border-radius: var(--radius);
}

#result-message {
  font-size: 1.2rem;
  font-weight: 800;
  margin: -8px 0;
}
#result-message.won  { color: var(--green); }
#result-message.lost { color: var(--red); }

@media (max-height: 720px) { #result-message { display: none; } }
#result-stats {
  width: 100%;
  text-align: center;
}
.result-donate-btn {
  width: 100%;
  text-align: center;
  font-size: 0.78rem;
  padding: 7px 16px;
  border-radius: var(--radius);
  opacity: 0.85;
  margin-top: auto;
}
/* Ribbon buttons (View Results/District Profile, Share, Post, Play Archive) are plain
   children of .banner-actions inside .gameover-ribbon banner, picking up the solid-red
   .banner button look by default. Play Archive is the one outlined exception — see
   #gameover-new-map-btn below.
   Up to four buttons (Post is hidden outside the daily result view — see buildGameoverDiv/
   buildGameoverContent) need help staying on one row: shrink below the ribbon's own 480px
   stacking breakpoint, and swap in the two longest labels' short alternates (District
   Profile → Profile, Play Archive → Archive) via ribbon-label-full/short below. flex-shrink
   (not wrap) lets button padding/text give up space gracefully instead of ever dropping to
   a second row. */
.gameover-ribbon .banner-actions { flex-wrap: nowrap; }
.gameover-ribbon .banner-actions button { flex-shrink: 1; min-width: 0; white-space: nowrap; }
.ribbon-label-short { display: none; }
@media (max-width: 480px) {
  .gameover-ribbon .banner-actions { gap: 5px; }
  .gameover-ribbon .banner-actions button { padding: 5px 8px; font-size: 0.76rem; }
  .ribbon-label-full { display: none; }
  .ribbon-label-short { display: inline; }
}
/* Very narrow phones (≤360px, e.g. iPhone SE): the longest label ("Today's District")
   can't shrink its text, so tighten the whole row a touch more so all four buttons still
   fit on one line instead of the last one ("Archive") clipping at the ribbon's edge. */
@media (max-width: 360px) {
  .gameover-ribbon .banner-actions { gap: 4px; }
  .gameover-ribbon .banner-actions button { padding: 5px 6px; font-size: 0.7rem; }
}
#share-copied {
  font-size: 0.85rem;
  color: var(--green);
  font-weight: 600;
}

/* ---- Guess progress counter (dots + label) ---- */
.guess-counter {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.gc-dots { display: flex; gap: 3px; }
.gc-dot {
  width: 11px;
  height: 11px;
  border-radius: 2px;
  border: 2px solid var(--border);
  background: transparent;
  transition: background 0.2s, border-color 0.2s;
}
/* Used guesses: show SVG icon, no border */
.gc-dot.gc-used {
  border: none;
  background: transparent;
  width: 13px;
  height: 13px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.gc-icon-svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
  display: block;
}
.gc-icon-svg { color: #000; }
.dark-mode .gc-icon-svg { color: #fff; }
.gc-label { font-size: 0.75rem; color: var(--muted); font-weight: 600; }

/* ---- US Reference map tooltip ---- */
.us-ref-tooltip {
  position: fixed;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 3px 9px;
  font-size: 0.78rem;
  font-weight: 600;
  pointer-events: none;
  z-index: 5000;
  box-shadow: var(--shadow);
  display: none;
  white-space: nowrap;
}
.us-ref-tooltip.visible { display: block; }

.result-dist {
  width: 100%;
}
/* Avg guesses / time line — shown under the leaderboard "My Stats" panel only. */
.rstat-avg-time {
  font-size: 0.8rem;
  color: var(--muted);
  text-align: center;
  margin-top: 8px;
  line-height: 1.6;
}
.rstat-avg-time strong {
  font-size: 0.85rem;
  color: var(--accent);
  font-weight: 700;
}
@media (max-height: 640px) { .rstat-avg-time { display: none; } }
.result-dist h4 {
  font-size: 0.7rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text);
  text-align: center;
  margin-bottom: 10px;
}
.rdist-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}
.rdist-n {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--text);
  width: 14px;
  text-align: center;
  flex-shrink: 0;
}
.rdist-bar-wrap {
  flex: 1;
  height: 17px;
  background: var(--surface);
  border-radius: 3px;
  overflow: hidden;
}
.rdist-bar {
  height: 100%;
  background: var(--muted);
  border-radius: 3px;
  transition: width 0.4s ease;
  min-width: 0;
  position: relative;
  overflow: hidden;
}
.rdist-bar.today { background: var(--cmu-red); }
.rdist-count {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.72rem;
  font-weight: 700;
  color: white;
  pointer-events: none;
  line-height: 1;
  white-space: nowrap;
  text-align: right;
  flex-shrink: 0;
}

/* ---- Census tab inside result modal ---- */
#census-section.result-tab-pane.active {
  align-items: stretch;
}
#census-section h2 {
  font-size: 1.1rem;
  font-weight: 800;
  margin-bottom: 14px;
  display: flex;
  align-items: center;
  gap: 8px;
}
#census-loading {
  color: var(--muted);
  font-size: 0.9rem;
  text-align: center;
  padding: 10px;
}
.census-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 12px;
}
.census-card {
  position: relative;   /* positioning context for the corner ⓘ */
  background: var(--surface-alt);
  border-radius: var(--radius);
  padding: 12px 14px;
  min-height: 140px;   /* uniform card height so the grid isn't ragged */
  display: flex;
  flex-direction: column;
}
/* Footer graphic (bar / stack / shape / emblem) drops to the bottom of every card so
   the bars + their rank lines align at a consistent height across the grid. */
.census-card > .mp-wrap,
.census-card > .mini-stack,
.census-card > .mini-shape,
.census-card > .party-icon.party-lg { margin-top: auto; }
/* Inside the frosted District Profile sheet, the cards are glassy too so the
   blurred map behind shows through them. */
.district-profile .census-card {
  background: rgba(255, 255, 255, 0.75);   /* more opaque than the 0.2 sheet so cards stand out */
  border: 1px solid rgba(255, 255, 255, 0.45);
}
.district-profile .result-answer { background: transparent; padding: 2px 0 14px; }
/* The District Profile doesn't need the "Solved in X guesses" line. */
.district-profile .result-time-line { display: none; }
/* Dark theme: tint the glass dark so light text stays readable over the map. */
body.dark-mode .district-profile { background: rgba(22, 22, 26, 0.34); border-color: rgba(255, 255, 255, 0.14); }
body.dark-mode .district-profile .census-card { background: rgba(44, 44, 50, 0.62); border-color: rgba(255, 255, 255, 0.16); }
@media (prefers-color-scheme: dark) {
  body:not(.light-mode) .district-profile { background: rgba(22, 22, 26, 0.34); border-color: rgba(255, 255, 255, 0.14); }
  body:not(.light-mode) .district-profile .census-card { background: rgba(44, 44, 50, 0.62); border-color: rgba(255, 255, 255, 0.16); }
}
.census-card .label {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
  margin-bottom: 4px;
}
.census-card .value {
  font-size: 1.2rem;
  font-weight: 800;
  color: var(--text);
}
/* Current Representative: the name is a link — match the other card values
   (same color/weight/size) but keep an underline so it reads as clickable. */
.census-card.census-rep .value a {
  color: inherit;
  font-weight: 600;
  text-decoration: none;
  background: linear-gradient(currentColor, currentColor) 0 100% / 0 0.0625em no-repeat;
  padding-bottom: 0.0625em;
  transition: background-size .4s ease, color .4s ease;
}
.census-card.census-rep .value a:hover {
  color: inherit;
  background-size: 100% 0.0625em;
}

/* ── District Profile mini-graphics ───────────────────────────────────────── */
/* Percentile bar: where this district ranks among all 435 (tick = its position). */
.mp-wrap   { margin-top: 8px; }
.mini-pct  { display: block; width: 100%; height: 9px; overflow: visible; }
.mp-track  { stroke: var(--border); stroke-width: 2.5; stroke-linecap: round; }
.mp-tick   { fill: var(--cmu-red); }
.mp-ends   { display: flex; justify-content: space-between; margin-top: 2px; font-size: 0.66rem; color: var(--muted); }
/* Plain-words percentile rank under a bar ("Higher than 97% of districts"). */
.mp-rank   { margin-top: 4px; font-size: 0.72rem; color: var(--muted); }
/* 100%-stacked composition bar (race, 2024 vote). */
.mini-stack { display: block; width: 100%; height: 9px; margin-top: 8px; border-radius: 5px; overflow: hidden; }
.seg-white { fill: #9aa6b2; } .seg-black { fill: #566472; } .seg-hisp { fill: #cc7a3a; }
.seg-asian { fill: #6f9a55; } .seg-other { fill: #ccd2d8; }
.seg-dem   { fill: #3b6bc4; } .seg-rep   { fill: #c4123a; } .seg-oth  { fill: #ccd2d8; }
/* Color-keyed legend so the stacked race bar is self-explanatory. */
.race-legend { display: flex; flex-wrap: wrap; gap: 3px 10px; margin-top: 7px; font-size: 0.72rem; color: var(--muted); }
.race-legend span { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; }
.rl-dot { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.rl-white { background: #9aa6b2; } .rl-black { background: #566472; } .rl-hisp { background: #cc7a3a; }
.rl-asian { background: #6f9a55; } .rl-other { background: #ccd2d8; }
/* Party emblem on the representative card — official party logos (own colors). */
.party-icon { width: 1.5em; height: 1.5em; vertical-align: -0.34em; margin-right: 2px; }
.party-icon.party-lg { display: block; width: 52px; height: 52px; vertical-align: 0; margin: 8px 0 0; }
.party-icon.party-I { fill: #777; }   /* the Independent star is the only themed mark */
/* Shape thumbnails: district-in-circle (compactness) and district-in-state. */
.mini-shape  { display: block; width: 52px; height: 52px; margin-top: 8px; overflow: visible; }
.ms-circle   { fill: var(--cmu-red); fill-opacity: 0.10; stroke: var(--muted); stroke-width: 1; stroke-dasharray: 3 2; }
.ms-district { fill: var(--cmu-red); fill-opacity: 0.38; stroke: var(--cmu-red); stroke-width: 1.3; stroke-linejoin: round; }
.ms-state    { fill: none; stroke: var(--muted); stroke-width: 1.3; stroke-linejoin: round; }
.ms-here     { fill: var(--cmu-red); stroke: var(--cmu-red); stroke-width: 0.6; }
/* Compactness explainer — a <details> so it works by tap on mobile and click on desktop. */
/* Cards with an explanation are clickable anywhere to toggle it; a small ⓘ in the corner
   is the only affordance (no "What do these mean?" label) so the card keeps max space. */
.census-card:has(.ms-explain) { cursor: pointer; }
.ms-info {
  position: absolute; top: 6px; right: 8px;
  color: var(--accent); font-size: 0.92rem; font-weight: 700; line-height: 1;
}
.ms-explain  { display: none; margin-top: 6px; font-size: 0.68rem; color: var(--muted); line-height: 1.45; }
.census-card.expanded .ms-explain { display: block; }
.ms-explain b { color: var(--text); font-weight: 700; }
/* Two labeled compactness graphics (Polsby–Popper + Reock) side by side. */
.cmpct-cols  { display: flex; gap: 14px; }
.cmpct-col   { flex: 1; min-width: 0; text-align: center; }
.cmpct-col .mini-shape { width: 100%; height: auto; max-width: 54px; margin: 0 auto 4px; }
.cmpct-name  { font-weight: 700; color: var(--text); font-size: 0.60rem; }
.cmpct-val   { font-weight: 800; color: var(--text); font-size: 0.62rem; }
.cmpct-rank  { font-size: 0.66rem; color: var(--muted); line-height: 1.3; }
.cmpct-note  { margin: 8px 0 0; line-height: 1.45; }
body.dark-mode .ms-circle, body:not(.light-mode) .ms-circle { stroke: rgba(255,255,255,0.5); }
body.dark-mode .seg-white, body:not(.light-mode) .seg-white { fill: #7c8794; }
body.dark-mode .seg-other, body.dark-mode .seg-oth,
body:not(.light-mode) .seg-other, body:not(.light-mode) .seg-oth { fill: #5a626b; }
body.dark-mode .rl-white, body:not(.light-mode) .rl-white { background: #7c8794; }
body.dark-mode .rl-other, body:not(.light-mode) .rl-other { background: #5a626b; }
.census-card .sub {
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: 2px;
}
.census-source {
  font-size: 0.75rem;
  color: var(--muted);
  margin-top: 12px;
}

/* ---- Modals ---- */
.modal {
  isolation: isolate;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.5);
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  /* Keep the sticky header (now above modals) clear of modal content so the
     menu bar stays reachable on every screen. Also clears #postgame-ribbon when it's
     showing (fixed, so it doesn't push modal content down on its own) — --postgame-
     ribbon-h is 0px the rest of the time, so this is a no-op outside game-over. */
  padding-top: calc(var(--header-h) + var(--postgame-ribbon-h) + 16px);
}
.modal.hidden { display: none; }
.modal-content {
  background: var(--surface);
  border-radius: 12px;
  padding: 24px;
  max-width: 480px;
  width: 100%;
  /* Fill the full height below the header (and #postgame-ribbon, when showing) when
     content needs it — the .modal reserves header-h + postgame-ribbon-h + 16px on top
     and 16px below; scroll internally past that. */
  max-height: calc(100dvh - var(--header-h) - var(--postgame-ribbon-h) - 32px);
  overflow-y: auto;
  position: relative;
  box-shadow: var(--shadow-lg);
}
.modal-content h2 {
  font-size: 1.2rem;
  font-weight: 800;
  margin-bottom: 14px;
}
.modal-content h3 {
  font-size: 0.9rem;
  font-weight: 700;
  margin: 14px 0 6px;
}
.modal-content p, .modal-content li {
  font-size: 0.9rem;
  line-height: 1.6;
  color: var(--text-secondary);
}
.modal-content ul, .modal-content ol {
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.modal-close {
  position: absolute;
  top: 12px;
  right: 14px;
  background: none;
  border: none;
  font-size: 1rem;
  cursor: pointer;
  color: var(--muted);
  line-height: 1;
  padding: 4px;
}
.modal-close:hover { color: var(--text); }

/* ---- How-to modal footer ---- */
.how-to-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 20px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
}
.how-to-note {
  font-size: 0.75rem;
  color: var(--muted);
  flex: 1;
  min-width: 0;
}
.got-it-btn {
  background: var(--cmu-red);
  color: white;
  border: none;
  border-radius: var(--radius);
  padding: 8px 22px;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s;
  white-space: nowrap;
  flex-shrink: 0;
}
.got-it-btn:hover { background: var(--cmu-red-dark); }
/* ---- Push notification opt-in modal ---- */
.push-optin-icon { font-size: 2.2rem; text-align: center; margin-bottom: 4px; }
.push-optin-content h2 { text-align: center; }
.push-optin-content > div > p { color: var(--muted); }
.push-optin-actions { justify-content: center; }
.push-ios-steps {
  margin: 12px 0 0;
  padding-left: 20px;
  color: var(--text-secondary);
  line-height: 1.5;
}
.push-ios-steps li + li { margin-top: 8px; }
.push-ios-icon { color: var(--accent); }

/* Donate modal's single footer CTA — .how-to-footer's space-between only makes sense
   with two children, so center the lone button here instead. */
#donate-modal .how-to-footer { justify-content: center; }
#donate-modal p + p { margin-top: 14px; }
.donate-modal-cta { text-decoration: none; text-align: center; }
/* Decorative header graphic — the tiled globe spinner, purely for visual interest here
   (not tied to any loading state). */
.donate-modal-globe { display: flex; justify-content: center; margin-bottom: 8px; }
/* Inline text links in the modal body (e.g. the sponsorship mailto) — match the app's
   accent-red link treatment used elsewhere (census-card links) instead of browser blue. */
#donate-modal p a {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
  background: linear-gradient(currentColor, currentColor) 0 100% / 0 0.0625em no-repeat;
  padding-bottom: 0.0625em;
  transition: background-size .4s ease, color .4s ease;
}
#donate-modal p a:hover {
  color: var(--accent);
  background-size: 100% 0.0625em;
}

/* ---- Leaderboard ---- */
.scores-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  /* No max-height/overflow here: the enclosing .tab-panel already scrolls. A cap here
     clips the lower part of the panel (e.g. the 5/6/X guess-distribution rows). */
}
.lb-empty { text-align: center; color: var(--muted); font-size: 0.9rem; padding: 20px; }
/* Pure-CSS loading block: the tiled globe loader + a label. Compositor-animated, so it
   keeps moving even while the main thread is busy building the panel it precedes. */
.lb-loading {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 28px 0; color: var(--muted); font-size: 0.9rem;
}

/* ── Tiled globe loader (pure CSS) ─────────────────────────────────────────────
   Small Carnegie-red square tiles on a canted disc; each tile loops empty→solid→empty
   and per-tile animation-delays (set inline in JS, by distance from the top-right) make
   the fill sweep across like a forming globe. Used everywhere EXCEPT the welcome screen,
   which keeps the canvas TiledGlobe. Hexagonal <polygon>s (in a 0-100 viewBox, since CSS
   Grid can't do the offset-row hex tiling) are generated by globeLoader() — SVG so each
   tile can carry a real stroke (a clip-path <div> can only fake an outline via drop-shadow,
   which gets covered by whichever neighbor tile is later in DOM order). */
.globe-loader {
  --size: 96px;
  --red: #C41230;     /* Carnegie Red */
  --roll: 20deg;      /* cant / angle */
  width: var(--size); height: var(--size);
  position: relative; border-radius: 50%; overflow: hidden;
  display: inline-block; vertical-align: middle;
}
.globe-loader svg.tiles,
.globe-loader svg.shade {
  position: absolute; inset: -20%;   /* oversize so the cant never bares a corner */
  width: 140%; height: 140%; display: block; overflow: visible;
  transform: rotate(var(--roll));
  -webkit-mask: radial-gradient(circle at 50% 50%, #000 58%, transparent 90%);
          mask: radial-gradient(circle at 50% 50%, #000 58%, transparent 90%);
}
.globe-loader .tiles polygon {
  fill: var(--red); opacity: 0;
  animation: gl-fill 3.5s linear infinite;
  transform-box: fill-box; transform-origin: center;
  will-change: opacity, transform;
}
/* Grayscale hex-tile vignette layer — currently switched off (fill: none) in favor of
   the plain tile field; kept in place (rather than removed) so it can be turned back on
   by swapping the fill back to a gray, without regenerating the shade polygon markup in
   globeLoader(). */
.globe-loader svg.shade { pointer-events: none; }
.globe-loader .shade polygon { fill: none; }
body.dark-mode .globe-loader .shade polygon { fill: none; }
@media (prefers-color-scheme: dark) { body:not(.light-mode) .globe-loader .shade polygon { fill: none; } }
@keyframes gl-fill {
  0%   { opacity: 0; transform: scale(.55); }
  7%   { opacity: 1; transform: scale(1); }
  60%  { opacity: 1; transform: scale(1); }
  74%  { opacity: 0; transform: scale(.55); }
  100% { opacity: 0; transform: scale(.55); }
}
@media (prefers-reduced-motion: reduce) { .globe-loader .tiles polygon { animation-play-state: paused; } }

/* Guess-pending globe — a real clipped hex field (not a tiling <pattern>) overlaid on the
   tapped state/district-tile shape while its /guess round-trip is in flight (see
   armGuessPendingPattern() in script.js). Reuses globeLoader()'s exact gl-fill sweep so the
   "this is loading" language stays identical to the loader used elsewhere. Each hex is an
   individual <polygon>, so the scale transform resolves against its own fill-box cleanly. */
.guess-pending-group { pointer-events: none; }
.guess-pending-hex {
  fill: var(--red);
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
  animation: gl-fill 1.4s linear infinite;
}
/* Rim vignette layer — bg-colored hexes whose per-hex opacity is set inline (0 at the
   globe's center, ramping up toward the rim), giving the tile its lit-sphere falloff. */
.guess-pending-shade { fill: var(--bg); }
@media (prefers-reduced-motion: reduce) { .guess-pending-hex { animation-play-state: paused; opacity: 0.7; } }

/* ---- Personal stats ---- */
.personal-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
  margin-bottom: 18px;
  width: 100%;   /* fill the pane so cards are the same size on every tab */
}
/* The stats containers must fill the pane (the pane centres its children), so the
   stat cards + histogram are the same width on the Result, Me and Everyone tabs. */
#result-personal-stats,
#result-stats,
#personal-stats { width: 100%; }
/* Four compact stats across one row (no card chrome): value stacked over its label,
   so the whole block is a single short band instead of tall two-line cards. */
.stat-card {
  text-align: center;
  padding: 2px 0;
}
.stat-card .stat-val {
  font-size: 1.35rem;
  font-weight: 800;
  color: var(--accent);
  line-height: 1.15;
}
.stat-card .stat-label {
  font-size: 0.68rem;
  color: var(--muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 1.3;
}
.guess-dist {
  margin-top: 10px;
}
.guess-dist h4 {
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin-bottom: 8px;
}
.dist-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
  font-size: 0.85rem;
}
.dist-row .dist-bar-wrap { flex: 1; background: #e5e7eb; border-radius: 4px; height: 18px; overflow: hidden; }
.dist-row .dist-bar { background: var(--accent); height: 100%; border-radius: 4px; transition: width 0.4s ease; min-width: 2px; }
.dist-row .dist-count { min-width: 20px; text-align: right; color: var(--muted); }

/* ---- Username prompt ---- */
#username-modal .modal-content {
  max-width: 360px;
  text-align: center;
}
#username-modal h2 { margin-bottom: 8px; }
#username-modal p { margin-bottom: 16px; font-size: 0.9rem; color: var(--muted); }
#username-input {
  width: 100%;
  padding: 10px 14px;
  font-size: 0.95rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  outline: none;
  margin-bottom: 10px;
  text-align: center;
  font-weight: 600;
}
#username-input:focus { border-color: var(--accent); }
#username-submit {
  width: 100%;
  padding: 10px;
  background: var(--accent);
  color: white;
  border: none;
  border-radius: var(--radius);
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
}
#username-submit:hover { background: var(--accent-hover); }
#username-error { color: var(--red); font-size: 0.82rem; margin-top: 6px; min-height: 18px; }

/* ---- Reference panel ---- */
#map-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 5px 5px;
  box-shadow: var(--shadow);
}
#map-panel h3 {
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin-bottom: 12px;
}
.map-view-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 4px;
  padding: 0 5px;
}
.map-view-header .ref-label { margin-bottom: 0; }
.map-view-header .guess-counter { padding: 0; margin: 0; }

.ref-label {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 8px;
}
.ref-label strong { color: var(--text); }
#state-chips-hint {
  font-weight: 400;
  color: #9ca3af;
  margin-left: 4px;
}

/* State chips section — fixed height, does not grow into the map area */
#state-chips-section {
  flex-shrink: 0;
  padding: 0 5px;
}

/* State chips */
#state-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-bottom: 4px;
}
.state-chip {
  padding: 3px 7px;
  font-size: 0.75rem;
  font-weight: 700;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: #f0f4ff;
  color: var(--accent);
  cursor: pointer;
  transition: all 0.15s;
  letter-spacing: 0.03em;
}

@media (prefers-color-scheme: dark) {
  .state-chip { background: #3a3a3c; color: var(--bg); }
}
body.dark-mode .state-chip { background: #3a3a3c; color: var(--bg); }
body.light-mode .state-chip { background: #e5e5ea; color: var(--muted); }
.state-chip:hover:not(:disabled):not(.eliminated) {
  background: var(--accent);
  color: white;
  border-color: var(--accent);
}
.state-chip.selected {
  background: var(--accent);
  color: white;
  border-color: var(--accent);
}
.state-chip.eliminated {
  background: var(--surface-alt);
  color: var(--muted-border);
  border-color: var(--border);
  cursor: default;
  text-decoration: line-through;
  text-decoration-color: var(--muted);
  opacity: 0.25;
}
.state-chip:disabled {
  cursor: default;
}

/* US states reference map */
#map-view {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
#us-ref-map {
  flex: 1;
  min-height: 0;
  width: 100%;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--bg);
  margin-top: 8px;
  overflow: hidden;
  transition: opacity 0.35s ease;
}

.us-ref-map-wrap {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.map-zoom-btns {
  position: absolute;
  top: 10px;
  left: 10px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  z-index: 5;
  pointer-events: auto;
}
.mzb {
  width: 28px;
  height: 28px;
  border-radius: 6px;
  border: 1px solid rgba(255,255,255,0.25);
  background: rgba(0,0,0,0.45);
  color: #fff;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s;
}
.mzb:hover { background: rgba(0,0,0,0.65); }
/* Disabled (e.g. Fit when the view already frames the remaining candidates) */
.mzb:disabled, .mzb.is-disabled { opacity: 0.35; cursor: default; pointer-events: none; }
.mzb:disabled:hover, .mzb.is-disabled:hover { background: rgba(0,0,0,0.45); }
.mzb-fit .mzb-icon { width: 14px; height: 14px; stroke-width: 2.5; }
.mzb-go-fit .mzb-icon { width: 15px; height: 15px; stroke-width: 2; }
/* Cycling zoom button: crossfade/scale the icon when it morphs to the next level. */
.mzb-go-cycle .mzb-icon { transition: opacity 0.16s ease, transform 0.16s ease; }
.mzb-go-cycle.swapping .mzb-icon { opacity: 0; transform: scale(0.5) rotate(-40deg); }

/* Pulsing "View Results" arrow on game-over map */
.gameover-results-arrow {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 56px;
  border: none;
  border-radius: 6px 0 0 6px;
  background: var(--cmu-red);
  color: #fff;
  cursor: pointer;
  /* No backdrop-filter here: this element pulses forever (animation: infinite),
     and an animated backdrop-filter forces continuous recompositing/re-blurring
     that's expensive on mobile (iOS especially) — for no visible benefit on a
     small solid arrow. */
  animation: gra-pulse 2s ease-in-out infinite;
  pointer-events: auto;
}
.gameover-results-arrow svg {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
}
.gameover-results-arrow:hover {
  background: rgba(196,18,48,0.95);
  animation-play-state: paused;
}
@keyframes gra-pulse {
  0%, 100% { opacity: 0.7;  transform: translateY(-50%) scale(1);    }
  50%       { opacity: 1;   transform: translateY(-50%) scale(1.06); }
}

.us-ref-hint {
  position: absolute;
  bottom: 10px;
  right: 10px;
  padding: 5px 9px;
  font-size: 0.72rem;
  font-weight: 600;
  color: #fff;
  background: rgba(0, 0, 0, 0.45);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 999px;
  pointer-events: none;
  opacity: 0.9;
  transition: opacity 0.4s ease;
  letter-spacing: 0.02em;
  white-space: nowrap;
  z-index: 5;
}
.us-ref-hint.dismissed { opacity: 0; }
.us-ref-hint .hint-mobile { display: none; }
@media (max-width: 480px) {
  .us-ref-hint .hint-desktop { display: none; }
  .us-ref-hint .hint-mobile  { display: inline; }
}

/* ---- District D3 map (replaces US map once state is confirmed) ---- */
/* Absolutely positioned to overlay the full wrap. If it were a flex sibling of
   #us-ref-map, the two would split the wrap height 50/50 during the 370ms
   cross-fade — the district map would build/zoom at half height and lock in a
   too-low zoom (badly under-zoomed for tall, narrow states like NJ). */
#district-tiles {
  position: absolute;
  inset: 0;
  min-height: 0;
  transition: opacity 0.35s ease;
  overflow: hidden;
}
/* ===== GAME OVER ===== */
#gameover-modal {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.gameover-modal-content {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--bg);
}

/* Top ribbon (#postgame-ribbon) — one static, always-mounted element (see index.html)
   fixed right below the header so it's a true full-viewport-width band no matter which
   screen (game-over map or the result modal on top of it) is showing beneath it — a
   plain in-flow width:100% band would only ever be as wide as whichever container
   happened to hold it, which is what caused the two screens' ribbons to drift apart
   before this. Its content (text + action buttons) is held to the header's own content
   width via --menubar-w so it lines up with the menu bar above it, not the game/card
   width below. z-index sits above every .modal (200) but below the header (600). */
.gameover-ribbon {
  position: fixed;
  top: var(--header-h);
  left: 0;
  right: 0;
  z-index: 250;
  flex-shrink: 0;
  width: 100%;
  display: block;        /* override .banner's flex — the inner row is the flex container */
  margin: 0;
  padding: 0;
  border: none;
  border-bottom: 1px solid var(--banner-border);
  /* A real margin here would be transparent — showing whatever happens to be behind the
     ribbon at that screen position (a modal's dark scrim, if one's open), which reads as
     a stray gray gap rather than an intentional part of the design. An opaque border in
     --bg (the screen's own background color) instead reads as the ribbon being gently
     inset from the header, as if it belongs to the screen below rather than floating
     independently of it. Counted for free in _syncPostgameRibbonHeight()'s measurement
     (getBoundingClientRect() is the border box), unlike a margin would've been. */
  border-top: 5px solid var(--bg);
  border-radius: 0;
  background: var(--banner-bg);
}
.gameover-ribbon-inner {
  max-width: var(--menubar-w);
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  padding: 9px 16px;
}
/* #postgame-ribbon being fixed (above) means it can now land on top of the persistent,
   normal-flow verify-email-banner (both sit right below the header) instead of naturally
   stacking after it in document order like before — push the banner down out of the way
   on the rare occasion both are showing at once (an unverified account with a finished
   game). 0px the rest of the time, so this is a no-op outside that overlap. */
#verify-email-banner { margin-top: var(--postgame-ribbon-h); }
.gameover-ribbon-text {
  font-size: 0.95rem;
  font-weight: 600;
  min-width: 0;
  flex: 1;
}
/* On narrow screens the text has enough content to want more than the shrunk sliver
   flex-wrap otherwise leaves it (both items fit on one "row" by squeezing the text down
   to a tall narrow column instead of actually wrapping, since text can keep shrinking
   via word-wrap without ever exceeding the container). Force a clean stack instead:
   the ribbon text full-width on its own line, the action buttons in a row below it. */
@media (max-width: 480px) {
  .gameover-ribbon-inner { flex-direction: column; align-items: stretch; }
  .gameover-ribbon-text { flex: 1 1 auto; }
  .banner-actions { justify-content: flex-start; }
}

/* White card below ribbon */
.gameover-card {
  flex: 1;
  display: flex;
  flex-direction: column;
  margin: 10px;
  border-radius: 10px;
  background: var(--surface, #fff);
  overflow: hidden;
  min-height: 0;
  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
/* ---- District Profile bottom sheet (game-over) ----
   Open by default over a blurred backdrop; swipe down / chevron to minimize. */
.gameover-census {
  position: fixed;
  inset: calc(var(--header-h) + var(--postgame-ribbon-h)) 0 0 0;
  /* Below the modals (welcome splash, result, etc. are z-200+) so the splash
     sits in front of an open profile on reload; above the game-over screen. */
  z-index: 150;
  pointer-events: none;          /* only the backdrop, sheet & reopen pill are interactive */
  overflow: hidden;
}
.district-profile {
  position: absolute;
  /* Never wider than the game-over map behind it: the map fills `main`, which is
     capped at 900px (16px padding) — 1400px (20px padding) in landscape ≥600px.
     Match that width and center, so the sheet lines up with the map, not the viewport. */
  left: 0; right: 0; bottom: 0;
  width: min(100% - 32px, 868px);
  margin-inline: auto;
  /* Cap at half the viewport so the upper map — and the district spark/firework that
     plays when returning from the result modal — stays visible above the sheet. Also
     keeps the result banner + headline exposed, signalling more pages behind the sheet.
     The wrapper is pointer-events:none with no backdrop, so that exposed strip is
     still directly clickable. */
  max-height: 50vh;
  display: flex;
  flex-direction: column;
  /* Frosted glass (css.glass recipe) — translucent + blurred so the map behind
     stays visible through it. */
  background: rgba(255, 255, 255, 0.20);
  -webkit-backdrop-filter: blur(5px);
  backdrop-filter: blur(5px);
  border: 1px solid rgba(255, 255, 255, 0.30);
  border-bottom: none;
  border-radius: 18px 18px 0 0;
  box-shadow: 0 4px 30px rgba(0, 0, 0, 0.10);
  transform: translateY(101%);
  transition: transform .34s cubic-bezier(.2,.7,.2,1);
  pointer-events: auto;
  touch-action: none;
}
.gameover-census.open .district-profile { transform: translateY(0); }
/* Content fits the available height → size the sheet to its content (show everything)
   instead of the reduced default. Still capped at 50vh so it can't grow over the
   firework; taller content scrolls. JS toggles `.fits` from a measured comparison. */
.gameover-census.fits .district-profile { max-height: 50vh; }
/* Expanded: swipe the handle up (or tap it) to fill the area below the header —
   full height, but still capped to the map width (never over the menu/header). */
.gameover-census.expanded .district-profile {
  max-height: 100%;
}

.gameover-census-handle {
  flex-shrink: 0;
  display: flex;
  justify-content: center;
  padding: 9px 0 4px;
  cursor: ns-resize;   /* drag up/down to resize the sheet */
  touch-action: none;
  user-select: none;   /* mouse drag on desktop must resize, not text-select the page */
  -webkit-user-select: none;
}
.gameover-census-grip {
  width: 38px; height: 4px; border-radius: 999px;
  background: var(--muted-border, #c4c9d4);
}
.gameover-census-titlebar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 2px 8px 8px 16px;
  cursor: ns-resize;   /* drag up/down to resize the sheet */
  touch-action: none;
  user-select: none;   /* mouse drag on desktop must resize, not text-select the title/page */
  -webkit-user-select: none;
}
.gameover-census-title { font-weight: 800; font-size: 0.98rem; color: var(--text); }
.gameover-census-body {
  flex: 1 1 auto;     /* fill the remaining sheet height so it scrolls at any dragged size */
  min-height: 0;
  padding: 0 16px 18px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* Reopen pill — shown only when the sheet is minimized. */
.gameover-census-reopen {
  position: absolute;
  left: 50%; bottom: 16px;
  transform: translateX(-50%);
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 16px;
  border: 1px solid rgba(0, 0, 0, 0.28);   /* visible stroke on the light glass */
  border-radius: 999px;
  /* Frosted glass — neutral (no red tint). */
  background: rgba(255, 255, 255, 0.20);
  -webkit-backdrop-filter: blur(5px);
  backdrop-filter: blur(5px);
  color: var(--cmu-red);
  font-weight: 700; font-size: 0.82rem;
  cursor: pointer;
  box-shadow: 0 4px 30px rgba(0, 0, 0, 0.10);
  pointer-events: auto;
  opacity: 1;
  transition: opacity .2s ease, background .15s ease, color .15s ease;
}
.gameover-census-reopen:hover { background: rgba(196, 18, 48, 0.85); color: #fff; }
body.dark-mode .gameover-census-reopen { color: #fff; background: rgba(22, 22, 26, 0.34); border-color: rgba(0, 0, 0, 1); }
@media (prefers-color-scheme: dark) {
  body:not(.light-mode) .gameover-census-reopen { color: #fff; background: rgba(22, 22, 26, 0.34); border-color: rgba(0, 0, 0, 1); }
}
.gameover-census.open .gameover-census-reopen { opacity: 0; pointer-events: none; }

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

.gameover-card-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border, rgba(0,0,0,0.1));
  min-width: 0;
}
.gameover-headline {
  font-size: 0.85rem;
  font-weight: 700;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  flex: 1;
}
.gameover-headline.won { color: var(--green, #2e7d32); }
.gameover-headline.lost { color: var(--text); }
.gameover-stats {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
  font-size: 0.78rem;
  color: var(--muted);
  white-space: nowrap;
}
/* ===== Auth lock: block the game until signed in, fall back to the splash ===== */
/* Sign-in is optional — the splash always shows a secondary "Sign in" link while
   signed out (login.js sets [hidden] once a user is present). */
#welcome-signin-btn {
  display: block;
  margin: 0 auto clamp(16px, 3vh, 28px);
}
#welcome-signin-btn[hidden] { display: none; }

/* Anonymous results-screen sign-in CTA */
.result-anon-cta {
  display: flex; align-items: center; gap: 12px;
  margin: 14px 0; padding: 14px 16px;
  border: 1px solid var(--border, #d8d8e0);
  border-radius: 12px;
  background: var(--card-bg, rgba(196,18,48,0.05));
  text-align: left;
}
.result-anon-cta-text { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; min-width: 0; }
.result-anon-cta-text strong { font-size: 0.98rem; color: var(--text, #111); }
.result-anon-cta-text span { font-size: 0.82rem; color: var(--muted, #666); }
.result-anon-cta-btn {
  flex: 0 0 auto; cursor: pointer;
  padding: 10px 16px; border: none; border-radius: 999px;
  background: var(--red, #C41230); color: #fff;
  font-weight: 700; font-size: 0.88rem; white-space: nowrap;
}
.result-anon-cta-btn:hover { filter: brightness(1.08); }
@media (max-width: 420px) {
  .result-anon-cta { flex-direction: column; align-items: stretch; text-align: center; }
  .result-anon-cta-text { align-items: center; }
}

/* Game-over "new district at midnight ET" ribbon (+ optional sign-in nudge) */
.gameover-next {
  margin: 0; padding: 0;
  border: none;
  border-radius: 0;   /* flush against the card top — no rounded corners revealing the card bg */
  background: var(--card-bg, rgba(196,18,48,0.05));
  text-align: left;
}
.gameover-next-main { display: flex; flex-direction: column; gap: 1px; padding: 8px 12px; }
.gameover-next-title { font-weight: 700; font-size: 0.96rem; color: var(--text, #111); }
.gameover-next-sub { font-size: 0.84rem; color: var(--muted, #666); }
.gameover-next-sub strong { color: var(--text, #111); font-variant-numeric: tabular-nums; }
.gameover-next-cta {
  display: flex; align-items: center; gap: 10px;
  margin-top: 0; padding: 10px 12px;
  border-top: 1px solid var(--border, #e2e2ea);
}
.gameover-next-cta span { flex: 1 1 auto; font-size: 0.82rem; color: var(--muted, #666); }
.gameover-next-signin {
  flex: 0 0 auto; cursor: pointer; white-space: nowrap;
  padding: 9px 14px; border: none; border-radius: 999px;
  background: var(--red, #C41230); color: #fff; font-weight: 700; font-size: 0.84rem;
}
.gameover-next-signin:hover { filter: brightness(1.08); }
@media (max-width: 420px) {
  .gameover-next-cta { flex-direction: column; align-items: stretch; text-align: center; }
}

.login-close {
  position: absolute; top: 10px; right: 12px;
  width: 32px; height: 32px; padding: 0;
  border: none; background: none; cursor: pointer;
  font-size: 1.6rem; line-height: 1; color: var(--muted, #888);
}
.login-close:hover { color: var(--text, #111); }
.login-modal-content { position: relative; }

/* ===== Account menu (initials avatar + dropdown) ===== */
.account-wrap { position: relative; display: inline-flex; align-items: center; gap: 6px; }
.account-btn {
  width: 34px; height: 34px; border-radius: 50%;
  flex: 0 0 auto; min-width: 34px; min-height: 34px;  /* match .btn-icon, never shrink */
  border: none; cursor: pointer; padding: 0;
  background: var(--red, #C41230); color: #fff;
  font-weight: 700; font-size: 0.8rem; letter-spacing: 0.02em;
  display: inline-flex; align-items: center; justify-content: center;
}
.account-btn:hover { filter: brightness(1.08); }

/* Header "Sign in" button styling lives with .donate-btn (shared pill). */
.header-signin-btn.hidden { display: none; }
.account-menu {
  position: absolute; top: calc(100% + 8px); right: 0; left: auto;
  min-width: 210px; padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.18);
  z-index: 400;
}
.account-menu-header { padding: 8px 10px 10px; border-bottom: 1px solid var(--border, #eee); margin-bottom: 4px; }
.account-menu-name { font-weight: 700; font-size: 0.9rem; }
.account-menu-email { font-size: 0.8rem; color: var(--muted); word-break: break-all; }
.account-menu-item {
  display: block; width: 100%; text-align: left;
  padding: 9px 10px; border: none; background: none; cursor: pointer;
  font: inherit; color: var(--text, #111); border-radius: 7px;
}
.account-menu-item:hover { background: var(--surface-alt); }

/* ===== Archive: in-game unofficial badge + month headers ===== */
.archive-badge {
  /* In normal flow (a thin strip between the header and the game) so it never
     covers the district map. */
  flex: 0 0 auto;
  align-self: center;
  margin: 6px auto 0;
  max-width: calc(100vw - 24px);
  background: var(--muted, #555); color: #fff;
  font-size: 0.7rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  padding: 4px 12px; border-radius: 100px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.archive-badge.hidden { display: none; }

/* ===== Archive picker ===== */
.archive-note { font-size: 0.85rem; color: var(--muted); margin: 0 0 12px; }
.archive-list { display: flex; flex-direction: column; gap: 6px; max-height: 55vh; overflow-y: auto; }
/* On desktop the 480px-wide card (then a 720px one) still read as a small floating box
   lost against the busy game-over screen behind it — scale with the viewport instead of
   a fixed cap so it reads as genuinely large on real desktop widths, not just "less small".
   More padding throughout gives it presence without enlarging any individual piece of
   text. Three columns (up from two) puts the extra width toward showing more months at
   once instead of stretching each row wider than its content needs. The calendar
   (day-grid) view keeps its own narrower cap below so day cells stay compact squares
   instead of ballooning to match the card's full width. */
@media (orientation: landscape) and (min-width: 600px) {
  .modal-content.archive-modal-content { max-width: min(92vw, 960px); padding: 32px; }
  .archive-list:not(.archive-calendar-view) {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    align-content: start;
    gap: 10px;
    max-height: 70vh;
  }
  .archive-item { padding: 14px 18px; }
  .archive-calendar-view { max-width: 480px; margin: 0 auto; }
}
.archive-item {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; padding: 11px 14px; font: inherit; cursor: pointer;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 9px;
}
.archive-item:hover { background: var(--surface-alt); border-color: var(--muted); }
.archive-num { font-weight: 700; }
.archive-date { color: var(--muted); font-size: 0.9rem; }

/* Archive calendar — month list drills into a day grid; only days with a puzzle click. */
.archive-cal-head { display: flex; align-items: center; gap: 10px; padding: 2px 2px 10px; }
.archive-cal-back {
  border: none; background: transparent; cursor: pointer;
  color: var(--accent, #C41230); font-weight: 700; font-size: 0.9rem; padding: 4px 2px;
}
.archive-cal-title { font-weight: 800; font-size: 1rem; color: var(--text); }
.archive-cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; }
.archive-cal-wd {
  text-align: center; font-size: 0.7rem; font-weight: 700; color: var(--muted); padding-bottom: 2px;
}
.archive-cal-cell {
  aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  width: auto; padding: 0; font-size: 0.92rem; color: var(--muted);
  border: 1px solid transparent; border-radius: 8px; background: transparent;
}
.archive-cal-cell.empty { visibility: hidden; }
.archive-cal-cell.has-puzzle {
  cursor: pointer; font-weight: 700; color: var(--text);
  background: rgba(255, 255, 255, 0.75); border-color: rgba(255, 255, 255, 0.45);
}
.archive-cal-cell.has-puzzle:hover { background: rgba(255, 255, 255, 0.95); border-color: var(--muted); }
body.dark-mode .archive-cal-cell.has-puzzle { background: rgba(44, 44, 50, 0.62); border-color: rgba(255, 255, 255, 0.16); }
@media (prefers-color-scheme: dark) {
  body:not(.light-mode) .archive-cal-cell.has-puzzle { background: rgba(44, 44, 50, 0.62); border-color: rgba(255, 255, 255, 0.16); }
}

/* No scrim: like the District Profile, the panel floats over the un-dimmed screen and
   its own backdrop-filter frosts what's directly behind it. The full-screen .modal layer
   still blocks clicks to the background, so the scrim was only ever cosmetic. */
#archive-modal { background: transparent; }

/* Frosted-glass treatment loosely matching the District Profile sheet (.district-profile),
   but noticeably more opaque + blurred: that sheet mostly overlays a map, where a light
   touch of translucency is fine, while this one is a wall of text (month/puzzle-count
   list) read against a busy, colorful game-over screen — legibility needs to win out
   over seeing much of what's behind it. */
.modal-content.archive-modal-content {
  background: rgba(255, 255, 255, 0.55);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.30);
  border-radius: 18px;
  box-shadow: 0 4px 30px rgba(0, 0, 0, 0.10);
}
.archive-modal-content .archive-item {
  background: rgba(255, 255, 255, 0.75);
  border-color: rgba(255, 255, 255, 0.45);
}
.archive-modal-content .archive-item:hover {
  background: rgba(255, 255, 255, 0.92);
  border-color: var(--muted);
}
/* Dark theme: tint the glass dark so light text stays readable. */
body.dark-mode .archive-modal-content { background: rgba(22, 22, 26, 0.72); border-color: rgba(255, 255, 255, 0.14); }
body.dark-mode .archive-modal-content .archive-item { background: rgba(44, 44, 50, 0.62); border-color: rgba(255, 255, 255, 0.16); }
@media (prefers-color-scheme: dark) {
  body:not(.light-mode) .archive-modal-content { background: rgba(22, 22, 26, 0.72); border-color: rgba(255, 255, 255, 0.14); }
  body:not(.light-mode) .archive-modal-content .archive-item { background: rgba(44, 44, 50, 0.62); border-color: rgba(255, 255, 255, 0.16); }
}

/* ===== Login modal ===== */
/* Sits above the welcome splash (z-index 200) so the sign-in gate shows first. */
#login-modal { z-index: 300; }
#profile-modal { z-index: 320; }
/* Header-triggered utility modals must always supersede whatever game modal is up
   (result / welcome / game-over, all z-index 200) — and login/profile — since the menu
   bar (z-index 600) is reachable over everything. #donate-modal (the "About" modal) opens
   from the header too, so it belongs here — without it, it ties the result modal at the
   base .modal z-index (200) and loses on DOM order, opening BEHIND the result screen. */
#settings-modal, #how-to-modal, #hints-modal, #feedback-modal, #push-optin-modal, #donate-modal { z-index: 340; }
.profile-checkbox {
  display: flex; align-items: center; gap: 8px;
  font-size: 0.85rem; color: var(--muted); cursor: pointer; padding: 2px 0;
}
.profile-checkbox input { width: auto; }
.login-modal-content {
  max-width: 360px;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.login-title { margin: 0; font-size: 1.6rem; }
.login-subtitle { margin: 0; color: var(--muted); font-size: 0.95rem; }
.login-providers { display: flex; flex-direction: column; gap: 8px; }
.login-provider-btn,
.login-submit,
.login-secondary {
  font: inherit;
  font-weight: 600;
  border-radius: 10px;
  padding: 11px 14px;
  cursor: pointer;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}
.login-provider-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.login-provider-icon { flex: 0 0 auto; }
.login-provider-btn:hover { background: var(--surface-alt); }
.login-submit {
  background: var(--red, #C41230);
  border-color: var(--red, #C41230);
  color: #fff;
}
.login-secondary { background: transparent; border: none; color: var(--muted); }
.login-help {
  display: flex; justify-content: center; gap: 10px; margin-top: 2px;
}
.login-link {
  background: none; border: none; padding: 4px 2px; cursor: pointer;
  color: var(--muted); font-size: 0.8rem; text-decoration: underline;
}
.login-link:hover { color: var(--text, inherit); }
.login-divider {
  display: flex; align-items: center; gap: 10px;
  color: var(--muted); font-size: 0.8rem;
}
.login-divider::before, .login-divider::after {
  content: ''; flex: 1; height: 1px; background: var(--border, #e3e3e3);
}
/* Sign-in in progress: the clicked button (submit or an OAuth provider) shows a spinner
   in place of its label, every other login action is hidden entirely (not just disabled)
   so there's nothing left to double-click while the network round-trip is in flight. */
.login-btn-spinner {
  display: inline-block;
  width: 15px; height: 15px;
  border: 2px solid rgba(255,255,255,0.4);
  border-top-color: #fff;
  border-radius: 50%;
  vertical-align: -3px;
  margin-right: 8px;
  animation: login-spin 0.7s linear infinite;
}
.login-provider-btn .login-btn-spinner { border-color: var(--border); border-top-color: var(--text); }
@keyframes login-spin { to { transform: rotate(360deg); } }
.login-action-hidden { display: none !important; }
.login-email-form { display: flex; flex-direction: column; gap: 8px; }
.login-email-form input {
  font: inherit;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}
.login-email-form input::placeholder { color: var(--muted); }
/* Password field + its show/hide toggle — the form's flex column gives every direct
   child (inputs, this wrapper) the same width, so the wrapper just needs to size its
   input the same way `.login-email-form input` normally would on its own. */
.login-password-wrap { position: relative; display: flex; }
.login-password-wrap input {
  font: inherit;
  padding: 10px 38px 10px 12px;   /* extra right padding clears the toggle icon */
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  width: 100%;
}
.login-password-toggle {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  padding: 0;
  color: var(--muted);
  cursor: pointer;
}
.login-password-toggle svg { width: 18px; height: 18px; }
.login-password-toggle:hover { color: var(--text); }
/* Eye has a slash through it while the password is masked (click to reveal); the slash
   disappears once revealed, so the icon itself communicates the current state. */
.login-password-toggle::after {
  content: "";
  position: absolute;
  left: 9px; right: 9px;
  top: 50%;
  height: 1.5px;
  background: currentColor;
  transform: translateY(-50%) rotate(-45deg);
}
.login-password-toggle[aria-pressed="true"]::after { display: none; }
.login-error { color: var(--red, #C41230); font-size: 0.85rem; min-height: 1em; }
.login-error.login-success { color: var(--green, #2e7d32); }

/* ---- Profile danger zone (delete account) ---- */
.profile-danger {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  text-align: center;
}
.profile-danger.hidden { display: none; }
.profile-delete-btn {
  background: transparent;
  border: none;
  color: var(--red, #C41230);
  font-size: 0.85rem;
  font-weight: 600;
  text-decoration: underline;
  cursor: pointer;
  padding: 4px 8px;
}
.profile-delete-confirm.hidden,
.profile-delete-btn.hidden { display: none; }
.profile-delete-confirm p {
  font-size: 0.84rem;
  line-height: 1.45;
  color: var(--text-secondary, var(--text));
  margin: 0 0 12px;
  text-align: left;
}
.profile-delete-actions {
  display: flex;
  gap: 8px;
  justify-content: center;
  flex-wrap: wrap;
}
.profile-delete-confirm-btn {
  background: var(--red, #C41230);
  color: #fff;
  border: none;
  border-radius: var(--radius);
  padding: 9px 16px;
  font-size: 0.88rem;
  font-weight: 700;
  cursor: pointer;
}
.profile-delete-confirm-btn:disabled { opacity: 0.6; cursor: default; }
.login-privacy {
  font-size: 0.72rem; line-height: 1.4; color: var(--muted);
  text-align: center; margin: 12px 0 0;
}
.login-privacy a { color: var(--muted); text-decoration: underline; }

.gameover-grid { letter-spacing: 0.12em; }
.gameover-grid .go-slot-state {
  display: inline-flex;
  align-items: center;
  vertical-align: middle;
  width: 1.3em;
  height: 1.3em;
}

/* State boundary SVG — the displayed size is ALWAYS set by CSS via the slot the
   SVG sits in, never the SVG's own intrinsic dimensions. The container and the
   svg simply fill that slot. */
.state-svg-container {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
.state-svg-container svg {
  display: block;
  width: 100%;
  height: 100%;
}
.state-svg-container svg path {
  stroke: currentColor !important;
  fill: none !important;
}
.gameover-solved-label { font-weight: 600; color: var(--green, #2e7d32); }
.gameover-time-wrap {
  display: flex;
  align-items: center;
  gap: 3px;
}
.gameover-time-wrap svg { opacity: 0.65; flex-shrink: 0; }

/* Map fills card */
#gameover-map-wrap {
  flex: 1;
  position: relative;
  overflow: hidden;
  min-height: 0;
}
#gameover-map {
  position: absolute;
  inset: 0;
  background: var(--bg);
}

/* "New Map" outlined */
#gameover-new-map-btn {
  background: transparent;
  color: var(--accent);
  border: 1.5px solid var(--accent);
}
#gameover-new-map-btn:hover { background: var(--accent); color: white; }


/* ---- Welcome / splash modal ---- */
/* Solid backdrop (matches the splash card's own page background) while the game behind
   it is still loading — there's nothing meaningful back there yet, so a blurred/dimmed
   peek just reads as a flash of broken UI. Once the map is actually built (loader
   dropped, .bg-ready added by buildWelcomeButtons()), cross-fade to frosted glass so the
   game is visible-but-blurred around the centered card on desktop. */
#welcome-modal {
  background: var(--bg);
  transition: background-color .5s ease, backdrop-filter .5s ease;
}
#welcome-modal.bg-ready {
  background: rgba(18, 18, 24, 0.42);
  backdrop-filter: blur(12px) saturate(118%);
  -webkit-backdrop-filter: blur(12px) saturate(118%);
}

.welcome-modal-content {
  max-width: 480px;
  width: 92vw;
  height: min(94dvh, 760px);
  text-align: center;
  padding: clamp(20px, 4vh, 40px) 32px clamp(16px, 3vh, 28px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  /* Content (logo, buttons, meta, donate) can exceed the height on short windows — let
     it scroll instead of clipping. The flex spacers still center it when it fits. */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.welcome-top-spacer { flex: 1.2; }
.welcome-logo-wrap { margin-bottom: clamp(8px, 1.5vh, 16px); }
.welcome-logo-svg { width: clamp(80px, 14vh, 140px); height: auto; }
.welcome-wordmark {
  margin-bottom: clamp(14px, 2.5vh, 24px);
  line-height: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(10px, 1.8vh, 18px);
}
.welcome-beta-badge {
  padding: 4px 10px;
  border-radius: 7px;
}
.welcome-beta-badge .beta-label,
.welcome-beta-badge .beta-version {
  font-size: 0.75rem;
}
.welcome-wordmark-svg {
  height: clamp(28px, 5vh, 44px);
  width: auto;
  display: block;
  margin: 0 auto;
}
@media (prefers-color-scheme: dark) {
  .welcome-wordmark-svg { filter: brightness(0) invert(1); }
}
body.dark-mode .welcome-wordmark-svg { filter: brightness(0) invert(1); }
body.light-mode .welcome-wordmark-svg { filter: none; }
.welcome-back-text {
  font-family: 'Barlow Condensed', 'Barlow', system-ui, sans-serif;
  font-size: clamp(1.1rem, 3vh, 1.6rem);
  font-weight: 500;
  letter-spacing: -0.012em;
  color: var(--text);
  text-align: center;
}
.welcome-back-text[hidden] { display: none; }
.welcome-slogan {
  font-size: clamp(0.95rem, 2.2vh, 1.3rem);
  font-weight: 400;
  color: var(--muted);
  line-height: 1.3;
  margin-bottom: clamp(12px, 2vh, 20px);
}
.welcome-mid-spacer { flex: 1; }
.welcome-buttons {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin-bottom: clamp(16px, 3vh, 28px);
}
.welcome-meta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  margin-bottom: 0;
}
/* min-height (not just font-size) so these reserve their line's space even before JS
   fills them in (date-line: inline script right after parse; puzzle-num: after the
   `today` fetch resolves) — otherwise the empty div collapses to 0 height and everything
   below it (editor credit, donate button) jumps up then back down as they populate. */
.welcome-date-line {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--text);
  min-height: 1.2em;
}
.welcome-puzzle-num {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--muted);
  min-height: 1.2em;
}
.welcome-editor {
  font-size: 0.8rem;
  color: var(--muted);
  margin-top: 2px;
}
.welcome-action-btn {
  width: 60%;
  min-width: 160px;
  max-width: 200px;
  padding: 14px 0;
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  border-radius: 100px;
  background: var(--accent);
  color: #fff;
  border: none;
  cursor: pointer;
}
/* Hover handled by the slide-up fill (::before). */
.welcome-action-btn.secondary {
  background: transparent;
  color: var(--text);
  border: 2px solid var(--border);
}
.welcome-action-btn.secondary:hover { border-color: var(--text); }
.welcome-loading-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}
/* Full-screen build loader (archive fetch + map build) — covers the screen with the
   app background and centers the welcome globe + "Loading..." text. */
.build-loader {
  position: fixed;
  inset: 0;
  z-index: 1500;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg, #fff);
}
.welcome-loading-text {
  font-size: 0.9rem;
  color: var(--muted);
  font-weight: 500;
  letter-spacing: 0.05em;
}
.welcome-loading-text span {
  display: inline-block;
  animation: loading-hop 1.1s ease-in-out infinite;
  animation-delay: calc(var(--i) * 0.07s);
  will-change: transform;
}
@keyframes loading-hop {
  0%, 70%, 100% { transform: translateY(0); }
  35%           { transform: translateY(-0.45em); }
}
@media (prefers-reduced-motion: reduce) {
  .welcome-loading-text span { animation: none; }
}
.welcome-donate-spacer { flex: 1; min-height: 8px; }
.welcome-how-to-link {
  background: none;
  border: none;
  padding: 0;
  font-size: 0.8rem;
  color: var(--muted);
  text-decoration: underline;
  cursor: pointer;
  margin-bottom: 10px;
}
.welcome-how-to-link:hover { color: var(--text); }

/* Now a <button> (opens the About modal) rather than an <a> straight out to GiveCampus —
   explicit background/font/cursor since buttons don't inherit those like anchors did, and
   this deliberately sits outside the global swipe-underline `a` rule, which was fighting
   this pill's own centering (that rule's background/padding-bottom is meant for inline text
   links, not a bordered pill button). */
.welcome-donate-btn {
  display: block;
  width: 60%;
  min-width: 180px;
  padding: 10px;
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  text-align: center;
  border-radius: 100px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  text-decoration: none;
  cursor: pointer;
  box-sizing: border-box;
  margin-top: 0;
}
.welcome-donate-btn:hover { color: var(--text); border-color: var(--text); }

/* ---- Settings modal ---- */
.settings-modal-content {
  max-width: 440px;
  width: 92vw;
  padding: 0;
  overflow: hidden;
}
.settings-header {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}
.settings-title {
  font-size: 0.85rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  color: var(--text);
}
.settings-close-btn {
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1.2rem;
  color: var(--muted);
  padding: 4px 8px;
  line-height: 1;
}
.settings-close-btn:hover { color: var(--text); }
.settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 20px;
}
.settings-row-name {
  font-size: 1rem;
  font-weight: 500;
  color: var(--text);
}
.settings-divider { height: 1px; background: var(--border); margin: 0 20px; }
/* Toggle switch */
.settings-toggle-wrap { position: relative; cursor: pointer; }
.settings-toggle-wrap.disabled { cursor: not-allowed; opacity: 0.5; }
.settings-toggle-input { position: absolute; opacity: 0; width: 0; height: 0; }
.settings-toggle-track {
  display: block;
  width: 48px;
  height: 28px;
  border-radius: 14px;
  background: var(--border);
  transition: background 0.2s;
  position: relative;
}
.settings-toggle-input:checked + .settings-toggle-track { background: #6aaa64; }
.settings-toggle-thumb {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #fff;
  transition: transform 0.2s;
  box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}
.settings-toggle-input:checked + .settings-toggle-track .settings-toggle-thumb { transform: translateX(20px); }
.settings-link-btn {
  display: block;
  width: 100%;
  padding: 14px 20px;
  background: none;
  border: none;
  cursor: pointer;
  font-size: 0.88rem;
  color: var(--muted);
  text-align: left;
}
.settings-link-btn:hover { color: var(--text); }
.settings-footer {
  padding: 16px 20px;
  font-size: 0.75rem;
  color: var(--muted);
  border-top: 1px solid var(--border);
  margin-top: 8px;
}
.settings-version { display: inline-block; margin-top: 4px; opacity: 0.75; font-size: 0.7rem; }

.settings-sub-link {
  display: inline-block;
  background: none;
  border: none;
  cursor: pointer;
  font-size: 0.75rem;
  color: var(--muted);
  text-decoration: underline;
  padding: 0;
  margin-top: 3px;
  text-align: left;
}
.settings-sub-link:hover { color: var(--text); }

/* ============================================================
   CMU BUTTON + LINK STYLES  (cmu.edu cmu_base design system)
   - Red CTAs (.Button--alt): a red arrow box on the left expands to a
     full red fill on hover; the label flips to white.
   - Clear button (.Button): outlined; the theme colour fills up from the
     bottom on hover and the label flips to white.
   - Links (a:not(.Button)): carnegie-red with an underline that thickens
     1px → 2px on hover.
   ============================================================ */

/* ---- Red "alt" CTAs: Play / login Sign in ---- */
.login-submit,
.welcome-action-btn:not(.secondary) {
  --arrow-w: 40px;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: clip;
  color: var(--text);                   /* theme-aware label at rest; white on hover */
  border: 1px solid var(--cmu-red);
  background-color: transparent;
  background-image: linear-gradient(var(--cmu-red), var(--cmu-red));
  background-repeat: no-repeat;
  background-position: 0 0;
  background-size: var(--arrow-w) 100%; /* red strip only over the arrow box… */
  padding-left: calc(var(--arrow-w) + 1rem);
  padding-right: calc(var(--arrow-w) + 1rem);  /* keep the label visually centred */
  transition: background-size .4s ease, color .4s ease;
}
.login-submit::before,
.welcome-action-btn:not(.secondary)::before {
  content: "";
  position: absolute; top: 0; left: 0;
  width: var(--arrow-w); height: 100%;
  background-color: #fff;
  -webkit-mask: var(--cmu-arrow) center / 42% no-repeat;
  mask: var(--cmu-arrow) center / 42% no-repeat;
  pointer-events: none;
}
.login-submit:hover,
.welcome-action-btn:not(.secondary):hover {
  color: #fff;
  background-size: 100% 100%;           /* …red sweeps across to fill on hover */
}

/* ---- Clear button: header "Sign in" pill (fill slides up from the bottom) ---- */
/* ---- Text links: carnegie-red with an underline that swipes in left→right
   on hover (jonathancervas.com / cmu_base ".link-underline-swipe"). The
   underline is a currentColor gradient grown from 0 to full width.
   Base rule catches every plain <a> in the app (privacy policy link, About
   modal attribution links, the push-blocked-notice link, the game-over
   reload link, etc.) so new links get this treatment automatically instead
   of needing a selector added here each time; button-styled anchors
   (.got-it-btn, .donate-modal-cta) opt out. ---- */
a:not(.got-it-btn):not(.donate-modal-cta) {
  color: var(--cmu-red);
  font-weight: 600;
  text-decoration: none;
  background: linear-gradient(currentColor, currentColor) 0 100% / 0 0.0625em no-repeat;
  padding-bottom: 0.0625em;
  transition: background-size .4s ease, color .4s ease;
}
a:not(.got-it-btn):not(.donate-modal-cta):hover {
  background-size: 100% 0.0625em;
}
.login-link,
.welcome-how-to-link,
.settings-link-btn span,
.settings-sub-link {
  color: var(--cmu-red);
  font-weight: 600;
  text-decoration: none;
  background: linear-gradient(currentColor, currentColor) 0 100% / 0 0.0625em no-repeat;
  padding-bottom: 0.0625em;
  transition: background-size .4s ease, color .4s ease;
}
.login-link:hover,
.welcome-how-to-link:hover,
.settings-link-btn:hover span,
.settings-sub-link:hover {
  color: var(--cmu-red);
  background-size: 100% 0.0625em;
}

/* ---- Secondary welcome button (e.g. "Review Result"): outlined, with the fill
   sliding up from the bottom on hover (the clear ".Button" behaviour). ---- */
.welcome-action-btn.secondary {
  position: relative;
  z-index: 0;
  overflow: clip;
}
.welcome-action-btn.secondary::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: -1;
  border-radius: inherit;
  background: var(--text);
  transform: translateY(100%);
  transition: transform .4s ease;
  pointer-events: none;
}
.welcome-action-btn.secondary:hover { color: var(--bg); }
.welcome-action-btn.secondary:hover::before { transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  .login-submit, .welcome-action-btn:not(.secondary),
  .login-link, .welcome-how-to-link, .settings-link-btn, .settings-sub-link,
  .welcome-action-btn.secondary::before { transition: none; }
}

/* ---- Confirm-selection pending indicator ---- */
#confirm-hint {
  display: none;
  text-align: center;
  font-size: 0.82rem;
  font-weight: 600;
  color: #FDB515;
  background: rgba(253,181,21,0.12);
  border: 1px solid rgba(253,181,21,0.4);
  border-radius: var(--radius);
  padding: 6px 12px;
  margin: 4px 5px 0;
  pointer-events: none;
}
#confirm-hint.visible { display: block; }

/* Settings rows with description */
.settings-row-desc {
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: 2px;
}
.settings-row-label { flex: 1; }
.settings-row.disabled { opacity: 0.45; pointer-events: none; }

/* Feedback form structured questions */
.fb-rating-group { display: flex; gap: 6px; margin-top: 6px; }
.fb-rating-group input[type="radio"] { display: none; }
.fb-star-label {
  font-size: 1.4rem;
  cursor: pointer;
  color: var(--border);
  transition: color 0.1s;
  line-height: 1;
}
.fb-star-label.selected,
.fb-star-label.hovered { color: #FDB515; }

/* ---- Result modal close button ---- */
.result-modal-header {
  display: flex;
  justify-content: flex-end;   /* "Back to district X" sits top-right */
  padding: 10px 12px 0;
  flex-shrink: 0;
}
/* Back button overlays the district preview's top-right corner (no border, saves a row).
   A translucent pill keeps it legible over the map imagery. */
.result-modal-close {
  position: absolute;
  top: 20px; right: 20px;
  z-index: 3;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 4px 10px;
  border: none;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.45);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  cursor: pointer;
  color: #fff;
}
/* Keep the text white on hover — the base .modal-close:hover sets color:var(--text),
   which turns it black on the dark pill in light mode. */
.result-modal-close:hover { background: rgba(0, 0, 0, 0.65); color: #fff; }
/* "View District Profile" (footer) needs .modal-close for the shared close-button click
   handler, but that generic class is otherwise built for the corner-X close button on
   every OTHER modal (position:absolute, top-right corner, transparent/no border) — applied
   as-is here it floated the button out of the footer entirely and over the ribbon above.
   Reset it back to the plain .donate-btn/.result-donate-btn pill look/position it actually
   needs. */
#result-view-profile-btn {
  position: static;
  top: auto; right: auto;
  background: transparent;
  border: 2px solid var(--cmu-red);
  color: var(--cmu-red);
  font-size: 0.78rem;
  padding: 7px 16px;
  /* This is the only visible hint that the game-over map + District Profile sheet are
     sitting right behind the result modal (mobile especially — the modal covers the
     full screen there, so there's no glimpse of it like the gutters give on desktop).
     A slow, continuous glow keeps drawing the eye back to it for as long as the modal
     stays open, the same way .gameover-results-arrow pulses forever rather than just
     flashing once on arrival. */
  animation: result-profile-glow 2.2s ease-in-out infinite;
}
#result-view-profile-btn:hover {
  background: var(--cmu-red);
  color: #fff;
  animation-play-state: paused;
  box-shadow: none;
}
@keyframes result-profile-glow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(196, 18, 48, 0.45); }
  50%      { box-shadow: 0 0 14px 4px rgba(196, 18, 48, 0.45); }
}
@media (prefers-reduced-motion: reduce) {
  #result-view-profile-btn { animation: none; }
}
/* Ad placeholder — only shown to not-signed-in players (toggled in JS). */
.result-ad {
  /* Fill the leftover space in the result pane (the pane centers its children, so
     align-self:stretch is needed for full width). */
  flex: 1 1 auto;
  align-self: stretch;
  width: 100%;
  min-height: 120px;
  margin: 8px 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.result-ad ins.adsbygoogle { width: 100%; }

/* ---- Desktop side ad rails ---- */
.side-ad {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  /* Above the modal scrims (result/About/etc., 200–340) so the ads stay bright and
     clickable in the gutters while a modal dims the game behind it — the ads sit ON the
     scrim rather than the scrim having to be narrowed to dodge them. Still below the
     sticky header (600). They only render in the gutters (fixed at 50%±726px), so they
     never overlap the centered modal content despite the higher z-index. */
  z-index: 400;
  width: 160px;
  /* Height tracks the served ad, but min-height keeps a 600px box (so the placeholder
     still shows if the ad collapses/hasn't filled). translateY(-50%) centers it. */
  height: auto;
  min-height: 600px;
  max-height: calc(100dvh - var(--header-h) - 24px);
  display: none;               /* shown only on wide desktop via the media query below */
  overflow: hidden;
}
/* Pin each rail just outside the centered 1100px game column (half = 550, + 160 rail +
   16 gap = 726), so they stay game-adjacent at any width rather than hugging the screen. */
.side-ad-left  { left:  calc(50% - 726px); }
.side-ad-right { right: calc(50% - 726px); }

/* Placeholder: a dashed "AD" box that reserves the rail space while no real ad has filled
   (new units serve blank for a while). It hides itself the moment AdSense reports a fill. */
.side-ad-ph {
  position: absolute;          /* overlay the ad's box rather than stacking below it (which
                                  would push the placeholder into the bottom half) */
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--border);
  border-radius: 8px;
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.side-ad:has(ins[data-ad-status="filled"]) .side-ad-ph { display: none; }

/* Show the rails once the gutter beside the 1100px game column can hold a 160px rail
   without overlap (~1100 + 2×(160 + 16) ≈ 1460; 1500 leaves a little breathing room). */
@media (min-width: 1500px) {
  .side-ad { display: block; }
}

/* ---- Utilities ---- */
.hidden { display: none !important; }

/* ============================================================
   RESPONSIVE LAYOUT — always stacked (map top 40%, ref bottom 60%)
   ============================================================ */

/* ---- Rotate overlay (mobile landscape only) ---- */
#rotate-overlay {
  display: none;
}
@media (orientation: landscape) and (max-height: 500px) {
  #rotate-overlay {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 9999;
    background: var(--bg);
    align-items: center;
    justify-content: center;
  }
  .rotate-overlay-inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 20px;
    color: var(--muted);
  }
  .rotate-overlay-inner p {
    font-size: 1rem;
    font-weight: 600;
    letter-spacing: 0.02em;
  }
}

@media (orientation: landscape) and (min-width: 600px) {
  /* 1100px is plenty for the map + panels (the game scales all the way down to mobile);
     keeping it narrower leaves real gutter room for the desktop ad rails. */
  main { max-width: 1100px; padding: 10px 20px; padding-top: calc(10px + var(--postgame-ribbon-h)); }
  /* Keep the District Profile sheet matched to the map width at this breakpoint. */
  .district-profile { width: min(100% - 40px, 1060px); }
}

/* ============================================================
   MOBILE LANDSCAPE — side-by-side layout, vertical hints
   Targets phones (max-height ≤ 500px landscape); excludes iPads
   ============================================================ */
@media (orientation: landscape) and (max-height: 500px) {
  /* Compact header */
  .header-inner { height: 44px; }

  main { padding: 4px 6px; gap: 0; padding-top: calc(4px + var(--postgame-ribbon-h)); }

  /* 3-column grid:
       narrow left  = district map (Leaflet)
       thin middle  = hints strip
       wide right   = reference panel (US map / district tiles — clickable)  */
  #game-section {
    display: grid;
    grid-template-columns: 28% auto 1fr;
    grid-template-rows: 1fr;
    grid-template-areas: "map controls reference";
    height: 100%;   /* fill main (already sized to viewport minus the header) */
    gap: 4px;
    overflow: hidden;
  }

  #map {
    grid-area: map;
    flex: unset;
    height: 100%;
    min-height: 0;
  }

  #game-controls {
    grid-area: controls;
    /* Reserve the hint-row height (cards 52 + hint-bar padding 8 + this padding 4). */
    min-height: 64px;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    gap: 4px;
    padding: 2px 0;
    width: auto;
  }

  #map-panel {
    grid-area: reference;
    flex: unset;
    min-height: 0;
    overflow: hidden;
  }


  /* ---- Hints: vertical strip, icon-only cards ---- */
  #hint-bar {
    flex-direction: column;
    overflow-x: hidden;
    overflow-y: auto;
    padding: 0;
    gap: 3px;
    flex: 1;
    min-height: 0;
  }

  /* Each card: narrow pill showing just the icon */
  .hint-card {
    flex: 0 0 auto;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    /* Keep the locked/revealed heights matched on mobile too (see base rule) so the
       hint row doesn't grow when the first clue reveals. */
    min-height: 52px;
    padding: 5px 6px;
    gap: 2px;
    width: 38px;
  }

  /* Override small-screen collapsed styles */
  .hint-card.locked,
  .hint-card.revealed:not(.latest):not(.expanded) {
    flex: 0 0 auto;
    min-width: 0;
    width: 38px;
    overflow: hidden;
    padding: 5px 6px;
  }

  /* Hide label and value — icon only in the strip */
  .hint-card-label,
  .hint-card-val { display: none !important; }

  /* Guess counter fits below the hint strip */
  .guess-counter { flex-shrink: 0; }
  .gc-label { display: none; }

  /* State chips: hide to save space */
  #state-chips-section { display: none; }

  /* Result modal: side-by-side layout in landscape */
  #result-modal {
    align-items: flex-start;
    padding: 0;
    padding-top: calc(var(--header-h) + var(--postgame-ribbon-h));
  }
  #result-modal .result-modal-content {
    display: grid;
    grid-template-columns: 38vw 1fr;
    grid-template-rows: auto auto 1fr;
    grid-template-areas:
      "preview header"
      "preview tabs"
      "preview content";
    width: 100vw;
    max-width: 100vw;
    height: calc(100dvh - var(--header-h) - var(--postgame-ribbon-h));
    max-height: calc(100dvh - var(--header-h) - var(--postgame-ribbon-h));
    border-radius: 0;
  }
  #result-district-preview {
    grid-area: preview;
    flex: unset;
    height: calc(100dvh - var(--header-h) - var(--postgame-ribbon-h));
    max-height: calc(100dvh - var(--header-h) - var(--postgame-ribbon-h));
    min-height: 0;
  }
  .result-modal-header  { grid-area: header; }
  .result-modal-tabs    { grid-area: tabs; padding: 8px 12px 0; }
  /* The wrapper (not the pane) owns the "content" grid cell now, since the shared footer
     is a sibling of the panes inside it — the pane itself just scrolls within that cell. */
  .result-tabs-content { grid-area: content; min-height: 0; overflow-y: auto; }
  .result-tab-pane.active { padding: 10px 14px; }
  .result-modal-footer { padding: 0 14px 12px; }
}

/* --- Small screens (narrow phones, etc.) --- */
@media (max-width: 480px) {
  /* Header shrinks to 46px here — keep the modal offset var in sync. */
  :root { --header-h: 46px; }
  /* ── Header: move timer inline, keep donate compact ── */
  #timer-display { display: none; }        /* timer moves inline with guess dots */
  .header-inner h1 { font-size: 1rem; gap: 5px; max-width: 60vw; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
  .header-inner { height: 46px; padding: 0 8px; }
  /* Collapse the *header* Sign in + Donate pills to icon circles to free up
     header space — but NOT the full-width Donate button inside the result modal. */
  .donate-btn:not(.result-donate-btn),
  .header-signin-btn {
    width: 34px;
    min-width: 34px;
    padding: 0;
    gap: 0;
    border-radius: 50%;
  }
  .donate-btn:not(.result-donate-btn) .hdr-btn-label,
  .header-signin-btn .hdr-btn-label { display: none; }
  .donate-btn:not(.result-donate-btn) .hdr-btn-icon,
  .header-signin-btn .hdr-btn-icon { display: block; }
  /* Progressive hiding: hide header beta badge on narrow screens, but keep it on welcome modal */
  .header-inner .beta-badge { display: none; }
  .game-logo { display: none; }   /* hide the US-map logo on mobile — saves header space */
  /* Compact the icon row so the centered wordmark keeps room beside the groups. */
  .header-left { gap: 2px; }
  .btn-icon { width: 32px; height: 32px; }
  @media (max-width: 300px) { .header-wordmark { display: none; } }

  /* ── Game layout: fit in one screen ── */
  main { padding: 6px 8px; padding-top: calc(6px + var(--postgame-ribbon-h)); }

  /* Suppress Leaflet attribution on mobile to save space */
  .leaflet-control-attribution { display: none !important; }

  /* Show inline timer next to guess dots */
  .timer-inline { display: flex; }

  /* Reference panel: keep state map, drop chips */
  #state-chips-section { display: none; }
  #map-panel h3 { display: none; }
  #map-panel { padding: 6px 8px; gap: 4px; }

  /* Game-over banner: sticky below header so it's always reachable */
  #already-played-banner:not(.hidden) {
    position: static;
    z-index: 90;
    border-radius: 0;
    border-left: none;
    border-right: none;
    margin: 0 -8px;
  }

  /* Clues panel: compact */
  #clues-panel { padding: 4px 8px; }
  #clues-panel h3 { display: none; }
  .clue-item.revealed {
    padding: 5px 10px;
    gap: 6px;
    border-radius: 6px;
  }
  .clue-item.revealed .clue-label { font-size: 0.62rem; margin-bottom: 0; }
  .clue-item.revealed .clue-text  { font-size: 0.82rem; line-height: 1.2; }
  .clue-icon-svg { width: 14px; height: 14px; }
  #clues-list { gap: 3px; }

  /* Guess counter: smaller dots, no wasted space */
  .guess-counter { padding: 0 0 4px; gap: 5px; }
  .gc-dot { width: 9px; height: 9px; }
  .gc-icon-svg { width: 9px; height: 9px; }
  .gc-label { font-size: 0.7rem; }

  /* Guess section: compact */
  /* Modals */
  .modal-content { padding: 18px; }
  .census-grid { grid-template-columns: 1fr 1fr; }
  /* .personal-grid keeps its 4-across layout on mobile (compact single band) */

  /* Welcome + Result modals take full viewport on mobile, minus the header strip (and
     #postgame-ribbon, when it's showing underneath either of them) so the menu bar
     (leaderboard / settings / account) stays reachable. */
  #welcome-modal,
  #result-modal {
    align-items: flex-start;
    padding: 0;
    padding-top: calc(var(--header-h) + var(--postgame-ribbon-h));
  }
  #welcome-modal .welcome-modal-content,
  #result-modal .result-modal-content {
    width: 100vw;
    max-width: 100vw;
    max-height: calc(100dvh - var(--header-h) - var(--postgame-ribbon-h));
    height: calc(100dvh - var(--header-h) - var(--postgame-ribbon-h));
    border-radius: 0;
    margin: 0;
  }
  #result-district-preview {
    flex: 0 0 clamp(120px, 30dvh, 200px);
  }
  .result-tab-pane {
    padding: 12px 16px;
  }
  .result-modal-footer {
    padding: 0 16px 12px;
  }
  .result-modal-tabs {
    padding: 8px 10px 0;
    gap: 0;
  }
  .result-tab-btn { padding: 8px 9px; font-size: 0.84rem; }
}

/* ============================================================
   FEEDBACK FORM
   ============================================================ */
.form-group {
  margin-bottom: 14px;
}
.form-group label {
  display: block;
  font-size: 0.85rem;
  font-weight: 600;
  margin-bottom: 5px;
  color: var(--text);
}
.form-group input,
.form-group textarea {
  width: 100%;
  padding: 8px 10px;
  border: 1.5px solid var(--border);
  border-radius: 6px;
  background: var(--surface-alt);
  color: var(--text);
  font-size: 0.92rem;
  font-family: inherit;
  transition: border-color 0.15s;
}
.form-group input:focus,
.form-group textarea:focus {
  outline: none;
  border-color: var(--accent);
}
.form-group textarea {
  resize: vertical;
  min-height: 80px;
}
.form-group select {
  width: 100%;
  padding: 8px 10px;
  border: 1.5px solid var(--border);
  border-radius: 6px;
  background: var(--surface-alt);
  color: var(--text);
  font-size: 0.92rem;
  font-family: inherit;
  cursor: pointer;
  transition: border-color 0.15s;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23888' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 28px;
}
.form-group select:focus { outline: none; border-color: var(--accent); }
.fb-section-label {
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 20px 0 10px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
}
.fb-scale-labels {
  display: flex;
  justify-content: space-between;
  font-size: 0.72rem;
  color: var(--muted);
  margin-top: 3px;
}
.form-optional {
  font-weight: 400;
  color: var(--muted);
  font-size: 0.8rem;
}
.form-error {
  color: var(--cmu-red);
  font-size: 0.82rem;
  margin-bottom: 8px;
  min-height: 18px;
}
.form-note {
  font-size: 0.78rem;
  color: var(--muted);
  margin-top: 12px;
  text-align: center;
}
