/* LingoGoat — tiny word game. Side-view vertical climber, CSS-only. */

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

/* Locale-appropriate quotes for any element using open-quote/close-quote. */
:root            { quotes: "\201C" "\201D"; }          /* English "…" */
:root:lang(fr)   { quotes: "\00AB\00A0" "\00A0\00BB"; } /* French « … » */

html, body {
  height: 100%;
  height: 100dvh; /* tracks the dynamic viewport on mobile (address bar etc.) */
  width: 100%;
  margin: 0;
  overflow: hidden;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  color: #1f2937;
}

/* Desktop gets a dark letterbox around the portrait play area so the game
   reads as a phone-shaped window regardless of monitor size. On actual phones
   (viewport ≤ 620), min() collapses to 100vw and the bars disappear.

   Background echoes the in-game scene muted and darker: deep-sky navy at top
   fading into earth-brown at the bottom — "the wider world" framing the
   playfield. Simple enough to stay out of the way, but not a flat slab. */
body {
  background:
    linear-gradient(to bottom,
      #0c1b33 0%,
      #152338 45%,
      #2a1a0e 100%);
  display: flex;
  justify-content: center;
  align-items: stretch;
}

/* ---------- Game: portrait playfield ---------- */
.game {
  position: relative;
  width: min(100vw, 620px);
  height: 100vh;
  height: 100dvh; /* follow visible viewport so mobile browser chrome doesn't clip the bottom */
  overflow: hidden;
  /* Subtle shadow lifts the playfield out of the letterbox on desktop. */
  box-shadow: 0 0 40px rgba(0, 0, 0, 0.55);
}

/* ---------- HUD cluster (pause + altitude + score) ---------- */
/* position: fixed so the cluster anchors to the viewport — on desktop that
   parks it in the dark letterbox (outside the 620px playfield); on mobile
   the frame fills the viewport and it sits at the game's top-right. */
.hud-cluster {
  position: fixed;
  top: 20px;
  right: 20px;
  display: flex;
  align-items: center;
  gap: 12px;
  z-index: 15;
}

/* ---------- Pause button (rounded square, inside .hud-cluster) ---------- */
.pause-btn {
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: rgba(42, 24, 16, 0.82);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 2px solid rgba(217, 119, 6, 0.6);
  border-radius: 14px;
  color: #fde68a;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, background 0.15s;
}
.pause-btn.active {
  opacity: 1;
  pointer-events: auto;
}
.pause-btn:hover { background: rgba(42, 24, 16, 0.98); }

.score-badge {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  background: rgba(42, 24, 16, 0.82);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 2px solid rgba(217, 119, 6, 0.6);
  border-radius: 14px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
  color: #fde68a;
  font-weight: 700;
  font-size: 18px;
}
.score-icon { font-size: 18px; }

/* ---------- Altitude badge (flex sibling inside .hud-cluster) ---------- */
.altitude-badge {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 16px;
  background: rgba(42, 24, 16, 0.82);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 2px solid rgba(217, 119, 6, 0.6);
  border-radius: 14px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
  color: #fde68a;
  font-weight: 700;
  font-size: 16px;
  font-variant-numeric: tabular-nums;
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
}
.altitude-badge.active { opacity: 1; }
.altitude-icon { font-size: 14px; opacity: 0.85; }
.altitude-unit { opacity: 0.7; font-size: 14px; }

/* Freeze the countdown ring CSS animation while paused. */
.game.paused .timer-fill { animation-play-state: paused; }

/* Pause overlay — dims the scene, shows an icon + label. Click to resume. */
.pause-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 19;  /* above HUD (15), below start/end overlays (20) */
  cursor: pointer;
}
.pause-overlay[hidden] { display: none; }
/* Brand mark at the top of the pause screen. SVG textPath traces a smile-up
   quadratic curve so the letters arch like a fairground slogan. Amber fill +
   dark-brown stroke gives the "sticker / puffy sign" feel; drop-shadow adds
   lift off the dim backdrop. Pointer-transparent so a tap anywhere on the
   overlay still resumes the round. */
.pause-brand {
  position: absolute;
  top: clamp(32px, 10vh, 80px);
  left: 50%;
  transform: translateX(-50%);
  width: min(86vw, 460px);
  height: auto;
  pointer-events: none;
  user-select: none;
  overflow: visible; /* letters can extend past viewBox at the arc's peak */
}
.pause-brand text {
  font-family: 'Lilita One', system-ui, sans-serif;
  font-size: 78px;
  fill: #fbbf24;
  paint-order: stroke fill;
  stroke: #7c2d12;
  stroke-width: 10;
  stroke-linejoin: round;
  letter-spacing: -1px;
  filter: drop-shadow(0 8px 14px rgba(0, 0, 0, 0.55));
}
.pause-panel {
  text-align: center;
  color: #fde68a;
  user-select: none;
}
.pause-icon {
  font-size: 80px;
  line-height: 1;
  margin-bottom: 12px;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.3));
}
.pause-label {
  font-size: 32px;
  font-weight: 800;
  letter-spacing: 2px;
  text-transform: uppercase;
  text-shadow: 0 2px 0 #7c2d12;
}
.pause-hint {
  font-size: 13px;
  opacity: 0.7;
  margin-top: 12px;
  letter-spacing: 0.5px;
}
/* ---------- Floating "+N" points indicator ---------- */
.points-float {
  position: absolute;
  transform: translate(-50%, -50%);
  font-weight: 800;
  pointer-events: none;
  z-index: 8;
  text-shadow: 0 3px 0 rgba(0, 0, 0, 0.45);
  animation: points-pop 1.2s cubic-bezier(0.2, 0.8, 0.3, 1) forwards;
  user-select: none;
}
.points-float.fast { color: #bbf7d0; font-size: 52px; }
.points-float.med  { color: #fde68a; font-size: 44px; }
.points-float.slow { color: #fecaca; font-size: 36px; }
@keyframes points-pop {
  0%   { transform: translate(-50%, -50%)  scale(0.5); opacity: 0; }
  20%  { transform: translate(-50%, -100%) scale(1.25); opacity: 1; }
  65%  { transform: translate(-50%, -150%) scale(1.1); opacity: 1; }
  100% { transform: translate(-50%, -220%) scale(0.9); opacity: 0; }
}

/* ---------- Menu (restart): quarter-circle anchored to top-left ---------- */
/* position: fixed — lives in the viewport's top-left corner so it sits in
   the letterbox on desktop while staying at the playfield corner on mobile. */
.menu-btn {
  position: fixed;
  top: 0;
  left: 0;
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Box-center (28,28) sits near the arc because the visible pie skews toward
     the top-left corner. Shifting the flex-center inward with bottom+right
     padding puts the icon closer to the pie's optical center. */
  padding: 0 12px 12px 0;
  background: rgba(42, 24, 16, 0.88);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 2px solid rgba(217, 119, 6, 0.6);
  border-top: none;
  border-left: none;
  border-radius: 0 0 100% 0;
  color: #fde68a;
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
  /* Clip the hit area to match the visible quarter-pie so clicks in the
     transparent corner don't register. */
  clip-path: circle(100% at 0 0);
  transition: background 0.15s;
  z-index: 16;
}
.menu-btn:hover {
  background: rgba(42, 24, 16, 0.98);
}
/* Rotate only the icon on hover — the quarter-pie stays anchored so its
   clip-path doesn't drift out from under the cursor. SVG has a precise
   center so rotation pivots in place (unlike text glyphs, which sit on the
   baseline and swing). */
.menu-icon {
  display: block;
  width: 16px;
  height: 16px;
  transition: transform 0.3s ease;
}
.menu-btn:hover .menu-icon,
.menu-btn:focus-visible .menu-icon {
  transform: rotate(-180deg);
}

/* Hover-revealed "Restart" label, sitting to the RIGHT of the quarter-pie.
   Also fixed so it tracks the menu-btn into the letterbox on desktop. */
.menu-label {
  position: fixed;
  top: 14px;
  left: 64px;
  padding: 6px 14px;
  background: rgba(42, 24, 16, 0.9);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 2px solid rgba(217, 119, 6, 0.6);
  border-radius: 999px;
  color: #fde68a;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.3px;
  white-space: nowrap;
  opacity: 0;
  transform: translateX(-8px);
  transition: opacity 0.2s ease, transform 0.2s ease;
  pointer-events: none;
  z-index: 18;
}
.menu-btn:hover ~ .menu-label,
.menu-btn:focus-visible ~ .menu-label {
  opacity: 1;
  transform: translateX(0);
}

/* ---------- Hint (top-center) — countdown → prompt → word reveal ---------- */
.hint {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  padding: 14px 26px 14px 18px;
  /* Grow to the text's natural single-line width instead of shrink-wrapping
     (which was breaking "Qu'est-ce que c'est ?" into 3 lines by stacking
     each word). Capped at viewport-minus-gutter so it can't overflow; if
     text genuinely exceeds that, it wraps naturally to 2 lines. */
  width: max-content;
  max-width: calc(100% - 16px);
  /* More transparent than before so the goat / scene underneath shows through
     the hint pill. backdrop-filter keeps the text legible. */
  background: rgba(42, 24, 16, 0.58);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  /* Anchored to the viewport top — no top border / rounding, just the two
     bottom corners rounded so the pill reads as an overhang from the edge. */
  border: 2px solid rgba(217, 119, 6, 0.6);
  border-top: none;
  border-radius: 0 0 14px 14px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
  pointer-events: none;
  z-index: 15;
  opacity: 0;
  transition: opacity 0.25s ease;
}
.hint.counting,
.hint.revealed { opacity: 1; }
/* Click-to-TTS is gated off via TTS_ENABLED in script.js while the quality
   of browser SpeechSynthesis voices is too poor to ship. Keeping these
   rules commented so re-enabling is one-liner per file when we have
   pre-recorded audio per language.
.hint.revealed {
  pointer-events: auto;
  cursor: pointer;
}
*/

/* Countdown ring — sits to the LEFT of the prompt, collapses on reveal */
.hint-timer {
  position: relative;
  width: 44px;
  height: 44px;
  flex: 0 0 auto;
  opacity: 0;
  max-width: 0;
  margin-right: 0;
  overflow: hidden;
  transition:
    opacity 0.2s ease,
    max-width 0.2s ease,
    margin-right 0.2s ease;
}
.hint.counting .hint-timer {
  opacity: 1;
  max-width: 44px;
  margin-right: 12px;
}
.timer-ring {
  display: block;
  width: 44px;
  height: 44px;
}
.timer-track {
  fill: none;
  stroke: rgba(255, 255, 255, 0.18);
  stroke-width: 2.5;
}
.timer-fill {
  fill: none;
  stroke: #fbbf24;
  stroke-width: 2.5;
  stroke-linecap: round;
  transform: rotate(-90deg);
  transform-origin: 22px 22px;
  /* circumference = 2·π·r where r=19 → ~119.38 */
  stroke-dasharray: 119.38;
  stroke-dashoffset: 0;
}
.hint.counting .timer-fill {
  animation: hint-countdown var(--reveal-duration, 3000ms) linear forwards;
}
@keyframes hint-countdown {
  to { stroke-dashoffset: 119.38; }
}
.timer-number {
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  transform: translateY(-50%);
  text-align: center;
  font-size: 18px;
  font-weight: 800;
  color: #fde68a;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.hint-content {
  text-align: center;
}

.hint-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: #fbbf24;
  opacity: 0;
  max-height: 0;
  overflow: hidden;
  transition: opacity 0.25s ease, max-height 0.25s ease;
}
.hint.revealed .hint-label {
  opacity: 0.9;
  max-height: 20px;
  margin-bottom: 6px;
}

.hint-text {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: 1px;
  color: #fde68a;
  text-shadow: 0 2px 0 #7c2d12;
  line-height: 1.2;
  transition: font-size 0.25s ease, letter-spacing 0.25s ease;
  /* Honor explicit `\n` in translated strings (e.g. French prompt forces a
     2-line break) while still collapsing other whitespace like `normal`. */
  white-space: pre-line;
}
.hint.revealed .hint-text {
  font-size: 28px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 1px;
}
.hint.revealed .hint-text::before { content: open-quote; }
.hint.revealed .hint-text::after  { content: close-quote; }

/* ---------- Scene: side-view vertical climber ---------- */
.scene {
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* Sky gradient: deeper blue high up fading to warm near the horizon.
     The warm band sits just above the ground strip so the horizon reads
     as sunset-lit. */
  background: linear-gradient(to bottom,
    #38bdf8 0%,
    #7dd3fc 45%,
    #bae6fd 75%,
    #fef3c7 100%);
}

/* Soft vignette at the edges */
.scene::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-shadow: inset 0 0 120px rgba(0, 0, 0, 0.18);
  z-index: 10;
}

/* Parallax cloud layer: sibling of .world, scrolled at a fraction of the
   camera speed. DOM order (before .world) keeps it behind platforms and the
   goat without needing an explicit z-index. */
.clouds {
  position: absolute;
  inset: 0;
  pointer-events: none;
  will-change: transform;
}
.cloud {
  position: absolute;
  line-height: 1;
  user-select: none;
  filter: blur(0.5px);
  transform: translateX(-50%);
  /* Horizontal drift. Each cloud gets a randomized --drift-duration and
     --drift-delay via inline styles from JS so the layer looks alive instead
     of metronomically synced. alternate = oscillate both directions. Using
     longhand animation properties because var() inside the `animation:`
     shorthand is parsed inconsistently across browsers. */
  animation-name: cloud-drift;
  animation-duration: var(--drift-duration, 24s);
  animation-delay: var(--drift-delay, 0s);
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}
@keyframes cloud-drift {
  from { transform: translateX(calc(-50% - 55px)); }
  to   { transform: translateX(calc(-50% + 65px)); }
}

/* World: every gameplay element lives here. The camera is a CSS transform
   on this node — translateY(positive) pushes the world DOWN on screen,
   which visually is "we climbed higher." */
.world {
  position: absolute;
  inset: 0;
  will-change: transform;
}

/* Ground: the goat's starting surface. Grass cap on top, earth underneath.
   The top 56px (worldY 0 → 56) is grass; below that, the element extends
   ~400px down so it fills the viewport's bottom area with brown soil at
   game start. As the camera rises, the whole block scrolls off as one. */
.ground {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -400px;        /* extends into worldY = -400 */
  height: 456px;         /* top at worldY = 56 (goat's starting perch) */
  /* Grass in the top 56px, then the soil column underneath. Stops are
     pixel-absolute so the transition lines up precisely with the goat's
     perch. */
  background:
    linear-gradient(to bottom,
      #86efac   0px,
      #4ade80  30px,
      #166534  54px,     /* dark grass edge — just above the dirt boundary */
      #92400e  56px,     /* dirt starts here */
      #7c2d12 140px,     /* richer brown */
      #5a1e0b 260px,     /* deeper earth */
      #3f1608 456px);    /* near-black soil at the bottom */
  border-top: 4px solid #14532d;
  border-radius: 8px 8px 0 0;
  pointer-events: none;
  z-index: 1;  /* behind platforms and player */
}
/* Brand mark baked into the dirt just below the grass line. Visible on game
   start (before the camera scrolls past worldY ~ -100) and caught in every
   start-screen screenshot. Same smile-up SVG textPath arc as the pause
   screen so the wordmark reads as one identity across the game. Amber over
   dark dirt + dark-brown stroke = "carved sign" look. Pointer-transparent so
   it never interferes with clicks. */
.ground-brand {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  /* Was 140px (84px into the dirt). Moved up to 80px (24px into the dirt)
     so the wordmark stays in-frame longer as the camera rises at game
     start — visible for roughly a full extra second of play. */
  top: 80px;
  width: min(86%, 460px);
  height: auto;
  pointer-events: none;
  user-select: none;
  overflow: visible;                  /* letters can crest above the viewBox */
}
.ground-brand text {
  font-family: 'Lilita One', system-ui, sans-serif;
  font-size: 78px;
  fill: #fbbf24;
  paint-order: stroke fill;
  stroke: #7c2d12;
  stroke-width: 10;
  stroke-linejoin: round;
  letter-spacing: -1px;
  filter: drop-shadow(0 8px 14px rgba(0, 0, 0, 0.5));
}

/* Grass blades: restricted to the top 56px via background-size + no-repeat.
   Pseudo-element layered on top of the main dirt gradient so the blade
   pattern doesn't bleed into the soil. */
.ground::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 56px;
  background: repeating-linear-gradient(90deg,
    rgba(0, 0, 0, 0) 0 5px,
    rgba(22, 101, 52, 0.3) 5px 6px,
    rgba(0, 0, 0, 0) 6px 11px);
  pointer-events: none;
}

/* Platform: floating wooden shelf with a grass cap. `bottom` and `left`
   are set inline by JS per-platform; the rest is static. */
.platform {
  position: absolute;
  transform: translateX(-50%);
  width: 150px;
  height: 26px;
  background:
    linear-gradient(to bottom,
      #86efac 0% 8px,
      #4ade80 8px 14px,
      #92400e 14px 20px,
      #78350f 20px 100%);
  border-radius: 10px;
  box-shadow: 0 8px 12px rgba(0, 0, 0, 0.3);
  z-index: 3;
}
/* Edge-connected variants: platform sits flush against the left or right
   side of the playfield. Three-corner radius suggests it's jutting out
   from a wall rather than floating free. */
.platform.edge-left {
  left: 0;
  right: auto;
  transform: none;
  border-radius: 0 12px 12px 0;
}
.platform.edge-right {
  left: auto;
  right: 0;
  transform: none;
  border-radius: 12px 0 0 12px;
}

/* Emoji sits on the top edge of its platform */
.platform-emoji {
  position: absolute;
  left: 50%;
  bottom: 100%;
  transform: translate(-50%, 8px);
  font-size: 54px;
  filter: drop-shadow(0 4px 4px rgba(0, 0, 0, 0.3));
  user-select: none;
  pointer-events: none;
  line-height: 1;
}
.platform-emoji.eaten {
  animation: emoji-eaten 0.4s ease-out forwards;
}
@keyframes emoji-eaten {
  0%   { transform: translate(-50%, 8px)   scale(1);    opacity: 1; }
  45%  { transform: translate(-50%, -30px) scale(1.35); opacity: 1; }
  100% { transform: translate(-50%, -60px) scale(0);    opacity: 0; }
}

/* Reused by the "correct!" flash on a platform emoji (was .object.flash). */
.platform-emoji.flash {
  animation: emoji-pop 0.5s ease-out 2;
}
@keyframes emoji-pop {
  0%, 100% { transform: translate(-50%, 8px) scale(1); }
  50%      { transform: translate(-50%, 4px) scale(1.35); }
}

/* Player character. Lives inside .world so it scrolls with the camera.
   `bottom` and `left` are set inline by JS; transitions make the hop feel
   springy without real physics. */
.player {
  --size: 64px;
  position: absolute;
  font-size: var(--size);
  transform: translateX(-50%);
  filter: drop-shadow(0 5px 5px rgba(0, 0, 0, 0.45));
  user-select: none;
  line-height: 1;
  z-index: 5;
  /* Horizontal side-to-side travel still tweens via CSS; vertical motion is
     driven by a Web Animations API keyframe in jumpGoatTo so it can arc
     (briefly overshoot the destination before landing). */
  transition: left 280ms cubic-bezier(0.34, 1.45, 0.64, 1);
}
.player.falling {
  transition: bottom 500ms ease-in, transform 500ms ease-in;
  transform: translateX(-50%) rotate(30deg);
}

/* ---------- Feedback toast (only visible when correct/wrong) ---------- */
.feedback {
  position: absolute;
  bottom: 150px;
  left: 50%;
  transform: translateX(-50%);
  padding: 0;
  /* Bumped from 15px — the heard/typed echo is the main learning signal, so
     it should read at a glance without the player squinting. */
  font-size: 20px;
  font-weight: 500;
  color: #fff;
  background: transparent;
  border-radius: 8px;
  text-align: center;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  z-index: 15;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s;
}
.feedback.correct,
.feedback.wrong {
  padding: 8px 22px;
  opacity: 1;
}
.feedback.correct { color: #bbf7d0; background: rgba(22, 101, 52, 0.8); }
.feedback.wrong   { color: #fecaca; background: rgba(153, 27, 27, 0.8); }

/* ---------- Controls (floating bottom-center) ---------- */
/* Sits near the viewport bottom. The mic-status label now floats ABOVE the
   mic (showing the live STT transcript), so the button itself can hug the
   bottom edge — previously we needed to reserve space below the mic for
   that label, but that's gone. */
.controls {
  position: absolute;
  bottom: 28px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 12px;
  z-index: 15;
}

/* Dev panel — hidden by default, appears next to the mic when toggled */
.dev-panel {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  /* Translucent so the goat below the controls stays partially visible. */
  background: rgba(255, 255, 255, 0.72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border-radius: 14px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
}
.dev-panel[hidden] { display: none; }

.mic-btn {
  position: relative;
  width: 68px;
  height: 68px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 2px solid #e5e7eb;
  border-radius: 50%;
  /* Translucent so the goat / scene reads through the mic, matching the hint
     pill and dev-panel's glass-chip look. backdrop-filter keeps the emoji
     clearly legible. */
  background: rgba(243, 244, 246, 0.72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  font-size: 36px;
  line-height: 1;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, transform 0.05s;
  overflow: visible;
}
.mic-btn .mic-emoji {
  display: block;
  transition: filter 0.15s, opacity 0.15s;
}
/* Hover: keep the same translucency as the resting state (don't jump to the
   opaque yellow we had before) — mobile "hover" on tap was causing the mic
   to flash between translucent and opaque, which read as a rendering bug. */
.mic-btn:hover:not(:disabled) { border-color: #d97706; }
.mic-btn:active:not(:disabled) { transform: scale(0.95); }
.mic-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.mic-btn.unsupported { display: none; }

/* LISTENING — state carried by the green border + pulsing ring halo, not by
   a background color swap. Keeping the bg constant across states prevents the
   perceived "flicker" each time recognition restarts (every few seconds) and
   lets backdrop-filter blend consistently with whatever's behind the mic. */
.mic-btn.listening {
  border-color: #22c55e;
  animation: mic-pulse 1.1s ease-in-out infinite;
}
@keyframes mic-pulse {
  0%   { box-shadow: 0 0 0 0   rgba(34, 197, 94, 0.55); }
  70%  { box-shadow: 0 0 0 16px rgba(34, 197, 94, 0); }
  100% { box-shadow: 0 0 0 0   rgba(34, 197, 94, 0); }
}

/* MUTED — state carried by the border + the bold diagonal slash (::before).
   Background stays the default resting tint so the mic stops flashing color
   between states. */
.mic-btn.muted {
  border-color: #9ca3af;
}
.mic-btn.muted .mic-emoji {
  filter: grayscale(1);
  opacity: 0.55;
}
.mic-btn.muted::before {
  content: "";
  position: absolute;
  top: 50%;
  /* Span the full button width so the slash, after 45° rotation, reaches
     edge-to-edge across the circle's diameter. */
  left: 0;
  right: 0;
  height: 4px;
  border-radius: 2px;
  background: #ef4444;
  transform: translateY(-50%) rotate(-45deg);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.9);
}

/* Status label — floats ABOVE the mic button so the live STT transcript is
   in the player's natural gaze path. Larger text than before because what
   STT is hearing is the main mid-round diagnostic signal. */
.mic-status {
  position: absolute;
  bottom: calc(100% + 24px);
  left: 50%;
  transform: translateX(-50%);
  padding: 8px 16px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.7);
  color: white;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: 0.3px;
  white-space: nowrap;
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease, background 0.15s ease;
}
.mic-btn.listening .mic-status {
  opacity: 1;
  background: #16a34a;
}
.mic-btn.muted .mic-status {
  opacity: 1;
  background: #4b5563;
}
/* Hide the pill entirely when there's no transcript to show (e.g. listening
   but the player hasn't said anything yet). Avoids the tiny empty green
   rectangle that would otherwise appear from the pill's padding. */
.mic-status:empty { display: none; }
.dev-panel input {
  width: 220px;
  padding: 10px 12px;
  border: 2px solid #e5e7eb;
  border-radius: 10px;
  /* Transparent so the panel's translucent background (and scene underneath)
     reads through the resting state. correct/wrong states below re-assert
     colored fills so feedback remains legible. */
  background: transparent;
  /* iOS Safari auto-zooms the viewport on focus if an input's font-size is
     below 16px. The zoom doesn't reset when the keyboard closes, which is why
     the end screen looked cropped. 16px keeps us at the user's chosen zoom. */
  font-size: 16px;
  outline: none;
  transition: border-color 0.15s, background 0.15s;
}
.dev-panel input:focus { border-color: #d97706; }
.dev-panel input.correct { border-color: #4ade80; background: #ecfdf5; }
.dev-panel input.wrong   { border-color: #f87171; background: #fef2f2; animation: shake 0.35s; }

@keyframes shake {
  0%, 100% { transform: translateX(0); }
  20%      { transform: translateX(-8px); }
  40%      { transform: translateX(8px); }
  60%      { transform: translateX(-5px); }
  80%      { transform: translateX(5px); }
}

/* ---------- Overlays (start / end) ---------- */
.overlay {
  position: absolute;
  inset: 0;
  background: rgba(15, 23, 42, 0.6);
  backdrop-filter: blur(3px);
  display: flex;
  flex-direction: column; /* brand stacks above the panel on the start overlay */
  align-items: center;
  justify-content: center;
  gap: 4px;
  z-index: 20;
  /* On short viewports the brand + panel may together exceed the overlay
     height. Allow scroll so the CTAs never disappear off-screen. */
  overflow-y: auto;
  padding: 20px 0;
}
.overlay.hidden { display: none; }

/* Start-screen wordmark — same arched SVG textPath as ground + pause brands
   for identity consistency. Sits above the panel. Responsive width scales
   cleanly from 375px phones to the 620px desktop playfield. */
.start-brand {
  flex: 0 0 auto;
  width: min(86%, 420px);
  height: auto;
  pointer-events: none;
  user-select: none;
  overflow: visible;
}
.start-brand text {
  font-family: 'Lilita One', system-ui, sans-serif;
  font-size: 78px;
  fill: #fbbf24;
  paint-order: stroke fill;
  stroke: #7c2d12;
  stroke-width: 10;
  stroke-linejoin: round;
  letter-spacing: -1px;
  filter: drop-shadow(0 8px 14px rgba(0, 0, 0, 0.5));
}

.panel {
  /* Warm off-white — game-y, not clinical */
  background: #fffbeb;
  padding: 36px 28px 28px;
  border-radius: 22px;
  text-align: center;
  /* Scope to the playfield width on desktop (400px frame) while leaving a
     small gutter so the panel doesn't kiss the edges. */
  width: min(560px, calc(100% - 24px));
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
}
.panel h1 {
  font-size: 32px;
  margin-bottom: 10px;
  color: #1f2937;
  line-height: 1.2;
  letter-spacing: -0.3px;
}

/* End screen title has more swagger — bigger, bolder, with a playful
   text-shadow to suggest the bold-stroke feel of arcade game-over screens. */
#end-overlay .panel h1 {
  font-size: 40px;
  font-weight: 900;
  letter-spacing: -0.5px;
  color: #92400e;
  text-shadow: 0 3px 0 #fcd34d;
  margin-bottom: 6px;
}
.panel .tag {
  color: #6b7280;
  margin-bottom: 40px;
  font-size: 19px;
  line-height: 1.5;
}

/* Mascot — goat peeking above the headline */
.panel-mascot {
  font-size: 72px;
  line-height: 1;
  margin-top: -20px;
  margin-bottom: 10px;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.15));
}

/* Block label — tiny uppercase heading shared by each section */
.panel .block-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1.6px;
  color: #9ca3af;
  margin-bottom: 12px;
  font-weight: 700;
}

.panel .mic-note {
  font-size: 12px;
  color: #92400e;
  background: #fef3c7;
  padding: 8px 12px;
  border-radius: 8px;
  margin-bottom: 20px;
}
/* Hide the mic-note when it carries no message (supported browsers) */
.panel .mic-note:empty { display: none; }

/* Language choice block — margin matches the play block's internal rhythm */
.lang-choice {
  margin-bottom: 40px;
}
/* End-screen footer variant: de-emphasized, tighter, smaller flag pills.
   Sits under the Play Again hero so it doesn't compete with the main CTA. */
.lang-choice.lang-footer {
  margin-top: 24px;
  margin-bottom: 0;
}
.lang-choice.lang-footer .block-label {
  font-size: 10px;
  margin-bottom: 8px;
  opacity: 0.7;
}
.lang-choice.lang-footer .lang-btn {
  padding: 5px 12px;
  font-size: 12px;
}

/* Language picker — visible, clickable flag pills
   (.panel prefix needed so these rules beat .panel button specificity).
   Mobile: flex-wrap naturally, aiming for 4 per row via reduced button
   padding/font (see mobile override below). Flag + code stay side-by-side
   thanks to `white-space: nowrap` on the button.
   Desktop: grid with exactly 5 per row so 10 packs render as 2×5 on both
   start and end overlays regardless of panel width. */
.lang-picker {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}
@media (min-width: 501px) {
  .lang-picker {
    display: grid;
    grid-template-columns: repeat(5, max-content);
    gap: 12px;
    justify-content: center;
  }
}
.panel .lang-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  border: 2px solid #e5e7eb;
  border-radius: 999px;
  background: white;
  color: #374151;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.5px;
  cursor: pointer;
  /* Keep flag emoji + 2-letter code on one line even in narrow grid cells. */
  white-space: nowrap;
  transition: color 0.15s, background 0.15s, border-color 0.15s, transform 0.05s;
}
/* Mobile: shrink padding + font so 4 buttons (not 3) fit per row and the
   lang picker takes one fewer vertical row. */
@media (max-width: 500px) {
  .panel .lang-btn {
    padding: 6px 10px;
    font-size: 13px;
    gap: 4px;
    letter-spacing: 0.3px;
  }
}
.panel .lang-btn:hover {
  background: #fffbeb;
  border-color: #fcd34d;
  color: #374151;
}
.panel .lang-btn:active { transform: scale(0.97); }
.panel .lang-btn.active {
  /* Softer selected state so the flag buttons don't fight the primary CTA. */
  background: #fef3c7;
  border-color: #fbbf24;
  color: #78350f;
}
.panel .big-score {
  font-size: 56px;
  font-weight: 800;
  color: #d97706;
  line-height: 1.1;
  margin: 4px 0 20px;
}

/* End-screen: three-column stats. Each stat in an outlined card (warm amber
   border, transparent background) so it reads as a "stat" not a button. */
.stats-row {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 10px;
  margin: 4px 0 32px;
  text-align: center;
}
.stat {
  border: 1.5px solid rgba(217, 119, 6, 0.35);
  border-radius: 12px;
  padding: 14px 8px 12px;
  background: transparent;
}
.stat-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: #a16207;
  font-weight: 700;
  margin-bottom: 6px;
}
.stat-value {
  font-size: 26px;
  font-weight: 800;
  color: #78350f;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.stat-unit {
  font-size: 14px;
  font-weight: 600;
  opacity: 0.55;
  margin-left: 1px;
}

/* High score under the current score, Flappy-Bird style. */
.best-score {
  font-size: 13px;
  color: #92400e;
  opacity: 0.7;
  margin: -24px 0 32px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.best-score[hidden] { display: none; }
.best-score-label {
  text-transform: uppercase;
  letter-spacing: 1.2px;
  font-weight: 700;
  margin-right: 6px;
}
.best-score.new-best {
  opacity: 1;
  color: #d97706;
  font-weight: 700;
  animation: best-pulse 1.4s ease-in-out infinite;
}
@keyframes best-pulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.06); }
}
.panel button {
  padding: 22px 52px;
  font-size: 24px;
  font-weight: 700;
  background: #d97706;
  color: white;
  border: none;
  border-radius: 12px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, transform 0.05s;
}
.panel button:active { transform: scale(0.98); }
.panel button:hover { background: #b45309; }

/* Hero CTA — chunky amber pill with a "stamp" shadow below it. Shared by
   the start screen's Speak button and the end screen's Play Again. Amber +
   dark brown ties back to the LingoGoat wordmark (same fill + stroke).
   Green was deliberately dropped to avoid looking like Duolingo. */
.panel .cta-hero {
  display: block;
  width: 100%;
  padding: 20px 28px;
  font-size: 22px;
  font-weight: 800;
  letter-spacing: 0.3px;
  color: #451a03;
  background: #fbbf24;
  border: none;
  border-radius: 14px;
  box-shadow: 0 4px 0 #7c2d12;
  cursor: pointer;
  transition: transform 0.05s, background 0.15s, box-shadow 0.1s;
}
.panel .cta-hero:hover   { background: #fcd34d; }
.panel .cta-hero:active  { transform: translateY(2px); box-shadow: 0 2px 0 #7c2d12; }
/* End-screen replay sits below the share row; needs a gap there. The start
   screen's Speak button gets its gap from the block-label above it instead. */
.panel #replay.cta-hero { margin-top: 18px; }

/* Mode switch — text link under the green button. Stays unobtrusive but
   discoverable so "Play again" doesn't feel like a forced mode choice. */
.panel .mode-switch-link {
  display: block;
  margin: 12px auto 0;
  padding: 4px 12px;
  background: transparent;
  color: #6b7280;
  border: none;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: #9ca3af;
  text-underline-offset: 3px;
  transition: color 0.15s, text-decoration-color 0.15s;
}
.panel .mode-switch-link:hover {
  color: #d97706;
  text-decoration-color: #d97706;
}

/* End-screen share row — big pill buttons so the share action reads as a
   real CTA, not an afterthought. Two equal-weight buttons; X gets the brand
   dark fill, Copy link is a strong outline. */

/* Last-attempt diagnostic — sits between stats and share row on the end
   screen. Populated by endGame() with what STT heard / the player typed on
   the final round vs. the round's target. Helps the player post-mortem
   "was it me or STT?" especially on mobile where they die before reading
   the mid-round feedback. */
.last-attempt {
  margin: 12px 0 4px;
  padding: 10px 14px;
  background: rgba(0, 0, 0, 0.04);
  border-radius: 10px;
  font-size: 15px;
  color: #374151;
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  flex-wrap: wrap;
}
.last-attempt-icon { opacity: 0.7; }
.last-attempt-heard { font-weight: 600; color: #1f2937; }
.last-attempt-arrow { opacity: 0.4; }
.last-attempt-target { font-weight: 600; color: #065f46; }
.last-attempt-emoji { font-size: 18px; }
.last-attempt-verdict { font-weight: 700; font-size: 18px; }
.last-attempt-verdict.ok   { color: #059669; }  /* green — STT matched */
.last-attempt-verdict.nope { color: #dc2626; }  /* red — STT didn't match */

.share-row {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 20px;
}
.panel .share-btn {
  flex: 1;
  min-width: 0;
  padding: 14px 18px;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.3px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.05s;
}
.panel .share-btn:active { transform: scale(0.97); }
.panel .share-btn.share-x {
  background: #0f172a;
  color: #fff;
  border: 2px solid #0f172a;
}
.panel .share-btn.share-x:hover {
  background: #1e293b;
  border-color: #1e293b;
}
.panel .share-btn.share-copy {
  background: #fff;
  color: #1f2937;
  border: 2px solid #1f2937;
}
.panel .share-btn.share-copy:hover {
  background: #f3f4f6;
  color: #0f172a;
  border-color: #0f172a;
}
.panel .share-btn.copied {
  background: #dcfce7 !important;
  color: #166534 !important;
  border-color: #16a34a !important;
}

/* Stack the two start buttons; promote the primary, de-emphasize the secondary */
.start-buttons {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}
.panel button.secondary {
  background: transparent;
  color: #6b7280;
  padding: 6px 14px;
  font-size: 13px;
  font-weight: 500;
  /* Permanent underline so it reads as a clickable link on touch devices too. */
  text-decoration: underline;
  text-decoration-color: #9ca3af;
  text-underline-offset: 3px;
}
.panel button.secondary:hover {
  background: transparent;
  color: #d97706;
  text-decoration-color: #d97706;
}

/* Type mode — hide the voice UI so keyboard users aren't confused */
.game.mode-type .mic-btn { display: none; }

/* ---------- Author signature (bottom-right) ---------- */
/* Fixed so it lives in the desktop letterbox; sits above gameplay on mobile.
   Subtle glass chip with a hover amber accent on the handle. */
.signature {
  position: fixed;
  bottom: 0;
  right: 0;
  padding: 6px 14px;
  font-size: 13px;
  line-height: 1.2;
  color: #fde68a;
  /* Solid-enough background so the chip reads clearly on every screen,
     including over the start/end overlay's blur. Dropping the old
     backdrop-filter removes a stacked-blur chain that iOS / some browsers
     render as "the link looks behind the overlay" even though z-index has
     it on top. */
  background: rgba(17, 24, 39, 0.85);
  border-top: 1px solid rgba(255, 255, 255, 0.10);
  border-left: 1px solid rgba(255, 255, 255, 0.10);
  border-top-left-radius: 12px;
  text-decoration: none;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
  transition: background 0.2s ease, color 0.2s ease;
  /* Above the start/end overlay (z-index 20) so the link stays clickable on
     every screen state. */
  z-index: 30;
}
.signature:hover {
  background: rgba(17, 24, 39, 0.95);
}
.signature span { color: inherit; transition: color 0.2s ease; }
.signature:hover span { color: #fbbf24; }

/* ---------- Small screens ---------- */
@media (max-width: 500px) {
  .player         { --size: 48px; }
  .platform       { width: 118px; height: 22px; }
  .platform-emoji { font-size: 42px; }
  /* Move altitude + score to the bottom corners (altitude left, score right)
     so the top-right is free for the pause button alone. Prevents the
     three-pill cluster from getting clipped on narrow phones. */
  .hud-cluster {
    top: auto;
    bottom: 20px;
    left: 20px;
    right: 20px;
    justify-content: space-between;
  }
  .pause-btn {
    position: fixed;
    top: 20px;
    right: 20px;
    z-index: 16;
  }
  /* Align the mic's bottom with the altitude + score badges' bottoms. They
     all sit at bottom:20 on mobile, with altitude left, mic center, score
     right (space-between). The mic (68px) is wider than the badges but the
     viewport has enough horizontal room that they don't overlap. */
  .controls {
    bottom: 20px;
  }
  /* Hide the signature during active gameplay on mobile. Its bottom-right
     corner overlaps the centered mic / typing input and the score badge.
     It re-appears whenever a start/end overlay is up, which is where the
     credit actually matters. */
  .signature { display: none; }
  body:has(.overlay:not(.hidden)) .signature { display: block; }
}
