/* Fermidle: "The Pile". Tokens per DESIGN.md, nothing else.
   CP-1's materials on a silvered instrument face: cadmium silver-gray paper,
   graphite ink, one muted uranium-yellow accent, oxide red for the measure
   of how far off a guess was, oxidized green for correct. All colors live
   in the two token blocks below; rules only ever reference tokens, so the
   dark theme (the chalkboard) is a pure palette swap and Chrome auto-darken
   (which respects color-scheme + a real dark theme) leaves the page alone. */

:root {
  color-scheme: light dark;
  --paper: #E5E8E6;
  --surface: #EEF0EE;
  --ink: #26251F;
  --ink-soft: #606665;
  --accent: #A98526;
  --accent-press: #8F6F1E;
  --correct: #2F6E55;
  --correct-wash: #DCE6DD;
  --wrong: #9E3423;
  --wrong-wash: #EDDBD6;
  --focus: #2F5D9E;
  /* Dark graphite text on the yellow accent fill; white on yellow fails AA. */
  --on-accent: #26251F;
  --border-soft: rgba(38, 37, 31, 0.16);
  --curve: rgba(47, 110, 85, 0.3);
  --vignette: rgba(38, 37, 31, 0.05);
  --scrim: rgba(38, 37, 31, 0.5);
  --thumb-shadow: rgba(38, 37, 31, 0.35);
  /* Graphite-block running bond, CP-1's masonry. Bricks 48x24 in a 96x48
     tile: two full-width courses, verticals offset half a brick per row. */
  --masonry: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='48'%3E%3Cpath d='M0 .5H96M0 24.5H96M.5 0V24M48.5 0V24M24.5 24V48M72.5 24V48' fill='none' stroke='%2326251F' stroke-opacity='.07'/%3E%3C/svg%3E");
  --mono: "IBM Plex Mono", monospace;
  --sans: "Jost", sans-serif;
  --hand: "Caveat", cursive;
  font-variant-numeric: tabular-nums;
}

/* Dark variant: the same lab after hours, worked out on the chalkboard.
   Charcoal slate, warm chalk-white ink, chalk-drawn green and red. The
   uranium yellow brightens a step so it still reads against slate; its
   button text stays dark graphite in both themes. */
@media (prefers-color-scheme: dark) {
  :root {
    --paper: #212623;
    --surface: #2B302B;
    --ink: #ECE7D8;
    --ink-soft: #A2A8A5;
    --accent: #D9B04C;
    --accent-press: #C09A38;
    --correct: #7FB894;
    --correct-wash: #253829;
    --wrong: #D07A66;
    --wrong-wash: #3B2B23;
    --focus: #8FB0DE;
    --border-soft: rgba(236, 231, 216, 0.18);
    --curve: rgba(236, 231, 216, 0.2);
    --vignette: rgba(0, 0, 0, 0.35);
    --scrim: rgba(0, 0, 0, 0.65);
    --thumb-shadow: rgba(0, 0, 0, 0.5);
    --masonry: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='48'%3E%3Cpath d='M0 .5H96M0 24.5H96M.5 0V24M48.5 0V24M24.5 24V48M72.5 24V48' fill='none' stroke='%23ECE7D8' stroke-opacity='.08'/%3E%3C/svg%3E");
  }
}

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

[hidden] { display: none !important; }

html { font-size: 16px; }

/* Background: the graphite pile. A faint offset-masonry block grid (a tiny
   inline SVG tile, one per theme token block) under a faint edge vignette.
   Both layers are static, nothing repaints. #app is exactly 100svh, so the
   body never scrolls and the single 100% vignette layer stays put without
   background-attachment. */
body {
  font-family: var(--sans);
  color: var(--ink);
  background-color: var(--paper);
  background-image:
    radial-gradient(ellipse 120% 90% at 50% 38%, transparent 60%, var(--vignette) 100%),
    var(--masonry);
  background-size: 100% 100%, 96px 48px;
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* Sized in small viewport units so the layout fits even when all browser
   chrome (URL bar, Android 3-button navigation) is on screen. dvh grows
   past the visible area in that state on Android Chrome, which shoved the
   bottom action zone under the system bar. If a viewport is shorter than
   the layout can compress to (see the short-viewport block near the end),
   #app scrolls instead of clipping the buttons. */
#app {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100svh;
  max-width: 100%;
  overflow-x: hidden;
  overflow-y: auto;
}

@media (min-width: 768px) {
  #app {
    max-width: 560px;
    margin: 0 auto;
  }
}

/* ---------- Header ---------- */

#header {
  flex: 0 0 auto;
  padding: 8px 16px 0;
}

.header-row {
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* Wide-tracked geometric caps: the Italian-modernist nameplate. */
.wordmark {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 1.125rem;
  letter-spacing: 0.3em;
}

.header-meta {
  display: flex;
  align-items: center;
  gap: 12px;
}

.meta-label {
  font-family: var(--mono);
  font-weight: 400;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
}

.help-btn {
  font-family: var(--mono);
  font-weight: 600;
  font-size: 0.75rem;
  width: 24px;
  height: 24px;
  border: 1.5px solid var(--ink);
  border-radius: 2px;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  position: relative;
}

/* Invisible extension of the tap target to 40px without growing the visual. */
.help-btn::after {
  content: "";
  position: absolute;
  inset: -8px;
}

.progress-row { position: relative; }

.progress {
  display: flex;
  justify-content: center;
  gap: 8px;
  padding: 8px 0;
}

/* Running golf total, pinned to the right of the progress ticks. */
.run-score {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
}

.run-score-num {
  font-weight: 600;
  color: var(--ink);
}

/* Five neutrons crossing the pile, one per question. */
.progress .dot {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--border-soft);
}

.progress .dot.current { background: var(--accent); }
.progress .dot.hit { background: var(--correct); }
.progress .dot.miss { background: var(--wrong); }

/* ---------- Game screen ---------- */

.game {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 16px 16px 0;
  gap: 16px;
}

.question-card {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: 2px;
  padding: 16px;
}

.category { display: block; margin-bottom: 8px; }

.question-text {
  font-size: 1.125rem;
  font-weight: 500;
  line-height: 1.4;
  max-width: 34ch;
}

/* ---------- Number stage ---------- */

/* No min-height floor: the stage is the flexible spacing of the screen.
   It grows to fill tall viewports and compresses to its content (the
   readout and reveal scale) on short ones, so the bottom action zone
   stays on screen before any scrolling kicks in. */
.stage {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  text-align: center;
}

/* The one large Fermi-Dirac curve: occupation dropping through the step at
   the chemical potential, drawn faintly behind the readout like a diagram
   left on the board. Negative z-index keeps it under the readout text and
   the reveal scale; it stretches with the stage and never animates. */
.fermi-curve {
  position: absolute;
  inset: 8px 0;
  z-index: -1;
  pointer-events: none;
}

.fd-curve {
  fill: none;
  stroke: var(--curve);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

.fd-mu {
  fill: none;
  stroke: var(--curve);
  stroke-width: 1;
  stroke-dasharray: 2 6;
  opacity: 0.7;
  vector-effect: non-scaling-stroke;
}

.readout {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.readout.pulse {
  animation: readout-pulse 150ms cubic-bezier(0.2, 0.9, 0.3, 1);
}

@keyframes readout-pulse {
  0% { transform: scale(1.02); }
  100% { transform: scale(1); }
}

.big-number-row {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.big-number {
  font-family: var(--mono);
  font-weight: 600;
  font-size: clamp(2.5rem, 11vw, 4.5rem);
  line-height: 1.05;
  letter-spacing: -0.01em;
}

.big-number .grow {
  display: inline-block;
  animation: digit-in 80ms ease-out;
}

@keyframes digit-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

.mag-word-viewport {
  display: block;
  overflow: hidden;
  height: 1.3em;
  font-size: clamp(1.125rem, 4.95vw, 2.025rem);
}

.mag-word {
  display: inline-block;
  font-family: var(--sans);
  font-weight: 700;
  font-size: inherit;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  line-height: 1.3;
}

.unit-line { margin-top: 4px; }

.sci-line {
  font-family: var(--mono);
  font-weight: 400;
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
}

.sci-line sup { font-size: 0.65em; }

/* ---------- Reveal scale and stamp ---------- */

.reveal-scale {
  position: relative;
  width: 100%;
  max-width: 320px;
  height: 48px;
  margin-top: 16px;
}

.reveal-track {
  position: absolute;
  top: 24px;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--border-soft);
}

.answer-tick, .guess-tick {
  position: absolute;
  top: 17px;
  left: 0;
  width: 2px;
  height: 15px;
}

.guess-tick { background: var(--accent); }

.answer-tick {
  background: var(--correct);
  transition: transform 450ms cubic-bezier(0.2, 0.9, 0.3, 1);
}

/* Exact hit: guess and answer collapse into one green marker that pops. */
.answer-tick.merged {
  width: 4px;
  margin-left: -1px;
  transform-origin: center bottom;
  animation: merged-pop 300ms cubic-bezier(0.2, 0.9, 0.3, 1);
}

@keyframes merged-pop {
  from { scale: 1 1.5; }
  to { scale: 1; }
}

/* The distance between guess and truth, drawn on the track in oxide red.
   Width is set once from JS; only scaleX animates. */
.gap-span {
  position: absolute;
  top: 23px;
  left: 0;
  height: 3px;
  background: var(--wrong);
  transition: transform 450ms cubic-bezier(0.2, 0.9, 0.3, 1);
}

.scale-label {
  position: absolute;
  left: 0;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  line-height: 1;
}

.guess-label {
  top: 4px;
  color: var(--accent);
}

.answer-label {
  bottom: 0;
  color: var(--correct);
  transition: opacity 150ms ease 350ms;
}

.answer-label.pending {
  opacity: 0;
  transition: none;
}

.stamp {
  position: absolute;
  top: 8px;
  right: 8px;
  font-family: var(--mono);
  font-weight: 600;
  font-size: 0.9375rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 4px 8px;
  border: 1.5px solid currentColor;
  border-radius: 2px;
  transform: rotate(-2deg);
  animation: stamp-in 200ms cubic-bezier(0.2, 0.9, 0.3, 1);
}

.stamp.hit { color: var(--correct); background: var(--correct-wash); }
.stamp.miss { color: var(--wrong); background: var(--wrong-wash); }

@keyframes stamp-in {
  from { opacity: 0; transform: rotate(-2deg) scale(1.3); }
  to { opacity: 1; transform: rotate(-2deg) scale(1); }
}

/* ---------- Slider ---------- */

.controls {
  flex: 0 0 auto;
  padding: 0 8px calc(16px + env(safe-area-inset-bottom, 0px));
}

#slider-block { padding: 0 0 8px; }

/* The exponent scale. Major ticks are the control rods: full-height
   graphite verticals every third detent. */
.ticks {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  height: 16px;
  margin: 0 14px;
}

.ticks .tick {
  width: 1px;
  height: 8px;
  background: var(--ink-soft);
}

.ticks .tick.major {
  height: 16px;
  background: var(--ink);
}

.ticks .tick.active {
  background: var(--accent);
  width: 2px;
}

#slider {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  height: 56px;
  background: transparent;
  cursor: pointer;
  touch-action: none;
}

#slider::-webkit-slider-runnable-track {
  height: 2px;
  background: var(--ink);
  border-radius: 0;
}

#slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 28px;
  height: 28px;
  margin-top: -13px;
  border-radius: 999px;
  background: var(--accent);
  border: 1.5px solid var(--ink);
  box-shadow: 2px 2px 0 var(--thumb-shadow);
  transition: transform 180ms cubic-bezier(0.2, 0.9, 0.3, 1);
}

#slider:active::-webkit-slider-thumb { transform: scale(1.15); }

#slider::-moz-range-track {
  height: 2px;
  background: var(--ink);
  border-radius: 0;
}

#slider::-moz-range-thumb {
  width: 28px;
  height: 28px;
  border-radius: 999px;
  background: var(--accent);
  border: 1.5px solid var(--ink);
  box-shadow: 2px 2px 0 var(--thumb-shadow);
  transition: transform 180ms cubic-bezier(0.2, 0.9, 0.3, 1);
}

#slider:active::-moz-range-thumb { transform: scale(1.15); }

.tick-labels {
  position: relative;
  height: 16px;
  margin: 0 14px;
}

.tick-labels span {
  position: absolute;
  top: 0;
  left: calc(var(--pos) / 23 * 100%);
  transform: translateX(-50%);
  font-family: var(--mono);
  font-size: clamp(0.5625rem, 2.4vw, 0.75rem);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-soft);
  white-space: nowrap;
}

.tick-labels span:first-child { transform: translateX(-25%); }

/* ---------- Buttons ---------- */

.action-btn {
  display: block;
  width: 100%;
  height: 52px;
  margin-top: 16px;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 0.9375rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--on-accent);
  background: var(--accent);
  border: none;
  border-radius: 2px;
  cursor: pointer;
}

.action-btn:active { background: var(--accent-press); }

/* ---------- Verdict block (post lock-in) ---------- */

#verdict-block { padding: 0 8px; }

.offby {
  font-family: var(--mono);
  font-weight: 600;
  font-size: 0.9375rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 8px;
}

.context-text {
  font-size: 1rem;
  font-weight: 400;
  line-height: 1.4;
  color: var(--ink);
}

.source-line { margin-top: 8px; }

.source-line a {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-decoration: underline;
  display: inline-block;
  padding: 4px 0;
  position: relative;
}

/* Invisible tap-target extension: a single-line source link is a 24px
   box, this brings the effective target to 40px like the help button. */
.source-line a::after {
  content: "";
  position: absolute;
  inset: -8px;
}

/* ---------- Results ---------- */

.results {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 24px 16px calc(24px + env(safe-area-inset-bottom, 0px));
}

.results-head { text-align: center; }

.results-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
}

.nav-arrow {
  font-family: var(--mono);
  font-weight: 600;
  font-size: 1rem;
  line-height: 1;
  width: 24px;
  height: 24px;
  border: 1.5px solid var(--ink);
  border-radius: 2px;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
  position: relative;
}

/* Invisible extension of the tap target to 40px without growing the visual. */
.nav-arrow::after {
  content: "";
  position: absolute;
  inset: -8px;
}

.nav-arrow:disabled {
  opacity: 0.3;
  cursor: default;
}

.score {
  font-family: var(--mono);
  font-weight: 600;
  font-size: clamp(2.5rem, 11vw, 4.5rem);
  line-height: 1.05;
  letter-spacing: -0.01em;
}

.score-label { display: block; margin-top: 4px; }

/* Consolation line under the hero score: a handwritten margin note, the
   pencil scrawl beside a back-of-the-envelope estimate. Sentence case
   because it is prose read once, not a label. */
.offby-note {
  font-family: var(--hand);
  font-weight: 600;
  font-size: 1.1875rem;
  letter-spacing: 0.01em;
  line-height: 1.3;
  color: var(--ink-soft);
  max-width: 30ch;
  margin: 8px auto 0;
}

/* The stamp on the results screen sits in flow under the hero score. */
.results-stamp {
  position: static;
  display: inline-block;
  margin-top: 8px;
}

.share-row {
  font-size: 1.125rem;
  letter-spacing: 0.04em;
  margin-top: 8px;
}

.verdict-rows {
  margin-top: 24px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.verdict-item {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* Rendered as a button once the day's facts are loaded (it unfolds the
   detail panel below), as a plain div while a browsed day's facts are
   still fetching; the resets keep both forms identical. */
.verdict-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  width: 100%;
  padding: 8px 12px;
  border: 1px solid var(--border-soft);
  border-radius: 2px;
  background: var(--surface);
  font: inherit;
  text-align: left;
  color: var(--ink);
}

button.verdict-row { cursor: pointer; }

.verdict-row.open { border-radius: 2px 2px 0 0; }

.verdict-row.hit { background: var(--correct-wash); }
.verdict-row.miss { background: var(--wrong-wash); }

/* Trailing verdict glyph, sitting after the HIGH/LOW score on the row's
   right edge: the share emoji row in flat theme colors. A green square for
   dead on, an oxide-red triangle pointing the way the guess went. On the
   left it read as a dropdown caret; on the right it reads as a verdict.
   Decorative only; the row text still spells out HIGH/LOW. */
.verdict-row::after {
  content: "";
  flex: 0 0 auto;
  align-self: flex-start;
  margin-top: 6px;
}

.verdict-row.hit::after {
  width: 8px;
  height: 8px;
  background: var(--correct);
}

.verdict-row.high::after {
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-bottom: 8px solid var(--wrong);
}

.verdict-row.low::after {
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 8px solid var(--wrong);
}

.verdict-row .q {
  flex: 1 1 auto;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.4;
  min-width: 0;
}

.verdict-row .v {
  flex: 0 0 auto;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.verdict-row.hit .v { color: var(--correct); }
.verdict-row.miss .v { color: var(--wrong); }

/* Unfolded detail under a row: fused to the row's bottom edge (shared
   border, squared corners) so the pair reads as one card. */
.verdict-detail {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border: 1px solid var(--border-soft);
  border-top: none;
  border-radius: 0 0 2px 2px;
  background: var(--surface);
}

.detail-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.detail-label {
  flex: 0 0 64px;
  color: var(--ink-soft);
}

/* Answer in verdict green, the guess in the same accent the reveal
   scale's YOU label wears. */
.detail-answer { color: var(--correct); font-weight: 600; }
.detail-guess { color: var(--accent); font-weight: 600; }

.verdict-detail .context-text { font-size: 0.875rem; }

.verdict-item.entering {
  animation: row-in 200ms cubic-bezier(0.2, 0.9, 0.3, 1) both;
}

@keyframes row-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

.results-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px 8px;
  margin-top: 24px;
  padding: 16px 0;
}

.stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

.stat-num {
  font-family: var(--mono);
  font-weight: 600;
  font-size: 1.5rem;
}

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

.share-note { text-align: center; margin-top: 8px; }

.countdown-line { text-align: center; margin-top: 16px; }

/* ---------- Error ---------- */

.error-panel {
  padding: 48px 16px;
  max-width: 420px;
  margin: 0 auto;
  text-align: center;
}

/* ---------- Help overlay ---------- */

.overlay {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  z-index: 10;
}

.modal {
  background: var(--surface);
  border: 1.5px solid var(--ink);
  border-radius: 2px;
  padding: 24px;
  max-width: 360px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.modal-title {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 1.125rem;
  letter-spacing: 0.22em;
}

/* ---------- Calibration access (debug playtest) ---------- */

/* Slim maintenance strip pinned above the header while a non-today puzzle
   is loaded: inverted ink so it reads as service equipment, not game UI. */
.playtest-banner {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  height: 24px;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--mono);
  font-weight: 400;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.playtest-exit {
  font-family: var(--mono);
  font-weight: 600;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  /* Inherits the strip's inverted text color; accent yellow fails AA on
     the light strip the dark theme produces. */
  color: inherit;
  background: none;
  border: none;
  text-decoration: underline;
  cursor: pointer;
  padding: 4px;
  position: relative;
}

/* Same invisible tap-target extension trick as the help button. */
.playtest-exit::after {
  content: "";
  position: absolute;
  inset: -8px;
}

.cal-date {
  font-family: var(--mono);
  font-weight: 400;
  font-size: 0.9375rem;
  color: var(--ink);
  background: var(--surface);
  border: 1.5px solid var(--ink);
  border-radius: 2px;
  padding: 8px 12px;
  width: 100%;
}

.cal-note { color: var(--wrong); }

.cal-return {
  font-family: var(--mono);
  font-weight: 400;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ink-soft);
  background: none;
  border: none;
  text-decoration: underline;
  cursor: pointer;
  padding: 8px;
  align-self: center;
}

/* ---------- Short viewports ---------- */

/* Compress the flexible spacing before allowing any scroll: on phones with
   3-button navigation and full browser chrome the small viewport can dip
   near 620px. Tightened paddings and gaps keep the whole play screen,
   reveal verdict included, inside that height with no scrolling. */
@media (max-height: 700px) {
  #header { padding-top: 4px; }
  .progress { padding: 6px 0; }
  .game { padding-top: 8px; gap: 8px; }
  .question-card { padding: 12px 16px; }
  .reveal-scale { margin-top: 8px; }
  .action-btn { margin-top: 8px; }
  .controls { padding-bottom: calc(8px + env(safe-area-inset-bottom, 0px)); }
  .offby { margin-bottom: 4px; }
  .source-line { margin-top: 4px; }
}

/* Second tier for browsers whose chrome eats even more height, like Opera
   Mobile with its bottom toolbar (visible viewport can dip near 560px).
   Everything scales down a notch so the reveal state fits around 540px. */
@media (max-height: 620px) {
  .header-row { height: 40px; }
  .progress { padding: 4px 0; }
  .game { padding-top: 6px; gap: 6px; }
  .question-card { padding: 10px 16px; }
  .question-text { font-size: 1rem; }
  .big-number { font-size: clamp(2rem, 9vw, 4.5rem); }
  .mag-word-viewport { font-size: clamp(1rem, 4.5vw, 2.025rem); }
  .reveal-scale { height: 40px; margin-top: 6px; }
  .reveal-track { top: 20px; }
  .answer-tick, .guess-tick { top: 14px; height: 13px; }
  .gap-span { top: 19px; }
  .guess-label { top: 1px; }
  .context-text { font-size: 0.875rem; }
  .action-btn { height: 44px; margin-top: 6px; }
  .controls { padding-bottom: calc(6px + env(safe-area-inset-bottom, 0px)); }
}

/* ---------- Focus and motion ---------- */

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 100ms !important;
    animation-name: none !important;
    transition-duration: 0ms !important;
  }
  .stamp, .verdict-item.entering { animation: fade-only 100ms both !important; }
  @keyframes fade-only {
    from { opacity: 0; }
    to { opacity: 1; }
  }
}
