/* Squirm.io — one stylesheet for the game chrome and the content pages.
   Keeping them together is deliberate: the HUD and the prose share a type
   scale and a palette, and two stylesheets is how they stop matching. */

:root {
  /*
   * Near-black and lime.
   *
   * The palette was navy and mint, which is calm — and calm is the wrong note
   * for an arcade game about being eaten. Lime on near-black is the genre's
   * own language, it survives being scaled down to a portal thumbnail, and it
   * is the one colour that cannot be confused with slither.io's blue.
   */
  --ink: #07090F;
  --ink-2: #10141F;
  --ink-3: #1A2030;
  --line: #262E42;
  --text: #F2F5FA;
  --text-dim: #9AA6BE;
  --accent: #C6F536;
  --accent-2: #A8DC18;
  --accent-ink: #10160A;
  --hot: #FF3B5C;
  --warn: #FFC53D;
  --radius: 16px;
  --radius-sm: 10px;
  /*
   * The reading measure, as a fixed length rather than ch.
   *
   * It was 74ch, and ch is relative to the element's OWN font size — so the
   * same variable gave a 46px h1 a max-width of ~1850px and a 17px paragraph
   * ~790px. The heading was effectively unconstrained, which is why it ran the
   * full width of the viewport above a much narrower column.
   */
  --max: 46rem;
  /* The game panel is a picture, not prose — it gets more room than the text. */
  --game-max: 1180px;
  /* The header is sticky and the arena sits directly under it, so its height
     is a layout constant rather than a guess. Keep it in step with the
     header's own padding and wordmark height. */
  --header-h: 68px;
  /* The touch joystick's size and inset. The sound dock stacks above it on a
     phone, so both need to read from one place. */
  /* The dock's corner: its own width plus the inset either side. The menu
     overlay keeps its panel out of this lane. */
  /* The dock's own footprint. No longer reserved as a lane — see .overlay —
     but check-layout uses it to assert the panel clears the dock. */
  --dock-lane: 148px;
  --joy-size: 116px;
  --joy-inset: 24px;
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-display: "Trebuchet MS", "Segoe UI", system-ui, sans-serif;
}

* { box-sizing: border-box; }

/* The overlays, joystick and boost button all set `display`, which beats the
   user-agent rule for [hidden] regardless of specificity — author styles always
   win over UA styles. Without this, every "hidden" panel renders on load. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ink);
  color: var(--text);
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.65;
  overflow-x: hidden;
}

img, svg, canvas { max-width: 100%; }

a { color: var(--accent); text-underline-offset: 3px; }
a:hover { color: var(--accent-2); }

.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;
}

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--accent); color: #06210F; padding: 10px 16px; border-radius: 0 0 var(--radius-sm) 0;
  font-weight: 700;
}
.skip-link:focus { left: 0; }

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

/* ------------------------------------------------------------ site chrome */
/* ------------------------------------------------------------- the header */
/*
 * The bar the genre actually uses, one notch up.
 *
 * Checked against the field before rebuilding it: the pure-game sites
 * (slither.io, snake.io, wormax.io, agar.io, diep.io) ship no header at all —
 * the game is the page. The ones that carry content pages, as this site does,
 * all run the same bar: chunky logo left, short uppercase nav, and a loud way
 * back into the game. Little Big Snake leads with PLAY NOW; Worms Zone leads
 * with Home. So the shape here is settled convention, and the polish is ours:
 * a sticky glass bar over the arena, display type at nav size, and one lime
 * accent spent on the two things that matter — where you are, and how to play.
 */
.siteheader {
  position: sticky; top: 0; z-index: 30;
  background: rgba(7, 9, 15, 0.82);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 1px 0 rgba(198, 245, 54, 0.16), 0 10px 30px -22px #000;
}
@supports (backdrop-filter: blur(1px)) {
  .siteheader { background: rgba(7, 9, 15, 0.66); backdrop-filter: blur(14px) saturate(140%); }
}
.siteheader__inner {
  display: flex; align-items: center; gap: 10px clamp(14px, 3vw, 30px); flex-wrap: wrap;
  /*
   * Centred on the same column as the game panel below it. The header used to
   * be full-bleed with its contents pinned to the left, so on a wide screen the
   * wordmark sat alone with 1,380px of empty bar beside it while everything
   * underneath was centred — which is what "it is all to the left" was.
   */
  /*
   * The bar spans the window, because the arena under it now does too. It used
   * to be capped at the game box's 1180px so the two lined up; with a
   * full-bleed arena that cap left the wordmark floating in the middle of
   * nothing. Capped well wide so it does not stretch on an ultrawide.
   */
  width: min(1600px, 100%);
  margin-inline: auto;
  padding: 12px clamp(16px, 3vw, 32px);
}
.siteheader__brand { text-decoration: none; flex: 0 0 auto; display: block; }
.wordmark {
  /* The real wordmark, sized by height so the aspect ratio does the rest. */
  display: block;
  height: 40px;
  width: auto;
  transition: transform 0.16s ease;
}
.siteheader__brand:hover .wordmark { transform: translateY(-1px) scale(1.02); }

.siteheader__nav { flex: 1 1 auto; min-width: 0; }
.siteheader__nav ul {
  display: flex; gap: 2px clamp(4px, 1.4vw, 14px); list-style: none; margin: 0; padding: 0;
  /* Five items on a phone beat a hidden menu: a tap you cannot see is a tap
     you do not make, and this is the whole internal link graph. */
  overflow-x: auto; scrollbar-width: none;
}
.siteheader__nav ul::-webkit-scrollbar { display: none; }
.siteheader__nav li { flex: 0 0 auto; }
.siteheader__nav a {
  position: relative; display: block; padding: 8px 10px 9px;
  font-family: var(--font-display); font-weight: 700;
  font-size: 0.86rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--text-dim); text-decoration: none; white-space: nowrap;
  transition: color 0.14s ease;
}
/* The underline is a scanline: lit under where you are, sweeping in on hover. */
.siteheader__nav a::after {
  content: ""; position: absolute; left: 10px; right: 10px; bottom: 2px; height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--accent) 22%, var(--accent) 78%, transparent);
  transform: scaleX(0); transform-origin: left; transition: transform 0.18s ease;
}
.siteheader__nav a:hover { color: var(--text); }
.siteheader__nav a:hover::after { transform: scaleX(1); }
.siteheader__nav a[aria-current="page"] { color: var(--text); }
.siteheader__nav a[aria-current="page"]::after { transform: scaleX(1); }

.siteheader__cta {
  flex: 0 0 auto;
  display: inline-block; padding: 10px 20px 11px; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink); text-decoration: none;
  font-family: var(--font-display); font-weight: 900;
  font-size: 0.9rem; letter-spacing: 0.05em; text-transform: uppercase;
  box-shadow: 0 0 0 1px rgba(198, 245, 54, 0.5), 0 8px 24px -12px var(--accent);
  transition: background 0.14s ease, transform 0.1s ease, box-shadow 0.14s ease;
}
.siteheader__cta:hover {
  background: #D6FF4A; transform: translateY(-1px);
  box-shadow: 0 0 0 1px var(--accent), 0 12px 28px -12px var(--accent);
}
.siteheader__cta:active { transform: translateY(0); }

@media (max-width: 640px) {
  :root { --header-h: 104px; }
  .wordmark { height: 32px; }
  /*
   * On a page that carries the game there is no Play button in the bar, so the
   * links fit beside the wordmark and the header stays one row — 56px instead
   * of 102px, which is 46px of arena back on a phone.
   */
  .siteheader__inner:not(:has(.siteheader__cta)) {
    /* nowrap is the whole trick: with wrapping on, a flex item wider than the
       space left is moved to its own line *before* shrinking is considered, so
       min-width and flex-shrink never got a say and the nav always took a
       second row. */
    flex-wrap: nowrap;
  }
  .siteheader__inner:not(:has(.siteheader__cta)) .siteheader__nav {
    order: 0; flex: 1 1 0; min-width: 0;
  }
  .siteheader__nav {
    order: 3; flex-basis: 100%;
    /* The strip scrolls on a phone, so fade its right edge rather than let the
       last link look chopped off. Only here — on desktop nothing overflows and
       a fade would just dim a link for no reason. */
    -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 26px), transparent 100%);
    mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 26px), transparent 100%);
  }
  .siteheader__nav a { font-size: 0.8rem; padding: 6px 8px 8px; }
  .siteheader__cta { margin-left: auto; padding: 9px 16px 10px; font-size: 0.82rem; }
}
@media (prefers-reduced-motion: reduce) {
  .siteheader__nav a::after, .wordmark, .siteheader__cta { transition: none; }
}

/*
 * The footer and breadcrumb share the header's column. Without this they run
 * full-bleed and pin left while everything between them is centred — the
 * "shit is all to the left" that started the layout rework.
 */
.sitefooter > *,
.breadcrumb > * {
  width: min(var(--game-max), 100%);
  margin-inline: auto;
}

.breadcrumb { padding: 12px clamp(16px, 4vw, 40px) 0; }
.breadcrumb ol {
  /*
   * margin-block, never the `margin` shorthand. The shorthand also sets
   * margin-inline, which silently cancelled the `margin-inline: auto` above
   * and pinned the breadcrumb to the far left of a 1512px screen while
   * everything else on the page was centred. Same specificity, written later,
   * so it won — and the centring rule was still sitting right there in the
   * file looking correct.
   */
  display: flex; gap: 8px; list-style: none; margin-block: 0; padding: 0;
  font-size: 0.87rem; color: var(--text-dim);
}
.breadcrumb li + li::before { content: "/"; margin-right: 8px; opacity: 0.5; }

main { padding: 22px clamp(16px, 4vw, 40px) 64px; }

/*
 * Centre everything in <main> on the reading measure.
 *
 * `main` had padding and nothing else, so on a wide screen the text column sat
 * hard against the left with 600px of dead space beside it — and worse, only
 * `.prose` and `.lede` were capped, so an h1 ran the full 1345px above a 792px
 * paragraph. A heading four times wider than the text it introduces reads as a
 * broken layout, not a design.
 *
 * Applied to every child rather than a list of block classes, so a block added
 * later cannot quietly escape the measure — which is exactly how the h1 and the
 * CTA came to be outside it.
 */
main > * {
  max-width: var(--max);
  margin-inline: auto;
}

/*
 * The arena is not the exception to the measure — it is not on the page at all.
 *
 * Checked against twenty snake games before rebuilding this. The ones that are
 * a *game* — slither.io, wormate.io, wormax.io, gulper.io, mope.io, splix.io —
 * all do the same thing: the arena fills the window, and a single centred
 * column floats on top of it carrying a wordmark, a nickname and a Play button.
 * Nothing else is above the fold. The ones that wrap the game in a document are
 * the ones whose real product is an app-store listing (worms.zone,
 * littlebigsnake.com, snake.io), and they lead with a download button.
 *
 * Squirm.io was doing neither: a 16:9 letterboxed box centred in a 1180px
 * column with an article under it and grey rails either side, which reads as a
 * blog post with a game widget in it. The arena now breaks out of `main` and
 * fills the viewport under the header, and the prose starts below the fold
 * where it belongs — still in the document, still indexed, just not competing
 * with the thing people came for.
 */
main > .game {
  /* Break out of main's padding and the reading measure both. */
  width: 100vw;
  max-width: none;
  margin-left: 50%;
  transform: translateX(-50vw);
  border-radius: 0;
  border-inline: 0;
  aspect-ratio: auto;
  height: calc(100dvh - var(--header-h));
  max-height: none;
  min-height: 420px;
}
.page--home main { padding-top: 0; }

/*
 * Below the arena, tell people there is more. Every one of the twenty sites
 * that carries content under the fold marks the seam; the ones that do not are
 * the pure-game sites with nothing down there.
 */
.scrollcue {
  position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
  z-index: 7; display: flex; align-items: center; gap: 10px;
  padding: 8px 16px 9px; border-radius: 999px;
  background: rgba(7, 9, 15, 0.72); border: 1px solid var(--line);
  color: var(--text-dim); text-decoration: none;
  font-family: var(--font-display); font-weight: 700;
  font-size: 0.78rem; letter-spacing: 0.09em; text-transform: uppercase;
  transition: color 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
@supports (backdrop-filter: blur(1px)) {
  .scrollcue { background: rgba(7, 9, 15, 0.5); backdrop-filter: blur(10px); }
}
.scrollcue:hover { color: var(--text); border-color: var(--accent); transform: translateX(-50%) translateY(-2px); }
.scrollcue__arrow { color: var(--accent); animation: nudge 2.4s ease-in-out infinite; }
@keyframes nudge {
  0%, 72%, 100% { transform: translateY(0); }
  84% { transform: translateY(3px); }
}
@media (prefers-reduced-motion: reduce) { .scrollcue__arrow { animation: none; } }
/*
 * The cue belongs to the menu screen only. During a run it sat in the middle of
 * the bottom edge of a live arena, which is both a distraction and a link you
 * do not want a player's mouse anywhere near.
 */
.scrollcue { display: none; }
.game:has(.overlay[data-game-menu]:not([hidden])) .scrollcue { display: flex; }

h1 {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(2rem, 4.6vw, 3rem); line-height: 1.08;
  margin-block: 0.2em 0.5em; letter-spacing: -0.035em;
}
h2 { font-weight: 800; font-size: clamp(1.35rem, 2.6vw, 1.8rem); line-height: 1.18; margin-block: 2em 0.5em; letter-spacing: -0.02em; }
h3 { font-size: 1.08rem; margin: 1.4em 0 0.35em; }

.prose { max-width: var(--max); }
.prose section:first-of-type h2 { margin-top: 1em; }
.prose ul, .prose ol { padding-left: 1.25em; }
.prose li { margin: 0.4em 0; }

.lede {
  font-size: 1.12rem; color: var(--text);
  border-left: 4px solid var(--accent); padding-left: 16px; margin-block: 0 1.4em;
  max-width: var(--max);
}

.button {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  padding: 14px 26px; border-radius: var(--radius-sm); border: 1px solid transparent;
  font: inherit; font-weight: 800; text-decoration: none; cursor: pointer;
  letter-spacing: 0.01em;
  transition: transform 0.12s ease, filter 0.12s ease;
}
.button:active { transform: translateY(1px); }
.button--primary {
  background: var(--accent); color: var(--accent-ink);
  text-transform: uppercase; letter-spacing: 0.045em; font-weight: 900;
}
.button--primary:hover { filter: brightness(1.08); color: var(--accent-ink); }
.button--ghost { background: transparent; border-color: var(--line); color: var(--text); }
.button--ghost:hover { border-color: var(--accent); }
/* A third action on the death screen, weighted below the other two: it is the
   way out, not a way on, and it must not compete with Play Again. */
.button--quiet {
  background: transparent; border-color: transparent; color: var(--text-dim);
  font-size: 0.9rem; padding: 8px 12px;
}
.button--quiet:hover { color: var(--text); border-color: var(--line); }
.button--big { font-size: 1.15rem; padding: 18px 30px; width: 100%; }
.button:disabled { opacity: 0.45; cursor: default; }
.button__timer { font-weight: 600; opacity: 0.8; font-size: 0.9em; }

.cta { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-block: 1.6em; }
.cta__note { color: var(--text-dim); font-size: 0.92rem; }

.faq { margin-top: 2em; }
.faq__item { border-top: 1px solid var(--line); padding: 14px 0; }
.faq__q { margin: 0 0 0.3em; font-size: 1.03rem; }
.faq__a { margin: 0; color: var(--text-dim); }

.related { margin-top: 2.6em; border-top: 1px solid var(--line); padding-top: 8px; }
.related__list { list-style: none; margin: 0; padding: 0; }
.related__list li { margin: 0.7em 0; }
.related__desc { color: var(--text-dim); font-size: 0.92rem; display: block; }

.gamelist { list-style: none; margin: 1.2em 0; padding: 0; display: grid; gap: 12px; }
.gamecard {
  border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px;
  background: var(--ink-2);
}
.gamecard--ours { border-color: rgba(110, 231, 183, 0.45); background: #0f1a1c; }
.gamecard__name { margin: 0 0 0.15em; font-size: 1.05rem; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.gamecard__badge {
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.08em;
  background: rgba(110, 231, 183, 0.16); color: var(--accent);
  border: 1px solid rgba(110, 231, 183, 0.4); border-radius: 999px; padding: 2px 9px; font-weight: 700;
}
.gamecard__genre { margin: 0 0 0.4em; color: var(--accent); font-size: 0.83rem; font-weight: 600; }
.gamecard__blurb { margin: 0; color: var(--text-dim); font-size: 0.96rem; }

/* --------------------------------------------------------------- skins page */
.skingroup { max-width: none; }
.skingrid {
  list-style: none; margin: 1.1em 0 0; padding: 0;
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
}
.skincard {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--ink-2); padding: 12px; text-align: center;
}
.skincard__art { display: block; width: 100%; height: auto; }
.skincard__name { margin: 6px 0 2px; font-size: 0.98rem; }
.skincard__unlock { margin: 0; font-size: 0.8rem; color: var(--text-dim); line-height: 1.4; }

/* ---------------------------------------------------------------- footer */
/*
 * A footer, not a bin.
 *
 * It was one paragraph, a flat run of eleven links, and a "Last updated" date.
 * Eleven links in a row tell a reader nothing about which one they want, and a
 * documentation date on a game is a convention borrowed from the wrong place —
 * no game in the field carries one, and the sitemap's <lastmod> is where a
 * crawler looks anyway.
 */
.sitefooter {
  border-top: 1px solid var(--line);
  padding: 40px clamp(16px, 4vw, 40px) 0;
  color: var(--text-dim); font-size: 0.92rem; background: #080b14;
}
.sitefooter__inner {
  display: grid; gap: 32px clamp(24px, 5vw, 64px);
  grid-template-columns: minmax(200px, 1fr) 2fr;
  align-items: start;
}
.sitefooter__mark { display: block; height: 30px; width: auto; margin-bottom: 12px; opacity: 0.9; }
.sitefooter__entity { margin: 0; max-width: 30ch; line-height: 1.5; }
.sitefooter__nav {
  display: grid; gap: 24px clamp(20px, 4vw, 48px);
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.sitefooter__heading {
  margin: 0 0 10px; font-family: var(--font-display);
  font-size: 0.74rem; font-weight: 700; letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--text);
}
.sitefooter__links {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 7px;
}
.sitefooter__links a { color: var(--text-dim); text-decoration: none; }
.sitefooter__links a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }

.sitefooter__base {
  margin-top: 36px; padding: 18px 0 34px;
  border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: 6px 24px; justify-content: space-between;
  font-size: 0.84rem;
}
.sitefooter__copy, .sitefooter__made { margin: 0; }
@media (max-width: 640px) {
  .sitefooter__inner { grid-template-columns: 1fr; gap: 28px; }
  .sitefooter__nav { grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); }
}

/* ---------------------------------------------------------------- the game */
.game {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  min-height: 380px;
  max-height: min(78vh, 780px);
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--line);
  background: #070a12;
  touch-action: none;
  user-select: none;
}
.game__stage { position: absolute; inset: 0; }
.squirm-canvas { display: block; width: 100%; height: 100%; }

.game__status {
  position: absolute; inset-inline: 0; top: 50%; transform: translateY(-50%);
  margin: 0 auto; max-width: 34ch; text-align: center; z-index: 4;
  color: var(--text); background: rgba(10, 14, 26, 0.86);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 14px 18px; font-weight: 600;
}

/*
 * The HUD belongs to a run, not to the menu.
 *
 * With the arena now behind the menu, LENGTH 10 / RANK — / TOP 10 sat over an
 * attract scene reporting on a worm that does not exist. The sound and
 * fullscreen controls stay: those are settings, and every game in the field
 * keeps them on the menu screen as corner furniture.
 */
.game:has(.overlay[data-game-menu]:not([hidden])) .hud__stat,
.game:has(.overlay[data-game-menu]:not([hidden])) .hud__board,
.game:has(.overlay[data-game-menu]:not([hidden])) .hud__minimap,
.game:has(.overlay[data-game-menu]:not([hidden])) .chip--ping { display: none; }

.hud {
  position: absolute; inset: 0; pointer-events: none;
  padding: 12px; display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: auto 1fr auto;
  gap: 10px; font-size: 0.9rem;
}
.hud__stat {
  grid-row: 1; align-self: start;
  background: rgba(10, 14, 26, 0.62); border: 1px solid rgba(37, 48, 70, 0.8);
  border-radius: var(--radius-sm); padding: 5px 11px; backdrop-filter: blur(3px);
}
/* Laid out by the strip above, not by hand-tuned offsets. */
.hud__label { display: block; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.09em; color: var(--text-dim); margin: 0; }
.hud__value { font-size: 1.18rem; font-variant-numeric: tabular-nums; }

/*
 * The leaderboard is not a panel.
 *
 * It was a bordered card with a near-solid background and full-brightness white
 * text, sitting over a third of a phone screen you are trying to steer through.
 * slither.io — and every other game in the field — draws its board straight
 * onto the arena: no box, muted text, and a shadow instead of a backdrop so it
 * stays readable over a bright patch of floor without hiding it.
 */
.hud__board {
  grid-column: 3; grid-row: 1; justify-self: end; align-self: start; min-width: 190px;
  background: none; border: 0; padding: 4px 2px;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.85), 0 0 2px rgba(0, 0, 0, 0.7);
}
.hud__board h2 { margin: 0 0 4px; }
.squirm-board {
  list-style: none; margin: 0; padding: 0; font-variant-numeric: tabular-nums;
  color: rgba(242, 245, 250, 0.62);
}
.squirm-board__row { display: grid; grid-template-columns: 1.4em 1fr auto; gap: 7px; font-size: 0.84rem; line-height: 1.5; }
.squirm-board__row.is-you { color: var(--accent); font-weight: 700; }
.squirm-board__pos { color: var(--text-dim); }
.squirm-board__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.squirm-board__len { color: var(--text-dim); }
.squirm-board__row.is-you .squirm-board__len { color: var(--accent); }

.hud__corner {
  grid-column: 3; grid-row: 3; justify-self: end; align-self: end;
  display: flex; flex-direction: column; align-items: flex-end; gap: 8px;
}
.hud__minimap { width: 118px; height: 118px; display: block; }
/*
 * The ping belongs to the stat row, not to the middle of the arena.
 *
 * It was absolutely placed with a margin that put it wherever the stat boxes
 * happened to end, so on a phone it ended up as a lone pill floating over the
 * floor with nothing near it. It is a readout like LENGTH and RANK, so it sits
 * in the same strip, in the same visual family, at the end of it.
 */
.hud__stats {
  grid-row: 1; grid-column: 1; justify-self: start; align-self: start;
  display: flex; align-items: stretch; gap: 8px;
}
.hud__stat { grid-row: auto; align-self: auto; }
.chip--ping { align-self: center; }
/* The arena count is context, not a score: same box, quieter number. */
.hud__stat--quiet .hud__value { font-size: 0.95rem; color: var(--text-dim); }
.chip {
  font: inherit; font-size: 0.76rem; font-weight: 600;
  background: rgba(10, 14, 26, 0.72); color: var(--text-dim);
  border: 1px solid var(--line); border-radius: 999px; padding: 4px 11px; cursor: pointer;
}
.chip:hover { color: var(--text); border-color: var(--accent); }
.chip--ping { cursor: default; font-variant-numeric: tabular-nums; }

.joystick {
  position: absolute; left: var(--joy-inset); bottom: var(--joy-inset);
  width: var(--joy-size); height: var(--joy-size);
  border-radius: 50%; border: 2px solid rgba(232, 237, 247, 0.22);
  background: rgba(10, 14, 26, 0.35); z-index: 5; pointer-events: none;
}
.joystick__knob {
  position: absolute; left: 50%; top: 50%; width: 52px; height: 52px; margin: -26px 0 0 -26px;
  border-radius: 50%; background: rgba(232, 237, 247, 0.34);
  border: 2px solid rgba(232, 237, 247, 0.5);
}
.boostbutton {
  position: absolute; right: 24px; bottom: 24px; width: 96px; height: 96px;
  border-radius: 50%; border: 2px solid rgba(110, 231, 183, 0.55);
  background: rgba(52, 211, 153, 0.22); color: var(--text);
  font: inherit; font-weight: 800; letter-spacing: 0.04em; z-index: 5;
}
.boostbutton.is-active { background: rgba(52, 211, 153, 0.45); }

/*
 * Exactly one scrollbar, ever.
 *
 * The overlay used to scroll and the skin list scrolled inside it, so a tall
 * menu in a short arena produced a scrollbar inside a scrollbar — and the Play
 * button could scroll out of sight. Now the overlay never scrolls; a simple
 * panel scrolls as a whole, and the menu pins its head and scrolls one region
 * beneath it.
 */
/*
 * The menu floats ON the arena, it does not cover it.
 *
 * The backdrop used to be an almost-opaque radial, which meant the first thing
 * a new player saw was a dark rectangle — the same first impression as a page
 * that has not loaded. Behind it now is a live arena (see attract.ts), so the
 * backdrop's job is to keep the panel readable over moving worms, not to hide
 * them: a vignette that darkens the edges and leaves the middle alive.
 */
.overlay {
  position: absolute; inset: 0; z-index: 6;
  display: grid; place-items: center; padding: 16px;
  /*
   * The panel is CENTRED. No lane.
   *
   * This used to reserve 148px on the left for the sound dock, which fixed a
   * real overlap on phones and created a permanent one-sided offset
   * everywhere else — a panel visibly pushed off centre, which is what Gray
   * spotted.
   *
   * The offset was never needed above the phone breakpoint. Measured, not
   * guessed: the panel is min(430px, 100%) and the dock ends at 14px + 148px =
   * 162px, so the panel's left edge is (viewport - 430) / 2, and at the
   * narrowest desktop width the phone rules do not cover — 768px — that is
   * 169px. Clear of the dock at every width from there up, and widening the
   * viewport only pushes them further apart.
   *
   * Below 768px the phone block does not use a lane either: it pins the panel
   * to the top instead, which gathers the slack into one bottom strip the dock
   * and the scroll cue share. tools/check-layout.mjs measures the real gap
   * across the range so this cannot regress into an overlap again.
   */
  background:
    radial-gradient(ellipse 120% 90% at 50% 45%, rgba(4, 6, 12, 0.34), rgba(4, 6, 12, 0.86) 78%);
  overflow: hidden;
}
/* Death, privacy and how-to still want the room dark — they are read, not
   looked past. */
.overlay:has(.panel--death) {
  background: radial-gradient(ellipse 120% 90% at 50% 45%, rgba(4, 6, 12, 0.34), rgba(4, 6, 12, 0.78) 80%);
}
.overlay:has(.panel__list) {
  background: radial-gradient(circle at 50% 40%, rgba(10, 14, 26, 0.86), rgba(4, 6, 12, 0.96));
}
.panel {
  width: min(560px, 100%); background: var(--ink-2);
  border: 1px solid var(--line); border-radius: var(--radius); padding: 20px;
  max-height: 100%; overflow-y: auto;
}
.panel--menu {
  width: min(430px, 100%);
  /* Leave the bottom strip for the scroll cue, which sits over the arena: its
     39px pill plus its 14px inset plus room to breathe. 56px was three pixels
     short and the two overlapped. */
  max-height: calc(100% - 76px);
  padding: 0; overflow: hidden; display: flex; flex-direction: column;
  background: rgba(16, 20, 31, 0.82);
  border-color: rgba(198, 245, 54, 0.22);
  box-shadow: 0 30px 80px -40px #000, 0 0 0 1px rgba(0, 0, 0, 0.4);
}
@supports (backdrop-filter: blur(1px)) {
  .panel--menu { background: rgba(16, 20, 31, 0.66); backdrop-filter: blur(18px) saturate(130%); }
}
.panel__head {
  flex: 0 0 auto; padding: 22px 22px 20px;
  border-bottom: 1px solid var(--line);
  border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
  text-align: center;
}
/* The wordmark is the title. A game's front page says its name in its own
   lettering, not in the body face at 1.5rem. */
.panel__mark { display: block; height: 54px; width: auto; margin: 2px auto 12px; }
.panel__head .panel__title { font-size: 1.12rem; margin: 0 0 4px; }
.panel__head .panel__sub { font-size: 0.9rem; margin: 0 0 16px; }
.panel__head .field__label { text-align: left; }
.panel__head .button--big { margin-bottom: 0; }
/* min-height:0 is what actually lets a flex child scroll instead of growing. */
.panel__scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 4px 20px 20px; }
.panel__section--first { margin-top: 16px; }
.panel--death {
  text-align: center; width: min(440px, 100%); padding: 26px 22px 22px;
  /*
   * Arrives, rather than appears. The arena is still playing underneath — the
   * burst, the body coming apart, the food it drops — so the panel comes up
   * from 80% and stays a little transparent at rest, which keeps the thing that
   * just happened visible behind the number.
   */
  background: rgba(16, 20, 31, 0.72);
  animation: deathIn 260ms cubic-bezier(0.16, 0.9, 0.3, 1) both;
}
@supports (backdrop-filter: blur(1px)) {
  .panel--death { background: rgba(16, 20, 31, 0.52); backdrop-filter: blur(14px) saturate(120%); }
}
@keyframes deathIn {
  from { opacity: 0; transform: scale(0.8); }
  to   { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .panel--death { animation: none; }
}
/* The backdrop behind it comes up too, so the arena is not slapped with a
   full-strength dim on the frame you died. */
.overlay:has(.panel--death) { animation: deathBackdrop 260ms ease both; }
@keyframes deathBackdrop { from { opacity: 0; } to { opacity: 1; } }

/* One scrollbar left, so make it look deliberate rather than borrowed. */
.panel, .panel__scroll { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
.panel::-webkit-scrollbar, .panel__scroll::-webkit-scrollbar { width: 10px; }
.panel::-webkit-scrollbar-track, .panel__scroll::-webkit-scrollbar-track { background: transparent; }
.panel::-webkit-scrollbar-thumb, .panel__scroll::-webkit-scrollbar-thumb {
  background: var(--line); border-radius: 99px;
  border: 3px solid var(--ink-2); background-clip: padding-box;
}
.panel__scroll::-webkit-scrollbar-thumb:hover { background: #3A4460; }

/*
 * The death screen.
 *
 * One number is the result of the run and everything else is context, so the
 * length is set at five times the size of the label above it. The old panel
 * gave the score, the rank, the best and the killer the same weight, which
 * made the player hunt for the one thing they wanted to know.
 */
.death__label {
  margin: 0; font-size: 0.72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.14em; color: var(--text-dim);
}
.death__score {
  margin: 2px 0 6px;
  font-family: var(--font-display); font-weight: 900;
  font-size: clamp(3.2rem, 15vw, 4.6rem); line-height: 1;
  letter-spacing: -0.04em; color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.death__stats {
  margin: 0 0 14px; font-weight: 700; font-size: 0.95rem; color: var(--text);
}
.death__stats span { color: var(--text-dim); font-weight: 600; }
.death__cause { margin: 0 0 16px; color: var(--text-dim); font-size: 0.92rem; }

/* Share */
.share { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 14px; }
.share__label {
  margin: 0 0 9px; font-size: 0.72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.14em; color: var(--text-dim);
}
.share__row { display: flex; flex-wrap: wrap; gap: 7px; justify-content: center; }
.share__btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 13px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--ink-3);
  color: var(--text); font: inherit; font-size: 0.85rem; font-weight: 700;
  text-decoration: none; cursor: pointer;
  transition: border-color 0.12s ease, color 0.12s ease;
}
.share__btn:hover { border-color: var(--accent); color: var(--accent); }
.share__btn--native { border-color: var(--accent); color: var(--accent); }
/* Confirmation replaces the label rather than appearing beside it, so the row
   does not reflow and shift the buttons under the player's cursor. */
.share__btn.is-done { border-color: var(--accent); color: var(--accent); }

/* On a content page the row is left-aligned with the prose, not centred. */
.share--page { margin: 2.4em 0 0; border-top: 1px solid var(--line); padding-top: 16px; }
.share--page .share__row { justify-content: flex-start; }
.panel__title { font-family: var(--font-display); font-size: 1.5rem; margin: 0 0 6px; }
.panel__sub { margin: 0 0 16px; color: var(--text-dim); }
.panel__section { font-size: 1rem; margin: 20px 0 4px; }
.panel__hint { margin: 4px 0 10px; font-size: 0.85rem; color: var(--text-dim); min-height: 1.2em; }
.panel__note { margin: 10px 0 0; font-size: 0.79rem; color: var(--text-dim); }
.panel__links { margin: 14px 0 0; font-size: 0.88rem; }
.panel__list { margin: 0 0 16px; padding-left: 1.15em; text-align: left; color: var(--text-dim); font-size: 0.92rem; }
.panel__list li { margin: 0.55em 0; }
.panel__list strong { color: var(--text); }

/* A button that reads as a link — used where a real link would be an outbound
   link, which portal builds are rejected for. */
.linkbutton {
  background: none; border: 0; padding: 0; font: inherit;
  color: var(--accent); cursor: pointer; text-decoration: underline;
  text-underline-offset: 3px;
}
.linkbutton:hover { color: var(--accent-2); }
.panel__actions { display: grid; gap: 10px; margin-top: 16px; }

.field { display: block; margin-bottom: 14px; }
.field__label { display: block; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.09em; color: var(--text-dim); margin-bottom: 5px; }
.field__input {
  width: 100%; font: inherit; padding: 12px 14px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--ink); color: var(--text);
}

.radio { margin-top: 6px; }
.radio__stations,
.region__choices { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
/* The same grid for the floors — four of them, two up on a narrow panel. */
.stations { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; }
.station.is-locked { opacity: 0.55; cursor: not-allowed; }
.station.is-locked .station__name { color: var(--text-dim); }
.region { margin-top: 6px; }
.station {
  display: grid; gap: 2px; padding: 8px 9px; cursor: pointer; text-align: left;
  background: var(--ink-3); border: 2px solid transparent; border-radius: var(--radius-sm);
  color: var(--text); font: inherit;
}
.station:hover:not(:disabled) { border-color: var(--line); }
.station.is-selected { border-color: var(--accent); }
.station.is-selected .station__name { color: var(--accent); }
.station:disabled { opacity: 0.5; cursor: not-allowed; }
.station__name { font-size: 0.88rem; font-weight: 700; }
.station__desc { font-size: 0.72rem; color: var(--text-dim); line-height: 1.35; }

.radio__transport { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.radio__transport .button { padding: 8px 16px; }
.radio__volume { flex: 1; display: flex; }
.radio__volume input { width: 100%; accent-color: var(--accent); }
.radio__now { margin: 8px 0 0; font-size: 0.82rem; color: var(--text-dim); }
.radio__credit { margin: 3px 0 0; font-size: 0.72rem; color: var(--text-dim); opacity: 0.85; }

.toast {
  margin: 12px 0 0; padding: 9px 13px; border-radius: var(--radius-sm);
  background: rgba(110, 231, 183, 0.14); border: 1px solid rgba(110, 231, 183, 0.4);
  color: var(--accent); font-weight: 700; font-size: 0.9rem;
}

/* No longer a scroller of its own — .panel__scroll is the only one. */
.squirm-skins { padding-right: 0; }
.squirm-skins__group + .squirm-skins__group { margin-top: 12px; }
.squirm-skins__heading {
  margin: 0 0 6px; font-size: 0.72rem; text-transform: uppercase;
  letter-spacing: 0.09em; color: var(--text-dim); font-weight: 700;
}
.squirm-skins__row { display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); gap: 7px; }
.squirm-skin {
  display: grid; gap: 2px; padding: 5px; cursor: pointer;
  background: var(--ink-3); border: 2px solid transparent; border-radius: var(--radius-sm);
  color: var(--text); font: inherit;
}
.squirm-skin__art { width: 100%; height: 40px; display: block; }
.squirm-skin__name { font-size: 0.72rem; color: var(--text-dim); }
.squirm-skin:hover:not(:disabled) { border-color: var(--line); }
.squirm-skin.is-selected { border-color: var(--accent); }
.squirm-skin.is-selected .squirm-skin__name { color: var(--accent); }
.squirm-skin.is-locked { cursor: not-allowed; opacity: 0.72; }

@media (max-width: 720px) {
  body { font-size: 16px; }
  /* `.game { aspect-ratio: 3/4 }` used to live here, from when the arena was a
     box on the page. It is the viewport now — see `main > .game`. */
  .hud__board { min-width: 0; }
  .hud__minimap { width: 82px; height: 82px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

/*
 * The seam between the arena and the article.
 *
 * The arena ends at the window edge and the prose begins immediately under it,
 * so without a marker the first heading reads as the page having scrolled by
 * accident. A small lime eyebrow says "this is the write-up, deliberately".
 */
.eyebrow {
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 0.72rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent); margin: 26px 0 0;
}
.prose section:first-of-type .eyebrow + h2 { margin-top: 0.3em; }

/* ------------------------------------------------------------ sound dock */
/*
 * Above the overlay (z-index 6), because that is the whole point: with sound
 * off, the old chips sat *under* the menu and could be seen but not clicked.
 */
/*
 * Hidden while the death screen is up.
 *
 * The dock sits above the overlay so sound can be changed from the menu — but
 * on the death screen that put a floating pill through the middle of the share
 * buttons. Death is a modal moment with its own actions; the controls are back
 * the instant you are in the arena or the menu again.
 */
.game:has(.overlay[data-game-death]:not([hidden])) .dock { display: none; }

.dock {
  position: absolute; left: 14px; bottom: 14px; z-index: 9;
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
}
.dock__bar {
  display: flex; gap: 6px; padding: 5px;
  border-radius: 999px; border: 1px solid var(--line);
  background: rgba(7, 9, 15, 0.55);
}
@supports (backdrop-filter: blur(1px)) {
  .dock__bar { background: rgba(7, 9, 15, 0.34); backdrop-filter: blur(12px); }
}
.dock__icon {
  width: 34px; height: 34px; display: grid; place-items: center;
  border-radius: 50%; border: 1px solid transparent; background: transparent;
  color: var(--text-dim); cursor: pointer; font-size: 0.95rem; line-height: 1;
  transition: background 0.14s ease, color 0.14s ease, border-color 0.14s ease;
}
.dock__icon:hover { background: rgba(255, 255, 255, 0.07); color: var(--text); }
.dock__icon:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.dock__icon[aria-expanded="true"] { background: var(--accent); color: var(--accent-ink); }
.dock__glyph {
  width: 18px; height: 18px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.dock__speaker-body { fill: currentColor; stroke: currentColor; stroke-width: 1.2; }
/* Waves when there is sound, a cross when there is not. One icon, two states. */
.dock__cross { opacity: 0; }
.dock.is-silent .dock__wave { opacity: 0; }
.dock.is-silent .dock__cross { opacity: 1; }
.dock.is-silent [data-game-mute] { color: var(--warn); }

/*
 * Four bars, at rest when nothing is playing and dancing when it is. It is the
 * only ornament in here and it earns its place: it is the one part of the
 * control that tells you the radio is running without opening anything.
 */
.eq { display: flex; align-items: flex-end; gap: 2px; height: 14px; }
.eq i { width: 3px; height: 4px; border-radius: 1px; background: var(--text-dim); transition: height 0.2s ease; }
.dock__icon:hover .eq i { background: var(--text); }
.eq.is-playing i { background: var(--accent); animation: bounce 900ms ease-in-out infinite; }
.eq.is-playing i:nth-child(1) { animation-delay: 0ms; }
.eq.is-playing i:nth-child(2) { animation-delay: 140ms; }
.eq.is-playing i:nth-child(3) { animation-delay: 300ms; }
.eq.is-playing i:nth-child(4) { animation-delay: 80ms; }
@keyframes bounce { 0%, 100% { height: 4px; } 50% { height: 14px; } }
@media (prefers-reduced-motion: reduce) {
  .eq.is-playing i { animation: none; height: 9px; }
}

.dock__panel {
  width: 268px; padding: 14px; border-radius: 14px;
  border: 1px solid var(--line); background: rgba(10, 13, 22, 0.86);
  display: flex; flex-direction: column; gap: 12px;
}
@supports (backdrop-filter: blur(1px)) {
  .dock__panel { background: rgba(10, 13, 22, 0.68); backdrop-filter: blur(20px) saturate(130%); }
}
.dock__group { display: flex; flex-direction: column; gap: 8px; }
.dock__group + .dock__group { border-top: 1px solid var(--line); padding-top: 12px; }
.dock__row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.dock__label {
  margin: 0; font-family: var(--font-display); font-size: 0.74rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-dim);
}
.dock__slider { display: block; }
.dock__slider input { width: 100%; accent-color: var(--accent); }
.dock__stations { display: flex; gap: 5px; }
/*
 * Three chips that look like three chips.
 *
 * The unselected two were transparent with dim text next to a solid lime one,
 * so the row read as one button and two disabled labels. All three carry the
 * same fill, border and text weight now; selection changes the colour, not
 * whether the thing looks like a control.
 */
.dock__stations .station {
  flex: 1; min-width: 0; padding: 7px 6px; border-radius: 8px;
  border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.05); color: var(--text); cursor: pointer;
  font-family: var(--font-display); font-weight: 700; font-size: 0.68rem;
  letter-spacing: 0.05em; text-transform: uppercase; text-align: center;
  line-height: 1.2;
  transition: background 0.14s ease, border-color 0.14s ease, color 0.14s ease;
}
.dock__stations .station:hover:not(:disabled):not(.is-selected) {
  background: rgba(255, 255, 255, 0.1); border-color: #38425c;
}
.dock__stations .station:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Only the station name fits at this size; the descriptions were the bulk of
   what made the old panel a wall of text. */
.dock__stations .station__desc { display: none; }
.dock__stations .station.is-selected {
  background: var(--accent); color: var(--accent-ink); border-color: var(--accent);
}
/* The name has its own colour from the region picker's version of this
   component, and on a lime chip that made the selected station read as a blank
   block. Inherit here rather than fork the component. */
.dock__stations .station__name { font-size: inherit; font-weight: inherit; color: inherit; }
.dock__stations .station.is-selected .station__name { color: var(--accent-ink); }
.dock__stations .station:disabled { opacity: 0.4; cursor: default; }
.dock__transport { display: flex; gap: 6px; }
.dock__btn {
  flex: 1; padding: 6px 10px; border-radius: 8px; border: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.04); color: var(--text); cursor: pointer;
  font-size: 0.8rem; font-weight: 600;
}
.dock__btn:hover { background: rgba(255, 255, 255, 0.09); }
.dock__btn:disabled { opacity: 0.4; cursor: default; }
.dock__now { margin: 0; font-size: 0.72rem; color: var(--text-dim); line-height: 1.4; }
.dock__now.is-problem { color: var(--warn); }

/* A switch, because on/off is a state and a button that says "Sound on" is a
   riddle about whether that is what it does or what it will do. */
.switch { background: none; border: 0; padding: 0; cursor: pointer; }
.switch__track {
  display: block; width: 38px; height: 21px; border-radius: 999px;
  background: var(--ink-3); border: 1px solid var(--line);
  transition: background 0.16s ease, border-color 0.16s ease;
}
.switch__knob {
  display: block; width: 15px; height: 15px; margin: 2px; border-radius: 50%;
  background: var(--text-dim); transition: transform 0.16s ease, background 0.16s ease;
}
.switch[aria-pressed="true"] .switch__track { background: rgba(198, 245, 54, 0.28); border-color: var(--accent); }
.switch[aria-pressed="true"] .switch__knob { transform: translateX(17px); background: var(--accent); }
.switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 999px; }

/*
 * Not on a phone.
 *
 * The dock's corner is where the boost button lives, and a radio with three
 * stations and two volume sliders is not what anyone is reaching for with a
 * thumb mid-run. Mobile keeps the mute and fullscreen buttons and loses the
 * panel — the settings still exist, they are just not a phone control.
 */
/*
 * The reason to hide the panel is thumbs, not pixels.
 *
 * Width alone put a 680px desktop window — a perfectly ordinary half-screen —
 * on the phone path, so the radio simply was not there. A coarse pointer is the
 * actual condition: a radio with three stations and two volume sliders is not
 * what anyone is reaching for mid-run on a touchscreen. The arena's *layout*
 * rules stay width-based below, because those are about space.
 */
@media (pointer: coarse) and (max-width: 900px) {
  .dock__panel { display: none; }
  [data-dock-open] { display: none; }
}

/* ------------------------------------------------------- the arena on a phone */
/*
 * Everything here is a fault visible in one portrait screenshot: the HUD boxes
 * were desktop-sized and ate the top of the arena, the leaderboard was nearly
 * opaque and covered a third of the width, the ping chip ran off the right
 * edge, and the corner controls sat underneath the boost button.
 */
@media (max-width: 767px) {
  .hud { padding: 8px; }
  .hud__stat { padding: 3px 8px; border-radius: 8px; }
  .hud__label { font-size: 0.58rem; letter-spacing: 0.07em; }
  .hud__value { font-size: 0.95rem; }

  /* You are steering through this, on the smallest screen there is. */
  .hud__board { font-size: 0.7rem; padding: 2px; max-width: 44vw; }
  .hud__board .hud__label { font-size: 0.55rem; }
  .squirm-board { color: rgba(242, 245, 250, 0.5); }

  /* Your own numbers stay a shade more present than the board — they are the
     ones you glance at mid-run — but the heavy card goes. */
  .hud__stat {
    background: rgba(7, 9, 15, 0.3); border-color: rgba(37, 48, 70, 0.45);
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
  }

  .chip--ping { font-size: 0.62rem; padding: 2px 6px; }

  /*
   * Bottom left, above the joystick.
   *
   * The corner itself belongs to the joystick during a run, so the dock sits
   * one joystick-height up — computed from the same tokens the joystick uses,
   * so moving one moves the other. It stays put between the menu and a run
   * rather than jumping corners when the touch controls appear.
   */
  .dock {
    left: 12px; right: auto; top: auto;
    bottom: calc(var(--joy-inset) + var(--joy-size) + 12px);
  }

  /*
   * On a phone the panel fills the width, so no corner beside it is free — and
   * centring it vertically leaves a useless sliver of slack top and bottom.
   * Pinning it to the top gathers all of that slack into one strip along the
   * bottom, which is then wide enough for the dock on the left and the "More
   * below" cue in the middle. The wordmark and the Play button end up higher on
   * the screen too, which is where a thumb wants them.
   */
  .overlay:has(.panel--menu) { align-items: start; }
  .panel--menu { max-height: calc(100% - 66px); }
  .game:has(.overlay[data-game-menu]:not([hidden])) .dock { top: auto; bottom: 10px; }


  /* The minimap sat in the bottom-right corner, which on a phone is where the
     boost button is — they overlapped, and the boost button won. Row 2 of the
     HUD grid puts it directly under the leaderboard instead, in the one
     right-hand space nothing else claims. */
  .hud__corner { grid-row: 2; align-self: start; }
  .hud__minimap { width: 74px; height: 74px; }
}

/* ---------------------------------------------------------------- the secret
 *
 * The Konami reveal. Sits above everything at z-index 7 — one above the other
 * overlays — because it is shown while the menu is up and must land ON it, not
 * behind it.
 *
 * The suspense is carried by the transition rather than by any content: the
 * panel scales up from 80% while the words arrive on their own timers, so
 * there is a beat where something has clearly happened and you do not yet know
 * what. Same 20%-smaller entrance the death modal uses, deliberately, so the
 * game has one language for "a screen has arrived".
 */
.overlay--secret {
  z-index: 7;
  /*
   * Nearly opaque, and the panel below has its own ground.
   *
   * At 0.86 the menu read straight through it: "JOIN THE ARENA" sat inside the
   * word GALAXY and the skin picker showed through the copy. A reveal has to
   * be the only thing on screen, so this is a curtain rather than a tint.
   */
  background: radial-gradient(circle at 50% 45%, rgba(24, 10, 48, 0.97), rgba(3, 4, 8, 0.985) 70%);
  opacity: 0;
  transition: opacity 340ms ease;
  /* This one IS centred on the arena — it is a moment, not a working panel, so
     the dock lane the menu reserves does not apply. */
  padding: 16px;
}
.overlay--secret.is-in { opacity: 1; }

.secret {
  text-align: center;
  max-width: 460px;
  width: min(460px, 100%);
  padding: 30px 26px 26px;
  border-radius: var(--radius);
  /* Its own ground, so nothing behind can read through the type even where the
     curtain is thinnest. */
  background: linear-gradient(168deg, #180C36, #08060F 78%);
  border: 1px solid #3A2668;
  box-shadow: 0 0 0 1px rgba(155, 77, 255, 0.16), 0 26px 60px rgba(0, 0, 0, 0.7),
              0 0 70px rgba(123, 59, 224, 0.22);
  transform: scale(0.8);
  opacity: 0;
  transition: transform 620ms cubic-bezier(.16, 1, .3, 1), opacity 500ms ease;
}
.overlay--secret.is-in .secret { transform: scale(1); opacity: 1; }

.secret__eyebrow {
  font-family: var(--font-display); font-weight: 700; font-size: 0.68rem;
  letter-spacing: 0.24em; text-transform: uppercase;
  color: #7FE8FF; margin: 0 0 10px; min-height: 1em;
}
.secret__name {
  font-family: var(--font-display); font-weight: 900;
  font-size: clamp(2.2rem, 9vw, 3.6rem); line-height: 1; margin: 0;
  min-height: 1em;
  /* The skin's own palette, as type. Falls back to plain white where
     background-clip: text is unsupported, which is legible either way. */
  color: #F2F5FA;
  background: linear-gradient(96deg, #7FE8FF, #FFFFFF 38%, #C2338F);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}
.secret__sub {
  margin: 12px 0 0; color: var(--text-dim); font-size: 0.95rem; min-height: 1.4em;
}
.secret__bar {
  margin: 16px auto 0; width: min(280px, 70vw); height: 6px;
  border-radius: 999px; background: var(--ink-3);
  border: 1px solid var(--line); overflow: hidden;
}
.secret__bar i {
  display: block; height: 100%; width: 0%; border-radius: 999px;
  background: linear-gradient(90deg, #22348F, #C2338F, #7FE8FF);
  transition: width 900ms cubic-bezier(.16, 1, .3, 1);
}
.secret__actions { margin-top: 22px; }

@media (prefers-reduced-motion: reduce) {
  .overlay--secret, .secret, .secret__bar i { transition-duration: 1ms; }
}
