/*
 * Gridiron Game v2
 *
 * The legacy page had one <style> block with a handful of classes and the rest
 * of the look inline on each element (widths, borders, margins, colors). This is
 * a small design system instead: colours and sizes are variables, the field is a
 * table with transparent cells over row backgrounds, and everything else is a
 * card.
 */

:root {
    --bg: #07130c;
    --panel: #0e2318;
    --panel-line: #1d4a33;
    --panel-raised: #123322;
    --ink: #e8f5ee;
    --muted: #9dc4ae;
    --turf: #1c7a45;
    --endzone: rgba(127, 29, 29, 0.92);
    --line: rgba(255, 255, 255, 0.92);
    --sideline: 6px;
    --accent: #fbbf24;
    /*
     * ui.js measures the play area and sets --cell inline from layout.js.
     * This clamp is the no-JS fallback: roughly one board that fits a laptop.
     * The gutter is one cell thick in either orientation, and every piece is
     * drawn from --cell, so the whole board scales as one.
     */
    --cell: clamp(15px, 2.4vh, 34px);
    --gutter: calc(var(--cell) * 1.2);
    --icon-size: calc(var(--cell) - 4px);
    --radius: 14px;
    color-scheme: dark;
}

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    min-height: 100vh;
    background:
        radial-gradient(1200px 600px at 50% -10%, #123a24 0%, transparent 60%),
        var(--bg);
    color: var(--ink);
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

/*
 * The page is a grid: the board on the left, and a HUD column on its right
 * with the title, scoreboard and tools at the top and the panels below.
 * Keeping the header beside the board rather than above it lets the board
 * use the full height of the window, which is what portrait needs.
 */
.game {
    display: grid;
    /*
     * The board gets everything the screen can give it: the sidebar is a fixed
     * 420 px column (ui.js uses the same number when it measures the fit) and
     * there is no page max-width, so a wide desktop grows the field until the
     * height runs out instead of stopping at a 1560 px page.
     */
    grid-template-columns: auto 420px;
    grid-template-rows: auto 1fr;
    grid-template-areas:
        "field header"
        "field sidebar";
    gap: 12px 16px;
    align-items: start;
    justify-content: center;
    box-sizing: border-box;
    min-height: 100vh;
    margin: 0 auto;
    padding: 10px 14px;
}

/* ---------- header ---------- */

/*
 * The HUD column reads top to bottom: the title, then the scoreboard, then the
 * three tools. Everything is centred, so the column lines up with the cards in
 * the sidebar underneath it.
 */
.topbar {
    grid-area: header;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
}

.brand {
    display: flex;
    align-items: center;
    gap: 10px;
}

.brand h1 {
    margin: 0;
    font-size: 1.45rem;
    letter-spacing: 0.01em;
}

/* The wordmark next to the logo. Bebas Neue is loaded in index.html; Impact is
 * the fallback on machines that already have it. */
.brand__word {
    font-family: 'Bebas Neue', Impact, 'Arial Narrow Bold', 'Haettenschweiler', sans-serif;
    font-size: 1.65rem;
    line-height: 1;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--ink);
    white-space: nowrap;
}

.badge {
    padding: 0.1rem 0.45rem;
    border: 1px solid var(--accent);
    border-radius: 999px;
    color: var(--accent);
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
}

/*
 * The test switch, put beside the wordmark by admin.js.
 *
 * It is a flask rather than a fifth tool button: test mode is an admin switch,
 * not a game action, and it is on or off rather than momentary. Grey while the
 * board plays by the rules, red while it does not - the only two states it has,
 * so it needs no label of its own (aria-pressed and the title say the rest).
 */
.test-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1px solid var(--panel-line);
    border-radius: 9px;
    background: var(--panel-raised);
    color: #8fa3ad;
    opacity: 0.65;
    cursor: pointer;
    transition: color 120ms ease, border-color 120ms ease, opacity 120ms ease, box-shadow 120ms ease;
}

.test-toggle svg {
    display: block;
    width: 18px;
    height: 18px;
}

.test-toggle:hover,
.test-toggle:focus-visible {
    opacity: 1;
    color: #cbd8e0;
}

.test-toggle--on,
.test-toggle--on:hover,
.test-toggle--on:focus-visible {
    color: #ef4444;
    border-color: rgba(239, 68, 68, 0.6);
    opacity: 1;
    box-shadow:
        0 0 0 1px rgba(239, 68, 68, 0.25),
        0 0 12px rgba(239, 68, 68, 0.35);
}

.scoreboard {
    display: flex;
    /* One row, always: the sidebar is only 420 px, so the name inputs shrink
       rather than letting the possession line drop to a second row. */
    flex-wrap: nowrap;
    align-items: center;
    justify-content: center;
    gap: 8px;
    max-width: 100%;
    padding: 6px 10px;
    background: var(--panel);
    border: 1px solid var(--panel-line);
    border-radius: 18px;
}

.score {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1 1 auto;
    min-width: 0;
    opacity: 0.7;
    transition: opacity 120ms ease, color 120ms ease;
}

.score--active {
    opacity: 1;
    color: var(--accent);
}

/* The name is editable: an input, not a label. */
.score__name {
    width: 9ch;
    min-width: 4ch;
    flex: 1 1 auto;
    padding: 0.15rem 0.3rem;
    background: transparent;
    border: 1px solid transparent;
    border-radius: 6px;
    color: var(--ink);
    font: inherit;
    font-size: 0.8rem;
    font-weight: 600;
    text-align: center;
    text-overflow: ellipsis;
}

.score__name:hover {
    border-color: var(--panel-line);
}

.score__name:focus {
    outline: none;
    border-color: var(--accent);
    background: rgba(0, 0, 0, 0.25);
}

.score__name::placeholder {
    color: var(--muted);
}

/* The team colour swatch: a small round picker, locked after the first play. */
.score__color {
    flex: none;
    width: 20px;
    height: 20px;
    padding: 0;
    background: none;
    border: 2px solid var(--panel-line);
    border-radius: 50%;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
}

.score__color::-webkit-color-swatch-wrapper {
    padding: 0;
}

.score__color::-webkit-color-swatch {
    border: 0;
    border-radius: 50%;
}

.score__color::-moz-color-swatch {
    border: 0;
    border-radius: 50%;
}

.score__color:hover:not(:disabled) {
    border-color: var(--accent);
}

.score__color:disabled {
    opacity: 0.45;
    cursor: not-allowed;
}

.score__value {
    min-width: 1.5ch;
    font-size: 1.35rem;
    text-align: center;
}

/* Who has the ball, with a football on either side of the name. */
.possession {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 6px;
    padding: 0 8px;
    border-left: 1px solid var(--panel-line);
    border-right: 1px solid var(--panel-line);
    color: var(--accent);
    font-size: 0.95rem;
    font-weight: 700;
    white-space: nowrap;
}

.possession__ball {
    flex: none;
    width: 26px;
    height: auto;
    filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.55));
}

.possession__name {
    max-width: 14ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
 * The special each team picked, in a strip under the scoreboard. ui.js fills the
 * two chips and unhides the strip; a game without specials leaves it hidden.
 *
 * Most specials are team abilities (see renderSpecials in ui.js), so this is
 * where they are named: the chip wears the team's colour down its left edge and
 * greys out once the special has been spent for this play.
 */
.specials {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 6px;
    max-width: 100%;
    margin: 0;
}

.specials[hidden] {
    display: none;
}

.specials__chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 9px 2px 7px;
    border: 1px solid var(--panel-line);
    border-left: 3px solid var(--chip, var(--accent));
    border-radius: 999px;
    background: var(--panel);
    color: var(--ink);
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    white-space: nowrap;
}

.specials__chip[hidden] {
    display: none;
}

/* The gold star the key explains, before the special's name. */
.specials__chip::before {
    content: '\2605';
    color: #f5b301;
    font-size: 0.8rem;
    line-height: 1;
}

.specials__chip--spent {
    color: var(--muted);
    opacity: 0.6;
}

.specials__chip--spent::before {
    color: var(--muted);
}

.tools {
    display: flex;
    gap: 8px;
}

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

.button {
    font: inherit;
    font-size: 0.85rem;
    font-weight: 600;
    padding: 0.5rem 0.8rem;
    border: 1px solid var(--panel-line);
    border-radius: 8px;
    background: var(--panel-raised);
    color: var(--ink);
    cursor: pointer;
    transition: border-color 120ms ease, background 120ms ease;
}

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

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

.button--primary {
    background: var(--accent);
    border-color: var(--accent);
    color: #1c1205;
}

.button--primary:hover {
    background: #fcd34d;
}

.button--ghost {
    background: transparent;
}

.button__note {
    margin-left: 0.35em;
    font-size: 0.9em;
    font-weight: 700;
    color: var(--accent);
}

/* ---------- layout ---------- */

/*
 * The field and the sidebar are grid items of .game (display: contents), so
 * the board sits beside the HUD column without another wrapper box.
 */
.layout {
    display: contents;
}

.field-wrap {
    grid-area: field;
    max-width: 100%;
    overflow: auto;
    padding: 6px;
    background: #0a1c12;
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
}

.sidebar {
    grid-area: sidebar;
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
}

.panel,
.readout,
.legend {
    background: var(--panel);
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
    padding: 14px;
}

.panel[hidden] {
    display: none;
}

/*
 * Whose move it is: the panel the acting side uses (its formation cards, or the
 * dice and buttons) wears that team's colour. ui.js only sets this while someone
 * can actually act - between turns, while a question is open and after the game
 * ends no panel is marked, so the border never promises a turn nobody can take.
 */
.panel--active {
    border-color: var(--turn-color, var(--accent));
    box-shadow:
        inset 0 0 0 1px var(--turn-color, var(--accent)),
        0 0 18px -6px var(--turn-color, var(--accent));
}

.panel__title {
    margin: 0 0 8px;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--muted);
}

.panel__hint {
    margin: 8px 0 0;
    color: var(--muted);
    font-size: 0.78rem;
    line-height: 1.45;
}

/* ---------- the field ---------- */

/*
 * The board sits on grass.
 *
 *   layer 2  radial       a soft light from the top, so the board is not flat
 *   layer 1  grass.svg    a 240 px seamless tile: fractal-noise colour plus 90
 *                         blades (see the file's own comment)
 *
 * The light/dark mowing bands are row classes (three rows each, starting at
 * yard 4) rather than a gradient, so they line up with the yard rows at any
 * --cell size and keep the texture visible through them.
 *
 * The cells themselves are transparent, and the end zone / first-down rows use
 * semi-transparent fills, so the turf reads through the whole board.
 */
.field {
    border-collapse: separate;
    border-spacing: 0;
    /* Fixed layout keeps every cell exactly --cell square: with auto layout a
       two-digit yard number in the header could widen its column. */
    table-layout: fixed;
    background-color: var(--turf);
    background-image:
        radial-gradient(120% 90% at 50% 0%, rgba(255, 255, 255, 0.07), transparent 60%),
        url("../img/grass.svg");
    background-size: auto, 240px 240px;
    border: 1px solid #0b3d22;
    user-select: none;
    touch-action: manipulation;
}

/*
 * Pin the table to the exact column count. With `table-layout: fixed` an auto
 * width can be stretched by some engines, which would break the square cells.
 * 20.4 = 18 lanes + two 1.2-cell gutters; keep it in step with GUTTER_RATIO in
 * layout.js. The +2px is this table's own left and right border.
 */
.field--portrait {
    width: calc(var(--cell) * 20.4 + 2px);
}

.field--landscape {
    width: calc(var(--cell) * 30 + 2px);
}

.gutter {
    position: relative;
    width: var(--gutter);
    padding-right: calc(var(--cell) * 0.2);
    color: rgba(240, 250, 236, 0.92);
    /* Scales with the board but never becomes unreadable or bulky. */
    font-size: clamp(8px, calc(var(--cell) * 0.38), 12px);
    font-weight: 700;
    text-align: right;
    text-shadow: 0 1px 0 rgba(0, 0, 0, 0.7);
    vertical-align: middle;
}

/* The mirrored column: same numbers, aligned to the field. */
.gutter--right {
    padding-right: 0;
    padding-left: calc(var(--cell) * 0.2);
    text-align: left;
}

/*
 * Landscape puts the yards across the top and bottom, so the gutter turns 90
 * degrees: a narrow column over each yard, one cell wide and one gutter tall.
 */
.gutter--column {
    width: var(--cell);
    height: var(--gutter);
    padding: 0;
    text-align: center;
    vertical-align: middle;
    font-size: clamp(8px, calc(var(--cell) * 0.34), 11px);
}

/*
 * The line-of-scrimmage football. It is absolutely positioned inside the
 * left gutter cell, so it paints over that row's yard number; the right-hand
 * gutter still shows the number. In landscape it covers the number above the
 * ball instead, and the bottom row keeps it readable.
 */
.gutter__marker {
    position: absolute;
    top: 50%;
    right: 0;
    z-index: 1;
    width: calc(var(--gutter) - 2px);
    height: auto;
    transform: translateY(-50%);
    filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.6));
    pointer-events: none;
}

.gutter--column .gutter__marker {
    right: auto;
    left: 50%;
    width: calc(var(--gutter) * 0.92);
    transform: translate(-50%, -50%);
}

.cell {
    position: relative;
    width: var(--cell);
    height: var(--cell);
    padding: 0;
    background: transparent;
    border-right: 1px solid rgba(0, 0, 0, 0.10);
    border-bottom: 1px solid rgba(0, 0, 0, 0.10);
    text-align: center;
    vertical-align: middle;
    line-height: 0;
    cursor: default;
}

/*
 * Only a square with something to do answers the cursor. ui.js adds this class
 * after asking `game.canAct`, so an empty field does not light up cell by cell
 * as the mouse crosses it - a highlight there would promise an action that does
 * not exist. It is also the only place the pointer cursor appears.
 */
.cell--hover {
    box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.35);
    cursor: pointer;
}

/*
 * End zones wear the colour of the team that attacks them - the low end (yards
 * 1-3) is team one's, the high end (28-30) team two's - so the colour a player
 * sees at the end they are driving at is their own. `--endzone` is the fallback
 * for a page with no team colours yet.
 *
 * The wash is translucent, so the grass grain under the board still reads
 * through, and the mowing bands and checker are painted over it below: an end
 * zone has exactly the turf pattern the rest of the field has.
 */
.cell--endzone {
    background-color: var(--endzone);
}

.cell--endzone-one {
    background-color: var(--endzone-one, var(--endzone));
}

.cell--endzone-two {
    background-color: var(--endzone-two, var(--endzone));
}

/*
 * The bands and the checker are washes *over* the cell rather than its colour,
 * which is what lets an end zone keep its team tint and its grass pattern at
 * the same time. A pseudo-element also gives the two washes their own layers,
 * so the checker's gradient and the band's colour cannot overwrite each other.
 */
.cell::before {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
}

.cell--band-light::before {
    background-color: rgba(255, 255, 255, 0.08);
}

.cell--band-dark::before {
    background-color: rgba(0, 0, 0, 0.16);
}

/*
 * A faint checker over each mowing band: every second square gets one more
 * wash, so the grass reads as a mown checkerboard rather than flat stripes.
 * It is deliberately weak - the eye should catch it, not the players.
 */
.cell--checker-b::before {
    background-image: linear-gradient(rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.035));
}

/*
 * The other way to draw the two marks: as **tinted rows** instead of borders
 * between rows. `field--lines-rows` (set by the Lines button in ui.js) tints the
 * row the offence starts on and the row it must reach, and switches the edge bars
 * off. Nothing about which squares are marked changes - only how they are
 * painted - so this mode needs no direction logic at all: a row is a row however
 * the drive is going.
 */
/*
 * The row tints carry the same weight as each other - the white is at the same
 * alpha as the yellow - so the two marked rows read as a pair: a transparent
 * white over the grass on the line of scrimmage, a transparent yellow over it on
 * the line to gain. At 16% the white all but vanished next to the yellow.
 */
.field.field--lines-rows .cell--scrimmage {
    background-color: rgba(255, 255, 255, 0.42);
}

.field.field--lines-rows .cell--scrimmage::after {
    display: none;
}

.field.field--lines-rows .cell--first-down {
    background: rgba(251, 191, 36, 0.42);
}

/*
 * The line to gain: a thick solid yellow bar on the *near* edge of its row - the
 * boundary the offence crosses - so it reads between the two rows the way the
 * scrimmage line does, from either end of the field. It used to tint the whole
 * row, which could not say which side of the row the line was on.
 *
 * Near edge means the side the offence is coming from, so this is the mirror of
 * the scrimmage rule: driving down the screen the bar sits on the top of the row,
 * driving up it sits on the bottom; landscape does the same with left/right.
 * `field--drive-forward` is set by ui.js from the on-screen direction.
 */
.field--portrait .cell--first-down {
    background-image: linear-gradient(#fbbf24, #fbbf24);
    background-repeat: no-repeat;
    background-size: 100% 4px;
    background-position: 0 100%;
}

.field--portrait.field--drive-forward .cell--first-down {
    background-position: 0 0;
}

.field--landscape .cell--first-down {
    background-image: linear-gradient(#fbbf24, #fbbf24);
    background-repeat: no-repeat;
    background-size: 4px 100%;
    background-position: 100% 0;
}

.field--landscape.field--drive-forward .cell--first-down {
    background-position: 0 0;
}

/*
 * The white rectangle around the field:
 *   sidelines  lanes 1 and 18   - left/right in portrait, top/bottom in landscape
 *   end lines  yards 1 and 30   - the outer edge of each end zone
 *   goal lines yards 3 and 28   - where the field meets an end zone
 * `--sideline` and `--line` are the same in both views; only the sides change.
 */
.field--portrait .cell--sideline-start {
    border-left: var(--sideline) solid var(--line);
}

.field--portrait .cell--sideline-end {
    border-right: var(--sideline) solid var(--line);
}

.field--landscape .cell--sideline-start {
    border-top: var(--sideline) solid var(--line);
}

.field--landscape .cell--sideline-end {
    border-bottom: var(--sideline) solid var(--line);
}

.field--portrait .cell--edge-start {
    border-top: var(--sideline) solid var(--line);
}

.field--portrait .cell--edge-end {
    border-bottom: var(--sideline) solid var(--line);
}

.field--landscape .cell--edge-start {
    border-left: var(--sideline) solid var(--line);
}

.field--landscape .cell--edge-end {
    border-right: var(--sideline) solid var(--line);
}

.field--portrait .cell--goal-low {
    border-bottom: var(--sideline) solid var(--line);
}

.field--portrait .cell--goal-high {
    border-top: var(--sideline) solid var(--line);
}

.field--landscape .cell--goal-low {
    border-right: var(--sideline) solid var(--line);
}

.field--landscape .cell--goal-high {
    border-left: var(--sideline) solid var(--line);
}

/* ---------- the pieces ---------- */

/*
 * A player is a helmet and a bust.
 *
 *   helmet     position colour
 *   shoulders  the owning team's chosen jersey colour (--jersey, from ui.js),
 *              cut to a silhouette per position
 *   football   a badge on whoever carries the ball (top-right corner)
 *   star       a gold star on the chest of a special player (bottom-left)
 *
 * Position is carried by shape and colour, never by a letter: the helmet colour
 * and the width of the shoulders - linemen broadest, backs narrowest - so the
 * board still reads at 15 px a square.
 *
 * The icon element is the helmet dome; ::before draws the shoulders, ::after the
 * football, and the one child in the slot (from makeCell in ui.js) the star. The
 * strong lineman shares the lineman helmet and shoulders and is marked by a
 * yellow rim, `.cell--strong`.
 * See ICON and SPECIAL_POSITION in config.js for the data behind this.
 */
.icon {
    position: relative;
    display: inline-block;
    width: var(--icon-size);
    height: var(--icon-size);
    vertical-align: middle;
    user-select: none;
    background: var(--helmet, #64748b);
    border-radius: 50% 50% 44% 44% / 62% 62% 38% 38%;
    box-shadow:
        inset 0 -0.16em 0 rgba(0, 0, 0, 0.24),
        inset 0 0.08em 0 rgba(255, 255, 255, 0.3),
        0 0 0 1px #000000;
}

/* A class rule would otherwise beat the UA `[hidden]` rule and paint every
   empty square as a token. */
.icon[hidden] {
    display: none;
}

/* The bust: a pair of shoulders in the team's chosen colour. Its black outline
   crosses the helmet, which draws the seam between jersey and helmet. */
.icon::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 46%;
    border-radius: 46% 46% 18% 18% / 78% 78% 22% 22%;
    background: var(--jersey, #64748b);
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.18),
        0 0 0 1px #000000;
}

/*
 * Shoulders by position: the cue that does not depend on colour at all. Linemen
 * are the widest and lowest (they are the wall), the backs the narrowest and
 * stooped, the receivers tall and upright. Two or three pixels at board size,
 * but that is what the eye compares across a row.
 */
.icon--qb::before {
    left: 12%;
    right: 12%;
    height: 44%;
}

.icon--rb::before {
    left: 18%;
    right: 18%;
    height: 38%;
}

.icon--wr::before {
    left: 13%;
    right: 13%;
    height: 52%;
}

.icon--ol::before {
    left: -3%;
    right: -3%;
    height: 42%;
    border-radius: 30% 30% 14% 14% / 66% 66% 20% 20%;
}

.icon--dl::before {
    left: -5%;
    right: -5%;
    height: 38%;
    border-radius: 26% 26% 14% 14% / 58% 58% 20% 20%;
}

.icon--db::before {
    left: 9%;
    right: 9%;
    height: 48%;
}

/* One helmet colour per position. */
.icon--qb {
    --helmet: #2563eb;
}

.icon--rb {
    --helmet: #16a34a;
}

.icon--wr {
    --helmet: #06b6d4;
}

.icon--ol {
    --helmet: #64748b;
}

.icon--dl {
    --helmet: #dc2626;
}

.icon--db {
    --helmet: #f97316;
}

/*
 * The strong lineman keeps the lineman's slate helmet, so the *piece* is what
 * marks him: a bold two-tone rim around his silhouette, and a glow behind it.
 *
 * Not an `outline` and not a `box-shadow`: both follow the element's box, and a
 * lineman's shoulders are wider than his helmet (`left: -3%`), so either would
 * cut across him. A `filter` follows the painted silhouette instead - helmet
 * dome, shoulder cut - and it survives `icon--ball` and `icon--star` sitting on
 * the same element.
 *
 * The rim is hard, un-blurred drop-shadows at +/-d. Chaining them in alternating
 * directions paints a copy of the piece d pixels out on every side, and
 * alternating the signs stops the copies from stacking outward, so d *is* the
 * thickness.
 *
 * The yellow comes first, so it sits against the piece's own 1px black outline,
 * and a black keyline goes outside it at 2.5px - 1.5 of yellow, then 1px of
 * black. Earlier this was the other way round (black inside, yellow last) and
 * the bright ring ended on whatever was behind it: on the turf it looked soft at
 * 15px, and on a light jersey it had no edge at all. Black outside frames the
 * yellow the way every sprite on the board is framed, and the footprint is the
 * same either way - both orders reach 2.5px. The filtered copies are painted
 * front to back in list order, which is why the ordering is the change and the
 * offsets are not.
 *
 * It was red, and red was the wrong cue twice over. The defensive lineman's
 * helmet is #dc2626 - 11 delta-E from red-500, so a marked guard read as another
 * red helmet - and red-500 is dimmer (55 L*) than the cyan and orange helmets, so
 * it never won the eye at 15px either. Amber is no better: the special-player
 * star is #f5b301. Yellow-300 is 89 L*, brighter than anything on the board but
 * the line of scrimmage, and clear of all six helmets, both default jerseys, the
 * star and the white ball halo. The line to gain is amber (#fbbf24) but it is a
 * straight bar on a row boundary, and only an offensive lineman can be strong -
 * he stands on the line of scrimmage, not on that bar - so the two never meet.
 *
 * The same mark is on the Key's sample (`.legend__strong .icon`), but only the
 * board's version breathes - a pulsing swatch in a legend is noise.
 */
.cell--strong .icon,
.legend__strong .icon {
    filter:
        drop-shadow(1.5px 0 0 #fde047)
        drop-shadow(-1.5px 0 0 #fde047)
        drop-shadow(0 1.5px 0 #fde047)
        drop-shadow(0 -1.5px 0 #fde047)
        drop-shadow(2.5px 0 0 #0a0a0a)
        drop-shadow(-2.5px 0 0 #0a0a0a)
        drop-shadow(0 2.5px 0 #0a0a0a)
        drop-shadow(0 -2.5px 0 #0a0a0a)
        drop-shadow(0 0 3px rgba(253, 224, 71, 0.9))
        drop-shadow(0 0 7px rgba(234, 179, 8, 0.55));
}

.cell--strong .icon {
    animation: strong-glow 2s ease-in-out infinite;
}

/* The same ten shadows in every keyframe, or the filter cannot interpolate; only
   the two blurred ones move. The trough still keeps a 4px bloom: a piece on a
   phone is barely 14px tall, and the rim does the marking now, so the glow can
   fade further without the piece going cold. */
@keyframes strong-glow {
    50% {
        filter:
            drop-shadow(1.5px 0 0 #fde047)
            drop-shadow(-1.5px 0 0 #fde047)
            drop-shadow(0 1.5px 0 #fde047)
            drop-shadow(0 -1.5px 0 #fde047)
            drop-shadow(2.5px 0 0 #0a0a0a)
            drop-shadow(-2.5px 0 0 #0a0a0a)
            drop-shadow(0 2.5px 0 #0a0a0a)
            drop-shadow(0 -2.5px 0 #0a0a0a)
            drop-shadow(0 0 1.5px rgba(253, 224, 71, 0.85))
            drop-shadow(0 0 4px rgba(234, 179, 8, 0.45));
    }
}

/*
 * The line of scrimmage: a dashed white line along the boundary between the
 * offence and the defence. Portrait puts the boundary on the top edge of the
 * LOS row, landscape on the left edge of the LOS column. Drawn as an overlay so
 * no row of helmets is pushed out of line by a border.
 */
.cell--scrimmage {
    position: relative;
}

/*
 * The dashed line of scrimmage always marks the edge the play is heading for, so
 * it lands between the offensive line's row and the defensive line's: above the
 * offensive line on an upward drive, below it on a downward one. It used to sit
 * on the top edge always, which read correctly going up and put the offensive
 * line on the far side of the line going down. `field--drive-forward` is set by
 * ui.js from the on-screen direction - it flips with a flipped board.
 */
.field--portrait .cell--scrimmage::after {
    content: '';
    position: absolute;
    inset: 0 0 auto 0;
    height: 2px;
    background-image: repeating-linear-gradient(90deg, #ffffff 0 4px, transparent 4px 8px);
    pointer-events: none;
}

.field--portrait.field--drive-forward .cell--scrimmage::after {
    inset: auto 0 0 0;
}

.field--landscape .cell--scrimmage::after {
    content: '';
    position: absolute;
    inset: 0 auto 0 0;
    width: 2px;
    background-image: repeating-linear-gradient(180deg, #ffffff 0 4px, transparent 4px 8px);
    pointer-events: none;
}

.field--landscape.field--drive-forward .cell--scrimmage::after {
    inset: 0 0 0 auto;
}

/* Possession: a football badge, with a white halo so it reads on any colour. It
   has two placements, because where the ball sits *is* what the player is doing
   with it. The quarterback is under centre with it up at his shoulder, so his
   badge hangs off the top-right corner, half over the helmet's edge. Anyone else
   carrying it has it tucked in front of him, so the badge sits on the piece
   itself, across the seam between helmet and shoulders. One corner placement for
   everybody - what this was - made every carrier look about to throw. The
   square's spotlight (.cell--carrier) still marks possession too. */
.icon--ball::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 54%;
    z-index: 3;
    width: calc(var(--icon-size) * 0.46);
    height: calc(var(--icon-size) * 0.46);
    background: url('../img/football.svg') center / contain no-repeat;
    transform: translate(-50%, -50%) rotate(-16deg);
    filter:
        drop-shadow(0 0 2px rgba(255, 255, 255, 1))
        drop-shadow(0 0 4px rgba(255, 255, 255, 0.9))
        drop-shadow(0 1px 1px rgba(0, 0, 0, 0.8));
}

/* The passer's version: the same badge, lifted clear of the piece. */
.icon--ball-raised::after {
    left: auto;
    right: -8%;
    top: -8%;
    transform: rotate(-16deg);
}

/*
 * A special player: a gold star on the chest, hanging off the bottom-left corner
 * so it never covers the football or the helmet. Only the specials that belong
 * to one position are drawn on the board (SPECIAL_POSITION in config.js); a
 * team-wide one is named beside the score instead.
 */
.icon__star {
    display: none;
}

.icon--star .icon__star {
    display: block;
    position: absolute;
    left: -6%;
    bottom: -6%;
    z-index: 3;
    width: 48%;
    height: 48%;
    background: #f5b301;
    clip-path: polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%);
    filter:
        drop-shadow(0 0 1px #000000)
        drop-shadow(0 1px 1px rgba(0, 0, 0, 0.7));
    pointer-events: none;
}

.cell--carrier {
    background-color: rgba(255, 255, 255, 0.13);
}

/* A white spotlight, not an amber one: the glow must not tint the helmet,
   which is the position colour. */
.cell--carrier .icon {
    filter:
        drop-shadow(0 0 2px rgba(255, 255, 255, 1))
        drop-shadow(0 0 6px rgba(255, 255, 255, 0.85));
}

/* Only one `filter` lands on a piece, so the rare square that is both - a strong
   lineman who has come up with the ball - keeps the yellow rim, held at the
   bright end of the pulse. "Cannot be pushed" is the rarer fact and the one the
   player needs before clicking. */
.cell--strong.cell--carrier .icon {
    filter:
        drop-shadow(1.5px 0 0 #fde047)
        drop-shadow(-1.5px 0 0 #fde047)
        drop-shadow(0 1.5px 0 #fde047)
        drop-shadow(0 -1.5px 0 #fde047)
        drop-shadow(2.5px 0 0 #0a0a0a)
        drop-shadow(-2.5px 0 0 #0a0a0a)
        drop-shadow(0 2.5px 0 #0a0a0a)
        drop-shadow(0 -2.5px 0 #0a0a0a)
        drop-shadow(0 0 3px rgba(253, 224, 71, 1))
        drop-shadow(0 0 7px rgba(234, 179, 8, 0.7));
}

/* A loose ball: no player in the square, just the football waiting to be
   recovered. */
.cell--loose {
    background-color: rgba(255, 255, 255, 0.14);
}

.icon--loose {
    background: none;
    box-shadow: none;
}

.icon--loose::before {
    display: none;
}

.icon--loose::after {
    content: '';
    position: absolute;
    inset: 4%;
    background: url('../img/football.svg') center / contain no-repeat;
    transform: rotate(-20deg);
    filter:
        drop-shadow(0 0 2px rgba(255, 255, 255, 1))
        drop-shadow(0 1px 1px rgba(0, 0, 0, 0.8));
    animation: loose-pulse 1.4s ease-in-out infinite;
}

@keyframes loose-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
}

@media (prefers-reduced-motion: reduce) {
    .icon--loose::after,
    .cell--strong .icon {
        animation: none;
    }
}

.cell--selected {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* The hover cost badge: points to reach the square, or -shortfall in red. */
.cell__cost {
    position: absolute;
    top: 1px;
    left: 2px;
    z-index: 3;
    color: #ffffff;
    font-size: 0.55rem;
    font-weight: 800;
    line-height: 1;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
    pointer-events: none;
}

.cell__cost:empty {
    display: none;
}

.cell__cost--short {
    color: #f87171;
}

/* ---------- readout ---------- */

/*
 * The readout is centred and only as tall as what it has to say. The two message
 * lines are hidden while empty, so at rest the card holds the down and distance
 * alone instead of two blank rows with the text stranded in the bottom corner.
 */
.readout {
    text-align: center;
}

.readout__info {
    margin: 0;
    font-size: 1.2rem;
    font-weight: 700;
}

.readout__note {
    margin: 0.2em 0;
    color: var(--muted);
    font-size: 0.85rem;
}

.readout__info:empty,
.readout__note:empty {
    display: none;
}

.readout__down {
    margin: 0;
    color: var(--accent);
    font-size: 1.5rem;
    font-weight: 800;
    letter-spacing: 0.01em;
}

/* ---------- play cards ---------- */

.plays {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
    gap: 10px;
}

.play {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 8px;
    background: #0a1a11;
    border: 1px solid var(--panel-line);
    border-radius: 10px;
    color: inherit;
    font: inherit;
    cursor: pointer;
    transition: border-color 120ms ease, transform 120ms ease;
}

.play:hover {
    border-color: var(--accent);
    transform: translateY(-1px);
}

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

.play__grid {
    display: grid;
    grid-template-columns: repeat(12, 9px);
    grid-auto-rows: 9px;
    gap: 1px;
    padding: 3px;
    background: #14532d;
    border-radius: 4px;
}

.play__square {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: transparent;
}

/* Miniatures of the board helmets: one colour per position. */
.play__square--qb {
    background: #2563eb;
}

.play__square--rb {
    background: #16a34a;
}

.play__square--wr {
    background: #06b6d4;
}

.play__square--ol {
    background: #64748b;
}

.play__square--dl {
    background: #dc2626;
}

.play__square--db {
    background: #f97316;
}

.play__label {
    color: var(--muted);
    font-size: 0.72rem;
    font-weight: 600;
}

/* ---------- dice + actions ---------- */

/*
 * The dice panel: two halves spaced evenly from each other and from the edges,
 * the dice centred under their heading, and the banked total under the dice.
 */
.dice {
    display: flex;
    justify-content: space-evenly;
    align-items: flex-start;
    margin-bottom: 12px;
}

.dice__side {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-align: center;
}

.dice__side .panel__title {
    margin: 0;
}

.dice__images {
    display: flex;
    justify-content: center;
    gap: 8px;
    min-height: 40px;
}

/* A die is a 3 x 3 grid; only the face's pips are painted. */
.die {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(3, 1fr);
    gap: 3px;
    width: 40px;
    height: 40px;
    padding: 6px;
    border-radius: 9px;
    background: #f8fafc;
    border: 2px solid #94a3b8;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.45);
    transition: border-color 140ms ease, box-shadow 140ms ease, opacity 140ms ease;
}

.die__pip {
    border-radius: 50%;
}

.die__pip--on {
    background: #0f172a;
}

/* The high die is the one the rule compares, so it lights up. */
.die--high {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px rgba(251, 191, 36, 0.35), 0 0 14px rgba(251, 191, 36, 0.45);
}

/* Winning the comparison is brighter still; losing it is greyed out. */
.die--won {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px rgba(251, 191, 36, 0.55), 0 0 20px rgba(251, 191, 36, 0.7);
}

.die--lost {
    opacity: 0.4;
    border-color: #475569;
    box-shadow: none;
}

.dice__points {
    margin: 0;
    color: var(--ink);
    font-size: 1.4rem;
    font-weight: 700;
    line-height: 1;
}

.actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
}

.actions .button--primary {
    grid-column: 1 / -1;
}

/* ---------- legend ---------- */

.legend ul {
    display: grid;
    gap: 4px;
    margin: 0;
    padding: 0;
    color: var(--muted);
    font-size: 0.78rem;
    list-style: none;
}

.legend li {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* The key draws the same .icon as the board, at a fixed readable size. */
.legend .icon {
    --icon-size: 22px;
    flex: none;
}

/* The one-line note above the samples, explaining the three cues. */
.legend__note {
    margin: 0 0 8px;
}

/* The strong-lineman sample: the shared lineman helmet with the yellow rim from
 * `.cell--strong .icon` above, held still so the legend does not flicker. */
.legend__strong {
    display: inline-flex;
    flex: none;
    padding: 0 3px;
}

/* ---------- rules dialog ---------- */

.rules {
    width: min(680px, calc(100% - 32px));
    max-height: 86vh;
    padding: 0;
    background: var(--panel);
    border: 1px solid var(--panel-line);
    border-radius: 16px;
    color: var(--ink);
}

.rules::backdrop {
    background: rgba(2, 8, 5, 0.72);
}

.rules__inner {
    display: flex;
    flex-direction: column;
    max-height: 86vh;
}

.rules__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 16px 20px;
    border-bottom: 1px solid var(--panel-line);
}

.rules__header h2 {
    margin: 0;
    font-size: 1.1rem;
}

.rules__body {
    padding: 4px 20px 20px;
    overflow-y: auto;
}

.rules__body h3 {
    margin: 1.1em 0 0.3em;
    color: var(--accent);
    font-size: 0.72rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
}

.rules__body p,
.rules__body li {
    color: #d5e8dd;
    font-size: 0.88rem;
    line-height: 1.55;
}

.rules__body ul,
.rules__body ol {
    margin: 0;
    padding-left: 1.2em;
}

/* The rules carry two tables: the pass odds and the cost/risk list. */
.rules__body table {
    width: 100%;
    margin: 10px 0 6px;
    border-collapse: collapse;
    font-size: 0.78rem;
}

.rules__body caption {
    padding-bottom: 5px;
    color: var(--muted);
    font-size: 0.75rem;
    text-align: left;
}

.rules__body th,
.rules__body td {
    padding: 4px 6px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.07);
    text-align: center;
    vertical-align: top;
    white-space: nowrap;
}

.rules__body thead th {
    color: var(--muted);
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.rules__body tbody th {
    color: #d5e8dd;
    font-weight: 600;
    text-align: left;
    white-space: normal;
}

/* The cost column and the risk column are prose, so they wrap. */
.rules__body .rules__costs td:nth-child(2),
.rules__body .rules__costs td:nth-child(3) {
    text-align: left;
    white-space: normal;
    color: #d5e8dd;
    line-height: 1.45;
}

/* Pass odds: green is a comfortable throw, amber is a coin flip, red a prayer. */
.rules__body .odds {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
}

.rules__body .odds--auto {
    color: var(--accent);
    text-align: center;
}

.rules__body .odds--good {
    color: #7ee0a6;
}

.rules__body .odds--lean {
    color: #e8d27a;
}

.rules__body .odds--bad {
    color: #e89a8a;
}

.rules__note {
    margin-top: 1.4em;
    padding-top: 12px;
    border-top: 1px solid var(--panel-line);
    color: var(--muted) !important;
    font-style: italic;
}

.noscript {
    margin: 40px auto;
    max-width: 40em;
    color: var(--accent);
    text-align: center;
}

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

/*
 * Below 960 px the HUD goes under the board instead of beside it. That is not
 * only about the window being small: a landscape board needs 30 cells of width,
 * and beside a 460 px column there is not enough of it left to keep the cells at
 * their 14 px floor until the window is roughly 960 px wide. Body of the
 * breakpoint lives in measureFit() in ui.js - change both together.
 */
@media (max-width: 959px) {
    :root {
        --sideline: 4px;
    }

    .game {
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: auto auto auto;
        grid-template-areas:
            "header"
            "field"
            "sidebar";
        gap: 12px;
        padding: 10px 10px 24px;
        min-height: 0;
    }

    .field-wrap {
        width: 100%;
    }

    .sidebar {
        width: 100%;
    }

    .scoreboard {
        /* A phone has room to wrap, so it does: nothing is squeezed there. */
        flex-wrap: wrap;
        gap: 6px;
        padding: 6px 8px;
    }

    .score {
        flex: 0 0 auto;
    }

    .score__name {
        width: 10ch;
        min-width: 0;
        flex: 0 0 auto;
    }

    .possession {
        padding: 0 8px;
        font-size: 0.85rem;
    }

    .possession__ball {
        width: 20px;
    }

    /* Two special chips fit a phone; they wrap rather than overflow. */
    .specials {
        gap: 4px;
    }

    .specials__chip {
        padding: 2px 7px 2px 6px;
        font-size: 0.62rem;
    }
}



/* ---------------------------------------------------------------------------
 * Online chrome: the room status line, the lobby and the question modal.
 * The board, dice and panels are untouched; only the additions below belong
 * to the online version.
 * ------------------------------------------------------------------------- */

.online-status {
    margin: 0 0 12px;
    padding: 10px 12px;
    border: 1px solid var(--panel-line);
    border-radius: 10px;
    background: var(--panel);
    color: var(--ink);
    font-size: 0.9rem;
    line-height: 1.45;
}

.online-status--error {
    border-color: #b91c1c;
    background: rgba(127, 29, 29, 0.35);
    color: #fecaca;
}

.lobby,
.prompt {
    max-width: min(440px, calc(100vw - 24px));
    width: calc(100% - 24px);
    padding: 0;
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
    background: var(--panel);
    color: var(--ink);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}

.lobby::backdrop,
.prompt::backdrop {
    background: rgba(2, 10, 6, 0.72);
    backdrop-filter: blur(2px);
}

.lobby__inner,
.prompt__inner {
    display: grid;
    gap: 14px;
    padding: 20px;
}

.lobby__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.lobby__header h2 {
    margin: 0;
    font-size: 1.15rem;
}

.lobby__hint {
    margin: 0;
    color: var(--muted);
    font-size: 0.9rem;
    line-height: 1.5;
}

.lobby__field {
    display: grid;
    gap: 6px;
    min-width: 0;
    font-size: 0.85rem;
    color: var(--muted);
}

.lobby__field input {
    width: 100%;
    min-width: 0;
    padding: 9px 11px;
    border: 1px solid var(--panel-line);
    border-radius: 8px;
    background: var(--panel-raised);
    color: var(--ink);
    font: inherit;
}

.lobby__field select {
    width: 100%;
    min-width: 0;
    padding: 9px 11px;
    border: 1px solid var(--panel-line);
    border-radius: 8px;
    background: var(--panel-raised);
    color: var(--ink);
    font: inherit;
    max-width: 100%;
}

.lobby__row {
    display: flex;
    gap: 10px;
    align-items: center;
}

.lobby__row .button {
    flex: 1;
}

.lobby__row--join input {
    width: 7ch;
    padding: 9px 11px;
    border: 1px solid var(--panel-line);
    border-radius: 8px;
    background: var(--panel-raised);
    color: var(--ink);
    font: inherit;
    letter-spacing: 0.2em;
    text-transform: uppercase;
}

.lobby__error {
    min-height: 1.2em;
    margin: 0;
    color: #fca5a5;
    font-size: 0.85rem;
}

.prompt__message {
    margin: 0;
    font-size: 1rem;
    line-height: 1.5;
}

.prompt__answers {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
}

.prompt__answers .button {
    flex: 1;
    min-width: 110px;
}

.prompt__number {
    width: 8ch;
    padding: 9px 11px;
    border: 1px solid var(--panel-line);
    border-radius: 8px;
    background: var(--panel-raised);
    color: var(--ink);
    font: inherit;
}

/* ---------------------------------------------------------------------------
 * The mode chooser: the first thing the page asks, before either game boots.
 * Reuses the lobby's dialog chrome so the two never look like different apps.
 * ------------------------------------------------------------------------- */

.mode {
    max-width: min(400px, calc(100vw - 24px));
    width: calc(100% - 24px);
    padding: 0;
    border: 1px solid var(--panel-line);
    border-radius: var(--radius);
    background: var(--panel);
    color: var(--ink);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}

.mode::backdrop {
    background: rgba(2, 10, 6, 0.72);
    backdrop-filter: blur(2px);
}

.mode__inner {
    display: grid;
    gap: 10px;
    padding: 22px;
    text-align: center;
}

.mode__title {
    margin: 0;
    font-size: 1.35rem;
}

.mode__hint {
    margin: 0 0 6px;
    color: var(--muted);
    font-size: 0.9rem;
}

.mode__choice {
    width: 100%;
    padding: 0.7rem 1rem;
    font-size: 0.95rem;
    white-space: normal;
}

/* The three new-game buttons, stacked under the resume box. */
.mode__choices {
    display: grid;
    gap: 8px;
}

/*
 * Continue game: boxed off from the modes below it, because it is a different
 * kind of answer - "the game you already have" rather than "a new one". The box
 * is the accent at low alpha so it reads as the recommendation without shouting
 * over the board behind it.
 */
.mode__resume {
    display: grid;
    gap: 6px;
    padding: 12px;
    border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
    border-radius: var(--radius);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.mode__resume[hidden] {
    display: none;
}

.mode__resume-button {
    display: grid;
    gap: 2px;
}

.mode__resume-stamp {
    font-size: 0.78rem;
    font-weight: 500;
    opacity: 0.85;
}

.mode__resume-note {
    margin: 0;
    color: var(--muted);
    font-size: 0.78rem;
}

/* "or start a new one" - the hinge between the boxed resume and the modes. */
.mode__or {
    margin: 2px 0 -2px;
    color: var(--muted);
    font-size: 0.78rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.mode__or[hidden] {
    display: none;
}

/* The readout's "whose move" line, under the down and distance. */
.readout__turn {
    margin: 0.35em 0 0;
    color: var(--muted);
    font-size: 0.9rem;
}

/* The last thing that happened, in words. Persistent, so it survives renders
 * that are not moves. */
.readout__last {
    margin: 0.35em 0 0;
    color: var(--ink);
    font-size: 0.85rem;
    font-weight: 600;
}

.readout__last:empty {
    display: none;
}

/* A clone of a piece (or the ball) sliding from one square to another. */
.flight {
    border-radius: 6px;
    opacity: 0.92;
    filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.45));
    will-change: transform;
}

.flight--ball {
    width: 18px !important;
    height: 18px !important;
    object-fit: contain;
}

/* The local setup dialog: a target score and two field-goal choices. */
.lobby__choices {
    margin: 0;
    padding: 0;
    border: 0;
    display: grid;
    gap: 8px;
}

.lobby__choices legend {
    padding: 0;
    color: var(--muted);
    font-size: 0.85rem;
}

.lobby__choices label {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.9rem;
    line-height: 1.4;
}

.lobby__choices input {
    flex: none;
    accent-color: var(--accent);
}

/*
 * Buttons that cannot be used right now: the kicks from a defence seat, a kick
 * out of range, and a side that has already ended its turn this round.
 */
.button:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.button:disabled:hover {
    border-color: var(--panel-line);
}

.button--done {
    opacity: 0.55;
    text-decoration: line-through;
}

.button--done:hover {
    border-color: var(--panel-line);
}

/* The finished side's dice and banked total fade with its button. */
.dice__side--done .die,
.dice__side--done .dice__points {
    opacity: 0.3;
}

/* Formation editor (admin): a 5 x 12 grid of click-to-cycle squares. */
.editor {
    width: min(96vw, 560px);
}

.editor__row {
    display: flex;
    gap: 12px;
    align-items: end;
    flex-wrap: wrap;
}

.editor__grid {
    display: grid;
    grid-template-columns: repeat(12, 30px);
    gap: 2px;
    margin-top: 12px;
}

.editor__cell {
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1px solid var(--panel-line);
    border-radius: 4px;
    background: #0a1a11;
    color: #e8f5e9;
    font: 700 13px/1 system-ui, sans-serif;
    cursor: pointer;
}

.editor__cell--filled {
    background: #14532d;
    border-color: #22c55e;
}

.editor__problems {
    min-height: 1.4em;
    margin: 8px 0 0;
    color: #fbbf24;
    font-size: 12px;
}

.editor__actions {
    display: flex;
    gap: 8px;
    margin-top: 10px;
}

.editor__list {
    list-style: none;
    padding: 0;
    margin: 12px 0 0;
    display: grid;
    gap: 6px;
    font-size: 13px;
}

.editor__list li {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
    border-top: 1px solid var(--panel-line);
    padding-top: 6px;
}

/* Test mode (admin) */
.test {
    border-top: 1px solid var(--panel-line);
    margin-top: 12px;
    padding-top: 10px;
}

.test__hint {
    margin: 0 0 8px;
    font-size: 12px;
    opacity: 0.85;
}

.test__row {
    display: flex;
    gap: 16px;
    flex-wrap: wrap;
}

.test__row label {
    display: grid;
    gap: 4px;
    font-size: 12px;
}

.test-menu {
    position: fixed;
    z-index: 60;
    display: grid;
    gap: 4px;
    padding: 6px;
    background: #0a1a11;
    border: 1px solid var(--accent);
    border-radius: 8px;
    box-shadow: 0 10px 24px rgba(0, 0, 0, 0.5);
}

.test-menu button {
    text-align: left;
    padding: 4px 8px;
    border: 0;
    border-radius: 4px;
    background: transparent;
    color: inherit;
    font: inherit;
    cursor: pointer;
}

.test-menu button:hover {
    background: rgba(255, 255, 255, 0.10);
}

/* A fumble scramble hides the ball; a faint dashed square would give it away, so
   the cells stay untouched. */
