/*
 * Gobman
 *
 * The board is a canvas, so this sheet is only the chrome around it: the HUD
 * strip, the overlay panels, the touch pad. Colours are variables because the
 * game itself re-colours the maze every board and the page should stay out of
 * its way - the chrome is one dark cyan accent, the board is whatever the level
 * palette says.
 */

:root {
    --bg: #04080f;
    --panel: rgba(9, 20, 34, 0.94);
    --panel-line: #1b3554;
    --panel-soft: rgba(15, 32, 52, 0.66);
    --ink: #e7f2ff;
    --muted: #93b2cd;
    --accent: #38bdf8;
    --accent-2: #22d3ee;
    /* The board's own three tones, set on #app by syncHud once a level is known.
       The banner over the maze and the glow inside the case are what read them:
       the HUD numbers stay in `--ink` on every board, because a panel that turned
       magenta on board four read as part of the board rather than as the game's
       furniture. The defaults are the first board's colour, so those two are
       never colourless while the page boots. */
    --board-core: #d9a6f2;
    --board-mid: #b04ac0;
    --board-halo: #a000a0;
    --gold: #fde68a;
    --warn: #fb7185;
    /* The bomb's grey. It is the same value as `PICKUP_COLOURS.bomb` in
       render.js on purpose: the HUD glyph and the pickup on the board are the
       same object, so they are the same colour, and the bomb stopped being red
       when the red ghost won it. */
    --bomb: #94a3b8;
    /* The mark on a run the server could sign. Green because a check is green
       everywhere; it is the one colour on the page that means "trust this". */
    --ok: #34d399;
    --radius: 16px;
    --radius-sm: 10px;
    --shadow: 0 18px 44px rgba(2, 8, 20, 0.6);
    color-scheme: dark;
}

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

body {
    margin: 0;
    min-height: 100dvh;
    background:
        radial-gradient(900px 520px at 50% -12%, #0e2b45 0%, transparent 62%),
        radial-gradient(760px 420px at 108% 104%, #0a2436 0%, transparent 60%),
        var(--bg);
    color: var(--ink);
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    -webkit-tap-highlight-color: transparent;
}

/* Three rows: the readouts, the board, the controls. The board takes whatever
   is left, which is what makes the page work on a phone held sideways. The
   height is definite (`100dvh`, with a floor for absurdly short viewports), so
   the middle row can be `1fr` and the canvas inside it can be 100% tall.
   A wide screen gets a fourth area instead - see the media query at the bottom -
   because a maze that is 17 tiles tall runs out of height long before it runs
   out of width.
   The width cap is the one number that decides how big the board gets: the maze
   is 23x17, so the page needs about 1730px of width before a 1080p window runs
   out of height first, and about 2220px for a 1440p one. Capped lower, a tall
   window letterboxes the board inside a stage that is taller than the board can
   ever be - dead space above and below a board that had room to grow - so the cap
   sits above both and lets the height be the thing that runs out.
   The third row is `.side` - the header and the footer, wrapped in index.html -
   which is a box only on a phone. See the phone queries at the bottom. */
.app {
    --gap: clamp(8px, 1.4vw, 14px);
    display: grid;
    grid-template-areas:
        "hud"
        "stage"
        "pad";
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto;
    gap: var(--gap);
    height: 100dvh;
    min-height: 420px;
    max-width: 2400px;
    margin: 0 auto;
    padding: clamp(8px, 1.4vw, 18px);
}

/* The readouts and the controls, wrapped so a phone can move them as one thing.
   `display: contents` is what makes the wrapper free: it has no box, so the
   header and the footer are still placed by `grid-area` in the app grid, and
   every layout above this line is the layout it was. The phone queries give it a
   box back - a column under the board, or a strip beside it - and `order` on the
   panels then decides what a thumb reaches first. */
.side {
    display: contents;
}

/* ---------- a word for a reader, not for the page ---------- */

/* The three counters in the header are a glyph and a number, and the name of each
   one has to be somewhere for a screen reader. Not `display: none`, which takes it
   out of the reading too, and not zero-size, which does the same in some readers:
   a 1px box, clipped, and out of the way of the layout. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

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

/* Three rows, in the order a run reads: the wordmark with the switches beside it,
   the numbers that are the run itself (score, the board's switch, best, and what
   the pen is doing), and the three counters in the board's own art. The first row
   is the only two-column one: the mark is 91px of a 246px panel, so the empty half
   beside it is where the switches went, and everything under it is one column
   across. A strip too narrow to hold both puts them back under the mark - see the
   landscape phone query. */
.hud {
    grid-area: hud;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
        "brand tools"
        "stats stats"
        "kit   kit";
    gap: 8px;
    align-items: center;
    padding: 10px clamp(12px, 2vw, 18px);
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
    background: linear-gradient(180deg, rgba(17, 36, 60, 0.96), rgba(6, 14, 25, 0.96));
    box-shadow: var(--shadow), inset 0 1px 0 rgba(120, 190, 255, 0.09);
}

.hud__brand {
    grid-area: brand;
    min-width: 0;
}

.hud__title {
    margin: 0;
    font-size: clamp(1.1rem, 2.4vw, 1.5rem);
    font-weight: 800;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    background: linear-gradient(120deg, var(--accent-2), var(--accent) 55%, #a5f3fc);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.hud__board {
    margin: 2px 0 0;
    font-size: 0.78rem;
    color: var(--muted);
    letter-spacing: 0.04em;
}

/* The header brand is the JAWNLogic Apps wordmark for this game, not the tab
   icon. logo-pacard.png is the wide lockup (1348x443) - the asset the branding
   skill puts in an app's own logo slot - and it is the only link off the page.
   Height is set and width left auto so the wide wordmark is never squashed:
   30px on narrow screens, 38px once the HUD has its own column. */
.hud__mark {
    display: block;
    height: 30px;
    width: auto;
}

/* The mark links back to the app shelf: the one thing on this page that leaves
   the game, so it is the only link in the header. */
.hud__link {
    display: inline-flex;
    border-radius: var(--radius-sm);
    text-decoration: none;
}

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

.hud__value {
    color: var(--ink);
    font-weight: 700;
}

.hud__dot {
    margin: 0 4px;
    color: var(--panel-line);
}

/* Score, the board's switch, best: three cells on one line, with the switch in the
   flexible middle one so it sits on the row's centre whatever the two numbers are
   worth (they are the same six digits in the same tabular face, so the middle of
   the row is the middle of the gap between them either way). Best is right-aligned,
   label and number both, so the two numbers mirror each other across the switch
   instead of both crowding the left edge with an empty half to their right. The
   cubes state keeps a row of its own under them, which is where it already was on a
   wide screen. */
.hud__stats {
    grid-area: stats;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
        "score switch best"
        "mode  mode   mode";
    align-items: center;
    gap: 4px clamp(10px, 2.5vw, 24px);
    margin: 0;
    padding: 0;
}

/* The three numbers are a definition list and the switch is not one of them, so the
   list is the box that goes away: `display: contents` puts its three cells straight
   into the grid above, with nothing of its own between the score and the best. The
   markup stays a list of three numbers, which is what it is. */
.hud__numbers {
    display: contents;
}

.stat--score {
    grid-area: score;
    justify-self: start;
}

.stat--best {
    grid-area: best;
    justify-self: end;
    text-align: right;
}

.stat--mode {
    grid-area: mode;
    justify-self: start;
}

/* The switch keeps the middle column to itself, and the column keeps its width when
   the button is hidden - it is the flexible one - so a player who is not offered the
   switch still gets the score and the best at the two edges, mirrored. */
.hud__switch {
    grid-area: switch;
    display: flex;
    justify-content: center;
    min-width: 0;
}

.stat {
    display: grid;
    gap: 1px;
    min-width: 3.4rem;
}

.stat dt {
    font-size: 0.62rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--muted);
}

.stat dd {
    margin: 0;
    font-size: clamp(0.95rem, 1.9vw, 1.15rem);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.06em;
    color: var(--ink);
}

.stat--score dd {
    color: var(--accent-2);
}

.stat--mode dd {
    font-size: 0.85rem;
    letter-spacing: 0.18em;
    color: var(--muted);
}

.stat--mode dd[data-mode="frightened"] {
    color: #a5b4fc;
}

.stat--mode dd[data-mode="frozen"] {
    color: #7dd3fc;
}

.stat--mode dd[data-mode="loose"] {
    color: var(--gold);
}

/* ---------- what is left, and the switches ---------- */

/* Three counters on one line, in the board's own art: a torpedo, a shield, a
   nanoprobe. They are one row at one height because they answer one question -
   how much of each is there - and a number beside the glyph answers it faster
   than a word beside the number did. `flex: 1 1 0` gives each an equal share of
   the width, so the three sit at the same pitch whatever the numbers read. */
.hud__kit {
    grid-area: kit;
    display: flex;
    align-items: stretch;
    gap: 6px;
}

.kit {
    flex: 1 1 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    min-width: 0;
    padding: 3px 9px;
    border: 1px solid var(--panel-line);
    border-radius: 999px;
    background: var(--panel-soft);
    color: var(--ink);
    font: inherit;
    font-size: 0.78rem;
    letter-spacing: 0.04em;
}

/* The glyph is a canvas whose backing store `main.js` sizes at the device pixel
   ratio, so giving it a size here is what keeps it sharp rather than blurry. */
.kit__icon {
    flex: none;
    width: 1.15rem;
    height: 1.15rem;
}

.kit__count {
    font-size: 1rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.04em;
    color: var(--ink);
}

/* Only the torpedo is a button, because it is the one counter a press spends. It
   wears the same box as the two readouts - that is what makes the row one row -
   and carries what a button owes: a pointer, a hover, a press. */
button.kit {
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease, transform 120ms ease;
}

button.kit:hover:not(:disabled) {
    border-color: var(--accent);
    background: rgba(56, 189, 248, 0.12);
}

button.kit:active:not(:disabled) {
    transform: translateY(1px);
}

button.kit:disabled {
    opacity: 0.42;
    cursor: default;
}

/* Nothing in the rack: dim, but still pressable so the press can answer back. */
.kit[data-empty="true"] {
    opacity: 0.55;
}

.kit[data-empty="true"] .kit__count {
    color: var(--muted);
}

.kit[data-shake="true"] {
    border-color: var(--warn);
    animation: tool-shake 340ms ease;
}

@keyframes tool-shake {
    0%, 100% { transform: translateX(0); }
    20% { transform: translateX(-3px); }
    45% { transform: translateX(3px); }
    70% { transform: translateX(-2px); }
    88% { transform: translateX(1px); }
}

@media (prefers-reduced-motion: reduce) {
    .kit[data-shake="true"] {
        animation: none;
    }
}

/* ---------- the switches ---------- */

/* Beside the wordmark, against the panel's right edge: pause, sound and the door to
   the studio are three states a player flips mid-run, and a row of small pictures is
   a row a thumb can hit without reading. Each button is the same square, whatever is
   drawn in it. The row was the panel's floor, under the counters and centred, and
   the switches were four; the skin switch moved up into the middle of the numbers
   and the rest came up here, into the room the brand line had empty beside the mark.
   The row is right-aligned rather than centred because it is a corner of the panel
   now, and the wordmark it shares a line with is against the other corner. */
.tools {
    grid-area: tools;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    justify-content: flex-end;
}

.tool {
    display: inline-grid;
    place-items: center;
    /* Never squeezed: a switch is a 2rem square or it is not a switch. In the row
       beside the wordmark that means a narrow strip wraps them instead of turning
       three circles into three ovals, and in the numbers row it keeps the one in
       the middle round inside a 28px column. */
    flex: none;
    width: 2rem;
    height: 2rem;
    padding: 0;
    border: 1px solid var(--panel-line);
    border-radius: 999px;
    background: var(--panel-soft);
    color: var(--ink);
    font: inherit;
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease, transform 120ms ease;
}

/* The Studio switch is a link, not a button: it goes to another page. It keeps
   the tool look, without the underline an anchor brings with it. */
a.tool {
    text-decoration: none;
}

/* .tool sets display, which would otherwise win over the hidden attribute, so
   the Studio button has to be told to stay away for players. */
.tool[hidden] {
    display: none;
}

.tool:hover:not(:disabled) {
    border-color: var(--accent);
    background: rgba(56, 189, 248, 0.12);
}

.tool:active:not(:disabled) {
    transform: translateY(1px);
}

.tool:disabled {
    opacity: 0.42;
    cursor: default;
}

/* The drawing is a 16px path filled in the button's own colour, so a hover, a
   press and a lit switch all colour it by colouring the button. */
.tool__icon {
    display: block;
    width: 1rem;
    height: 1rem;
    fill: currentColor;
}

/* Each switch carries both of its drawings, and its own state picks one - the
   same `aria-pressed` main.js already keeps current for a screen reader, so the
   picture cannot say something the state does not. A side that is not chosen is
   `display: none` rather than transparent, so nothing invisible is left for a
   pointer or a reader to land on. */

/* The bars while a run is running, the triangle while it is paused: the button
   draws what pressing it does, as the word it replaced used to ("Pause" then
   "Resume"). */
.tool--pause .tool__icon--play,
.tool--pause[aria-pressed="true"] .tool__icon--pause {
    display: none;
}

.tool--pause[aria-pressed="true"] .tool__icon--play {
    display: block;
}

/* A speaker with its waves while there is sound, and the same speaker crossed out
   once there is not - the state, not the action, because a crossed-out speaker is
   the one picture everybody already reads as "silent". */
.tool--sound .tool__icon--off,
.tool--sound[aria-pressed="true"] .tool__icon--on {
    display: none;
}

.tool--sound[aria-pressed="true"] .tool__icon--off {
    display: block;
}

/* The saucer while the board is the ship's, the wedge-mouthed figure while it is
   the arcade's. The glyph is the board the player is looking at, which is what the
   word on this button used to be too. */
.tool--skin .tool__icon--classic,
.tool--skin[aria-pressed="true"] .tool__icon--space {
    display: none;
}

.tool--skin[aria-pressed="true"] .tool__icon--classic {
    display: block;
}

/* A switch that is on says so in the house blue, as it did when these were words:
   paused, muted and the skin are all states a player can be in without noticing. */
.tool--ghost[aria-pressed="true"] {
    border-color: var(--accent);
    color: var(--accent);
}

/* ---------- stage ---------- */

.stage {
    grid-area: stage;
    position: relative;
    display: grid;
    min-height: 200px;
    border-radius: var(--radius);
}

/* Absolute, so the canvas does not size its own row: with `width:100%` and an
   auto height the browser would fall back to the intrinsic ratio and the stage
   would grow past the viewport. The renderer reads clientWidth and
   clientHeight every frame and letterboxes the 23x17 board inside whatever box
   this ends up being, so the one place that knows the grid is the renderer - and
   the `aspect-ratio` a phone gives the stage is the same rectangle. */
.stage__board {
    position: absolute;
    inset: 0;
    display: block;
    width: 100%;
    height: 100%;
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
    background: #04080f;
    box-shadow: var(--shadow), inset 0 0 70px rgba(56, 189, 248, 0.05);
    touch-action: none;
    /* The board answers a finger, it is not something to read: a long press must
       not offer to save it as a picture or paint a selection across it. Only the
       canvas - the overlay above it holds a name field, where selection is the
       point. */
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
}

.stage__banner {
    position: absolute;
    top: 42%;
    left: 50%;
    transform: translate(-50%, -50%);
    margin: 0;
    padding: 6px 18px;
    border-radius: 999px;
    background: rgba(4, 10, 20, 0.78);
    color: var(--gold);
    font-size: clamp(1rem, 3.4vw, 1.6rem);
    font-weight: 800;
    letter-spacing: 0.3em;
    text-transform: uppercase;
    text-shadow: 0 0 18px rgba(253, 230, 138, 0.55);
    pointer-events: none;
}

.stage__banner[hidden] {
    display: none;
}

/* ---------- overlay ---------- */

/* Semi-transparent on purpose: on the title screen and the high-score board the
   maze is the picture, so the panel floats over it instead of hiding it. */
.overlay {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    padding: clamp(8px, 2.4vw, 26px);
    border-radius: var(--radius);
    background: radial-gradient(64% 64% at 50% 40%, rgba(4, 10, 20, 0.42), rgba(3, 8, 16, 0.8));
    backdrop-filter: blur(2px);
    z-index: 3;
}

.overlay[hidden] {
    display: none;
}

.screen {
    display: none;
    width: min(440px, 100%);
    max-height: 100%;
    overflow: auto;
    gap: 10px;
    justify-items: center;
    padding: clamp(16px, 3vw, 26px);
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
    background: var(--panel);
    box-shadow: var(--shadow), inset 0 1px 0 rgba(120, 190, 255, 0.08);
    text-align: center;
}

.overlay[data-screen="title"] .screen--title,
.overlay[data-screen="pause"] .screen--pause,
.overlay[data-screen="online"] .screen--online,
.overlay[data-screen="over"] .screen--over {
    display: grid;
}

/* The lobby is the one panel with a form in it and a code to read out loud, so
   the two halves of it - sit down, or join - are set apart by a rule rather than
   by three buttons in a row that look like one choice. */
.screen--online .initials {
    margin-top: 2px;
}

.screen--online .over-note {
    margin: 0;
}

/* "GAME OVER" is the one title that has to fit one line of a fixed-width panel,
   so it carries its own size instead of the airy letter-spacing the title screen
   uses. */
.screen--over .screen__title {
    font-size: clamp(1.7rem, 5.4vw, 2.3rem);
    letter-spacing: 0.14em;
}

.screen__title {
    margin: 0;
    font-size: clamp(1.9rem, 6vw, 2.8rem);
    font-weight: 800;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    background: linear-gradient(120deg, var(--accent-2), var(--accent) 60%, #a5f3fc);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    text-shadow: 0 0 40px rgba(56, 189, 248, 0.35);
}

.screen__title--small {
    font-size: clamp(1.35rem, 4vw, 1.9rem);
}

.screen__text {
    margin: 0;
    font-size: 0.88rem;
    line-height: 1.55;
    color: var(--muted);
}

.screen__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    justify-content: center;
    margin-top: 2px;
}

.screen__hint {
    margin: 2px 0 0;
    font-size: 0.72rem;
    line-height: 1.9;
    color: var(--muted);
}

/* ---------- buttons, board, initials ---------- */

.button {
    padding: 10px 20px;
    border: 1px solid var(--panel-line);
    border-radius: 999px;
    background: var(--panel-soft);
    color: var(--ink);
    font: inherit;
    font-size: 0.85rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    cursor: pointer;
    transition: transform 120ms ease, box-shadow 160ms ease, border-color 160ms ease;
}

.button--primary {
    border-color: transparent;
    background: linear-gradient(120deg, var(--accent), var(--accent-2));
    color: #04121f;
    box-shadow: 0 10px 26px rgba(34, 211, 238, 0.28);
}

.button--primary:hover {
    box-shadow: 0 12px 32px rgba(34, 211, 238, 0.42);
    transform: translateY(-1px);
}

.button--ghost:hover {
    border-color: var(--accent);
    color: var(--accent);
}

.button:active {
    transform: translateY(1px);
}

.board {
    display: grid;
    gap: 2px;
    width: 100%;
    margin: 2px 0 0;
    padding: 0;
    list-style: none;
    font-variant-numeric: tabular-nums;
    text-align: left;
}

.board__row {
    display: grid;
    grid-template-columns: 1.3rem minmax(0, 1fr) auto 2.2rem;
    gap: 8px;
    align-items: baseline;
    padding: 4px 9px;
    border-radius: var(--radius-sm);
    font-size: 0.86rem;
}

.board__row:nth-child(odd) {
    background: rgba(56, 189, 248, 0.07);
}

.board__row--empty {
    display: block;
    background: none;
    color: var(--muted);
    font-size: 0.8rem;
    text-align: center;
}

.board__rank {
    font-size: 0.76rem;
    color: var(--muted);
}

.board__name {
    letter-spacing: 0.08em;
    font-weight: 700;
    color: var(--accent-2);
    /* A board row is one line whatever the name: twelve characters fit the
       column, and anything longer fades out rather than wrapping the row. */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The mark the server puts on a run it could sign. Inside the name column, so a
   checked row still lines up with the rest of the board. */
.board__check {
    margin-right: 4px;
    color: var(--ok);
}

/* The run that just landed, while the game-over screen is still up. */
.board__row[data-you='true'] {
    background: rgba(52, 211, 153, 0.14);
    box-shadow: inset 0 0 0 1px rgba(52, 211, 153, 0.35);
}

.board__score {
    font-weight: 700;
}

.board__level {
    font-size: 0.72rem;
    color: var(--muted);
    text-align: right;
}

.initials {
    display: grid;
    gap: 6px;
    justify-items: center;
    width: 100%;
}

.initials[hidden] {
    display: none;
}

/* The line above the form: what the run can expect, and the only line a run
   with nothing to save still gets - it is outside `#over-form`, so hiding the
   form cannot hide the reason as well. */
.over-note {
    margin: 0;
    font-size: 0.74rem;
    line-height: 1.6;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--gold);
}

/* A run that cannot be saved is not news worth gold: the note steps back and
   lets whatever is below it catch the eye. */
.over-note[data-state='practice'] {
    color: var(--muted);
}

.initials__label {
    font-size: 0.72rem;
    color: var(--muted);
}

.initials__row {
    display: flex;
    gap: 8px;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    /* Fills the panel so the field can take the width the Save button leaves,
       rather than sizing to its own content and pushing the button down. */
    width: 100%;
}

.initials__input {
    /* Grows into the room the button leaves, and never gets narrower than a
       full name: on a 320px phone that makes the row wrap and the field take
       the whole line, instead of scrolling a name nobody can read. */
    flex: 1 1 9rem;
    min-width: min(11rem, 100%);
    padding: 9px 12px;
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    background: rgba(4, 12, 22, 0.9);
    color: var(--accent-2);
    font: inherit;
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-align: center;
    text-transform: uppercase;
}

.initials__input:focus {
    outline: 2px solid var(--accent-2);
    outline-offset: 2px;
}

/* ---------- touch pad ---------- */

/* Two gaps, not one. The column gap is the room between the steering and the
   legend beside it, and it wants air; the row gap is the room between the blocks
   stacked under that row, and every one of those already carries its own room
   above and below (`padding-block` on `.pad__group` and `.standings`). Counting
   the gap and the padding as well as the panel's own made ~40px of nothing above
   the key's first line and under its last one. The row gap is now the same 4px
   the blocks use, so the whole key sits on one rhythm instead of three. */
.pad {
    grid-area: pad;
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: 4px clamp(10px, 2vw, 22px);
    align-items: center;
    padding: 8px clamp(12px, 2vw, 18px);
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
    background: linear-gradient(180deg, rgba(17, 36, 60, 0.9), rgba(6, 14, 25, 0.9));
}

.pad__dpad {
    display: grid;
    grid-template-columns: repeat(3, 2.4rem) auto;
    grid-template-rows: repeat(2, 2.4rem);
    grid-template-areas:
        ". up . switch"
        "left down right switch";
    gap: 4px;
}

.pad__key {
    border: 1px solid var(--panel-line);
    border-radius: var(--radius-sm);
    background: var(--panel-soft);
    color: var(--ink);
    font-size: 0.85rem;
    line-height: 1;
    cursor: pointer;
    touch-action: manipulation;
}

.pad__key:hover,
.pad__key:active {
    border-color: var(--accent);
    background: rgba(56, 189, 248, 0.16);
    color: var(--accent);
}

.pad__key--up { grid-area: up; }
.pad__key--left { grid-area: left; }
.pad__key--down { grid-area: down; }
.pad__key--right { grid-area: right; }

/* The stick's own button occupies the cross's spare column, both rows of it, so
   it is a thumb's width beside the arrows rather than under them. It is hidden
   outside the Collective's mode, and `[hidden]` loses to the `display` a grid
   item needs, so the rule is written here. */
.pad__switch {
    grid-area: switch;
    align-self: stretch;
    padding: 0 6px;
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.12em;
}

.pad__switch[hidden] {
    display: none;
}

/* The mode switch is a toggle, and a toggle has to look pressed: the words on it
   are what a press would give, so the only thing that says which one is on is
   this rule. */
.button[aria-pressed="true"] {
    border-color: var(--accent);
    color: var(--accent);
    box-shadow: inset 0 0 0 1px rgba(56, 189, 248, 0.25);
}

.pad__legend {
    display: grid;
    gap: 2px;
    margin: 0;
    font-size: 0.72rem;
    line-height: 1.7;
    color: var(--muted);
}

/* The key to the pickups, under the steering, in two groups: what the board is
   made of, and what drops on it. One glyph and one line each, so the board
   teaches its own icons rather than relying on a manual.

   Two lists because they are two things: the top two rows of the old flat key
   were the floor and the things that land on it, drawn at the same weight and
   the same indent, and nine rows like that read as a paragraph about the board
   rather than as a key to it.

   Each group carries the same room above its first line as under its last one, so
   the three blocks in this panel - the floor, the drops and the board of scores
   below them - sit at one rhythm. The room is 4px, the same number as the pad's
   row gap: at 12px a group's padding plus a full grid gap plus the next group's
   padding stacked into nearly 50px of air between two descriptions, which read as
   a gap in the key rather than as spacing in it. */
.pad__group {
    grid-column: 1 / -1;
    width: 100%;
    display: grid;
    gap: 7px;
    padding-block: 4px;
}

/* The line that names a group. Not a heading - the page has one `h1`, and the
   footer speaks in a small voice - so it is a caption, and it carries the rule
   that separates the groups: the rule belongs to the label, not to the list
   under it, which is why it moved up here from `.pad__items`. The 5px is the
   label's own room under its rule; the 4px above the rule is the group's, plus
   the pad's 4px row gap. */
.pad__caption {
    margin: 0;
    padding-top: 5px;
    border-top: 1px solid var(--panel-line);
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    text-align: left;
    color: var(--muted);
}

/* The cells: the glyph in a column of its own - 2.4rem, which is the `Space` cap
   that stacks under the torpedo's glyph plus a little air, so the cap sits inside
   the column instead of hanging out of it and across the first words of the
   sentence beside it - and every name starting on the same edge of the same
   column, so the key has a left margin to read down. */
.pad__items {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: 10px 24px;
    margin: 0;
    padding: 0;
    list-style: none;
    font-size: 0.72rem;
    line-height: 1.5;
    color: var(--muted);
}

/* One cell: the glyph, then the words. `text-wrap: pretty` is here rather than on
   the sentence because it is a block container's property and the sentence is an
   inline span inside this one - it stops a sentence ending on one short word, which
   is the ragged edge a key like this is trying to lose. */
.pad__item {
    display: grid;
    grid-template-columns: 2.4rem minmax(0, 1fr);
    align-items: start;
    gap: 8px;
    text-align: left;
    text-wrap: pretty;
}

/* The name on a line of its own, the sentence under it. This is most of the
   difference between a key and a wall: in one flow a bold name and a sentence of
   any length share a line, so twelve rows of twelve lengths have no edge for the
   eye to run down. */
.pad__item strong {
    display: block;
    margin-bottom: 1px;
    color: var(--ink);
    font-weight: 600;
    letter-spacing: 0.01em;
}

/* The glyph of a key row, at 1.7rem: a drawing of a pickup is smaller on a page
   than the same drawing on the board, and at 1.35rem it was a picture you had to
   lean in for. The glyph inside it stays the same fraction of its box
   (`drawPickup()` draws at 0.34 of it), so the box is what the size is asked of,
   and nothing is painted behind it - see `paintPickupGlyphs()`. */
.pad__icon {
    width: 1.7rem;
    height: 1.7rem;
    justify-self: center;
}

/* The glyph and the key that drops it, stacked in the icon's column. Only the
   torpedo has a control of its own, and the identifier belongs to the pair, so
   the row reads icon-then-key, then the words - the same order the other eight
   rows read, with the key in the space the glyph leaves under it. The 2px is the
   cap's own room under the drawing: at 1px the outline of the `kbd` touched the
   tail of the torpedo. */
.pad__control {
    display: grid;
    justify-items: center;
    gap: 2px;
}

.pad__control kbd {
    padding: 0 3px;
    font-size: 0.6rem;
    line-height: 1.5;
}

/* ---------- standings ---------- */

/* The board lives here, folded away. `grid-column: 1 / -1` puts it under the
   steering and the key to the pickups, across both columns, at every width. The
   padding is the key's: 4px over the rule and 4px under the last row, so this
   block is spaced like the two groups above it instead of ending flush against
   the panel's own edge. */
.standings {
    grid-column: 1 / -1;
    width: 100%;
    padding-block: 4px;
    border-top: 1px solid var(--panel-line);
    font-size: 0.72rem;
    color: var(--muted);
}

.standings__summary {
    cursor: pointer;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    color: var(--muted);
}

.standings__summary:hover,
.standings[open] .standings__summary {
    color: var(--accent);
}

.standings .board {
    margin-top: 8px;
}

/* Which way the board is read, in words. The glyph below says how the rows are
   grouped; this says what a good row is, which is the one thing a reader cannot
   see: the classic board's best row is its biggest number and the Collective's is
   its smallest, because that board ranks the Enterprise rather than the player. */
.standings__note {
    margin: 6px 0 0;
    font-size: 0.68rem;
    letter-spacing: 0.06em;
    color: var(--muted);
}

/* ---------- which way the board is read ---------- */

/* One icon above the board, on the right: the reading that is on, drawn. A
   figure alone is one row per player; a crowd is every stored run. The glyph is
   picked from `data-view` on the button, which is the same value main.js uses to
   choose the rows, so the icon cannot say one thing while the board shows
   another. */
.standings__view {
    display: grid;
    gap: 2px;
}

.board__view {
    justify-self: end;
    display: grid;
    place-items: center;
    width: 24px;
    height: 24px;
    margin-top: 6px;
    padding: 0;
    border: 1px solid var(--panel-line);
    border-radius: 7px;
    background: rgba(4, 12, 22, 0.7);
    color: var(--accent-2);
    cursor: pointer;
    transition: color 120ms ease, border-color 120ms ease;
}

.board__view:hover,
.board__view:focus-visible {
    border-color: var(--accent);
    color: var(--accent);
}

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

.board__glyph {
    display: block;
    fill: currentColor;
}

.board__view[data-view='unique'] .board__glyph--all,
.board__view[data-view='all'] .board__glyph--one {
    display: none;
}

kbd {
    padding: 1px 5px;
    border: 1px solid var(--panel-line);
    border-bottom-width: 2px;
    border-radius: 5px;
    background: rgba(4, 12, 22, 0.8);
    color: var(--ink);
    font: inherit;
    font-size: 0.72rem;
}

/* ---------- small screens ---------- */

@media (max-width: 620px) {
    /* No `.hud` override: the header is three rows in one column at this width, which
       is the base sheet. The mark and the switches still share the top row - 199px of
       a 344px panel at 390, and 199 of 274 at 320. */

    .pad {
        grid-template-columns: minmax(0, 1fr);
        justify-items: center;
        text-align: center;
    }

    .pad__legend {
        justify-items: center;
    }
}

/* Wide screens: the maze needs height, not width, so the readouts move beside
   it and the board gets the full height of the page. */
@media (min-width: 900px) {
    .app {
        grid-template-areas:
            "stage hud"
            "stage pad";
        grid-template-columns: minmax(0, 1fr) 272px;
        grid-template-rows: auto minmax(0, 1fr);
    }

    /* The header is the base sheet's three rows, in a column 272px wide; what it
       needs here is to stop stretching down the page. No `.hud__stats` override
       either: the numbers keep the base sheet's three columns, and 246px of panel
       holds the two six-digit numbers and the switch between them with room left
       over. */
    .hud {
        align-content: start;
        align-self: start;
    }

    .hud__mark {
        height: 38px;
    }

    .stat dd {
        overflow-wrap: anywhere;
    }

    .pad {
        grid-template-columns: minmax(0, 1fr);
        justify-items: center;
        text-align: center;
        align-self: start;
    }

    .pad__legend {
        justify-items: center;
    }

    .screen__title {
        letter-spacing: 0.18em;
    }
}

/* A pointer that can hover is a mouse or a trackpad, and that player has arrow
   keys: the on-screen pad is for a phone, where fingers are the only input. It
   is hidden by pointer type rather than by width, because a phone in landscape
   is wide and a touchscreen laptop still has a keyboard. */
@media (hover: hover) and (pointer: fine) {
    .pad__dpad {
        display: none;
    }

    .pad {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ---------- a phone ---------- */

/* Fingers, and only fingers. `pointer: coarse` is the *primary* pointer, so a
   touchscreen laptop with a mouse in it does not match this and a desktop
   browser never does - everything below is the phone's, and no rule above is
   rewritten by it. */
@media (hover: none) and (pointer: coarse) {
    /* A thumb is not a cursor: the keys are bigger, and they claim the gesture.
       `touch-action: none` on a key is what makes a press-and-hold a press
       rather than the beginning of a scroll - a drag that starts on the pad
       cannot pan the page out from under the player's hand, which is what used
       to happen once steering ran off the edge of a key. */
    .pad__dpad {
        grid-template-columns: repeat(3, 3rem);
        grid-template-rows: repeat(2, 3rem);
        gap: 6px;
    }

    .pad__key {
        font-size: 1rem;
        touch-action: none;
        -webkit-user-select: none;
        user-select: none;
        -webkit-touch-callout: none;
    }

    /* The legend under the pad is a keyboard's: arrows, WASD, Space, P, M. A
       phone has none of those keys and all of them on screen already - the pad
       steers and the Torpedo button fires - so on a phone it is a paragraph
       about somebody else's hardware. */
    .pad__legend {
        display: none;
    }
}

/* Portrait: no fixed height, because there is nothing to fit a maze into - the
   board is pinned to the top of the screen, the arrows are under it, and
   everything that is read rather than pressed is below that. The page is taller
   than the screen on purpose: the scores are reached by scrolling, and scrolling
   slides them up behind the board rather than carrying it off the screen. */
@media (hover: none) and (pointer: coarse) and (orientation: portrait) {
    .app {
        grid-template-areas:
            "stage"
            "side";
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: auto auto;
        align-content: start;
        height: auto;
        min-height: 100dvh;
        /* No padding above the board: the board is the top of the screen. The
           only thing left up there is a notch, and that keeps its own inset so a
           cut-out never eats the maze's first row of tiles - on a phone in Safari,
           which is where the page is read, that inset is zero and the board starts
           at the very edge. */
        padding:
            env(safe-area-inset-top, 0px)
            max(10px, env(safe-area-inset-right))
            max(14px, env(safe-area-inset-bottom))
            max(10px, env(safe-area-inset-left));
    }

    /* The maze is 23 tiles wide and 17 tall, so this is the whole of the board's
       size: at any width the canvas is given a box of its own ratio and the
       renderer has no band to letterbox. The floor stays for a browser that does
       not know `aspect-ratio`, where the canvas - absolute, and the only child
       with a size - would otherwise have nothing to be tall against.
       It is pinned. The board is the one thing that must not move while a thumb is
       steering, so it sticks to the top of the screen and everything below it - the
       arrows, the readouts, the key to the pickups, the scores - scrolls up and
       behind it. Hence the opaque background (it has something to hide), the
       shadow under it (so the panels passing behind read as passing behind and not
       as clipped), and the square top corners (it runs to the edge it is pinned
       to, and a rounded corner would leak the scrolling page through it). */
    .stage {
        position: sticky;
        top: 0;
        z-index: 4;
        aspect-ratio: 23 / 17;
        border-radius: 0 0 var(--radius) var(--radius);
        background: #04080f;
        box-shadow: 0 14px 26px rgba(2, 8, 20, 0.72);
    }

    /* The canvas takes the stage's shape, corners included: square where the stage
       is square, or the panel's own rounding would show inside it at the top. */
    .stage__board {
        border-radius: inherit;
    }

    /* The readouts and the controls become one column here, in the order a thumb
       wants them: the arrows first, then the score and the buttons pressed
       mid-run, then the key to the pickups, then the board of scores. */
    .side {
        grid-area: side;
        display: flex;
        flex-direction: column;
        gap: var(--gap);
        min-height: 0;
    }

    /* The footer stops being one panel and becomes items of that column, which is
       what lets the readouts sit between the arrows and the key to the pickups
       instead of under both. Its panel is not lost: each block wears one. */
    .pad {
        display: contents;
    }

    /* The vertical padding is 6px where it was 10px: two cards in a column each
       pad their own edge, so the room between them is card, padding, the column's
       gap, padding, card - the phone's copy of the same over-spacing the desktop
       key had between two descriptions. The dpad sets its own 8px below, so this
       number only moves the key's groups and the scores. */
    .pad__dpad,
    .pad__group,
    .standings {
        padding: 6px clamp(12px, 2vw, 18px);
        border: 1px solid var(--panel-line);
        border-radius: var(--radius);
        background: linear-gradient(180deg, rgba(17, 36, 60, 0.9), rgba(6, 14, 25, 0.9));
    }

    /* Inside a card the caption needs no rule above it: the card is the edge, and
       a second line 5px under the card's own reads as a mistake. */
    .pad__group .pad__caption {
        padding-top: 0;
        border-top: none;
    }

    /* The arrows are the width of the screen. They used to be a 3rem cluster
       floating in the middle of a card, which put the two outside keys a stretch
       away from a thumb that was already at the edge. Now the card spans the page
       and its columns are `1fr` each, so the bottom row divides that width into
       three equal keys, and the up key - which is in the middle column, the down
       key's own column - is the same width as the three of them for free and sits
       squarely over the middle one. There is no second width to keep in step: the
       key above is wide because the key below it is. */
    .pad__dpad {
        order: 1;
        align-self: stretch;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        grid-template-rows: repeat(2, clamp(3.5rem, 14vw, 4.75rem));
        gap: 8px;
        padding: 8px;
    }

    /* A key is a whole thumb wide now, so its glyph is big enough to read at a
       glance rather than aimed at. */
    .pad__key {
        font-size: clamp(1.25rem, 5.2vw, 1.75rem);
    }

    .hud {
        order: 2;
    }

    /* Both groups keep one order value: they are one block of reading, and the
       two of them are in the markup in the order they are read. */
    .pad__group {
        order: 3;
    }

    .standings {
        order: 4;
    }
}

/* Landscape: the board gets the whole height of the screen and the readouts get
   a strip down one side, narrow enough to leave the board its height and wide
   enough for a thumb. The strip is where the page's scrolling went - it scrolls
   on its own, with no rubber band into the page, so the board cannot move while
   a player is steering and the scores are still two flicks away. */
@media (hover: none) and (pointer: coarse) and (orientation: landscape) {
    .app {
        grid-template-areas: "stage side";
        grid-template-columns: minmax(0, 1fr) clamp(11.5rem, 24vw, 17rem);
        grid-template-rows: minmax(0, 1fr);
        /* No 420px floor: the board is only ever as tall as the screen is. */
        min-height: 0;
        padding:
            max(6px, env(safe-area-inset-top))
            max(6px, env(safe-area-inset-right))
            max(6px, env(safe-area-inset-bottom))
            max(8px, env(safe-area-inset-left));
    }

    .stage {
        min-height: 0;
    }

    .side {
        grid-area: side;
        display: flex;
        flex-direction: column;
        gap: var(--gap);
        min-height: 0;
        overflow-y: auto;
        overscroll-behavior: contain;
        -webkit-overflow-scrolling: touch;
    }

    /* The strip is 176px of content at its narrowest and the wordmark is 91 of them,
       so the switches go back under the mark here rather than squeezing into the 68
       that are left - a row of three is 108. Everything in this strip scrolls, so the
       extra row costs a thumb nothing. The numbers row keeps its three columns with
       the gap cut to 6px: 62 + 32 + 62 across two of them is 168 of the 176. */
    .hud {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas:
            "brand"
            "tools"
            "stats"
            "kit";
    }

    .hud__stats {
        gap: 4px 6px;
    }

    .pad {
        grid-template-columns: minmax(0, 1fr);
        justify-items: center;
        text-align: center;
        align-self: start;
    }

    /* One column, and one that may be narrower than the 13rem the footer asks
       for: the strip is that narrow, and a grid that cannot shrink would put its
       own content into a horizontal scrollbar beside the board. */
    .pad__items {
        grid-template-columns: minmax(0, 1fr);
    }
}

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

/* ---------- the board's colour, on the board and not in the HUD ---------- */

/* A level is recognised by its colour before its shape is read, so the canvas
   wears it: the walls, the pulse in the case, and the banner over the maze. The
   HUD does not. The board number, the score, the cubes word and the three
   counters took the same three tones, which put a magenta panel around a white
   pause button and made the numbers look like the level's rather than the run's.
   They read in `--ink` now, the colour the two switches and every other button in
   that panel already uses, so the panel is the same panel on board 1 and board 14
   and only the colour a player reads the *board* by is the level's. */

/* The bar under the wordmark is chrome, not the board: it keeps the page's light
   blue - `--accent`, the colour a switch takes on hover and a button's ring is
   drawn in - on every board. It is what a player reads the line by, so it has to
   mean "this is the board you are on" rather than "this board is magenta"; wearing
   the palette it changed every ten boards and read as a second, smaller answer to
   the same question the maze already answers in a much bigger font. */
.hud__board {
    border-left: 3px solid var(--accent);
    padding-left: 8px;
    line-height: 1.5;
}

/* The score is the one number the switches do not already say in `--ink`: its own
   rule has it in the second accent, so it is named here rather than inherited. */
.stat--score dd {
    color: var(--ink);
    text-shadow: 0 0 18px rgba(231, 242, 255, 0.22);
}

.stat--mode dd[data-mode="loose"] {
    color: var(--ink);
}

.stage__banner {
    color: var(--board-core);
    text-shadow:
        0 0 10px color-mix(in srgb, var(--board-halo) 80%, transparent),
        0 0 34px color-mix(in srgb, var(--board-halo) 45%, transparent);
}

.stage__board {
    box-shadow:
        inset 0 0 60px rgba(160, 0, 160, 0.14),
        0 18px 44px rgba(2, 8, 20, 0.6);
    box-shadow:
        inset 0 0 60px color-mix(in srgb, var(--board-halo) 18%, transparent),
        0 18px 44px rgba(2, 8, 20, 0.6);
}
