/* ==========================================================================
   SNAKE // NEON CIRCUIT

   The palette is declared here and nowhere else. renderer.js reads these same
   tokens out of :root at init, so the canvas and the CSS chrome cannot drift
   apart. This is the restrained foundation, not the final art direction.
   ========================================================================== */

:root {
  color-scheme: dark;

  /* Palette — the board as a printed circuit: substrate, trace, node.
     Contrast is measured, not eyeballed; the figures live in
     docs/tasks/002-neon-circuit-identity.md and must be recomputed if any
     value here changes. The favicon in index.html copies three of these and
     must be updated in the same change. */
  --bg: #0a0d12;
  --arena: #0e141b;
  --grid: #171e29;
  --grid-major: #26313f;
  --frame: #2a3446;
  --edge-mark: #3d4d66;
  --snake-head: #a8ffd8;
  --snake-body: #2fe0a0;
  --food: #ff4d70;

  /* Text — both pass WCAG AA against --bg and against the overlay panel */
  --ink: #e8eef7;
  --ink-dim: #93a1b5;

  /* Spacing scale */
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;

  /* Type scale */
  --text-xs: 0.6875rem;
  --text-sm: 0.8125rem;
  --text-lg: 1.125rem;
  --text-xl: 1.5rem;

  /* Motion */
  --duration-fast: 140ms;
  --ease-out: cubic-bezier(0.2, 0.8, 0.3, 1);

  --radius-md: 10px;

  /* Touch targets. 44px is the accessibility floor, not a preference: the pad's
     whole footprint — 3 x 44 + 2 x 8 = 148px — follows from it, and so does the
     arithmetic that keeps the board from shrinking to make room.

     The floor is the wrong size on its own, because the pad is a fixed number of
     pixels and the screen is not: 44px is a seventh of a 320px phone, and in
     desktop mode the same 44px is drawn at 73% of itself. So the target grows with
     the viewport, from the floor to 56px, which it reaches at 960px — still well
     under the shell's own cap.

     That is not quite enough in the one case the growth was added for, and the
     rule below it closes the gap. */
  --touch-target: clamp(44px, 2.5vw + 32px, 56px);

  /* The pad's footprint: three keys and the two gaps between them. Named because
     the board's maximum width in landscape has to leave room for it, and a
     formula that copies 148px is a formula that can drift from the pad. */
  --pad-footprint: calc(3 * var(--touch-target) + 2 * var(--space-2));

  /* The shell's own gutter, named so the same landscape cap can subtract exactly
     what the shell took instead of a second copy of this clamp. */
  --shell-pad: clamp(var(--space-3), 2.5vw, var(--space-5));

  /* index.html opts into viewport-fit=cover, which is permission to draw under
     the notch and the home indicator. These are what make that permission safe:
     zero on every device without insets, so nothing about the layout moves
     there, and on a phone the board and the pad clear the hardware instead of
     sitting beneath it. Without them the pad is exactly where a thumb rests and
     exactly where a home indicator is drawn. */
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);

  --tracking-wide: 0.14em;

  --font-mono: ui-monospace, SFMono-Regular, "Cascadia Mono", "Segoe UI Mono", Menlo,
    Consolas, monospace;
}

/* The one viewport where the clamp above does not reach the floor it is built on.

   Desktop mode on a phone does not change the device. It changes the layout
   viewport: `width=device-width` is ignored, the page is laid out at about 980 CSS
   px, and that layout is then drawn at roughly 73% to fit the screen. So the 56px
   the clamp arrives at is 41px on screen — three under the 44px floor, at exactly
   the size where the pad also looks smallest against the screen it is on.

   60px is that number exactly: 60 x 0.73 = 44. It costs the board nothing, and the
   reasons differ by orientation rather than by device. Portrait: the board is square
   and, at this width, limited by its column rather than by height, so the 12px the
   pad grows into is space the board was never going to have — the row keeps its
   full slack. Landscape: the pad sits *beside* the board rather than below it, so it
   spends width, and width is the thing a viewport this wide has spare.

   This also catches real tablets, which have no such problem, and it cannot not:
   width on a coarse pointer is the only signal there is for desktop mode, and CSS
   has no way to ask how wide the screen is. So the rule is written to be safe where
   it is not needed rather than narrow enough to miss. On a tablet it spends 4px of
   pad and nothing else — 1024x1366 and 1366x768 both keep their cell size.

   The threshold sits below 980 on purpose, so that a browser whose desktop mode lays
   out somewhat narrower than Chrome's is still caught. */
@media (pointer: coarse) and (min-width: 900px) {
  :root {
    --touch-target: 60px;
  }
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

/* Layout ------------------------------------------------------------------ */

.shell {
  display: grid;
  grid-template-rows: auto 1fr;
  gap: var(--space-4);
  /* dvh, not vh: mobile browser chrome must not push the board off-screen. */
  height: 100dvh;
  max-width: 46rem;
  margin-inline: auto;
  padding:
    calc(var(--shell-pad) + var(--safe-top))
    calc(var(--shell-pad) + var(--safe-right))
    calc(var(--shell-pad) + var(--safe-bottom))
    calc(var(--shell-pad) + var(--safe-left));
}

.hud {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  /* Matched to the board rather than to the shell, so the rule ends exactly
     where the board does. The cell size is floored to a whole pixel, which
     leaves up to 23px of slack shared either side of the canvas; without this
     the rule overhangs the board by half of it. --board-px is published by the
     renderer, which is the only thing that knows how big the board came out. */
  width: var(--board-px, 100%);
  max-width: 100%;
  margin-inline: auto;
  /* The silkscreen label area of the board: a rule that separates the chrome
     from the field without competing with it. */
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--frame);
}

.title {
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-dim);
}

.title__mark {
  color: var(--snake-body);
}

.stat {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0;
}

.stat__label {
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-dim);
}

.stat__value {
  /* Tabular figures so the score cannot jitter as it counts up. */
  font-variant-numeric: tabular-nums;
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--ink);
}

/* Board ------------------------------------------------------------------- */

.board {
  position: relative;
  display: grid;
  place-items: center;
  /* Lets the 1fr row shrink below the canvas's intrinsic size. */
  min-height: 0;
  /* A viewport too small for the minimum cell size clips rather than scrolling
     the page, which the acceptance criteria forbid at any width. */
  overflow: hidden;
}

.board__canvas {
  display: block;
  /* No border-radius, deliberately — do not add one back. A radius here clips at
     composite time only, so the canvas bitmap keeps its square frame while the
     stroke on screen stops ~6px short of each corner, leaving the corner to the
     arena-versus-page contrast at 1.05:1. Square also matches the fiducials,
     which are corner registration marks on a substrate with no rounded edges.
     The rounded things in this design are the cards placed on the board. */
}

/* Overlays ---------------------------------------------------------------- */

.overlay {
  position: absolute;
  /* Sized to the board, not to the container it sits in. The container is
     taller than the board wherever the board is width-constrained, and the
     scrim would then veil a band of page around the board — invisible only for
     as long as the page background and the scrim happen to be the same colour.
     Out of flow, so this cannot feed back into the size the renderer measures. */
  inset: auto;
  place-self: center;
  width: var(--board-px, 100%);
  height: var(--board-px, 100%);
  display: grid;
  place-content: center;
  padding: var(--space-4);
  /* No radius, and light rather than heavy. It has to match the board it veils:
     a rounded scrim over a square board would leave the board's four corners
     undimmed, showing as bright notches exactly where the fiducials are. And on
     game over the player has just died and needs to see where the run ended,
     which the 94% veil this replaces hid. The opaque fallback comes first, so an
     unsupported color-mix degrades to a plain panel rather than to no scrim. */
  background: var(--bg);
  background: color-mix(in srgb, var(--bg) 55%, transparent);
  /* Informational only — nothing inside is interactive, so clicks reach the
     board rather than being swallowed by an invisible layer. */
  pointer-events: none;
  animation: overlay-in var(--duration-fast) var(--ease-out) both;
}

/* Opaque, and that is what makes a light scrim safe: no translucent layer ever
   sits behind the type, so the board cannot tint it. Veiling the whole board at
   94% was working around that symptom rather than fixing it. */
.overlay__panel {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-5);
  text-align: center;
  background: var(--arena);
  border: 1px solid var(--frame);
  border-radius: var(--radius-md);
}

.overlay__title {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink);
}

.overlay__detail {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--ink-dim);
}

/* One instruction per input device. Which one shows is decided in the touch
   section below, alongside every other `pointer: coarse` rule. */
.overlay__detail--touch {
  display: none;
}

/* A supplementary line under the one that matters: a step down in size and
   dimmer than the detail above it, so the ready screen still reads as "press
   Enter" first and "here is what you are chasing" second, and the game-over
   screen as the score first and the attempt count second.

   One class for both, because they are the same thing — a quiet figure line
   that supports the line above it. The record is absent until a game has been
   finished, so nothing here has to describe an empty state. */
.overlay__meta {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--ink-dim);
}

/* Figures carry the HUD's own class wherever one is shown, so a figure is one
   thing across the whole interface rather than being restyled per screen: the
   weight, the colour, and the tabular figures that stop a record jittering as it
   counts from 9 to 10 are all the HUD's, and that is the whole of the emphasis.

   Inherited rather than restated, so a figure is the size of the line it sits on
   and neither line has to name a size. Monospace is what makes weight free: a
   heavier glyph is the same width as a light one, so the emphasis costs nothing
   at all. */
.overlay__meta .stat__value,
.overlay__detail .stat__value {
  font-size: inherit;
}

/* The music control, and the one interactive thing in any overlay.

   `.overlay` is `pointer-events: none` so that clicks reach the board, and the panel
   inside it is documented as informational. This is the exception, and it re-enables
   the pointer for itself alone rather than for the panel around it: the panel is the
   wider box, and a panel that swallowed clicks would take a band of the board with
   it — the board being the one thing the overlays deliberately do not cover.

   It borrows `.control` from the pad's neighbours, because it is the same kind of
   thing — a labelled key that reports its own state — and because one class shared
   across two homes is already this file's habit (see `.stat__value`). Everything
   below is only what the new home changes.

   Height is `--touch-target` from `.control` and is not overridden: it is the
   accessibility floor wherever a control appears, not a property of the row.

   Both changes here are custom properties rather than the properties themselves,
   because this rule sits above `.control` in the file and at one class each the
   later rule would win every shared property — which is how the `padding-inline`
   that used to be written here came to do nothing at all. Setting the variables
   instead is order-independent and makes this rule only what its comment claims:
   what the new home changes.

   The width floor is the one thing here that is genuinely new, rather than a
   different value of something `.control` already sets. This is the only
   control whose two labels are different lengths — "Music" against "Music off",
   where the row's Pause never relabels and its "Sound"/"Muted" happen to be a
   matched pair. Left to size itself to the label, the button grows 30px the
   moment it is pressed: a layout shift caused by the acknowledgement of the
   press, which is the worst place for one. So the floor is the longer label,
   nine characters, plus this control's own padding and border.

   `ch` is exact rather than approximate because the whole interface is set in the
   monospace stack, where every glyph has the same advance; and the tracking is
   counted per character because `letter-spacing` applies after the last one too.
   The padding is read from the variable above it rather than repeated, so the two
   cannot drift apart. The row keeps the plain `--touch-target` floor, where the
   296px budget leaves no room to reserve anything. */
.overlay__control {
  --control-padding-inline: var(--space-3);
  --control-min-width: calc(9ch + 9 * var(--tracking-wide) + 2 * var(--control-padding-inline) + 2px);
  justify-self: center;
  pointer-events: auto;
}

/* The mark that says the run you just finished beat your record.

   A new best is the one thing the game-over screen is allowed to celebrate, and
   celebrating it with the same separator that introduces a record you failed to
   beat says the opposite. So it gets a star, in the accent reserved for "live" —
   the two readings are told apart by shape, not by a word and not by colour.

   The glyph is generated content on an element that carries no text of its own,
   and the element is `aria-hidden` besides: decoration is not something to read
   aloud, and the sentence a screen reader is handed stays "Score 42 New best".

   `inline-block` because a transform does nothing to an inline box, and the
   animation is the overlay's own duration and easing — one arrival, not two. */
.result__mark {
  display: inline-block;
  margin-inline: 0 0.4em;
  color: var(--snake-body);
  animation: mark-in var(--duration-fast) var(--ease-out) both;
}

.result__mark::before {
  content: "✦";
}

@keyframes mark-in {
  from {
    opacity: 0;
    transform: scale(0.4);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

kbd {
  font: inherit;
  padding: 0.1em 0.4em;
  /* Recessed against the opaque panel above it, so the chip reads as a key
     rather than as a gap in the text. */
  background: var(--bg);
  border: 1px solid var(--frame);
  border-radius: 4px;
  color: var(--ink);
}

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

/* The hidden attribute must win over the display rules above. */
[hidden] {
  display: none !important;
}

/* Touch controls ---------------------------------------------------------- */

/* Hidden by default, then shown by capability. `pointer: coarse` is a question
   CSS can already answer, so nothing here is detected at runtime and nothing is
   stored as a preference. */
.controls {
  display: none;
}

.pad {
  display: grid;
  grid-template-columns: repeat(3, var(--touch-target));
  grid-template-rows: repeat(3, var(--touch-target));
  gap: var(--space-2);
  /* Stated rather than left to the tracks, so the token the board's landscape
     cap subtracts is the same number the pad actually occupies. */
  width: var(--pad-footprint);
  height: var(--pad-footprint);
}

.pad__key {
  position: relative;
  display: grid;
  place-items: center;
  width: var(--touch-target);
  height: var(--touch-target);
  padding: 0;
  background: var(--arena);
  border: 1px solid var(--frame);
  border-radius: var(--radius-md);
  color: var(--ink-dim);
  cursor: pointer;
  /* The gesture belongs to the game, not to the page: without this a press can
     be reinterpreted as a scroll or a double-tap zoom partway through. Applied to
     the controls rather than to the page, so everything the game is not still
     scrolls normally. */
  touch-action: none;
  /* A long press otherwise selects the label or raises the callout, and the tap
     highlight would paint over the :active state below. */
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition:
    color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
}

/* The visual target is the 44px minimum with an 8px gap to its neighbour, so a
   thumb landing between two keys would otherwise do nothing — and during fast
   play that is a lost input, not a miss. This grows only the hit rect, by half
   the gap on each side, so neighbouring rects meet exactly: no dead zone, no
   overlap, and nothing drawn differently. */
.pad__key::before {
  content: "";
  position: absolute;
  inset: calc(var(--space-2) * -0.5);
}

/* pointerdown is the intent, so the pressed state is the only feedback there is
   and it has to be immediate. */
.pad__key:active {
  color: var(--snake-body);
  border-color: var(--snake-body);
  background: var(--bg);
}

/* One chevron, rotated. Two borders rather than an SVG: it is the same 2px
   stroke as the frame and the fiducials, so it needs no icon set to stay in
   register with them. */
.pad__chevron {
  width: 10px;
  height: 10px;
  border-top: 2px solid currentColor;
  border-left: 2px solid currentColor;
  transform: rotate(var(--chevron-rotation));
}

.pad__key--up {
  --chevron-rotation: 45deg;
  grid-area: 1 / 2;
}

.pad__key--left {
  --chevron-rotation: -45deg;
  grid-area: 2 / 1;
}

.pad__key--right {
  --chevron-rotation: 135deg;
  grid-area: 2 / 3;
}

.pad__key--down {
  --chevron-rotation: 225deg;
  grid-area: 3 / 2;
}

/* The pad's neighbours: Pause and Sound. One class, because they are one kind of
   thing — a labelled key beside the pad — and three rules is where a copied block
   stops being cheaper than a shared name.

   Labels rather than glyphs: "Pause" is what the paused overlay tells the player
   to tap, and "Sound" is what it is called everywhere else. A word means the same
   thing to a screen reader, a sighted player, and the copy, with no icon font and
   no symbol that might not render. */
.control {
  display: grid;
  place-items: center;
  /* The two properties a second home may need to change. Read here with the row's
     values as the fallback, so a home can set them without having to out-order this
     rule — see `.overlay__control`, which is declared above it and, at one class
     each, would otherwise lose every property the two have in common. */
  min-width: var(--control-min-width, var(--touch-target));
  height: var(--touch-target);
  /* The height is what makes the target 44px, and it is not negotiable; the width
     only has to clear --touch-target, which this padding does by about 15px. The
     padding was picked under a row budget that has since been lifted — portrait no
     longer sets two controls on the pad's row — and it is kept, because the row
     now has slack it has no use for and a wider button buys nothing the 44px
     height has not already bought. */
  padding-inline: var(--control-padding-inline, var(--space-2));
  background: var(--arena);
  border: 1px solid var(--frame);
  border-radius: var(--radius-md);
  color: var(--ink-dim);
  font: inherit;
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  cursor: pointer;
  touch-action: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition:
    opacity var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    border-color var(--duration-fast) var(--ease-out),
    background-color var(--duration-fast) var(--ease-out);
}

.control:active {
  color: var(--snake-body);
  border-color: var(--snake-body);
  background: var(--bg);
}

/* Pause is inert on the ready and over screens: there is nothing to pause before a
   run begins and nothing to resume after it has ended. Disabled rather than hidden,
   because hiding it would reflow the row it shares with the pad on every start and
   every death; dimmed rather than left live, because a control that lights up under
   a thumb and then does nothing is worse than one that says so beforehand.

   This is the one place the interface takes a label under 4.5:1 — 3.2:1 here, where
   the resting control is 7.1:1. WCAG 1.4.3 exempts an inactive component, and it is
   inactive: the platform does not deliver pointer events to a disabled control, so
   it cannot be pressed, hovered, or focused, and the pad beside it and the sound
   control are both at full strength and both still work. */
.control:disabled {
  opacity: 0.6;
  cursor: default;
}

/* The only focusable things in the game — everything else is reached through the
   window's keydown listener — so this is the only place a focus ring is ever
   drawn, and the browser's stock outline is the only part of this interface that
   was not designed. Same 2px stroke as the chevrons and the board's fiducials, in
   the one accent reserved for "live". */
.control:focus-visible {
  outline: 2px solid var(--snake-body);
  outline-offset: 2px;
}

/* Hover, but only where hovering is a thing that exists. On a touch screen the
   browser applies :hover to whatever was last tapped and leaves it there, so an
   unguarded rule would strand a key in its hover state — looking permanently
   lit — until the player happened to tap something else.

   `hover` alone, which is wider than the `pointer: coarse and hover` this said
   while the pad was the only thing it could reach: the pad and its neighbours are
   hidden on a fine pointer, so the two guards named the same rule. The music
   control lives on an overlay, where it is visible on every device, and it is the
   one control a mouse can actually point at. */
@media (hover: hover) {
  .pad__key:hover,
  .control:hover {
    color: var(--ink);
    border-color: var(--edge-mark);
  }
}

/* Where the pointer is a finger. .shell gains its third row only here: a grid
   row with nothing in it still contributes its gap, so declaring it in the base
   layout would take 16px off the board row on every desktop. */
@media (pointer: coarse) {
  .shell {
    grid-template-rows: auto 1fr auto;
  }

  .controls {
    display: flex;
    align-items: center;
    justify-content: center;
    /* The separation between the pad and the controls beside it, and the one number
       the whole arrangement turns on. The scale's top step was 24px until this
       needed more: the previous arrangement spent its slack on a margin, and this
       one reaches a step further than that margin ever did. Affordable because of
       the arithmetic below — the row that has to hold it is 148 + 32 + 56. */
    gap: var(--space-6);
  }

  /* Sound and Pause, stacked in a column beside the pad rather than on a row with
     it. Three things fall out of that, and all three are the point.

     The pad's nearest control is now diagonal rather than adjacent. A thumb aiming
     at the pad's right key overshoots sideways, and sideways is where the 32px gap
     is, not where a button is — which is the complaint this answers, and it is
     answered by geometry rather than by making the buttons smaller or the labels
     longer.

     The pair is 44 + 16 + 44 = 104px against the pad's 148, so the row is still
     exactly as tall as the pad and the board row above it does not move by a pixel
     at any viewport. Separating the controls from the pad must not cost the board,
     and this is what makes that possible.

     And it is narrower than what it replaced — 236px of row against 292 — so the
     row ends with slack instead of wanting it, which is what the wider gap above is
     spending. */

  /* The two are --space-4 apart rather than the pad's own --space-2. A tight gap is
     right inside the pad, where every key is the same kind of action and a thumb
     landing between two of them still means "a direction". These two are not the
     same kind of thing as each other — one is a setting and one interrupts the run
     — so a slip between them should be harder, and the space to make it harder is
     free because the stack is 104px against the pad's 148. Two gaps, 16 and 32,
     also say which of the three is the odd one out. */
  .controls__neighbours {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
  }

  .overlay__detail--keys {
    display: none;
  }

  .overlay__detail--touch {
    display: block;
  }
}

/* Landscape puts the pad beside the board rather than below it. Not a taste
   call: at 844x390 the board row is 292px for a 288px board, so taking the pad's
   148px out of it clamps the cell size to the minimum and the board becomes a
   postage stamp. Beside, the board keeps its full height and spends width it has
   to spare. Applied to all landscape rather than only short landscape, because
   height is the binding constraint in landscape generally — beside is never the
   worse answer. */
@media (pointer: coarse) and (orientation: landscape) {
  .shell {
    grid-template-rows: auto 1fr;
    grid-template-columns: auto auto;
    justify-content: center;
  }

  /* Square, and sized from the row rather than from its own contents. That is
     what lets the column be `auto` at all: a board whose width came from its
     canvas would be circular, because the renderer sizes the canvas from the
     board it is inside. Left to its contents the column would pin itself to the
     canvas's intrinsic width and could never grow to fill a taller viewport.

     Deriving the width from the height instead puts the board and the pad the
     shell's gap apart, rather than leaving the board centred in a column with
     slack on both sides — which at 844x390 left 137px of nothing between them,
     wider than the pad itself. */
  .board {
    grid-row: 2;
    grid-column: 1;
    height: 100%;
    aspect-ratio: 1;
    /* Bounded by width as well as by height. A row taller than the space left
       beside the pad would make a board wider than its column, and a centred
       track that overflows overflows both ways — pushing half the board off the
       left edge, where no amount of scrolling reaches it. Only bites on a
       landscape viewport barely wider than it is tall; on every real one the
       height is the binding constraint, as it should be. */
    max-height: calc(
      100vw - 2 * var(--shell-pad) - var(--safe-left) - var(--safe-right)
        - var(--pad-footprint) - var(--space-4)
    );
  }

  /* The controls stack beside the board instead of under it, two to a row rather
     than three to a column. Measured: at 568x320 the board row is 236px, and the
     pad plus one control already spent 208 of it — a second control below would
     have made it 268 and pushed the stack out of the shell by 32px. Side by side
     they cost one gap instead, and the column stays exactly as wide as the pad,
     which is the number the board's own cap above subtracts. */
  .controls {
    grid-row: 2;
    grid-column: 2;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-2);
    width: var(--pad-footprint);
  }

  .pad {
    grid-column: 1 / -1;
  }

  /* The column here is the pad's own width, so there is no space beside the pad
     for a control to sit in and the stack opens back out into the row the pad
     already spanned. That is the arrangement this orientation has always had, and
     the row height above was measured against it — which is why nothing else about
     landscape changes.

     The --space-4 between the two carries over from portrait, because the reason
     for it does. They sit side by side here rather than stacked, but either way one
     of them is a setting and the other interrupts the run. */
  .controls__neighbours {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-4);
  }

  /* The header spans both columns rather than matching the board. Matched to the
     board it is as narrow as the board, and beside the pad the board is narrower
     than it was — at 568x320 that is 216px for a wordmark and a score that need
     231, so the title wrapped mid-name. A header over a two-column body reads as
     a header; a header the width of the left column reads as a mistake. */
  .hud {
    grid-column: 1 / -1;
    width: 100%;
  }
}

/* The standing guard for the motion above and for anything added later. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
