/* =====================================================================
 * RangeRead — the play screen.
 *
 * THE ONE IDEA IN THIS FILE
 * -------------------------
 * The play screen is not responsive. It is a single fixed design, drawn on a
 * canvas of exactly 402 x 874 — the logical resolution of an iPhone 17 Pro —
 * and then scaled as a whole to fit whatever viewport it lands in.
 *
 * Every earlier attempt at this screen was responsive: percentages, max-width,
 * flex:1, breakpoints. A responsive layout has infinitely many states, so
 * approving one screenshot approves nothing about the others — which is why
 * widening the window "looked the same but wrong". Only the width changed;
 * type, cards and spacing stayed at phone size, so every proportion drifted.
 *
 * Here there is exactly one state. On a phone it fills the screen; on a
 * desktop it is the same screen, centred, with room around it. Wordle's model.
 *
 * HOW TO AUTHOR IN IT
 * -------------------
 * --u is the size of one design pixel. Write every length as a multiple of it:
 *
 *     height: calc(46 * var(--u));      <- 46 design px
 *
 * Never write a raw px length below. A raw px would not scale with the rest and
 * would break the proportions at every size except the one you tested.
 *
 * THE CONTAINERS
 * --------------
 *   #stage                     the phone. 402 x 874 design px. Never scrolls.
 *     .app-bar                 46   brand
 *     .street-tabs             38   one tab per street reached
 *     #table-felt             ~339  seats, bet zones, board panel
 *     #action-feed            ~92   this street's action, capped and scrollable
 *     .replay-btn              32   replay this street
 *     #guess-history-zone      1fr  grows into the leftover space
 *     #mobile-action-bar       79   the single primary action, pinned
 *     #selection-grid-zone     absolutely positioned sheet, slides up over all
 *
 * Only #guess-history-zone and the feed's inner scroller may change size. Every
 * other container has a height the content cannot argue with, so nothing above
 * the button can ever push it.
 *
 * Everything is scoped under #stage. main.css still carries older rules for
 * some of these class names; an ID beats any number of classes, so this file
 * wins regardless of what is left over there.
 * ===================================================================== */

:root {
    --design-w: 402;
    /* 800, not the 874 of an iPhone's full screen: a browser never gives you
       the full screen — Safari's toolbars take 100-160px — so a 874-tall canvas
       was always height-bound on a phone, shrinking everything to ~80% and
       leaving grey gutters down both sides. The content was compacted to fit
       this, which also closes the empty band above the guess history. */
    --design-h: 784;

    /* The device's unsafe edges: the home indicator, the Dynamic Island, and
       the rounded corners. index.html asks for viewport-fit=cover, so the page
       extends underneath all of them and it is on us to keep clear. Held in
       variables rather than used inline so a test can substitute values —
       env() cannot be set from script. */
    --safe-top: env(safe-area-inset-top, 0px);
    --safe-right: env(safe-area-inset-right, 0px);
    --safe-bottom: env(safe-area-inset-bottom, 0px);
    --safe-left: env(safe-area-inset-left, 0px);

    /* One design pixel. Whichever axis runs out first decides the scale, so the
       whole phone is always visible without scrolling. The canvas is fitted to
       the SAFE box, not the viewport, so the action bar never ends up under the
       home indicator — the design simply scales down to clear it, which keeps
       every proportion exactly as approved. Clamped so it never gets
       microscopic on a short window or cartoonish on a 27" display. Declared on
       :root, not on the stage, so the intro and the overlays scale with it too. */
    /* The viewport, as the script measures it from window.visualViewport --
       what is actually on screen. 100svh is the fallback and is what these
       start as, but on iOS with Safari's floating bottom bar svh reports the
       space BEHIND the bar too, and the canvas came out taller than the
       visible page: the picker's bottom row and the submit button sat under
       the browser chrome. */
    --vvw: 100vw;
    --vvh: 100svh;
    /* The floor is where "fit the screen" gives up. 0.74 did not fit an
       iPhone SE with Safari's toolbars showing (548px visible; the canvas
       came out 580) and put the submit button under the browser chrome.
       0.68 fits 533px and up, which is every phone in current use. */
    --u: clamp(0.68px,
               /* These two must match --design-w / --design-h above. They are
                  literals because a var() used as a divisor does not resolve
                  correctly here. */
               min((var(--vvw) - var(--safe-left) - var(--safe-right)) / 402,
                   (var(--vvh) - var(--safe-top) - var(--safe-bottom)) / 784),
               1.30px);
}

/* ------------------------------------------------------- the page around it */

body.game-active {
    box-sizing: border-box;
    -webkit-text-size-adjust: 100%;
    margin: 0;
    /* Sit the whole column inside the safe box. main.css put this on body, and
       the reset here was overriding it. */
    padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left);
    /* svh, not dvh. dvh tracks Safari's collapsing toolbar, so the page grew
       as you scrolled and there was always a little more to scroll to -- the
       thing that made this feel like a document. svh is the viewport with the
       toolbar SHOWING, so the canvas is sized for the smallest the screen ever
       gets and nothing ever scrolls. When the toolbar hides you simply get a
       band of page colour, which is invisible now that it matches. */
    min-height: 100svh;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    /* The canvas is a fixed 402x800 scaled to fit, so on a phone there is
       always a band left over -- above the header, below the action bar, and a
       few px down each side. Painting the page the same white as the canvas
       makes that band invisible: the app reads edge to edge instead of as a
       white card floating on grey. Both the header and the action bar are
       already --surface, so the seam closes top and bottom without either of
       them changing. The grey comes back on a desktop-sized viewport (below),
       where wide gutters are the point rather than a seam. */
    background: var(--surface);
    /* --u has a readable floor, so on a window shorter than about 650px the
       phone no longer fits. Scrolling to it beats shrinking the type past
       legibility; above that floor there is nothing to scroll. */
    overflow-x: hidden;
    overflow-y: auto;
}
/* auto margins centre the stage when there is room and clamp to the top when
   there is not, which align-items:center cannot do without clipping. */
body.game-active #stage.game-container { margin: auto; }

/* Wide viewport: there is no seam left to hide, only a column in a lot of
   empty space, so give it back its ground and let it sit on the page as a
   deliberate object. This changes nothing about the layout -- the canvas and
   every length inside it are identical at every width. */
@media (min-width: 700px) {
    body.game-active { background: var(--surface-2); }
    body.game-active #stage.game-container {
        border-radius: calc(14 * var(--u));
        box-shadow: 0 calc(2 * var(--u)) calc(24 * var(--u)) rgba(var(--shadow-ink), .10),
                    0 0 0 1px var(--rule);
    }
}

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

/* A finger is the same size whatever --u is, so these expansions are in raw
   pixels, not design units. The pseudo-element enlarges only what can be
   TAPPED — nothing moves and nothing is repainted, so the design is untouched
   while every one of these controls reaches the 44px minimum. */
#stage .street-tab,
#stage .replay-btn,
#stage .btn-icon,
#stage .hdr-btn,
#stage .sheet-close { position: relative; }

#stage .street-tab::after,
#stage .replay-btn::after,
#stage .btn-icon::after,
#stage .hdr-btn::after,
#stage .sheet-close::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    min-width: 44px;
    min-height: 44px;
    width: 100%;
    height: 100%;
    transform: translate(-50%, -50%);
}

/* Kills the ~300ms wait Safari spends deciding whether a tap is a double-tap
   zoom. Every control here is a single tap. */
#stage button,
#stage .card-wrapper,
#modal-overlay button,
#modal-overlay a,
#help-overlay button { touch-action: manipulation; }

/* Scrolling a panel to its end must not start dragging the page behind it. */
#stage #action-feed,
#stage #guess-history-zone,
#stage #card-grid-wrapper,
#modal-overlay .modal-content,
#help-overlay .modal-content { overscroll-behavior: contain; }

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

/* main.css never set a global box-sizing, so widths and heights here were
   content-box and every padding was added on top of the size asked for. */
#stage, #stage *, #stage *::before, #stage *::after,
#modal-overlay, #modal-overlay *, #help-overlay, #help-overlay *,
#settings-overlay, #settings-overlay * { box-sizing: border-box; }

#stage.game-container {
    position: relative;
    overflow: hidden;
    flex: none;
    width: calc(var(--design-w) * var(--u));
    height: calc(var(--design-h) * var(--u));
    max-width: none;
    margin: 0;
    /* main.css pads the bottom to clear its old fixed action bar; the bar is
       inside the stage now, so that padding is 78px of dead space. */
    padding: 0;
    gap: 0;
    /* NO `display` here. main.css keeps .game-container hidden until Play is
       pressed; an unconditional display on an ID outranks that and renders the
       whole game underneath the intro screen. It is turned on below, once
       body.game-active is set. */
    flex-direction: column;
    background: var(--surface);
    color: var(--ink);
    text-align: left;
    font-size: calc(15 * var(--u));
    line-height: var(--lh-snug);
    -webkit-font-smoothing: antialiased;
}

body.game-active #stage.game-container { display: flex; }

/* Off a phone there is room around the stage, so give it an edge. On a phone it
   fills the viewport and a radius would just clip content against black. */
@media (min-width: 480px) and (min-height: 780px) {
    #stage.game-container {
        border-radius: calc(22 * var(--u));
        box-shadow: 0 calc(12 * var(--u)) calc(40 * var(--u)) rgba(var(--shadow-ink), .13);
    }
}

/* --------------------------------------------------------------- app bar */

#stage > header {
    flex: none;
    /* 46u bought nothing but air around a nine-character word, and on a canvas
       that is scaled to fit, every unit here is taken from the play area and
       widens the gutters down both sides. */
    height: calc(42 * var(--u));
    /* Three columns, not a flex row: the outer two take an equal share whatever
       they hold, so the wordmark is centred on the SCREEN rather than centred
       in whatever space the buttons happen to leave over. Add a fourth control
       one day and the title does not drift. */
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    padding: 0 calc(8 * var(--u));
    border-bottom: 1px solid var(--rule);
}
#stage > header .hdr-right {
    display: flex;
    align-items: center;
    justify-self: end;
    gap: calc(2 * var(--u));
}
#stage .hdr-btn {
    display: grid;
    place-items: center;
    width: calc(34 * var(--u));
    height: calc(34 * var(--u));
    padding: 0;
    border: 0;
    background: none;
    color: var(--ink-soft);
    border-radius: var(--r-md);
    cursor: pointer;
}
#stage .hdr-btn:active { background: var(--surface-2); }
#stage .hdr-btn svg {
    width: calc(24 * var(--u));
    height: calc(24 * var(--u));
    /* Stroked, so one rule sets every icon's weight the same way. */
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
}
#stage .hdr-btn svg .fill { fill: currentColor; stroke: none; }
#stage > header h1 {
    margin: 0;
    font-size: calc(17 * var(--u));
    font-weight: var(--fw-heavy);
    /* Centred and slightly open, so it reads as a wordmark rather than as a
       label that happens to sit in the top left corner. */
    letter-spacing: calc(0.2 * var(--u));
    color: var(--ink);
}

#stage .game-zone {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    gap: 0;                 /* main.css lays this out as a 20px-gap grid */
    padding: 0;
    /* main.css sets align-items:start above 1100px, which stopped every
       section stretching to the column and left them hugging the left edge. */
    align-items: stretch;
}

/* ---------------------------------------------------------------- tabs */

#stage .street-tabs {
    flex: none;
    height: calc(38 * var(--u));
    display: flex;
    align-items: stretch;
    gap: 0;
    padding: 0 calc(6 * var(--u));
    border-bottom: 1px solid var(--rule);
}

/* Equal shares of the row. Packed left, four tabs used about 60% of the width
   and left a bald patch on the right that got worse the further you got into
   the hand -- and the underline, which is the whole point of the tab, ended up
   a stub under a short word instead of marking out its share of the street. */
#stage .street-tab {
    flex: 1 1 0;
    min-width: 0;
    padding: 0 calc(4 * var(--u));
    background: none;
    border: 0;
    font: inherit;
    font-size: calc(10 * var(--u));
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--ink-faint);
    cursor: pointer;
    white-space: nowrap;
}
#stage .street-tab[aria-selected="true"] {
    color: var(--ink);
    box-shadow: inset 0 calc(-2.5 * var(--u)) 0 var(--ink);
}

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

/* One card element serves the board, the seats, the picker, the slots and the
   guess tiles. Each context sets the four variables; nothing else changes. */
#stage .card {
    --cw: calc(41 * var(--u));
    --ch: calc(56 * var(--u));
    --crank: calc(18 * var(--u));
    --csuit: calc(13 * var(--u));

    box-sizing: border-box;
    width: var(--cw);
    height: var(--ch);
    flex: none;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(1 * var(--u));
    background: var(--card-bg);
    border: calc(1.5 * var(--u)) solid var(--card-line);
    border-radius: calc(6 * var(--u));
    font-weight: var(--fw-heavy);
    line-height: 1;
    color: var(--card-ink);
    overflow: hidden;
}
#stage .card-rank-display { font-size: var(--crank); line-height: 1; }
#stage .card-symbol-display { font-size: var(--csuit); line-height: 1; }
#stage .card-suit-red { color: var(--card-red); }
#stage .card-suit-black { color: var(--card-ink); }

#stage .card.card-back {
    background: var(--card-back-bg);
    border-style: dashed;
    border-color: var(--card-back-line);
}
#stage .card-placeholder {
    box-sizing: border-box;
    width: calc(41 * var(--u));
    height: calc(56 * var(--u));
    flex: none;
    border: calc(1.5 * var(--u)) dashed var(--rule);
    border-radius: calc(6 * var(--u));
    background: transparent;
}

/* ------------------------------------------------------- table and seats */

#stage #table-felt {
    flex: none;
    padding: calc(10 * var(--u)) calc(16 * var(--u)) 0;
    display: flex;
    flex-direction: column;
    gap: calc(12 * var(--u));   /* the bet zones used to do this spacing */
}

#stage .player-area { display: flex; flex-direction: column; gap: calc(6 * var(--u)); }

#stage .seat-top {
    display: flex;
    align-items: baseline;
    /* Beside the name, not pinned to the far edge: out there it read as a
       separate column of numbers rather than as this player's stack. It keeps
       the larger size — legibility was the point, not the position. */
    justify-content: flex-start;
    gap: calc(10 * var(--u));
    height: calc(17 * var(--u));
}
#stage .player-label {
    font-size: calc(13.5 * var(--u));
    font-weight: var(--fw-bold);
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
#stage .stack-info { display: flex; align-items: center; flex: none; }
#stage .stack-chips { display: none; }   /* the chip graphics retired with the felt */
#stage .stack-pill {
    font-family: var(--font-mono);
    font-size: calc(13 * var(--u));
    font-weight: 600;
    color: var(--ink-soft);
    background: none;
    border: 0;
    padding: 0;
}

#stage .player-hand-area {
    display: flex;
    align-items: center;
    gap: calc(7 * var(--u));
    height: calc(56 * var(--u));
}
/* The badge and amount sit on the seat's own row, to the right of the hole
   cards and centred in what is left of it. On a line of their own they pushed
   the cards a long way off the board and cost 88u of height for something that
   is empty most of the time. */
#stage .bet-zone {
    flex: 1 1 auto;
    min-width: 0;
    height: 100%;
    margin: 0;
    padding-left: calc(4 * var(--u));
}
#stage #hero-cards,
#stage #villain-cards { display: flex; gap: calc(5 * var(--u)); }

#stage .button-spacer { width: calc(18 * var(--u)); flex: none; }
#stage .dealer-button {
    width: calc(18 * var(--u));
    height: calc(18 * var(--u));
    border-radius: var(--r-pill);
    border: 1px solid var(--rule);
    color: var(--ink-faint);
    font-size: calc(9 * var(--u));
    font-weight: var(--fw-bold);
    display: grid;
    place-items: center;
}
#stage .dealer-button.hidden { display: none; }

/* ---- the bet zones the street animation plays into ---- */

#stage .bet-zone {
    display: flex;
    align-items: center;
    /* Centred in what the cards leave rather than shoved up against them:
       there is half a row of space here and the badge was using none of it. */
    justify-content: center;
    gap: calc(6 * var(--u));
    padding-right: calc(10 * var(--u));
}
#stage .bet-zone .chip-stack,
#stage .bet-zone .chip { display: none; }

/* The badge carries the kind of play in its colour: grey for a check, a call
   or a posted blind, red for a fold, green for a bet or raise, purple once
   the pot is re-raised. */
#stage .action-badge {
    display: inline-flex;
    align-items: center;
    padding: calc(5 * var(--u)) calc(11 * var(--u));
    border: 0;
    border-radius: var(--r-pill);
    font-size: calc(10.5 * var(--u));
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-label);
    line-height: 1;
    text-transform: uppercase;
    color: var(--on-badge);
    background: var(--act-neutral);
}
#stage .action-check,
#stage .action-call { background: var(--act-passive); }
#stage .action-fold { background: var(--act-fold); }
#stage .action-bet { background: var(--act-aggro); }
/* A raise and every re-raise on top of it are the same escalation, so they
   share a colour; only the opening bet is green. */
#stage .action-raise,
#stage .action-reraise,
#stage .action-allin { background: var(--act-reraise); }
/* The pill colour already says all-in; the extra glyph just crowded it. */
#stage .allin-tri { display: none; }

#stage .bet-amount {
    font-family: var(--font-mono);
    font-size: calc(13 * var(--u));
    font-weight: 700;
    line-height: 1;
    color: var(--ink);
    background: var(--surface-2);
    border-radius: var(--r-pill);
    padding: calc(6 * var(--u)) calc(10 * var(--u));
}

/* ---- the board panel: the only shaded surface on the table ---- */

#stage #board-area {
    background: var(--surface-2);
    border-radius: calc(14 * var(--u));
    padding: calc(11 * var(--u)) calc(10 * var(--u));
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(9 * var(--u));
    flex: none;
}
#stage #board-cards { display: flex; gap: calc(5 * var(--u)); }
#stage #board-area .card { box-shadow: 0 calc(1 * var(--u)) calc(3 * var(--u)) rgba(var(--shadow-ink), .10); }
#stage #board-area .card-placeholder { border-color: var(--placeholder-line); }

#stage #pot-area {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(3 * var(--u));
    /* Tall enough for its own contents: the label and the figure below set
       line-height explicitly, so this is 9 + 3 + 17 with room to spare. Fixed
       so the panel does not resize when the pot appears. */
    height: calc(32 * var(--u));
}
#stage #pot-chips { display: none; }
#stage .pot-label {
    font-size: calc(9 * var(--u));
    font-weight: var(--fw-bold);
    line-height: 1;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--ink-faint);
}
#stage #pot-size {
    font-family: var(--font-mono);
    font-size: calc(17 * var(--u));
    font-weight: 700;
    line-height: 1;
    color: var(--ink);
}

/* ------------------------------------------------------------ action feed */

#stage #action-feed {
    /* Starts as an empty panel and grows as the animation deals plays into it.
       No max-height: the guess history below is pinned to the bottom, so the
       feed grows into whatever space is free and then shrinks — and scrolls —
       rather than ever pushing into it. */
    /* This is the section that gives way when the column runs short. The shrink
       factor is enormous next to the guess history's 1, so effectively the
       whole shortfall lands here first; below its natural height it scrolls,
       held to the newest play. The floor keeps one play readable. */
    flex: 0 100 auto;
    /* Three lines, not one. This is still the section that yields -- the shrink
       factor next to the guess history's 1 says so -- but yielding all the way
       down to a single play was the bug: on a busy river the one thing you are
       actually reading got squeezed to nothing while the guess history below it
       kept every pixel of its natural height. A floor makes the history give
       too, and the history can scroll; three plays cannot. */
    /* A feed line measures 30.1u, so this is exactly three of them. Measured,
       not guessed: the previous floor of 70u looked like three and was 2.35,
       which is how the river ended up showing a single play. */
    min-height: calc(92 * var(--u));
    /* No scroll-behavior here on purpose: dealing and re-rendering jump, and
       only the replay scrolls smoothly, which those paths ask for explicitly. */
    /* A long street scrolls, held to the newest play, so the cut lands at the
       top where a partially shown older line reads as history. */
    overflow-y: auto;
    margin: calc(10 * var(--u)) calc(16 * var(--u)) 0;
    padding: calc(4 * var(--u)) calc(6 * var(--u));
    background: var(--surface-2);
    border-radius: calc(14 * var(--u));
    display: flex;
    flex-direction: column;
    gap: 0;
    scrollbar-width: thin;
}
/* Visible, and present only when there is somewhere to scroll. Hidden, the
   only clue that a street had more plays was that the last line was cut in
   half. -webkit-appearance:none is what makes iOS draw a styled bar at all
   rather than its usual overlay one, which fades out before you have looked
   at the panel. */
#stage #action-feed::-webkit-scrollbar,
#stage #guess-history-zone::-webkit-scrollbar {
    -webkit-appearance: none;
    width: calc(4 * var(--u));
}
#stage #action-feed::-webkit-scrollbar-thumb,
#stage #guess-history-zone::-webkit-scrollbar-thumb {
    border-radius: var(--r-pill);
    background: var(--ink-ghost);
}
#stage #action-feed::-webkit-scrollbar-track,
#stage #guess-history-zone::-webkit-scrollbar-track { background: transparent; }

#stage #action-feed,
#stage #guess-history-zone { scrollbar-color: var(--ink-ghost) transparent; }

/* Spacing between lines is set here and nowhere else. A <p> carries a 1em
   margin by default, which is what used to drift them apart. */
#stage .feed-line {
    margin: 0;
    flex: none;
    display: flex;
    align-items: baseline;
    gap: calc(8 * var(--u));
    padding: calc(6 * var(--u)) calc(10 * var(--u));
    font-size: calc(12.5 * var(--u));
    line-height: 1.45;
    color: var(--ink-soft);
}
/* A hairline between plays rather than a gap — the spacing read as the panel
   being half empty. */
#stage .feed-line + .feed-line { border-top: 1px solid var(--rule); }

/* The play the animation is on. On a replay every line stays written and this
   is the only thing that moves down the list. */
#stage .feed-line.is-playing {
    background: var(--surface);
    border-radius: calc(9 * var(--u));
    color: var(--ink);
}
/* The rule above and below a lifted line would cut through its corners. */
#stage .feed-line.is-playing,
#stage .feed-line.is-playing + .feed-line { border-top-color: transparent; }
#stage .feed-line .feed-dot { transition: transform var(--dur) var(--ease); }
#stage .feed-line.is-playing .feed-dot {
    transform: translateY(calc(-3 * var(--u))) scale(1.5);
}
#stage .feed-line b { font-weight: var(--fw-bold); color: var(--ink); }
/* Not yet played. The line is in the DOM so the panel never reflows as the
   street fills; it is simply not on screen until its chips land. */
#stage .feed-line.is-pending { display: none; }
#stage .feed-line:not(.is-pending) { animation: feed-in var(--dur) var(--ease) both; }
@keyframes feed-in {
    from { opacity: 0; transform: translateY(calc(4 * var(--u))); }
    to   { opacity: 1; transform: none; }
}
#stage .feed-dot {
    width: calc(6 * var(--u));
    height: calc(6 * var(--u));
    flex: none;
    border-radius: var(--r-pill);
    background: var(--act-neutral);
    transform: translateY(calc(-3 * var(--u)));
}
#stage .feed-dot.is-check,
#stage .feed-dot.is-call { background: var(--act-passive); }
#stage .feed-dot.is-fold { background: var(--act-fold); }
#stage .feed-dot.is-bet { background: var(--act-aggro); }
#stage .feed-dot.is-raise,
#stage .feed-dot.is-reraise,
#stage .feed-dot.is-allin { background: var(--act-reraise); }
#stage .error-message { margin: 0; padding: calc(8 * var(--u)); font-size: calc(12.5 * var(--u)); }

/* ---------------------------------------------------------------- replay */

#stage .replay-btn {
    flex: none;
    height: calc(28 * var(--u));
    margin: calc(6 * var(--u)) calc(16 * var(--u)) 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: calc(7 * var(--u));
    /* White with a hairline border read as a disabled field, not a control. */
    background: var(--replay-soft);
    border: 1px solid var(--replay-line);
    border-radius: calc(9 * var(--u));
    font: inherit;
    font-size: calc(11 * var(--u));
    font-weight: var(--fw-bold);
    color: var(--replay-ink);
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur) var(--ease);
}
#stage .replay-btn.replay-ready { opacity: 1; pointer-events: auto; }
#stage .replay-icon { font-size: calc(12 * var(--u)); }

/* --------------------------------------------------------- guess history */

/* The one container allowed to absorb leftover space, so the button below it
   never moves when the feed or the board changes size. */
/* The history gives way too now, rather than only the feed. Its content is a
   grid of small boxes that reads fine partially scrolled; three plays does not
   read fine as one. */
#stage #guess-history-zone {
    /* Pinned to the bottom, directly above the action bar, and growing upward:
       one row for the first three streets, a second for the river. margin-top
       takes the free space so the block never drifts off the bottom. */
    /* The feed above absorbs essentially all of any shortfall, but this must
       still be able to give: at small --u, rounding across dozens of little
       boxes accumulates ~19px past the canvas, and with nothing left to shrink
       the block simply overlapped the action bar. Scrolling beats overlapping. */
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
    scrollbar-width: thin;
    /* margin-top:auto already pins the block to the bottom of the column, so
       the content inside it starts at the top — justify-content:flex-end would
       clip the labels off the top of the scroller with no way to reach them. */
    margin-top: auto;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    /* The title labels the cards, so it sits close to them; the air belongs
       above it, between the block and the replay button. It was the other way
       round -- crowding the button it has nothing to do with and floating away
       from the thing it names. */
    gap: calc(3 * var(--u));
    padding: calc(15 * var(--u)) calc(16 * var(--u)) calc(10 * var(--u));
    background: none;
    border: 0;
    box-shadow: none;
}
#stage .guess-history-header {
    display: flex;
    align-items: center;
    justify-content: flex-start;   /* main.css spaces these to the edges */
    gap: calc(7 * var(--u));
    flex: none;
}
#stage #guess-history-zone h2 {
    margin: 0;
    font-size: calc(9.5 * var(--u));
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--ink-faint);
    /* The count is the part that changes, so it is the part that gets the
       colour -- see .count-chip below. */
    display: flex;
    align-items: center;
    gap: calc(6 * var(--u));
}
/* "6 left" as a chip. Before this the whole line was one flat grey label with
   nothing to look at, which read as a leftover rather than as a scoreboard --
   worst of all before the first guess, when the row below it is empty. */
#stage #guess-history-zone h2 .count-chip,
#stage #picker-heading .count-chip {
    display: inline-flex;
    align-items: center;
    padding: calc(3 * var(--u)) calc(7 * var(--u));
    border-radius: var(--r-pill);
    background: var(--accent-soft);
    color: var(--accent);
    font-size: calc(9 * var(--u));
    letter-spacing: var(--ls-label);
}
/* Running low is worth noticing before you spend the last one. */
/* In the picker's heading the chip sits under the sheet handle; a touch
   smaller and a hair lower keeps the two apart. */
#stage #picker-heading .count-chip {
    font-size: calc(8.5 * var(--u));
    padding: calc(2.5 * var(--u)) calc(6 * var(--u));
    margin-top: calc(3 * var(--u));
}
#stage #guess-history-zone h2 .count-chip.is-low,
#stage #picker-heading .count-chip.is-low {
    background: var(--warn-soft);
    color: var(--warn-ink);
}
#stage .btn-icon {
    width: calc(17 * var(--u));
    height: calc(17 * var(--u));
    min-width: 0;
    padding: 0;
    /* Was a 1px hairline in --rule on white at 15u across: technically there,
       practically invisible. Filled and darkened so it reads as a button. */
    border: 0;
    border-radius: var(--r-pill);
    background: var(--surface-2);
    font: inherit;
    font-size: calc(10 * var(--u));
    font-weight: var(--fw-bold);
    color: var(--ink-soft);
    display: grid;
    place-items: center;
    cursor: pointer;
}
#stage #feedback-legend { display: none; }   /* superseded by the how-to-play modal */

#stage #guess-history-grid {
    display: flex;
    flex-direction: column;
    /* Between the pre-flop/flop/turn row and the river row. At 5u the river
       label sat almost on the cards above it and the two read as one block. */
    gap: calc(11 * var(--u));
    flex: none;
}
/* Two rows of three, never one tall column. */
#stage .guess-street-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: calc(6 * var(--u)) calc(7 * var(--u));
    justify-items: center;
}
#stage .guess-street-column {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(2 * var(--u));
    /* main.css sets min-height: 80px here. A raw px floor on a canvas that is
       scaled by --u is always wrong -- it does not shrink with everything
       else, so the smaller the screen the more of it there is -- and 80px on
       36px of content was more than double. It is what squeezed the feed to a
       single line on the river. */
    min-height: 0;
    padding: 0;
    background: none;
    border: 0;
}
/* A street with no guess yet takes no space at all. */
#stage .guess-street-column:not(:has(.guess-row)) { display: none; }
#stage .guess-street-column h4 {
    margin: 0;
    line-height: 1.1;
    font-size: calc(9 * var(--u));
    font-weight: var(--fw-bold);
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--ink-faint);
}
#stage #guess-history-zone[hidden] { display: none; }
#stage .guess-list { display: flex; flex-direction: column; gap: calc(4 * var(--u)); }
/* The river takes three guesses. Stacked, they cost three rows of height and
   squeeze the action feed off the screen; side by side they cost one. The
   column has to span the whole grid to have room for them — on a third of the
   width they just wrapped again. */
#stage .river-column { grid-column: 1 / -1; }
#stage #river-guesses { width: 100%; }
#stage #river-guesses .guess-list {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    justify-items: center;
    gap: calc(5 * var(--u)) calc(7 * var(--u));
    width: 100%;
}
#stage .guess-row { display: flex; justify-content: center; }
#stage .guess-cards { display: flex; gap: calc(8 * var(--u)); }

/* Bigger than before, not smaller: killing the 80px floor gave back about 88px
   of pure air, and legible cards are a better use of it than a gap. */
#stage .guess-cards .card {
    --cw: calc(29 * var(--u));
    --ch: calc(35 * var(--u));
    --crank: calc(13 * var(--u));
    --csuit: calc(9.5 * var(--u));
    border: 0;
    border-radius: calc(5 * var(--u));
    gap: 0;
}
#stage .guess-row .card.green  { background: var(--hit);  color: var(--on-hit); }
#stage .guess-row .card.yellow { background: var(--near); color: var(--on-near); }
#stage .guess-row .card.grey   { background: var(--miss); color: var(--miss-ink); }
#stage .guess-row .card.green .card-suit-red,
#stage .guess-row .card.yellow .card-suit-red,
#stage .guess-row .card.green .card-suit-black,
#stage .guess-row .card.yellow .card-suit-black { color: inherit; }
#stage .guess-row .card.grey .card-suit-red,
#stage .guess-row .card.grey .card-suit-black { color: var(--miss-ink); }

/* ------------------------------------------------------- the action bar */

/* main.css hides the bar, backdrop, handle and close above 900px, on the old
   assumption that desktop got an inline picker. There is one layout now, so
   all four are always present. */
#stage #mobile-action-bar {
    display: block;
    position: static;
    flex: none;
    padding: calc(11 * var(--u)) calc(16 * var(--u)) calc(14 * var(--u));
    border-top: 1px solid var(--rule);
    background: var(--surface);
    z-index: 3;
}
#stage #mobile-action-bar.bar-hidden { visibility: hidden; }

/* The bar changes purpose between guessing and advancing, and a label change
   alone was easy to miss — the colour carries it too. */
#stage #mobile-action-bar .btn[data-mode="next"],
#stage #mobile-action-bar .btn[data-mode="results"] { background: var(--accent); }
/* Going back to the live street is a real action, not a cancel, so it stops
   looking like a disabled button. Tinted rather than solid: it should read as
   secondary to the green, not compete with it. */
#stage #mobile-action-bar .btn[data-mode="return"] {
    background: var(--accent-soft);
    color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent-line);
}
/* ...and announces itself once when that purpose changes. */
#stage #mobile-action-bar .btn.mode-changed {
    animation: bar-attn calc(var(--dur) * 3) var(--ease) 1;
}
@keyframes bar-attn {
    from { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), .5); }
    to   { box-shadow: 0 0 0 calc(12 * var(--u)) rgba(var(--accent-rgb), 0); }
}

#stage #mobile-action-bar .btn {
    display: block;
    width: 100%;
    height: calc(50 * var(--u));
    padding: 0;
    border: 0;
    border-radius: calc(13 * var(--u));
    background: var(--ink);
    color: var(--surface);
    font: inherit;
    font-size: calc(15 * var(--u));
    font-weight: var(--fw-bold);
    letter-spacing: -.01em;
    cursor: pointer;
}

/* Kept for keyboard users and the tests; the bar above carries the same action. */
#stage #next-street-btn { display: none; }

/* ------------------------------------------------------- the picker sheet */

/* Fixed to the viewport, not absolute in the stage: the canvas sits inside
   gutters on most phones, and dimming only the canvas drew a bright frame
   around it every time the sheet opened. The stage has no transform or
   filter, so a fixed child escapes its overflow clip. */
#stage #sheet-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    background: var(--backdrop);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur) var(--ease);
    z-index: 4;
}
#stage #sheet-backdrop.open { opacity: 1; pointer-events: auto; }

/* ------------------------------------------------------------ menu drawer */

#stage #menu-backdrop {
    position: absolute;
    inset: 0;
    background: var(--backdrop);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--dur) var(--ease);
    z-index: 6;
}
#stage #menu-backdrop.open { opacity: 1; pointer-events: auto; }

#stage #menu-drawer {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 7;
    width: calc(268 * var(--u));
    max-width: 78%;
    padding: calc(14 * var(--u)) 0 calc(16 * var(--u));
    display: flex;
    flex-direction: column;
    background: var(--surface);
    box-shadow: calc(2 * var(--u)) 0 calc(28 * var(--u)) rgba(var(--shadow-ink), .22);

    transform: translateX(-101%);
    /* visibility, not the transform alone. A drawer parked off-canvas is still
       in the tab order, and focusing something inside it scrolls the stage --
       which has overflow:hidden and no way to scroll back. This is the same
       trap the picker sheet had. */
    visibility: hidden;
    transition: transform var(--dur) var(--ease),
                visibility 0s linear var(--dur);
}
#stage #menu-drawer.open {
    transform: translateX(0);
    visibility: visible;
    transition: transform var(--dur) var(--ease), visibility 0s;
}

#stage .drawer-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: calc(8 * var(--u));
    padding: 0 calc(8 * var(--u)) calc(12 * var(--u)) calc(18 * var(--u));
    border-bottom: 1px solid var(--rule);
}
#stage .drawer-title {
    font-size: calc(16 * var(--u));
    font-weight: var(--fw-heavy);
    color: var(--ink);
}
#stage .drawer-close {
    display: grid;
    place-items: center;
    width: calc(30 * var(--u));
    height: calc(30 * var(--u));
    padding: 0;
    border: 0;
    background: none;
    color: var(--ink-soft);
    border-radius: var(--r-md);
    cursor: pointer;
}
#stage .drawer-close svg {
    width: calc(17 * var(--u));
    height: calc(17 * var(--u));
    fill: none;
    stroke: currentColor;
    stroke-width: 1.8;
    stroke-linecap: round;
}

#stage .drawer-item {
    display: block;
    width: 100%;
    padding: calc(14 * var(--u)) calc(18 * var(--u));
    border: 0;
    background: none;
    font: inherit;
    font-size: calc(14.5 * var(--u));
    font-weight: var(--fw-bold);
    color: var(--ink);
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}
#stage .drawer-item + .drawer-item { border-top: 1px solid var(--rule-soft); }
#stage .drawer-item:active { background: var(--surface-2); color: var(--accent); }
/* The tip jar is the one item that asks something of the player, so it is
   not an item at all: a green button pinned to the foot of the drawer. */
#stage .drawer-support {
    margin: auto calc(16 * var(--u)) 0;
    height: calc(46 * var(--u));
    display: flex;
    align-items: center;
    justify-content: center;
    gap: calc(8 * var(--u));
    border-radius: calc(13 * var(--u));
    background: var(--hit);
    color: var(--on-hit);
    font-size: calc(14.5 * var(--u));
    font-weight: var(--fw-bold);
    text-decoration: none;
    box-shadow: 0 calc(4 * var(--u)) calc(12 * var(--u)) rgba(58, 160, 106, .30);
}
#stage .drawer-support svg {
    width: calc(16 * var(--u));
    height: calc(16 * var(--u));
    fill: currentColor;
    stroke: none;
}
#stage .drawer-support:active { transform: translateY(calc(1 * var(--u))); box-shadow: none; }

#stage #selection-grid-zone {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 5;
    /* The sheet is exactly as tall as the picker needs, and no taller —
       main.css otherwise stretches it and leaves a void above Submit. */
    height: auto;
    max-height: calc(784 * var(--u));
    display: flex;
    flex-direction: column;
    gap: calc(10 * var(--u));
    padding: calc(8 * var(--u)) calc(16 * var(--u)) calc(16 * var(--u));
    background: var(--surface);
    border: 0;
    border-radius: calc(20 * var(--u)) calc(20 * var(--u)) 0 0;
    box-shadow: 0 calc(-6 * var(--u)) calc(28 * var(--u)) rgba(var(--shadow-ink), .16);
    transform: translateY(101%);
    /* visibility, not just the transform: a translated-away sheet is still in
       the tab order, and focusing the close button scrolled the whole stage
       466px with overflow:hidden leaving no way back. Hiding it takes the
       subtree out of the tab order; the 0s delay holds it visible for the
       length of the closing slide so the animation still reads. */
    visibility: hidden;
    transition: transform var(--dur) var(--ease),
                visibility 0s linear var(--dur);
}
#stage #selection-grid-zone.open {
    transform: translateY(0);
    visibility: visible;
    transition: transform var(--dur) var(--ease), visibility 0s;
}

#stage .sheet-handle {
    display: block;
    /* main.css floats it over the top of the sheet under 900px, where it sat
       on the heading's count chip. It is a row of its own here. */
    position: static;
    transform: none;
    width: calc(36 * var(--u));
    height: calc(4 * var(--u));
    flex: none;
    margin: 0 auto;
    border-radius: var(--r-pill);
    background: var(--rule);
}
#stage .sheet-close {
    display: block;
    position: absolute;
    top: calc(10 * var(--u));
    right: calc(12 * var(--u));
    width: calc(26 * var(--u));
    height: calc(26 * var(--u));
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-size: calc(18 * var(--u));
    line-height: 1;
    color: var(--ink-faint);
    cursor: pointer;
}
#stage #selection-grid-zone h2 {
    margin: 0;
    flex: none;
    display: flex;
    align-items: center;
    gap: calc(8 * var(--u));
    font-size: calc(15 * var(--u));
    font-weight: var(--fw-heavy);
    letter-spacing: var(--ls-tight);
    color: var(--ink);
    text-align: left;
}

/* The legend and the after-a-green note occupy the same strip, one at a time.
   Fixed height and absolutely-positioned children, so swapping them cannot
   move the grid underneath. */
#stage .picker-legend {
    flex: none;
    /* Pull up against the heading: the sheet's 10u gap read as too far. */
    margin-top: calc(-4 * var(--u));
    display: flex;
    align-content: center;
    flex-wrap: wrap;
    gap: calc(4 * var(--u)) calc(12 * var(--u));
    font-size: calc(9.5 * var(--u));
    font-weight: var(--fw-medium);
    color: var(--ink-faint);
}

/* Shown once a card comes back green, because the rule it explains is not
   guessable from the board: a confirmed card does not win on its own. */
#stage .picker-note[hidden] { display: none; }
#stage .picker-note {
    flex: none;
    height: calc(24 * var(--u));
    margin-top: calc(-2 * var(--u));
    display: flex;
    align-items: center;
    gap: calc(6 * var(--u));
    margin: 0;
    padding: 0 calc(9 * var(--u));
    border-radius: calc(7 * var(--u));
    background: var(--accent-soft);
    border: calc(1 * var(--u)) solid var(--accent-line);
    color: var(--ink);
    font-size: calc(10.5 * var(--u));
    font-weight: var(--fw-bold);
    line-height: 1.15;
    animation: note-in calc(var(--dur) * 2) var(--ease) both;
}
#stage .picker-note::before {
    content: "";
    flex: none;
    width: calc(7 * var(--u));
    height: calc(7 * var(--u));
    border-radius: 50%;
    background: var(--hit);
}
@keyframes note-in {
    0%   { opacity: 0; transform: translateY(calc(-4 * var(--u))); }
    35%  { opacity: 1; transform: translateY(0); box-shadow: 0 0 0 calc(3 * var(--u)) var(--hit); }
    60%  { box-shadow: 0 0 0 calc(3 * var(--u)) transparent; }
    100% { opacity: 1; transform: translateY(0); }
}

/* A tap on a card the game has already ruled out says why, rather than doing
   nothing. Parented to the sheet, not the card, so the grid's own scroll
   container cannot clip it. */
#stage .card-note {
    position: absolute;
    z-index: 6;
    padding: calc(4 * var(--u)) calc(7 * var(--u));
    border-radius: calc(6 * var(--u));
    background: var(--ink);
    color: var(--surface);
    font-size: calc(9.5 * var(--u));
    font-weight: var(--fw-bold);
    white-space: nowrap;
    pointer-events: none;
    transform: translate(-50%, -100%);
    animation: card-note calc(var(--dur) * 8) var(--ease) both;
}
@keyframes card-note {
    0%   { opacity: 0; transform: translate(-50%, calc(-100% + 4 * var(--u))); }
    12%  { opacity: 1; transform: translate(-50%, -100%); }
    78%  { opacity: 1; transform: translate(-50%, -100%); }
    100% { opacity: 0; transform: translate(-50%, -100%); }
}
@media (prefers-reduced-motion: reduce) {
    #stage .picker-note,
    #stage .card-note { animation-duration: 1ms; animation-fill-mode: both; }
    #stage .card-note { opacity: 1; transform: translate(-50%, -100%); }
}
#stage .picker-legend span { display: flex; align-items: center; gap: calc(4 * var(--u)); }
/* The chips are the saturated key colours -- the same green and amber as the
   tiles in the history on the table -- not the pale shading the cards wear.
   Four pale 9u squares were indistinguishable; a swatch stands for a tint
   the way a map key does, by hue, not by matching it exactly. */
#stage .legend-chip {
    width: calc(9 * var(--u));
    height: calc(9 * var(--u));
    border-radius: calc(3 * var(--u));
    background: var(--ink-faint);
}
#stage .legend-chip.is-known { background: var(--legend-known); }
#stage .legend-chip.is-near { background: var(--near); }
#stage .legend-chip.is-hit  { background: var(--hit); }

#stage .picker-body {
    flex: 0 1 auto;
    min-height: 0;
    display: flex;
    gap: calc(10 * var(--u));
}
#stage #card-grid-wrapper {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    overflow-y: auto;
    scrollbar-width: none;
    /* The pick's ring sits 2.5u outside the card, and a scroll box clips on
       both axes; without this the ring was cut off on the outer columns and
       the top row. The negative margin gives the width back to the grid. */
    padding: calc(4 * var(--u));
    margin: 0 calc(-4 * var(--u));
    background: none;
    border: 0;
}
#stage #card-grid-wrapper::-webkit-scrollbar { display: none; }

/* Rank-major: four columns, so each row is one rank in a fixed suit order. */
#stage #card-grid {
    display: grid;
    /* minmax(0,...) not 1fr: main.css gives the card a min-width, and a plain
       1fr track floors at min-content, which pushed the grid past the sheet. */
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: calc(5 * var(--u));
    padding: 0;
    min-width: 0;           /* main.css sets 572px, which is what overflowed */
    width: 100%;
    background: none;
    border: 0;
}
#stage .card-wrapper {
    display: block;
    min-width: 0;
    padding: 0;
    background: none;
    border: 0;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
#stage .card-wrapper .card {
    --cw: 100%;
    min-width: 0;
    --ch: calc(34 * var(--u));
    --crank: calc(14 * var(--u));
    --csuit: calc(12 * var(--u));
    flex-direction: row;
    gap: calc(3 * var(--u));
    border-radius: calc(7 * var(--u));
    border-width: 1px;
    padding: 0;
}

/* Picker states. Everything but "still possible" is non-interactive. */
#stage .card-wrapper.rank-miss .card {
    background: var(--surface-2);
    border-color: var(--surface-2);
    color: var(--ink-ghost);
}
#stage .card-wrapper.rank-miss .card .card-suit-red,
#stage .card-wrapper.rank-miss .card .card-suit-black { color: var(--ink-ghost); }

/* Already on the table — your hole cards and the board. Ruled out for a
   different reason from a missed rank, so it reads red rather than grey. */
#stage .card-wrapper.known-card .card {
    background: var(--card-known);
    border-color: var(--card-known);
    color: var(--card-known-ink);
    /* main.css fades these to 0.55, which is invisible over a white sheet
       and turns a pale pink card to mud over a dark one. */
    opacity: 1;
}
#stage .card-wrapper.known-card .card .card-suit-red,
#stage .card-wrapper.known-card .card .card-suit-black { color: var(--card-known-ink); }

/* These used to be pointer-events: none, which made a tap do nothing at all.
   They still cannot be played -- handleCardSelection refuses them -- but they
   now receive the tap so they can say why.
   Taking the pointer back also re-exposed main.css's .card-wrapper:hover lift
   and green glow, which on a ruled-out card reads as an invitation. Cancelled
   here: a card you cannot play must not behave like one you can. */
#stage .card-wrapper.known-card,
#stage .card-wrapper.rank-miss,
#stage .card-wrapper.rank-match { cursor: default; }
/* main.css lifts a hovered card and glows it green. On a touch screen a tap
   leaves :hover stuck on the last card touched, so a card you had just
   deselected kept a green glow and sat 2px high until you tapped elsewhere.
   No hover treatment at all on touch; a neutral lift where hover is real. */
#stage .card-wrapper:hover { transform: none; box-shadow: none; }
@media (hover: hover) {
    #stage .card-wrapper:not(.known-card):not(.rank-miss):not(.rank-match):hover .card {
        box-shadow: 0 calc(3 * var(--u)) calc(8 * var(--u)) rgba(var(--shadow-ink), .18);
    }
}
/* Flat means unplayable. main.css gives every card a drop shadow and only
   took it off the amber; a red card that cannot be tapped was still sitting
   up off the sheet like one that can. Confirmed keeps its shadow -- it is
   still a card you can play. */
#stage .card-wrapper.known-card .card,
#stage .card-wrapper.rank-miss .card,
#stage .card-wrapper.rank-match .card { box-shadow: none; }


#stage .card-wrapper.rank-match .card {
    background: var(--card-near);
    border-color: var(--card-near);
    color: var(--card-near-ink);
}
#stage .card-wrapper.rank-match .card .card-suit-black { color: var(--card-near-ink); }
/* Feedback is shading of the whole card and nothing else -- no border, no
   ring. Anything on the edge of a card is reserved for the pick, so the two
   can never be confused. Ink stays normal so the suit colours survive. */
/* A confirmed card is still playable, so it keeps what every playable card
   has: the neutral edge and the drop shadow. Only the fill changes. Without
   the edge it read as a flat tile like the unplayable ones, even with the
   shadow -- the border is what says "card" at a glance. Not a green border:
   the edge of a card belongs to the pick. */
#stage .card-wrapper.exact-match .card {
    background: var(--card-hit);
    border-color: var(--card-hit-line);
    color: var(--card-ink);
}
#stage .card-wrapper.exact-match .card .card-suit-red { color: var(--card-red-tint); }
#stage .card-wrapper.rank-match .card .card-suit-red { color: var(--card-near-red); }


/* Fill is what the game told you (green / amber / grey / red); the outline is
   what YOU are choosing right now. So the ring sits outside the card rather
   than inset -- an inset ring eats into the feedback colour and the two read
   as one muddled state. The old ring was 1px, which is why selection read as
   vague. The grid gap is 5u, so 2u each side still leaves daylight, and the
   raised z-index keeps the ring above the neighbouring cards. */
#stage .card-wrapper.selected { position: relative; z-index: 2; }
#stage .card-wrapper.selected .card {
    position: relative;
    border-color: var(--pick);
    box-shadow: 0 0 0 calc(2.5 * var(--u)) var(--pick);
    /* main.css runs a selectionPulse animation with `forwards`, whose last
       keyframe is a green glow -- held for ever, and over the top of any ring
       set here. That was the neon. */
    animation: none;
}
/* A ring alone was still lost on a coloured fill. The badge is the same
   affordance a photo picker uses, and it reads on green, amber and white
   alike. Inside the card's bounds because .card clips its overflow. Purple on
   white in both themes: the card is always light, whatever the sheet is. */
#stage .card-wrapper.selected .card::after {
    content: "\2713";
    position: absolute;
    top: calc(2 * var(--u));
    right: calc(2 * var(--u));
    width: calc(13 * var(--u));
    height: calc(13 * var(--u));
    border-radius: 50%;
    background: var(--pick);
    color: #fff;
    font-size: calc(8.5 * var(--u));
    font-weight: var(--fw-heavy);
    line-height: calc(13 * var(--u));
    text-align: center;
}

/* ---- slots and submit, at the foot of the sheet ---- */

/* One line: [slot][slot] | [last guess] [submit]. The last guess is a small
   block with a hairline on its left, and it is not rendered at all until there
   is a guess -- so the button is full width on the pre-flop and gives up a
   little width once, at the first result. */
#stage #selection-controls {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: calc(8 * var(--u));
    height: calc(56 * var(--u));
    padding: 0;
    margin: 0;
    flex: none;
}
#stage .card-slot {
    width: calc(42 * var(--u));
    height: calc(56 * var(--u));
    flex: none;
    padding: 0;
    margin: 0;
    display: grid;
    place-items: center;
    background: var(--surface-2);
    border: calc(1.5 * var(--u)) dashed var(--rule);
    border-radius: calc(7 * var(--u));
    font-size: 0;
    color: transparent;
}
#stage .card-slot[data-card=""] { }
#stage .card-slot .card {
    --cw: calc(39 * var(--u));
    --ch: calc(53 * var(--u));
    --crank: calc(17 * var(--u));
    --csuit: calc(13 * var(--u));
    margin: 0;
}
/* The result lands on the two cards you played, held for a beat before the
   sheet moves on. */
#stage .card-slot .card.green  { background: var(--hit);  border-color: var(--hit);  color: var(--on-hit); }
#stage .card-slot .card.yellow { background: var(--near); border-color: var(--near); color: var(--on-near); }
#stage .card-slot .card.grey   { background: var(--rule); border-color: var(--rule); color: var(--miss-ink); }
#stage .card-slot .card.green .card-suit-red,
#stage .card-slot .card.green .card-suit-black,
#stage .card-slot .card.yellow .card-suit-red,
#stage .card-slot .card.yellow .card-suit-black { color: inherit; }
#stage .card-slot .card.grey .card-suit-red,
#stage .card-slot .card.grey .card-suit-black { color: var(--miss-ink); }
#stage .card-slot .card.slot-reveal { animation: slot-pop calc(var(--dur) * 1.6) var(--ease) both; }
@keyframes slot-pop {
    0%   { transform: scale(1); }
    45%  { transform: scale(1.14); }
    100% { transform: scale(1); }
}
#stage #selection-buttons {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    height: 100%;
    padding: 0;
    margin: 0;
}

/* ---- previous guesses, the column beside the grid ---- */

#stage .recent-guesses[hidden] { display: none; }
#stage .recent-guesses {
    flex: none;
    width: calc(58 * var(--u));
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(6 * var(--u));
    min-height: 0;
}
#stage .recent-label {
    flex: none;
    font-size: calc(7.5 * var(--u));
    font-weight: var(--fw-bold);
    letter-spacing: calc(0.5 * var(--u));
    text-transform: uppercase;
    color: var(--ink-faint);
    text-align: center;
    line-height: 1.25;
    padding-top: calc(10 * var(--u));
    margin-bottom: calc(10 * var(--u));
}
/* Top-aligned at a fixed gap. Spreading six slots over the grid's whole
   height put the entries too far apart to read as one list. */
#stage .recent-list {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(24 * var(--u));
}
#stage .recent-entry {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(2 * var(--u));
}
#stage .recent-street {
    font-size: calc(7 * var(--u));
    font-weight: var(--fw-bold);
    letter-spacing: calc(0.5 * var(--u));
    text-transform: uppercase;
    color: var(--ink-faint);
    line-height: 1;
    white-space: nowrap;
}
/* Same mini cards as the history on the table, so the result you just got and
   the result in the history are visibly the same object. */
#stage .recent-entry .guess-row { justify-content: center; }
#stage .recent-entry .guess-cards { gap: calc(4 * var(--u)); }
#stage .recent-entry .card {
    --cw: calc(25 * var(--u));
    --ch: calc(31 * var(--u));
    --crank: calc(11.5 * var(--u));
    --csuit: calc(9 * var(--u));
    border: 0;
    border-radius: calc(5 * var(--u));
    gap: 0;
}
#stage .recent-entry .card.green  { background: var(--hit);  color: var(--on-hit); }
#stage .recent-entry .card.yellow { background: var(--near); color: var(--on-near); }
#stage .recent-entry .card.grey   { background: var(--miss); color: var(--miss-ink); }
#stage .recent-entry .card.green .card-suit-red,
#stage .recent-entry .card.yellow .card-suit-red,
#stage .recent-entry .card.green .card-suit-black,
#stage .recent-entry .card.yellow .card-suit-black { color: inherit; }
#stage .recent-entry .card.grey .card-suit-red,
#stage .recent-entry .card.grey .card-suit-black { color: var(--miss-ink); }

#stage #submit-guess-btn {
    flex: 1 1 auto;
    min-width: 0;
    width: 100%;
    height: calc(56 * var(--u));
    padding: 0;
    border: 0;
    border-radius: calc(13 * var(--u));
    background: var(--ink);
    color: var(--surface);
    font: inherit;
    font-size: calc(15 * var(--u));
    font-weight: var(--fw-bold);
    cursor: pointer;
}
#stage #submit-guess-btn:disabled { background: var(--rule); color: var(--ink-faint); cursor: default; }

/* ---- the sheet after the hand is over ---- */

/* The finished-hand panel replaces the picker, so the picker's own legend
   goes with it — setPickerGameOver() hides the heading, grid and controls but
   never knew about the legend. */
#stage #selection-grid-zone:has(.post-game-panel.show) .picker-legend { display: none; }

#stage .post-game-panel { display: none; }
#stage .post-game-panel.show {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: calc(8 * var(--u));
    padding: calc(10 * var(--u)) 0 calc(4 * var(--u));
}
#stage .post-game-eyebrow {
    margin: 0;
    font-size: calc(9.5 * var(--u));
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--ink-faint);
}
#stage .post-game-result {
    margin: 0;
    font-size: calc(24 * var(--u));
    font-weight: var(--fw-heavy);
    letter-spacing: var(--ls-tight);
}
#stage .post-game-result.win { color: var(--hit); }
#stage .post-game-result.loss { color: var(--ink); }
#stage .post-game-sub {
    margin: 0;
    font-size: calc(11 * var(--u));
    color: var(--ink-soft);
}
#stage .post-game-cards { display: flex; gap: calc(6 * var(--u)); }
#stage .post-game-panel .btn {
    width: 100%;
    height: calc(50 * var(--u));
    margin-top: calc(4 * var(--u));
    border: 0;
    border-radius: calc(13 * var(--u));
    background: var(--ink);
    color: var(--surface);
    font: inherit;
    font-size: calc(15 * var(--u));
    font-weight: var(--fw-bold);
    cursor: pointer;
}

/* ------------------------------------------------------------- animations */

#stage .street-in-fwd  { animation: street-fwd var(--dur) var(--ease) both; }
#stage .street-in-back { animation: street-back var(--dur) var(--ease) both; }
@keyframes street-fwd {
    from { opacity: 0; transform: translateX(calc(14 * var(--u))); }
    to   { opacity: 1; transform: none; }
}
@keyframes street-back {
    from { opacity: 0; transform: translateX(calc(-14 * var(--u))); }
    to   { opacity: 1; transform: none; }
}

#stage .board-card-reveal { animation: board-in calc(260ms) var(--ease) both; }
@keyframes board-in {
    from { opacity: 0; transform: translateY(calc(-6 * var(--u))) scale(.94); }
    to   { opacity: 1; transform: none; }
}

/* Restoring a finished hand from storage: no replay of every guess. */
body.restoring #stage .guess-row { animation: none !important; }

/* =====================================================================
 * The two overlays: results and how-to-play.
 *
 * Same design system as the stage — authored in --u, so they scale with it
 * and stay inside the phone at every size. main.css still styles these by
 * class; the ID scoping here outranks all of it.
 * ===================================================================== */

body.game-active #modal-overlay,
body.game-active #help-overlay,
body.game-active #settings-overlay {
    position: fixed;
    inset: 0;
    z-index: 50;
    display: none;
    align-items: center;
    justify-content: center;
    padding: calc(24 * var(--u)) calc(16 * var(--u));
}
body.game-active #modal-overlay.show,
body.game-active #help-overlay.show,
body.game-active #settings-overlay.show { display: flex; }

#modal-overlay .modal-content,
#help-overlay .modal-content {
    box-sizing: border-box;
    width: calc(370 * var(--u));
    max-width: 100%;
    max-height: calc(784 * var(--u));
    overflow-y: auto;
    scrollbar-width: none;
    position: relative;
    padding: calc(18 * var(--u)) calc(20 * var(--u)) calc(16 * var(--u));
    background: var(--surface);
    color: var(--ink);
    border: 0;
    border-radius: calc(18 * var(--u));
    box-shadow: 0 calc(16 * var(--u)) calc(48 * var(--u)) rgba(var(--shadow-ink), .24);
    text-align: center;
    font-size: calc(14 * var(--u));
}
#modal-overlay .modal-content::-webkit-scrollbar,
#help-overlay .modal-content::-webkit-scrollbar { display: none; }

/* The close control is an icon in the corner; the old "Back to puzzle"
   wording sat on top of the title at this width. */
#modal-overlay #modal-close-btn,
#help-overlay #help-close-btn,
#help-overlay .modal-x {
    position: absolute;
    top: calc(12 * var(--u));
    right: calc(12 * var(--u));
    width: calc(28 * var(--u));
    height: calc(28 * var(--u));
    padding: 0;
    display: grid;
    place-items: center;
    background: var(--surface-2);
    border: 0;
    border-radius: var(--r-pill);
    font: inherit;
    font-size: calc(16 * var(--u));
    line-height: 1;
    color: var(--ink-soft);
    cursor: pointer;
}
#modal-overlay #modal-close-btn .back-text { display: none; }
#modal-overlay #modal-close-btn .back-x { display: block; }

#modal-overlay #modal-title {
    margin: 0 calc(28 * var(--u)) calc(10 * var(--u));
    font-size: calc(20 * var(--u));
    font-weight: var(--fw-heavy);
    letter-spacing: var(--ls-tight);
    text-transform: none;
    color: var(--ink);
}
/* The one word of colour on the page. A win is green because green is what
   a found card is; a loss stays in ink -- red would shout. */
#modal-overlay #modal-title.win { color: var(--hit); }

#modal-overlay #modal-solution-label {
    margin: 0 0 calc(10 * var(--u));
    font-size: calc(9.5 * var(--u));
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--ink-faint);
}
#modal-overlay .modal-solution-cards {
    display: flex;
    justify-content: center;
    gap: calc(8 * var(--u));
    margin: 0 0 calc(12 * var(--u));
}
#modal-overlay .modal-solution-cards .card {
    box-sizing: border-box;
    width: calc(42 * var(--u));
    height: calc(58 * var(--u));
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: calc(2 * var(--u));
    padding: 0;
    background: var(--card-bg);
    border: calc(1.5 * var(--u)) solid var(--card-line);
    border-radius: calc(8 * var(--u));
    box-shadow: var(--shadow-sm);
    font-weight: var(--fw-heavy);
    line-height: 1;
    color: var(--card-ink);
}
#modal-overlay .modal-solution-cards .card-rank-display { font-size: calc(19 * var(--u)); }
#modal-overlay .modal-solution-cards .card-symbol-display { font-size: calc(14.5 * var(--u)); }

#modal-overlay .modal-context {
    margin: 0 0 calc(12 * var(--u));
    font-size: calc(12.5 * var(--u));
    font-style: normal;
    line-height: var(--lh-body);
    color: var(--ink-soft);
}

#modal-overlay .modal-stats {
    padding: calc(12 * var(--u)) 0 calc(2 * var(--u));
    border-top: 1px solid var(--rule);
}
#modal-overlay .stats-heading,
#modal-overlay .stats-dist-title {
    margin: 0 0 calc(10 * var(--u));
    font-size: calc(9.5 * var(--u));
    font-weight: var(--fw-bold);
    letter-spacing: var(--ls-label);
    text-transform: uppercase;
    color: var(--ink-faint);
}
#modal-overlay .stats-dist-title { margin-top: calc(12 * var(--u)); }

#modal-overlay .stats-summary {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: calc(6 * var(--u));
}
#modal-overlay .stat { display: flex; flex-direction: column; gap: calc(3 * var(--u)); }
#modal-overlay .stat-num {
    font-family: var(--font-mono);
    font-size: calc(20 * var(--u));
    font-weight: 700;
    line-height: 1;
    color: var(--ink);
}
#modal-overlay .stat-label {
    font-size: calc(9 * var(--u));
    line-height: 1.25;
    color: var(--ink-faint);
}

#modal-overlay .stats-distribution {
    display: flex;
    flex-direction: column;
    gap: calc(5 * var(--u));
}
#modal-overlay .dist-row {
    display: grid;
    grid-template-columns: calc(52 * var(--u)) 1fr;
    align-items: center;
    gap: calc(8 * var(--u));
}
#modal-overlay .dist-label {
    font-size: calc(10 * var(--u));
    font-weight: var(--fw-bold);
    color: var(--ink-soft);
    text-align: right;
}
/* The track is the groove; .dist-bar inside it is the fill whose width the
   script sets as a percentage. */
#modal-overlay .dist-track {
    height: calc(20 * var(--u));
    display: flex;
    align-items: stretch;
    padding: 0;
    background: var(--surface-2);
    border-radius: calc(4 * var(--u));
    overflow: hidden;
}
#modal-overlay .dist-bar {
    min-width: calc(22 * var(--u));
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding: 0 calc(6 * var(--u));
    background: var(--ink-ghost);
    border-radius: calc(4 * var(--u));
}
#modal-overlay .dist-bar.current { background: var(--hit); }
#modal-overlay .dist-count {
    font-family: var(--font-mono);
    font-size: calc(10 * var(--u));
    font-weight: 700;
    color: var(--ink-soft);
}
#modal-overlay .dist-bar.current .dist-count { color: var(--on-hit); }

#modal-overlay .modal-buttons {
    display: flex;
    flex-direction: column;
    gap: calc(6 * var(--u));
    margin-top: calc(14 * var(--u));
}
#modal-overlay .modal-buttons .btn {
    box-sizing: border-box;
    width: 100%;
    height: calc(42 * var(--u));
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    border-radius: calc(13 * var(--u));
    font: inherit;
    font-size: calc(14.5 * var(--u));
    font-weight: var(--fw-bold);
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
}
/* Two buttons, two colours, each with a reason: the video is red because
   that is what YouTube is, and share is the game's green. Both lift off the
   sheet with a shadow in their own colour rather than a grey one, which is
   where the "pop" comes from without adding a third hue. */
#modal-overlay .modal-buttons .btn-share {
    background: var(--hit);
    color: var(--on-hit);
    box-shadow: 0 calc(4 * var(--u)) calc(12 * var(--u)) rgba(58, 160, 106, .30);
}
#modal-overlay .modal-buttons .btn-youtube {
    /* flex, not the grid the other buttons use: the glyph and the label have
       to sit together and be centred as a pair, not one per grid column. */
    display: flex;
    align-items: center;
    justify-content: center;
    gap: calc(8 * var(--u));
    background: var(--video);
    color: var(--on-badge);
    box-shadow: 0 calc(4 * var(--u)) calc(12 * var(--u)) rgba(224, 36, 36, .30);
}
#modal-overlay .modal-buttons .btn:active { transform: translateY(calc(1 * var(--u))); box-shadow: none; }
#modal-overlay .modal-buttons .btn-youtube::before {
    content: "\25B6";
    font-size: calc(11 * var(--u));
    line-height: 1;
}

/* ---- how to play ---- */

#help-overlay .help-title {
    margin: 0 calc(34 * var(--u)) calc(6 * var(--u)) 0;
    font-size: calc(20 * var(--u));
    font-weight: var(--fw-heavy);
    letter-spacing: var(--ls-tight);
    text-align: left;
}
#help-overlay .help-lede,
#help-overlay .help-note,
#help-overlay .help-footnote {
    margin: 0 0 calc(14 * var(--u));
    font-size: calc(12.5 * var(--u));
    line-height: var(--lh-body);
    color: var(--ink-soft);
    text-align: left;
}
#help-overlay .help-footnote { margin-bottom: 0; color: var(--ink-faint); }
#help-overlay .help-steps {
    margin: 0 0 calc(16 * var(--u));
    padding-left: calc(18 * var(--u));
    display: flex;
    flex-direction: column;
    gap: calc(7 * var(--u));
    font-size: calc(12.5 * var(--u));
    line-height: var(--lh-body);
    color: var(--ink-soft);
    text-align: left;
}
#help-overlay .help-examples {
    display: flex;
    flex-direction: column;
    gap: calc(9 * var(--u));
    margin: 0 0 calc(14 * var(--u));
}
#help-overlay .help-example {
    display: flex;
    align-items: center;
    gap: calc(10 * var(--u));
    text-align: left;
}
#help-overlay .help-example p {
    margin: 0;
    font-size: calc(12 * var(--u));
    line-height: 1.4;
    color: var(--ink-soft);
}
#help-overlay .help-card {
    box-sizing: border-box;
    width: calc(34 * var(--u));
    height: calc(44 * var(--u));
    flex: none;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    border-radius: calc(6 * var(--u));
    font-weight: var(--fw-heavy);
    line-height: 1;
    color: var(--on-badge);
}
#help-overlay .help-card-rank { font-size: calc(15 * var(--u)); }
#help-overlay .help-card-suit { font-size: calc(11 * var(--u)); }
/* These are the picker's cards, exactly -- the same shading, the same ink --
   because that is what the player is being taught to read. A legend that does
   not match the thing it describes is worse than no legend. The ink is per
   card, since each example is a fixed rank and suit. */
#help-overlay .help-card { border: 0; }   /* main.css draws a coloured one */
#help-overlay .help-card.green  { background: var(--card-hit);   color: var(--card-ink); }
#help-overlay .help-card.yellow { background: var(--card-near);  color: var(--card-near-ink); }
#help-overlay .help-card.known  { background: var(--card-known); color: var(--card-known-ink); }
/* The picker's own grey ink is a ghost on purpose -- an unavailable card
   should recede. An explanation of it should not, so this uses the history
   tiles' grey ink, which reads in both themes. */
#help-overlay .help-card.grey   { background: var(--surface-2);  color: var(--miss-ink); }

/* =====================================================================
 * The intro screen.
 *
 * The first thing anyone sees, so it is drawn on the same canvas and in the
 * same voice as the stage rather than the old green-pill styling in main.css.
 * ===================================================================== */

#intro-screen, #intro-screen * { box-sizing: border-box; }

/* main.css pads the body and centres it as a flex container; both push the
   card off-centre and make the page scroll. */
body:not(.game-active) {
    box-sizing: border-box;
    margin: 0;
    padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left);
    display: block;
    background: var(--surface-2);
}

#intro-screen {
    min-height: calc(100svh - var(--safe-top) - var(--safe-bottom));
    padding: calc(24 * var(--u)) calc(16 * var(--u));
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: left;
}
#intro-screen .intro-content {
    width: calc(370 * var(--u));
    max-width: 100%;
    padding: calc(28 * var(--u)) calc(24 * var(--u)) calc(24 * var(--u));
    background: var(--surface);
    border-radius: calc(18 * var(--u));
    box-shadow: var(--shadow-md);
    display: flex;
    flex-direction: column;
}
/* The ™ is a claim, not a word: small, raised, and quiet. Once per surface. */
.tm {
    font-size: 0.4em;
    font-weight: var(--fw-medium);
    vertical-align: super;
    margin-left: 0.15em;
    color: var(--ink-faint);
    letter-spacing: 0;
}
#intro-screen h1 {
    margin: 0 0 calc(10 * var(--u));
    font-size: calc(34 * var(--u));
    font-weight: var(--fw-heavy);
    letter-spacing: var(--ls-tight);
    line-height: 1;
    color: var(--ink);
    text-align: left;
}
#intro-screen .intro-tagline {
    margin: 0 0 calc(14 * var(--u));
    text-align: left;
    font-size: calc(14 * var(--u));
    line-height: var(--lh-body);
    color: var(--ink);
}
#intro-screen .intro-description {
    margin: 0 0 calc(8 * var(--u));
    text-align: left;
    font-size: calc(12.5 * var(--u));
    line-height: var(--lh-body);
    color: var(--ink-soft);
}
#intro-screen .intro-legend-preview {
    display: flex;
    flex-direction: column;
    gap: calc(8 * var(--u));
    padding: calc(14 * var(--u));
    margin: calc(12 * var(--u)) 0 calc(20 * var(--u));
    background: var(--surface-2);
    border-radius: calc(12 * var(--u));
}
#intro-screen .legend-item {
    display: flex;
    align-items: center;
    gap: calc(9 * var(--u));
}
#intro-screen .legend-item p {
    margin: 0;
    font-size: calc(11.5 * var(--u));
    line-height: 1.35;
    color: var(--ink-soft);
}
#intro-screen .legend-item p strong { color: var(--ink); font-weight: var(--fw-bold); }
#intro-screen .legend-color-box {
    width: calc(14 * var(--u));
    height: calc(14 * var(--u));
    flex: none;
    border-radius: calc(4 * var(--u));
}
#intro-screen .legend-color-box.green  { background: var(--hit); }
#intro-screen .legend-color-box.yellow { background: var(--near); }
#intro-screen .legend-color-box.grey   { background: var(--ink-ghost); }
#intro-screen .legend-color-box.known  { background: var(--card-red); }

#intro-screen #play-game-btn {
    width: 100%;
    height: calc(50 * var(--u));
    padding: 0;
    border: 0;
    border-radius: calc(13 * var(--u));
    background: var(--ink);
    color: var(--surface);
    font: inherit;
    font-size: calc(15 * var(--u));
    font-weight: var(--fw-bold);
    cursor: pointer;
}
#intro-screen #intro-help-btn {
    margin-top: calc(10 * var(--u));
    padding: calc(6 * var(--u)) 0;
    background: none;
    border: 0;
    font: inherit;
    font-size: calc(12.5 * var(--u));
    font-weight: var(--fw-medium);
    color: var(--ink-soft);
    text-decoration: none;
    cursor: pointer;
}
/* A short hairline between the intro's actions and the date, so the date
   reads as a footer to the card rather than a third button. */
#intro-screen .intro-date::before {
    content: "";
    display: block;
    width: calc(40 * var(--u));
    height: 1px;
    margin: 0 auto calc(12 * var(--u));
    background: var(--rule);
}
#intro-screen .intro-date {
    margin: calc(14 * var(--u)) 0 0;
    font-size: calc(12 * var(--u));
    font-weight: var(--fw-medium);
    letter-spacing: 0;
    text-transform: none;
    color: var(--ink-faint);
    text-align: center;
}

/* ------------------------------------------------- menu and settings sheets */

/* Both reuse .modal-overlay/.modal-content so there is one way a panel opens,
   one backdrop, one close button and one Escape handler. */
#settings-overlay .sheet-content {
    width: calc(320 * var(--u));
    max-width: calc(100% - 32px);
    padding: calc(22 * var(--u)) calc(20 * var(--u)) calc(18 * var(--u));
    text-align: left;
}
#settings-overlay .sheet-title {
    margin: 0 0 calc(14 * var(--u));
    font-size: calc(17 * var(--u));
    font-weight: var(--fw-heavy);
    color: var(--ink);
}
#settings-overlay .sheet-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: calc(14 * var(--u));
    padding: calc(12 * var(--u)) 0;
    border-top: 1px solid var(--rule-soft);
    cursor: pointer;
}
#settings-overlay .sheet-row strong {
    display: block;
    font-size: calc(14 * var(--u));
    font-weight: var(--fw-bold);
    color: var(--ink);
}
#settings-overlay .sheet-row small {
    display: block;
    margin-top: calc(2 * var(--u));
    font-size: calc(11.5 * var(--u));
    color: var(--ink-faint);
}
#settings-overlay .sheet-label { min-width: 0; }
#settings-overlay .sheet-row-stack { flex-direction: column; align-items: stretch; gap: calc(10 * var(--u)); cursor: default; }

/* ---- the segmented control ----
   Three buttons in one pill, for a setting with three states. Real buttons
   with the radio role, so arrow keys and a screen reader make sense of it. */
#settings-overlay .seg {
    display: flex;
    padding: calc(3 * var(--u));
    border-radius: calc(11 * var(--u));
    background: var(--surface-2);
}
#settings-overlay .seg button {
    flex: 1;
    padding: calc(8 * var(--u)) 0;
    border: 0;
    border-radius: calc(8 * var(--u));
    background: none;
    font: inherit;
    font-size: calc(13 * var(--u));
    font-weight: var(--fw-bold);
    color: var(--ink-faint);
    cursor: pointer;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
#settings-overlay .seg button.is-on {
    background: var(--surface);
    color: var(--ink);
    box-shadow: 0 calc(1 * var(--u)) calc(3 * var(--u)) rgba(var(--shadow-ink), .18);
}
#settings-overlay .seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ---- the switch ----
   A real <input type="checkbox"> with its own appearance removed and a track
   and knob drawn over it, so it keeps focus, Space, and the right role while
   looking like the switch it behaves as. */
#settings-overlay input.switch {
    flex: none;
    position: relative;
    -webkit-appearance: none;
    appearance: none;
    margin: 0;
    width: calc(46 * var(--u));
    height: calc(28 * var(--u));
    border-radius: var(--r-pill);
    background: var(--rule);
    transition: background var(--dur) var(--ease);
    cursor: pointer;
}
#settings-overlay input.switch::after {
    content: "";
    position: absolute;
    top: calc(2 * var(--u));
    left: calc(2 * var(--u));
    width: calc(24 * var(--u));
    height: calc(24 * var(--u));
    border-radius: var(--r-pill);
    background: var(--switch-knob);
    box-shadow: 0 calc(1 * var(--u)) calc(3 * var(--u)) rgba(var(--shadow-ink), .28);
    transition: transform var(--dur) var(--ease);
}
#settings-overlay input.switch:checked { background: var(--accent); }
#settings-overlay input.switch:checked::after {
    transform: translateX(calc(18 * var(--u)));
}
#settings-overlay input.switch:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: calc(2 * var(--u));
}

/* ---- the speed slider ----
   Five stops rather than a free scrub: the values are 0.5 to 1.5 in quarters,
   and a continuous slider would offer precision the timeline cannot use. */
#settings-overlay .speed-range {
    flex: none;
    width: calc(150 * var(--u));
    height: calc(28 * var(--u));
    -webkit-appearance: none;
    appearance: none;
    margin: 0;
    background: none;
    cursor: pointer;
}
#settings-overlay .speed-range::-webkit-slider-runnable-track {
    height: calc(5 * var(--u));
    border-radius: var(--r-pill);
    background: var(--rule);
}
#settings-overlay .speed-range::-moz-range-track {
    height: calc(5 * var(--u));
    border-radius: var(--r-pill);
    background: var(--rule);
}
#settings-overlay .speed-range::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: calc(22 * var(--u));
    height: calc(22 * var(--u));
    margin-top: calc(-8.5 * var(--u));
    border: 0;
    border-radius: var(--r-pill);
    background: var(--accent);
    box-shadow: 0 calc(1 * var(--u)) calc(3 * var(--u)) rgba(var(--shadow-ink), .28);
}
#settings-overlay .speed-range::-moz-range-thumb {
    width: calc(22 * var(--u));
    height: calc(22 * var(--u));
    border: 0;
    border-radius: var(--r-pill);
    background: var(--accent);
}
#settings-overlay .speed-range:focus-visible { outline: none; }
#settings-overlay .speed-range:focus-visible::-webkit-slider-thumb {
    outline: 2px solid var(--accent);
    outline-offset: calc(2 * var(--u));
}

/* Nothing to set the speed of while the animation is off. Disabled rather than
   hidden, so the row does not appear and disappear under the finger. */
#settings-overlay .sheet-row.is-disabled { opacity: .42; }
#settings-overlay .sheet-row.is-disabled .speed-range { cursor: not-allowed; }
#settings-overlay input:disabled { cursor: not-allowed; }
#settings-overlay #speed-readout {
    font-variant-numeric: tabular-nums;
    font-family: var(--font-mono);
}

/* Set when the OS asks for reduced motion, or when Settings does. Kept as a
   body class so one rule covers everything rather than each animation
   re-checking the media query. */
body.reduce-motion #stage *,
body.reduce-motion #modal-overlay *,
body.reduce-motion #help-overlay *,
body.reduce-motion #settings-overlay * {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
}


/* ==========================================================================
   main.css, themed
   ==========================================================================
   The old stylesheet carries its own colour variables and still owns the
   modals, the help overlay and a long tail of smaller rules. Overriding those
   one at a time would mean finding all of them, and missing one shows up as
   white text on a white panel the first night somebody uses dark mode.

   This re-points the legacy names at the tokens instead. play.css loads after
   main.css, so a :root here wins, and everything main.css still owns follows
   the theme without being touched.

   (The primary buttons are `background: var(--ink); color: var(--surface)` in
   four places. That is deliberate and wants no override: it flips with the
   theme, so the one primary action is a black pill on white and a near-white
   pill on near-black — the highest-contrast thing on the screen either way.)
   ========================================================================== */

:root {
    --color-text-primary:       var(--ink);
    --color-text-secondary:     var(--ink-soft);
    --color-background:         var(--surface-2);
    --color-container-bg:       var(--surface);
    --color-container-bg-inner: var(--surface-2);
    --color-border:             var(--rule);
    --color-card-bg:            var(--card-bg);
    --color-card-border:        var(--card-line);
    --color-card-red:           var(--card-red);
    --color-card-black:         var(--card-ink);
    --color-green-wordle:       var(--hit);
    --color-yellow-wordle:      var(--near);
    --color-grey-wordle:        var(--miss-ink);
    --color-known:              var(--known-ink);
    --color-error:              var(--act-fold);
    --color-disabled-btn:       var(--rule);
}
