@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&family=Patrick+Hand&family=Inter:wght@400;500;600;700;800&display=swap');
@import 'tokens.css';

/* ===========================================================================
   Base
=========================================================================== */
* { box-sizing: border-box; }
html, body {
  margin: 0; padding: 0;
  min-height: 100vh;
  background: var(--cream);
  color: var(--ink);
  font-family: var(--font-body);
  overflow-x: hidden;
  cursor: none; /* custom cursor takes over — see js/cursor.js */
}
img { max-width: 100%; display: block; }
button, input, textarea, select { font-family: inherit; }
button { cursor: none; }
a { color: inherit; text-decoration: none; cursor: none; }
:root { --font-hand: 'Patrick Hand', cursive; }

/* ===========================================================================
   Custom cursor + particle trail (js/cursor.js)
=========================================================================== */
#cursorDot {
  position: fixed; top: 0; left: 0;
  width: 26px; height: 26px;
  pointer-events: none;
  z-index: 9999;
  transform: translate(-50%, -50%);
  will-change: transform;
  font-size: 22px;
  line-height: 1;
  filter: drop-shadow(1px 2px 1px rgba(29,53,87,0.35));
}
/* The trail is drawn on a single full-viewport canvas by js/cursor.js — no
   per-spark elements, animations or compositor layers (those were the cost).
   It only ever paints a few small sparks; the canvas itself is transparent and
   non-interactive. */
#cursorTrail {
  position: fixed; top: 0; left: 0;
  width: 100vw; height: 100vh;
  pointer-events: none;
  z-index: 9998;
}

/* "None" in the cursor picker (js/cursor.js stops the custom cursor and adds
   this class to <html>): hand the pointer back to the browser. Everything in
   this file that says `cursor: none` (and js/mascot.js's inline one) is
   overridden here with !important rather than edited one by one, so a future
   `cursor: none` added anywhere else is covered automatically. Clickables get
   the pointer hand, text fields the I-beam, everything else the arrow. */
html.system-cursor,
html.system-cursor * { cursor: auto !important; }
html.system-cursor button,
html.system-cursor a,
html.system-cursor select,
html.system-cursor label,
html.system-cursor [role="button"],
html.system-cursor .activity-card,
html.system-cursor .bingo-cell,
html.system-cursor .emoji-option,
html.system-cursor .pf-template-option,
html.system-cursor .eggable { cursor: pointer !important; }
html.system-cursor button:disabled,
html.system-cursor .cta-disabled { cursor: not-allowed !important; }
html.system-cursor input:not([type="checkbox"]):not([type="radio"]):not([type="date"]),
html.system-cursor textarea { cursor: text !important; }

/* ===========================================================================
   Ambient background layer (js/ambient.js)
=========================================================================== */
#bgLayer {
  position: fixed; inset: -60px;
  z-index: 0;
  pointer-events: none;
  background-image:
    linear-gradient(var(--sage-dark) 1.5px, transparent 1.5px),
    linear-gradient(90deg, var(--sage-dark) 1.5px, transparent 1.5px);
  background-size: 46px 46px;
  opacity: 0.35;
  transition: transform 0.15s ease-out;
}
.ambient-item {
  position: absolute;
  pointer-events: none;
  opacity: 0.8;
}
.ambient-cloud {
  width: 64px; height: 30px;
  background: var(--white);
  border-radius: 50px;
  box-shadow: -18px 6px 0 -4px var(--white), 18px 6px 0 -6px var(--white);
  opacity: 0.7;
  animation: driftX linear infinite;
}
.ambient-sparkle { animation: sparkleTwinkle ease-in-out infinite; color: var(--gold); }
.ambient-confetti { animation: confettiFloat ease-in-out infinite; }
.ambient-skid { animation: skidFade ease-in-out infinite; opacity: 0.25; }

@keyframes driftX {
  from { transform: translateX(-8vw); }
  to { transform: translateX(108vw); }
}
@keyframes sparkleTwinkle {
  0%, 100% { opacity: 0.15; transform: scale(0.8) rotate(0deg); }
  50% { opacity: 0.9; transform: scale(1.15) rotate(20deg); }
}
@keyframes confettiFloat {
  0% { transform: translateY(0) rotate(0deg); opacity: 0.5; }
  50% { transform: translateY(-18px) rotate(180deg); opacity: 0.9; }
  100% { transform: translateY(0) rotate(360deg); opacity: 0.5; }
}
@keyframes skidFade {
  0%, 100% { opacity: 0.12; }
  50% { opacity: 0.3; }
}

/* ===========================================================================
   Layout shell
=========================================================================== */
#appRoot { position: relative; z-index: 1; min-height: 100vh; }
/* `clamp(min, preferred-vw, max)` throughout this file's layout-shell rules
   (here and below) replaces what used to be a single hard breakpoint at
   1500px (see the responsive section near the bottom of this file for why
   that alone wasn't enough — it wasn't a scaling bug at all). The `vw`
   middle term means these values now shrink *continuously* as the viewport
   narrows, all the way from a large monitor down to where the 900px tablet
   tier takes over, instead of jumping between exactly two fixed sizes with
   nothing in between — so 1366px and 1440px (and anything else in that
   range) each get their own genuinely-scaled value, not whichever of two
   buckets they happened to fall into. The `max` term is also the hard
   ceiling other elements are visually checked against, and the `vw`
   component can only ever land between `min` and `max`, so none of this can
   ever produce a value larger than what already rendered safely before.
   `max-width: 1360px` for the whole canvas is untouched — it's already a
   ceiling, not a fixed width, so it was never the overflow risk here. */
.canvas-main {
  max-width: 1360px;
  margin: 0 auto;
  padding: clamp(48px, 3.4vw, 72px) clamp(24px, 2.3vw, 44px) clamp(56px, 4.6vw, 90px);
  position: relative;
}

/* Header: stacked title block, given real room to breathe before the track */
.hub-hero {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  text-align: center;
  margin-bottom: clamp(32px, 2.4vw, 48px);
}
/* Scoped size bump for the hub's own eyebrow tag — .doodle-tag's base size
   is shared with day-tags inside every activity/game modal, so it's sized
   here rather than changed globally. */
.hub-hero .doodle-tag { font-size: clamp(20px, 1.5vw, 26px); }
/* Compound selector (not just .hub-hero__title) is required here: the base
   .sketch-title rule further down this file also sets font-size at equal
   specificity (one class each), and previously won by source order —
   silently keeping this at 30px through several "increase the title" passes.
   .sketch-title.hub-hero__title outranks it outright, order notwithstanding. */
.sketch-title.hub-hero__title { display: block; font-size: clamp(28px, 2.1vw, 36px); margin: 0; }

/* ===========================================================================
   Player badge (replaces the old nav bar — just an identity chip, pinned)
=========================================================================== */
.player-badge {
  position: fixed;
  top: 22px; right: 28px;
  z-index: 60;
  display: flex; align-items: center; gap: 8px;
  background: var(--white);
  padding: 5px;
  border-radius: var(--radius-pill);
  border: 2.5px solid var(--navy);
  filter: drop-shadow(3px 4px 0 rgba(29,53,87,0.18));
}
.player-badge__avatar {
  width: 38px; height: 38px; border-radius: 50%;
  background: var(--gold); color: var(--navy);
  display: flex; align-items: center; justify-content: center;
  border: 2px solid var(--navy);
  appearance: none; padding: 0;
  transition: transform 0.15s ease;
}
.player-badge__avatar svg { width: 17px; height: 17px; }
.player-badge__avatar:hover { transform: scale(1.08); }
.player-badge__exit {
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--cream);
  border: 2px solid var(--navy);
  color: var(--navy);
  font-size: 13px;
  transition: transform 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.player-badge__exit:hover { background: var(--brick); color: var(--cream); transform: rotate(-8deg) scale(1.08); }
/* Reopens the onboarding/welcome content on demand (see
   App.openOnboardingModal) — same size/shape as the exit button next to
   it, but hovers gold rather than brick since this isn't a destructive
   action. */
.player-badge__help {
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--cream);
  border: 2px solid var(--navy);
  color: var(--navy);
  font-family: var(--font-display); font-size: 13px; font-weight: 800;
  transition: transform 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.player-badge__help:hover { background: var(--gold); transform: scale(1.08); }

/* Cursor picker popover, opened from the gear button */
.cursor-picker-panel {
  position: fixed; right: 28px; z-index: 65;
  background: var(--white);
  border: 2.5px solid var(--navy);
  border-radius: 20px 24px 18px 24px / 24px 18px 24px 20px;
  padding: 14px 16px;
  filter: drop-shadow(3px 5px 0 rgba(29,53,87,0.2));
  flex-direction: column; gap: 10px;
  min-width: 220px;
}
.cursor-picker-panel__title {
  font-family: var(--font-hand); font-size: 16px; color: var(--navy); text-align: center;
}
.cursor-picker-panel__grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
}
.cursor-picker-option {
  font-size: 22px; padding: 10px 0;
  border-radius: 14px;
  border: 2px solid var(--sage-dark);
  background: var(--cream);
  transition: transform 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}
.cursor-picker-option:hover { transform: scale(1.08); border-color: var(--gold); }
.cursor-picker-option.is-selected { background: var(--gold); border-color: var(--gold-dark); }
/* "Default cursor" — the performance escape hatch, so it leads the grid and
   spans every column instead of sitting in a cell like the emoji do. */
.cursor-picker-option--none {
  grid-column: 1 / -1;
  display: flex; align-items: center; justify-content: center; gap: 6px;
  font-size: 13px; font-weight: 800; color: var(--navy); padding: 10px 8px;
}
.cursor-picker-option--none span { font-weight: 600; color: var(--ink-soft); font-size: 11px; }

/* Developer Mode panel — day-jump + Reset + real week-date config, tucked
   under the badge. Dev-only: gated behind js/dev-mode.js's DEV_MODE flag
   AND (as of this round) an MIFN-only check, must not ship in the real-
   event build. Brick border (rather than this app's usual navy) is
   deliberate — it's the one UI element in the whole app meant to read as
   "caution, dev tool," not as part of the actual player-facing experience. */
.dev-mode-panel {
  /* Player badge sits at top:22px and is ~48px tall (5px padding + 38px
     avatar + 5px padding), i.e. its bottom edge is ~70px down — this used
     to start at 68px, overlapping it by 2px with no visible gap at all.
     84px leaves a real ~14px breathing gap between the two. */
  position: fixed; top: 84px; right: 28px; z-index: 60;
  background: var(--gold); color: var(--navy);
  border: 2.5px solid var(--brick);
  border-radius: var(--radius-md);
  padding: 8px 12px;
  font-size: 11px; font-weight: 800;
  filter: drop-shadow(3px 4px 0 rgba(29,53,87,0.18));
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  /* Widened from 230px to fit two native date inputs — see
     .dev-mode-panel__week-config below — without them clipping. */
  max-width: 270px;
}
/* Wraps the label + minimize button on one row — previously the label
   itself carried `width: 100%` to force everything below it onto its own
   line; that job now belongs to this wrapper so the minimize button can
   share the row instead of falling below it too. */
.dev-mode-panel__header {
  width: 100%;
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-bottom: 2px;
}
.dev-mode-panel__label {
  font-weight: 900; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--brick); white-space: nowrap;
}
.dev-mode-panel__minimize {
  width: 18px; height: 18px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%; line-height: 1; padding: 0;
  background: rgba(29,53,87,0.14); border: none; color: var(--navy);
  font-weight: 900; font-size: 13px;
}
/* Collapsed state — a compact pill with no day/week-config content in the
   DOM at all (see js/dev-mode.js's render) rather than that content merely
   hidden, so there's nothing left over to accidentally interact with while
   tucked away. Click anywhere on it to re-expand. */
.dev-mode-panel--minimized { padding: 6px 10px; }
.dev-mode-panel__pill {
  background: none; border: none; padding: 0;
  display: flex; align-items: center; gap: 5px;
  font-weight: 900; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--brick); font-size: 11px;
}
.dev-mode-panel__chevron { font-size: 9px; }
.dev-mode-panel button {
  background: rgba(29,53,87,0.14); border: none; border-radius: 999px;
  padding: 3px 8px; font-weight: 800; font-size: 10.5px; color: var(--navy);
}
.dev-mode-panel button.is-active { background: var(--navy); color: var(--cream); }
/* Reset stands apart from the day-selector pills (which stay exactly as
   they were) — brick-filled so it visibly reads as the destructive one. */
.dev-mode-panel button.dev-mode-panel__reset { background: var(--brick); color: var(--cream); }

/* The real week start/end date control (js/dev-mode.js) — visually set
   apart from the day-jump pills above via its own dashed divider, since
   it's a materially different, higher-stakes action (a real, global
   config write, not a per-browser preview toggle). Stacked full rows
   (column, not the parent's row-wrap) since two native date inputs side by
   side don't fit this panel's narrow width comfortably. */
.dev-mode-panel__week-config {
  width: 100%;
  display: flex; flex-direction: column; gap: 4px;
  margin-top: 4px; padding-top: 6px;
  border-top: 1.5px dashed rgba(29,53,87,0.35);
}
.dev-mode-panel__week-config-label {
  font-weight: 900; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--brick); white-space: normal; line-height: 1.3;
}
.dev-mode-panel__week-config label {
  display: flex; align-items: center; justify-content: space-between; gap: 6px;
  font-weight: 700; color: var(--navy);
}
.dev-mode-panel__week-config input[type="date"] {
  width: 132px;
  border: none; border-radius: 6px;
  padding: 2px 4px;
  font-size: 10px; font-family: inherit; color: var(--navy);
  background: var(--white);
}
.dev-mode-panel__week-config button {
  align-self: flex-end;
  background: var(--navy); color: var(--cream);
}

/* Manage Admins + Download Results — same dashed-divider-separated-section
   pattern as .dev-mode-panel__week-config above, since both are also
   higher-stakes/global actions rather than per-browser preview toggles. */
.dev-mode-panel__admins {
  width: 100%;
  display: flex; flex-direction: column; gap: 4px;
  margin-top: 4px; padding-top: 6px;
  border-top: 1.5px dashed rgba(29,53,87,0.35);
}
.dev-mode-panel__admins-list { display: flex; flex-wrap: wrap; gap: 4px; }
.dev-mode-panel__admin-chip {
  display: inline-flex; align-items: center; gap: 4px;
  background: var(--navy); color: var(--cream);
  border-radius: 999px; padding: 2px 4px 2px 8px;
  font-size: 10.5px; font-weight: 800;
}
.dev-mode-panel__admin-chip button {
  background: rgba(255,255,255,0.22); color: var(--cream);
  width: 15px; height: 15px; border-radius: 50%; padding: 0;
  display: flex; align-items: center; justify-content: center; font-size: 9px;
}
.dev-mode-panel__admins-add { display: flex; gap: 4px; margin-top: 2px; }
.dev-mode-panel__admins-add input {
  width: 60px; border: none; border-radius: 6px; padding: 2px 4px;
  font-size: 10px; font-family: inherit; color: var(--navy); background: var(--white);
}
.dev-mode-panel__export {
  width: 100%; margin-top: 4px;
  background: var(--navy); color: var(--cream);
}

/* ===========================================================================
   Sketch card — hand-drawn border via layered offset pseudo-borders +
   asymmetric radius + independent rotation + hard "pinned" drop shadow
=========================================================================== */
.sketch-card {
  position: relative;
  background: var(--white);
  border: 2.5px solid var(--navy);
  border-radius: 22px 26px 20px 28px / 26px 20px 28px 22px;
  filter: drop-shadow(4px 6px 0 rgba(29, 53, 87, 0.16));
  transition: transform 0.25s cubic-bezier(.34,1.56,.64,1), filter 0.2s ease;
}
.sketch-card::before {
  content: '';
  position: absolute;
  inset: -6px;
  border: 2px solid var(--navy);
  border-radius: 24px 20px 28px 22px / 20px 28px 22px 24px;
  opacity: 0.16;
  z-index: -1;
  pointer-events: none;
}
.sketch-card.sage { background: var(--sage); }
.sketch-card.cream { background: var(--cream-dark); }

.sketch-card.interactive:hover {
  transform: translateY(-6px) scale(1.035) rotate(var(--hover-rot, -1.2deg));
  filter: drop-shadow(6px 10px 0 rgba(29, 53, 87, 0.2));
}
.sketch-card.interactive:hover { animation: cardWiggle 0.45s ease; }
.sketch-card.interactive:active { transform: translateY(-2px) scale(0.98); }
@keyframes cardWiggle {
  0% { rotate: 0deg; }
  25% { rotate: -1.4deg; }
  55% { rotate: 1.2deg; }
  80% { rotate: -0.5deg; }
  100% { rotate: 0deg; }
}
.sketch-card.locked { filter: grayscale(0.35) drop-shadow(3px 4px 0 rgba(29,53,87,0.1)); opacity: 0.75; }
.sketch-card.locked:hover { animation: lockedShake 0.4s ease; }
@keyframes lockedShake {
  0%, 100% { transform: translateX(0) rotate(var(--hover-rot,0deg)); }
  25% { transform: translateX(-4px) rotate(-2deg); }
  75% { transform: translateX(4px) rotate(2deg); }
}

/* ===========================================================================
   Doodle buttons
=========================================================================== */
.doodle-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: 2.5px solid var(--navy);
  border-radius: 999px 999px 999px 999px / 999px 999px 999px 999px;
  padding: clamp(10px, 0.8vw, 13px) clamp(18px, 1.6vw, 26px);
  font-weight: 800; font-size: clamp(12px, 0.93vw, 14px); text-transform: uppercase; letter-spacing: 0.03em;
  background: var(--gold);
  color: var(--navy);
  filter: drop-shadow(3px 4px 0 rgba(29,53,87,0.22));
  transition: transform 0.12s ease, filter 0.12s ease;
}
.doodle-btn:hover { transform: translateY(-2px) rotate(-1deg); }
.doodle-btn:active { transform: translateY(2px) scale(0.95); filter: drop-shadow(1px 2px 0 rgba(29,53,87,0.22)); }
.doodle-btn:disabled { opacity: 0.4; filter:none; transform:none; }
.doodle-btn.brick { background: var(--brick); color: var(--cream); }
.doodle-btn.navy { background: var(--navy); color: var(--cream); }
.doodle-btn.ghost { background: var(--white); }
.doodle-btn.sm { padding: 8px 16px; font-size: 12px; }

/* ===========================================================================
   Sketch headings / labels
=========================================================================== */
.sketch-title {
  font-family: var(--font-display);
  text-transform: uppercase;
  color: var(--navy);
  /* Shared by every modal header (game modals, leaderboard, bingo,
     onboarding) — fluid here, not just on .hub-hero__title above, is what
     keeps modal content from feeling oversized on a smaller laptop too,
     not only the hub page behind them. */
  font-size: clamp(24px, 1.9vw, 30px);
  margin: 0 0 4px;
  position: relative;
  display: inline-block;
}
.sketch-title .scribble-underline {
  display: block;
  width: 100%;
  height: 10px;
  margin-top: 2px;
}
.doodle-tag {
  font-family: var(--font-hand);
  font-size: 18px;
  color: var(--brick);
  transform: rotate(-2deg);
  display: inline-block;
}
.eyebrow-hand {
  font-family: var(--font-hand);
  font-size: 16px;
  color: var(--ink-soft);
}

/* Game modal header: day/theme tag on its own line, activity title below.
   A flex column forces this stacking regardless of the children's own
   display value (both .doodle-tag and .sketch-title are inline-block, which
   would otherwise sit side by side on the same line — flex items always
   stack per flex-direction no matter what). */
.game-modal-header {
  display: flex; flex-direction: column; align-items: center;
  gap: 4px; margin-bottom: 18px; text-align: center;
}

/* ===========================================================================
   Week-locked banner — the global end-of-week view-only notice (see
   js/app.js's renderWeekLockedBanner). Sits above the track, the first
   thing on the canvas, so it reads as context for every grayed-out card
   below rather than a separate alert.
=========================================================================== */
.week-locked-banner {
  display: flex; align-items: center; justify-content: center;
  gap: 8px; text-align: center;
  margin: 0 0 24px;
  padding: 14px 20px;
  background: var(--navy); color: var(--cream);
  border: 2.5px solid var(--navy);
  border-radius: 20px 22px 18px 24px / 22px 18px 24px 20px;
  font-family: var(--font-hand); font-size: 16px;
  filter: drop-shadow(3px 4px 0 rgba(29,53,87,0.2));
}
.week-locked-banner strong { font-family: var(--font-display); font-size: 13px; text-transform: uppercase; letter-spacing: 0.03em; }

/* ===========================================================================
   Race track strip (kept concept, restyled sketchy)
=========================================================================== */
.track-wrap {
  position: relative;
  /* Without this, the weekday popover (.track-marker-popover, nested many
     levels inside here) can render BEHIND activity cards further down the
     page despite its own z-index:20 — that z-index only competes within
     .track-wrap's own stacking context (which the `filter` below already
     creates), and .track-wrap itself was `position:relative` with no
     explicit z-index, i.e. z-index:auto. .activity-card (via .sketch-card)
     is *also* `position:relative`, so the two were competing as equally-
     ranked auto-stacked elements, and DOM order — activity cards come
     later in the markup — let cards paint on top of the entire track,
     popover included, no matter how high the popover's own number was.
     Giving .track-wrap itself an explicit (if modest) z-index moves its
     whole subtree to a later paint step than any z-index:auto sibling,
     which is what actually fixes it. */
  z-index: 2;
  margin: 0 0 clamp(36px, 2.9vw, 56px);
  padding: clamp(36px, 2.7vw, 54px) clamp(24px, 2vw, 40px);
  background: var(--sage);
  border: 2.5px solid var(--navy);
  border-radius: 30px 34px 28px 36px / 34px 28px 36px 30px;
  filter: drop-shadow(5px 7px 0 rgba(29,53,87,0.15));
}
/* Discovery nudge for the hover popover — small, persistent, out of the way
   in the corner rather than a one-time toast or per-marker badge, so it
   never demands attention but is always there for someone who hasn't
   found the hover behavior yet. */
.track-hint {
  position: absolute; top: 14px; right: 22px;
  font-family: var(--font-hand); font-size: 14px; color: var(--ink-soft);
  opacity: 0.8;
  pointer-events: none;
}
/* Mobile equivalent of .track-hint (swipe/tap instead of hover) — hidden by
   default, shown instead of .track-hint below 640px. See sketch.css's
   mobile media query and index.html. */
.track-scroll-hint { display: none; }
/* Created dynamically (js/app.js's ensureMobileDayCard) and only ever
   populated from the mobile-gated tap handler, but hidden by default here
   too regardless — defensive, in case a window gets resized from mobile
   to desktop mid-session while it's showing. */
.mobile-day-card { display: none; }
.track-road {
  position: relative;
  height: 5px;
  background: var(--navy);
  border-radius: 999px;
  margin: 0 30px;
}
.track-road::before {
  content: '';
  position: absolute; inset: 0; margin: auto 0;
  height: 2px;
  background-image: repeating-linear-gradient(90deg, var(--gold) 0 16px, transparent 16px 32px);
}
/* pointer-events:none — this row is decorative only, and its empty flex gaps
   would otherwise sit in a later stacking position than .track-road and
   silently swallow clicks aimed at the mascot underneath it. */
.track-markers { position: relative; display: flex; justify-content: space-between; margin: 0 30px; pointer-events: none; }
.track-marker {
  position: relative; top: -18px;
  width: 38px; height: 38px;
  border-radius: 50% 48% 52% 46% / 46% 52% 48% 50%;
  background: var(--white);
  border: 2.5px solid var(--navy);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-size: 13px; color: var(--navy);
  filter: drop-shadow(2px 3px 0 rgba(29,53,87,0.12));
  /* Parent .track-markers is pointer-events:none so its empty flex gaps
     don't swallow clicks meant for the mascot underneath — but the marker
     circle itself (and the popover it hosts) opts back in so hover works. */
  pointer-events: auto;
  cursor: default;
}
.track-marker.past { background: var(--success); border-color: var(--success); color: white; }
.track-marker.expired { background: var(--brick); border-color: var(--brick-dark); color: var(--cream); }
.track-marker.active { background: var(--gold); border-color: var(--gold-dark); color: var(--navy); animation: pulse-marker 1.6s ease-in-out infinite; }
.track-marker-label {
  position: absolute; top: 46px; left: 50%; transform: translateX(-50%);
  font-family: var(--font-hand); font-size: 16px; color: var(--ink-soft); white-space: nowrap;
  text-align: center; line-height: 1.3;
}
.track-marker-date { display: block; font-size: 13px; opacity: 0.75; }
@keyframes pulse-marker {
  0%, 100% { box-shadow: 0 0 0 0 rgba(242, 167, 59, 0.5); }
  50% { box-shadow: 0 0 0 9px rgba(242, 167, 59, 0); }
}

/* Weekly calendar popover — hover-only, built per-marker in js/app.js from
   CSW_SCHEDULE (js/state.js AppState.computeDashboardDays), so it always
   mirrors the same day/theme/activity list/status data driving the timeline
   and activity cards — every activity for that day (remote portal ones and
   on-site ones alike), not just the tracked one. Pure-CSS :hover toggle: no
   JS show/hide bookkeeping, and it vanishes as soon as the pointer leaves
   the marker — no persistent clutter on the hub. */
.track-marker-popover {
  position: absolute;
  /* Always opens downward, for every day, no exceptions — this used to
     default to opening upward with a JS-driven flip to downward when
     there wasn't room above (see positionTrackPopover in js/app.js), but
     that made the direction inconsistent day to day depending on how much
     room happened to be above each marker. Anchoring from the marker's
     bottom unconditionally is what makes it consistent; a popover tall
     enough to reach the bottom of the viewport scrolls its own content
     instead (max-height set inline by positionTrackPopover) rather than
     flipping direction to avoid it. */
  top: 100%; left: 50%;
  /* --popover-shift-x: set by js/app.js's positionTrackPopover when
     centered would push either edge past the viewport (Monday near the
     left, Friday near the right) — 0px the rest of the time. This is the
     one piece of the old flip/collision logic that's unchanged — this fix
     is about vertical direction only. */
  transform: translateX(calc(-50% + var(--popover-shift-x, 0px))) translateY(8px);
  width: 250px;
  overflow-y: auto;
  background: var(--white);
  border: 2px solid var(--navy);
  border-radius: 14px 16px 12px 15px / 15px 12px 16px 14px;
  padding: var(--space-4) var(--space-4) var(--space-3);
  text-align: center;
  filter: drop-shadow(2px 3px 0 rgba(29,53,87,0.18));
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
  z-index: 20;
}
.track-marker:hover .track-marker-popover,
.track-marker:focus-visible .track-marker-popover {
  opacity: 1;
  pointer-events: auto;
}
/* The arrow tail — points up at the marker, since the popover always sits
   below it now. */
.track-marker-popover::after,
.track-marker-popover::before {
  content: '';
  position: absolute; left: 50%; transform: translateX(-50%);
  border: 6px solid transparent;
}
.track-marker-popover::after { top: auto; bottom: 100%; border-top-color: transparent; border-bottom-color: var(--navy); }
.track-marker-popover::before { top: auto; bottom: calc(100% - 2.5px); border-top-color: transparent; border-bottom-color: var(--white); border-width: 5px; }
.track-marker-popover__day { font-family: var(--font-hand); font-size: 16px; color: var(--navy); line-height: 1.2; }
.track-marker-popover__theme { font-family: var(--font-hand); font-size: 14px; color: var(--ink-soft); margin-top: var(--space-1); }
/* One row per activity that day (remote or on-site) — divider lines between
   them so a multi-activity day (e.g. Friday) doesn't read as one run-on
   block. Padding matches the app's --space scale rather than one-off pixel
   values, same as everywhere else spacing is tuned in this file. */
.track-marker-popover__item {
  padding: var(--space-3) 0;
  border-top: 1px dashed var(--sage-dark);
}
.track-marker-popover__item:first-of-type { border-top: none; padding-top: var(--space-3); }
.track-marker-popover__item-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
  text-align: left;
}
.track-marker-popover__activity { font-size: 13px; font-weight: 700; line-height: 1.4; color: var(--navy); }
/* Pills: an explicit line-height (rather than inheriting the popover's
   text line-height, which was leaving them looking crushed) plus generous,
   symmetric padding and inline-flex centering — height comes purely from
   font-size + line-height + padding, so "Remote" and "On-Site" always come
   out the same size regardless of label length, and the fully-rounded
   radius reads as a proper pill instead of a thin capsule. */
.track-marker-popover__type {
  flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase;
  line-height: 1; white-space: nowrap;
  padding: 6px 12px; border-radius: var(--radius-pill);
  background: var(--sage); color: var(--ink-soft);
}
.track-marker-popover__type.onsite { background: #FFF4DE; color: var(--gold-dark); }
/* No .track-marker-popover__status rule anymore — the hover card is
   schedule-only (activity + Remote/On-Site tag), not per-user completion
   status. That status still lives on the activity cards below the
   timeline (.activity-card__cta). */
/* Optional per-activity extra lines — see data/schedule.js's own comment
   on `description`/`time`. Both sit below the title/type row, quieter
   than the title itself so the row above still reads as the primary
   thing; `__item-time` picks up the same gold-dark used by the On-Site
   pill rather than a third color, so a glance already ties "this has a
   time window" back to "this is an on-site perk" without reading either. */
.track-marker-popover__item-desc {
  font-size: 12px; color: var(--ink-soft); line-height: 1.35;
  margin-top: 3px; text-align: left;
}
.track-marker-popover__item-time {
  font-size: 11px; font-weight: 700; color: var(--gold-dark);
  margin-top: 2px; text-align: left;
}

/* Mascot now lives pinned in the bottom-right corner of the screen,
   independent of the track — a fixed companion rather than a runner
   positioned along the road. */
.track-mascot,
#trackMascot.eggable {
  /* The #id selector out-specifies .eggable's own `position: relative`
     (see .eggable below), which would otherwise win by source order. */
  position: fixed; bottom: 24px; right: 28px;
  width: 92px; height: 92px;
  cursor: none;
  z-index: 55;
}
.track-mascot .mascot-bob { animation: mascotBob 2.6s ease-in-out infinite; }
.track-mascot img { width: 100%; height: 100%; object-fit: contain; border-radius: 18px; animation: mascotBlink 4.4s ease-in-out infinite; }
.track-mascot .speech {
  position: absolute; bottom: 100%; right: 0; left: auto; transform: none;
  margin-bottom: 10px;
  background: var(--white); border: 2.5px solid var(--navy); border-radius: 14px 16px 12px 16px;
  padding: 6px 12px; font-family: var(--font-hand); font-size: 15px; white-space: nowrap;
  filter: drop-shadow(2px 3px 0 rgba(29,53,87,0.15));
}

@keyframes mascotBob {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50% { transform: translateY(-7px) rotate(-2deg); }
}
@keyframes mascotBlink {
  0%, 90%, 100% { transform: scaleY(1); }
  95% { transform: scaleY(0.15); }
}

/* ===========================================================================
   Generic mascot mount (login, empty states, modals)
=========================================================================== */
.mascot-block { display: flex; flex-direction: column; align-items: center; gap: 14px; text-align: center; }
.mascot-block .mascot-bob { animation: mascotBob 3s ease-in-out infinite; display:inline-block; }
.mascot-block img { width: 170px; border-radius: 20px; animation: mascotBlink 4.8s ease-in-out infinite; }
.mascot-block .speech {
  background: var(--white); border: 2.5px solid var(--navy); border-radius: 16px 18px 14px 18px;
  padding: 10px 18px; font-family: var(--font-hand); font-size: 18px; color: var(--navy);
  filter: drop-shadow(3px 4px 0 rgba(29,53,87,0.15));
  position: relative;
}
.mascot-block .speech::after {
  content: ''; position: absolute; bottom: -10px; left: 50%; transform: translateX(-50%);
  border-width: 10px 8px 0 8px; border-style: solid; border-color: var(--navy) transparent transparent transparent;
}
.mascot-block .speech::before {
  content: ''; position: absolute; bottom: -6px; left: 50%; transform: translateX(-50%);
  border-width: 8px 6px 0 6px; border-style: solid; border-color: var(--white) transparent transparent transparent; z-index: 1;
}

/* Easter-egg trigger: the mascot itself, click it repeatedly */
.eggable { position: relative; }
.eggable::after {
  content: '';
}
.egg-progress-ring {
  position: absolute; inset: -8px;
  border-radius: 50%;
  border: 3px dashed var(--gold);
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
}
.egg-progress-ring.show { opacity: 0.9; animation: ringSpin 3s linear infinite; }
@keyframes ringSpin { to { transform: rotate(360deg); } }

/* ===========================================================================
   Activity board (cards on canvas)
=========================================================================== */
.board-section { margin-bottom: clamp(32px, 2.4vw, 46px); }
.activities-title { display: block; margin-bottom: clamp(16px, 1.3vw, 26px); }
/* Race Day Trivia's own section reuses .board-section/.activities-title as-
   is (same title styling, same grid) — the only addition is this one
   explanatory line under the heading. */
.trivia-section__blurb {
  font-family: var(--font-hand); font-size: 17px; color: var(--ink-soft);
  margin: -14px 0 22px; max-width: 640px;
}
.activity-grid {
  display: grid;
  /* auto-fit rather than a fixed column count: most days now carry two
     activities instead of one (Race Day Trivia plus that day's other
     activity — see data/schedule.js), so the board can be anywhere from 5
     to 9 cards depending on the week, and this reflows correctly for any
     of those counts at any viewport width without per-count overrides. */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: clamp(14px, 1.15vw, 22px);
}

/* Leaderboard / Bingo Card float free of the main content flow, so the
   track can lead straight into the activity cards with nothing between. */
.utility-dock {
  position: fixed; bottom: 24px; left: 28px; z-index: 55;
  display: flex; flex-direction: column; gap: 10px;
}
.utility-dock .doodle-btn { gap: 10px; }
.activity-card {
  padding: clamp(16px, 1.2vw, 22px) clamp(14px, 1.1vw, 18px);
  min-height: clamp(150px, 11.3vw, 190px);
  display: flex; flex-direction: column; gap: 10px;
  cursor: none;
}
.activity-card__medal {
  width: clamp(40px, 3.15vw, 52px); height: clamp(40px, 3.15vw, 52px); border-radius: 50%;
  background: var(--brick); color: var(--cream);
  display: flex; align-items: center; justify-content: center;
  border: 2px solid var(--navy);
}
.activity-card__medal svg { width: clamp(20px, 1.6vw, 26px); height: clamp(20px, 1.6vw, 26px); }
.activity-card.locked .activity-card__medal,
.activity-card.expired .activity-card__medal { background: var(--locked); color: var(--white); }
.activity-card.done .activity-card__medal { background: var(--sage-dark); color: var(--navy); }
.activity-card__day { font-family: var(--font-hand); font-size: 15px; color: var(--ink-soft); }
.activity-card__title { font-weight: 800; font-size: 17px; color: var(--navy); line-height: 1.25; }

/* Every card state renders as this same button-like pill — never plain
   status text — so what you can do with a card always reads the same way:
   Locked/Expired/Week-Ended are styled inert (cta-disabled, cursor:
   not-allowed, no lift-on-hover), Completed is distinctly clickable
   (cta-done, green), the Photo Finish/Nomination browse-only carve-out is
   distinctly clickable too but visually its own thing (cta-view, outlined),
   and Active (no modifier) keeps the normal gold doodle-btn look. All four
   share a native `title` attribute (also mirrored on the card itself) for
   the hover tooltip rather than a separate tooltip widget. */
.activity-card__cta { width: 100%; margin-top: auto; }
.activity-card__cta.cta-disabled {
  background: var(--locked); color: var(--white); border-color: var(--locked);
  cursor: not-allowed; filter: none;
}
.activity-card__cta.cta-disabled:hover { transform: none; }
.activity-card__cta.cta-done { background: var(--success); color: white; border-color: var(--success); }
.activity-card__cta.cta-view { background: var(--white); color: var(--navy); border-color: var(--navy); }

/* ===========================================================================
   Bingo board
=========================================================================== */
.bingo-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 18px;
  max-width: 820px;
}
.bingo-cell {
  aspect-ratio: 1 / 0.8;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  text-align: center; padding: 16px; cursor: none;
}
.bingo-cell__title { font-weight: 800; color: var(--navy); font-size: 14px; }
.bingo-cell.checked { background: var(--sage); }
.bingo-cell.checked .activity-card__medal, .bingo-cell.checked .bingo-medal { background: var(--success); color: white; }
.bingo-cell.bonus { background: linear-gradient(135deg, var(--gold), var(--brick)); }
.bingo-cell.bonus .bingo-cell__title { color: var(--white); }
.bingo-cell.bonus-locked { opacity: 0.65; }
.bingo-medal {
  width: 46px; height: 46px; border-radius: 50%;
  background: var(--brick); color: var(--cream);
  display: flex; align-items: center; justify-content: center;
  border: 2px solid var(--navy);
}
.bingo-medal svg { width: 22px; height: 22px; }

/* ===========================================================================
   Modal — expanded card
=========================================================================== */
.modal-backdrop {
  position: fixed; inset: 0;
  background: rgba(29,53,87,0.45);
  display: flex; align-items: center; justify-content: center;
  z-index: 500;
  padding: 30px;
  cursor: none;
  animation: fadeIn 0.15s ease;
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.sketch-modal {
  background: var(--white);
  border: 3px solid var(--navy);
  border-radius: 30px 34px 28px 36px / 34px 28px 36px 30px;
  filter: drop-shadow(6px 10px 0 rgba(29,53,87,0.25));
  max-width: 640px; width: 100%;
  max-height: 88vh; overflow-y: auto;
  padding: clamp(22px, 1.9vw, 34px);
  position: relative;
  animation: modalPop 0.28s cubic-bezier(.34,1.56,.64,1);
}
@keyframes modalPop {
  0% { transform: scale(0.85) rotate(-2deg); opacity: 0; }
  100% { transform: scale(1) rotate(0deg); opacity: 1; }
}
.modal-close {
  position: absolute; top: 16px; right: 16px;
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--cream-dark); border: 2px solid var(--navy);
  font-family: var(--font-display); font-size: 15px; color: var(--navy);
  display: flex; align-items: center; justify-content: center;
}
.modal-close:hover { background: var(--brick); color: var(--cream); transform: rotate(90deg); }

/* ===========================================================================
   Forms / misc reused from earlier build, restyled
=========================================================================== */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: var(--space-4); }
.field label { font-weight: 700; font-size: 13px; color: var(--navy); text-transform: uppercase; letter-spacing: 0.03em; }
.field input, .field textarea, .field select {
  border: 2.5px solid var(--sage-dark); border-radius: 14px;
  padding: 12px 14px; font-size: 15px; font-family: inherit;
  background: var(--cream); color: var(--ink);
}
.field input:focus, .field textarea:focus { outline: none; border-color: var(--gold); }
/* Custom chevron, not the native one — the OS-drawn arrow ignores padding
   and sits flush against the border in most browsers, which is exactly the
   "touching the edge" spacing bug this replaces. Drawing our own via
   background-image gives it real, guaranteed breathing room from the edge. */
.field select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  padding-right: 40px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='9' viewBox='0 0 14 9'><path d='M1 1l6 6 6-6' fill='none' stroke='%231D3557' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 12px 8px;
}
.field select:focus { outline: none; border-color: var(--gold); }

/* Leaderboard category tabs — a fixed grid rather than flex-wrap, so the
   buttons always land in balanced, evenly-sized rows regardless of how many
   wrap to a second line or how long a label is. */
.lb-tabs {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 8px; margin-bottom: 20px;
}
.lb-tabs .doodle-btn { width: 100%; }
/* "Combined Overall" is the primary/default category — flagged with a
   constant soft glow (not an emoji, not tied to selected state) so it reads
   as the headline option even when a different tab is active. */
.lb-tabs button[data-key="combined"] {
  box-shadow: 0 0 0 2px rgba(242, 167, 59, 0.45), 0 0 14px 3px rgba(242, 167, 59, 0.5);
}

/* Current-leader chip: mascot + name sitting side by side, vertically
   centered, instead of stacked. */
.lb-champion {
  display: flex; align-items: center; justify-content: center;
  gap: 20px; margin-bottom: 24px;
}
.lb-champion img { width: 72px; height: 72px; object-fit: contain; border-radius: 16px; }
.lb-champion__info { text-align: left; }
.lb-champion .doodle-tag { font-size: 20px; }
.lb-champion__name { font-family: var(--font-display); font-size: 28px; color: var(--navy); line-height: 1.15; margin-top: 2px; }

.lb-row {
  display: grid; grid-template-columns: 52px 1fr 120px;
  align-items: center; padding: 12px 18px;
  border-radius: 16px; background: var(--white);
  border: 2px solid var(--sage-dark);
  margin-bottom: 9px;
}
.lb-row.rank-1 { background: #FFF4DE; border-color: var(--gold); }
.lb-row.rank-2 { border-color: #C9CED6; }
.lb-row.rank-3 { border-color: #D9A579; }
.lb-rank { font-family: var(--font-display); font-size: 18px; color: var(--navy); }
.lb-code { font-weight: 800; letter-spacing: 0.05em; color: var(--navy); }
.lb-score { font-weight: 800; text-align: right; color: var(--brick); }

.toast {
  position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%) translateY(20px);
  background: var(--navy); color: var(--cream);
  padding: 13px 24px; border-radius: 999px; font-family: var(--font-hand); font-size: 17px;
  filter: drop-shadow(3px 5px 0 rgba(29,53,87,0.3));
  opacity: 0; pointer-events: none; transition: all 0.25s ease; z-index: 700;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.toast.error { background: var(--brick); }
.toast.success { background: var(--success); }

/* ===========================================================================
   Login overlay
=========================================================================== */
#loginScreen {
  position: fixed; inset: 0; z-index: 300;
  display: flex; align-items: center; justify-content: center; gap: clamp(32px, 5vw, 90px);
  /* Safety net, not a layout change at any width this was actually tested
     at (1366px/1440px comfortably fit the mascot + gap + card unwrapped,
     with hundreds of pixels to spare — see the responsive-audit note
     below). If the two-column content were ever wider than the viewport
     for some reason this hasn't hit, wrapping to a stacked layout here is
     what keeps it from overflowing sideways instead. `padding` gives that
     stacked state — and any narrow viewport in general — real breathing
     room from the screen edge, since this element has no margin of its
     own otherwise (`inset: 0`, flush to every edge). */
  flex-wrap: wrap;
  padding: 24px;
  background: var(--cream);
}
/* `min(380px, 100%)`, not a bare fixed `380px` — shrinks instead of
   overflowing if this card is ever laid out somewhere narrower than
   380px (a wrapped #loginScreen row on an unusually narrow desktop
   viewport, mid-transition between this and the dedicated <=640px phone
   layout further down). */
.login-card { width: min(380px, 100%); padding: 34px; }
.login-title { font-family: var(--font-display); font-size: 38px; text-transform: uppercase; color: var(--navy); line-height: 1.05; margin: 6px 0 10px; }
.login-title span { color: var(--brick); }
.login-sub { color: var(--ink-soft); margin: 0 0 24px; font-size: 15px; }
.code-input {
  width: 100%; text-align: center; letter-spacing: 0.5em; font-family: var(--font-display);
  font-size: 30px; padding: 16px 10px 16px 20px; border-radius: 16px;
  border: 3px solid var(--sage-dark); background: var(--cream); color: var(--navy); text-transform: uppercase;
}
.code-input:focus { outline: none; border-color: var(--gold); }
.code-hint { font-size: 12px; color: var(--ink-soft); margin-top: 8px; text-align: center; font-family: var(--font-hand); font-size: 14px; }
/* Deliberately its own class, not the shared .field — .field input's
   descendant selector (0,0,1,1) would outrank .code-input (0,0,1,0) and
   silently override its font-size/padding/border, same bug as elsewhere
   in this file. Keep pin-field's own input styling coming from .code-input
   alone. */
.pin-field { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 2px; }
.pin-field label {
  font-weight: 700; font-size: 13px; color: var(--navy);
  text-transform: uppercase; letter-spacing: 0.03em; text-align: center;
}
.login-error {
  color: var(--brick); font-weight: 700; font-size: 13px;
  text-align: center; margin: 4px 0 0;
}
.login-btn { width: 100%; margin-top: 18px; }
.forgot-pin-link {
  display: block; width: 100%; margin-top: 12px;
  background: none; border: none;
  font-family: var(--font-hand); font-size: 15px; color: var(--ink-soft);
  text-decoration: underline; text-underline-offset: 3px;
}
.forgot-pin-link:hover { color: var(--brick); }

/* ===========================================================================
   Finish line celebration overlay
=========================================================================== */
.finish-overlay {
  position: fixed; inset: 0; z-index: 600;
  background: rgba(29,53,87,0.55);
  display: flex; align-items: center; justify-content: center;
}
.finish-card { max-width: 620px; width: 90%; padding: 40px; text-align: center; }
.finish-card img { width: 220px; margin: 0 auto 16px; border-radius: 24px; }
.finish-card h1 { font-family: var(--font-display); font-size: 40px; color: var(--brick); margin: 8px 0; text-transform: uppercase; }

/* ===========================================================================
   Secret leaderboard + minigame accents
=========================================================================== */
.secret-glow {
  animation: secretGlow 1.6s ease-in-out infinite;
}
@keyframes secretGlow {
  0%, 100% { filter: drop-shadow(0 0 0px var(--gold)); }
  50% { filter: drop-shadow(0 0 14px var(--gold)); }
}

/* ===========================================================================
   Mile Dash mini-game HUD (lives + pre-game explainer)
=========================================================================== */
.mg-lives { display: flex; gap: 4px; }
.mg-life-icon { font-size: 18px; line-height: 1; transition: opacity 0.2s ease, transform 0.2s ease; }
.mg-life-icon.lost { opacity: 0.25; transform: scale(0.85); filter: grayscale(1); }
/* Shared pre-game explainer style — originally the mini-game's, reused as
   `.game-explainer` for Snap Judgement/Trivia's own pre-round rules screens
   (js/games.js) so every activity's "how this works" text looks the same. */
.mg-explainer, .game-explainer { text-align: left; font-family: var(--font-hand); font-size: clamp(15px, 1.1vw, 17px); color: var(--navy); line-height: 1.55; margin: 12px 0 18px; }
.mg-explainer p, .game-explainer p { margin: 0 0 8px; display: flex; align-items: flex-start; gap: 8px; }
.mg-explainer strong, .game-explainer strong { color: var(--brick); }

/* Utility: hides an element visually/from layout while it's still in the
   DOM and clickable via an associated <label> — used to swap a native file
   input for a themed doodle-btn "Choose a Photo" trigger (js/games.js). */
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ===========================================================================
   Nomination — emoji picker + Recognition Wall
=========================================================================== */
.emoji-picker { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; }
.emoji-option {
  font-size: 22px; line-height: 1; padding: 10px 0;
  background: var(--cream); border: 2.5px solid var(--sage-dark); border-radius: 12px;
  cursor: none; transition: transform 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.emoji-option:hover { transform: translateY(-2px) scale(1.06); }
.emoji-option.selected {
  background: #FFF4DE; border-color: var(--gold-dark);
  box-shadow: 0 0 0 2px rgba(242,167,59,0.4);
  transform: scale(1.08);
}
/* Recognition Wall cards now reuse Photo Finish's Gallery structure inline
   (same grid, same sketch-card padding/thumbnail/caption rhythm — see
   renderRecognitionWall in js/games.js) rather than a separate class-based
   layout, so there's one design system between them, not two. */

/* ===========================================================================
   Admin Panel (js/admin-panel.js) — tabs/sub-tabs reuse .lb-tabs' grid-of-
   doodle-btn look (see that rule above); `.is-active-tab` is this module's
   own selected-state class, parallel to the leaderboard's own per-call-site
   highlighting rather than a shared one, since the two tab rows mean
   different things (activity vs. active/deleted).
=========================================================================== */
.doodle-btn.is-active-tab {
  background: var(--navy); color: var(--cream);
}
.admin-subtabs { display: flex; gap: 8px; margin-bottom: 14px; }
.admin-subtabs .doodle-btn { flex: 1; }
.admin-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 20px; max-height: 320px; overflow-y: auto; }
.admin-list__row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.admin-list__row-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.admin-list__row-main span:last-child {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 320px;
}
.admin-reset-tool {
  border-top: 1.5px dashed var(--sage-dark); padding-top: 16px;
}
.admin-reset-tool__row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.admin-reset-tool__row select {
  flex: 1; min-width: 160px; padding: 8px; border-radius: 10px;
  border: 2px solid var(--sage-dark); font-family: inherit; font-size: 13px; color: var(--navy);
}

/* ===========================================================================
   Photo Finish — template picker grid + Gallery voting-rules note
=========================================================================== */
.pf-template-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;
  max-height: 260px; overflow-y: auto; padding: 2px;
}
.pf-template-option {
  padding: 0; background: var(--cream); border: 2.5px solid var(--sage-dark); border-radius: 12px;
  overflow: hidden; line-height: 0; cursor: none;
  transition: transform 0.15s ease, border-color 0.15s ease;
}
.pf-template-option img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; }
.pf-template-option:hover { transform: translateY(-2px) scale(1.04); }
.pf-template-option.selected {
  border-color: var(--gold-dark);
  box-shadow: 0 0 0 2px rgba(242,167,59,0.4);
  transform: scale(1.03);
}
.gallery-rules {
  text-align: center; font-family: var(--font-hand); font-size: 15px;
  color: var(--ink-soft); background: var(--sage); border-radius: 12px;
  padding: 8px 14px; margin: 0 0 14px;
}

/* ===========================================================================
   Snap Judgement — reveal-level progress bar (replaces the old text-only
   "Reveal level N / 4" indicator). A horizontal track with a fill + tick
   marks for each level and a flag marker sliding to the current position.
=========================================================================== */
.reveal-track {
  position: relative;
  height: 10px;
  background: var(--sage);
  border: 2px solid var(--navy);
  border-radius: 999px;
  margin: 6px 0 2px;
}
.reveal-track__fill {
  height: 100%;
  background: var(--gold);
  border-radius: 999px 0 0 999px;
  transition: width 0.3s ease;
}
.reveal-track__tick {
  position: absolute; top: -3px;
  width: 2px; height: 16px;
  background: var(--navy); opacity: 0.25;
  transform: translateX(-50%);
}
.reveal-track__marker {
  position: absolute; top: 50%;
  transform: translate(-50%, -50%);
  font-size: 18px; line-height: 1;
  transition: left 0.3s ease;
  filter: drop-shadow(1px 2px 0 rgba(29,53,87,0.3));
}
.reveal-track__labels {
  display: flex; justify-content: space-between;
  font-family: var(--font-hand); font-size: 13px; color: var(--ink-soft);
  margin: 2px 2px 0;
}

/* ===========================================================================
   Hyperlink Race — the mock docs site (data/mock-docs.js) + its persistent
   toolbar. Deliberately breaks from the rest of the app's hand-drawn look:
   this is meant to read as "you've stepped into a real, slightly dry docs
   site" — clean sans-serif body copy, a muted Learn-blue for links, thin
   hairline borders — framed by the still-doodle-styled toolbar above it
   (the timer + Found It! button) so the *game* stays in the app's voice
   even while the *content* it's set inside doesn't.
=========================================================================== */
/* Not used by anything right now — Hyperlink Race dropped its sidebar (see
   the comment on .hr-toolbar below), which was the reason its modal needed
   more than the standard 640px. Left in place, and `openModal`'s `wide`
   option along with it (js/app.js), for whatever future render needs a
   roomier modal rather than removed and re-added later. */
.sketch-modal--wide { max-width: 920px; }

/* Keeps the modal's top edge at a fixed spot on screen while Hyperlink
   Race's browsing phase is up, rather than the default centered-in-
   viewport modal recentering (and so visibly shifting) page to page as
   articles of very different lengths render. `setHyperlinkModalPinned`
   (js/games.js) toggles both classes directly on the open modal's own
   elements — only for renderBrowsing(); the intro and word-prompt screens
   are short, fixed-length content and stay at the normal shrink-wrapped/
   centered size every other activity's screens use, which is the default
   here (these classes toggled off, not applied). Earlier, this was applied
   for the entire session (intro through submit) via an `openModal`
   `pinned` option — that made the short intro/submit screens sit at the
   natural top of a much taller-than-needed fixed-height box, leaving a
   large empty stretch of blank modal below them (with a stray unneeded
   scrollbar) before reaching the box's own bottom edge.
   The height is `100vh` minus exactly the backdrop's own top+bottom
   padding (30px + 30px — see .modal-backdrop below), not an approximate
   vh guess: with flex-start alignment, anything less than that leaves a
   gap between the box's bottom edge and the bottom of the (dimmed, but
   not opaque) backdrop, through which the hub page behind it — including
   its corner mascot — was faintly visible, reading as a stray graphic
   floating in leftover space rather than a deliberately-composed screen. */
.modal-backdrop--top { align-items: flex-start; }
.sketch-modal--pinned {
  height: calc(100vh - 60px);
  /* .sketch-modal's own base rule sets max-height:88vh, which — being a
     separate property from height — silently clamps this explicit height
     right back down below its intended value regardless of it being more
     specific; override it explicitly rather than leave that cap in place. */
  max-height: none;
}

/* Sticky within the modal's own scroll container (.sketch-modal has
   overflow-y:auto — see the modal rules earlier in this file) so the whole
   toolbar stays visible no matter how far into a long article the player
   has scrolled — the same "always-visible while browsing" promise the old
   real-second-tab version got for free just by living outside the modal
   entirely. Two sections, not one undifferentiated row: .hr-objective
   (timer, start/goal, Found It!) is the "what am I doing and how do I
   finish" area, styled to read as the primary one; .hr-nav (Back + the
   visited-pages trail) is pure backtracking, secondary and quieter. */
.hr-toolbar {
  position: sticky; top: 0; z-index: 5;
  background: var(--white);
  border: 2.5px solid var(--navy);
  border-radius: 16px;
  padding: 10px 16px;
  margin-bottom: 16px;
  filter: drop-shadow(3px 4px 0 rgba(29,53,87,0.15));
}

/* The objective area: timer + pinned start/goal + Found It!, grouped
   together because all three are about the same thing — know where you
   started, know where you're going, submit once you're there. */
.hr-objective {
  display: flex; align-items: center; gap: 12px;
  padding-bottom: 10px;
}
.hr-objective__timer { font-family: var(--font-display); font-size: 26px; color: var(--navy); flex-shrink: 0; }
.hr-objective__goals {
  flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column; gap: 2px;
}
.hr-objective__goal {
  font-family: var(--font-hand); font-size: 13.5px; color: var(--ink-soft);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.hr-objective__goal-icon { margin-right: 3px; }
.hr-objective__goal-label {
  font-family: var(--font-body); font-weight: 800; text-transform: uppercase;
  font-size: 10px; letter-spacing: 0.03em; color: var(--brick); margin-right: 4px;
}
.hr-objective__goal strong { color: var(--navy); font-weight: 800; }
.hr-objective #hrFound { flex-shrink: 0; }

/* The navigation area: Back sits with the trail rather than off in the
   objective row, since both are backtracking tools, not goal-tracking
   ones. Deliberately plainer than .hr-objective above — smaller text, no
   bold weight — so the two areas read as primary vs. secondary at a
   glance, not just by position. */
.hr-nav {
  display: flex; align-items: center; gap: 10px;
  padding-top: 9px;
  border-top: 1.5px dashed var(--docs-border, #D9DEE3);
}
.hr-nav .doodle-btn:disabled { opacity: 0.35; }
.hr-nav__trail-toggle {
  background: none; border: none; padding: 4px 2px;
  display: flex; align-items: center; gap: 4px;
  font-family: var(--font-hand); font-size: 13px; color: var(--ink-soft);
}
.hr-nav__trail-toggle:hover { color: var(--navy); }
.hr-nav__chevron { font-size: 9px; }

/* Expands in place below the collapsed "Path: N pages" toggle — wraps
   rather than scrolling horizontally (the old single-line, overflow-x:auto
   version — see the redesign note above), so an arbitrarily long
   backtracking trail grows the panel downward instead of ever needing a
   scrollbar, and it's only ever on screen when actually expanded. */
.hr-nav__trail-expanded {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px 4px;
  padding-top: 8px; margin-top: 8px;
  border-top: 1px dashed var(--docs-border, #D9DEE3);
}
.mockdoc-trail__item {
  font-size: 12px; color: var(--docs-link, #0969DA);
  background: none; border: none; padding: 2px 2px;
  max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mockdoc-trail__item:not(:disabled):hover { text-decoration: underline; }
.mockdoc-trail__item.is-current { color: var(--ink-soft); font-weight: 700; cursor: default; }
.mockdoc-trail__sep { color: var(--ink-soft); opacity: 0.5; font-size: 12px; }

.mockdoc {
  --docs-link: #0969DA;
  --docs-border: #D9DEE3;
  --docs-text: #24292F;
  --docs-text-soft: #57606A;
  font-family: var(--font-body);
}
.mockdoc-breadcrumb {
  font-size: 13px; color: var(--docs-text-soft);
  margin-bottom: 14px;
}
.mockdoc-breadcrumb__sep { margin: 0 4px; opacity: 0.6; }
.mockdoc-breadcrumb__current { color: var(--docs-text); font-weight: 600; }

.mockdoc-article__meta { font-size: 12.5px; color: var(--docs-text-soft); margin-bottom: 4px; }
.mockdoc-article__title { font-family: var(--font-body); font-size: 24px; font-weight: 700; color: var(--docs-text); margin: 0 0 14px; }
.mockdoc-article__p { font-size: 14.5px; line-height: 1.65; color: var(--docs-text); margin: 0 0 14px; }
.mockdoc-article__p a.mockdoc-link { color: var(--docs-link); text-decoration: none; }
.mockdoc-article__p a.mockdoc-link:hover { text-decoration: underline; }

/* The per-user assigned word — see data/mock-docs.js's answerCandidates
   and js/games.js's runHyperlinkRace. Styled like an actual highlighter
   pass over the text, not a hyperlink, so it doesn't read as "click me". */
.mockdoc-answer {
  background: var(--gold); color: var(--navy);
  padding: 1px 5px; border-radius: 4px;
  font-weight: 800;
}

/* ===========================================================================
   Responsive — tablet and mobile
   Everything above this point was written desktop-first, but the layout-
   shell rules that actually matter for a 13–14" laptop (canvas padding,
   hero, track, section spacing, activity cards, shared modal padding/
   heading size, buttons, explainer text) now scale continuously via
   `clamp(min, Nvw, max)` right on their own base rules, rather than
   jumping between fixed sizes at a breakpoint — see each of those rules'
   own comments. That's what actually fixed the "oversized on a smaller
   laptop" report: a plain `@media (max-width: 1500px)` tier used to live
   here doing the same reduction, but a hard jump at one width doesn't
   verify anything about the *range* either side of it, and (per this
   round's investigation — see the README changelog entry for the full
   root-cause writeup) the originally-reported overflow wasn't actually a
   scaling problem at all, so tuning the numbers again wasn't the fix that
   mattered; removing the single breakpoint in favor of continuous scaling
   is a genuine improvement on top of that, not a re-hash of the same idea.
   Two tiers remain here, for the same reason they always did — a
   genuinely different, non-continuous layout kicks in at each:
     - <=900px  (tablet): trims outer spacing and oversized type further,
       and un-hides tablet-specific affordances (e.g. the weekday timeline
       hint text switching off).
     - <=640px  (phone): the real layout changes — stacked login, wrapping
       timeline, single-column grids, edge-to-edge modals. Verified live at
       375×812 (iPhone-class) and 768×1024 (tablet) widths, not just by
       reading the rules.
=========================================================================== */
@media (max-width: 900px) {
  .canvas-main { padding: 88px 24px 60px; }
  .hub-hero .doodle-tag { font-size: 20px; }
  .sketch-title.hub-hero__title { font-size: 28px; }
  .track-wrap { padding: 46px 20px 40px; }
  .track-hint { display: none; } /* the hover popover itself is desktop-only below 640px anyway — see .track-marker-popover */
  .lb-tabs { grid-template-columns: repeat(2, 1fr); }
  .sketch-modal { max-width: 92vw; padding: 26px; }
}

@media (max-width: 640px) {
  html, body { cursor: auto; } /* the custom cursor trail is a mouse-hover flourish — irrelevant (and untestable) on touch */
  button, input, textarea, select, a, .doodle-btn, .activity-card, .bingo-cell, .emoji-option {
    cursor: pointer; /* restore real touch/pointer affordance now the desktop custom cursor is off */
  }

  .canvas-main { padding: 84px 14px 48px; }
  .hub-hero { margin-bottom: 32px; gap: 8px; }
  .hr-toolbar { padding: 8px 12px; }
  .hr-objective { flex-wrap: wrap; }
  .hr-objective__goal { font-size: 12px; }
  .hr-objective__timer { font-size: 20px; }
  .hr-toolbar .doodle-btn.sm { padding: 8px 12px; font-size: 11px; }
  .mockdoc-trail__item { max-width: 90px; }
  .hub-hero .doodle-tag { font-size: 16px; }
  .sketch-title.hub-hero__title { font-size: 22px; }
  .activities-title { margin-bottom: 16px; font-size: 22px; }

  /* Fixed corner widgets: still fixed, just tucked in tighter so a small
     viewport doesn't feel like it's losing real content space to them. */
  .player-badge { top: 12px; right: 12px; }
  .dev-mode-panel { top: 72px; right: 12px; max-width: 170px; font-size: 10px; } /* badge bottom ~60px + gap, same fix as desktop */
  .utility-dock { bottom: 14px; left: 14px; }
  .utility-dock .doodle-btn.sm { padding: 8px 12px; font-size: 11px; }
  .track-mascot, #trackMascot.eggable { bottom: 14px; right: 14px; width: 72px; height: 72px; }
  .track-mascot .speech { font-size: 13px; padding: 5px 10px; max-width: 46vw; white-space: normal; }

  /* Timeline: a horizontally-scrolling strip instead of either squeezing 5
     markers into one fixed-width row (cramped) or wrapping them into a
     centered flow (reads as one clump, not five spaced-out days). Each
     marker gets real, fixed breathing room via min-width, and the whole
     row scrolls — a proper doodle-tag hint (below) signals it's scrollable. */
  .track-wrap { padding: 20px 0 44px; }
  .track-hint { display: none; } /* replaced by .track-scroll-hint below, sized for mobile */
  .track-scroll-hint { display: block; }
  .track-road { display: none; }
  .track-markers {
    flex-wrap: nowrap; justify-content: flex-start;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    /* CSS quirk worth flagging: setting overflow-x alone forces overflow-y
       to compute as 'auto' too (the spec doesn't allow one axis 'visible'
       and the other not) — so .track-marker-label below, an absolutely-
       positioned child sitting well below the marker circle itself, was
       getting vertically clipped by that auto-promoted overflow-y even
       though only horizontal scrolling was ever intended. Giving the
       strip enough height to contain a marker + its label avoids ever
       triggering that vertical clipping in the first place. */
    min-height: 92px;
    scroll-snap-type: x proximity;
    gap: 0;
    margin: 0;
    padding: 0 28px;
  }
  .track-marker {
    /* Generous horizontal margin — .track-marker-label below (e.g.
       "Wednesday") is wider than the 38px circle itself and would overlap
       a neighboring label at anything tighter, now that markers no longer
       need to all fit on-screen at once (the strip scrolls). */
    top: 0; flex: 0 0 auto;
    margin: 0 34px;
    scroll-snap-align: center;
  }
  .track-scroll-hint {
    text-align: center; font-family: var(--font-hand); font-size: 13px;
    color: var(--ink-soft); opacity: 0.8; margin: 0 0 4px;
  }
  /* The desktop hover popover is retired on mobile entirely (not just
     hidden-but-still-there) — :hover never fires on touch, and nesting it
     inside this now-horizontally-scrolling strip would clip it regardless
     (see the .mobile-day-card comment in js/app.js). Its tap-to-reveal
     replacement is the fixed-position .mobile-day-card below, completely
     decoupled from this container. */
  .track-marker-popover { display: none; }

  /* Fixed-position mobile "tap a day" card — see showMobileDayCard in
     js/app.js. Reuses the .track-marker-popover__* content classes above
     (day/theme/item rows) for free; only the outer container differs. */
  .mobile-day-card {
    display: none;
    position: fixed;
    left: 16px; right: 16px; bottom: 16px;
    z-index: 200;
    text-align: center;
    background: var(--white);
    border: 2.5px solid var(--navy);
    border-radius: 20px 24px 18px 24px / 24px 18px 24px 20px;
    padding: var(--space-4);
    filter: drop-shadow(3px 5px 0 rgba(29,53,87,0.2));
  }
  .mobile-day-card.show { display: block; animation: modalPop 0.2s ease; }
  .mobile-day-card__close {
    position: absolute; top: 10px; right: 10px;
    width: 28px; height: 28px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--cream-dark); border: 2px solid var(--navy);
    font-size: 12px; color: var(--navy);
  }

  .activity-grid { grid-template-columns: 1fr; gap: 14px; }
  .activity-card { min-height: 0; padding: 18px 16px; }

  .bingo-grid { grid-template-columns: repeat(2, 1fr); gap: 12px; max-width: none; }
  .bingo-cell { aspect-ratio: auto; padding: 14px; }

  .modal-backdrop { padding: 0; align-items: flex-end; }
  .sketch-modal {
    max-width: 100vw; width: 100%; max-height: 94vh;
    padding: 22px 16px 26px;
    border-radius: 26px 26px 0 0 / 22px 22px 0 0;
  }
  .modal-close { top: 12px; right: 12px; }

  #loginScreen { flex-direction: column; gap: 18px; padding: 24px 16px; overflow-y: auto; }
  .mascot-block img { width: 110px; }
  .mascot-block .speech { font-size: 15px; padding: 8px 14px; }
  .login-card { width: 100%; max-width: 380px; padding: 26px 20px; }
  .login-title { font-size: 30px; }

  .lb-tabs { grid-template-columns: repeat(2, 1fr); gap: 6px; }
  .lb-champion { flex-direction: column; gap: 10px; text-align: center; }
  .lb-champion__info { text-align: center; }
  .lb-row { grid-template-columns: 40px 1fr auto; padding: 10px 12px; gap: 4px; }

  .emoji-picker { grid-template-columns: repeat(4, 1fr); }
  #recognitionWall, #pfGallery { grid-template-columns: 1fr !important; }

  .finish-card { padding: 26px 18px; }
  .finish-card img { width: 150px; }
  .finish-card h1 { font-size: 28px; }
}
