/* The board palette. The map pages (.atlas) and the home cards (.home) draw
   the same boards and seat colours outside a game, so they share it. */
.game-shell,
.atlas,
.home {
    width: 100%;
    /* The pigment a player picks from in setup, used for army markers, badge
       rings, order arrows and list dots. Longer than the 6-player cap so the
       last seat to choose still has a choice; the first six are the seat-order
       defaults.
       Chosen for separation first and named second: every one of the 45 pairs
       clears deltaE2000 15 under normal, deuteranope and protanope vision.
       That only holds because the set spans L* 42-93 — lightness is carrying
       as much of the work as hue, and the hues are held near their names.
       Re-run that check before editing any entry. */
    --seat-0: #f8eacb; /* bone */
    --seat-1: #6d96d1; /* indigo */
    --seat-2: #ffcc68; /* brass */
    --seat-3: #03d2c9; /* verdigris */
    --seat-4: #a3828f; /* wine */
    --seat-5: #97aa82; /* moss */
    --seat-6: #c77e2d; /* rust */
    --seat-7: #6a58a5; /* tyrian */
    --seat-8: #785d52; /* hide */
    --seat-9: #8e5700; /* ochre */
    /* Parchment land on a cold sea. The sea has to stay clearly a different
       substance from the page ground behind it, so it runs cool while
       everything else in the hall runs warm — luminance alone was not enough
       separation, hue is doing the work.
       --ink is the near-black *behind text*: label halos and army plates.
       It used to be --ocean doing both jobs, which meant the sea could not
       be lifted without washing out every numeral on the board. */
    --ink: #0f0c09;
    --ocean: #1e2a37;
    --land: #6b563a;
    --extrude: #121a22;
    --coast: #8a6d47;
    --ridge: #8b8272;
    /* Creep holds ground for nobody, so it is the one marker outside the seat
       set, and it is held to the same bar: deltaE2000 15 from all ten pigments
       under normal, deuteranope and protanope vision. The seats now fill
       L* 42-93, so no neutral inside that band can clear it — this one sits
       below the whole set at L* 22 and clears 19.5. Re-run that check against
       every seat if either this or the palette moves. */
    --creep: #34343e;
    /* Ink rims vanish against that fill, so a garrison takes a cold pale rim to
       stand off the sea and dark land. Cold to stay apart from a beast's brass;
       a rim is not a fill, so it sits outside the seat check above. */
    --creep-rim: #c4c9d6;
    /* A beast is creep that fights as one body. It keeps the creep fill and
       takes a lit brass rim, figure and shield both, so it reads apart from
       bandits at a glance without borrowing a seat pigment. */
    --beast: #ddb264;
    /* An order is coloured by what it does: cool between your own territories,
       ember into someone else's. Arrow, selection rings and the staging line
       all read --tone. */
    --move: #7c93c4;
    --attack: #c2593c;
    --tone: var(--move);
    /* Region palette. These are the land tint itself — a region's territories
       are filled with --region, not with --land — so the set has to separate
       as six, ten or twelve large flat areas while still reading as one map.
       Every one of the 66 pairs clears deltaE2000 8, and the 15 pairs among the
       first six clear 11. That only holds because the set spans L* 30-68:
       lightness carries the separation, hue only names it. The same check keeps
       every region at least deltaE 9 from every seat pigment, so a region tint
       never reads as an army marker, and at least deltaE 15 from --ocean, so
       the dark and cool regions never read as sea.
       --region-N-ink is the same hue lifted: region outlines and stamps. It
       is the lighter half of the internal border too, so it is held at least
       deltaE 12 from its own fill. The inks separate from each other by 8.
       Order matters — a map's first paying regions take the low slots, which
       are the better-separated half of the set, so the ten-region inks are the
       ones carrying the tightest margins. Two slots are spoken for by what a
       region pays (mapdef.SlotNoBonus, mapdef.SlotCostly): cinder for nothing,
       sand, the lightest, for a cost.
       Re-run all of those checks before editing any entry. */
    --region-0: #5a492c;   --region-0-ink: #ac926a;  /* umber */
    --region-1: #564c4a;   --region-1-ink: #af9c98;  /* cinder */
    --region-2: #817c53;   --region-2-ink: #d4cb9b;  /* olive */
    --region-3: #44746f;   --region-3-ink: #84c8c1;  /* pine */
    --region-4: #55437a;   --region-4-ink: #a695c0;  /* heather */
    --region-5: #6d7f9f;   --region-5-ink: #b5caf4;  /* slate */
    --region-6: #a2a99f;   --region-6-ink: #ccf2cb;  /* lichen */
    --region-7: #7b9eb1;   --region-7-ink: #dce9ff;  /* mist */
    --region-8: #5b6582;   --region-8-ink: #9f9ba8;  /* dusk */
    --region-9: #9c827a;   --region-9-ink: #f3e4e3;  /* rose */
    --region-10: #344f30;  --region-10-ink: #7a9b70; /* fern */
    --region-11: #b59d7d;  --region-11-ink: #fefae1; /* sand */
    /* Terrain fills. A mountain or marsh territory wears its terrain over its
       region's tint: it costs whoever holds it, and that has to read with the
       names off. Both clear deltaE2000 17 from every region, 9 from every seat
       pigment and 15 from --ocean, and 35 from each other; stone is kept off
       sand's warm hue so a costly territory never reads as a costly region.
       --terrain-*-ink darkens the glyphs drawn on them. */
    --terrain-mountain: #c0b4c0;  --terrain-mountain-ink: #3c3540; /* stone */
    --terrain-marsh: #42a88a;     --terrain-marsh-ink: #12352b;    /* fen */
    /* A label's bonus: ember for a cost, gold for a gain. */
    --bonus-cost: #e0805e;
    --bonus-gain: #f2c75c;
    /* Map ink and terrain accents. */
    --wave: #46596b;
    --searoute: #93794c;
    --decor: #1a130c;
    --delta: #dca553;
    /* Water territories: the line between tiles, a water region's wash, and the
       ink their names are set in. */
    --sea-line: #5f7a92;
    --sea-wash: #2a4257;
    --sea-ink: #9fc0d8;
}

.game-shell .tone-attack {
    --tone: var(--attack);
}

/* A support is aimed at a player, so it takes the supported seat's colour. */
.game-shell .tone-support {
    --tone: var(--seat);
}

/* A revealed order is coloured by the seat that gave it. */
.game-shell .tone-seat {
    --tone: var(--seat);
}

/* A support with no seat picked yet stages in a neutral tone. */
.game-shell .tone-neutral {
    --tone: var(--text-4);
}

/* The board floats on the sea, which now fills the whole area around it. */
.board-area {
    background: var(--ocean);
    isolation: isolate;
}

/* One sea, not two. The waves sit on the area rather than on the board, so the
   water reads the same under the map and out past its edge — the board's own
   rect used to be the only water with any texture, which drew a square seam
   around it. Screen-scale, not board-scale: this is the water the board floats
   on, so a zoom moves the board over it and does not stretch it.
   z-index with the isolate above: a ::before is painted over in-flow children,
   so it has to be pushed behind the map and held inside this box. */
.board-area::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background: var(--wave);
    opacity: 0.34;
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='80'%3E%3Cg stroke='black' stroke-width='3' fill='none' stroke-linecap='round'%3E%3Cpath d='M10 22 q15 -10 30 0'/%3E%3Cpath d='M70 62 q15 -10 30 0'/%3E%3C/g%3E%3C/svg%3E");
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='80'%3E%3Cg stroke='black' stroke-width='3' fill='none' stroke-linecap='round'%3E%3Cpath d='M10 22 q15 -10 30 0'/%3E%3Cpath d='M70 62 q15 -10 30 0'/%3E%3C/g%3E%3C/svg%3E");
    /* A third of the tile it is drawn at, so the strokes come down with it and
       the water reads as distance rather than as a pattern. */
    mask-size: 40px 26.7px;
    -webkit-mask-size: 40px 26.7px;
}

/* Map zoom (static/js/map-zoom.js). The frame is the whole area; the board
   inside grows with --map-zoom (set on <html>, out of the morph's reach) and
   the frame scrolls. */
.map {
    width: 100%;
    height: 100%;
    display: grid;
    /* Queried by the board below, which needs the area's width and height in px
       to fit itself by its own ratio. */
    container-type: size;
    /* The frame below is this box's own size and clips itself, so this is only
       the backstop that keeps a sub-pixel of board off the rail under it and
       the HUD over it. */
    overflow: hidden;
}

/* The frame takes the whole area, whatever shape the board is. The frame used
   to be cut to the board's own ratio, which on a wide map over a tall phone
   left it a short letterbox with dead sea above and below — and zooming then
   grew the board behind that same slot instead of into the room beside it.

   Across, the board is centred when it is the narrower of the two (safe: once
   it is the wider, centring in a scroll box would hide the overflow to the left
   with no way to reach it). Down, it is not: the board carries its own room
   below to scroll clear of the sheet, and matching room above to rest in the
   middle at scroll 0 — so the frame must leave scroll 0 at the top. */
.map-viewport {
    width: 100%;
    height: 100%;
    display: grid;
    align-content: start;
    justify-content: center;
    justify-content: safe center;
    overflow: auto;
    /* Up/down overscroll chains on to the page; sideways stays contained so a
       pan past the edge does not fire the browser's back gesture. */
    overscroll-behavior-x: contain;
    overscroll-behavior-y: auto;
    scrollbar-width: thin;
    /* No browser pinch here: the map takes it, the page keeps its own. */
    touch-action: pan-x pan-y;
}

/* Contain: the board takes the area's full width, or the width its ratio allows
   at the area's full height — whichever is the smaller — times the zoom. The
   sum sits here rather than on the frame above, which no longer has the board's
   shape to borrow. Height is the SVG's own, restated so the room below can be
   worked out from it. */
.board-stack {
    position: relative;
    --board-w: calc(min(100cqw, 100cqh * (var(--ar))) * var(--map-zoom, 1));
    --board-h: calc(var(--board-w) * var(--bh) / var(--bw));
    width: var(--board-w);
}

/* --slack is the letterbox the board rests in when it is the shorter of the
   two, spelled out above and below it. The frame aligns to the start, so that
   room above is what stands the board in the middle at scroll 0; without it the
   board would sit flush to the top the moment there was anything to scroll. At
   rest the two together come to exactly the frame, which leaves a fit board
   nothing to scroll — the map is a picture until the player pinches it.

   An empty border rather than padding on the frame: a border box can never be
   smaller than its own padding, so padding there would have grown the frame
   instead of the room inside it. content-box keeps the board itself the size it
   was, and map-zoom.js's commit() carries the scroll across a change of zoom,
   which changes --slack with it. */
@media (max-width: 899px) {
    .board-stack {
        --slack: max(0px, (100cqh - var(--board-h)) / 2);
        box-sizing: content-box;
        border-block: var(--slack) solid transparent;
    }

    /* With the sheet up, the board gains its ceiling as room underneath, so a
       pick anywhere on the board can rise clear of it. Only then: room standing
       there the rest of the time is room to drag the map off into empty sea.
       Nothing across or above — a pick is nudged into view, not centred, so it
       never has to travel past an edge. */
    .is-dock-open .board-stack {
        border-block-end-width: calc(var(--slack) + var(--sheet-max-h, 0px));
    }
}

/* The wide screen's hold floats over the map's top right, so the board fits
   under it rather than under the whole area: the room above is an empty border,
   as the phone's letterbox is, so a zoomed board still scrolls clear of it. */
@media (min-width: 900px) {
    .board-area:has(> .map-hold) .board-stack {
        --hold-room: calc(var(--hold-h) + 2 * var(--size-2));
        --board-w: calc(min(100cqw, (100cqh - var(--hold-room)) * (var(--ar))) * var(--map-zoom, 1));
        box-sizing: content-box;
        border-block-start: var(--hold-room) solid transparent;
    }
}

.board-stack .game-board {
    position: relative;
    width: 100%;
    max-width: none;
    max-height: none;
    margin: 0;
}

/* Quiet chrome, not a control panel: the map already reads itself, so the zoom
   steps sit under it in the same grey as the board's own labels. */
.zoom {
    display: flex;
    border: var(--border-size-1) solid var(--surface-4);
    border-radius: var(--radius-2);
    background: var(--surface-2);
    font-size: var(--font-size-0);
}

.zoom-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--size-1);
    padding: 1px var(--size-2);
    line-height: 1.35;
    color: var(--text-3);
}

.zoom-btn {
    font: inherit;
    border: none;
    border-radius: var(--radius-1);
    background: transparent;
}

.zoom .ui-icon {
    width: 1em;
    height: 1em;
}

.zoom-btn:active {
    background: var(--surface-3);
    color: var(--text-1);
}

@media (hover: hover) {
    .zoom-btn:hover {
        background: var(--surface-3);
        color: var(--text-1);
    }
}

html:not(.map-zoomed) .zoom-btn:is([data-map-zoom="out"], [data-map-zoom="reset"]),
.map-zoom-max .zoom-btn[data-map-zoom="in"] {
    opacity: 0.4;
    pointer-events: none;
}

/* Touch pinches, so − and + only take room. Until zoomed there is nothing to
   show; once zoomed the fit button is the way back. */
@media (pointer: coarse) {
    .zoom-btn:is([data-map-zoom="out"], [data-map-zoom="in"]) {
        display: none;
    }

    html:not(.map-zoomed) .zoom-bar {
        display: none;
    }
}

.ocean {
    fill: var(--ocean);
}

.waves-layer {
    fill: url(#icon-waves);
}

.wave {
    stroke: var(--wave);
    stroke-width: 2;
    fill: none;
    stroke-linecap: round;
    opacity: 0.18;
}

/* Wide enough to show as a dark rim round every coast, not just a lip below it:
   that rim is what lifts the darker regions off the sea. */
.extrude path {
    fill: var(--extrude);
    stroke: var(--extrude);
    stroke-width: 7;
    stroke-linejoin: round;
    fill-rule: evenodd;
}

.board-terr {
    stroke: var(--coast);
    stroke-width: 2.2;
    stroke-linejoin: round;
    fill: var(--land);
    fill-rule: evenodd;
}

.region {
    fill: none;
    fill-rule: evenodd;
    stroke-width: 4.5;
    stroke-linejoin: round;
    pointer-events: none;
}

.region.region-0, .terrcard-region.region-0, .board-terr.region-0, .region-stamp.region-0 { --region: var(--region-0); --region-ink: var(--region-0-ink); }
.region.region-1, .terrcard-region.region-1, .board-terr.region-1, .region-stamp.region-1 { --region: var(--region-1); --region-ink: var(--region-1-ink); }
.region.region-2, .terrcard-region.region-2, .board-terr.region-2, .region-stamp.region-2 { --region: var(--region-2); --region-ink: var(--region-2-ink); }
.region.region-3, .terrcard-region.region-3, .board-terr.region-3, .region-stamp.region-3 { --region: var(--region-3); --region-ink: var(--region-3-ink); }
.region.region-4, .terrcard-region.region-4, .board-terr.region-4, .region-stamp.region-4 { --region: var(--region-4); --region-ink: var(--region-4-ink); }
.region.region-5, .terrcard-region.region-5, .board-terr.region-5, .region-stamp.region-5 { --region: var(--region-5); --region-ink: var(--region-5-ink); }
.region.region-6, .terrcard-region.region-6, .board-terr.region-6, .region-stamp.region-6 { --region: var(--region-6); --region-ink: var(--region-6-ink); }
.region.region-7, .terrcard-region.region-7, .board-terr.region-7, .region-stamp.region-7 { --region: var(--region-7); --region-ink: var(--region-7-ink); }
.region.region-8, .terrcard-region.region-8, .board-terr.region-8, .region-stamp.region-8 { --region: var(--region-8); --region-ink: var(--region-8-ink); }
.region.region-9, .terrcard-region.region-9, .board-terr.region-9, .region-stamp.region-9 { --region: var(--region-9); --region-ink: var(--region-9-ink); }
.region.region-10, .terrcard-region.region-10, .board-terr.region-10, .region-stamp.region-10 { --region: var(--region-10); --region-ink: var(--region-10-ink); }
.region.region-11, .terrcard-region.region-11, .board-terr.region-11, .region-stamp.region-11 { --region: var(--region-11); --region-ink: var(--region-11-ink); }

.region[class*="region-"] {
    stroke: var(--region-ink);
    color: var(--region-ink);
}

/* Internal borders stay inside their region's own hue and sit halfway to the
   light end of it, which puts every one at deltaE 13-15 from the land it draws
   on. Mixing toward --coast instead tied all six lines together and left
   them at deltaE 8-10, at the edge of visible.
   The region outline above stays the stronger line by going the rest of the way
   to --region-ink and running twice as wide, not by muting this one. */
.board-terr[class*="region-"] {
    fill: var(--region);
    stroke: color-mix(in srgb, var(--region-ink) 50%, var(--region));
}

.decor {
    fill: var(--decor);
    opacity: 0.18;
    pointer-events: none;
}

/* Terrain wins over the region tint: after .board-terr[class*="region-"] and
   as specific, so the later rule takes the fill. */
.board-terr.board-terr--mountain {
    fill: var(--terrain-mountain);
    stroke: color-mix(in srgb, var(--terrain-mountain-ink) 50%, var(--terrain-mountain));
}

.board-terr.board-terr--marsh {
    fill: var(--terrain-marsh);
    stroke: color-mix(in srgb, var(--terrain-marsh-ink) 50%, var(--terrain-marsh));
}

/* The glyphs are what still say mountain or marsh to a player who cannot
   tell the two fills apart, so on terrain they are solid. */
.decor--mountain,
.decor--marsh {
    opacity: 0.55;
}

.decor--mountain {
    fill: var(--terrain-mountain-ink);
}

.decor--marsh {
    fill: var(--terrain-marsh-ink);
}

/* A water territory is the sea itself: no land fill and no extrude, only a
   faint lift and a dashed line between tiles. A water region adds a wash and a
   solid rim, so its bonus reads as a claim on the sea. */
.sea-tile {
    fill: var(--sea-line);
    fill-opacity: 0.12;
    stroke: var(--sea-line);
    stroke-width: 1.6;
    stroke-dasharray: 4 6;
    stroke-linecap: round;
}

.sea-region {
    fill: var(--sea-wash);
    fill-opacity: 0.55;
    stroke: var(--sea-line);
    stroke-width: 3;
    stroke-linejoin: round;
    pointer-events: none;
}

/* A blocked corner drawn as water: the same sea as round the coast. */
.lake {
    fill: var(--ocean);
    stroke: var(--coast);
    stroke-width: 2.2;
    stroke-linejoin: round;
    pointer-events: none;
}

.ridge {
    fill: var(--ridge);
    stroke: var(--extrude);
    stroke-width: 1.5;
    stroke-linejoin: round;
    pointer-events: none;
}

.ridge-cap {
    fill: var(--text-3);
    stroke: none;
    opacity: 0.75;
}

.searoute {
    stroke: var(--searoute);
    stroke-width: 3;
    stroke-linecap: round;
    stroke-dasharray: 1 10;
    fill: none;
    opacity: 0.55;
    pointer-events: none;
}

/* Roman and medium, not the italic of secondary text: at map size the italic's
   hairlines break up against the land and its halo. */
.board-terr-name {
    fill: var(--mat-bone);
    font-size: var(--fs-map-label);
    font-weight: var(--fw-medium);
    font-family: var(--font-body);
    letter-spacing: 0.02em;
    text-anchor: middle;
    pointer-events: none;
    paint-order: stroke;
    stroke: var(--ink);
    stroke-width: 4px;
    stroke-linejoin: round;
}

.board-terr-name--tucked {
    text-anchor: start;
}

.board-terr-name--sea {
    fill: var(--sea-ink);
    font-style: italic;
}

.board-terr-bonus--cost {
    fill: var(--bonus-cost);
}

.board-terr-bonus--gain {
    fill: var(--bonus-gain);
}

/* One stamp per region, where the region has room for it: its name over what it
   pays. Bigger than a territory name, letter-spaced, and in the region's own
   ink, so it reads as a mark on the land rather than as another territory — it
   is the only label on the board that belongs to several at once. */
.region-stamp {
    fill: var(--region-ink);
    font-size: var(--fs-map-stamp);
    font-weight: var(--fw-medium);
    font-family: var(--font-body);
    letter-spacing: 0.06em;
    text-anchor: middle;
    dominant-baseline: central;
    pointer-events: none;
    paint-order: stroke;
    stroke: var(--ink);
    stroke-width: 5px;
    stroke-linejoin: round;
}

.region-stamp--sea {
    fill: var(--sea-ink);
}

/* The number is the line being looked for, so it keeps the tracking off and
   reads tighter than the name above it. */
.region-stamp-bonus {
    letter-spacing: normal;
}

/* Names off, regions off. Each state is a class on <html>
   (static/js/map-toggles.js), not a signal: the board is morphed on every
   action, and a root class is the one thing that morph cannot strand or reset. */
:root.names-off .game-board .labels,
:root.regions-off .game-board .region-stamps {
    display: none;
}

/* Size tiers are carried by the group's .tier-* class — they add figures and
   weight, never scale. */
.army {
    pointer-events: none;
}

/* The figures reach the edges of their box, so a symbol's default clipping would
   cut the rim off down each side. */
.fig-symbol {
    overflow: visible;
}

/* paint-order puts the stroke under the fill, so this reads as a thin ink rim
   outside the figure rather than eating half of it. Without it a mid seat sinks
   into the land it stands on — indigo over the slate region is deltaE 15. */
.army-fig {
    fill: var(--seat);
    paint-order: stroke;
    stroke: var(--ink);
    stroke-width: 1.5;
    stroke-linejoin: round;
    transform: translateX(calc((var(--plate-gap) + var(--plate-w)) / -2));
}

/* The count is set in --fs-map-count, which grows as the board is rendered
   smaller, so a width worked out server-side in map units would clip the digits
   on a narrow screen. Measuring the plate here keeps the two in step: roughly
   half an em per glyph, which covers the widest of the digits and the "+".
   The markup puts the figure centred on --cx and the plate and count on its
   centre line; everything here shifts off that. The pair — figure, gap, plate —
   is centred on the anchor, so the figure moves left by half of what stands
   beside it, and the plate's centre lands at half a figure plus half a gap
   right of the anchor whatever its width. */
.plate-w {
    --plate-w: max(16px, calc(var(--chars) * var(--fs-map-count) * 0.55 + var(--fs-map-count) * 0.45));
    --plate-h: max(14px, calc(var(--fs-map-count) * 1.05));
    --plate-gap: calc(var(--map-unit) * 1.5);
    --fig-half: 8px;
    --plate-cx: calc(var(--cx) * 1px + var(--fig-half) + var(--plate-gap) / 2);
}

/* A ship is shipFigScale wider than the army figure. */
.plate-w.army--fleet {
    --fig-half: 11.2px;
}

/* A ship being bought: drawn where it will float, faded until it launches. */
.army-fig--ghost {
    fill-opacity: 0.55;
    stroke-opacity: 0.6;
    stroke-dasharray: 2 1.5;
}

/* A beast's figure is beastFigScale wider, and stands further off its plate. */
.plate-w.army--beast {
    --plate-gap: calc(var(--map-unit) * 4);
    --fig-half: 10.4px;
}

.army-plate {
    x: calc(var(--plate-cx) - var(--plate-w) / 2);
    width: var(--plate-w);
    height: var(--plate-h);
    transform: translateY(calc(var(--plate-h) / -2));
    fill: var(--ink);
    stroke: color-mix(in srgb, var(--seat) 45%, transparent);
    stroke-width: 1;
}

/* The board picks each territory's spot: --side is the row edge the badges
   stand off (0 centres them under the group), --step how many badges along
   from there. The row spans the figure, gap and plate, centred on --cx.
   Matches badgeOut and badgeStep in game/board/geometry.go. */
.badge-corner {
    transform: translate(
        calc(var(--cx) * 1px + var(--side) * (8px + var(--plate-gap) / 2 + var(--plate-w) / 2 + 12px) + var(--step) * 25px),
        calc(var(--by) * 1px)
    );
}

/* Centred on the plate, so it re-centres when the staged delta appears. */
.army-count {
    fill: var(--text-on-color);
    font-size: var(--fs-map-count);
    font-weight: var(--fw-medium);
    font-family: var(--font-body);
    text-anchor: middle;
    dominant-baseline: central;
    transform: translateX(calc(var(--fig-half) + var(--plate-gap) / 2));
}

.army-delta {
    fill: var(--delta);
}

.army.tier-1 .army-plate { stroke: color-mix(in srgb, var(--seat) 65%, transparent); }

.army.tier-2 .army-plate {
    stroke: var(--seat);
    stroke-width: 1.5;
}

.army.tier-2 .army-count { font-weight: var(--font-weight-8); }

/* The top tier cannot buy a bigger number — the plate is sized from the glyph
   advance — so it buys a lit rim instead. */
.army.tier-3 .army-plate {
    stroke: var(--seat);
    stroke-width: 2;
    filter: drop-shadow(0 0 4px var(--seat));
}

.army.tier-3 .army-count { font-weight: var(--font-weight-8); }

.creep .army-fig {
    stroke: var(--creep-rim);
}

/* A beast is one creature, not a crowd: a larger figure (figBox) ringed in
   brass, so it reads apart from bandits on the same plain plate. */
.army--beast .army-fig {
    stroke: var(--beast);
}

.army--beast .army-count { font-weight: var(--font-weight-8); }

/* A fleet is the army marker with hulls for the figure; the plate reads the
   troops aboard over the room there. An empty fleet keeps its hulls in its
   owner's colour, so it is plain whose it is, and dims only its count; a full
   one's room is set in its own colour. */
.fleet-cap {
    fill: color-mix(in srgb, var(--text-on-color) 50%, transparent);
    font-weight: var(--fw-medium);
}

.army--fleet-empty .army-count {
    fill: color-mix(in srgb, var(--text-on-color) 60%, transparent);
}

.army--fleet-full .fleet-cap { fill: var(--seat); }

/* What a beast won back the round it lived through, rising off the figure
   once as the board arrives. */
.army-heal {
    fill: var(--beast);
    stroke: var(--ink);
    stroke-width: 3;
    paint-order: stroke;
    font-size: var(--fs-map-count);
    font-weight: var(--font-weight-8);
    font-family: var(--font-body);
    text-anchor: middle;
    animation: army-heal 2.4s ease-out forwards;
}

@keyframes army-heal {
    0% { opacity: 0; transform: translateY(4px); }
    20% { opacity: 1; }
    70% { opacity: 1; }
    100% { opacity: 0; transform: translateY(-10px); }
}

.board-node--tappable {
    cursor: pointer;
}

/* Selection rings are their own layer, so a ring traces its whole border
   instead of being painted over by whichever neighbour is drawn later. */
.board-sel-layer {
    pointer-events: none;
}

.board-sel {
    fill: none;
    fill-rule: evenodd;
    stroke: none;
}

.board-sel.is-on {
    stroke: var(--tone);
    stroke-width: 4;
    stroke-linejoin: round;
    filter: drop-shadow(0 0 8px var(--tone));
}

/* A legal destination while an order is being built. Thinner than the selection
   ring on purpose: the source is both lit and picked, and the two must still
   read apart. Declared before .board-sel.is-on above it in the cascade would be wrong
   — this one loses when both are on. */
.board-sel.is-lit:not(.is-on) {
    stroke: var(--color-accent);
    stroke-width: 2.5;
    stroke-linejoin: round;
    stroke-dasharray: 7 5;
}

/* The scrim over ground the half-built order can never name. Its own layer, so
   one territory's scrim cannot be painted over by a neighbour drawn later. */
.board-dim-layer {
    pointer-events: none;
}

.board-dim {
    fill: none;
    fill-rule: evenodd;
}

.board-dim.is-on {
    fill: var(--color-backdrop);
}

.route {
    stroke: var(--tone);
    stroke-width: 4.5;
    stroke-linecap: round;
    fill: none;
    pointer-events: none;
    filter: drop-shadow(0 1px 2px color-mix(in srgb, var(--ink) 40%, black));
}

/* Queued orders sit behind the route being staged or edited. */
.route--staged {
    opacity: 0.75;
}

.staged.is-editing .route--staged {
    opacity: 1;
}

.route-num-bg {
    fill: var(--ink);
    stroke: var(--tone);
    stroke-width: 1.5;
    opacity: 0.85;
    pointer-events: none;
}

.staged.is-editing .route-num-bg {
    opacity: 1;
}

.route-num {
    fill: var(--text-on-color);
    font-size: var(--fs-map-num);
    font-weight: var(--font-weight-7);
    font-family: var(--font-body);
    text-anchor: middle;
    dominant-baseline: central;
    opacity: 0.85;
    pointer-events: none;
}

.staged.is-editing .route-num {
    opacity: 1;
}

.route-seq-bg {
    fill: var(--ink);
    stroke: var(--tone);
    stroke-width: 1;
    opacity: 0.85;
    pointer-events: none;
}

.route-seq {
    fill: var(--text-3);
    font-size: var(--fs-map-seq);
    font-weight: var(--font-weight-7);
    font-family: var(--font-body);
    text-anchor: middle;
    dominant-baseline: central;
    pointer-events: none;
}

.icon-arrow-move {
    fill: var(--move);
}

.icon-arrow-attack {
    fill: var(--attack);
}

/* The seat class sets --seat on the path itself, so one rule covers every
   seat's head. */
.arrow-seat {
    fill: var(--seat);
}

/* Nothing is staged over a reveal, so its orders are not demoted. */
.reveal .route--staged,
.reveal .route-num-bg,
.reveal .route-num,
.reveal .route-seq-bg,
.reveal .badge-icon {
    opacity: 1;
}

.reveal-panel {
    display: grid;
    gap: var(--size-3);
    justify-items: center;
    text-align: center;
}

/* A revealed round is read on the map, so the dock keeps to one line and the
   way on. */
.reveal-panel .game-waiting-text {
    padding-block: var(--size-2);
}

/* The floating twin of that panel, for the width where the drawer is shut over
   it. It is absent everywhere else: two Continues in sight at once would be two
   things to choose between that do the same thing. */
.reveal-bar {
    display: none;
}

/* The end panel was written for a page-wide card; in the dock it drops the
   framing padding and the display-sized winner line. */
.game-over-panel.game-end-panel {
    gap: var(--size-2);
    padding: var(--size-3) 0;
}

.game-over-panel .game-winner-text {
    font-size: var(--fs-2);
}

/* A locked plan is a read-back, so its sections run the panel's full width
   under the line it opens with, and the summary sheds the margin it carries
   inside the lock dialog. */
.wait-panel > * {
    width: 100%;
}

.wait-panel .game-waiting-text {
    padding-block: var(--size-2);
}

.wait-panel .lock-summary {
    margin-bottom: 0;
    max-height: none;
}

.plan {
    display: flex;
    flex-direction: column;
    gap: var(--size-2);
    align-items: center;
    width: 100%;
    max-width: 420px;
    margin-inline: auto;
    padding-bottom: var(--size-8);
}

/* The card's trailing slack would be a hole above whatever stands under it.
   Datastar's data-show writes display:none inline, which is what the :not asks
   about. */
.plan:not([style*="none"]) {
    padding-bottom: var(--size-4);
}

.plan-cards {
    width: 100%;
}

.seat-dot {
    width: var(--size-2);
    height: var(--size-2);
    border-radius: var(--radius-round);
    flex: none;
}

/* One colour mapping for every consumer: map figures, panel dots, and the HUD
   swatch that is a player's legend. */
.seat-0 { --seat: var(--seat-0); }
.seat-1 { --seat: var(--seat-1); }
.seat-2 { --seat: var(--seat-2); }
.seat-3 { --seat: var(--seat-3); }
.seat-4 { --seat: var(--seat-4); }
.seat-5 { --seat: var(--seat-5); }
.seat-6 { --seat: var(--seat-6); }
.seat-7 { --seat: var(--seat-7); }
.seat-8 { --seat: var(--seat-8); }
.seat-9 { --seat: var(--seat-9); }
.creep { --seat: var(--creep); }

.seat-dot {
    background: var(--seat);
}

.alloc-rows {
    display: flex;
    flex-wrap: wrap;
    gap: var(--size-1);
    justify-content: center;
}

.alloc-row {
    display: flex;
    align-items: center;
    gap: var(--size-1);
    background: var(--surface-2);
    border-radius: var(--radius-2);
    padding: 0 var(--size-1);
    font-size: var(--fs-0);
}

/* Scoped under .plan so the square sizing beats .game-dock-body button's
   padding, which is more specific than a bare class. */
.plan .quiet-btn {
    justify-content: center;
    padding: 0;
    width: var(--size-6);
    height: var(--size-6);
    font-size: var(--fs-0);
    flex: none;
    background: transparent;
    color: var(--text-3);
    border: none;
    border-radius: var(--radius-2);
    cursor: pointer;
}

.plan .quiet-btn:active {
    background: transparent;
    color: var(--text-1);
}

@media (hover: hover) {
    .plan .quiet-btn:hover {
        background: transparent;
        color: var(--text-1);
    }
}

/* Dropping an order on its own card is the one thing that card can undo, so it
   is a word in the theme's destructive ink rather than another grey glyph. */
.plan .order-drop {
    width: auto;
    padding: 0 var(--size-2);
    font-size: var(--font-size-0);
    font-weight: var(--font-weight-6);
    color: var(--color-destructive);
}

.alloc-label {
    font-weight: var(--font-weight-7);
}

.alloc-count {
    min-width: 1.5em;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

.remaining {
    color: var(--text-2);
    font-size: var(--fs-0);
    text-align: center;
    margin: 0;
}

.remaining-count {
    color: var(--text-1);
    font-size: var(--fs-2);
    font-weight: var(--font-weight-7);
    font-variant-numeric: tabular-nums;
}

/* The same line, once the pick is ground the rule refuses. */
.alloc-hint.is-bad {
    color: var(--color-destructive);
}

.alloc-hint {
    margin: 0;
    font-size: var(--fs-0);
}

.plan .alloc-row .quiet-btn {
    width: var(--size-5);
    height: var(--size-5);
    font-size: var(--font-size-0);
}

.stage-panel {
    display: flex;
    flex-direction: column;
    gap: var(--size-2);
    align-items: center;
    width: 100%;
}

/* ---------------------------------------------------------------------------
   The rail's marks: the purse, the orders left and the hand, read-only. The
   phone's rail and the wide screen's map overlay (.map-hold) both stand them.
   --------------------------------------------------------------------------- */
.rail-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--size-1);
}

.rail-slots {
    display: flex;
    gap: var(--size-1);
}

.rail-slots[role="button"] {
    cursor: pointer;
}

/* The hand row. The slots take the tap — the button beside them is the
   affordance, and small enough to share one short row with them. */
.rail-hand {
    display: flex;
    align-items: center;
    gap: var(--size-2);
    min-height: var(--size-6);
}

.rail-hand-btn {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--size-6);
    height: var(--size-6);
    padding: 0;
    background: var(--surface-2);
    border: var(--rule-1);
    border-radius: var(--radius-1);
    color: var(--text-2);
    cursor: pointer;
}

.rail-hand-btn .ui-icon {
    width: 1.1em;
    height: 1.1em;
}

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

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

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

/* A slot is held whether or not a card is in it: the row must not reflow
   when one is drawn or spent. */
.rail-slot {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--size-6);
    height: var(--size-6);
    background: var(--surface-2);
    border: var(--rule-1);
    border-radius: var(--radius-1);
    color: var(--text-2);
    font-size: var(--fs-0);
}

.rail-slot .ui-icon {
    width: 1.1em;
    height: 1.1em;
}

.rail-slot--empty {
    background: none;
    border-style: dashed;
    color: var(--text-4);
}

.rail-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.2em;
    padding: 0.1em 0.35em;
    background: var(--surface-2);
    border: var(--rule-1);
    border-radius: var(--radius-1);
    color: var(--text-2);
    font-size: var(--fs-0);
}

.rail-chip .ui-icon {
    width: 1.1em;
    height: 1.1em;
}

.rail-gold {
    border-color: var(--color-accent-muted);
    color: var(--color-accent-text);
}

/* Spent, not gone: the kind still has to hold its place, or the row reflows
   every time an order is given. */
.rail-chip--spent,
.rail-slot--staged,
.shop-item.shop-item--staged {
    opacity: 0.4;
}

.rail-n {
    font-variant-numeric: tabular-nums;
}

/* What a stage panel lists: the troops bought and the orders given, or the
   deployment's placements. It lays out as the panel does. */
.stage-list {
    display: flex;
    flex-direction: column;
    gap: var(--size-2);
    align-items: center;
    width: 100%;
}

/* The wide Orders tab holds still at both ends: the lock at the foot, and only
   the list above it scrolls. Each box down
   to the list gives up its height so the list is what overflows. A phase with
   no list scrolls whole. */
@media (min-width: 900px) {
    .game-dock-orders {
        display: flex;
        flex-direction: column;
        overflow: hidden;
    }

    .game-dock-orders > .phase-body {
        flex: 1 1 auto;
        min-height: 0;
        display: flex;
        flex-direction: column;
        overflow-y: auto;
    }

    .game-dock-orders .plan:has(.stage-list) {
        flex: 1 1 auto;
        min-height: 0;
        padding-bottom: 0;
    }

    .game-dock-orders .stage-panel:has(> .stage-list) {
        flex: 1 1 auto;
        min-height: 0;
    }

    .game-dock-orders .stage-list {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
        justify-content: flex-start;
        /* An edge fades only while there is more past it: the list's own scroll
           drives the two depths, and a list that fits has no scroll timeline,
           so both stay 0 and nothing fades. The mask reads alpha alone, so
           black here is opacity, not a colour. */
        --fade-top: 0px;
        --fade-bottom: 0px;
        mask-image: linear-gradient(
            to bottom,
            transparent 0,
            black var(--fade-top),
            black calc(100% - var(--fade-bottom)),
            transparent 100%
        );
        animation: list-fade linear both;
        animation-timeline: scroll(self);
    }
}

@property --fade-top {
    syntax: "<length>";
    inherits: false;
    initial-value: 0px;
}

@property --fade-bottom {
    syntax: "<length>";
    inherits: false;
    initial-value: 0px;
}

/* Quick at each end: one row's scroll brings the top fade in, and the last
   row's takes the bottom one out. */
@keyframes list-fade {
    0% {
        --fade-top: 0px;
        --fade-bottom: var(--size-7);
    }

    8% {
        --fade-top: var(--size-7);
    }

    92% {
        --fade-bottom: var(--size-7);
    }

    100% {
        --fade-top: var(--size-7);
        --fade-bottom: 0px;
    }
}

/* The rail's two rows on the wide screen, over the map's top right in the
   zoom bar's box: the same marks, a step up from the phone's. The chips sit
   right, against the corner; the hand reads from the left. */
/* Its height is fixed, not read off its content, because the board leaves
   room for it in CSS. */
.board-area {
    --hold-h: calc(2 * var(--size-7) + 3 * var(--size-1) + 2 * var(--border-size-1));
}

.map-hold {
    box-sizing: border-box;
    height: var(--hold-h);
    position: absolute;
    right: var(--size-2);
    top: var(--size-2);
    z-index: 1;
    display: flex;
    flex-direction: column;
    gap: var(--size-1);
    padding: var(--size-1);
    border: var(--border-size-1) solid var(--surface-4);
    border-radius: var(--radius-2);
    background: var(--surface-2);
}

.map-hold .rail-chips {
    flex-wrap: nowrap;
    justify-content: flex-end;
    height: var(--size-7);
}

.map-hold .rail-hand {
    gap: var(--size-1);
    min-height: 0;
}

.map-hold .rail-slot,
.map-hold .rail-hand-btn {
    width: var(--size-7);
    height: var(--size-7);
    padding: 0;
}

/* Scoped to beat .game-dock-body button's padding and font-size, which
   stretched the circle into an oval. One fixed box holds ✕, ↺ and the
   spinner, so a click never moves the card beside it. */
.shop-item .hand-slot-toggle {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: var(--size-3);
    height: var(--size-3);
    padding: 0;
    background: var(--color-destructive-bg);
    border: var(--rule-1);
    border-radius: var(--radius-token);
    color: var(--text-on-color);
    font-size: var(--font-size-00);
    line-height: 1;
    cursor: pointer;
}

.hand-slot-toggle > .loader-label {
    display: inline-flex;
}

.hand-slot-toggle .ui-icon {
    width: 1.1em;
    height: 1.1em;
}

/* Sized for a thumb: the hand is spent on a phone too. */
.shop-item .hand-slot-toggle {
    width: var(--size-6);
    height: var(--size-6);
    font-size: var(--font-size-0);
}

@media (hover: hover) {
    .hand-slot-toggle:hover {
        background: var(--color-destructive-bg-hover);
    }
}

.hand-slot-toggle.hand-slot-toggle--keep {
    background: var(--surface-3);
    color: var(--text-1);
}

@media (hover: hover) {
    .hand-slot-toggle.hand-slot-toggle--keep:hover {
        background: var(--surface-4);
    }
}

/* The Pick panel stands under the wide dock's tabs whichever is up, at one
   fixed 40%, so the tabs above never move as a pick comes and goes. It
   scrolls past that. It goes last by order, not in the DOM, because a phone
   reads it above the Orders tab. With nothing in it (every child hidden) it
   goes. */
@media (min-width: 900px) {
    .game-dock-pick {
        order: 1;
        flex: 0 0 40%;
        border-block-start: var(--rule-strong);
    }

    .game-dock-pick:not(:has(> :not([style*="none"]))) {
        display: none;
    }
}

/* A phone reads the Pick panel in the Orders sheet, and only for a pick: the
   hint is the wide screen's. Under a pick the phase panel keeps a seam. */
@media (max-width: 899px) {
    .game-dock-pick.is-away,
    .game-dock-pick:not(:has(> .plan--pick:not([style*="none"]))) {
        display: none;
    }

    .game-dock-pick:not(.is-away):has(> .plan--pick:not([style*="none"])) + .game-dock-body:has(> .phase-body:not(.is-covered)) {
        border-block-start: var(--rule-1);
    }
}

.num-field {
    color: var(--text-2);
    font-size: var(--fs-0);
    display: flex;
    align-items: center;
    gap: var(--size-1);
    white-space: nowrap;
    /* A stepper is only usable whole, so it wraps as one unit. */
    flex: 0 0 auto;
}

/* Padded out from the 1rem glyph so the tap target is reachable. */
.hint-icon {
    display: inline-flex;
    padding: var(--size-1);
    cursor: help;
}

.hint-icon .ui-icon,
.meta-pair .ui-icon {
    width: 1rem;
    height: 1rem;
    flex: none;
}

/* One border around the group rather than one per segment, which keeps the
   whole add-an-order row on one phone-width line. */
.stepper {
    display: inline-flex;
    align-items: center;
    background: var(--surface-2);
    border: var(--border-size-1) solid var(--border-2);
    border-radius: var(--radius-round);
    overflow: hidden;
}

/* The base button is inline-flex but only centres on the cross axis, so a glyph
   in a min-width cell hangs off to the left without this. */
.plan .nudge {
    justify-content: center;
}

/* Scoped to beat .game-dock-body button's padding, which is more specific than a
   bare class. */
.plan .nudge {
    padding: 0;
    height: 2rem;
    color: var(--text-2);
    font-size: var(--fs-0);
    line-height: 1;
    cursor: pointer;
}

.plan .nudge {
    min-width: 1.75rem;
    background: var(--surface-3);
    border: none;
    font-size: var(--fs-0);
    font-weight: var(--font-weight-6);
}

.plan .nudge:first-child {
    border-right: var(--border-size-1) solid var(--border-2);
}

.plan .nudge:last-child {
    border-left: var(--border-size-1) solid var(--border-2);
}

.plan .nudge:active:not(:disabled) {
    background: var(--surface-4);
    color: var(--text-1);
}

@media (hover: hover) {
    .plan .nudge:hover:not(:disabled) {
        background: var(--surface-4);
        color: var(--text-1);
    }
}

/* A stepper at its limit: the button stays, so the row keeps its shape, but
   reads as out of reach. */
.plan .nudge:disabled {
    color: var(--text-4);
    cursor: default;
}


.num-field .hint-icon {
    padding: var(--size-1) 0;
}

/* Fixed width so stepping through 9 → 10 doesn't shuffle the buttons. */
.num-val {
    padding: 0 var(--size-1);
    min-width: 2.25ch;
    text-align: center;
    color: var(--text-1);
    font-size: var(--fs-1);
    font-variant-numeric: tabular-nums;
}

/* The block is a flex item of the centred stage panel, so it must claim the
   full row or its head floats to the middle. */
.staged-troops {
    width: 100%;
}

.order-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--size-2);
    width: 100%;
}

/* Scoped to beat .game-dock-body button's padding and font-size. */
.plan .clear-btn {
    background: transparent;
    color: var(--color-destructive);
    border: none;
    padding: 0;
    font-size: var(--font-size-0);
    font-weight: var(--font-weight-6);
    cursor: pointer;
}

@media (hover: hover) {
    .plan .clear-btn:hover {
        background: transparent;
        color: var(--color-destructive-hover);
    }
}

.order-head-label {
    color: var(--text-1);
    font-size: var(--fs-1);
    font-weight: var(--font-weight-6);
}

.order-count {
    color: var(--text-3);
}

.order-list {
    width: 100%;
    font-size: var(--font-size-0);
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--size-1);
}

.order-item {
    display: flex;
    align-items: center;
    gap: var(--size-1);
    padding: 0;
}

/* The plan's own queue is the longest list in the sheet, so it gives up the
   gap between rows and takes a hairline instead: same rows, less height. */
.order-list--plan {
    gap: 0;
}

.order-list--plan .order-item + .order-item {
    border-block-start: var(--rule-1);
}

.plan .order-list--plan .order-row {
    gap: var(--size-1);
    padding-block: 0;
    padding-inline: var(--size-1);
}

/* Said once, where the queue would be. Italic body, like every other line the
   panel says rather than labels. */
.order-empty {
    width: 100%;
    margin: 0;
    color: var(--text-3);
    font-size: var(--font-size-0);
    font-style: italic;
}

/* Scoped like .quiet-btn above, to beat .game-dock-body button's font-size. */
.plan .order-row {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: var(--size-2);
    background: transparent;
    color: var(--text-1);
    border: none;
    border-radius: var(--radius-2);
    padding: var(--size-1) var(--size-2);
    font-size: var(--font-size-0);
    font-weight: var(--font-weight-4);
    text-align: left;
    cursor: pointer;
}

.order-row:active {
    background: var(--surface-3);
}

@media (hover: hover) {
    .order-row:hover {
        background: var(--surface-3);
    }
}

.order-row.is-editing {
    background: var(--surface-3);
    outline: var(--border-size-1) solid var(--move);
}

@media (hover: hover) {
    .order-row.is-editing:hover {
        background: var(--surface-3);
        outline: var(--border-size-1) solid var(--move);
    }
}

/* Fixed width: only moves are numbered, so the column has to hold its place. */
.order-num {
    min-width: 1ch;
    text-align: center;
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
}

.order-text {
    flex: 1;
    min-width: 0;
}

.order-meta {
    display: flex;
    align-items: center;
    gap: var(--size-2);
    color: var(--text-2);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.meta-pair {
    display: inline-flex;
    align-items: center;
    gap: var(--size-1);
}

.lock-row,
/* SubmitOverlay's scope and its overlay/content wrappers, which would
   otherwise shrink to the label. */
.lock-row > div,
.lock-row > .submit-scope > div {
    width: 100%;
}

.lock-row {
    margin-top: var(--size-4);
}

.lock-summary {
    margin: 0 0 var(--size-5);
    max-height: 40vh;
    overflow-y: auto;
    text-align: start;
}

.lock-summary-row {
    gap: var(--size-2);
    padding: var(--size-1) var(--size-2);
}

/* The per-row menus name their anchors inline, since the triggers are generated
   and can't be paired in a stylesheet. */

/* What to tap next, at the top of the half-built order. Italic body, because
   it is the panel telling you something rather than labelling a control. */
.target-hint {
    margin-block: 0 var(--size-1);
    font-style: italic;
    font-size: var(--font-size-0);
    color: var(--text-3);
}

/* The half-built order in the dock. The rows, steppers and seat chips inside it
   are the ones .plan already defines. */
.target-to {
    display: flex;
    align-items: baseline;
    gap: var(--size-2);
    margin-block: var(--size-1);
}

/* A support names one player, so its chips are a short row of names, not the
   thumb-sized controls the leg rows need. */
.plan .target-panel .seat-chip {
    min-height: 1.75rem;
    padding-block: 0;
}

.target-to-label {
    font-style: italic;
    color: var(--text-3);
}

.target-to-name {
    font-family: var(--font-display);
    color: var(--text-1);
}

/* On the map, bottom-left, clear of the dock. */
.zoom-bar {
    position: absolute;
    left: var(--size-2);
    bottom: var(--size-2);
    z-index: 1;
    display: flex;
    margin: 0;
}

/* On the map, top-left: every Ceasefire binding this round. */
.truce-hold {
    position: absolute;
    left: var(--size-2);
    top: var(--size-2);
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--size-1);
    pointer-events: none;
}

/* The dev bar holds the same corner; stand under it. */
.board-area:has(> .dev-bar) .truce-hold {
    top: calc(var(--size-2) * 2 + 24px);
}

.truce-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--size-1);
    padding: var(--size-1) var(--size-2);
    border: var(--border-size-1) solid var(--surface-4);
    border-radius: var(--radius-2);
    background: var(--surface-2);
    color: var(--text-1);
    pointer-events: auto;
}

.truce-note .seat-dot {
    display: inline-block;
    margin-inline: 2px;
}

/* DEV_TOOLS only: on the map, top-left. */
.dev-bar {
    position: absolute;
    left: var(--size-2);
    top: var(--size-2);
    z-index: 1;
    display: flex;
    gap: var(--size-1);
    font-size: var(--font-size-0);
}

.dev-btn,
.dev-round {
    font: inherit;
    padding: 1px var(--size-2);
    border: var(--border-size-1) solid var(--surface-4);
    border-radius: var(--radius-1);
    background: var(--surface-2);
    color: var(--text-2);
}

.dev-round {
    width: 4em;
}

.dev-cards-btn {
    anchor-name: --dev-cards;
}

.dev-cards {
    position-anchor: --dev-cards;
    position-area: block-end span-inline-end;
}

/* A support is dashed and carries no arrowhead, so it is never mistaken for an
   attack. */
.route--support,
.route.route--support {
    stroke-dasharray: 7 6;
    marker-end: none;
}

.badge-icon {
    color: var(--tone);
    opacity: 0.85;
    pointer-events: none;
}

.staged.is-editing .badge-icon {
    opacity: 1;
}

.badge-count {
    fill: var(--text-on-color);
    font-size: var(--fs-map-badge);
    font-weight: var(--font-weight-7);
    font-family: var(--font-body);
    text-anchor: middle;
    dominant-baseline: central;
    pointer-events: none;
}

.seat-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--size-1);
    width: 100%;
}

.section-head {
    display: flex;
    align-items: baseline;
    gap: var(--size-1);
    width: 100%;
    margin-top: var(--size-1);
    font-size: var(--font-size-00);
    font-style: italic;
    letter-spacing: var(--font-letterspacing-2);
}

.section-title {
    color: var(--text-2);
    font-weight: var(--font-weight-7);
}

.section-note {
    color: var(--text-3);
    text-transform: none;
    letter-spacing: normal;
}

.section-head > :last-child:not(.section-note) {
    margin-inline-start: auto;
}

.plan .seat-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--size-1);
    min-height: 2.25rem;
    padding: 0 var(--size-2);
    justify-content: flex-start;
    background: transparent;
    border: var(--border-size-1) solid var(--border-2);
    border-radius: var(--radius-round);
    color: var(--text-2);
    font-size: var(--fs-0);
    font-weight: var(--font-weight-6);
}

@media (hover: hover) {
    .plan .seat-chip:hover {
        background: var(--surface-3);
        color: var(--text-1);
    }
}

.plan .seat-chip.seat-chip.is-on {
    background: color-mix(in srgb, var(--color-accent) 22%, transparent);
    border-color: color-mix(in srgb, var(--color-accent) 60%, transparent);
    color: var(--text-1);
}

/* Dead to the pointer: the whole chip is the tap target, and the count must not
   swallow part of it. */
.chip-count {
    color: var(--text-3);
    font-size: var(--fs-0);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    pointer-events: none;
}

.shop-list {
    text-align: left;
    display: flex;
    flex-direction: column;
    gap: var(--size-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.shop-item {
    display: flex;
    align-items: flex-start;
    gap: var(--size-2);
}

.shop-glyph {
    display: inline-flex;
    flex: none;
    padding: var(--size-1);
    border: var(--border-size-1) solid var(--border-2);
    border-radius: var(--radius-2);
    background: var(--surface-3);
    color: var(--text-1);
}

.shop-text {
    display: flex;
    flex-direction: column;
    gap: var(--size-1);
    min-width: 0;
}

.shop-name {
    display: flex;
    align-items: baseline;
    gap: var(--size-2);
    font-size: var(--fs-1);
    font-weight: var(--font-weight-7);
    color: var(--text-1);
}

/* What the card costs to play, read the same way as the hand chip's price. */
.shop-price {
    display: inline-flex;
    align-items: center;
    gap: var(--size-1);
    color: var(--text-3);
    font-size: var(--fs-0);
    font-style: italic;
    font-weight: var(--font-weight-4);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.shop-price svg {
    width: 1em;
    height: 1em;
}

.shop-body {
    color: var(--text-2);
    font-size: var(--fs-0);
    line-height: 1.4;
}

/* A card's actions stand on its name line, at the row's end. */
.hand-acts {
    display: flex;
    flex: none;
    align-items: center;
    gap: var(--size-2);
    margin-inline-start: auto;
}

/* Scoped past .game-dock-body button, so the dock and the modal size it
   alike. */
.shop-item .hand-play {
    height: var(--size-6);
    padding: 0 var(--size-3);
    border-radius: var(--radius-2);
    font-size: var(--fs-0);
    font-weight: var(--font-weight-6);
}

/* The faded card must not fade its own take back or keep. */
.shop-item.shop-item--staged:has(.hand-acts) {
    opacity: 1;
}

.shop-item.shop-item--staged:has(.hand-acts) > :not(.hand-acts) {
    opacity: 0.4;
}

.hand-modal {
    width: min(28rem, calc(100vw - 2 * var(--size-4)));
}

.hand-modal .card-title {
    margin-block-end: var(--size-4);
}

.seat-row {
    width: auto;
}

/* The same mark the chip and the map badge carry, so a row is identifiable
   without reading it. */
.order-glyph {
    display: inline-flex;
    flex: none;
    color: var(--text-3);
}

.order-glyph .ui-icon {
    width: 1rem;
    height: 1rem;
}

/* The plain cards copy their baseline order's mark exactly, so the frame is the
   only thing that can carry the difference. */
.order-glyph--card {
    padding: 2px 4px;
    border: var(--border-size-1) solid var(--border-2);
    border-radius: var(--radius-1);
    background: var(--surface-3);
    color: var(--text-2);
}

/* Kept in the layout so unnumbered rows still line up under the numbered ones. */
.order-num.order-num--none {
    visibility: hidden;
}

/* Only a move carries the steppers, and it carries one row per destination, so
   the whole list shows and hides as one block. */
.legs {
    display: flex;
    flex-direction: column;
    gap: var(--size-1);
    width: 100%;
}

/* One row per destination, and it stays one row: the steppers and the drop
   button keep their footprint, and the name gives up whatever is left over. */
.leg-row {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 0;
    /* The name sits in from the box's edge, so the row reads as a leg of the
       order above it rather than as another control. */
    padding-left: var(--size-1);
}

/* Takes every pixel the controls leave, so the steppers sit against the row's
   right edge instead of the row bunching up in the middle. min-width:0 is what
   lets a long name truncate rather than push the steppers off a phone. */
.leg-name {
    flex: 1 1 auto;
    min-width: 0;
    color: var(--move);
    font-size: var(--fs-0);
    /* The panel centres its children; a name that truncates has to start at the
       same place on every row instead. */
    text-align: start;
}

/* A leg row carries two steppers and a drop button before the name gets any
   width at all, so inside one they run tighter than they do on their own —
   tightest of all below the common phone width, where every pixel the controls
   keep is one the name has to truncate away. The gap after each icon survives
   that: it is what keeps the glyph off the stepper it labels. */
.plan .leg-row .nudge {
    min-width: 1.25rem;
    /* A leg row is a list entry, not a control strip: the steppers stay short
       so several rows fit a phone. The row is wide, so the width carries the
       touch target the height gives up. */
    height: 1.75rem;
}

@media (min-width: 360px) {
    .leg-row {
        gap: var(--size-1);
        padding-left: var(--size-2);
    }

    .plan .leg-row .nudge {
        min-width: 1.5rem;
    }
}

.leg-row .num-val {
    min-width: 1.75ch;
}

/* The name over what the leg does: a sail, an attack, a landing. The tag is a
   note against the leg, so it is italic body, never a control. */
.leg-head {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

.leg-head .leg-name {
    max-width: 100%;
}

.leg-tag {
    color: var(--text-3);
    font-size: var(--font-size-00);
    font-style: italic;
}

/* Each stepper names what it counts in a line over it, since a phone shows no
   hover hint. The label may run wider than its stepper rather than wrap. */
.leg-field {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    flex: 0 0 auto;
}

/* A move beside an attack keeps the retreat slot's width but not its stepper. */
.leg-field.is-blank {
    visibility: hidden;
}

.leg-field-label {
    color: var(--text-3);
    font-size: var(--font-size-00);
    font-style: italic;
    white-space: nowrap;
}

/* The × sits level with the steppers rather than the middle of the row, whose
   labels push the steppers down, and stands off from the + so a thumb reaching
   for one more troop does not drop the destination. */
.plan .leg-row .leg-drop {
    width: var(--size-5);
    height: 1.75rem;
    align-self: flex-end;
    margin-left: var(--size-3);
}

/* A boarding or landing steps each ship in turn; its count reads as a load,
   "5/8" or "3 of 8", so it takes more room than a bare number. */
.num-val--wide {
    min-width: 5ch;
}

.leg-field .num-field + .num-field {
    margin-top: var(--size-1);
}

/* A sail picks its ships: one chip a ship, its load beside the hull, lit when
   it goes. */
.leg-ships {
    display: flex;
    flex-wrap: wrap;
    gap: var(--size-1);
}

.plan .ship-pick {
    display: inline-flex;
    align-items: center;
    gap: var(--size-1);
    height: 1.75rem;
    padding: 0 var(--size-2);
    border: var(--border-size-1) solid var(--border-2);
    border-radius: var(--radius-1);
    background: var(--surface-2);
    color: var(--text-3);
    font-size: var(--fs-0);
    font-variant-numeric: tabular-nums;
    cursor: pointer;
}

.plan .ship-pick.is-on {
    border-color: var(--color-accent);
    background: var(--surface-4);
    color: var(--text-1);
}

/* A destination the viewer does not hold is a battle, and reads like one. */
.leg-row.is-attack {
    color: var(--attack);
}

/* Reads as a note against the order, not as a count of its own. */
.meta-ways {
    color: var(--text-3);
    font-style: italic;
}

/* --- lobby: seats and colours ------------------------------------------ */

/* The picker sits after the name: a colour dot that opens the palette, and
   doubles as the loader for the pick it fires. Scoped to the card to beat the
   lobby's own button rules, which are more specific than a bare class. */
.lobby-card .seat-trigger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: var(--size-5);
    height: var(--size-5);
    padding: 0;
    border-radius: var(--radius-round);
    background: transparent;
    color: var(--text-2);
    cursor: pointer;
}

@media (hover: hover) {
    .lobby-card .seat-trigger:hover {
        background: var(--surface-3);
    }
}

/* The row dots match the picker's dot exactly, transparent border included, so
   the only difference between a row and your own is the tap ring. The side
   margins pad the dot out to the trigger button's box, so every row's colour
   sits in the same column. */
.seat-setup > .seat-dot {
    width: var(--size-3);
    height: var(--size-3);
    border: var(--border-size-1) solid transparent;
    background-clip: padding-box;
    margin-inline: calc((var(--size-5) - var(--size-3)) / 2);
}

.seat-trigger .seat-dot {
    width: var(--size-3);
    height: var(--size-3);
    border: var(--border-size-1) solid var(--border-2);
}

.seat-menu {
    flex-direction: row;
    min-width: auto;
}

.seat-swatches {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--size-2);
    max-width: 11rem;
}

.lobby-card .seat-swatch {
    flex: none;
    width: var(--size-5);
    height: var(--size-5);
    padding: 0;
    border: var(--border-size-2) solid transparent;
    border-radius: var(--radius-round);
    background: var(--seat);
    cursor: pointer;
}

.lobby-card .seat-swatch.is-on {
    border-color: var(--text-1);
}

.seat-setups {
    align-self: stretch;
    display: flex;
    flex-direction: column;
    gap: var(--size-1);
    width: 100%;
    max-width: 22rem;
    margin: 0 auto var(--size-3);
    padding: 0;
    list-style: none;
}

.seat-setup {
    display: flex;
    align-items: center;
    gap: var(--size-2);
    padding: var(--size-1) var(--size-2);
    border-radius: var(--radius-2);
    background: var(--surface-2);
    font-size: var(--font-size-0);
}

/* The name eats the slack so the colour and the state keep fixed columns
   however long the names are. */
.seat-setup > .name-ellipsis {
    flex: 1 1 auto;
    text-align: start;
}

/* Your own row is marked by weight and a ring, not by renaming you to "You". */
.seat-setup--mine {
    outline: var(--border-size-1) solid var(--surface-4);
}

.seat-setup--mine .name-ellipsis {
    font-weight: var(--font-weight-6);
    color: var(--text-1);
}

.seat-setup-state {
    flex: none;
    width: 4.25rem;
    text-align: end;
    color: var(--text-3);
    font-size: var(--font-size-00);
    font-style: italic;
    letter-spacing: var(--font-letterspacing-2);
}

.seat-setup.seat-setup--ready .seat-setup-state {
    color: var(--color-accent);
}

/* --- lobby: map card ---------------------------------------------------- */

.map-card {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: var(--size-2);
    margin-bottom: var(--size-3);
}

/* One ruled plate holding the preview — so a map error sits inside an edge
   rather than floating. The map takes the card's width, less a thin margin. */
.map-plate {
    display: flex;
    flex-direction: column;
    width: 100%;
    padding: var(--size-2);
    border: var(--rule-1);
    border-radius: var(--radius-2);
}

/* The frame takes the board's shape from an inline aspect-ratio
   (lobby.templ's mapFrameStyle), so the spinner waiting for a new map holds
   the old one's place. */
.map-frame {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
}

/* Before the game starts the lobby is a page like any other, so its preview
   is sized to the page, not to the board. On a wide
   screen the height leads instead, so a tall map does not push the seats off
   it, and the plate shrinks to hug whatever width that leaves.
   It stays here rather than moving to lobby.css because static.Stylesheets links
   lobby.css first — over there it would lose to the rule above. */
@media (min-width: 900px) {
    .map-plate {
        align-self: center;
        width: fit-content;
        max-width: 100%;
    }

    .map-frame {
        width: auto;
        height: 30rem;
        max-width: 100%;
    }
}

.map-frame > .loader-label {
    position: relative;
    width: 100%;
    height: 100%;
}

.map-frame:has(.map-error) .preview-board {
    opacity: 0.2;
}

.map-frame > .loader-spinner {
    color: var(--text-3);
}

.map-frame .btn-spinner {
    width: var(--size-6);
    height: var(--size-6);
}

/* Same board, same viewBox — only the size differs, so the preview reads as the
   map the table is about to play. It fills the frame; the viewBox keeps the
   board's shape and centres it where the desktop cap trims the height. */
.preview-board {
    display: block;
    width: 100%;
    height: 100%;
}

/* --fs-map-stamp shrinks as the screen grows, to suit a board that grows with
   it. The preview stays near one size at every width, so it takes a fixed
   size in board units instead. */
.preview-board .region-stamp {
    font-size: 26px;
}

.creep-garrison {
    fill: var(--creep);
    paint-order: stroke;
    stroke: var(--creep-rim);
    stroke-width: 0.8;
    stroke-linejoin: round;
}

.creep-beast {
    fill: var(--creep);
    paint-order: stroke;
    stroke: var(--beast);
    stroke-width: 0.8;
    stroke-linejoin: round;
}

.map-row {
    display: flex;
    justify-content: center;
    margin-block-start: var(--size-1);
}

.map-name {
    display: block;
    color: var(--text-1);
    font-weight: var(--font-weight-6);
    font-size: var(--font-size-1);
}

.map-meta {
    display: block;
    color: var(--text-3);
    font-size: var(--font-size-00);
}

.map-trigger,
.map--static {
    display: flex;
    align-items: baseline;
    gap: var(--size-2);
    padding: var(--size-1) var(--size-3);
    border-radius: var(--radius-2);
    text-align: center;
}

/* The map name is a caption you can click, not a control in a box. */
.map-trigger {
    border: none;
    background: none;
    cursor: pointer;
    transition: background 0.15s;
}

@media (hover: hover) {
    .map-trigger:hover {
        background: var(--surface-2);
    }
}

/* Same caret as the HUD's score toggle: it points down, and flips while its
   menu is open. */
.map-caret {
    flex-shrink: 0;
    color: var(--text-3);
    font-size: var(--font-size-0);
    line-height: 1;
    transition: transform 0.15s ease;
}

body:has(#map-menu:popover-open) .map-caret {
    transform: rotate(180deg);
}

.map-menu {
    min-width: 14rem;
}

.map-choices {
    display: flex;
    flex-direction: column;
    gap: var(--size-1);
}

.map-choice {
    padding: var(--size-1) var(--size-2);
    border: var(--border-size-1) solid transparent;
    border-radius: var(--radius-2);
    background: none;
    text-align: start;
    cursor: pointer;
}

.map-choice.is-on {
    border-color: var(--color-accent);
}

/* Structures ------------------------------------------------------------- */

/* The badge is a solid silhouette in the owner's colour over an ink outline,
   the same treatment as the army figure -- line art was unreadable at badge
   size on a phone. Neither building is dimmed: a building you cannot see is one
   you forget you own. The fortress stands taller than the barracks. */
.structure {
    pointer-events: none;
}

.structure-fig {
    fill: var(--seat);
    paint-order: stroke;
    stroke: var(--ink);
    stroke-width: 2.5;
    stroke-linejoin: round;
}

/* A camp's rounds left, beside the tent, inked like the figure so it reads
   on any region colour. */
.camp-left {
    fill: var(--seat);
    paint-order: stroke;
    stroke: var(--ink);
    stroke-width: 3;
    stroke-linejoin: round;
    font-size: var(--fs-map-badge);
    font-weight: var(--font-weight-7);
    font-family: var(--font-body);
    dominant-baseline: central;
}

/* The fortress stands in a halo of its owner's colour, so a seat of power is
   found before it is read. The halo is flat, not blurred, like every other mark
   on the board. It is still on purpose: nothing inside the board SVG reaches
   the compositor, so any loop here repaints the board every frame for as long
   as it is open, and that was a phone's battery. */
.fort-glow {
    fill: var(--seat);
    stroke: var(--seat);
    stroke-width: 1.5;
    fill-opacity: 0.22;
    stroke-opacity: 0.6;
}

/* A fortress due to draw a card this round wears a plus on its corner, in its
   owner's colour so a rival reads whose it is at a glance. */
.fort-draw {
    pointer-events: none;
}

.fort-draw circle {
    fill: var(--seat);
    stroke: var(--ink);
    stroke-width: 1.5;
}

.fort-draw path {
    stroke: var(--ink);
    stroke-width: 1.8;
    stroke-linecap: round;
}

/* A glyph that is a filled silhouette inside the stroked icon frame. */
.glyph-solid {
    fill: currentColor;
    stroke: none;
}

/* A site the spacing rule still allows wears .board-sel.is-lit and the rest are under
   the scrim, which is the pattern a half-built order already uses. Any ground
   may still be picked -- the tap has to be answered, not swallowed -- so the
   ring the wrong one takes is the refusing one. */
.game-shell .board-sel--bad.board-sel.is-on {
    --tone: var(--color-destructive);
}

/* --- the territory head line --------------------------------------------- */

/* What one territory *is*, on the sheet's head line: its name, its region and
   its ground. Only the name gives way when the line runs out of room. */
.terrcard-head {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    gap: var(--size-2);
    min-width: 0;
    overflow: hidden;
    font-size: var(--fs-0);
    color: var(--text-2);
}

.terrcard-head > * {
    flex: none;
}

.terrcard-head-name {
    flex: 0 1 auto;
    min-width: 3ch;
    font-family: var(--font-display);
    font-weight: var(--font-weight-4);
    color: var(--text-1);
}

/* --- who holds the territory --------------------------------------------- */

/* The card's first part: the holder as a tag in their own colour on the left,
   their troops and building at the far edge, and what to know of them below. */
.terrcard-occ {
    display: flex;
    flex-direction: column;
    gap: var(--size-1);
    padding-block-start: var(--size-2);
}

.terrcard-occ-line {
    display: flex;
    align-items: center;
    gap: var(--size-2);
    min-width: 0;
    font-size: var(--fs-0);
    color: var(--text-2);
}

.terrcard-owner {
    flex: 0 1 auto;
    display: inline-flex;
    align-items: center;
    gap: var(--size-1);
    min-width: 0;
    margin-inline-end: auto;
    padding: 0 var(--size-2);
    background: color-mix(in srgb, var(--seat, var(--text-3)) 18%, transparent);
    border: var(--border-size-1) solid color-mix(in srgb, var(--seat, var(--text-3)) 55%, transparent);
    border-radius: var(--radius-2);
    color: var(--text-1);
}

.terrcard-stat {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: var(--size-1);
}

/* The same figure the board stands on the ground, in the same seat colour, so
   the panel and the map say who is there with one mark. Ink rim as on the
   board: a mid seat sinks into the dock's own surface without it. */
.army-icon {
    overflow: visible;
    width: 0.95rem;
    fill: var(--seat, var(--text-2));
    paint-order: stroke;
    stroke: var(--ink);
    stroke-width: 1.5;
    stroke-linejoin: round;
}

/* The building goes the same way as the figure beside it: the board paints it
   in its owner's colour, so the line does too. Elsewhere — the rules modal's
   legend — no seat is named and it stays the ink it always was. */
.terrcard-stat .glyph-solid {
    fill: var(--seat, currentColor);
    paint-order: stroke;
    stroke: var(--ink);
    stroke-width: 1.5;
    stroke-linejoin: round;
}

/* The board's brass rim, so the panel marks a beast the way the map does. */
.creep .army-icon {
    stroke: var(--creep-rim);
}

.army-icon.beast-icon {
    stroke: var(--beast);
}

.terrcard-stat b {
    color: var(--text-1);
    font-weight: var(--font-weight-7);
}

/* --- the territory card ------------------------------------------------- */

/* What one territory holds, and what may be done with it. It shares the panel
   scope (.plan) with the plan overview, so the chips, steppers and order
   rows inside it are the ones already defined above. */
/* stretch, not flex-start: the action row's SubmitOverlay scope would
   otherwise shrink to its own content instead of taking the sheet's width. */
.terrcard {
    display: flex;
    flex-direction: column;
    gap: var(--size-2);
    align-items: stretch;
    width: 100%;
    text-align: start;
}

/* What may be done here comes last, under a rule that parts it from who holds
   the ground. Stretch for the same reason the card does. */
.terrcard-orders {
    display: flex;
    flex-direction: column;
    gap: var(--size-2);
    align-items: stretch;
    padding-block-start: var(--size-2);
    border-block-start: var(--rule-1);
}

/* The region reads as a stamp on the head line, tinted from the same palette the
   board paints that region with. */
.terrcard-region {
    display: inline-flex;
    align-items: baseline;
    gap: var(--size-1);
    padding: 0 var(--size-1);
    background: var(--region);
    color: var(--region-ink);
    font-size: var(--font-size-00);
    font-style: italic;
}

/* What the land is and what holding it costs: plain text, not a stamp, since
   it belongs to this one territory rather than to a region. */
.terrcard-ground {
    display: inline-flex;
    align-items: baseline;
    gap: var(--size-1);
    color: var(--text-2);
    font-size: var(--font-size-00);
    font-style: italic;
}

.terrcard-bonus {
    font-style: normal;
    font-weight: var(--font-weight-7);
}

/* The bonus spelled out. The phone head has room for the number alone; the
   wide dock's panel shows the rest (see .terrcard-head-panel). */
.terrcard-bonus-note {
    display: none;
}

/* The troops this plan buys here, in the accent the army marker's own delta
   uses, so the board and the card say the same thing. */
.terrcard-plus {
    color: var(--color-accent-text);
    font-weight: var(--font-weight-7);
}

.terrcard-buy {
    display: flex;
    align-items: center;
    gap: var(--size-2);
    width: 100%;
    padding: 0 var(--size-2);
    background: var(--surface-2);
    border: var(--rule-1);
}

.terrcard-buy-label {
    flex: 1 1 auto;
    min-width: 0;
    font-size: var(--fs-0);
    color: var(--text-2);
}

/* The full width of the sheet: an order is picked from marks read side by side,
   not from a list scrolled through. The free slots come first and the hand
   after them, four to a row whatever the count, so a lone button keeps its size
   and a full hand wraps rather than squeezing the rest. */
.terrcard-acts {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--size-1);
    width: 100%;
}

.plan .terrcard-act {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0;
    min-height: var(--tap-min);
    padding: var(--size-1) var(--size-1);
    background: var(--surface-3);
    border: var(--border-size-1) solid var(--border-2);
    border-radius: var(--radius-2);
    color: var(--text-1);
    font-size: var(--fs-0);
    font-weight: var(--font-weight-6);
    text-align: start;
}

.plan .terrcard-act:disabled {
    opacity: 0.4;
}

.terrcard-act-label {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--font-size-00);
    font-weight: var(--font-weight-4);
}

/* Price and count get their own row across the top of the button, price at
   the left and count at the right, so neither crowds the icon. */
.terrcard-act-meta {
    order: -1;
    align-self: stretch;
    display: flex;
    align-items: center;
    justify-content: space-between;
    pointer-events: none;
    font-size: var(--font-size-00);
    color: var(--text-3);
}

.plan .terrcard-act .chip-count {
    font-size: inherit;
    color: inherit;
}

/* Every price is in the colour the purse is read in everywhere else. A card
   is the same button as a free slot — it is another order this ground can be
   given — and only the missing count marks it. */
.chip-price {
    display: inline-flex;
    align-items: center;
    gap: 0.15em;
    color: var(--color-attention-strong);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.chip-price svg {
    width: 1em;
    height: 1em;
}

.terrcard-note {
    margin: 0;
    width: 100%;
    color: var(--text-3);
    font-size: var(--font-size-0);
    font-style: italic;
}

/* An order row on the card is read, not tapped: the card it sits on is already
   the territory it belongs to. */
.order-row--static {
    cursor: default;
}

/* On the territory card the list is one of three things sharing a quarter of a
   screen, so the rows give up their own padding and sit against each other. */
.order-list--tight {
    gap: 0;
}

.order-list--tight .order-row--static {
    padding-block: 0;
    padding-inline: 0;
    font-size: var(--font-size-00);
}

.order-list--tight .order-item + .order-item {
    border-block-start: var(--rule-1);
}

@media (hover: hover) {
    .order-row--static:hover {
        background: transparent;
    }
}

/* Lock plan rides the sheet head on a phone. The row was written to fill a
   panel; in the head it is one small button, matched to the order actions. */
.game-sheet-acts-lock .lock-row,
.game-sheet-acts-lock .lock-row > div,
.game-sheet-acts-lock .lock-row > .submit-scope > div {
    width: auto;
    margin: 0;
}

.game-sheet-acts-lock .btn--block {
    min-height: 1.75rem;
    padding-inline: var(--size-3);
    font-size: var(--font-size-0);
}

/* The wide screen's own copy of something the phone keeps in the sheet head:
   the territory line, an order's Cancel and Add, Lock plan, Lock deployment.
   The head is hidden outright at 900px (game-shell.css), so these stand in its
   place there — and go down here, so only one is ever on screen. */

/* No padding of its own: with no territory picked every strip in here is
   hidden, and the wrapper has to collapse to nothing. The shown strip carries
   the padding instead. */
.terrcard-head-panel {
    display: flex;
}

/* The padding and the matching negative margin cancel out: the line still
   starts on the panel's edge, but the clip the strip carries for a long name
   no longer shaves the left bearing off the first letter of a short one. */
.terrcard-head-panel .terrcard-head {
    padding-block: var(--size-2);
    padding-inline: var(--size-1);
    margin-inline: calc(var(--size-1) * -1);
}

/* The wide dock has room the phone head does not, so the same strip stands as
   two lines here: the territory's name, then its region and ground. The break
   is the container's own ::after — a flex item like any other — wedged between
   them by order, so the markup stays the one line the sheet head needs. The
   rule under it parts what the ground is from who holds it. */
.terrcard-head-panel .terrcard-head {
    flex-wrap: wrap;
    row-gap: var(--size-1);
    overflow: visible;
    border-block-end: var(--rule-1);
}

.terrcard-head-panel .terrcard-head::after {
    content: "";
    order: 2;
    flex-basis: 100%;
    height: 0;
}

/* The panel's heading, so it takes the display face up a step. */
.terrcard-head-panel .terrcard-head-name {
    order: 1;
    font-size: var(--fs-1);
}

/* The region reads as a line of text here, not a stamp: the swatch before it
   keeps the colour the board paints it in. */
.terrcard-head-panel .terrcard-region {
    order: 3;
    align-items: center;
    padding: 0;
    background: none;
    color: var(--text-2);
    font-size: var(--fs-0);
}

.terrcard-head-panel .terrcard-region::before {
    content: "";
    width: 0.6em;
    height: 0.6em;
    background: var(--region);
}

.terrcard-head-panel .terrcard-bonus {
    color: var(--color-accent-text);
    font-weight: var(--font-weight-4);
}

.terrcard-head-panel .terrcard-bonus::before {
    content: "· ";
    color: var(--text-3);
}

.terrcard-head-panel .terrcard-bonus-note {
    display: inline;
}

.terrcard-head-panel .terrcard-ground {
    order: 3;
    font-size: var(--fs-0);
}

/* Cancel and Add end the panel, where the head's pair ends the sheet. Centred,
   not pushed right: the panel is centred on the dock, so the head's own
   margin-inline-start would only strand them under the hint's left half. */
.order-acts {
    display: flex;
    justify-content: center;
    margin-block-start: var(--size-3);
    margin-inline-start: 0;
}

/* The dock's 899px boundary, not the app chrome's: this is the width at which
   the dock becomes a drawer that a reveal shuts. */
@media (max-width: 899px) {
    .game-shell--rail .desktop-only {
        display: none;
    }

    /* A picked territory's card takes the whole sheet: there is no room behind
       it for the plan as well. The wide dock keeps both, so the orders given
       and Lock plan stay reachable with a territory picked. */
    .game-shell--rail .phase-body.is-covered {
        display: none;
    }

    /* The rail already carries the purse, so the phone's deployment sheet is
       the placements and nothing else. */
    .game-shell--rail .plan-brief {
        display: none;
    }

    /* The phone's sheet head already sits clear of the card. */
    .terrcard-occ {
        padding-block-start: 0;
    }

    .reveal-bar {
        position: absolute;
        inset-inline: var(--size-2);
        bottom: var(--size-2);
        z-index: 2;
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: space-between;
        gap: var(--size-1) var(--size-2);
        padding: var(--size-1) var(--size-2);
        border: var(--rule-strong);
        background: var(--surface-2);
        box-shadow: var(--bevel);
    }

    .reveal-bar-what {
        font-size: var(--fs-0);
        color: var(--text-2);
    }

    /* The bar takes the corner the zoom controls sit in, so they stand down for
       as long as it is there. */
    .reveal-bar ~ .zoom-bar {
        display: none;
    }

    /* The button is the bar's other half, not a block of its own. */
    .reveal-bar .btn--block {
        width: auto;
        margin-inline: 0;
    }

    /* ---------------------------------------------------------------------------
       The rail's chips: the purse and the orders left this round, read-only. An
       order is given on the territory it comes from, so there is nothing here to
       press — these are a count, not a picker. They wrap to a second line rather
       than scroll, because a rail that scrolls sideways hides half of itself.
       --------------------------------------------------------------------------- */
    /* The left half: the chips on top, the hand under them. Both rows start at
       the same edge, so the hand's four slots and the button read as a measure
       against the purse and the orders above. */
    .rail-left {
        flex: 1 1 auto;
        min-width: 0;
        display: flex;
        flex-direction: column;
        justify-content: center;
        gap: var(--size-1);
    }

    /* The sheet is the panel's width, so the 420px reading measure would only
       leave gutters the order buttons would rather have. The tail of empty page
       under the panel goes too: a sheet stands as tall as what is in it, so the
       tail is not padding, it is height the map has given up. */
    .plan {
        max-width: none;
        padding-bottom: var(--size-2);
    }
}

/* ----------------------------------------------------------------------
   Touch targets. A finger needs --tap-min; a mouse does not, and growing
   these for everyone would loosen a dock written to be dense. Two ways of
   getting there: a control that owns its row grows, and a control wedged
   into a row keeps its size and grows only the box that takes the tap.
   ---------------------------------------------------------------------- */
@media (pointer: coarse) {
    .plan .nudge {
        height: var(--tap-min);
        min-width: var(--tap-min);
    }

    .plan .seat-chip,
    .plan .order-row {
        min-height: var(--tap-min);
    }

    /* On touch it is the fit button only: a small box, with the tap box
       grown out to --tap-min past it. */
    .zoom-btn {
        position: relative;
        min-width: var(--size-7);
        min-height: var(--size-7);
    }

    .zoom-btn::after {
        content: "";
        position: absolute;
        inset: min(0px, calc((var(--tap-min) - 100%) / -2));
    }

    /* The remove ×: the row it sits in is built around its size, so the tap
       box grows past it instead. */
    .plan .quiet-btn {
        position: relative;
    }

    .plan .quiet-btn::after {
        content: "";
        position: absolute;
        /* Out to --tap-min, never in: a percentage inset resolves against the
           box's own side, and min() keeps a side that is already long enough
           from being pulled back in. */
        inset-block: min(0px, calc((var(--tap-min) - 100%) / -2));
        inset-inline: min(0px, calc((var(--tap-min) - 100%) / -2));
    }

    /* A leg row's steppers are built small so the name keeps its room, so
       their tap boxes grow instead: to --tap-min tall, and halfway to --tap-min
       wide on the side away from the count. The stepper's clip has to go for
       that, and its corners are too small to miss it. */
    .plan .leg-row .stepper {
        overflow: visible;
    }

    .plan .leg-row .nudge {
        position: relative;
    }

    .plan .leg-row .nudge::after {
        content: "";
        position: absolute;
        inset-block: min(0px, calc((var(--tap-min) - 100%) / -2));
        inset-inline: 0;
    }

    .plan .leg-row .nudge:first-child::after {
        inset-inline-start: min(0px, calc((100% - var(--tap-min)) / 2));
    }

    .plan .leg-row .nudge:last-child::after {
        inset-inline-end: min(0px, calc((100% - var(--tap-min)) / 2));
    }
}
