/* 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. */

/*
 * The display face. Self-hosted, latin subset, one variable file for every
 * weight — see assets/font/LICENCE.txt for why it is not a Google link.
 *
 * `swap` on purpose: the fallback is a rounded-ish system face, and a flash of
 * it beats a flash of nothing on the one screen people judge in a second.
 */
@font-face {
  font-family: "Fredoka";
  src: url("/font/fredoka.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
}

: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;
  /*
   * Rounder. Much rounder.
   *
   * "no cooperate looking squared sharp business look" — 16px on a 560px panel
   * reads as a business card. Games in this field run 20-28px on panels and
   * fully rounded pills on controls, and the softness is most of what separates
   * "fun" from "enterprise dashboard".
   */
  --radius: 24px;
  --radius-sm: 14px;
  --radius-pill: 999px;

  /*
   * A real colour system, not one accent on grey.
   *
   * Gray, looking at Shell Shockers: "more colour, bright colours, thats what
   * kids want". The lime stays the primary — it is the brand and it survives a
   * portal thumbnail — but a single accent on near-black gives every control
   * the same voice, so nothing reads as more or less important than anything
   * else, and none of it reads as playful.
   *
   * Four more, each with ONE job, so colour means something:
   *   sky    the surface a panel is made of, and the calm default
   *   grape  a second category, for what is yours (skins, progress)
   *   tang   attention: the selected thing, the thing to press next
   *   berry  destructive, and only that
   * Each has a `-deep` for the extruded bottom lip that makes a button look
   * like an object you can push instead of a rectangle with a label in it.
   */
  --sky: #2C8FD6;
  --sky-deep: #1D6BA8;
  --sky-soft: #57ADEC;
  --grape: #8B5CF6;
  --grape-deep: #6D33E0;
  --tang: #FF9F1C;
  --tang-deep: #D97B00;
  --berry: #FF3B5C;
  --berry-deep: #C8102E;
  --lime-deep: #8CBB0F;
  /* White, for the pill inputs and the panel copy that sits on colour. */
  --paper: #FFFFFF;
  --paper-dim: rgba(255, 255, 255, 0.78);
  /*
   * 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 boost button's size and inset, bottom right. The sound dock lives on the
     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;
  /* The boost button's footprint, bottom RIGHT. Nothing else may claim that
     corner during a run. */
  --boost-size: 96px;
  --boost-inset: 24px;
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  /*
   * A real display face, self-hosted.
   *
   * It was "Trebuchet MS" — a system font from 1996 that is the single biggest
   * reason this looked like a business tool rather than a game. Gray: "no
   * cooperate looking squared sharp business look". Fredoka is the soft
   * geometric round the WORDMARK is already drawn in, so the lettering and the
   * interface finally speak with one voice.
   *
   * Self-hosted rather than linked: a portal build must carry no third-party
   * resources at all, and a webfont fetched from Google is a request that can
   * fail and leave the game wearing Trebuchet anyway. 29KB, latin subset,
   * variable weight — see assets/font/LICENCE.txt.
   */
  --font-display: "Fredoka", "Trebuchet MS", "Segoe UI", system-ui, sans-serif;
}

* { box-sizing: border-box; }

/* The overlays 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.
 */
/* Same bar as the settings page's, so the two never read as two sites. */
.siteheader {
  position: sticky; top: 0; z-index: 30;
  background: rgba(16, 21, 42, 0.9);
  border-bottom: 2px solid rgba(255, 255, 255, 0.09);
  padding-top: var(--safe-t);
}
@supports (backdrop-filter: blur(1px)) {
  .siteheader { background: rgba(16, 21, 42, 0.7); backdrop-filter: blur(18px) saturate(150%); }
}
.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;
  transition: background 0.12s ease, transform 0.08s ease;
}
.siteheader__cta:hover { background: #D6FF56; }
.siteheader__cta:active { background: var(--accent-2); transform: translateY(1px); }

@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: 14px clamp(16px, 4vw, 40px) 0; }
.breadcrumb a, .breadcrumb li { color: var(--paper-dim); }
.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; }

/* ------------------------------------------------------------- the menu
 *
 * The front screen is the PAGE, not a card on it.
 *
 * "we dont need that box in the middle man thats one of the main issues nobody
 * does that" — and nobody does: slither.io, wormate.io, wormax.io, gulper.io
 * and littlebigsnake all put the name field and Play straight onto the arena
 * with everything else pinned to the edges. A 430px panel with its own
 * background, border and scrollbar was the one thing making this look like a
 * game embedded in a website rather than a game.
 *
 * Three rows: a top rail, the middle, a bottom rail — plus a footer line. Grid
 * rather than absolute positioning, so the middle is centred in what is
 * actually left over rather than in the whole viewport.
 */
.overlay--menu {
  /* No place-items: center. The menu IS the overlay's content and fills it. */
  display: block; padding: 0;
  /*
   * "soon as you start a game all that needs to fade out."
   *
   * `hidden` is `display: none` and cannot transition, so the client adds
   * `is-out` first and sets `hidden` when the fade has finished — see
   * Menu.hide(). Without that the whole front screen blinks out on the frame
   * you press Play, which reads as a glitch rather than as the game taking
   * over.
   */
  opacity: 1; transition: opacity 0.26s ease;
  /*
   * Depth, not a flat wash.
   *
   * "the homepage needs much more definition, its a bit flat" — and it was:
   * one even radial dim over a dark floor gives the eye nothing to climb. Four
   * layers, painted back to front, each doing one job:
   *
   *  1. a warm lime bloom behind the wordmark, so the centre has a light source
   *  2. a soft dark pool under the middle, so the type has something to sit on
   *  3. scrims top and bottom, so the two rails read as EDGES rather than as
   *     text floating over grass
   *  4. the overall dim, weakest in the middle where the arena should show
   *     through and strongest in the corners
   *
   * Every stop is transparent black or the accent — no new colours, so this is
   * depth rather than decoration.
   */
  background:
    /* The rails sit on scrims, so their text always has a ground. */
    linear-gradient(to bottom, rgba(4, 6, 12, 0.92) 0%, rgba(4, 6, 12, 0) 20%),
    linear-gradient(to top, rgba(4, 6, 12, 0.94) 0%, rgba(4, 6, 12, 0) 24%),
    /*
     * An aurora, in three colours.
     *
     * "nothing about any of it is exciting" — and a single lime bloom on
     * near-black is one light in an empty room. Lime centre, grape left, sky
     * right: the middle of the screen now has a colour temperature that
     * changes across it, which is what makes a flat plane read as lit. All
     * three are already in the palette, so this is depth rather than a new
     * scheme.
     */
    radial-gradient(ellipse 46% 40% at 28% 44%, rgba(139, 92, 246, 0.20), transparent 70%),
    radial-gradient(ellipse 46% 40% at 74% 40%, rgba(44, 143, 214, 0.20), transparent 70%),
    radial-gradient(ellipse 56% 44% at 50% 34%, rgba(198, 245, 54, 0.16), transparent 68%),
    /* ...and a pool under the middle, so type still has something to sit on. */
    radial-gradient(ellipse 60% 54% at 50% 52%, rgba(6, 9, 18, 0.82), transparent 74%),
    radial-gradient(ellipse 130% 100% at 50% 45%, rgba(4, 6, 12, 0.46), rgba(4, 6, 12, 0.90) 84%);
}
.overlay--menu.is-out { opacity: 0; pointer-events: none; }
@media (prefers-reduced-motion: reduce) {
  .overlay--menu { transition: none; }
}
.menu {
  height: 100%;
  display: grid;
  /*
   * `minmax(0, 1fr)`, not `1fr`, for the same reason as the column.
   *
   * A bare `1fr` track has an automatic MINIMUM of its content's height, so
   * the middle row stops shrinking while there is still content to fit and the
   * rows below it get pushed past the bottom of the screen. Measured on a
   * 393x852 phone with the home-indicator inset simulated: the footer's bottom
   * edge landed 14px under the indicator, where iOS will not show it.
   */
  grid-template-rows: auto minmax(0, 1fr) auto auto;
  /*
   * `minmax(0, 1fr)`, not `1fr`, and `min-width: 0` on every row.
   *
   * A grid item's default `min-width: auto` is its MIN-CONTENT width, and the
   * footer's link rail is `flex-wrap: nowrap` — so its min-content is the whole
   * 1,120px run of thirteen links. The column took that as its floor and every
   * row overflowed: measured on a 390px phone, the Play button came out 692px
   * wide and ran off the side of the screen. The rail is meant to scroll inside
   * its row, which it cannot do until the row is allowed to be narrower than
   * its contents.
   */
  grid-template-columns: minmax(0, 1fr);
  /* Belt as well as braces: with every row allowed to shrink, nothing SHOULD
     reach here — but a row that grows unexpectedly must clip rather than push
     the footer off a screen that cannot scroll. */
  overflow: hidden;
  padding:
    calc(16px + var(--safe-t)) calc(clamp(16px, 3vw, 34px) + var(--safe-r))
    calc(12px + var(--safe-b)) calc(clamp(16px, 3vw, 34px) + var(--safe-l));
  gap: 8px;
}
/*
 * Rows may be shorter than their contents, columns narrower than theirs.
 *
 * `min-height: auto` is the vertical twin of the min-width bug this file
 * already documents, and it bites in exactly the place there is no room to
 * spare: a 393px landscape phone. The middle row is `1fr` and CANNOT shrink
 * below its content, so the whole grid overflowed — measured, the footer's
 * bottom edge sat at 524px in a 393px viewport, with the bottom rail and every
 * link off the screen and no way to scroll to them.
 */
.menu > * { min-width: 0; min-height: 0; }

.menu__rail {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
}
.menu__rail--bottom { align-items: flex-end; }

.menu__eyebrow {
  margin: 0; font-family: var(--font-display); font-weight: 600;
  font-size: 0.72rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--accent);
  padding: 7px 14px; border-radius: var(--radius-pill);
  background: rgba(198, 245, 54, 0.12);
}
.menu__nav { display: flex; align-items: center; gap: clamp(12px, 2.2vw, 28px); flex-wrap: wrap; }
/*
 * Pills, not underlined text.
 *
 * A row of dim links over a moving arena is both hard to read and the most
 * document-like thing on a game's front screen. These are chips: a soft ground
 * so the text always has something behind it, and a real hover state.
 */
.menu__nav a, .menu__nav .linkbutton {
  /* Beats .linkbutton's underline — these are chips, not inline links. */
  color: var(--paper-dim); text-decoration: none;
  background: rgba(255, 255, 255, 0.07); border: 0;
  padding: 9px 16px 10px; border-radius: var(--radius-pill); cursor: pointer;
  font-family: var(--font-display); font-weight: 600; font-size: 0.95rem;
  transition: color 0.14s ease, background 0.14s ease, transform 0.12s ease;
}
.menu__nav a:hover, .menu__nav .linkbutton:hover {
  color: #16233A; background: var(--paper); transform: translateY(-1px);
}
.menu__nav a:focus-visible, .menu__nav .linkbutton:focus-visible {
  outline: 3px solid var(--accent); outline-offset: 3px;
}
.menu__rule { width: 2px; align-self: stretch; min-height: 20px; background: rgba(255,255,255,0.16); border-radius: 2px; }

/* ----------------------------------------------------------- the middle */
.menu__core {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 14px; min-height: 0; text-align: center;
}
.menu__mark {
  display: block; width: min(560px, 46vw); height: auto;
  /* Sized off the shorter axis too, or it eats a landscape phone whole. */
  max-height: 28vh; object-fit: contain;
  /* The artwork carries its own padding; pull the tagline back up to it. */
  margin-bottom: -8px;
  /* Lifts it off the floor. Two shadows: a tight dark one for the edge, and a
     wide lime one for the bloom the backdrop is already painting — they read
     as one light source rather than as a drop shadow. */
  filter:
    drop-shadow(0 2px 10px rgba(0, 0, 0, 0.85))
    drop-shadow(0 0 46px rgba(198, 245, 54, 0.22));
}
.menu__tagline {
  margin: 0; font-size: clamp(1.25rem, 2.4vw, 1.85rem);
  font-family: var(--font-display); font-weight: 600; letter-spacing: 0;
  /* Three claims, three colours — it is a joke with a rhythm, so let it have
     one. The last one is the lime, because "Go again" is the button. */
  color: var(--paper);
  text-shadow: 0 2px 16px rgba(0, 0, 0, 0.95);
}
.menu__tagline b { font-weight: 600; color: var(--tang); }
.menu__tagline i { font-style: normal; color: var(--sky-soft); }
.menu__tagline u { text-decoration: none; color: var(--accent); }

.menu__start {
  display: flex; gap: 12px; align-items: flex-end; justify-content: center;
  flex-wrap: wrap; width: min(700px, 100%); margin-top: 6px;
}
.menu__field { flex: 1 1 320px; text-align: left; display: block; }
.menu__label {
  display: block; margin-bottom: 7px;
  font-family: var(--font-display); font-weight: 600; font-size: 0.7rem;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--accent);
}
/*
 * White, not a dark hole.
 *
 * A near-black input on a near-black page is the single most "corporate dark
 * dashboard" element on the screen, and it is also the hardest thing to read
 * over a moving arena. Every game this is measured against uses a bright pill:
 * the one place you TYPE should be the one place that is paper.
 */
.menu__input {
  width: 100%; padding: 16px 20px; font: inherit; font-size: 1.2rem; font-weight: 600;
  color: #16233A; background: var(--paper);
  border: 0; border-radius: var(--radius-sm);
}
.menu__input::placeholder { color: #8496AE; font-weight: 500; }
.menu__input:focus { outline: 3px solid var(--accent); outline-offset: 2px; }
.menu__input:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
/*
 * Buttons are FLAT.
 *
 * They had an extruded lip and a coloured glow — "get rid of shadows on
 * buttons, the lip, make the buttons everywhere better". The lip is a 2010
 * mobile-game tell and the glow made every control look like it was on fire;
 * together they meant the Play button, the nav chips and the choice cards were
 * all shouting at the same volume.
 *
 * What is left carries the weight instead: a solid fill, a generous radius,
 * real size, and one honest press state — the whole thing dips a pixel and
 * darkens. That is enough to feel like a button and quiet enough that the
 * screen has a hierarchy again.
 */
.menu__play {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; gap: 12px;
  padding: 18px 38px; border: 0; border-radius: var(--radius-sm); cursor: pointer;
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--font-display); font-weight: 700; font-size: 1.45rem;
  letter-spacing: 0.01em;
  transition: background 0.12s ease, transform 0.08s ease;
}
.menu__play:hover { background: #D6FF56; }
.menu__play:active { background: var(--accent-2); transform: translateY(1px); }
.menu__play:focus-visible { outline: 3px solid var(--paper); outline-offset: 3px; }
.menu__arrow { font-size: 1.1em; line-height: 1; }

/* --------------------------------------------------------- the skin strip */
.menu__look { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; justify-content: center; }
.menu__look .menu__label { margin: 0; }
.swatches {
  display: flex; align-items: center; gap: 8px;
  max-width: min(520px, 70vw); overflow-x: auto; padding: 3px;
  scrollbar-width: none;
}
.swatches::-webkit-scrollbar { display: none; }
.swatch {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%; cursor: pointer;
  border: 2px solid transparent; padding: 0;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.45);
  transition: transform 0.12s ease, border-color 0.12s ease;
}
.swatch:hover { transform: scale(1.12); }
.swatch.is-selected { border-color: var(--text); }
.swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.menu__more {
  background: none; border: 0; padding: 0; cursor: pointer; font: inherit;
  color: var(--text-dim); font-size: 0.94rem; display: inline-flex; align-items: center; gap: 8px;
}
.menu__more:hover { color: var(--text); }
.menu__more:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; border-radius: 4px; }

/*
 * Three claims, three chips, and never a wrapped one.
 *
 * As one run of text with middots it broke as "FREE TO PLAY · NO DOWNLOAD · NO
 * SIGN-" / "UP" on a 390px phone — Gray spotted it. Chips cannot break a word
 * because each one is its own box with `white-space: nowrap`; the ROW wraps
 * instead, which is what should give.
 */
.menu__badges {
  margin: 2px 0 0; padding: 0; list-style: none;
  display: flex; flex-wrap: wrap; gap: 6px 8px; justify-content: center;
}
.menu__badges li {
  white-space: nowrap;
  font-family: var(--font-display); font-weight: 600; font-size: 0.72rem;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--paper-dim);
  background: rgba(255, 255, 255, 0.08); border-radius: var(--radius-pill);
  padding: 6px 13px 7px;
}
/*
 * The skin, shown.
 *
 * A card rather than a line of text, and a link rather than a button: it goes
 * to the settings page's Your worm section, which is where the choosing
 * happens. The whole card is the target, so nobody has to hit the word.
 */
.skinbox {
  display: grid; gap: 2px 14px; align-items: center; justify-items: center;
  grid-template-areas: "label art" "name art" "go art";
  grid-template-columns: auto minmax(0, 260px);
  padding: 10px 18px 10px 22px; border-radius: var(--radius);
  border: 2px solid rgba(255, 255, 255, 0.14);
  background: rgba(10, 14, 26, 0.55);
  text-decoration: none; color: var(--text);
  transition: border-color 0.14s ease, background 0.14s ease, transform 0.12s ease;
}
@supports (backdrop-filter: blur(1px)) {
  .skinbox { background: rgba(10, 14, 26, 0.38); backdrop-filter: blur(14px) saturate(130%); }
}
.skinbox:hover {
  border-color: var(--accent); background: rgba(10, 14, 26, 0.62); transform: translateY(-2px);
}
.skinbox:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.skinbox__label {
  grid-area: label; justify-self: start;
  font-family: var(--font-display); font-weight: 700; font-size: 0.66rem;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--text-dim);
}
.skinbox__name {
  grid-area: name; justify-self: start;
  font-family: var(--font-display); font-weight: 700; font-size: 1.15rem; color: var(--paper);
}
.skinbox__go {
  grid-area: go; justify-self: start; font-size: 0.78rem; color: var(--accent);
  font-weight: 600;
}
.skinbox__art { grid-area: art; display: block; width: 100%; height: auto; aspect-ratio: 260 / 96; }
/*
 * The pair.
 *
 * Skin and Arena are the two cosmetics you own, so they are one row of two
 * matching cards rather than a card and a control that look like different
 * species. `minmax(0, 1fr)` on both tracks, because the card's own grid has an
 * `auto` text column and a 260px picture — with a bare `1fr` the row refuses
 * to go below the sum of those and pushes the menu sideways on a phone.
 */
.pickrow {
  display: grid; gap: 12px;
  /* auto-fit, because a portal build ships the arena card alone. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
  width: 100%; max-width: 720px;
}
.pickrow > * { min-width: 0; }
/* The button resets: a <button> brings its own font, colour and centring. */
.skinbox--arena {
  font: inherit; text-align: left; cursor: pointer;
  appearance: none; -webkit-appearance: none;
}
.skinbox--arena:disabled { cursor: default; }
/* A card that cannot advance must not offer to: no lift, no accent border. */
.skinbox--arena:disabled:hover {
  border-color: rgba(255, 255, 255, 0.14);
  background: rgba(10, 14, 26, 0.55); transform: none;
}
/*
 * The floor swatch is a picture of a surface, not a worm, so it gets an edge.
 * Without one it bleeds into the card's own dark panel and reads as a hole.
 */
.skinbox__art--floor {
  border-radius: 14px; border: 1px solid rgba(255, 255, 255, 0.16);
  aspect-ratio: 260 / 96;
}
@media (max-width: 620px) {
  /* Two 260px pictures do not fit; stack, and let each card use the width. */
  .pickrow { grid-template-columns: minmax(0, 1fr); }
}
.menu__unlock { margin: 0; font-size: 0.8rem; color: var(--text-dim); min-height: 1.2em; }

/* ------------------------------------------------------ the bottom rail */
.menu__keys {
  margin: 0; display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  color: var(--paper-dim); font-size: 0.9rem;
  font-family: var(--font-display); font-weight: 500;
}
.menu__keys kbd {
  font-family: var(--font-display); font-weight: 700; font-size: 0.68rem;
  letter-spacing: 0.1em; text-transform: uppercase; color: var(--text);
  background: rgba(255, 255, 255, 0.06); border: 1px solid var(--line);
  border-radius: 7px; padding: 6px 10px;
}
.menu__keys kbd + kbd { margin-left: 8px; }
.menu__rules {
  margin: 0; color: var(--paper-dim); font-size: 0.92rem; text-align: center;
  font-family: var(--font-display); font-weight: 500;
  text-shadow: 0 1px 10px rgba(0, 0, 0, 0.95);
}
.menu__rail--bottom .linkbutton {
  text-decoration: none;
  background: rgba(255, 255, 255, 0.07); border: 0; cursor: pointer;
  padding: 8px 15px 9px; border-radius: var(--radius-pill);
  font-family: var(--font-display); font-weight: 600; font-size: 0.85rem;
  color: var(--paper-dim); transition: color 0.14s ease, background 0.14s ease;
}
.menu__rail--bottom .linkbutton:hover { color: #16233A; background: var(--paper); }
.menu__rail--bottom .linkbutton:focus-visible {
  outline: 3px solid var(--accent); outline-offset: 3px;
}

/* ----------------------------------------------------------- the footer */
.menu__foot {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px 20px; flex-wrap: wrap;
  font-size: 0.8rem; color: var(--paper-dim);
  font-family: var(--font-display); font-weight: 500;
  border-top: 2px solid rgba(255, 255, 255, 0.08); padding-top: 11px;
}
.menu__copy { flex: 0 0 auto; }
.menu__footlinks { display: flex; flex-wrap: wrap; gap: 4px 16px; justify-content: flex-end; min-width: 0; }
.menu__footlinks a {
  color: var(--paper-dim); text-decoration: none;
  padding: 3px 2px; border-radius: 6px;
}
.menu__footlinks a:hover { color: var(--accent); }

/*
 * The corner controls belong to a RUN.
 *
 * The menu carries Sound and Settings as words in its top rail, so the round
 * icons in the corner would be a second copy of both on the one screen that
 * has room to spell them out. They come back the moment there is a worm.
 */
.game:has(.overlay[data-game-menu]:not([hidden])) .dock { display: none; }

/*
 * A chromeless page IS the arena.
 *
 * No site header, no site footer, no document scroll. `--header-h` is set to 0
 * by the measuring script when there is no header to measure, so the arena's
 * `100dvh - var(--header-h)` below resolves to the whole viewport without a
 * second height rule to keep in step with the first.
 *
 * `min-height` has to go with it. A 420px floor inside a 390px landscape phone
 * is 30px of document scroll, and the moment the page can scroll a drag that
 * starts on the arena can pull the page instead of steering the worm.
 */
/*
 * `overflow: hidden` belongs to the ARENA, not to "no site header".
 *
 * It was on .page--chromeless, which the settings page also is — so the
 * settings page had to put `overflow: auto` back, which makes <body> the
 * scroll container. A body scroll container does not deliver scroll events to
 * `window` in Chrome: measured, `scrollY` moved from 0 to 1500 and a listener
 * on window fired ZERO times, so the settings sidebar's scroll spy never
 * updated after the first paint. The two concerns are separate and are
 * separated now.
 */
/*
 * Safe areas, on a phone held sideways.
 *
 * `viewport-fit=cover` lets the arena fill the glass, notch included — which is
 * right, a game should. What it must not do is put anything you need to READ
 * or PRESS under the notch, the home indicator or the browser's own chrome.
 * Gray, in landscape: "cant play sideways it dont line up right and is cut
 * off".
 *
 * So the canvas keeps the whole screen and every layer ON it is inset. One
 * variable per edge, defaulting to zero everywhere that has no cutout.
 */
:root {
  --safe-t: env(safe-area-inset-top, 0px);
  --safe-r: env(safe-area-inset-right, 0px);
  --safe-b: env(safe-area-inset-bottom, 0px);
  --safe-l: env(safe-area-inset-left, 0px);
}
.page--home { overflow: hidden; }
.page--chromeless main { padding: 0; }
/*
 * NO VIEWPORT BREAKOUT on the page that IS the arena.
 *
 * `main > .game` widens itself to 100vw and re-centres with
 * `margin-left: 50%; translateX(-50vw)`, which is the right trick for an arena
 * embedded in a padded reading column — and completely wrong here, where main
 * has no padding and no column to escape.
 *
 * It is also actively harmful on iOS. In landscape, Safari's side chrome sits
 * OVER the page: 100vw is wider than the part you can see, so the game hung off
 * BOTH edges — and everything pinned to `inset: 0` inside it went with it.
 * Gray's screenshot shows the readout starting at RANK, with the whole LENGTH
 * panel off the left of the screen behind Safari's sidebar, and the minimap and
 * Boost clipped on the right.
 *
 * There is nothing to break out of. 100% of main is the visible width, by
 * definition, whatever the browser is overlaying it with.
 */
.page--chromeless main > .game {
  width: 100%;
  margin-left: 0;
  transform: none;
  min-height: 0;
}

/* --------------------------------------------------- the content pages
 *
 * The same world as the settings page, not a different one.
 *
 * These were near-black with hairline rules while the settings page had a
 * blue-violet ground, cards and a rounded, coloured toolkit — Gray: "all these
 * old inner pages mate dont match settings page design". Two designs on one
 * site is worse than either of them on its own.
 *
 * So the ground, the surfaces, the type and the accents are shared. What is
 * NOT shared is density: a page of prose wants a reading measure and air, a
 * settings screen wants rows. Matching a design means the same materials, not
 * the same layout.
 */
.page:not(.page--chromeless) {
  background:
    radial-gradient(760px 500px at 8% 0%, rgba(139, 92, 246, 0.26), transparent 66%),
    radial-gradient(820px 540px at 96% 4%, rgba(44, 143, 214, 0.24), transparent 68%),
    linear-gradient(180deg, #161B33 0%, #10152A 44%, #0B0F1E 100%);
  background-attachment: fixed;
}

h1 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(2rem, 4.6vw, 3rem); line-height: 1.1;
  margin-block: 0.2em 0.5em; letter-spacing: -0.01em; color: var(--paper);
}
h2 {
  font-family: var(--font-display); font-weight: 600;
  font-size: clamp(1.4rem, 2.6vw, 1.9rem); line-height: 1.2;
  margin-block: 1.6em 0.5em; letter-spacing: 0; color: var(--paper);
}
h3 {
  font-family: var(--font-display); font-weight: 600;
  font-size: 1.15rem; margin: 1.4em 0 0.35em; color: var(--paper);
}

.prose { max-width: var(--max); color: var(--paper-dim); }
.prose p { color: var(--paper-dim); }
.prose strong { color: var(--paper); }
.prose a { color: var(--accent); text-decoration-color: rgba(198, 245, 54, 0.4); }
.prose a:hover { text-decoration-color: var(--accent); }
.prose ul, .prose ol { padding-left: 1.25em; }
.prose li { margin: 0.4em 0; }

/*
 * Every section is a card, the way the settings sections are.
 *
 * A run of h2s separated by nothing is what made these read as a document from
 * a different site. The card is the shared object: same radius, same lit edge,
 * same translucent surface over the same ground.
 */
.prose > section {
  border-radius: var(--radius); padding: 6px clamp(18px, 3vw, 30px) 24px;
  background: rgba(255, 255, 255, 0.05);
  border: 2px solid rgba(255, 255, 255, 0.08);
  margin-block: 20px;
}
.prose > section:first-of-type h2 { margin-top: 1em; }

.lede {
  font-size: 1.12rem; color: var(--paper);
  border-left: 4px solid var(--accent);
  padding: 18px 20px; margin-block: 0 1.6em;
  max-width: var(--max);
  background: rgba(198, 245, 54, 0.07);
  border-radius: 0 var(--radius) var(--radius) 0;
}

.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(--paper-dim); font-size: 0.92rem; }

.faq {
  margin-block: 20px; border-radius: var(--radius);
  padding: 6px clamp(18px, 3vw, 30px) 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 2px solid rgba(255, 255, 255, 0.08);
}
.faq__item { border-top: 2px solid rgba(255, 255, 255, 0.07); padding: 16px 0; }
.faq__item:first-of-type { border-top: 0; }
.faq__q {
  margin: 0 0 0.3em; font-size: 1.1rem;
  font-family: var(--font-display); font-weight: 600; color: var(--paper);
}
.faq__a { margin: 0; color: var(--paper-dim); }

.related {
  margin-top: 24px; border-radius: var(--radius);
  padding: 6px clamp(18px, 3vw, 30px) 22px;
  background: rgba(255, 255, 255, 0.05);
  border: 2px solid rgba(255, 255, 255, 0.08);
}
.related__list { list-style: none; margin: 0; padding: 0; }
.related__list li { margin: 0.9em 0; }
.related__list a { font-family: var(--font-display); font-weight: 600; font-size: 1.05rem; }
.related__desc { color: var(--paper-dim); font-size: 0.92rem; display: block; }

.gamelist { list-style: none; margin: 1.2em 0; padding: 0; display: grid; gap: 12px; }
.gamecard {
  border: 2px solid rgba(255, 255, 255, 0.09); border-radius: var(--radius-sm);
  padding: 16px 18px; background: rgba(255, 255, 255, 0.05);
}
.gamecard--ours {
  border-color: rgba(198, 245, 54, 0.45); background: rgba(198, 245, 54, 0.08);
}
.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: 2px solid rgba(255, 255, 255, 0.09);
  padding: 40px clamp(16px, 4vw, 40px) 0;
  color: var(--paper-dim); font-size: 0.92rem;
  background: rgba(8, 11, 20, 0.5);
}
.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(--paper-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 corner controls
 * stay: settings and mute 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:
    calc(12px + var(--safe-t)) calc(12px + var(--safe-r))
    calc(12px + var(--safe-b)) calc(12px + var(--safe-l));
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: auto 1fr auto;
  gap: 10px; font-size: 0.9rem;
}
/*
 * ONE readout, not four boxes.
 *
 * It was three fat rounded cards and a floating pill, each with its own border
 * and its own backdrop — four objects to say three numbers, unevenly spaced,
 * with the ping drifting off on its own. Gray: "sort this ugly shit man, its
 * badly laid out".
 *
 * One strip now, with hairline dividers between the readouts, a lime edge to
 * tie it to everything else, and the numbers on a shared baseline so they read
 * across rather than as separate widgets. The ping lives INSIDE it, at the end,
 * where it is context rather than a fourth card.
 */
.hud__stat {
  grid-row: 1; align-self: stretch;
  display: flex; flex-direction: column; justify-content: center;
  padding: 6px 16px; background: none; border: 0; border-radius: 0;
}
.hud__stat + .hud__stat { border-left: 1px solid rgba(198, 245, 54, 0.16); }
.hud__label {
  display: block; margin: 0 0 1px; white-space: nowrap;
  font-family: var(--font-display); font-weight: 600; font-size: 0.62rem;
  text-transform: uppercase; letter-spacing: 0.13em; color: var(--text-dim);
}
.hud__value {
  font-family: var(--font-display); font-weight: 600; font-size: 1.35rem;
  line-height: 1.05; font-variant-numeric: tabular-nums; color: var(--paper);
}

/*
 * 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: 0;
  /* The gentle lime edge Gray asked for, on the one piece of chrome that is up
     the whole time you are playing. Hairline, and low alpha: it should read as
     the strip belonging to this game, not as a highlighted control. */
  border: 1px solid rgba(198, 245, 54, 0.22);
  border-radius: var(--radius-sm);
  background: rgba(7, 10, 18, 0.55);
  overflow: hidden;
}
@supports (backdrop-filter: blur(1px)) {
  .hud__stats { background: rgba(7, 10, 18, 0.34); backdrop-filter: blur(10px) saturate(130%); }
}
.hud__stat { grid-row: auto; }
/* The arena count is context, not a score: same strip, quieter number. */
.hud__stat--quiet .hud__value { font-size: 1.05rem; color: var(--text-dim); }
/* Length is the score. It gets the accent and the size; the other two do not,
   or the strip is three equal numbers and none of them is the one that matters. */
.hud__stat:first-child .hud__value { color: var(--accent); font-size: 1.55rem; }
.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); }
/*
 * The ping sits INSIDE the strip, at the end. It was a separate floating pill
 * with its own border, drifting beside three cards — a fourth object for a
 * number nobody looks at twice.
 */
.chip--ping {
  cursor: default; font-variant-numeric: tabular-nums;
  align-self: stretch; display: flex; align-items: center;
  margin: 0; padding: 0 14px;
  background: none; border: 0;
  /* No divider before it. The strip is clipped to its own radius, so a border
     on the LAST child renders as an arc following the rounded corner rather
     than as a straight rule — it looked like a rendering fault. Space does the
     separating instead. */
  padding-left: 4px;
  font-family: var(--font-display); font-weight: 500; font-size: 0.72rem;
  color: var(--text-dim);
}
.chip--ping:hover { color: var(--text-dim); border-color: transparent; }

.boostbutton {
  position: absolute;
  right: calc(var(--boost-inset) + var(--safe-r));
  bottom: calc(var(--boost-inset) + var(--safe-b));
  width: var(--boost-size); height: var(--boost-size);
  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); }
/* Boost steers nothing on a menu. It also sat in the same corner as the link
   bar's right-hand end, which on a phone put a 96px circle over three links. */
.game:has(.overlay[data-game-menu]:not([hidden])) .boostbutton,
.game:has(.overlay[data-game-settings]:not([hidden])) .boostbutton,
.game:has(.overlay[data-game-death]:not([hidden])) .boostbutton { display: none; }

/*
 * 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));
}
/*
 * Settings is read, not looked past, and it sits ON the menu.
 *
 * At the ordinary backdrop strength the menu panel behind it stayed legible
 * through the blur — the wordmark and the NAME YOUR WORM field showing as a
 * ghost rectangle behind the word "Settings", which reads as a rendering fault
 * rather than as depth.
 */
.overlay--settings {
  background: radial-gradient(circle at 50% 40%, rgba(10, 14, 26, 0.9), rgba(4, 6, 12, 0.97));
}
.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;
}
/*
 * There is no `.panel--menu` any more.
 *
 * It was a 430px card with its own background, border and scrollbar floating
 * in the middle of a live arena — the single thing that made this read as a
 * game embedded in a website. The front screen is laid straight onto the
 * arena now; see `.menu` above.
 */
.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; }
/*
 * The menu's foot: pinned like the head, for the same reason.
 *
 * Settings used to be a section inside the scroller, under forty skin tiles.
 * A control you have to scroll past a wall of cosmetics to reach is a control
 * most people never find.
 */
.panel__foot {
  flex: 0 0 auto; display: flex; gap: 8px; justify-content: center; flex-wrap: wrap;
  padding: 12px 18px 14px; border-top: 1px solid var(--line);
}
.panel__foot .button { flex: 0 1 auto; }
.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: calc(14px + var(--safe-l)); bottom: calc(14px + var(--safe-b)); z-index: 9;
  display: flex; flex-direction: column; align-items: flex-start; gap: 8px;
}
/*
 * The corner does not move any more, because it is not on screen at the same
 * time as anything it could collide with — the menu hides it outright (see the
 * `.menu` block) and puts Sound and Settings in its top rail as words.
 *
 * The rule that used to live here lifted it over the bottom link bar with
 * `bottom: calc(var(--linkbar-h) + 10px)` and a transition. Worth remembering
 * why it went: a `transition` on a value fed by a custom property that
 * JavaScript sets after first paint left the property parked on its FALLBACK
 * permanently — measured 58px with the variable sitting at 70px, unchanged
 * whatever it was set to afterwards. Never transition a property whose value
 * comes from a measured variable.
 */
.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; }
}

/*
 * The dock's popover is gone.
 *
 * It held one copy of the sound switches while the menu held another, and the
 * two disagreed — see settings.ts. There is one settings screen now, and the
 * corner is two buttons that reach it.
 */
.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; }

/* ------------------------------------------------------- the settings screen */
.panel--settings {
  width: min(460px, 100%);
  /* The link bar fades out while this is up, so the whole height is free —
     nothing to reserve, and no constant to go stale. */
  max-height: 100%;
  padding: 0; overflow: hidden; display: flex; flex-direction: column;
  background: rgba(16, 20, 31, 0.86);
  border-color: rgba(198, 245, 54, 0.18);
  box-shadow: 0 30px 80px -40px #000, 0 0 0 1px rgba(0, 0, 0, 0.4);
}
@supports (backdrop-filter: blur(1px)) {
  .panel--settings { background: rgba(16, 20, 31, 0.7); backdrop-filter: blur(18px) saturate(130%); }
}
/* Title on the left, the way out on the right — the one control on this screen
   that must never need scrolling to, for the same reason Play does not. */
.panel__head--row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; text-align: left; padding: 16px 18px;
}
.panel__head--row .panel__title { margin: 0; font-size: 1.2rem; }

.setting { display: flex; flex-direction: column; gap: 8px; padding: 16px 0; }
.setting + .setting { border-top: 1px solid var(--line); }
.setting:first-child { padding-top: 6px; }
.setting__row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.setting__label {
  margin: 0; font-family: var(--font-display); font-size: 0.76rem; font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-dim);
}
.setting__blurb { margin: 0; font-size: 0.82rem; color: var(--text-dim); line-height: 1.45; }
.setting__slider { display: block; }
.setting__slider input { width: 100%; accent-color: var(--accent); }
.setting__transport { display: flex; gap: 6px; }
.setting__now { margin: 0; font-size: 0.76rem; color: var(--text-dim); line-height: 1.4; }
.setting__now.is-problem { color: var(--warn); }
.setting__credit { margin: 0; font-size: 0.72rem; color: var(--text-dim); line-height: 1.4; }

/* 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; }

/*
 * The corner survives on a phone now.
 *
 * It used to hide the settings button below 900px with a coarse pointer,
 * because what it opened was a 268px popover with three station chips and two
 * sliders crammed into the corner over a live arena. What it opens now is a
 * full screen with room to breathe, which is if anything BETTER on a phone
 * than on a desktop. The mute button was always here; the settings button
 * joins it.
 */

/* ------------------------------------------------------- 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) {
  /*
   * The readout on a phone.
   *
   * "this shit aint gonna work like that on mobile mate" — and it did not:
   * IN ARENA broke across two lines, which made the strip taller than the
   * leaderboard beside it and pushed the two into each other. Labels never
   * wrap now (see .hud__label), the padding halves, and the ping goes: it is
   * the least useful number on the strip and the first thing that should give
   * up its space on the smallest screen there is.
   */
  .hud { padding: calc(8px + var(--safe-t)) calc(8px + var(--safe-r))
                 calc(8px + var(--safe-b)) calc(8px + var(--safe-l)); }
  .hud__stat { padding: 4px 9px; }
  .hud__label { font-size: 0.5rem; letter-spacing: 0.06em; }
  .hud__value { font-size: 0.95rem; }
  .hud__stat:first-child .hud__value { font-size: 1.1rem; }
  .hud__stat--quiet .hud__value { font-size: 0.85rem; }
  .chip--ping { display: none; }

  /* 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); }

  .hud__stats { text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); }

  /*
   * Bottom left, and it can sit right in the corner now.
   *
   * It used to be pushed a whole joystick-height up the screen, because the
   * joystick owned that corner during a run. Steering is drag-anywhere now and
   * the only touch control is the boost button, which is bottom RIGHT — so the
   * left corner is free and the dock stops floating oddly above nothing.
   */
  .dock {
    left: calc(12px + var(--safe-l)); right: auto; top: auto;
    bottom: calc(12px + var(--safe-b));
  }

  /*
   * The menu on a phone.
   *
   * Same three rails, tightened: the wordmark loses a third of its height, the
   * name field and Play stack rather than sitting side by side (320px + a
   * button does not fit in 390px), and the footer's twelve links become one
   * rail you push sideways instead of a block that wraps to five rows.
   */
  .menu {
    padding: calc(12px + var(--safe-t)) calc(14px + var(--safe-r))
             calc(10px + var(--safe-b)) calc(14px + var(--safe-l));
    gap: 6px;
  }
  /*
   * The top rail on a phone.
   *
   * Four chips and an eyebrow do not fit on 390px, so they wrapped — into two
   * and sometimes three rows of pills stacked over the wordmark, which is what
   * Gray's screenshot shows. Stacking is the wrong give: the rail is
   * NAVIGATION and the middle of the screen is the game.
   *
   * So the whole rail becomes one line that scrolls sideways, eyebrow first,
   * and the middle keeps its room. Same treatment as the settings tabs and the
   * footer links, for the same reason and with the same no-mask rule.
   */
  .menu__rail--top {
    display: flex; flex-wrap: nowrap; gap: 8px; align-items: center;
    overflow-x: auto; scrollbar-width: none; margin-inline: -14px;
    padding: 0 14px; scroll-snap-type: x proximity;
  }
  .menu__rail--top::-webkit-scrollbar { display: none; }
  .menu__eyebrow { flex: 0 0 auto; font-size: 0.6rem; letter-spacing: 0.1em; padding: 6px 11px; }
  .menu__nav { flex: 0 0 auto; flex-wrap: nowrap; gap: 6px; }
  .menu__nav a, .menu__nav .linkbutton {
    flex: 0 0 auto; font-size: 0.86rem; padding: 8px 13px 9px; scroll-snap-align: start;
  }
  .menu__rule { display: none; }        /* a divider inside a scrolling rail is noise */
  .menu__mark { width: 78vw; max-height: 22vh; }
  .menu__tagline { font-size: 1rem; }
  /*
   * `flex: 1 1 320px` on the field is a WIDTH on a desktop and a HEIGHT here —
   * flex-basis follows the main axis. Stacked, it made the name field 320px
   * tall, so the Play button sat a third of a screen below the input it
   * belongs to. Measured on a 390x844 phone.
   */
  .menu__start { flex-direction: column; align-items: stretch; gap: 10px; }
  .menu__field { flex: 0 0 auto; }
  .menu__play { justify-content: center; width: 100%; }
  .menu__rail--bottom { flex-direction: column; align-items: center; gap: 6px; }
  .menu__keys { font-size: 0.8rem; }
  .menu__foot { justify-content: center; }
  .menu__copy { display: none; }        /* the © is the first thing to go */
  .menu__footlinks {
    flex-wrap: nowrap; overflow-x: auto; justify-content: flex-start;
    width: 100%; gap: 0 14px; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .menu__footlinks::-webkit-scrollbar { display: none; }
  /* No fade. It half-erased the last link whether or not there was anything
     past it — see the settings rail above. Padding past the edge instead, so
     the last item can scroll clear. */
  .menu__footlinks { padding-right: 18px; }
  .menu__footlinks a { flex: 0 0 auto; }

  /* 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; }
}

/* ------------------------------------------------------- a phone held sideways
 *
 * This is how people actually play, and nothing was built for it. The phone
 * rules keyed off `max-width: 767px`, and a phone in landscape is 844px wide —
 * so it got the DESKTOP layout in 390px of height: a menu panel taller than
 * the screen, and a HUD sized for a laptop.
 *
 * Keyed on HEIGHT, because that is the thing that is scarce. It applies to a
 * short window on a desktop too, which is correct — the layout has the same
 * problem there.
 */
@media (max-height: 500px) {
  /*
   * 390px of height, and the middle row is what has to give.
   *
   * The wordmark comes down to a strip, the tagline and the badges go — they
   * are the two lines nobody reads twice — and the name field goes back beside
   * Play, because width is the one thing there is plenty of here.
   */
  .menu {
    padding: calc(8px + var(--safe-t)) calc(14px + var(--safe-r))
             calc(6px + var(--safe-b)) calc(14px + var(--safe-l));
    gap: 4px;
  }
  .menu__mark { width: min(300px, 34vw); max-height: 14vh; }
  /*
   * What goes, in 390px of height, and why in this order.
   *
   * The tagline and the badges are read once. The skin card is a PREVIEW — the
   * name of your worm is still in Settings, one tap away, and a 96px picture of
   * it is the most expensive thing on a screen this short. What survives is the
   * name field, Play, and the navigation.
   */
  .menu__tagline, .menu__badges, .pickrow { display: none; }
  .menu__core { gap: 8px; }
  .menu__start { flex-direction: row; align-items: flex-end; }
  .menu__play { width: auto; padding: 12px 22px; font-size: 1.05rem; }
  .menu__input { padding: 11px 14px; font-size: 1rem; }
  .menu__rail--bottom { flex-direction: row; }
  .menu__keys, .menu__rules { font-size: 0.76rem; }
  .menu__keys kbd { padding: 4px 8px; font-size: 0.62rem; }
  /* One line, and it scrolls rather than wrapping — three rows of links is a
     third of the screen. */
  .menu__foot { padding-top: 6px; font-size: 0.68rem; flex-wrap: nowrap; }
  .menu__copy { display: none; }
  .menu__footlinks {
    flex-wrap: nowrap; overflow-x: auto; gap: 0 13px;
    scrollbar-width: none; padding-right: 18px;
  }
  .menu__footlinks::-webkit-scrollbar { display: none; }
  .menu__footlinks a { flex: 0 0 auto; }

  /* The HUD is glanceable, not readable — shrink it rather than let it cover
     a third of a 390px-tall arena. */
  .hud__chip { padding: 4px 7px; }
  .hud__chip .hud__label { font-size: 0.52rem; }
  .hud__chip .hud__value { font-size: 0.92rem; }
  .hud__board { font-size: 0.72rem; }
  .hud__board li:nth-child(n + 6) { display: none; }   /* top 5 is plenty */

  /* Both thumbs are already on the glass; the controls can be smaller and
     tucked further in — but never inside a safe area. In landscape that is
     where the Dynamic Island and the home indicator live, and a mute button
     under either is a mute button you cannot press. */
  :root { --boost-size: 74px; --boost-inset: 14px; }
  .dock {
    bottom: calc(8px + var(--safe-b));
    left: calc(8px + var(--safe-l));
  }
}

/*
 * Nothing anywhere may rely on the page scrolling to reach the game.
 *
 * A phone that rotates mid-run must not leave the arena half off-screen, and
 * the arena is sized from its own box rather than the viewport, so this is the
 * one rule that keeps them agreeing.
 */
@media (max-height: 500px) and (orientation: landscape) {
  .game { height: 100dvh; max-height: 100dvh; }
}

/* ==========================================================================
   THE SETTINGS PAGE

   A real page, not a panel over the arena. The shape comes from measuring
   eight of these: Krunker runs eight categories deep, Shell Shockers splits
   tabs from a two-column list, agar.io hangs a gear off the front screen —
   and every one worth copying separates NAVIGATION from CONTENT rather than
   stacking six sections in one column. Two levels, never three: that is the
   depth at which players stop knowing where they are, and it is the one
   number every game-UI guide agrees on.

   It reads as a DOCUMENT, not as an overlay. A shade lighter than the arena,
   with a real column, real dividers and one accent — the arena is a place,
   this is a desk.
   ========================================================================== */

/*
 * Out of the reading measure.
 *
 * `main > *` centres every direct child on --max (46rem / 736px), which is
 * right for prose and wrong for a two-column settings page: measured at
 * 1512px, the whole thing rendered 640px wide and left-shifted, with the skin
 * grid squeezed to two columns. The arena breaks out of the same rule the same
 * way — see `main > .game`.
 */
.page--settings main { padding: 0; }
.page--settings main > * { width: 100%; max-width: none; margin-inline: 0; }
/*
 * Colour, not a dark dashboard.
 *
 * "more colour, bright colours, thats what kids want, no cooperate looking
 * squared sharp business look" — this was near-black with hairline dividers,
 * which is a settings screen for an IDE. It is a deep blue-violet now with
 * three coloured glows in it, and every surface on top is a rounded card with
 * a lit edge rather than a 1px box.
 */
.page--settings {
  background:
    radial-gradient(760px 500px at 8% 0%, rgba(139, 92, 246, 0.30), transparent 66%),
    radial-gradient(820px 540px at 96% 6%, rgba(44, 143, 214, 0.28), transparent 68%),
    radial-gradient(900px 500px at 50% 100%, rgba(198, 245, 54, 0.10), transparent 70%),
    linear-gradient(180deg, #161B33 0%, #10152A 40%, #0B0F1E 100%);
  background-attachment: fixed;
  min-height: 100dvh;
}
.settings { min-height: 100dvh; display: flex; flex-direction: column; }

/* -------------------------------------------------------------- the bar */
.settings__bar {
  position: sticky; top: 0; z-index: 20;
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: 16px;
  padding: calc(13px + var(--safe-t)) calc(clamp(14px, 4vw, 40px) + var(--safe-r))
           13px calc(clamp(14px, 4vw, 40px) + var(--safe-l));
  border-bottom: 2px solid rgba(255, 255, 255, 0.09);
  background: rgba(16, 21, 42, 0.9);
}
@supports (backdrop-filter: blur(1px)) {
  .settings__bar { background: rgba(16, 21, 42, 0.7); backdrop-filter: blur(18px) saturate(150%); }
}
.settings__back {
  justify-self: start; display: inline-flex; align-items: center; gap: 9px;
  color: var(--paper); text-decoration: none;
  font-family: var(--font-display); font-weight: 600; font-size: 0.95rem;
  padding: 11px 20px 12px; border-radius: var(--radius-pill);
  background: var(--sky);
  transition: background 0.12s ease, transform 0.08s ease;
}
.settings__back:hover { background: var(--sky-soft); }
.settings__back:active { background: var(--sky-deep); transform: translateY(1px); }
.settings__title {
  margin: 0; font-family: var(--font-display); font-weight: 600;
  font-size: 1.2rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--paper);
}
/*
 * The receipt.
 *
 * Writing to localStorage is instant and invisible, which reads as the control
 * not working. Deliberately not a toast: something that flies in over the page
 * on every slider tick would be worse than silence.
 */
.settings__saved {
  justify-self: end; margin: 0; font-size: 0.78rem; color: var(--accent);
  font-family: var(--font-display); font-weight: 700;
  letter-spacing: 0.12em; text-transform: uppercase;
  opacity: 0; transform: translateY(-2px);
  transition: opacity 0.18s ease, transform 0.18s ease;
}
.settings__saved.is-on { opacity: 1; transform: translateY(0); }

/* ------------------------------------------------------------ the split */
.settings__body {
  flex: 1 1 auto; width: 100%; max-width: 1120px; margin-inline: auto;
  display: grid; grid-template-columns: 208px minmax(0, 1fr);
  gap: clamp(24px, 4vw, 56px); padding: clamp(24px, 4vw, 44px) clamp(16px, 4vw, 40px) 80px;
}
.settings__nav { position: sticky; top: 86px; align-self: start; }
.settings__nav ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.settings__nav a {
  display: block; padding: 11px 18px; border-radius: var(--radius-pill);
  color: var(--paper-dim); text-decoration: none;
  font-family: var(--font-display); font-weight: 600; font-size: 1rem;
  border-left: 0;
  /* Short. A 140ms cross-fade between two items reads, in a screenshot and to
     the eye, as two items being highlighted at once. */
  transition: color 0.08s ease, background 0.08s ease;
}
.settings__nav a:hover { color: var(--paper); background: rgba(255, 255, 255, 0.09); }
/* Set by the scroll spy in settings.ts, not by :target — :target only moves
   when a link is clicked, so scrolling past three sections left it lying. */
.settings__nav a.is-current { color: var(--accent-ink); background: var(--accent); }

.settings__pane { min-width: 0; display: grid; gap: 44px; }

/* --------------------------------------------------------- a section */
/*
 * Each section is a CARD, not a run of hairlines.
 *
 * A soft raised surface with a lit top edge — the same object language as the
 * buttons. Hairline dividers on a flat ground is the look Gray called
 * corporate, and it is also harder to scan: nothing tells you where one
 * subject stops.
 */
.set-section {
  scroll-margin-top: 96px;
  border-radius: var(--radius); padding: 24px clamp(18px, 3vw, 30px) 26px;
  background: rgba(255, 255, 255, 0.055);
  border: 2px solid rgba(255, 255, 255, 0.09);
}
@supports (backdrop-filter: blur(1px)) {
  .set-section { backdrop-filter: blur(10px) saturate(120%); }
}
.set-section__head { margin-bottom: 20px; }
.set-section__title {
  margin: 0 0 4px; font-family: var(--font-display); font-weight: 600;
  font-size: 1.7rem; letter-spacing: 0; color: var(--paper);
}
.set-section__blurb { margin: 0; color: var(--paper-dim); font-size: 0.97rem; }

/* ------------------------------------------------------------- a row */
/*
 * Label and description on the left, the control on the right. This is the
 * anatomy every settings screen that is pleasant to use shares, and the
 * description is not optional: a switch called "Ornaments" with nothing under
 * it is a guess.
 */
.set-row {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 16px 24px; align-items: center;
  padding: 18px 0; border-top: 2px solid rgba(255, 255, 255, 0.07);
}
.set-row:first-of-type { border-top: 0; padding-top: 4px; }
.set-row--stack { grid-template-columns: minmax(0, 1fr); align-items: stretch; }
.set-row--slider { grid-template-columns: 150px minmax(0, 1fr); padding-top: 0; border-top: 0; }
.set-row__text { min-width: 0; }
.set-row__label {
  margin: 0 0 3px; font-family: var(--font-display); font-weight: 600;
  font-size: 1.1rem; color: var(--paper);
}
.set-row__label label { cursor: pointer; }
.set-row__sub { color: var(--text-dim); font-size: 0.88rem; }
.set-row__desc { margin: 0; color: var(--paper-dim); font-size: 0.9rem; line-height: 1.5; }
.set-row__desc a { color: var(--accent-2); }
.set-row__control { justify-self: end; }

.set-input {
  width: min(320px, 100%); padding: 14px 18px; font: inherit; font-size: 1.05rem;
  font-weight: 600; color: #16233A; background: var(--paper);
  border: 0; border-radius: var(--radius-sm);
}
.set-input::placeholder { color: #8496AE; font-weight: 500; }
.set-input:focus { outline: 3px solid var(--accent); outline-offset: 2px; }
.set-slider { width: 100%; accent-color: var(--accent); }
.set-note { margin: 10px 0 0; font-size: 0.85rem; color: var(--paper-dim); line-height: 1.5; }
.set-note.is-problem { color: var(--warn); }
.set-transport { display: flex; gap: 8px; margin-top: 10px; }
.set-btn {
  padding: 11px 22px 12px; border-radius: var(--radius-pill); border: 0;
  background: var(--sky); color: var(--paper); cursor: pointer;
  font-family: var(--font-display); font-size: 0.92rem; font-weight: 600;
  transition: background 0.12s ease, transform 0.08s ease;
}
.set-btn:hover { background: var(--sky-soft); }
.set-btn:active { background: var(--sky-deep); transform: translateY(1px); }
.set-btn:disabled { opacity: 0.4; cursor: default; }

/* One row is not like the others, and has to look it. */
.set-row--danger { border-top: 1px solid rgba(255, 59, 92, 0.28); }
.set-row--danger .set-row__label { color: var(--hot); }
.set-danger {
  padding: 12px 24px 13px; border-radius: var(--radius-pill); cursor: pointer;
  font-family: var(--font-display); font-weight: 600; font-size: 0.98rem;
  color: var(--paper); background: var(--berry); border: 0;
  transition: background 0.12s ease, transform 0.08s ease;
}
.set-danger:hover { background: #FF5C79; }
.set-danger:active { background: var(--berry-deep); transform: translateY(1px); }
.set-danger.is-armed { animation: armed 0.9s ease-in-out infinite; }
@keyframes armed { 50% { filter: brightness(1.3); } }
@media (prefers-reduced-motion: reduce) { .set-danger.is-armed { animation: none; } }

/* ------------------------------------------------------ choice cards */
/*
 * One of N. Used by quality, arena floors, radio stations and regions — four
 * controls that are the same question, so they are one component rather than
 * four near-identical sets of classes.
 */
.choices {
  display: grid; gap: 10px; margin-top: 14px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.choice {
  display: grid; gap: 4px; text-align: left; cursor: pointer;
  padding: 15px 18px 17px; border-radius: var(--radius-sm);
  border: 2px solid rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.06);
  color: var(--paper); font: inherit;
  transition: border-color 0.14s ease, background 0.14s ease, transform 0.12s ease;
}
.choice:hover:not(:disabled) { border-color: #38425c; background: rgba(255, 255, 255, 0.05); }
.choice:active:not(:disabled) { transform: translateY(1px); }
.choice:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.choice__name { font-family: var(--font-display); font-weight: 600; font-size: 1.05rem; }
.choice__desc { color: var(--paper-dim); font-size: 0.82rem; line-height: 1.4; }
.choice.is-selected, .choices .station.is-selected {
  border-color: var(--accent); background: rgba(198, 245, 54, 0.16);
}
.choice.is-selected .choice__name { color: var(--accent); }
.choice:disabled, .choice.is-locked { opacity: 0.45; cursor: not-allowed; }

/* The pickers reuse the arena's `.station` markup, so it maps onto this. */
.choices .station {
  display: grid; gap: 4px; text-align: left; cursor: pointer;
  padding: 15px 18px 17px; border-radius: var(--radius-sm);
  border: 2px solid rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.06);
  color: var(--paper); font: inherit;
}
.choices .station.is-selected { border-color: var(--accent); background: rgba(198, 245, 54, 0.07); }
.choices .station.is-selected .station__name { color: var(--accent); }
.choices .station__name { font-family: var(--font-display); font-weight: 600; font-size: 1.05rem; }
.choices .station__desc { color: var(--paper-dim); font-size: 0.82rem; line-height: 1.4; }

/* ------------------------------------------------------- the preview */
/*
 * The point of the skins section.
 *
 * Every cosmetic picker worth copying shows the thing at a size you can see it
 * — Shell Shockers spins a 3D character beside its loadout. A 96px tile is a
 * swatch; this is the worm, and it swims.
 */
.preview {
  margin: 20px 0 8px; border-radius: var(--radius); overflow: hidden;
  border: 2px solid rgba(255, 255, 255, 0.12);
  background:
    radial-gradient(700px 260px at 50% 120%, rgba(198, 245, 54, 0.09), transparent 70%),
    linear-gradient(180deg, #0D111C, #080B13);
}
.preview__canvas { display: block; width: 100%; height: auto; aspect-ratio: 64 / 26; }
.preview__meta {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  flex-wrap: wrap; padding: 12px 18px 14px; border-top: 1px solid var(--line);
  background: rgba(255, 255, 255, 0.02);
}
.preview__name {
  margin: 0; font-family: var(--font-display); font-weight: 600; font-size: 1.3rem;
  color: var(--paper);
}
.preview__unlock { margin: 0; color: var(--paper-dim); font-size: 0.87rem; }

/* ------------------------------------------------------ the skin grid */
.skinset { display: grid; gap: 30px; margin-top: 10px; }
.skinset__heading {
  margin: 0 0 2px; display: inline-block;
  font-family: var(--font-display); font-size: 0.72rem; font-weight: 600;
  letter-spacing: 0.15em; text-transform: uppercase; color: var(--accent);
  background: rgba(198, 245, 54, 0.12); padding: 6px 13px; border-radius: var(--radius-pill);
}
.skinset__how { margin: 8px 0 12px; font-size: 0.86rem; color: var(--paper-dim); }
.skinset__grid {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
}
.skintile {
  display: grid; gap: 2px; padding: 9px 9px 11px; cursor: pointer;
  border-radius: var(--radius-sm); border: 2px solid rgba(255, 255, 255, 0.1);
  background: rgba(255, 255, 255, 0.06); color: var(--paper); font: inherit;
  transition: border-color 0.14s ease, background 0.14s ease, transform 0.12s ease;
}
.skintile:hover:not(:disabled) {
  border-color: #38425c; background: rgba(255, 255, 255, 0.055); transform: translateY(-2px);
}
.skintile:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.skintile__art { display: block; width: 100%; height: auto; aspect-ratio: 176 / 100; }
.skintile__name {
  font-family: var(--font-display); font-size: 0.86rem; font-weight: 500;
  color: var(--paper-dim); text-align: center;
}
.skintile.is-selected {
  border-color: var(--accent); background: rgba(198, 245, 54, 0.16);
}
.skintile.is-selected .skintile__name { color: var(--accent); }
.skintile.is-locked { opacity: 0.42; cursor: not-allowed; }
.skintile:disabled { cursor: not-allowed; }

/* ---------------------------------------------------------- controls */
.keys { margin: 0; display: grid; gap: 0; }
.keys__row {
  display: grid; grid-template-columns: minmax(0, 240px) minmax(0, 1fr);
  gap: 10px 24px; align-items: baseline;
  padding: 14px 0; border-top: 2px solid rgba(255, 255, 255, 0.07);
}
.keys__row:first-child { border-top: 0; padding-top: 2px; }
.keys dt { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.keys dd { margin: 0; color: var(--paper-dim); font-size: 0.94rem; line-height: 1.5; }
.keys__or { color: var(--paper-dim); font-size: 0.82rem; }
/* Flat, like everything else. A key cap does not need a fake bevel to read as
   a key when it is white on a dark page. */
.keys kbd, .menu__keys kbd {
  font-family: var(--font-display); font-weight: 600; font-size: 0.7rem;
  letter-spacing: 0.08em; text-transform: uppercase; color: #16233A;
  background: var(--paper); border: 0;
  border-radius: 8px; padding: 7px 11px; white-space: nowrap;
}

/* -------------------------------------------------------- the stats */
.stats { margin: 0 0 8px; display: grid; gap: 0; }
.stats__row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 20px;
  padding: 13px 0; border-top: 2px solid rgba(255, 255, 255, 0.07);
}
.stats__row:first-child { border-top: 0; padding-top: 2px; }
.stats dt { color: var(--paper-dim); font-size: 0.95rem; }
.stats dd {
  margin: 0; font-family: var(--font-display); font-weight: 600; font-size: 1.35rem;
  color: var(--accent); font-variant-numeric: tabular-nums;
}

.settings__foot {
  margin: 0; padding-top: 26px; border-top: 2px solid rgba(255, 255, 255, 0.08);
  font-size: 0.86rem; color: var(--paper-dim); line-height: 1.6;
}
.settings__foot a { color: var(--paper-dim); }
.settings__foot a:hover { color: var(--accent); }

/* ------------------------------------------------------- on a phone */
@media (max-width: 820px) {
  /*
   * The settings page on a phone.
   *
   * Gray: "ss2 is mobile, its not nice". It was the desktop layout with the
   * sidebar stacked on top — a rail of tabs above six sections, then a wall of
   * left-aligned rows with their controls pushed to a column that no longer
   * existed. What it needed was not squeezing: the CARD is the unit, and on a
   * phone each row becomes its own little card too, so a control is always
   * next to the words that name it.
   */
  .settings__body { grid-template-columns: minmax(0, 1fr); gap: 16px; padding: 14px 12px 70px; }
  .settings__nav {
    position: sticky; top: 56px; z-index: 15; margin: 0 -12px;
    background: rgba(16, 21, 42, 0.94); padding: 8px 12px;
    border-bottom: 2px solid rgba(255, 255, 255, 0.09);
  }
  @supports (backdrop-filter: blur(1px)) {
    .settings__nav { background: rgba(16, 21, 42, 0.78); backdrop-filter: blur(16px); }
  }
  /*
   * The rail scrolls, and the LAST tab must be reachable and whole.
   *
   * It was masked to a fade at the right edge, which looked tidy and cut
   * "Controls" in half whether you had scrolled or not — Gray: "button is cut
   * off man, same on all that nav on mobile". A fade is a hint that something
   * is off-screen; applying it permanently to the last item makes the item
   * look broken instead.
   *
   * No mask. Instead the rail is padded past the viewport edge and given
   * scroll snapping, so a tab always comes to rest fully visible, and the
   * right-hand padding means the last one can scroll clear of the edge.
   */
  .settings__nav ul {
    grid-auto-flow: column; grid-auto-columns: max-content; gap: 6px;
    overflow-x: auto; scrollbar-width: none;
    scroll-snap-type: x proximity;
    padding-right: 20px;
  }
  .settings__nav ul::-webkit-scrollbar { display: none; }
  .settings__nav li { scroll-snap-align: start; }
  .settings__nav a { padding: 10px 18px; white-space: nowrap; font-size: 0.95rem; }

  .settings__pane { gap: 16px; }
  .set-section { padding: 18px 15px 20px; border-radius: 20px; }
  .set-section__title { font-size: 1.4rem; }
  .set-section__blurb { font-size: 0.92rem; }

  /*
   * Each row is its own card: label, description, then the control underneath
   * at full width. Stacked WITHOUT a surface, a switch ended up floating
   * under a paragraph with nothing tying the two together.
   */
  .set-row {
    grid-template-columns: minmax(0, 1fr); gap: 12px;
    padding: 14px; border-top: 0; margin-top: 10px;
    border-radius: var(--radius-sm); background: rgba(255, 255, 255, 0.05);
  }
  .set-row:first-of-type { padding-top: 14px; margin-top: 0; }
  .set-row--stack { background: none; padding: 0; margin-top: 18px; }
  .set-row__control { justify-self: stretch; }
  .set-row__control .switch { justify-self: start; }
  .set-input { width: 100%; }
  .set-row--slider {
    grid-template-columns: minmax(0, 1fr); gap: 8px; background: none; padding: 4px 0 0;
    margin-top: 0;
  }
  .set-row--danger { background: rgba(255, 59, 92, 0.09); border-top: 0; }
  .set-danger { width: 100%; }

  /* Two columns of choices at most, or the text inside them wraps to five
     lines and the cards become paragraphs. */
  .choices { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 8px; }
  .choice, .choices .station { padding: 13px 14px 15px; }
  .choice__desc, .choices .station__desc { font-size: 0.78rem; }

  .skinset__grid { grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 8px; }
  .skintile__name { font-size: 0.78rem; }
  .preview { margin: 16px 0 6px; }
  .preview__meta { padding: 11px 14px 12px; }

  /* The key list is two columns of narrow text at this width. One column,
     with the caps above what they do. */
  .keys__row { grid-template-columns: minmax(0, 1fr); gap: 6px; padding: 12px 0; }

  .settings__bar { grid-template-columns: auto 1fr auto; padding: 9px 12px; }
  .settings__title { font-size: 0.86rem; letter-spacing: 0.12em; }
  .settings__back { padding: 8px 13px 9px; font-size: 0.86rem; }
  .settings__saved { font-size: 0.7rem; }
}

/* ---------------------------------------------------------- the all-time board
 *
 * Real players only, best run first. A table would be the obvious shape and is
 * the wrong one: a rank, a name and a number are three fields, and an <ol> of
 * rows keeps the ordinal in the markup rather than in a column of typed digits.
 */
.board {
  margin: 28px 0 34px; border-radius: var(--radius);
  border: 2px solid var(--line); background: var(--ink-2);
  overflow: hidden;
}
.board__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px 20px; flex-wrap: wrap;
  padding: 18px 22px; border-bottom: 2px solid var(--line);
  background: linear-gradient(180deg, rgba(198, 245, 54, 0.1), transparent);
}
.board__title {
  margin: 0; font-family: var(--font-display); font-weight: 600; font-size: 1.3rem;
}
.board__status { margin: 0; font-size: 0.86rem; color: var(--text-dim); }
.board__status.is-problem { color: var(--warn); }
.board__rows { margin: 0; padding: 0; list-style: none; }
.board__row {
  display: grid; align-items: baseline; gap: 4px 16px;
  grid-template-columns: 3.4rem minmax(0, 1fr) auto 6.5rem;
  padding: 11px 22px; border-top: 1px solid rgba(38, 46, 66, 0.6);
}
.board__row:first-child { border-top: 0; }
.board__row:nth-child(even) { background: rgba(255, 255, 255, 0.018); }
.board__rank {
  font-family: var(--font-display); font-weight: 600; font-size: 0.9rem;
  color: var(--text-dim); font-variant-numeric: tabular-nums;
}
/* The top three get the colour. Everyone else gets the number — a board where
   every row is decorated has no top three. */
.board__row--1 .board__rank { color: var(--tang); }
.board__row--2 .board__rank { color: #C9D4E4; }
.board__row--3 .board__rank { color: #CE8946; }
.board__row--1 .board__name, .board__row--1 .board__len { color: var(--accent); }
.board__name {
  min-width: 0; overflow-wrap: anywhere;
  font-family: var(--font-display); font-weight: 500; font-size: 1.02rem;
}
.board__len {
  font-family: var(--font-display); font-weight: 600; font-size: 1.05rem;
  font-variant-numeric: tabular-nums; text-align: right;
}
.board__when {
  font-size: 0.78rem; color: var(--text-dim); text-align: right;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 560px) {
  /* The date is the first thing to go: it is context, not the score. */
  .board__row { grid-template-columns: 2.8rem minmax(0, 1fr) auto; padding: 10px 14px; }
  .board__when { display: none; }
  .board__head { padding: 14px 16px; }
}
