/* Flappy Hranol — page shell, HUD and menu styling.
   The gameplay itself is drawn on the canvas; everything here is the frame
   around it plus the accessible HTML controls.

   Design system: every colour, corner shape and shadow used by a UI element
   is driven from the tokens below, sampled directly from the logo artwork
   (assets/flappy-hranol-logo.png) and shared with the canvas gameplay
   palette in src/scene.js — a deep navy frame, a Slovak red/white/blue
   tricolour, and an angular "cut-corner sticker" silhouette instead of the
   old fully-rounded pill shapes. */

:root {
  /* --- Slovak tricolour + navy frame, colour-matched to the logo --------- */
  --fh-navy-950: #081020;                    /* logo outline colour, measured from the artwork */
  --fh-navy-900: #0b1d3b;                    /* app background base */
  --fh-navy-800: #12294f;                    /* panel fill */
  --fh-navy-700: #1a3d72;                    /* panel fill, lighter step / hover */
  --fh-blue-700: #0b4ea2;                    /* deep blue — matches the logo + src/scene.js PALETTE.blue */
  --fh-blue-500: #2f7bdc;                    /* bright blue accent */
  --fh-red-700: #8f1414;                     /* deep red — the logo's extrusion shadow */
  --fh-red-600: #d7242e;                     /* primary red — matches the logo + src/scene.js PALETTE.red */
  --fh-red-500: #ef4048;                     /* bright red accent / hover */
  --fh-white: #ffffff;
  --fh-cream: #f3ece0;                       /* matches the canvas ground colour */
  --fh-text: #ffffff;
  --fh-text-dim: rgba(255, 255, 255, 0.68);
  --fh-shadow: rgba(3, 9, 20, 0.55);
  --fh-stroke: rgba(255, 255, 255, 0.16);
  --fh-focus: #ffffff;

  /* --- Typography ---------------------------------------------------------
     System stacks only — no webfont, no third-party dependency, no network
     request, full Slovak diacritic coverage (á ä č ď é í ĺ ľ ň ó ô ŕ š ť ú ý ž)
     on every platform this ships to. */
  --fh-font-body: "Trebuchet MS", "Segoe UI", system-ui, -apple-system, sans-serif;
  --fh-font-display: "Arial Black", "Arial Narrow Bold", "Segoe UI", system-ui, sans-serif;

  /* --- Shape ---------------------------------------------------------------
     A shared asymmetric-corner "sticker" silhouette (sharp top-left and
     bottom-right, rounded top-right and bottom-left) used by every card and
     button instead of the old uniform rounded pill, to echo the logo's own
     angular, die-cut outline.

     This is deliberately `border-radius`, not `clip-path`: clip-path clips
     an element's *entire* rendered output — including anything a `filter`,
     `outline` or `box-shadow` would normally paint outside the border box —
     to the polygon. On a focused or shadowed button that silently deletes
     the focus ring and flattens the depth shadow's overflow, since neither
     can extend past the clipped edge. border-radius has none of that
     problem, so outline/box-shadow work exactly as anywhere else. */
  --fh-corner-lg: 22px;
  --fh-corner-sm: 4px;
  --fh-corner-lg-tight: 10px;

  --radius: 20px; /* the outer #stage frame only — the "device" shape, not a UI card */
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  margin: 0;
}

body {
  font-family: var(--fh-font-body);
  color: var(--fh-text);
  background:
    radial-gradient(120% 80% at 50% 0%, var(--fh-navy-700) 0%, var(--fh-navy-900) 55%, var(--fh-navy-950) 100%);
  overflow: hidden;
  overscroll-behavior: none;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

#app {
  min-height: 100vh;          /* fallback for browsers without dynamic units */
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding:
    max(6px, env(safe-area-inset-top))
    max(6px, env(safe-area-inset-right))
    max(6px, env(safe-area-inset-bottom))
    max(6px, env(safe-area-inset-left));
}

/* The playfield fills what the viewport allows, between 1:1.6 and 1:1.9.
   The game keeps a constant world *width*, so a taller stage simply means more
   sky — the spacing and timing of the columns never change.
   Each dimension is declared twice: the vh line is the fallback, the svh line
   wins where small-viewport units exist. This deliberately uses `svh`/`svw`
   (sized for the address bar *shown*), not `dvh`/`dvw` (which tracks it live):
   `dvh` means the stage's own box resizes every time the address bar
   animates in or out during a scroll or a tap, and each of those resizes
   fires the canvas's ResizeObserver — which rebuilds every offscreen
   background/column/cloud tile from scratch (measured at up to a few ms per
   rebuild on desktop, more on mobile CPUs). `svh` never fluctuates, so
   nothing about the browser's chrome can retrigger that rebuild mid-run.
   The trade-off is cosmetic only: the stage doesn't grow into the extra
   space when the address bar auto-hides. */
#stage {
  position: relative;
  width: min(100vw - 12px, 460px, (100vh - 52px) * 0.625);
  width: min(100svw - 12px, 460px, (100svh - 52px) * 0.625);
  height: min(100vh - 52px, (100vw - 12px) * 1.9, 874px);
  height: min(100svh - 52px, (100svw - 12px) * 1.9, 874px);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow:
    0 26px 60px var(--fh-shadow),
    0 0 0 1px var(--fh-stroke) inset;
  /* `manipulation` — not `none` — so taps on the overlay buttons inside the
     stage still produce a click. Double-tap zoom is still suppressed. */
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
}

/* The actual gameplay surface is the only element that swallows every gesture. */
#game {
  display: block;
  width: 100%;
  height: 100%;
  background: #5cb8ee;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

.credit {
  margin: 0;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: rgba(255, 255, 255, 0.4);
  text-align: center;
}

/* ------------------------------------------------------------------- HUD */

#hud {
  position: absolute;
  inset: 10px 10px auto auto;
  display: flex;
  gap: 8px;
  z-index: 3;
}

.icon-button {
  width: 46px;                 /* >= the 44px minimum touch target */
  height: 46px;
  display: grid;
  place-items: center;
  font: inherit;
  font-size: 17px;
  color: var(--fh-white);
  background: linear-gradient(160deg, var(--fh-navy-700), var(--fh-navy-900));
  border: 2px solid rgba(255, 255, 255, 0.28);
  border-radius: 50%;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  box-shadow: 0 6px 14px var(--fh-shadow);
  transition: transform 0.15s ease, border-color 0.2s ease, opacity 0.25s ease;
}

.icon-button:hover {
  border-color: var(--fh-blue-500);
}

.icon-button:active {
  transform: scale(0.92);
}

/* The pause control only exists while a run is in progress. */
#btn-pause {
  opacity: 0;
  pointer-events: none;
}

body[data-state="playing"] #btn-pause {
  opacity: 1;
  pointer-events: auto;
}

body[data-state="loading"] #hud {
  opacity: 0;
  pointer-events: none;
}

/* -------------------------------------------------------------- overlays */

/* An inactive overlay is invisible, un-hittable AND removed from hit-testing by
   `visibility`, so it can never sit invisibly on top of the playfield and
   swallow taps. The JS additionally marks it `inert`. */
.overlay {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: max(20px, env(safe-area-inset-top)) max(20px, env(safe-area-inset-right))
    max(20px, env(safe-area-inset-bottom)) max(20px, env(safe-area-inset-left));
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.28s ease, visibility 0s linear 0.28s;
  z-index: 2;
}

.overlay--dim {
  background: rgba(4, 10, 22, 0.55);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
}

/* The shared card: a deep-navy angular sticker with a bright top edge. */
.panel {
  position: relative;
  width: min(100%, 320px);
  padding: 26px 22px 22px;
  text-align: center;
  background: linear-gradient(165deg, var(--fh-navy-700) 0%, var(--fh-navy-800) 45%, var(--fh-navy-900) 100%);
  border: 2px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--fh-corner-lg) var(--fh-corner-sm) var(--fh-corner-lg) var(--fh-corner-sm);
  /* Clips the ::before flourish below to the same rounded shape. Safe here —
     unlike clip-path on a *button*, this is a card with generous internal
     padding, so it never clips a focused/shadowed control living inside it. */
  overflow: hidden;
  box-shadow: 0 18px 30px var(--fh-shadow);
  backdrop-filter: blur(10px) saturate(1.2);
  -webkit-backdrop-filter: blur(10px) saturate(1.2);
  transform: translateY(14px) scale(0.96);
  opacity: 0;
  transition: transform 0.32s cubic-bezier(0.2, 0.9, 0.25, 1), opacity 0.28s ease;
}

/* A thin tricolour flourish along the top edge — the one deliberate
   decorative accent, purely CSS, no extra DOM, never intercepts a tap. Uses
   --fh-cream rather than pure white for its light third: that is the exact
   colour the canvas ground is painted in (src/scene.js PALETTE.stone), so
   the DOM chrome and the gameplay art share a real colour, not just a
   similar one. */
.panel::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 5px;
  background: linear-gradient(90deg, var(--fh-cream) 0 33%, var(--fh-blue-500) 33% 66%, var(--fh-red-600) 66% 100%);
  pointer-events: none;
}

/* The start screen leaves the middle of the playfield clear so the character
   can float in place between the logo and the controls. */
.overlay--menu {
  place-items: stretch;
  padding: 0;
}

.menu-layout {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  height: 100%;
  /* The top-right HUD (sound / pause) is a fixed ~56px-tall band — 46px icon
     plus its 10px inset — independent of screen size. The 60px floor keeps
     the logo below it on every viewport; without it, a tall-but-narrow phone
     (where the height-relative clamp() below stays small) let the wide logo
     reach up under the sound button. */
  padding:
    max(clamp(16px, 5%, 34px), env(safe-area-inset-top), 60px)
    max(20px, env(safe-area-inset-right))
    max(clamp(18px, 6%, 38px), env(safe-area-inset-bottom))
    max(20px, env(safe-area-inset-left));
}

/* The logo mark. Deliberately its own element — never `.panel` — so it has no
   background, border or shadow by construction and can never inherit one
   through a class ordering accident. */
.brand {
  width: 100%;
}

.panel--start {
  width: min(100%, 300px);
  padding: 20px 20px 18px;
}

body[data-state="loading"] #loading,
body[data-state="menu"] #menu,
body[data-state="paused"] #pause,
body[data-state="gameover"] #gameover {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 0.28s ease, visibility 0s;
}

body[data-state="loading"] #loading .panel,
body[data-state="menu"] #menu .panel,
body[data-state="paused"] #pause .panel,
body[data-state="gameover"] #gameover .panel {
  transform: translateY(0) scale(1);
  opacity: 1;
}

/* The game-over card waits for the crash animation to land. */
body[data-state="gameover"] #gameover {
  transition-delay: 0.45s;
}

body[data-state="gameover"] #gameover .panel {
  transition-delay: 0.45s;
}

/* ------------------------------------------------------------ menu content */

/*
 * The name mark. `.logo-heading` is the <h1> itself: by default (and forever,
 * if the image never loads) it just shows its own "Flappy Hranol" text styled
 * to sit directly on the sky, echoing the artwork's own white-to-blue-to-red
 * lettering with a flat (non-blurred) navy extrusion shadow. `ui.setLogo()`
 * replaces that text with the `.logo-img` once the artwork has preloaded —
 * none of the properties below affect an <img>, so there is no visual bleed
 * between the two states.
 */
.logo-heading {
  margin: 0 0 4px;
  font-family: var(--fh-font-display);
  font-size: clamp(30px, 10vw, 44px);
  font-weight: 900;
  line-height: 1.05;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  /* Plain solid colour first: this is the text-fallback path (the JS never
     loaded the logo, or the browser doesn't support gradient text below), so
     it must always stay legible on its own. */
  color: var(--fh-white);
  text-shadow:
    0 3px 0 var(--fh-navy-950),
    0 6px 0 rgba(8, 16, 32, 0.55),
    0 12px 26px var(--fh-shadow);
}

/* Gradient-fill enhancement, only where the browser can actually clip a
   background to text — guarded so a browser without support keeps the solid
   white fallback above instead of silently rendering invisible text. */
@supports (background-clip: text) or (-webkit-background-clip: text) {
  .logo-heading {
    background: linear-gradient(180deg, var(--fh-white) 0%, var(--fh-white) 42%, var(--fh-blue-500) 62%, var(--fh-red-600) 100%);
    background-clip: text;
    -webkit-background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
  }
}

/* Sized relative to its parent (already responsive, bounded by #stage's own
   width formula) rather than raw viewport units, so it can never overflow the
   panel it lives in regardless of screen shape — 380px is a ceiling that only
   matters if the stage's own max-width is ever raised. The third `min()` term
   is a *height* budget instead (18% of the viewport, divided by the artwork's
   own height/width ratio): width-only sizing looks right on a phone, where a
   taller viewport also means a taller stage, but not on a short, wide desktop
   window, where the stage stays close to its max width regardless of height —
   there, the logo kept its full size while the canvas-drawn head idling below
   it had ever less room to clear it. */
.logo-img {
  display: block;
  width: min(86%, 380px, calc(18vh / 0.4717));
  height: auto;
  margin: 0 auto;
}

/* Back inside .brand, directly under the wordmark and above the idle head —
   centred (unlike the old left-aligned default) to match the logo's own
   centring. */
.tagline {
  margin: 12px 0 0;
  font-size: 14px;
  font-weight: 600;
  text-align: center;
  color: var(--fh-white);
  text-shadow: 0 2px 8px var(--fh-shadow);
}

.panel-title {
  margin: 0 0 14px;
  font-family: var(--fh-font-display);
  font-size: clamp(24px, 7.5vw, 32px);
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--fh-white);
  text-shadow: 0 2px 0 var(--fh-navy-950);
}

/* New-record ribbon — tricolour instead of the old unrelated yellow, so a
   celebratory moment still reads as the same brand rather than a different
   one. */
.record {
  margin: 0 0 12px;
  padding: 7px 16px;
  display: inline-block;
  font-family: var(--fh-font-display);
  font-weight: 900;
  font-size: 13px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--fh-white);
  background: linear-gradient(120deg, var(--fh-red-600) 0%, var(--fh-red-600) 45%, var(--fh-blue-700) 55%, var(--fh-blue-700) 100%);
  border: 2px solid rgba(255, 255, 255, 0.55);
  border-radius: var(--fh-corner-lg-tight) var(--fh-corner-sm) var(--fh-corner-lg-tight) var(--fh-corner-sm);
  text-shadow: 0 1px 0 var(--fh-navy-950);
  animation: pop-in 0.5s cubic-bezier(0.2, 1.4, 0.4, 1) both;
}

/* A class-level `display` would otherwise beat the UA rule for [hidden]. */
.record[hidden] {
  display: none;
}

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

.scoreboard {
  display: flex;
  gap: 10px;
  margin: 4px 0 18px;
}

.score-cell {
  flex: 1;
  padding: 10px 6px;
  background: rgba(255, 255, 255, 0.07);
  border: 2px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--fh-corner-lg-tight) var(--fh-corner-sm) var(--fh-corner-lg-tight) var(--fh-corner-sm);
}

.score-cell dt {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fh-text-dim);
}

.score-cell dd {
  margin: 2px 0 0;
  font-family: var(--fh-font-display);
  font-size: 28px;
  font-weight: 900;
  color: var(--fh-white);
}

.best-line {
  margin: 16px 0 0;
  font-size: 13px;
  color: var(--fh-text-dim);
}

.best-line strong {
  color: var(--fh-white);
  font-size: 16px;
}

.hint {
  margin: 14px 0 0;
  font-size: 12.5px;
  color: var(--fh-text-dim);
}

kbd {
  padding: 2px 7px;
  font: inherit;
  font-size: 11px;
  color: var(--fh-white);
  background: rgba(255, 255, 255, 0.14);
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-bottom-width: 2px;
  border-radius: 5px;
}

/* --------------------------------------------------------------- buttons */

/*
 * One shared angular silhouette for every button, matching the logo's own
 * cut-corner sticker outline. Plain `border-radius` + `box-shadow` — not
 * `clip-path` — specifically so that a focus ring can still render (see the
 * `:focus-visible` note further down for why that combination matters).
 */
.button {
  appearance: none;
  position: relative;
  min-height: 48px;
  min-width: 48px;
  padding: 12px 24px;
  font: inherit;
  font-family: var(--fh-font-display);
  font-weight: 900;
  font-size: 16px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--fh-white);
  background: linear-gradient(160deg, var(--fh-blue-500), var(--fh-blue-700));
  border: 2px solid rgba(255, 255, 255, 0.4);
  border-radius: var(--fh-corner-lg-tight) var(--fh-corner-sm) var(--fh-corner-lg-tight) var(--fh-corner-sm);
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  /* Slovak labels (e.g. "POKRAČOVAŤ") run noticeably longer than their
     English source ("Resume"). This is the safety net: if a future label is
     still too long for its button even after the responsive sizing below,
     it wraps instead of visually overflowing the button's edge. */
  overflow-wrap: anywhere;
  text-wrap: balance;
  text-shadow: 0 2px 0 rgba(8, 16, 32, 0.55);
  box-shadow: 0 6px 14px var(--fh-shadow);
  transition: transform 0.12s ease, box-shadow 0.2s ease, filter 0.2s ease, background 0.2s ease;
}

.button:hover {
  box-shadow: 0 8px 18px var(--fh-shadow);
  filter: brightness(1.08);
}

.button:active {
  transform: translateY(2px) scale(0.97);
  box-shadow: 0 3px 8px var(--fh-shadow);
}

.button:disabled {
  cursor: not-allowed;
  filter: none;
  opacity: 0.45;
  transform: none;
}

/* The strongest interactive element on screen: a vivid red face (matching the
   logo's own red-white-blue lettering) instead of the same blue as every
   secondary action. */
.button--primary {
  background: linear-gradient(160deg, var(--fh-red-500), var(--fh-red-600) 55%, var(--fh-red-700));
  border-color: rgba(255, 255, 255, 0.55);
}

.button--ghost {
  min-height: 44px;
  margin-top: 10px;
  padding: 10px 20px;
  font-size: 13px;
  background: transparent;
  border-color: rgba(255, 255, 255, 0.32);
  box-shadow: none;
}

.button--ghost:hover {
  background: rgba(255, 255, 255, 0.08);
  filter: none;
}

#btn-play {
  width: 100%;
  min-height: 56px;
  font-size: 19px;
  padding: 16px 24px;
}

.button-row {
  display: flex;
  gap: 10px;
  justify-content: center;
}

/* Paired buttons only get half the panel's width, and Slovak labels
   ("POKRAČOVAŤ", "HRAŤ ZNOVA") run longer than the English they replaced.
   The panel itself is capped at 320px CSS-wide, so above roughly a 360px
   viewport these buttons stop growing at all — sizing off raw vw would keep
   climbing well past that point and reopen the overflow. Measured against
   the longest label ("Pokračovať"): 13px is the largest size that still
   fits inside the ~131px a button gets once the panel hits its cap. */
.button-row .button {
  flex: 1;
  min-width: 0;
  padding-left: 8px;
  padding-right: 8px;
  font-size: clamp(12px, 3.4vw, 13px);
  letter-spacing: 0.01em;
}

/* A plain, universal keyboard-focus ring. Earlier drafts of the button shape
   used `clip-path`, which clips an element's entire painted output —
   including any `outline` that would otherwise extend past the box — so the
   ring silently vanished on every button despite `:focus-visible` correctly
   matching and the computed style looking right. Buttons now shape their
   corners with `border-radius` instead specifically so this ordinary
   outline keeps working like it would anywhere else. */
:focus-visible {
  outline: 3px solid var(--fh-focus);
  outline-offset: 3px;
}

/* ------------------------------------------------------------ leaderboard */

/* Sits above the state-driven overlays (all at z-index 2) and is shown/hidden
   by its own `.is-open` class rather than a `body[data-state]` rule, since it
   can be reached from more than one of those states. */
#leaderboard {
  z-index: 5;
}

#leaderboard.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 0.22s ease, visibility 0s;
}

#leaderboard.is-open .panel {
  transform: translateY(0) scale(1);
  opacity: 1;
}

.panel--leaderboard {
  width: min(100%, 340px);
  max-height: min(78dvh, 78vh, 600px);
  display: flex;
  flex-direction: column;
}

.lb-name-form {
  display: flex;
  gap: 8px;
  margin: 0 0 14px;
}

/* A class-level `display` would otherwise beat the UA [hidden] rule — the
   same fix already applied to .record[hidden] above. */
.lb-name-form[hidden] {
  display: none;
}

.lb-name-input {
  flex: 1;
  min-width: 0;
  padding: 10px 12px;
  font: inherit;
  font-size: 14px;
  color: var(--fh-white);
  background: rgba(255, 255, 255, 0.09);
  border: 2px solid rgba(255, 255, 255, 0.24);
  border-radius: var(--fh-corner-lg-tight) var(--fh-corner-sm) var(--fh-corner-lg-tight) var(--fh-corner-sm);
}

.lb-name-input::placeholder {
  color: var(--fh-text-dim);
}

.lb-name-save {
  min-height: 44px;
  padding: 10px 16px;
  font-size: 13px;
}

.lb-name-current {
  margin: 0 0 14px;
  font-size: 13px;
  color: var(--fh-text-dim);
}

.lb-name-current strong {
  color: var(--fh-white);
}

.lb-name-edit {
  margin-left: 8px;
  padding: 2px 8px;
  font: inherit;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--fh-blue-500);
  background: transparent;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 6px;
  cursor: pointer;
}

.lb-own-standing {
  margin: 0 0 10px;
  min-height: 16px;
  font-size: 13px;
  font-weight: 600;
  color: var(--fh-white);
  text-align: center;
}

.lb-list-wrap {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  margin: 0 0 16px;
  padding: 4px;
  border-radius: var(--fh-corner-sm);
  background: rgba(0, 0, 0, 0.14);
}

.lb-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.lb-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  font-size: 13px;
  background: rgba(255, 255, 255, 0.05);
  border-radius: var(--fh-corner-sm);
}

.lb-row.is-own {
  background: rgba(47, 123, 220, 0.32);
  border: 1px solid rgba(255, 255, 255, 0.3);
}

.lb-rank {
  flex: 0 0 36px;
  font-family: var(--fh-font-display);
  font-weight: 900;
  color: var(--fh-text-dim);
}

.lb-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
  color: var(--fh-white);
}

.lb-score {
  flex: 0 0 auto;
  font-family: var(--fh-font-display);
  font-weight: 900;
  color: var(--fh-white);
}

.lb-empty,
.lb-status {
  padding: 20px 10px;
  font-size: 13px;
  text-align: center;
  color: var(--fh-text-dim);
}

/* --------------------------------------------------------------- loading */

.spinner {
  width: 34px;
  height: 34px;
  margin: 0 auto 14px;
  border: 3px solid rgba(255, 255, 255, 0.22);
  border-top-color: var(--fh-blue-500);
  border-radius: 50%;
  animation: spin 0.9s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.loading-text {
  margin: 0;
  font-size: 14px;
  letter-spacing: 0.08em;
  color: var(--fh-text-dim);
}

/* ----------------------------------------------------------------- toast */

#toast {
  position: absolute;
  left: 50%;
  bottom: 18px;
  z-index: 4;
  margin: 0;
  padding: 10px 18px;
  max-width: 88%;
  font-size: 13px;
  font-weight: 600;
  text-align: center;
  color: var(--fh-white);
  background: linear-gradient(165deg, var(--fh-navy-700), var(--fh-navy-900));
  border: 2px solid rgba(255, 255, 255, 0.24);
  border-radius: var(--fh-corner-lg-tight) var(--fh-corner-sm) var(--fh-corner-lg-tight) var(--fh-corner-sm);
  box-shadow: 0 10px 24px var(--fh-shadow);
  transform: translate(-50%, 12px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease;
}

#toast.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  .overlay,
  .panel,
  .button,
  .icon-button,
  #leaderboard,
  #toast {
    transition-duration: 0.01ms;
  }

  .record {
    animation: none;
  }

  .spinner {
    animation-duration: 3s;
  }
}

/* Short, wide windows — mostly phones in landscape: drop the outer chrome so
   the playfield keeps as much height as it can. */
@media (max-height: 560px) {
  .credit {
    display: none;
  }

  #app {
    padding: 0;
    gap: 0;
  }

  #stage {
    width: min(100vw, 460px, 100vh * 0.625);
    width: min(100svw, 460px, 100svh * 0.625);
    height: min(100vh, 100vw * 1.9);
    height: min(100svh, 100svw * 1.9);
    border-radius: 0;
  }

  .panel {
    padding: 18px 18px 16px;
  }

  .scoreboard {
    margin-bottom: 12px;
  }

  /* The idle head between the logo and the Play card is drawn on <canvas> at
     a fixed fraction of the stage height, not part of this flow, so it can't
     be pushed out of the way — on a short landscape phone there just isn't
     room for a full-size wordmark, a tagline AND that gap at once. Drop the
     tagline (the hint below the Play button already states the controls)
     and shrink the logo further so the head has room to idle without
     touching either the wordmark above or the card below. */
  .tagline {
    display: none;
  }

  .logo-img {
    width: min(86%, 380px, calc(13vh / 0.4717));
  }

  .panel--start {
    padding-top: 12px;
  }
}

/*
 * Touch devices: drop the backdrop blurs.
 *
 * A backdrop-filter on a full-bleed layer forces the compositor to re-blur the
 * whole playfield behind it. On a phone that turns the game-over card sliding
 * in into a visibly janky transition, and it buys nothing on a small screen.
 * Solid fills read the same and cost nothing.
 */
@media (hover: none), (pointer: coarse) {
  .overlay--dim {
    background: rgba(4, 10, 22, 0.72);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  .panel {
    background: linear-gradient(165deg, var(--fh-navy-700) 0%, var(--fh-navy-800) 40%, var(--fh-navy-950) 100%);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  /* .icon-button's own backdrop-filter was removed everywhere (see its base
     rule above) rather than only here: unlike the overlays, it sits over
     the canvas for the entire length of every run, not just a brief
     transition -- a continuous per-frame recomposite cost on desktop too,
     for a blur that was purely decorative on top of an already-opaque
     button background. */
}
