/* In-game shell: full-bleed page, HUD bar, and the dock (side bar or sheet). */

/* The phone/desktop boundary is 899/900px, and it is the index of the phone UI:
   every rule that is the phone's and not the desktop's lives inside one of
   these blocks, and nothing outside them is phone-only.

     game-shell.css   min-width: 900px   the side bar, the foot under the board,
                                         and hiding the sheet head
                      max-width: 899px   the stage stacking, the rail, the
                                         sheet, the HUD reflow, the banner
     game.css         min-width: 900px   the Pick panel under the tabs, the
                                         board's room under the hold
                      max-width: 899px   the board's zoom scroll room, the
                                         Pick panel in the sheet, and
                                         the panel bits the rail already carries

   The same number is spelled in Go (components/layout.go, PhoneMax) and in JS
   (static/js/layout.js, RF_PHONE), because a CSS @media cannot read a custom
   property. Change one and change all three.

   One smaller boundary exists below it, in game.css, and it is narrow-phone
   fit rather than a change of layout:

     game.css         min-width: 360px   the order controls, the leg row

   The app chrome outside the game uses the same boundary:

     base.css         min-width: 900px   the top nav over the bottom nav, the
                                         page gutters
     home.css         min-width: 900px   the home page's two columns
     thread.css       min-width: 900px   the conversation's fixed height, the
                                         group drawer as a column, not a page
     game.css         min-width: 900px   the lobby's map preview, which is
                                         sized to the page because before the
                                         game starts it is a page, not a board

   (pointer: coarse) is a separate axis and cuts across all of this -- a touch
   laptop is wide and coarse, a narrow window is phone-width and fine. */

/* The game page does not scroll: the shell is exactly one viewport, and the
   map and the dock divide it between them. Saying so here means a stray pixel
   anywhere inside cannot turn the whole screen into a draggable surface. */
html:has(.game-fullbleed),
html:has(.game-fullbleed) body {
    overflow: hidden;
    overscroll-behavior: none;
}

/* A game is tapped fast, and a tap that lands just off a stepper's button would
   otherwise start Safari's double-tap zoom: it holds the tap back to wait for a
   second one, and a second one zooms the page. manipulation is meant to stop
   that, but iPhone Safari still zooms wherever pinch zoom is allowed, so the
   page gives up pinch zoom as well, as the map always has; the map keeps its
   own. Every element names it, not just the root: Safari does not take it from
   an ancestor. :where() keeps it below any element's own touch-action. */
:where(html:has(.game-fullbleed), html:has(.game-fullbleed) *) {
    touch-action: pan-x pan-y;
}

.content-wrapper.game-fullbleed {
    padding: 0;
    align-items: stretch;
}

.content-wrapper.game-fullbleed > * {
    max-width: none;
}

.game-fullbleed .game-page {
    max-width: none;
}

.game-shell {
    --dock-w: 392px;
    display: flex;
    flex-direction: column;
    /* svh, not dvh: the map and the dock split a fixed number, so neither
       resizes while mobile Chrome's toolbars slide in and out. */
    height: 100svh;
}

/* The page opts into viewport-fit=cover, so the sticky bar must reserve the top
   inset itself — without it the HUD renders under the iOS notch and taps on its
   left edge are swallowed by system chrome. */
.game-hud {
    anchor-name: --hud-stats-anchor;
    position: sticky;
    top: 0;
    z-index: 40;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--size-2);
    padding: var(--size-1) var(--size-3);
    padding-top: calc(var(--size-1) + env(safe-area-inset-top));
    background: var(--surface-2);
    border-bottom: 1px solid var(--border-1);
}

.game-hud-left,
.game-hud-right {
    display: flex;
    align-items: center;
    gap: var(--size-3);
    flex: 1 1 0;
    min-width: 0;
}

.game-hud-right {
    justify-content: flex-end;
}

.game-hud-game {
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: var(--size-2);
    line-height: 1.1;
}

.game-hud-game-label {
    font-size: var(--font-size-0);
    font-weight: var(--font-weight-7);
    color: var(--text-1);
    white-space: nowrap;
}

.game-hud-game-sub {
    font-size: var(--font-size-0);
    color: var(--text-2);
    white-space: nowrap;
}

.game-hud-group {
    display: inline-flex;
    color: var(--color-accent);
}

.game-hud-group .ui-icon {
    width: 1rem;
    height: 1rem;
}

.game-hud-scores {
    display: flex;
    align-items: center;
    gap: var(--size-2);
    flex-wrap: nowrap;
    justify-content: center;
    flex: 0 1 auto;
    min-width: 0;
}

/* The whole chip row is the table's toggle: a phone has no room for a separate
   control, and the chips are the thing the table expands on. The hover and
   active resets are not redundant — base.css paints every bare button with the
   accent, and those state rules outrank a plain class. */
.game-hud-scores-toggle,
.game-hud-scores-toggle:active {
    padding: 0;
    background: none;
    border: 0;
    color: inherit;
    font: inherit;
    cursor: pointer;
}

@media (hover: hover) {
    .game-hud-scores-toggle:hover {
        padding: 0;
        background: none;
        border: 0;
        color: inherit;
        font: inherit;
        cursor: pointer;
    }
}

@media (hover: hover) {
    .game-hud-scores-toggle:hover .game-hud-caret {
        color: var(--text-1);
    }
}

.game-hud-caret {
    flex-shrink: 0;
    color: var(--text-3);
    font-size: var(--font-size-0);
    line-height: 1;
    transition: transform 0.15s ease;
}

body:has(.game-hud-stats:popover-open) .game-hud-caret {
    transform: rotate(180deg);
}

/* Collapsed chip: colour, name, one number. Every other count lives in the
   table, so six seats still fit a phone's width. */
.game-hud-score {
    display: inline-flex;
    flex-direction: row;
    align-items: center;
    gap: 4px;
    padding: 3px var(--size-1);
    background: var(--surface-3);
    border-radius: var(--radius-2);
    border: 1.5px solid transparent;
    min-width: 0;
}

/* The stats table is a native popover in the top layer, so opening it lays the
   table over the board instead of pushing the board down. It is anchored to the
   HUD and spans its full width, which is why it takes an explicit anchor pair
   rather than the popover's implicit one. */
.game-hud-stats {
    display: none;
    position: fixed;
    inset: unset;
    position-anchor: --hud-stats-anchor;
    position-area: block-end span-all;
    width: anchor-size(width);
    max-width: 100vw;
    margin: 0;
    padding: var(--size-2) var(--size-3);
    overflow-x: auto;
    background: var(--surface-2);
    border: 0;
    border-bottom: 1px solid var(--border-1);
    box-shadow: var(--bevel);
}

.game-hud-stats:popover-open {
    display: block;
}

@media (prefers-reduced-motion: no-preference) {
    .game-hud-stats {
        opacity: 0;
        transform: translateY(-8px);
        transition:
            opacity 0.24s ease,
            transform 0.24s ease,
            overlay 0.24s ease allow-discrete,
            display 0.24s ease allow-discrete;
    }

    .game-hud-stats:popover-open {
        opacity: 1;
        transform: translateY(0);
    }

    @starting-style {
        .game-hud-stats:popover-open {
            opacity: 0;
            transform: translateY(-8px);
        }
    }
}

.game-hud-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fs-hud);
}

.game-hud-table th,
.game-hud-table td {
    padding: 3px var(--size-1);
    text-align: center;
    white-space: nowrap;
    font-weight: var(--font-weight-7);
    color: var(--text-1);
}

.game-hud-table thead th {
    color: var(--text-3);
    font-weight: var(--font-weight-5);
    border-bottom: 1px solid var(--border-1);
}

/* Icons are display:block app-wide, which text-align cannot move, so the header
   glyph needs to go inline before it will sit over its column's numbers. */
.game-hud-table thead .ui-icon {
    display: inline-block;
    width: 1em;
    height: 1em;
    vertical-align: middle;
}

/* The resolve-order rank. Narrow and quiet: position in the chip row already
   carries the order, and this column is only here to name what it means. */
.game-hud-table-rank {
    width: 2rem;
    text-align: left;
    color: var(--text-3);
}

.game-hud-table th.game-hud-table-name {
    text-align: left;
    width: 34%;
    font-weight: var(--font-weight-5);
    color: var(--text-2);
}

.game-hud-table-seat {
    display: flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
}

.game-hud-table tbody tr {
    border-bottom: 1px solid color-mix(in srgb, var(--border-1) 50%, transparent);
}

/* The viewer's own row. The lit left edge is what finds it at a glance: the
   background alone is one step of surface and reads as banding, not as "you". */
.game-hud-table-self {
    background: var(--surface-3);
}

/* Every row reserves the edge so lighting one does not shift its contents. */
.game-hud-table tbody tr > :first-child {
    border-left: 2px solid transparent;
}

.game-hud-table tbody tr.game-hud-table-self > :first-child {
    border-left-color: var(--color-accent);
}

.game-hud-table-self .game-hud-table-name {
    color: var(--text-1);
}

.game-hud-table--active .game-hud-table-name {
    color: var(--color-accent);
}

.game-hud-table-out td,
.game-hud-table-out .game-hud-table-name {
    color: var(--text-3);
}

.game-hud-table-tag {
    font-style: italic;
    font-weight: var(--font-weight-5);
    color: var(--text-3);
}

.game-hud-score--active {
    position: relative;
    border-color: var(--color-accent-muted);
}

/* The pulse's bright edge is its own layer, so only its opacity animates and
   the compositor runs it without repainting the chip. */
.game-hud-score--active::after {
    content: "";
    position: absolute;
    inset: -1.5px;
    border: 1.5px solid var(--color-accent-text);
    border-radius: inherit;
    opacity: 0;
    pointer-events: none;
}

/* The viewer's own seat, rimmed rather than labelled -- on a phone the chip is
   only a colour, so this is the one way to find your own place in the run. The
   rim goes on the troop, not the chip: the chip border already says "waiting on
   this seat", and two accents on one edge read as one. The table's row carries
   the same rim, so the mark is one thing in both places. It stands outside the
   troop's ink rim because the accent sits too close to the rust and brass seats
   to touch their fill. Hard-edged, so it reads as a line and not a glow. */
.game-hud-score-self .army-icon,
.game-hud-table-self .army-icon {
    filter:
        drop-shadow(0.5px 0 0 var(--color-accent))
        drop-shadow(-0.5px 0 0 var(--color-accent))
        drop-shadow(0 0.5px 0 var(--color-accent))
        drop-shadow(0 -0.5px 0 var(--color-accent));
}

.game-hud-name {
    font-size: var(--fs-hud);
    font-weight: var(--font-weight-5);
    color: var(--text-2);
}

/* Seat-colour swatch tying a HUD chip to that player's colour on the board.
   Geometry only: the fill comes from the game-scoped AccentClass. */
/* The seat's troop, in its colour, as the table's legend and the chip's label. */
.game-hud-fig {
    display: inline-flex;
    flex-shrink: 0;
    font-size: var(--fs-hud);
}

/* As tall as the name beside it, in the figure's own 12x16 proportion. */
.game-hud-fig .army-icon {
    width: 0.75em;
    height: 1em;
}

.game-hud-value {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    font-size: var(--fs-hud);
    font-weight: var(--font-weight-7);
    color: var(--text-1);
    white-space: nowrap;
}

.game-hud-table td.game-hud-stat-muted {
    font-weight: var(--font-weight-6);
    color: var(--text-3);
}

/* Applied by static/js/score-flash.js when a score's text changes in place. */
@media (prefers-reduced-motion: no-preference) {
    .game-hud-value-flash {
        animation: hud-value-flash 0.6s ease-out;
    }
}

@keyframes hud-value-flash {
    0% {
        color: var(--color-accent);
        transform: scale(1.18);
    }

    100% {
        color: var(--text-1);
        transform: scale(1);
    }
}

.game-hud-score--active .game-hud-name {
    color: var(--text-1);
}

.game-hud-sep {
    color: var(--text-2);
    font-size: var(--font-size-2);
}

.game-hud-back {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    background: transparent;
    color: var(--color-accent-muted);
    border: none;
    border-radius: var(--radius-1);
    text-decoration: none;
    flex-shrink: 0;
}

.game-hud-back .ui-icon {
    width: 1.25rem;
    height: 1.25rem;
}

/* Hit-area expander: grows the tap target to ~44px without changing the HUD's
   visual size or layout. */
.game-hud-back::after {
    content: "";
    position: absolute;
    inset: -6px;
}

@media (hover: hover) {
    .game-hud-back:hover {
        color: var(--text-1);
    }
}

.game-hud-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    background: transparent;
    color: var(--text-2);
    border: none;
    border-radius: var(--radius-1);
    font-size: var(--font-size-1);
}

@media (hover: hover) {
    .game-hud-icon:hover {
        background: var(--surface-3);
        color: var(--text-1);
    }
}

/* .popover-menu / .popover-menu-item live in base.css, shared with the site
   header's user menu. */

/* Chrome/backdrop/animation come from .modal-popover (base.css); only size +
   scroll layout live here. */
.game-info-modal {
    position: fixed;
    inset: 0;
    width: min(640px, 92vw);
    height: 80vh;
    max-height: 80vh;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 0 var(--size-4) var(--size-4);
}

.game-info-head {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--size-3);
    padding: var(--size-4) 0 var(--size-3);
    background: var(--surface-2);
    border-bottom: 1px solid var(--border-1);
}

.game-info-title {
    margin: 0;
    font-size: var(--font-size-4);
}

.game-info-close {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    padding: 0;
    flex-shrink: 0;
    background: transparent;
    color: var(--text-2);
    border: none;
    border-radius: var(--radius-1);
    font-size: var(--font-size-2);
    cursor: pointer;
}

@media (hover: hover) {
    .game-info-close:hover {
        background: var(--surface-3);
        color: var(--text-1);
    }
}

.game-info-overview {
    margin: var(--size-3) 0 0;
    color: var(--text-2);
}

.game-info-section {
    margin-top: var(--size-4);
    /* Clears the modal's sticky header, for its jump menu. */
    scroll-margin-top: 5rem;
}

.game-info-section-head {
    margin: 0 0 var(--size-2);
    font-size: var(--font-size-2);
    color: var(--text-1);
}

/* The HUD glyph a section explains, sized to the heading it leads. */
.game-info-section-head .ui-icon {
    display: inline-block;
    width: 1em;
    height: 1em;
    margin-right: var(--size-1);
    vertical-align: -0.125em;
}

.game-info-section p {
    margin: 0 0 var(--size-2);
    color: var(--text-2);
    line-height: 1.5;
}

/* A group is a second key inside one section -- the hand cards under the base
   orders -- so the two stay one place in the modal rather than two headings. */
.game-info-group {
    margin-top: var(--size-4);
}

.game-info-group-head {
    margin: 0 0 var(--size-2);
    font-size: var(--font-size-1);
    color: var(--text-1);
}

.game-info-group p {
    margin: 0 0 var(--size-2);
    color: var(--text-2);
    line-height: 1.5;
}

.game-info-bullets {
    margin: 0;
    padding-left: var(--size-5);
    color: var(--text-2);
}

.game-info-bullets li {
    margin-bottom: var(--size-1);
    line-height: 1.5;
}

.game-info-legend {
    margin: var(--size-3) 0 var(--size-3);
    display: grid;
    gap: var(--size-2);
}

.game-info-legend-row {
    display: flex;
    align-items: flex-start;
    gap: var(--size-3);
}

.game-info-legend-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 2rem;
    height: 2rem;
    margin: 0;
    background: var(--surface-2);
    border: 1px solid var(--border-1);
    border-radius: var(--radius-2);
    color: var(--text-1);
}

.game-info-legend-text {
    margin: 0;
    color: var(--text-2);
    line-height: 1.5;
}

.game-info-legend-sub {
    margin: var(--size-2) 0 0;
    padding-left: var(--size-4);
    display: grid;
    gap: var(--size-1);
}

.game-info-legend-sub li {
    line-height: 1.4;
}

.game-info-legend-term {
    color: var(--text-1);
    font-weight: var(--font-weight-7);
}

/* Every row states its numbers in the same place, so the term line reads
   "Move  3 slots - 0 gold" and the rule itself starts on its own line. */
.game-info-legend-meta {
    margin-left: var(--size-2);
    color: var(--text-3);
    font-style: italic;
    font-size: var(--font-size-0);
}

.game-info-legend-body {
    display: block;
}

/* Everything under the HUD: the board area takes what the dock leaves. Both
   are in flow, so the map can never end up beneath the sheet. */
.game-stage {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
}

.game-board-area {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    /* Anchor for the floating map controls. */
    position: relative;
    /* Soft table light: slightly above centre of the borderless board. */
    background: var(--light-table);
}

/* The lobby's body is a tall card, so it scrolls inside the area. */
.lobby-area {
    overflow-y: auto;
}

.game-board {
    display: block;
    width: 100%;
    margin: 0 auto;
}

/* The dock: side bar on a wide screen, bottom sheet on a narrow one. Contents
   are the same either way — a tab strip over one scrolling body. */
/* The game's dock carries popover="manual" for the phone sheet, and the UA's
   popover rules are not scoped to :popover-open — only its display is. So every
   one of them is undone here, and the narrow-screen block below puts back the
   ones the sheet actually wants. Without this the side bar would be a fit-content
   box floating in the middle of a wide screen. */
.game-dock {
    --dock-bar-h: 44px;
    position: static;
    inset: auto;
    width: auto;
    height: auto;
    margin: 0;
    padding: 0;
    border: 0;
    overflow: visible;
    color: inherit;
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--surface-1);
}

/* The dock's one fixed row: the tabs, plus the open/shut chevron. On a narrow
   screen this bar is the whole dock while the drawer is shut. */
.game-dock-bar {
    display: flex;
    flex: none;
    align-items: stretch;
    min-height: var(--dock-bar-h);
    border-block-end: var(--rule-1);
}

/* The lobby has one pane, so it gets a name instead of tabs. */
.game-dock-title {
    flex: 1 1 auto;
    display: inline-flex;
    align-items: center;
    padding-inline: var(--size-3);
    color: var(--text-2);
}

.game-dock-tab {
    flex: 1 1 0;
    min-height: var(--dock-bar-h);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--size-2);
    position: relative;
    padding: var(--size-2);
    background: none;
    border: none;
    border-block-end: 2px solid transparent;
    color: var(--text-2);
    font: inherit;
    cursor: pointer;
}

.game-dock-tab:active {
    background: var(--surface-2);
    color: var(--text-1);
}

@media (hover: hover) {
    .game-dock-tab:hover {
        background: var(--surface-2);
        color: var(--text-1);
    }
}

.game-dock-tab.is-on {
    color: var(--color-accent-text);
    border-block-end-color: var(--color-accent);
}

/* On the tab the dot sits beside the label, not pinned to a corner. */
.game-dock-tab .game-chat-unread {
    position: static;
}

.game-dock-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: var(--size-3);
    /* The phase panels were written for the old centred controls card. */
    text-align: center;
}

.game-dock-body--flush {
    padding: 0;
    text-align: start;
}

/* Carried over from the retired .game-controls card: the panels' buttons are
   sized by their container, and several .plan rules in game.css exist only
   to out-specify this one. */
.game-dock-body button {
    padding: var(--size-1) var(--size-3);
    font-size: var(--font-size-1);
}

@media (min-width: 900px) {
    .game-dock {
        flex: 0 0 var(--dock-w);
        border-inline-start: var(--rule-1);
    }

    /* A foot stands under the board alone; the side bar keeps the full height. */
    .game-stage--foot {
        display: grid;
        grid-template-columns: minmax(0, 1fr) var(--dock-w);
        grid-template-rows: minmax(0, 1fr) auto;
    }

    .game-stage--foot > .game-dock {
        grid-column: 2;
        grid-row: 1 / -1;
    }

    .game-rail.game-foot {
        display: flex;
        padding: var(--size-3);
        background: var(--surface-1);
        border-block-start: var(--rule-strong);
    }

    .game-foot .rail-acts {
        display: none;
    }

    /* The side bar is always open, so the chevron has nothing to say — and a
       bar holding only the lobby's title would be a rule with no purpose. */
    .game-dock-bar:not(:has(.game-dock-tab)) {
        display: none;
    }
}

/* ------------------------------------------------------------------------
   The phone. One block, so this is the whole of what the narrow screen does
   differently: the stage stacks, the rail takes the bottom strip, and the dock
   stops being a side bar — it becomes a sheet raised over the map and put away
   again, one height whatever is in it.

   A Foot (the lobby's) is the rail here too.
   ------------------------------------------------------------------------ */

.game-rail {
    display: none;
}

@media (max-width: 899px) {
    /* The stage is the board and the rail stacked. The board grows into
       whatever the rail leaves and never shrinks past its basis — the sheet is
       a popover over the top, so it takes no room off this. */
    .game-stage {
        flex-direction: column;
    }

    .game-board-area {
        flex: 1 0 40px;
    }

    /* The sheet grows up out of the rail, so it always covers the map's
       bottom-left corner — which is exactly where the zoom buttons sit. They
       come back when the sheet goes away. */
    .is-dock-open .zoom-bar {
        display: none;
    }

    .game-shell--rail {
        --rail-h: calc(64px + env(safe-area-inset-bottom));
        /* The one ceiling a sheet stands at. Half the screen, so the map keeps
           the other half whatever is up. The board's zoom scroll room reads it
           too (game.css), which is why it is a property and not a literal. */
        --sheet-max-h: 50svh;
    }

    /* The sheet is a popover, so it lives in the top layer and is laid out
       against the viewport — position:fixed, and the UA's inset:0 + margin:auto
       undone.

       Both edges are anchored rather than guessed. The rail wraps to two rows
       when a phase hands it enough chips, so a fixed --rail-h put the sheet over
       it; anchor(--game-rail top) sits the sheet exactly on the rail whatever it
       stands at. --rail-h stays as the fallback for a browser without anchors.
       The top edge is the HUD's, which is no more a fixed number. */
    .game-shell--rail .game-dock {
        position: fixed;
        inset: auto 0 var(--rail-h) 0;
        bottom: anchor(--game-rail top);
        /* The UA gives a popover width/height: fit-content and margin: auto.
           Left and right alone would not stretch it, so width is spelled out. */
        width: auto;
        margin: 0;
        z-index: 3;
        flex: none;
        /* One height for every sheet: as tall as what is in it, up to half the
           map, scrolling past that — which the dock body already does. The
           territory card, the order being built, the hand, the plan and the
           activity feed all stand the same way, so there is nothing to pick
           between and nothing for a class to decide. */
        height: auto;
        --sheet-min: 0px;
        --sheet-max: var(--sheet-max-h);
        min-height: var(--sheet-min);
        max-height: var(--sheet-max);
        /* The open and shut animation is the sheet's own height, so what hangs
           below the cut has to be hidden while it rises. */
        overflow: hidden;
        padding-block-end: 0;
        border: 0;
        border-block-start: var(--rule-strong);
    }

    /* The popover's own state, not .is-dock-open: the effect that calls
       showPopover is what reconciles the two, and the transition below needs a
       real :popover-open to start from. */
    .game-shell--rail .game-dock:not(:popover-open) {
        display: none;
    }

    .game-shell--rail .game-dock:popover-open {
        display: flex;
    }

    /* The sheet is read over a map that stays bright: the next tap is aimed at
       the ground behind it, so nothing is darkened. */
    .game-shell--rail .game-dock::backdrop {
        background: none;
    }

    /* The rail says what the tabs said, so the bar has nothing left to do. */
    .game-shell--rail .game-dock-bar {
        display: none;
    }

    /* The activity pane sizes itself with height: 100%, and a percentage height
       inside a stretched flex item does not resolve — so it stopped at its own
       content and left dead room under the chat box. A column and a grow say the
       same thing in terms the layout can answer. */
    .game-shell--rail .game-dock-body--flush {
        display: flex;
        flex-direction: column;
    }

    .game-shell--rail .game-dock-body--flush > .game-history-wrap {
        flex: 1 1 auto;
        height: auto;
    }

    .game-rail {
        anchor-name: --game-rail;
        flex: none;
        display: flex;
        align-items: center;
        gap: var(--size-2);
        min-height: var(--rail-h);
        padding-inline: var(--size-2);
        padding-block: var(--size-1) calc(var(--size-1) + env(safe-area-inset-bottom));
        background: var(--surface-1);
        border-block-start: var(--rule-strong);
    }

    /* The two halves: what you hold on the left, what you may open on the
       right. The buttons keep their full tap size against a left half that is
       two short rows, so the rail centres them against it. */
    .rail-acts {
        flex: none;
        display: flex;
        align-items: center;
        gap: var(--size-2);
    }

    /* Glyph-only, because the rail is 52px of a phone screen: the name is in the
       title and the aria-label. The badge is the one number each carries — orders
       given, cards held — and the chat dot rides the activity glyph. */
    .game-rail-btn {
        flex: none;
        position: relative;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: var(--tap-min);
        height: var(--tap-min);
        padding: 0;
        background: var(--surface-2);
        border: var(--rule-1);
        border-radius: var(--radius-1);
        color: var(--text-2);
        cursor: pointer;
    }

    .game-rail-btn:active:not(:disabled) {
        background: var(--surface-3);
        color: var(--text-1);
    }

    @media (hover: hover) {
        .game-rail-btn:hover:not(:disabled) {
            background: var(--surface-3);
            color: var(--text-1);
        }
    }

    .game-rail-btn.is-on {
        background: var(--surface-3);
        color: var(--text-1);
    }

    .game-rail-btn:disabled {
        opacity: 0.45;
        cursor: default;
    }

    .game-rail-btn .ui-icon {
        width: 1.35em;
        height: 1.35em;
    }

    .game-rail-badge {
        position: absolute;
        inset-block-start: -6px;
        inset-inline-end: -6px;
        min-width: 1.25em;
        padding-inline: 0.25em;
        background: var(--surface-4);
        border: var(--rule-1);
        border-radius: var(--radius-1);
        color: var(--text-1);
        font-size: var(--fs-0);
        font-variant-numeric: tabular-nums;
        line-height: 1.3;
    }

    /* The sheet grows up out of the rail and sinks back into it. Its own height
       is what moves, not a transform: the sheet is in the top layer and would
       paint over the rail on the way past it, and coming out of the rail is the
       whole gesture. overlay and display are allow-discrete so the top layer and
       display:none wait for the slide instead of cutting it. */
    @media (prefers-reduced-motion: no-preference) {
        .game-shell--rail .game-dock {
            min-height: 0;
            max-height: 0;
            transition:
                min-height var(--sheet-slide) ease,
                max-height var(--sheet-slide) ease,
                overlay var(--sheet-slide) ease allow-discrete,
                display var(--sheet-slide) ease allow-discrete;
        }

        .game-shell--rail .game-dock:popover-open {
            min-height: var(--sheet-min);
            max-height: var(--sheet-max);
        }

        @starting-style {
            .game-shell--rail .game-dock:popover-open {
                min-height: 0;
                max-height: 0;
            }
        }
    }

    /* Activity is a slide-out from the right edge, full height, not a sheet
       off the rail: it is read beside the map, the way it was before the dock. */
    .game-shell--rail.is-dock-side .game-dock {
        inset: 0 0 0 auto;
        width: min(360px, 85vw);
        --sheet-min: 0px;
        --sheet-max: none;
        min-height: 0;
        max-height: none;
        padding-block-end: env(safe-area-inset-bottom);
        border: 0;
        border-inline-start: var(--rule-strong);
    }

    @media (prefers-reduced-motion: no-preference) {
        .game-shell--rail.is-dock-side .game-dock {
            transform: translateX(100%);
            transition:
                transform var(--sheet-slide) ease,
                overlay var(--sheet-slide) ease allow-discrete,
                display var(--sheet-slide) ease allow-discrete;
        }

        .game-shell--rail.is-dock-side .game-dock:popover-open {
            min-height: 0;
            max-height: none;
            transform: none;
        }

        @starting-style {
            .game-shell--rail.is-dock-side .game-dock:popover-open {
                transform: translateX(100%);
            }
        }
    }
}

/* The sheet's head: what is up on the left, the way out on the right. It is
   the phone's alone — it stands in for the tab strip, which the rail replaced.
   The wide dock keeps its tab strip, so the head there would only say the same
   thing twice; everything it carries has a wide home in the dock body. */
.game-sheet-head {
    flex: none;
    display: flex;
    align-items: center;
    gap: var(--size-2);
    min-height: var(--dock-bar-h);
    padding-inline: var(--size-3);
    border-block-end: var(--rule-1);
}

@media (min-width: 900px) {
    /* The side bar is never a drawer: nothing shuts it, the tab strip already
       names the panel, and the rest of what the head holds is rendered again
       in the dock body under .desktop-only (game.css). */
    .game-sheet-head {
        display: none;
    }
}

.game-sheet-title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text-2);
}

.game-sheet-close {
    flex: none;
    margin-inline-start: auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--tap-min);
    height: var(--tap-min);
    margin-inline-end: calc(var(--size-2) * -1);
    padding: 0;
    background: none;
    border: none;
    color: var(--text-2);
    font: inherit;
    cursor: pointer;
}

.game-sheet-close:active {
    color: var(--text-1);
}

@media (hover: hover) {
    .game-sheet-close:hover {
        color: var(--text-1);
    }
}

/* The confirm-and-abandon pair that stands where the ✕ does while something is
   being built in the sheet. Unlike the ✕ it shows at every width: it is the
   action, not the way out of a drawer. */
.game-sheet-acts {
    flex: none;
    margin-inline-start: auto;
    display: inline-flex;
    align-items: center;
    gap: var(--size-2);
}

/* The loader covers both buttons, so they sit in one SubmitOverlay. Its content
   box lays them out as the pair. */
.game-sheet-acts > .submit-scope > .submit-content {
    display: inline-flex;
    align-items: center;
    gap: var(--size-2);
}

/* Compact text buttons, not glyphs: what they do is worth a word each. The
   tap target is kept by the coarse-pointer rule at the foot of this file. */
.game-sheet-act {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 1.75rem;
    padding-inline: var(--size-2);
    background: none;
    border: var(--rule-1);
    color: var(--text-2);
    font-family: var(--font-body);
    font-size: var(--font-size-0);
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
}

/* One of the two is the thing you came to do, so only it is drawn as a button:
   a filled accent pill. Two outlined buttons side by side made a red-and-green
   pair that asked to be read before either could be picked. */
.game-sheet-act-ok {
    border: none;
    border-radius: var(--radius-round);
    padding-inline: var(--size-3);
    background: var(--color-accent);
    color: var(--text-on-color);
    font-weight: var(--font-weight-6);
}

/* Backing out is always there and never the point, so it is a word, not a box.
   The destructive ink waits for the pointer. */
.game-sheet-act-no {
    border: none;
    color: var(--text-3);
}

@media (hover: hover) {
    .game-sheet-act-ok:hover:not(:disabled) {
        background: color-mix(in srgb, var(--color-accent) 85%, white);
    }
}

.game-sheet-act-ok:disabled {
    background: var(--surface-3);
    color: var(--text-4);
    cursor: default;
}

.game-history-wrap {
    display: flex;
    flex-direction: column;
    min-height: 0;
    height: 100%;
}

@media (prefers-reduced-motion: no-preference) {
    .game-hud-score--active::after {
        animation: hud-active-pulse 2s ease-in-out infinite;
    }
}

@keyframes hud-active-pulse {
    0%,
    100% {
        opacity: 0;
    }

    50% {
        opacity: 1;
    }
}

.game-error {
    color: var(--color-destructive);
    font-size: var(--font-size-1);
    margin: var(--size-2) 0 0;
}

.game-history-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--size-2);
    padding: var(--size-2) var(--size-3);
    background: var(--surface-1);
    border-bottom: 1px solid color-mix(in srgb, var(--border-1) 60%, transparent);
}

.game-chat-thread-wrap {
    position: relative;
    display: flex;
    flex: 0 1 auto;
    min-width: 0;
    max-width: 60%;
}

.game-chat-thread {
    display: flex;
    align-items: center;
    gap: var(--size-1);
    width: 100%;
    min-width: 0;
    padding: var(--size-1) var(--size-2);
    background: var(--surface-2);
    color: var(--text-1);
    border: 1px solid var(--border-1);
    border-radius: var(--radius-2);
    font-family: inherit;
    font-size: var(--font-size-0);
    font-weight: var(--font-weight-6);
    text-align: left;
    cursor: pointer;
}

/* Undo the global button accent hover: this is a quiet control. */
.game-chat-thread:active {
    background: var(--surface-3);
}

@media (hover: hover) {
    .game-chat-thread:hover {
        background: var(--surface-3);
    }
}

.game-chat-thread > .loader-label {
    display: flex;
    align-items: center;
    gap: var(--size-1);
    flex: 1;
    min-width: 0;
}

.game-chat-thread-label {
    min-width: 0;
}

.game-chat-thread-caret {
    flex: none;
    margin-left: auto;
    width: 0;
    height: 0;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 4px solid var(--text-3);
}

#game-chat-thread-menu {
    max-width: min(20rem, 80vw);
    padding: var(--size-1);
    gap: 0;
}

#game-chat-thread-menu .game-chat-thread-item {
    padding: var(--size-1) var(--size-2);
    font-size: var(--font-size-0);
    line-height: var(--font-lineheight-1);
}

.game-chat-thread-item {
    min-width: 0;
    font-family: inherit;
}

.game-chat-thread-item > .name-ellipsis {
    flex: 1 1 auto;
}

/* Absolute on the trigger, where it hangs off the corner; a normal trailing
   marker in a menu row. */
.game-chat-thread-item .game-chat-unread {
    position: static;
    margin-left: auto;
    flex: none;
}

/* No seat colour, no dot: the empty slot keeps the labels aligned. */
.game-chat-thread-dot-all {
    background: transparent;
}

/* A chat is named by its members, so its marker is a cluster of their seat dots
   rather than one. Overlapped, so three dots cost about the width of two. */
.game-chat-thread-dots {
    flex: none;
    display: flex;
    align-items: center;
}

.game-chat-thread-dots .seat-dot + .seat-dot {
    margin-left: -3px;
}

.game-chat-thread-new {
    color: var(--text-2);
    font-style: italic;
}

.game-chat-thread-plus {
    flex: none;
    width: var(--size-3);
    text-align: center;
    font-style: normal;
}

/* The member picker inside the New chat dialog. */
.game-chat-pick {
    margin-bottom: var(--size-5);
    max-height: 40vh;
    overflow-y: auto;
    text-align: left;
}

.game-chat-pick-row {
    gap: var(--size-2);
    padding-block: var(--size-1);
    cursor: pointer;
}

.game-chat-pick-box {
    flex: none;
    accent-color: var(--color-accent);
}

.game-history-toggle {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: var(--size-2);
    font-size: var(--font-size-0);
    color: var(--text-2);
    cursor: pointer;
    user-select: none;
}

.game-history-toggle-input {
    position: absolute;
    opacity: 0;
    width: 0;
    height: 0;
}

.game-history-toggle-input:checked ~ .game-history-toggle-label {
    color: var(--text-1);
}

.game-chat-unread {
    position: absolute;
    top: -4px;
    left: -4px;
    width: 10px;
    height: 10px;
    border-radius: var(--radius-token);
    background: var(--color-accent);
}

.game-chat-unread--thread {
    top: -3px;
    left: auto;
    right: -3px;
}

@media (prefers-reduced-motion: no-preference) {
    .game-chat-unread {
        animation: chat-unread-pulse 1.8s ease-out infinite;
    }
}

@keyframes chat-unread-pulse {
    0% {
        box-shadow: 0 0 0 0 color-mix(in srgb, var(--color-accent) 55%, transparent);
    }
    70% {
        box-shadow: 0 0 0 9px transparent;
    }
    100% {
        box-shadow: 0 0 0 0 transparent;
    }
}

@media (max-width: 899px) {
    .game-hud {
        flex-wrap: wrap;
        gap: var(--size-2);
        padding: var(--size-2);
    }

    .game-hud-left {
        flex: 1 1 auto;
    }

    .game-hud-right {
        flex: 0 0 auto;
    }

    .game-hud-scores {
        order: 3;
        flex: 1 1 100%;
        justify-content: center;
        flex-wrap: nowrap;
        gap: var(--size-1);
        min-width: 0;
    }

    .game-hud-scores:empty {
        display: none;
    }

    /* Six names cannot fit a phone's width, so the chip keeps only the seat
       colour and its headline number. The table below carries the names. */
    .game-hud-score .game-hud-name {
        display: none;
    }

    /* Chips size to their contents and shrink only when a full table needs the
       room, so two players sit centred together rather than stretched apart. */
    .game-hud-score {
        padding: 2px 4px;
        gap: 3px;
        justify-content: center;
        width: auto;
        flex: 0 1 auto;
        min-width: 0;
        max-width: 7.5rem;
    }

    .game-hud-stats {
        padding: var(--size-2);
    }

    .game-hud-game {
        padding-left: var(--size-2);
    }
}

/* The Activity tab's body: fills the dock, no chrome of its own. */
.game-history-panel {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

/* column-reverse + newest-first items keeps the list pinned to the bottom */
.game-activity-feed {
    display: flex;
    flex-direction: column-reverse;
    gap: var(--size-1);
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: var(--size-2);
}

.game-history-row {
    display: flex;
    align-items: flex-start;
    flex-wrap: wrap;
    gap: var(--size-2);
    min-height: 28px;
    padding-block: var(--size-1);
    flex-shrink: 0;
    border-top: 1px solid color-mix(in srgb, var(--border-1) 45%, transparent);
}

/* column-reverse feed: the DOM-last row renders at the top — no divider above it */
.game-history-row:last-child {
    border-top: none;
}

.game-history-name {
    flex: 0 0 8rem;
    min-width: 72px;
    max-width: 8rem;
    color: var(--text-3);
    font-size: var(--font-size-0);
    font-weight: var(--font-weight-5);
}

.game-history-cards {
    display: flex;
    align-items: center;
    gap: var(--size-1);
    flex-wrap: wrap;
    flex: 1 1 0;
    min-width: 0;
}

.game-history-action {
    color: var(--text-1);
    font-size: var(--font-size-0);
}

.game-history-action--win {
    color: color-mix(in srgb, var(--color-success) 65%, var(--text-1));
}

.game-history-action--muted {
    color: var(--text-2);
}

.game-history-note {
    color: var(--text-2);
    font-size: var(--font-size-0);
    font-style: italic;
}

.game-history-time {
    margin-left: auto;
    color: var(--text-2);
    font-size: var(--font-size-00);
    font-weight: var(--font-weight-4);
    white-space: nowrap;
    opacity: 0.7;
}

.game-history-empty {
    color: var(--text-2);
    font-size: var(--font-size-0);
    text-align: center;
    margin: 0;
    padding: var(--size-4);
}

/* Zero-size signal carrier for the peer-name map — must stay in the layout tree
   but take no room. */
.game-chat-peers {
    display: none;
}

.game-chat-form {
    display: flex;
    gap: var(--size-2);
    padding: var(--size-2);
    padding-bottom: calc(var(--size-3) + env(safe-area-inset-bottom));
    background: var(--surface-1);
    border-top: 1px solid color-mix(in srgb, var(--border-1) 60%, transparent);
}

/* Composing to a peer tints the whole compose row, a persistent cue that the
   next message is private. */
.game-chat-form--to-peer {
    background: color-mix(in srgb, var(--color-private) 10%, var(--surface-1));
}

.game-chat-form--to-peer .chat-input {
    border-color: color-mix(in srgb, var(--color-private) 40%, var(--border-1));
}

@media (pointer: coarse) {
    /* A word in the head is a small target, so the box around it grows. */
    .game-sheet-act {
        min-height: var(--tap-min);
        padding-inline: var(--size-3);
    }
}

@media (hover: none) and (pointer: coarse) {
    .game-chat-thread {
        font-size: 16px;
        font-weight: var(--font-weight-4);
        padding: 2px var(--size-2);
    }
}

/* One line under the HUD naming what this seat owes right now. A narrow
   screen's only: a wide one has the dock open beside the board, which says the
   same thing at length. In flow, not over the map, so it cannot cover ground
   the next tap is aimed at. */
.game-banner {
    display: none;
    flex: 0 0 auto;
    padding: var(--size-1) var(--size-3);
    background: var(--surface-3);
    border-bottom: 1px solid var(--border-1);
    color: var(--text-2);
    font-family: var(--font-body);
    font-size: var(--fs-hud);
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.game-banner--turn {
    background: var(--color-attention-bg);
    color: var(--text-1);
}

@media (max-width: 899px) {
    .game-banner {
        display: block;
    }
}

/* How the game is won, under the stats table. */
.game-hud-note {
    margin: var(--size-1) 0 0;
    font-family: var(--font-body);
    font-style: italic;
    font-size: var(--fs-hud);
    color: var(--text-2);
    white-space: normal;
}
