html, body {
    margin: 0px;
    padding: 0px;
    height: 100%;
    width: 100%;
    overflow: hidden;
}

.adl-dialog {
    box-sizing: initial;
}

#wrapper {
    grid-template-rows: minmax(0, 1fr) auto;
    grid-template-columns: minmax(0, 1fr);
    user-select: none;
}

#gameHolder {
    overflow: hidden;
    position: relative;
    display: grid;
    grid-template-rows: 1fr;
    grid-template-columns: minmax(0, 1fr);
    min-width: 0;
    min-height: 0;
    height: 100%;
    width: 100%;
}

/*
    Scroll3D's canvas host. It has to be given a size, because the engine sizes
    itself from the holder's offsetWidth/offsetHeight -- a holder with no box gets
    a zero-sized renderer and draws nothing, with no error.

    Do NOT style pointer-events on the holder's children. The engine appends a
    WebGL canvas and a touch overlay, and rewrites the WebGL canvas's
    pointer-events every frame ("all" normally, "none" while its touch pad is
    showing). A rule here would be overwritten, and it would also fight that mode
    switch. The engine saves and restores that value itself, which is only safe
    while nothing else writes it.
*/
#scroll3dHolder {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    width: 100%;
}

.mainSection {
    margin: 12px;
    margin-left: calc(12px + var(--sail));
    margin-right: calc(12px + var(--sair));
    margin-top: calc(12px + var(--sait));
    margin-bottom: calc(12px + var(--sab));
    grid-row: 1;
    grid-column: 1;
    position: relative;
    display: grid;
    overflow: hidden;
}

/*
    The gameplay UI overlay sits on top of the map, so without this it would
    swallow every drag, pinch and wheel meant for the camera -- which is exactly
    what the deleted #mainCanvas used to do. It is pointer-transparent as a
    container, and each interactive child opts back in with `pointer-events: all`.

    Same arrangement Realms of Conflict uses for its character-creator overlay
    (#characterCreatorGrid is `none`, its sidebar and bottombar are `all`). The
    point is that the map stays draggable *through* the UI, not only in the gaps
    between panels.

    Deliberately NOT applied to `.mainSection`: the title screen and the editor are
    full menus of controls and should receive pointer events normally. Only the
    overlay that is meant to be transparent to the map gets this.
*/
#playArea {
    pointer-events: none;
    min-width: 0;
    min-height: 0;
}

/*
    The old play UI's rules (`#cityToolbar`, `.city-tool`, `#cityStatus`, `#cityHud`,
    `.city-hud-*`, `#citySpeedControls`) were deleted here on 2026-10-03, when the play
    HUD replaced `src/ui/toolbar.js` and `src/ui/hud.js`. They were the dark-Material
    palette the design pass replaced, and leaving them would have been a second, dead
    answer to the same questions -- the `mc-` components below own all of it now.

    `#playArea` above is deliberately kept: it is the overlay's pointer-transparency
    contract and has nothing to do with either palette.
*/
#adUnit {
    background-color: #000000;
}
/*
    ========================================================================
    Design tokens — lifted from `design/tokens.css` on 2026-10-03
    ========================================================================

    The 2026-10-03 UI pass approved a visual language and wrote it as
    `design/tokens.css`, deliberately outside `src/` so the pass shipped
    nothing. This is that file's token layer moved in, so the panels can use it.
    `design/` keeps its own copy, because the sheets there are standalone
    mockups that have to stay openable from the filesystem and so cannot link
    this file; the two are now duplicates and must be kept in step by hand.

    ## What was taken, and what was left behind

    Taken: the `:root` block, the `prefers-reduced-motion` override, the
    `border-box` reset, and the three primitives (`.glass`, `.num`,
    `.focusable`). Left behind, because each is a visible change to shipped
    markup rather than a token, and each wants its own decision:

      * the mockups' `body` rule — font family, 13px size, text colour and a
        `#0a0c10` page background. `html, body` at the top of this file already
        owns the shell, and elements here set their own `font:` shorthands, so
        that rule would be a restyle rather than a token.
      * everything in `design/ui.css` — those are components, not tokens, and
        they arrive with the components that use them.

    ## Why the palette is warm on cool

    The map already owns two hues outright: a green grass field and blue water.
    The previous panels used Material's default blue (`#1e88e5`), which competes
    with the water and reads as a framework default rather than as a decision —
    the strongest single reason `TODO.md`'s UI gate says the panels must not look
    like programmer art. So the interface takes the hue the map cannot use:
    **civic gold**, warm, for actions and selection; and **deep slate glass** for
    surfaces, which is cool and recessive so that gold-on-dark reads as chrome
    rather than as scenery.

    The RCI valves are the one place where convention beats the palette: players
    read residential/commercial/industrial as green/blue/amber from three decades
    of the genre, so those three keep their conventional hues and are tuned for
    contrast on dark glass rather than replaced.

    ## Names, and why these are unprefixed

    Checked against everything else already on the page: ADL ships its own
    `:root` (`--sait`, `--sair`, `--commonRadius`, `--borderColor`, …) and there
    is no overlap, so these can stay unprefixed and readable. `design/ui.css`'s
    `mc-` class prefix exists for the component layer instead, so that the palette
    cannot collide with the app's own shell rules while the two live side by side.
    (Until 2026-10-03 that sentence named `.city-tool` / `.city-hud-*`; those were
    the old play UI and the play HUD deleted them.)
*/

:root {
    /* ---- Surfaces -------------------------------------------------------
     * Glass, not paint. Every panel is translucent so the city stays legible
     * *under* the UI: on a city builder the map is the content, and an opaque
     * panel hides the thing the player is looking at. The alpha values are the
     * lowest that keep 4.5:1 text contrast over the brightest plate the
     * backdrop tool captured (a white concrete roof), which is the worst case
     * the map can present.
     */
    --surface-1: rgba(14, 17, 22, 0.72);   /* standing panels */
    --surface-2: rgba(22, 26, 33, 0.86);   /* raised: menus, modals, popovers */
    --surface-3: rgba(35, 41, 51, 0.92);   /* inset: wells, table stripes */

    /* A 1px inner top highlight. This is what separates "glass" from "a dark
     * rectangle with a shadow": real glass catches light on its upper edge. */
    --surface-edge: rgba(255, 255, 255, 0.10);
    --surface-edge-strong: rgba(255, 255, 255, 0.18);

    /* ---- Scrollbars -----------------------------------------------------
     * The rules below are the two-engine spelling of these two colours. A
     * scrollbar is the one piece of chrome the palette cannot reach on its own:
     * on Windows the default is a light grey slab with arrow buttons, which
     * cuts across a dark translucent panel and is the only thing on screen the
     * design does not own (the user's report, 2026-10-07).
     */
    --scroll-track: rgba(0, 0, 0, 0.22);
    --scroll-thumb: rgba(255, 255, 255, 0.22);
    --scroll-thumb-hover: rgba(255, 255, 255, 0.34);

    /* ---- Text ----------------------------------------------------------- */
    --text-1: rgba(255, 255, 255, 0.96);   /* primary */
    --text-2: rgba(255, 255, 255, 0.72);   /* labels, secondary */
    --text-3: rgba(255, 255, 255, 0.48);   /* hints, disabled */
    --text-on-accent: #1a1206;             /* near-black, for gold fills */
    --text-on-data: #0b1016;               /* near-black, for the data blue */

    /* ---- Accents --------------------------------------------------------
     * `--accent` is singular on purpose. A UI where four things are highlighted
     * is a UI where nothing is: gold means "this is the armed tool, this is the
     * primary action, this is what has focus", and nothing else may use it.
     */
    --accent: #f2a33c;
    --accent-hover: #ffb457;
    --accent-press: #d98a26;
    --accent-dim: rgba(242, 163, 60, 0.16);
    --accent-glow: rgba(242, 163, 60, 0.35);

    /* Data blue: informational, never interactive. Demand, coverage, charts. */
    --data: #57b6e5;
    --data-dim: rgba(87, 182, 229, 0.18);

    /* Semantic. Reserved for actual state changes, never decoration. */
    --ok: #6fcf87;
    --ok-dim: rgba(111, 207, 135, 0.14);
    --warn: #f0b44a;
    --danger: #ef6d5a;
    --danger-dim: rgba(239, 109, 90, 0.16);

    /* ---- RCI valves -----------------------------------------------------
     * Conventional hues, re-tuned for a dark ground. The saturation is lowered
     * from the primary colours a legend would use, because a rail-to-rail fill
     * sits directly beside text and full-saturation green beside white text
     * vibrates.
     */
    --rci-residential: #6fcf87;
    --rci-commercial: #57b6e5;
    --rci-industrial: #e8804a;

    /* ---- Scale ----------------------------------------------------------
     * A 4px base. Every gap, pad and inset in the mockups is a multiple, which
     * is what stops a panel set from looking hand-nudged.
     */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;

    /* ---- Layout dimensions ----------------------------------------------
     * The status card's own width, as a token because **two panels share it**: the
     * standings card takes its `width` from it and the minimap takes its `width` from
     * it, so the map lines up with the card above it by construction rather than by
     * two numbers that happen to agree today.
     *
     * **It is the card's `width`, not its `min-width`, as of 2026-10-16.** The floor
     * was supposed to be the rendered width -- it was measured that way (a long name
     * and ten-figure figures did not widen the card) -- but the funds line beside the
     * treasury does widen it as its value changes, and the user's report was that a
     * card twitching above a minimap that never moves looks wrong. A row that cannot
     * fit a fixed width now wraps instead (`mc-status-funds`), which is the trade
     * that keeps the width honest at 232px, and at the 290px the TV breakpoint raises
     * it to.
     *
     * **What the map does with it is deliberately not the same number.** The map's
     * *square* is 10px narrower than this -- the minimap panel's 4px padding and 1px
     * border on each side -- so that the map's *panel*, which is what sits under the
     * card, is the width that has to match. That is the whole of why the aligned map
     * is 222px and not 232.
     */
    --status-card-width: 232px;

    /* Type scale, roughly 1.25 between steps. */
    --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --text-micro: 10px;
    --text-small: 12px;
    --text-body: 13px;
    --text-lead: 15px;
    --text-head: 19px;
    --text-hero: 27px;

    /* Radii. Panels are softer than the controls inside them, which is what
     * makes a control read as sitting *on* a surface rather than in it. */
    --radius-panel: 12px;
    --radius-control: 8px;
    --radius-pill: 999px;

    /* Elevation. Two shadows each: a tight contact shadow and a wide ambient
     * one. A single large blur reads as a glow and makes glass look like it is
     * floating unattached to the map. */
    --elev-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.28);
    --elev-2: 0 2px 6px rgba(0, 0, 0, 0.45), 0 12px 28px rgba(0, 0, 0, 0.38);
    --elev-3: 0 4px 12px rgba(0, 0, 0, 0.5), 0 24px 60px rgba(0, 0, 0, 0.48);

    /* ---- Controls -------------------------------------------------------
     * 44px is the minimum comfortable touch target, and it is also roughly the
     * size a controller focus ring wants to sit inside. Phone layouts hit it;
     * desktop layouts may compress to 36 because a mouse is precise -- the
     * difference is expressed as a token so the two stay related.
     */
    --control-h: 36px;
    --control-h-touch: 44px;

    --blur: 14px;

    /* Focus. One ring, used by keyboard *and* gamepad, so the two input modes
     * are visibly the same idea rather than two idioms a player must learn.
     *
     * Note it is a *ring* and not a colour change, and that is not a style
     * preference: ADL already sets `outline: none` on `*`, so the browser's own
     * focus indicator is suppressed page-wide, and the accent already means
     * "armed / selected" elsewhere. See `.focusable` below.
     */
    --focus-ring: 0 0 0 2px var(--surface-2), 0 0 0 4px var(--accent);

    /* Motion. Fast for hover and press feedback, panel for anything that
     * appears or disappears on its own. */
    --motion-fast: 120ms;
    --motion-panel: 180ms;
    --ease-out: cubic-bezier(0.2, 0.8, 0.3, 1);
}

/* Motion is a preference, not a flourish. Both the modal and the dock animate;
 * with `reduce` they resolve instantly and the panels stay usable. */
@media (prefers-reduced-motion: reduce) {
    :root {
        --motion-fast: 0ms;
        --motion-panel: 0ms;
    }
}

/*
 * `border-box` on everything, and this is not housekeeping -- it is the fix for
 * the reported "the toolbar has no right margin", and it is worth stating why.
 *
 * Nothing in this project set `box-sizing` anywhere, so every panel was on the
 * default `content-box`. That makes `width: 100%` mean *content* width, and the
 * padding and border are then added on top. A compact panel carrying
 * `width: 100%` plus 4px of padding and a 1px border therefore computed to 110%
 * of its column: the dock measured 384px inside a 374px column and hung 10px off
 * the right edge of the screen, with its right rounded corner outside the
 * viewport. Same rule, same 10px, took the bottom sheet's `width: 100%` modal 2px
 * past the right edge too.
 *
 * It reads as a missing margin because it *is* a missing margin, and it will come
 * back the moment anyone writes `width: 100%` next to a padding value.
 *
 * The shipped panels were measured before this landed and none of them overflows
 * today -- the toolbar is content-sized and anchored `justify-self: start`, so
 * `width: 100%` never applies to it. This is therefore a latent hazard being
 * disarmed rather than a live defect being fixed, and `tools/ui-layout-check.mjs`
 * is what keeps it disarmed.
 *
 * This is the universal selector and it is deliberate. A per-element fix would
 * have corrected the two panels that happen to overflow and left the next one to
 * be found by eye.
 */
*,
*::before,
*::after {
    box-sizing: border-box;
}

/*
 * ---- Primitives --------------------------------------------------------
 */
.glass {
    background: var(--surface-1);
    border: 1px solid var(--surface-edge);
    border-radius: var(--radius-panel);
    box-shadow: var(--elev-1), inset 0 1px 0 var(--surface-edge);
    backdrop-filter: blur(var(--blur)) saturate(140%);
    -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
}

/*
 * Figures are tabular *everywhere* money or population appears, not per-panel.
 * A rising treasury that reflows its own width every tick is the single most
 * distracting thing a standing HUD can do, and the readout had already
 * discovered this one class at a time -- so this is the generalisation of a rule
 * it was already carrying. The play HUD reuses this class rather than defining
 * its own; `status.js` and `dock.js` put `num` on every figure they print.
 */
.num {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

/*
 * Every interactive element gets this, and only interactive elements do. The
 * focus treatment is deliberately a *ring* and not a colour change, because a
 * colour change would be indistinguishable from the selected/armed state that
 * the same accent already carries.
 */
.focusable:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
    z-index: 2;
}

/*
    ========================================================================
    Title screen — lifted from `design/ui.css` and `design/title.html`
    ========================================================================

    The first component built on the tokens above. Two things differ from the
    mockup on purpose, and both are decisions rather than drift:

    1. There is no `.plate` image. The mockup stood a captured still behind the
       menu because there was nothing else to put there; the app runs a real
       session at speed 0 and lets the city be its own backdrop. What survives
       from the plate is the **scrim** below, which exists because the menu has
       to stay legible over whatever the map happens to be showing.

    2. Input instructions are omitted so the title stays focused on the game.
*/

/*
    Full-bleed, and this overrides `.mainSection` deliberately. That class carries
    the 12px inset (plus the safe-area insets) which is right for the play area and
    the editor, both of which are panels beside a map. A title screen is not a panel:
    the scrim has to reach the edges or the muted band stops short and the whole
    thing reads as a stray rectangle. The safe-area insets are not dropped, they move
    inside -- see the padding on `.mc-title`.
*/
#titleScreen {
    margin: 0;
    display: block;

    /*
        Stated here, and this line is the whole reason the wordmark was rendering
        **black** in the first screenshots of this screen.

        `design/tokens.css` carries `body { color: var(--text-1) }`, and that rule was
        deliberately not lifted -- `html, body` in this file already owns the shell, and
        the rest of it is a restyle. But the palette is built for a dark ground, so the
        *foreground* is not decoration: without it the page falls back to the browser's
        default black. Every other piece of text here happens to set its own colour
        (`--text-2` on the hints, `--text-3` on the build stamp, an explicit rgba on the
        tagline), so the wordmark -- the largest thing on the screen -- was the single
        casualty, which is what made it look like a logo bug rather than a missing line.

        Scoped to the screen rather than set on `body`, because the play HUD and the
        palette set their own colours and a global change is a bigger decision than this
        one -- it is recorded OPEN in TODO.md.
    */
    color: var(--text-1);
}

/*
    The scrim. Two gradients rather than one, from the mockup: a bottom-to-top fade
    that anchors the menu against the horizon, and a corner radial that darkens the
    lower left where the wordmark and the menu actually sit. A flat wash would have to
    be as dark as the darkest point to work, which would hide the city the backdrop
    exists to show.
*/
#titleScreen::before {
    content: "";
    position: absolute;
    inset: 0;
    background:
        linear-gradient(to top, rgba(6, 8, 12, 0.96) 0%, rgba(6, 8, 12, 0.7) 34%, rgba(6, 8, 12, 0.2) 70%),
        radial-gradient(120% 95% at 8% 100%, rgba(6, 8, 12, 0.9) 0%, rgba(6, 8, 12, 0) 60%);
}

.mc-title {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: var(--space-5);
    padding: var(--space-6);
    /* The safe-area inset that `#titleScreen` gave up when it lost its margin. */
    padding-bottom: calc(var(--space-6) + env(safe-area-inset-bottom, 0px));
}

/*
    The account button: the title screen's one control that is not a menu item.

    Positioned absolutely rather than given a row of its own, because `.mc-title` is a
    bottom-anchored column and a new flow child would push the menu up the screen. It is a
    statement about the player rather than an action on the game, so it belongs in the
    corner instead of the menu column -- see `titleAccount` in `src/ui/title.js`.

    Tab, menu arrow navigation and controller geometry navigation all reach it.
*/
.mc-title-account {
    position: absolute;
    top: calc(var(--space-6) + var(--sait));
    right: calc(var(--space-6) + var(--sair));
    /* Above the wordmark, which is the only other thing that could reach this corner. */
    z-index: 1;
}

.mc-account-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    height: var(--control-h);
    padding: 0 var(--space-3);
    border: 1px solid var(--surface-edge);
    border-radius: 999px;
    background: var(--surface-1);
    backdrop-filter: blur(var(--blur)) saturate(140%);
    -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
    box-shadow: var(--elev-1);
    color: var(--text-1);
    font: inherit;
    font-size: var(--text-small);
    font-weight: 600;
    cursor: pointer;
    transition: background var(--motion-fast) var(--ease-out),
                border-color var(--motion-fast) var(--ease-out);
}

.mc-account-btn:hover {
    background: var(--surface-3);
    border-color: var(--surface-edge-strong);
}

/*
    A pill reads as one object only if the avatar sits close to its edge. Without this the
    button's own left padding and the avatar's inset stack, and the circle floats in the
    middle of the pill.

    A class from the script rather than `:has()`, which is newer than the rest of this
    sheet's selectors and would be the only `:has()` in the project.
*/
.mc-account-btn.is-signed-in { padding-left: var(--space-1); }

.mc-account-avatar,
.mc-account-initial {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    flex: none;
}

.mc-account-avatar {
    object-fit: cover;
    background: var(--surface-3);
}

/*
    The fallback when the avatar cannot be fetched. The endpoint is remote, and a
    broken-image glyph on the first screen a player sees reads as a broken *game* rather
    than as an offline one -- see `avatarElement`.
*/
.mc-account-initial {
    display: grid;
    place-items: center;
    background: var(--accent);
    color: var(--text-on-accent);
    font-size: var(--text-small);
    font-weight: 700;
}

/* A long username must not stretch the pill across the screen, or push its own right edge
   off it. */
.mc-account-name {
    max-width: 30vw;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*
    The wordmark: the game's logo artwork (2026-10-16), on the `.mc-wordmark-text`
    heading that used to hold the words as type.

    **The size is carried in `width` on purpose: the clamp is the width the
    typographic wordmark had.** It is the old type scale -- `clamp(44px, 9vw, 92px)`
    -- multiplied by the ~3.26 width-to-cap-height ratio it produced, because
    *width* is what the rest of this screen was laid out around (the tagline's
    34ch measure, the bottom row, and the landscape-phone correction further
    down). A clamp on the artwork's *height* would not reproduce that: the
    artwork's height spans the dot of the `i` down past the descender of the `y`,
    which the old `line-height: 0.92` line box clipped, so the same pixel height
    would come out narrower than the type did.

    Two things about the artwork are load-bearing. It is **trimmed to its own
    ink** -- the source PNG had ~16% transparent margin above and below -- so the
    box is the logo and the column's gaps are the gaps written here. And it
    **carries its own drop shadow**, which is why the `text-shadow` this rule
    used to set is gone; the artwork is placed on the live city with no filter,
    glow or scrim, which is how the wordmark has always been asked to read.

    `display: block` is not tidiness: an inline replaced element would add the
    `<h1>`'s line-box leading above and below the image and the spacing would
    stop matching the rules in this block.
*/
.mc-wordmark {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.mc-wordmark-eyebrow {
    align-self: flex-start;
    padding: 4px 8px;
    border-radius: 4px;
    background: rgba(6, 8, 12, 0.82);
    font-size: var(--text-small);
    font-weight: 600;
    letter-spacing: 0.28em;
    text-transform: uppercase;
    color: var(--accent);
}

.mc-wordmark-text {
    margin: 0;
}

.mc-wordmark-logo {
    display: block;
    width: clamp(150px, 29vw, 300px);
    height: auto;
}

.mc-wordmark-rule {
    width: 68px;
    height: 4px;
    border-radius: 2px;
    background: var(--accent);
    box-shadow: 0 0 18px var(--accent-glow);
}

.mc-title-tagline {
    margin: 0;
    max-width: 34ch;
    font-size: var(--text-lead);
    /*
        Brighter than a secondary label would normally be. The tagline sits over the
        map rather than over a panel, so it has no glass to carry its contrast -- the
        first title shot rendered it barely legible over the bright concrete in the
        lower-middle of the backdrop. A text shadow, not a scrim: a scrim wide enough
        to cover the tagline would flatten the whole bottom third of the city.
    */
    color: rgba(255, 255, 255, 0.86);
    text-shadow: 0 1px 12px rgba(0, 0, 0, 0.85), 0 1px 3px rgba(0, 0, 0, 0.7);
    line-height: 1.5;
}

/*
    The bottom row: the menu on the left, and the version and copyright on the right.
    Putting the stamp beside the menu rather than centred means the two never fight
    over the same horizontal space at narrow widths.
*/
.mc-title-row {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-6);
}

/*
    The menu. Primary action first and physically largest, because on a title screen
    the first decision should be the obvious one.

    **One item is a normal state, not a bug.** On a first play only "New city" is
    available, so this column holds a single button -- see the note at the top of
    `src/ui/title.js` before adding disabled items to fill it out.
*/
.mc-title-menu {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
    width: min(340px, 100%);
}

.mc-menu-item {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    height: 46px;
    padding: 0 var(--space-4);
    border: 1px solid var(--surface-edge);
    border-radius: var(--radius-control);
    background: var(--surface-1);
    backdrop-filter: blur(var(--blur)) saturate(140%);
    -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
    color: var(--text-1);
    font: inherit;
    font-size: var(--text-lead);
    font-weight: 600;
    text-align: left;
    cursor: pointer;
    box-shadow: var(--elev-1);
    transition: background var(--motion-fast) var(--ease-out),
                border-color var(--motion-fast) var(--ease-out),
                transform var(--motion-fast) var(--ease-out);
}

.mc-menu-item:hover {
    background: var(--surface-3);
    border-color: var(--surface-edge-strong);
    transform: translateX(2px);
}

.mc-menu-item-primary {
    background: var(--accent);
    border-color: var(--accent-hover);
    color: var(--text-on-accent);
    box-shadow: 0 0 24px var(--accent-glow), var(--elev-2);
}

.mc-menu-item-primary:hover { background: var(--accent-hover); }

/* Button glyph, right-aligned and muted, so the menu scan-line is the label. */
.mc-menu-glyph {
    display: flex;
    align-items: center;
    color: inherit;
    opacity: 0.85;
}

/*
    **The focus ring, written twice, and this is the reason.**

    Keyboard selection is real DOM focus and `.focusable:focus-visible` above already
    covers it. Gamepad selection is NOT focus: GamepadHelper picks a candidate by
    bounding rectangle, adds `.gamepadHighlighted`, and calls `blur()` rather than
    `focus()`. So the two are separate mechanisms delivering one visual, and a rule
    that only handled `:focus-visible` would leave a controller with no indicator at
    all on a control the keyboard lights up perfectly.

    The primary item keeps its accent glow underneath the ring, because the ring is
    what says "this has focus" and the glow is what says "this is the main action" --
    dropping either would lose a distinction the palette depends on.
*/
.mc-menu-item.gamepadHighlighted {
    box-shadow: var(--focus-ring);
}

.mc-menu-item-primary.gamepadHighlighted {
    box-shadow: var(--focus-ring), 0 0 24px var(--accent-glow);
}

.mc-title-side {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: var(--space-3);
    min-width: 0;
    max-width: 340px;
}

/* ======================================================================
   Hint row
   ======================================================================
   Input-instruction rows are not shown. Keep individual `.mc-hint` status labels
   and tooltips, which carry game information rather than control instructions.
   ==================================================================== */
.mc-hints {
    display: none;
    align-items: center;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-4);
    font-size: var(--text-small);
    color: var(--text-2);
}

.mc-hint {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    white-space: nowrap;
}

.mc-key {
    display: inline-grid;
    place-items: center;
    min-width: 22px;
    height: 22px;
    padding: 0 6px;
    border-radius: 5px;
    background: rgba(255, 255, 255, 0.1);
    border: 1px solid var(--surface-edge-strong);
    box-shadow: 0 1px 0 rgba(0, 0, 0, 0.35);
    font-size: var(--text-micro);
    font-weight: 700;
    color: var(--text-1);
    /*
        Load-bearing. Without this a multi-word key label wraps *inside* the key --
        the first TV shot rendered "W A S D" as four stacked letters in a tall
        lozenge, which reads as a broken glyph rather than as a key. A key cap is
        never allowed to wrap.
    */
    white-space: nowrap;
}

/* A gamepad button is round; a keyboard key is not. The difference is a real
 * affordance -- it tells a player which device the hint is for at a glance. */
.mc-key.is-pad {
    border-radius: 50%;
    min-width: 22px;
    padding: 0;
}

.mc-key.is-pad[data-btn="a"] { background: #3f7d3f; border-color: #59a559; }
.mc-key.is-pad[data-btn="b"] { background: #8a3b3b; border-color: #b55252; }

/*
    The device chip: which device the hints are for. The mockup labelled it "ready",
    which was a placeholder; the recorded decision asks the hints to *name the active
    device*, so it does.
*/
.mc-hint-device {
    color: var(--text-3);
}

.mc-hint-device svg {
    color: var(--text-2);
}

.mc-build {
    text-align: right;
    font-size: var(--text-small);
    color: var(--text-3);
    line-height: 1.6;
}

/*
    Compact: a narrow *or* short viewport. Width alone is the wrong question for this
    layout and the mockup pass proved it -- at 844px wide a landscape phone never
    fired the 700px breakpoint, so it kept the desktop layout on a 390px-tall screen.
    The height test is what catches that, and it is why this is one query with two
    conditions rather than a width breakpoint.
*/
@media (max-width: 700px), (max-height: 520px) {
    .mc-title {
        padding: var(--space-4);
        gap: var(--space-4);
        padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
    }

    /*
        The corner control follows the tighter inset, or it ends up further from the screen
        edge than everything else on a phone -- which is the same asymmetry the dock's
        overhang was reported as, and just as invisible without measuring.
    */
    .mc-title-account {
        top: calc(var(--space-4) + var(--sait));
        right: calc(var(--space-4) + var(--sair));
    }

    .mc-title-menu { width: 100%; }
    .mc-menu-item { height: 46px; font-size: var(--text-body); }

    .mc-title-row {
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-4);
    }

    .mc-title-side {
        align-items: flex-start;
        max-width: none;
        /* Below the menu, so the menu keeps the thumb's easiest reach. */
        order: 2;
    }

    .mc-hints { justify-content: flex-start; }
    .mc-build { text-align: left; }

    /*
        The tagline goes before the layout does. It is the longest run of text on the
        screen and the least load-bearing -- a player who wants to know what the game
        is has already read it by the time they reach a phone.
    */
    .mc-title-tagline { display: none; }
}

/*
    The 10-foot step. `tokens.css` raises the whole type scale at this width; the
    menu's touch target has to grow with it or the largest screen gets the smallest
    relative button.

    **The wordmark's ceiling has to rise too, and that is not cosmetic.** The logo
    is `clamp()`ed on viewport *width* with a 300px ceiling, and at 1920px that
    ceiling has already been reached -- so without this block the title stays
    pixel-sized while everything around it grows by the 1.4x step above, and it
    ends up reading as *smaller* than it did on a laptop. That is the same defect
    the typographic wordmark had (a 92px clamp pinned at 1920px, against 92px at
    1280) and it was measured then; the ceiling moves to the artwork's own
    proportion of the old type scale, i.e. ~430px at 1920.

    The tagline gets `--text-lead` (which has already scaled) and a wider measure,
    because a 34ch column at this size wraps into a tall thin paragraph.
*/
@media (min-width: 1600px) {
    .mc-title {
        padding: var(--space-6);
        gap: var(--space-6);
    }

    .mc-menu-item { height: 56px; }
    .mc-title-side { max-width: 420px; }

    .mc-wordmark-logo { width: clamp(300px, 22vw, 430px); }
    .mc-title-tagline { font-size: var(--text-lead); max-width: 38ch; }
}

/* ======================================================================
   Play HUD

   Lifted from `design/ui.css`, which the design pass wrote to be lifted "as-is".
   The components are unchanged; what is new here is only the plumbing that turns
   three mockup sheets into one screen.

   Three things are worth knowing before editing this block:

   1. **The overlay is a 3x3 grid, not stacked absolute panels.** The old play UI
      was two absolutely-placed blocks (`#cityHud`, `#cityToolbar`) that had to be
      kept clear of each other by hand. `min-width: 0` on every grid child is
      load-bearing rather than tidiness -- an `auto` column takes what its content
      demands and overflows instead of shrinking, which is how the first phone plate
      ended up with the speed control clipped underneath the alerts button.

   2. **Pointer transparency is inherited from `#playArea`.** The overlay is
      `pointer-events: none` and every interactive child opts back in. Do not add a
      catch-all `pointer-events: all` here; it would swallow every drag meant for
      the map.

   3. **Demand is hidden at compact sizes, and that is the mockup's decision, not an
      oversight.** `.mc-status .mc-demand` is `display: none` under the compact query
      and only re-shown by the `min-width: 701px) and (min-height: 521px)` rule at
      the bottom -- which means **a phone player cannot see RCI demand at all**.
      Recorded in `TODO.md` rather than quietly changed, because a compact demand
      treatment is a design decision nobody has made.

   `body` deliberately still carries no foreground colour; `mc-panel` sets its own,
   and the global case is OPEN in `TODO.md` (it is what made the wordmark render
   black on the title screen).
   ==================================================================== */

.mc-panel {
    background: var(--surface-1);
    border: 1px solid var(--surface-edge);
    border-radius: var(--radius-panel);
    box-shadow: var(--elev-1), inset 0 1px 0 var(--surface-edge);
    backdrop-filter: blur(var(--blur)) saturate(140%);
    -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
    color: var(--text-1);
}

/*
    A recessed area inside a panel, so "these belong together" is shown by depth
    rather than by another border -- a box inside a box inside a box is the fastest
    way to make a panel look like a form.
*/
.mc-well {
    background: var(--surface-3);
    border-radius: var(--radius-control);
    box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.25);
}

.mc-label {
    font-size: var(--text-small);
    color: var(--text-2);
    letter-spacing: 0.02em;
}

/*
    Tabular figures come from the `.num` primitive in the token layer above -- not
    redefined here. `status.js` and `dock.js` put `num` on every figure they print.
*/
/* ---- the overlay grid ------------------------------------------------ */

.mc-overlay {
    position: absolute;
    inset: 0;
    /* Transparent to the pointer, so the map stays draggable *through* the UI. */
    pointer-events: none;
    padding: var(--space-3);
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-rows: auto 1fr auto;
    gap: var(--space-3);
    align-items: start;
    /* `#playArea` is `.mainSection`, which already carries the safe-area margins. */
}

.mc-overlay > * { pointer-events: all; }

/*
    The gamepad focus reticle.

    A ring at the centre of the play screen, shown only while the pad is aiming at
    the map (`playHud.setReticleVisible`). It is not the tile cursor: the renderer
    still draws a solid square on the tile under this centre, and the ring is the
    screen-space answer to "where is the pad pointing" when the tile itself is small
    at a wide zoom.

    The double shadow is deliberate -- a light ring alone disappears over the bright
    grass and the pale water, and a dark one disappears over asphalt. The centre dot
    gives the eye an exact point to aim with.
*/
.mc-overlay .mc-reticle {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    pointer-events: none;
}

.mc-overlay .mc-reticle[hidden] { display: none; }

.mc-reticle-ring {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, .95);
    box-shadow:
        0 0 0 2px rgba(0, 0, 0, .5),
        inset 0 0 0 2px rgba(0, 0, 0, .35);
    position: relative;
    transition: transform .12s ease, border-color .12s ease;
}

.mc-reticle-ring::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    background: #fff;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, .55);
}

.mc-reticle.is-dragging .mc-reticle-ring {
    transform: scale(1.18);
    border-color: var(--accent);
}

.mc-overlay.is-tv .mc-reticle-ring {
    width: 56px;
    height: 56px;
}

@media (prefers-reduced-motion: reduce) {
    .mc-reticle-ring { transition: none; }
}

/*
    Controller mode removes the camera buttons the pad already owns.

    Rotate and zoom are on the right stick and the D-pad, so leaving their HUD
    buttons in place only adds stops to the gamepad's geometry navigation -- and a
    cursor that walks over controls the player would never choose reads as a broken
    focus model. `display: none` also makes GamepadHelper skip them, because its
    candidate filter tests computed visibility.
*/
.mc-overlay.is-gamepad .mc-camera,
.mc-overlay.is-gamepad .mc-mobile-rotate { display: none; }

.mc-col-left,
.mc-col-right,
.mc-row-bottom { min-width: 0; }

.mc-col-left { grid-column: 1; grid-row: 1; }

/*
    The left column is a **stack**: the standings card and, under it, the Query readout
    (2026-10-11). It used to be the status card itself wearing this class, which left nowhere
    for a second left-hand card to go except the bottom bar -- where the feedback pill and the
    dock already live.

    `align-items: flex-start` matters and is not decoration: the column is a grid item in a
    column whose track is `auto`, so a stretched child would take the status card's width and
    the readout would be drawn as wide as the treasury display rather than as wide as its own
    longest row.
*/
.mc-col-left {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-2);
}
.mc-col-right { grid-column: 3; grid-row: 1; justify-self: end; }
.mc-row-bottom { grid-column: 1 / -1; grid-row: 3; align-self: end; }

.mc-top-right { display: flex; gap: var(--space-2); }

/* Feedback above the dock, controls to the right. */
.mc-bottom {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-3);
    min-width: 0;
}

.mc-bottom-left {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    align-items: flex-start;
    min-width: 0;
}

.mc-bottom-right {
    display: flex;
    align-items: flex-end;
    gap: var(--space-2);
}

/*
    The bottom band must not claim the pointer, because it is a full-width row and
    most of it is empty.

    `.mc-row-bottom` is `grid-column: 1 / -1`, and `.mc-overlay > *` hands it
    `pointer-events: all` -- so the whole strip across the bottom of the screen
    belonged to it, including the dead space between the dock and the camera cluster.
    `.mc-bottom` is `justify-content: space-between`, so that middle gap is the row's
    own space and not any child's, which is why hiding the children would not have
    fixed it. A drag started there was silently swallowed: reported by the user and
    then measured at 1280x800, where a drag at dead centre left the camera at
    `240.5,240.5,2` -- unchanged -- with `div.mc-bottom.mc-row-bottom` under the
    pointer.

    **This is the catch-all the comment on `#playArea` warns against, one level down.**
    That rule keeps the overlay pointer-transparent so the map stays draggable through
    the UI, and `.mc-overlay > *` then opted every direct child back in -- right for the
    two card columns, which are sized to their contents, and wrong for this row, which
    spans the screen. The layout containers opt **out**, and each real panel opts back
    in, so the rule is now the same at every level rather than being true at the top of
    the tree and false at the bottom.

    `-left` and `-right` are containers too rather than panels, and both have dead space
    of their own at compact widths: the left one is `width: 100%` and stacks the strip
    above the dock with a gap between them, and the right one keeps its `flex-end`
    alignment after the camera cluster is hidden on a phone, leaving the whole space to
    the left of the pinned Pan empty. Both were blocking before this for the same reason
    the row was.
*/
.mc-bottom > *,
.mc-bottom-left > *,
.mc-bottom-right > * {
    pointer-events: all;
}

/*
    **This block must stay below the one above, and that is not style.**

    `.mc-bottom-left` and `.mc-bottom-right` are each a container *and* a child of a
    container, so the two rules match them at **equal specificity** -- and the later one
    wins. Written the other way round, `.mc-bottom > *` hands `pointer-events: all`
    straight back to the two containers the rule above just turned off, which is exactly
    what the first version of this fix did: the row stopped blocking, and the two
    containers inside it kept blocking, which is most of the same strip.

    It was caught by the browser check rather than by looking, because the surviving
    dead space is the *gap* between two panels and reads as margin. The check walks a
    grid of points across the whole band and fails if any of them resolves to a layout
    container, which is a rule about the box tree rather than about one viewport's
    pixel positions.
*/
.mc-row-bottom,
.mc-bottom,
.mc-bottom-left,
.mc-bottom-right {
    pointer-events: none;
}

/* ---- status card ----------------------------------------------------- */

.mc-status {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);

    /*
        A **fixed** width, not a minimum (2026-10-16). It was `min-width`, so any row that
        grew -- the treasury gaining a digit, and the funds net beside it changing at all --
        widened the whole card, which reads as the panel twitching above a minimap that
        never moves. `--status-card-width` is what aligns the two, and both the minimap and
        the Query card already take it as a width rather than a floor.
    */
    width: var(--status-card-width);
}

/*
    ---- the Query readout ------------------------------------------------

    The card the Query tool fills in, in the left column under the standings. Hidden
    until the first query, so an armed-but-unused tool adds nothing to the screen.

    `[hidden]` is restated as a rule because `hidden` is only the *initial* value of
    `display`, so any later `display` on this element would silently win -- the same
    reason `.mc-hint[hidden]` exists further down.

    The list is a two-column grid rather than a run of `<li>`s: the readout is twelve
    label/value pairs and the value column has to line up, or scanning down the numbers
    is impossible. `tabular-nums` on the values is the other half of that -- it is what
    stops "136" and "0" from being different widths and jogging the column.
*/
.mc-query {
    padding: var(--space-2) var(--space-3);
    min-width: 200px;
    max-width: 248px;
}

.mc-query[hidden] { display: none; }

/*
    The minimap (2026-10-13, chunks 2-3): under the status card, above the Query readout.

    The canvas's backing store is one pixel per tile (480x480) and is scaled down here, with
    `pixelated` so a road stays a crisp line rather than a blur. ~160 px on desktop; on the
    compact layout it is ~120 px -- and hidden by default there, which is `script.js`'s
    choice (the dock's Minimap entry shows it). The `[hidden]` rule matters: `display: block`
    on the class would otherwise beat the attribute.

    Three canvases are stacked in the frame: the raster (chunk 2), the selected field overlay
    (chunk 4) and the camera footprint (chunk 3). Each of the two upper ones changes on a
    cadence of its own -- the camera's every time it moves, a field whenever the simulation
    republishes it -- and the raster must not be repainted to follow either.

    This panel sets no size of its own: the map's size is `--minimap-size` on the frame below,
    so by default the panel shrink-wraps its map. The HUD overhaul of 2026-10-19 moved the map
    into `.mc-workspace`, which now owns its size at the bottom of this sheet; the old
    "match the status card's width" variant went with that layout.

    `position: relative` is for the overlay selector, which floats over the bottom-right of the
    map rather than sitting under it as a row of its own; see `.mc-minimap-overlays`.
*/
.mc-minimap {
    position: relative;
    padding: var(--space-1);
    line-height: 0;
}

.mc-minimap[hidden] { display: none; }

/*
    The map square. One token drives both axes, so the map is square by construction rather
    than by two numbers agreeing. **200px is the desktop default since 2026-10-20**, raised from
    160px once the map stopped sharing the status card's width: details were hard to read, and
    nothing constrains the token to the card any more. `.mc-workspace` and the 10-foot layout
    override it; the compact layout has its own value at the end of the sheet.

    `calc()` and custom properties rather than `aspect-ratio`: this sheet keeps to properties
    every target's webview has, the same reason it carries no `:has()`.
*/
.mc-minimap-frame {
    --minimap-size: 200px;
    position: relative;
    width: var(--minimap-size);
    height: var(--minimap-size);
}

.mc-minimap-canvas {
    display: block;
    width: 100%;
    height: 100%;
    image-rendering: pixelated;
    border-radius: 2px;
}

/*
    The selected field overlay -- traffic today, the other fields when chunk 6 lands. Same
    backing size as the raster and the same *pixelated* scaling, which is what keeps its tint on
    the same tile boundaries the raster drew: a field is per-tile data, not a line, so it wants
    the blocks (compare `.mc-minimap-viewport` below, which wants the smooth scale for exactly
    the opposite reason).

    `pointer-events: none`: the frame owns the press that recentres the camera, and a tint
    covering the whole map would otherwise swallow every one of them. Between the raster and the
    footprint in the markup, so the order is map, tint, camera -- the camera rectangle is the one
    thing that must stay findable over any tint.
*/
.mc-minimap-field {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    image-rendering: pixelated;
    pointer-events: none;
}

/*
    The camera's footprint, over the raster. Sized and drawn by `minimapWidget.js` in sync
    with the map's own backing store, so one pixel here is one tile there. `pointer-events:
    none` twice over: the click that recentres the camera is handled on the frame (which knows
    its own box, and the canvas does not stroke a hit region), and this must not become one.

    No `image-rendering: pixelated` -- the same scaling as the raster underneath, and a
    hairline that is upscaled *nearest* reads as a wobbly 3 px band, where the smooth
    downscale reads as a line.
*/
.mc-minimap-viewport {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

/* The free-area boundary: the in-world line's amber (`BOUNDS_STYLE.color`, render/bounds.js). */
.mc-minimap-bounds {
    position: absolute;
    box-sizing: border-box;
    border: 1px solid #c6842c;
    pointer-events: none;
}

/* Clicking the map recentres the camera, so the frame is a control: `pointer` is the
   convention for that (`ui/budget.js` does the same for its slider run). */
.mc-minimap-frame { cursor: pointer; }

.mc-minimap-bounds[hidden] { display: none; }

/*
    ---- the overlay selector ----------------------------------------------

    Which field the map is tinted with. **Floated over the map rather than set below it as a
    row of the panel**: a row in the panel's flow would make the panel taller, and the map
    already sits in the bottom-left workspace where a taller panel eats the room above the
    dock. Over the map, the panel's height is untouched.

    It is a sibling of the frame in the markup, not a child: the frame is the map's click
    target, so a control inside it would need to stop every press from bubbling out to the
    recentre. Anchored to the panel's bottom-right padding, which is why the offsets are
    `--space-1`.

    **Round icon buttons, not a label that fills the map** (user, 2026-10-14). The earlier
    strip was `flex: 1 1 0` across the whole width, so one field read as a caption and four
    fields would have been ~29 px of "Pollution" on a phone map. A fixed circle does
    not divide the map by its entry count, so chunk 6's three more fields land in the same
    cluster without a layout decision. Bottom-right, packed leftward, so the middle of the
    map -- the part a player actually reads -- stays clear. `justify-content: flex-end` is
    what makes a one-button cluster sit in that corner rather than at the left edge.

    **28 px since 2026-10-20** (was 22 px, and 24 px on the compact layout): the user found
    the four circles too small to hit and read, and the map grew to 200 px in the same pass,
    so the cluster had room. The glyph scales with the circle.
*/
.mc-minimap-overlays {
    position: absolute;
    right: var(--space-1);
    bottom: var(--space-1);
    display: flex;
    justify-content: flex-end;
    gap: 6px;
    line-height: 0;
}

.mc-minimap-overlay {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    /* Dark rather than a panel colour: this sits over the map, and an icon has to stay
       readable over water, over a tinted field and over pale road at once. */
    background: rgba(0, 0, 0, 0.55);
    color: var(--text-2);
    cursor: pointer;
    transition: background var(--motion-fast) var(--ease-out),
                color var(--motion-fast) var(--ease-out);
}

.mc-minimap-overlay svg { width: 16px; height: 16px; }

/* The 10-foot layout scales its other controls, so the chips follow the 280px map. */
.mc-overlay.is-tv .mc-minimap-overlays { gap: 8px; }
.mc-overlay.is-tv .mc-minimap-overlay { width: 36px; height: 36px; }
.mc-overlay.is-tv .mc-minimap-overlay svg { width: 20px; height: 20px; }

.mc-minimap-overlay:hover { background: rgba(0, 0, 0, 0.75); color: var(--text-1); }

/*
    Pressed is the **data blue**, not the accent gold, which is the same call the dock's panel
    entries make (`.mc-tool.is-on`): this is which *view* is showing, and gold on this screen
    means the armed tool or the running speed. **It is a solid fill with a dark glyph and an
    outer ring, not the old translucent `--data-dim`:** over the map that tint nearly vanished,
    which is exactly what the user reported on 2026-10-20. The ring separates the blue circle
    from pale road and bright water without adding layout.
*/
.mc-minimap-overlay[aria-pressed="true"] {
    background: var(--data);
    color: var(--text-on-data);
    box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.55);
}

/*
    ---- sizes ------------------------------------------------------------

    The map used to be sized in this group: 160px by default, 120px on a compact window, and a
    height-gated "same width as the status card" variant on tall desktops. **The HUD overhaul of
    2026-10-19 moved the map into `.mc-workspace` at the bottom-left**, so the card alignment no
    longer describes the layout and the live sizes are the rules in that section (plus the
    compact rule near the end of the sheet). They are kept there rather than here so a future
    layout change edits one place. Nothing below changes the map's geometry.
*/

/* A toggle entry in the dock's Panels group, while its widget is showing. The data blue, not
   the accent: gold means the armed tool, and the minimap being on is not that. */
.mc-tool.is-on {
    border-color: var(--data);
    background: var(--data-dim);
    color: var(--text-1);
}

.mc-tool.is-on svg { opacity: 1; }

.mc-query-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2);
    margin-bottom: var(--space-2);
}

.mc-query-title {
    font-size: var(--text-small);
    letter-spacing: 0.02em;
    color: var(--text-2);
}

.mc-query-where { font-size: var(--text-micro); }

.mc-query-list {
    display: grid;
    grid-template-columns: auto auto;
    justify-content: space-between;
    column-gap: var(--space-3);
    row-gap: 1px;
    margin: 0;
}

.mc-query-key {
    font-size: var(--text-micro);
    color: var(--text-2);
}

.mc-query-value {
    margin: 0;
    font-size: var(--text-micro);
    color: var(--text-1);
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.mc-status-funds {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    /* The card is a fixed width, so a wide treasury and a wide net cannot be allowed to
       spill out of it: the net drops to a second line instead. Rare, and better than the
       card resizing. */
    flex-wrap: wrap;
}

/*
    The city's name, at the top of the status card (2026-10-07).

    It is the one line on this card whose *length* the player controls, so it truncates
    with an ellipsis rather than wrapping or pushing the card wider: the card's width is
    set by the treasury and the demand meter, and a 32-character name -- the stored
    maximum -- is wider than either. `text-overflow` needs `white-space: nowrap` and a
    `width` it can overflow, which the card's own sizing supplies.
*/
.mc-city-name {
    font-size: var(--text-head);
    font-weight: 650;
    letter-spacing: -0.01em;
    color: var(--text-1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/*
    The treasury is the one hero figure on the play screen: a player reads it more
    often than anything else, and it is the number a build decision turns on.
*/
.mc-funds-value {
    font-size: var(--text-hero);
    font-weight: 650;
    letter-spacing: -0.02em;
    line-height: 1;
}

/* A negative treasury is the one state that earns colour on this screen, because it
   is the only one that changes what the player may do. */
.mc-funds-value.is-negative { color: var(--danger); }

/*
    The net of the last budget, beside the treasury -- see `fundsNet` (2026-10-16).

    It is **always drawn**, because the month-delta it replaced came and went and dragged
    the card's width with it; and it is set in `tabular-nums`, because that is what makes
    a figure going from `$9,999` to `$10,000` widen by one character rather than by a smear
    of digits. `nowrap` keeps the `/yr` on the number it belongs to.
*/
.mc-funds-net {
    font-size: var(--text-small);
    font-weight: 600;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
}

.mc-funds-net.is-up { color: var(--ok); }
.mc-funds-net.is-down { color: var(--danger); }

/* Date and population are peers, deliberately the same size: not a headline and a
   footnote. */
.mc-facts { display: flex; gap: var(--space-4); }

.mc-fact {
    display: flex;
    flex-direction: column;
    gap: 1px;
}

.mc-fact-value {
    font-size: var(--text-lead);
    font-weight: 600;
}

/* ---- demand meter ---------------------------------------------------- */

.mc-demand {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-2) var(--space-2) var(--space-3);
}

.mc-demand-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

/* The shared "at rail" note. Warn-coloured because saturation is the thing a player
   is meant to notice, and *not* the bar colour, which carries the sign. */
.mc-demand-rail-note { color: var(--warn); }

.mc-demand-row {
    display: grid;
    grid-template-columns: 1.4em 1fr 3.6em;
    align-items: center;
    gap: var(--space-2);
}

.mc-demand-key {
    font-size: var(--text-small);
    font-weight: 700;
    letter-spacing: 0.02em;
}

.mc-demand-row[data-kind="residential"] .mc-demand-key { color: var(--rci-residential); }
.mc-demand-row[data-kind="commercial"] .mc-demand-key { color: var(--rci-commercial); }
.mc-demand-row[data-kind="industrial"] .mc-demand-key { color: var(--rci-industrial); }

.mc-demand-track {
    position: relative;
    height: 10px;
    border-radius: 3px;
    background: rgba(0, 0, 0, 0.42);
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.4);
    overflow: hidden;
}

/*
    The centre line. `::before` is fine here because the fill is a child *element*,
    not a pseudo-element, so paint order puts the line under the fill and the line
    stays visible only where the bar is empty.
*/
.mc-demand-track::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 0;
    bottom: 0;
    width: 1px;
    background: rgba(255, 255, 255, 0.4);
}

.mc-demand-fill {
    position: absolute;
    top: 1px;
    bottom: 1px;
    border-radius: 2px;
}

/*
    The fill's `color` is set alongside its `background` on purpose: the railed state
    draws its cap with `outline: currentColor`, and `currentColor` on the fill would
    otherwise resolve to the *inherited* text colour rather than to the bar's own --
    which is what the mockup's rule does, and it is a bug there rather than a choice.
    With both set, the cap always matches the bar it caps.
*/
.mc-demand-row[data-kind="residential"] .mc-demand-fill { background: var(--rci-residential); color: var(--rci-residential); }
.mc-demand-row[data-kind="commercial"] .mc-demand-fill { background: var(--rci-commercial); color: var(--rci-commercial); }
.mc-demand-row[data-kind="industrial"] .mc-demand-fill { background: var(--rci-industrial); color: var(--rci-industrial); }

/*
    Saturation. Only the **free end** is squared, so the bar keeps its direction, and
    which end that is depends on the sign -- which is why there are two rules and not
    one. The mockup wrote a single rule squaring the inline-end, which is correct only
    for a positive bar while every value in its own example was negative.
*/
.mc-demand-fill.is-railed { outline: 2px solid currentColor; outline-offset: -2px; }
.mc-demand-fill.is-railed.is-negative { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.mc-demand-fill.is-railed.is-positive { border-top-right-radius: 0; border-bottom-right-radius: 0; }

/* The end-of-scale tick. Positioned by script, which is the only way it can sit on
   the free end of a bar whose geometry depends on the value. */
.mc-demand-rail {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    background: var(--text-1);
    opacity: 0.85;
}

.mc-demand-value {
    font-size: var(--text-small);
    font-variant-numeric: tabular-nums;
    color: var(--text-2);
    text-align: right;
}

/* ---- speed control --------------------------------------------------- */

.mc-speed {
    display: flex;
    gap: var(--space-1);
    padding: var(--space-1);
    background: rgba(0, 0, 0, 0.3);
    border-radius: var(--radius-control);
}

.mc-speed-btn {
    flex: 1;
    min-width: 34px;
    height: 28px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--text-2);
    cursor: pointer;
    font: inherit;
    transition: background var(--motion-fast) var(--ease-out),
                color var(--motion-fast) var(--ease-out);
}

.mc-speed-btn:hover { background: rgba(255, 255, 255, 0.08); color: var(--text-1); }

/*
    Selected is a *fill*, not an outline. "The armed tool" and "the running speed" are
    both "which one is on", and they are the only two places the accent fill appears on
    this screen -- which is what keeps "gold = this is active" trustworthy.
*/
.mc-speed-btn.is-selected {
    background: var(--accent);
    color: var(--text-on-accent);
    box-shadow: 0 0 12px var(--accent-glow);
}

.mc-speed-btn svg { display: block; }

/*
    The compact status strip: **the demand meter and the speed control**, declared a second
    time so that the layouts which cannot hold them in the status card still get them.
    Shown only by the compact query below, and exactly one of each pair is ever visible --
    the same mechanism the speed control has always used.

    `flex-wrap: wrap` is the load-bearing property and it is why this placement is cheap at
    the size that is short of room: at 844x390 the two members share one line and the strip
    costs **no** vertical height, while at 390x844 it wraps to two lines and two rows are
    affordable on a tall screen. One query, both cases, no nested breakpoint.
*/
.mc-status-strip {
    display: none;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-3);
    padding: var(--space-1) var(--space-2);
}

/*
    The compact demand meter: one row of three, with neither the heading nor the shared
    "At rail" note, because at 390 px there is no room for either.

    What carries the rail state instead is the same **squared cap and tick** the full meter
    draws — shape, not colour — plus the value turning amber. The shape is the primary cue
    deliberately: an amber number on its own would be a colour-only encoding.
*/
.mc-demand-compact {
    flex-direction: row;
    align-items: center;
    gap: var(--space-3);
    padding: 0;
}

.mc-demand-compact .mc-demand-row {
    /* Key, a short bar, the value -- fixed columns, so the three groups read as a column of
       comparable bars rather than three differently-sized widgets. */
    grid-template-columns: auto 30px 3.4em;
    gap: var(--space-1);
}

.mc-demand-compact .mc-demand-key { font-size: var(--text-micro); }
.mc-demand-compact .mc-demand-track { height: 8px; }
.mc-demand-compact .mc-demand-value { font-size: var(--text-micro); }

/* The rail, stated per value rather than once for the group, in the note's own colour. */
.mc-demand-value.is-railed { color: var(--warn); }

/* ---- the dock -------------------------------------------------------- */

.mc-dock {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-2);
    /*
        `min-width: 0` guards a flex trap distinct from the one that caused the
        reported overhang: a flex item's automatic minimum size is its min-content
        width, so a dock whose tool row does not fit refuses to shrink below that row
        whatever `width` says. The overhang that was actually reported came from
        `box-sizing` (fixed in the token layer); this is the second trap, waiting for
        the first group with eight tools in it.
    */
    min-width: 0;
    max-width: 100%;
}

/*
    The head row: the group tabs, or -- folded -- what is armed. The toggle sits at its
    right end in *both* states, so the control that changes the dock's height never
    moves and never has to be hunted for.

    `min-height` is what makes that toggle a real target instead of a 22 px chevron
    beside a 22 px tab row, and the head is the tallest thing in the folded dock --
    which is why the folded bar is deliberately not shorter than one comfortable touch
    target.
*/
.mc-dock-head {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    min-width: 0;
    min-height: var(--control-h);
}

.mc-dock-groups {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    gap: var(--space-1);
    padding: 0 var(--space-1);
}

/*
    The fold.

    Folded, the dock shows what is *armed* rather than which group is being browsed.
    That is the reason the summary exists at all: the armed tool is the one piece of
    dock state a player must never lose, so it is what survives being folded.

    `display: none` rather than an animated height, deliberately: the tool row is a
    scroll container, and animating the height of one is the classic way to make a
    phone fling-scroll the wrong thing.
*/
.mc-dock-summary {
    display: none;
    flex: 1 1 auto;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
    padding: 0 var(--space-2);
    color: var(--text-1);
    font-size: var(--text-small);
    font-weight: 600;
}

.mc-dock-summary svg { flex: none; color: var(--accent); }

.mc-dock-summary-group {
    color: var(--text-3);
    font-size: var(--text-micro);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.mc-dock.is-collapsed .mc-dock-groups { display: none; }
.mc-dock.is-collapsed .mc-dock-summary { display: flex; }
.mc-dock.is-collapsed .mc-dock-tools { display: none; }

/* A real button, so mouse, touch, keyboard focus order and the gamepad focus model
   all reach it by the same route as every other control. The chevron points the way
   the dock will move: down to fold away, up to unfold. */
.mc-dock-toggle {
    flex: none;
    width: var(--control-h);
    height: var(--control-h);
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    border-radius: var(--radius-control);
    background: transparent;
    color: var(--text-3);
    cursor: pointer;
    transition: background var(--motion-fast) var(--ease-out),
                color var(--motion-fast) var(--ease-out);
}

.mc-dock-toggle:hover { background: rgba(255, 255, 255, 0.10); color: var(--text-1); }
.mc-dock-toggle svg { display: block; transition: transform var(--motion-panel) var(--ease-out); }
.mc-dock.is-collapsed .mc-dock-toggle svg { transform: rotate(180deg); }

.mc-dock-group {
    border: 0;
    background: transparent;
    color: var(--text-3);
    font: inherit;
    font-size: var(--text-small);
    font-weight: 600;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    padding: var(--space-1) var(--space-2);
    border-radius: 6px;
    cursor: pointer;
    transition: color var(--motion-fast) var(--ease-out);
}

.mc-dock-group:hover { color: var(--text-1); }

/* The active group is underlined rather than filled, so it cannot be confused with
   the armed *tool*, which does get the fill. Two levels of selection need two
   treatments. */
.mc-dock-group.is-active {
    color: var(--text-1);
    box-shadow: inset 0 -2px 0 var(--accent);
}

.mc-dock-tools {
    display: flex;
    gap: var(--space-1);
    min-width: 0;
}

/* A tool hidden by availability. This is deliberately a class rather than the
   `hidden` property: the dock's tab switch already owns `hidden`, and sharing it
   made every available tool appear in every tab. `.mc-tool`'s own `display: flex`
   would beat the browser's built-in `[hidden]`, so the rule is stated here too. */
.mc-tool.is-unavailable {
    display: none;
}

.mc-tool {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    min-width: 62px;
    height: 56px;
    padding: var(--space-1);
    border: 1px solid transparent;
    border-radius: var(--radius-control);
    background: rgba(255, 255, 255, 0.05);
    color: var(--text-2);
    font: inherit;
    font-size: var(--text-micro);
    font-weight: 600;
    letter-spacing: 0.01em;
    cursor: pointer;
    transition: background var(--motion-fast) var(--ease-out),
                color var(--motion-fast) var(--ease-out),
                transform var(--motion-fast) var(--ease-out);
}

/*
    `hidden` does nothing on an element whose own rule sets `display: flex`.

    **The same defect `.mc-tool[hidden]` below exists for, found a second time on
    2026-10-07.** The New City popup removes the river's direction control when there is no
    river -- this project's "nothing is ever drawn disabled" rule -- but the field is
    `display: flex`, so the `hidden` attribute was overridden by the UA stylesheet and the
    control stayed on screen. The browser probe caught it, and only a browser could:
    `hidden` is a DOM property, and nothing without a box model can measure its effect.

    `.mc-hint` is here for the same reason and from the same day -- the status card's
    city-name label is a hint, and it is hidden when a snapshot carries no name.

    Two class selectors rather than a blanket `[hidden] { display: none }`, because that
    would silently break any component that deliberately combines `display` with the
    attribute.
*/
.mc-hint[hidden],
.mc-field[hidden] { display: none; }

/* A tool hidden by a tab switch must not be measurable by the scroll cue, and
   `display: none` is what makes `scrollWidth` mean "what is on screen". */
.mc-tool[hidden] { display: none; }

.mc-tool:hover {
    background: rgba(255, 255, 255, 0.11);
    color: var(--text-1);
}

.mc-tool:active { transform: translateY(1px); }
.mc-tool svg { opacity: 0.9; }

/*
    The armed tool. This is the single most important state on the play screen --
    `TODO.md` records "nothing shows which tool is armed on the map itself" as open,
    and a player who cannot tell will build the wrong thing and blame the game. So it
    gets the accent fill, a glow, and a lift.
*/
.mc-tool.is-armed {
    background: var(--accent);
    border-color: var(--accent-hover);
    color: var(--text-on-accent);
    box-shadow: 0 0 16px var(--accent-glow), var(--elev-1);
}

.mc-tool.is-armed svg { opacity: 1; }

/* Cost is shown on the tool itself, because it is a fact about the tool. It is read
   off the definition, never written here -- see `palette.js` `entryCost`. */
.mc-tool-cost {
    font-size: var(--text-micro);
    opacity: 0.75;
    font-variant-numeric: tabular-nums;
}

/* ---- feedback pill --------------------------------------------------- */

.mc-toast {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-small);
    color: var(--text-1);
    max-width: 100%;
}

/* An empty pill is a box with a dot in it, which reads as a bug. */
.mc-toast[data-empty="true"] { display: none; }

.mc-toast-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex: none;
    background: var(--ok);
}

.mc-toast.is-refused .mc-toast-dot { background: var(--danger); }

/* ---- milestone banner ------------------------------------------------ */
/*
   A permanent unlock is a positive state change, so the banner is green (`--ok`)
   rather than the gold reserved for the armed tool and primary actions, or the
   red the Advisors panel uses for problems. It sits in the bottom-left stack
   above the dock and stays until dismissed: unlike the feedback pill, it is not
   a command verdict and must not be cleared by the next one.
*/
.mc-milestone {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    padding: var(--space-3);
    max-width: 100%;
    background: var(--ok-dim);
    border: 1px solid var(--ok);
    border-left-width: 3px;
    border-radius: var(--radius-control);
    box-shadow: var(--elev-1);
    overflow-wrap: anywhere;
}

.mc-milestone[hidden] { display: none; }

.mc-milestone-icon {
    flex: none;
    margin-top: 1px;
    color: var(--ok);
}

.mc-milestone-copy {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-width: 0;
}

.mc-milestone-title {
    font-size: var(--text-body);
    font-weight: 650;
    color: var(--text-1);
}

.mc-milestone-detail {
    font-size: var(--text-small);
    line-height: 1.45;
    color: var(--text-2);
}

.mc-milestone-close {
    flex: none;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--text-2);
    cursor: pointer;
}

.mc-milestone-close:hover {
    background: var(--surface-3);
    color: var(--text-1);
}

/* ---- alerts affordance ----------------------------------------------- */

.mc-icon-btn {
    position: relative;
    width: var(--control-h);
    height: var(--control-h);
    display: grid;
    place-items: center;
    border: 1px solid var(--surface-edge);
    border-radius: var(--radius-control);
    background: var(--surface-1);
    backdrop-filter: blur(var(--blur));
    -webkit-backdrop-filter: blur(var(--blur));
    color: var(--text-1);
    cursor: pointer;
    box-shadow: var(--elev-1);
}

.mc-icon-btn:hover { background: var(--surface-3); }

/* The live-disaster button: red, because it is the one control in this corner that means
   "something is wrong right now" (`--danger`, the same token the advisor badge uses). It is
   hidden until a disaster is in progress, and the `[hidden]` rule is restated because
   `.mc-icon-btn`'s `display: grid` would otherwise beat the browser's built-in
   `[hidden] { display: none }` and leave it drawn. */
.mc-disaster-btn {
    background: var(--danger);
    border-color: var(--danger);
    color: #fff;
    /*
        And it pulses, so a city on fire somewhere the player is not looking is not missed. A
        fade of the whole button, not a colour flash: the button stays the same red and its
        brightness breathes, which reads at the edge of vision without becoming a strobe. The
        cycle is quick (0.9s) and shallow (down to 55% opacity) on purpose -- enough to catch
        the eye, not enough to be irritating while a long fire burns.
    */
    animation: mc-disaster-pulse 0.9s ease-in-out infinite;
}
.mc-disaster-btn:hover { background: #f0806e; }
.mc-disaster-btn[hidden] { display: none; }

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

/* The pulse is decoration, so it stops for anyone who has asked for less motion -- the
   button is still there, still red, just steady. */
@media (prefers-reduced-motion: reduce) {
    .mc-disaster-btn { animation: none; }
}

/* An unread count, not a red dot with hidden meaning: advisors are how a
   city-builder explains itself, so the badge promises something countable. It is
   drawn only when a real count is supplied -- see `playHud.js`. */
.mc-badge {
    position: absolute;
    top: -5px;
    right: -5px;
    min-width: 17px;
    height: 17px;
    padding: 0 4px;
    border-radius: 999px;
    background: var(--danger);
    color: #fff;
    font-size: var(--text-micro);
    font-weight: 700;
    display: grid;
    place-items: center;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
}

/* ---- camera and pinned tools ---------------------------------------- */

.mc-camera,
.mc-pinned {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1);
}

/*
    The pinned panel is a **2x2 grid** made of two button columns -- Bulldozer over Pan on
    the left, Screenshot over Query on the right (the user's layout decision of 2026-10-11;
    see `dock.js`'s `PINNED_COLUMNS`).

    It is written as a row of two flex columns rather than as `display: grid` with
    `grid-auto-flow: column`, because the *column* is the unit the decision was made in:
    the left pair and the right pair are each a group whose internal order matters, and a
    single grid would place them by counting buttons, so a package shipping a second query
    tool would silently push Pan into the third cell. Here an extra tool lengthens its own
    column and nothing else moves.

    `align-items: flex-start` overrides the shared rule's `center`, so the two columns hang
    from a common top edge. Centred, a two-button column beside a one-button column would sit
    half a button lower and the grid would stop reading as rows.
*/
.mc-pinned { flex-direction: row; align-items: flex-start; }
.mc-pinned-col { display: flex; flex-direction: column; gap: var(--space-1); }
.mc-camera { flex-direction: column; }

/* Rotate over zoom: two rows of two, because the pairing is the meaning and
   `flex-wrap` would re-pair them at a narrower width. */
.mc-camera-row { display: flex; gap: var(--space-1); }

.mc-cam-btn {
    width: 32px;
    height: 30px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.07);
    color: var(--text-2);
    cursor: pointer;
    font: inherit;
    transition: background var(--motion-fast) var(--ease-out),
                color var(--motion-fast) var(--ease-out);
}

.mc-cam-btn:hover { background: rgba(255, 255, 255, 0.14); color: var(--text-1); }

/*
    An armed pinned tool wears the same accent fill as an armed dock tool, because it
    is the same state. Without this, arming Pan -- the default tool -- would show no
    state anywhere on the screen, which is the exact defect the armed state exists to
    fix.
*/
.mc-cam-btn.is-armed {
    background: var(--accent);
    color: var(--text-on-accent);
    box-shadow: 0 0 12px var(--accent-glow);
}

/* ---- compact: phones in portrait AND any short viewport -------------- */

@media (max-width: 700px), (max-height: 520px) {
    .mc-overlay {
        padding: var(--space-2);
        gap: var(--space-2);
    }

    /* One line: funds, then the secondary facts. The funds figure is the one number a
       player cannot do without, so it stays; the date is kept because it is a short
       string; population is the first thing to go when the row runs out. */
    .mc-status {
        flex-direction: row;
        align-items: center;
        /* The desktop card is a fixed width; here it sizes to the row, which `max-width:
           100%` then clamps to the viewport. */
        width: auto;
        min-width: 0;
        max-width: 100%;
        gap: var(--space-3);
        padding: var(--space-2) var(--space-3);
        overflow: hidden;
    }

    .mc-status-funds { flex: none; flex-direction: column; align-items: flex-start; gap: 0; }
    .mc-funds-value { font-size: var(--text-head); }
    /* The net moves under the treasury rather than beside it: at this width a third
       column has no room, and a wrapped figure is unreadable. */
    .mc-funds-net { font-size: var(--text-micro); }

    /* See the note at the top of this block: demand is not shown on a phone. */
    .mc-status .mc-demand,
    .mc-status .mc-speed { display: none; }

    .mc-facts { gap: var(--space-3); min-width: 0; }
    .mc-fact { min-width: 0; }
    .mc-fact-value { font-size: var(--text-body); }
    .mc-fact .mc-label { font-size: var(--text-micro); }

    /* Stacked, so the dock can be edge to edge and the controls sit under it rather
       than competing with it for a 390 px row. */
    .mc-bottom {
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-2);
    }

    .mc-bottom-left { width: 100%; }
    .mc-bottom-right { justify-content: flex-end; }
    .mc-dock { width: 100%; }

    /*
        The camera cluster leaves the thumb's way on a phone. Rotate and pinch-zoom are
        already two-finger gestures on touch, so the cluster is the one part of this
        layout that is both redundant and large.

        **This is the `max-width` half of the query doing real work, not the
        `max-height` half** -- and that is a measured distinction rather than a
        stylistic one. On a 390x844 phone the viewport is 844 px *tall*, so the
        short-viewport rule further down never fires there and only this rule hides the
        cluster. Leaving it out is not cosmetic: it puts four camera buttons on a phone.
        Measured on the real app at all four design viewports, which is how the omission
        was found.

        **The pinned panel is deliberately NOT in this rule.** Pan is the default tool
        and the thing that makes a one-finger drag pan the map, so hiding it alongside
        the cluster would leave a player who armed a build tool unable to get back to
        panning -- which is the whole reason the user had it pinned outside the dock.
    */
    .mc-camera { display: none; }

    /* The compact status strip, where the thumb already is. It gets its own panel because a
       bare control row floating over the map reads as an artifact. */
    .mc-status-strip {
        display: flex;
        padding: var(--space-1) var(--space-2);
    }

    .mc-dock { padding: var(--space-1); }

    .mc-dock-groups {
        overflow-x: auto;
        scrollbar-width: none;
    }
    .mc-dock-groups::-webkit-scrollbar { display: none; }

    .mc-dock-tools {
        overflow-x: auto;
        overflow-y: hidden;
        scrollbar-width: none;
        -webkit-overflow-scrolling: touch;
        /*
            No trailing padding of its own, deliberately: the row's inset comes from
            the panel's padding, so the first and last tool sit the same distance
            inside the panel. That symmetry is the property the reported overhang was
            missing, and adding inset here too would only move the asymmetry to the
            other end.
        */
        padding-right: 0;
    }

    /*
        The scroll cue, applied by script only while the row actually overflows. Both
        static states are wrong: a fade over a row that fits promises tools that are
        not there, and a fade left on permanently puts the last tool behind it -- which
        is half of why the previous pass looked as if the toolbar had been trimmed with
        a knife rather than scrolled to.

        Only the right edge fades, because that is the only side that ever has more
        content in this layout. **Whether it should fade only the side that still has
        content once scrolled is OPEN** -- as written, the fade stays on at the end of
        the scroll and hides the last tool at the exact moment the player reached it.
        The implementation plan records it; this slice does not settle it.
    */
    .mc-dock-tools.is-scrollable {
        mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 18px), transparent 100%);
        -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - 18px), transparent 100%);
    }
    .mc-dock-tools::-webkit-scrollbar { display: none; }

    .mc-tool {
        min-width: 56px;
        height: 56px;
        flex: none;
    }

    .mc-toast { font-size: var(--text-micro); padding: var(--space-1) var(--space-2); }
}

/*
    There is no separate block for the *play HUD's* short-viewport case, deliberately.

    A landscape phone is 844 px wide, so a width-only test misses it entirely -- which is
    how the design pass's first attempt gave it the desktop layout with a full-height
    status column eating a third of a 390 px-tall screen. "Width alone is the wrong
    question" is the finding, and the answer is already in the query above: it tests
    `max-height: 520px` as well as `max-width: 700px`, so every short viewport gets the
    compact treatment and a second block would only be a subset of it.

    ## The title screen is the one exception, and it is the opposite correction

    The compact query stacks `.mc-title-row`, which is right for a *narrow* screen and
    wrong for a wide-but-short one. At 844x390 -- and with the template's `#adUnit` taking
    ~90 px off the bottom -- the title block has about **300 px** to live in, and the
    stacked row is 242 px of it against 154 px horizontal. Bottom-anchored, that overflow
    goes off the **top**, so the wordmark was cut off: measured at 844x390, the wordmark's
    top sat at **-78 px** with its eyebrow entirely off-screen.

    **This was found by looking, and it is not new** -- it was confirmed pre-existing by
    hiding the account button at runtime and re-measuring, which changed nothing. Restoring
    the horizontal row for this one shape saves 88 px and fits the whole block with room to
    spare, and it loses nothing: at 844 px wide there is width to spend and no height to
    spend.
*/

@media (min-width: 701px) and (max-height: 520px) {
    .mc-title-row {
        flex-direction: row;
        align-items: flex-end;
        gap: var(--space-4);
    }

    /* Back into its own column beside the menu rather than below it. */
    .mc-title-side {
        align-items: flex-end;
        order: 0;
    }

    .mc-hints { justify-content: flex-end; }
    .mc-build { text-align: right; }
}

/* ---- 10-foot / TV: same arrangement, larger scale --------------------- */

@media (min-width: 1600px) {
    /* The card widens at 10 feet, so the map does too -- one number, both panels. */
    :root { --status-card-width: 290px; }
    .mc-overlay { padding: var(--space-5); }
    .mc-tool { min-width: 78px; height: 74px; }
    .mc-speed-btn { min-width: 44px; height: 36px; }
    .mc-icon-btn { width: 46px; height: 46px; }
    .mc-cam-btn { width: 40px; height: 38px; }
    .mc-dock-group { padding: var(--space-2) var(--space-3); }
}

/* ---- expanded: desktop, where the full standings panel applies -------- */

@media (min-width: 701px) and (min-height: 521px) {
    .mc-demand { display: flex; }
}

/* ======================================================================
   Panels: the modal shell, and the budget dialog that is its first tenant

   Lifted from `design/ui.css`'s Button, Modal, Field/slider and Ledger-table
   sections, which the design pass wrote to be lifted "as-is". The components
   are unchanged; what is new is the plumbing, and it is four things:

   1. **`.mc-panel-holder` is the layer.** The mockup's modal was a sibling of
      the scrim inside one `.screen`. Here it mounts into the play HUD's
      overlay, which does two useful things: it inherits that overlay's pointer
      rule (`.mc-overlay > *` sets `pointer-events: all`, and `pointer-events`
      inherits, so nothing below has to opt back in), and `position: absolute`
      takes the holder **out of the grid's flow**. A second *in-flow* grid child
      is exactly the bug that made the old `#playArea` placeholder render as
      text across the map, so this is load-bearing rather than tidiness.

   2. **The enter is a fade, and the exit is not animated.** `--motion-panel`
      already goes to `0ms` under `prefers-reduced-motion`, so the fade honours
      that for free. Opacity only, with no transform, because the modal's
      transform is already spent on centring it -- and the compact breakpoint
      uses a *different* transform for the bottom sheet, so an animated
      transform would have to be written twice and kept in step. Panel
      enter/exit motion is OPEN in `TODO.md`; this is the smallest thing that is
      not a specification decision.

   3. **`.mc-tax-note` and `.mc-empty` were inline styles in the mockup.** They
      are rules here so a second panel can use them.

   4. **The compact override is the mockup's own** -- a phone gets a sheet off
      the bottom, "where the hand already is".
   ==================================================================== */

.mc-panel-holder {
    position: absolute;
    inset: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* ---- Buttons (lifted) ------------------------------------------------- */

.mc-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    height: var(--control-h);
    padding: 0 var(--space-4);
    border: 1px solid var(--surface-edge-strong);
    border-radius: var(--radius-control);
    background: rgba(255, 255, 255, 0.06);
    color: var(--text-1);
    font: inherit;
    font-size: var(--text-body);
    font-weight: 600;
    cursor: pointer;
    transition: background var(--motion-fast) var(--ease-out),
                border-color var(--motion-fast) var(--ease-out),
                transform var(--motion-fast) var(--ease-out);
}

.mc-btn:hover { background: rgba(255, 255, 255, 0.12); border-color: var(--surface-edge-strong); }
.mc-btn:active { transform: translateY(1px); }

.mc-btn.is-primary {
    background: var(--accent);
    border-color: var(--accent-hover);
    color: var(--text-on-accent);
}

.mc-btn.is-primary:hover { background: var(--accent-hover); }

.mc-btn.is-ghost {
    background: transparent;
    border-color: transparent;
    color: var(--text-2);
}

.mc-btn.is-ghost:hover { background: rgba(255, 255, 255, 0.08); color: var(--text-1); }

/*
 * The destructive button, and it is `--danger` for the same reason the advisor badge and the
 * live-disaster button are: that token means "something is wrong or irreversible". It ships
 * with the city list's delete confirmation, which is the one place the game removes something
 * the player made and cannot put it back.
 */
.mc-btn.is-danger {
    background: var(--danger);
    border-color: var(--danger);
    color: var(--text-on-accent);
}

.mc-btn.is-danger:hover { background: var(--danger-dim); border-color: var(--danger); color: var(--text-1); }

/* The delete confirmation's sentence, inside the city list's modal body. */
.mc-city-confirm-text {
    margin: 0 0 var(--space-3);
    color: var(--text-1);
}

/* ---- Modal (lifted) --------------------------------------------------- */

.mc-scrim {
    position: absolute;
    inset: 0;
    background: rgba(4, 6, 9, 0.55);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
}

.mc-modal {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(560px, calc(100% - var(--space-6)));
    max-height: calc(100% - var(--space-6));
    display: flex;
    flex-direction: column;

    background: var(--surface-2);
    border: 1px solid var(--surface-edge-strong);
    border-radius: var(--radius-panel);
    box-shadow: var(--elev-3), inset 0 1px 0 var(--surface-edge);

    /*
        The same line `.mc-panel` carries, and it was missing here for the same reason the
        wordmark was black: nothing in this file sets a `color` on `body`, so any text that
        does not set one of its own inherits the browser default **black**, which is
        invisible on these dark surfaces. The HUD cards are fine because `.mc-panel` states
        it; the dialog is `.mc-modal` and did not, so `.mc-modal-title` -- and the table,
        labels and empty-state text beneath it -- read as black-on-black. Stated once here
        rather than on each child, because the dialog is one dark surface.

        This is the second symptom of the missing `body { color: var(--text-1) }` that
        `design/tokens.css` carries and this file deliberately does not (`html, body` here
        owns the shell); the global fix is recorded OPEN in `TODO.md`, and this stays scoped
        until that decision is taken.
    */
    color: var(--text-1);
    backdrop-filter: blur(20px) saturate(150%);
    -webkit-backdrop-filter: blur(20px) saturate(150%);
    overflow: hidden;
}

.mc-modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-4) var(--space-3);
}

.mc-modal-title {
    margin: 0;
    font-size: var(--text-head);
    font-weight: 650;
    letter-spacing: -0.01em;
}

.mc-modal-sub {
    margin: 2px 0 0;
    font-size: var(--text-small);
    color: var(--text-2);
}

.mc-modal-body {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: 0 var(--space-4) var(--space-4);
    overflow-y: auto;
}

.mc-modal-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--surface-edge);
    background: rgba(0, 0, 0, 0.22);
}

.mc-modal-actions {
    display: flex;
    gap: var(--space-2);
    margin-left: auto;
}

/* The enter. `both` keeps the last keyframe, so the dialog is not painted at
   its pre-animation opacity after the animation ends. */
@keyframes mc-panel-fade { from { opacity: 0; } to { opacity: 1; } }

.mc-panel-holder.is-open .mc-scrim,
.mc-panel-holder.is-open .mc-modal {
    animation: mc-panel-fade var(--motion-panel) var(--ease-out) both;
}

/* ---- Field: slider + value (lifted) ----------------------------------- */

.mc-field {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3);
    background: var(--surface-3);
    border-radius: var(--radius-control);
}

.mc-field-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
}

.mc-field-value {
    font-size: var(--text-head);
    font-weight: 650;
    font-variant-numeric: tabular-nums;
}

/*
 * The slider is drawn from the tokens rather than left native, because the native
 * control cannot be styled consistently across the three input modes and this one
 * has to accept gamepad left/right as well as a drag.
 */
.mc-slider {
    position: relative;
    height: 22px;
    display: flex;
    align-items: center;
}

.mc-slider-track {
    position: absolute;
    left: 0;
    right: 0;
    height: 6px;
    border-radius: 3px;
    background: rgba(0, 0, 0, 0.45);
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.4);
    overflow: hidden;
}

.mc-slider-fill {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    background: var(--accent);
}

.mc-slider-knob {
    position: absolute;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #fff;
    border: 3px solid var(--accent);
    box-shadow: var(--elev-1);
    transform: translateX(-50%);
}

/* The same focus ring the keyboard uses, shown on a control the gamepad owns. */
.mc-slider-knob.is-focused {
    box-shadow: var(--focus-ring), var(--elev-1);
}

.mc-slider-ticks {
    display: flex;
    justify-content: space-between;
    font-size: var(--text-micro);
    color: var(--text-3);
    font-variant-numeric: tabular-nums;
}

/*
 * The consequence line under the slider, and it is not a `.mc-hint`: a hint names a
 * key, and this is a sentence about what the simulation is doing. It reads as body
 * copy at the small size, in `--text-2`, so it is legible without competing with the
 * rate itself.
 */
.mc-tax-note {
    margin: 0;
    font-size: var(--text-small);
    color: var(--text-2);
}

/* ---- Ordinance switches ----------------------------------------------- */

/*
 * A list of rows, each one a policy and the switch that puts it in force.
 *
 * The row is a card rather than a table row because the two halves are different kinds
 * of thing: the left half is prose about the city (what the ordinance does) and the
 * right half is the control and its price. `align-items: center` is what keeps the
 * switch level with the name rather than with the note, which matters at three lines
 * of note where a baseline alignment would leave the switch floating.
 */
.mc-switch-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.mc-switch-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    padding: var(--space-3);
    background: var(--surface-3);
    border: 1px solid var(--surface-edge);
    border-radius: var(--radius-control);
}

.mc-switch-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    /* Without this the note's longest word sets a floor on the row's width and the
       switch is pushed out of the dialog on a narrow screen. */
    min-width: 0;
}

.mc-switch-name {
    font-size: var(--text-body);
    font-weight: 600;
    color: var(--text-1);
}

/*
 * The effect sentence. Body copy at the small size in `--text-2`, like the budget
 * panel's tax note and for the same reason: it is a statement about what the simulation
 * is doing, not a hint naming a key, so it must stay legible without competing with the
 * ordinance's own name above it.
 */
.mc-switch-note {
    font-size: var(--text-small);
    color: var(--text-2);
    line-height: 1.45;
}

.mc-switch-side {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex: none;
}

.mc-switch-cost {
    font-size: var(--text-small);
    color: var(--text-3);
    white-space: nowrap;
}

/*
 * The track, and the button *is* the track.
 *
 * Drawn rather than native for the third time in this file (the panel's close button,
 * the budget slider, this): a checkbox styled across the three input modes ends up with
 * three different sizes and no way to put the accent on it. The knob is a span inside,
 * which is what lets one rule move it.
 *
 * Off is the slider's own open track -- the same dark inset -- so a switch and a slider
 * read as the same family of control rather than as two things that happen to be near
 * each other.
 */
.mc-switch {
    position: relative;
    flex: none;
    width: 46px;
    height: 26px;
    padding: 0;
    border-radius: 13px;
    border: 1px solid var(--surface-edge-strong);
    background: rgba(0, 0, 0, 0.45);
    box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.4);
    cursor: pointer;
    transition: background var(--motion-fast) var(--ease-out),
                border-color var(--motion-fast) var(--ease-out);
}

.mc-switch-knob {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #fff;
    box-shadow: var(--elev-1);
    transition: transform var(--motion-fast) var(--ease-out);
}

/*
 * On takes the accent, which is the **same claim the armed tool makes**: the player did
 * something and the city is now in that state. It is deliberately not a new colour and
 * not a second accent -- every data figure on this screen stays on the informational
 * tokens, so gold means "you switched this on" and nothing else.
 */
.mc-switch.is-on {
    background: var(--accent);
    border-color: var(--accent);
}

.mc-switch.is-on .mc-switch-knob {
    transform: translateX(20px);
}

/* ---- Ledger table (lifted) -------------------------------------------- */

.mc-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-body);
}

.mc-table th {
    text-align: left;
    font-size: var(--text-micro);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-3);
    padding: 0 var(--space-3) var(--space-2);
}

/*
 * The header row is the loudest thing under a section caption, and both are
 * small uppercase text -- so the caption needs more air below it than a normal
 * block would, or the two stack into one crowded heading. That is exactly how
 * the first modal shot rendered.
 */
.mc-table th:first-child { padding-left: var(--space-3); }

.mc-table th.is-num,
.mc-table td.is-num { text-align: right; }

.mc-table td {
    padding: var(--space-2) var(--space-3);
    border-top: 1px solid var(--surface-edge);
    color: var(--text-1);
}

.mc-table tbody tr:first-child td { border-top: 1px solid var(--surface-edge-strong); }

.mc-table td.is-num {
    font-variant-numeric: tabular-nums;
}

.mc-table .is-income { color: var(--ok); }
.mc-table .is-expense { color: var(--text-2); }

.mc-table tfoot td {
    border-top: 1px solid var(--surface-edge-strong);
    font-weight: 650;
    padding-top: var(--space-3);
}

.mc-table-caption {
    font-size: var(--text-small);
    color: var(--text-2);
    padding: 0 var(--space-3) var(--space-2);
}

/* A panel with nothing to show says so, instead of an empty table. */
.mc-empty {
    padding: var(--space-3);
    font-size: var(--text-small);
    color: var(--text-3);
    background: var(--surface-3);
    border-radius: var(--radius-control);
}

/* ---- Advisor cards (2026-10-13) --------------------------------------- */
/*
   Added with the Advisors panel, which is the first tenant of the shell that
   *reads* rather than writes: six departments of label/value rows, and above
   them the advisories.

   The grid is `auto-fill` rather than a column count because the dialog is one
   fixed width but the card set is not -- a package that adds a seventh
   department should reflow, not overflow. `minmax(200px, 1fr)` gives two
   columns inside this dialog's 560px, which is the most a card of label/value
   rows can carry before the labels start wrapping.

   The notices are `--danger`, like the badge, and for the same reason: the
   palette reserves the semantic colours for actual state, and an advisory is
   the only thing on this panel that is a statement about the city rather than a
   reading of it. A missing stadium is coloured as the one "this is wrong"
   colour the palette has; inventing a severity scale would be inventing the
   thresholds this panel deliberately does not have. Operational figures
   (cut-off and congested tiles) are card rows, not notices; a large blackout
   is the one exception -- see `src/ui/advisors.js`.
*/

.mc-advisor-notices {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin-bottom: var(--space-4);
}

/* The empty state is the same box as a notice would be, so the panel does not
   change height when the last advisory clears. */
.mc-advisor-notices .mc-empty {
    margin: 0;
}

.mc-advisor-notice {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-3);
    background: var(--danger-dim);
    border-left: 3px solid var(--danger);
    border-radius: var(--radius-control);
}

/* Who is speaking. A collective voice, not a character: the label is the same
   micro-caps treatment the card titles use, so it reads as an attribution line
   rather than as a quotation from a named person. */
.mc-advisor-notice-speaker {
    font-size: var(--text-micro);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-3);
}

.mc-advisor-notice-title {
    font-size: var(--text-body);
    font-weight: 650;
    color: var(--text-1);
}

.mc-advisor-notice-detail {
    font-size: var(--text-small);
    line-height: 1.45;
    color: var(--text-2);
}

.mc-advisor-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: var(--space-3);
}

.mc-advisor-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    /* A grid track will not shrink below its content without this, and a long
       department name is enough to push one card wider than its column. */
    min-width: 0;
    padding: var(--space-3);
    background: var(--surface-3);
    border: 1px solid var(--surface-edge);
    border-radius: var(--radius-control);
}

.mc-advisor-card-title {
    margin: 0;
    font-size: var(--text-micro);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-3);
}

/* The department's name and role, under the department label. Presentation
   only -- the rows below are still the simulation's own figures. */
.mc-advisor-name {
    margin: 0;
    font-size: var(--text-small);
    line-height: 1.3;
    color: var(--text-2);
}

/* Label left, figure right, one row per reading. The two-column grid is what
   makes the figures line up down the card without a table. */
.mc-advisor-rows {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--space-1) var(--space-3);
    margin: 0;
}

.mc-advisor-key {
    margin: 0;
    min-width: 0;
    font-size: var(--text-small);
    color: var(--text-2);
}

.mc-advisor-value {
    margin: 0;
    font-size: var(--text-small);
    color: var(--text-1);
    font-variant-numeric: tabular-nums;
    text-align: right;
}

/* Why a card of dashes is a card of dashes -- see the module header. */
.mc-advisor-pending {
    margin: 0;
    font-size: var(--text-micro);
    color: var(--text-3);
}

/* ---- Neighbor links and deals (2026-10-20) --------------------------- */
/*
   The connection prompt reuses the modal shell; the panel uses the same
   card vocabulary as Advisors. Gold is reserved for primary actions, so the
   deal buttons are ordinary buttons and the active one is named "Cancel deal"
   rather than painted as a state.
*/
.mc-neighbor-offer-lead {
    margin: 0 0 var(--space-3);
    font-size: var(--text-body);
    line-height: 1.45;
    color: var(--text-1);
}

.mc-neighbor-offer-note {
    margin: 0;
    font-size: var(--text-small);
    line-height: 1.45;
    color: var(--text-2);
}

.mc-neighbor-grid {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.mc-neighbor-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    min-width: 0;
    padding: var(--space-3);
    background: var(--surface-3);
    border: 1px solid var(--surface-edge);
    border-radius: var(--radius-control);
}

.mc-neighbor-title {
    margin: 0;
    font-size: var(--text-body);
    font-weight: 650;
    color: var(--text-1);
}

.mc-neighbor-subtitle {
    margin: 0;
    font-size: var(--text-micro);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-3);
}

.mc-neighbor-rows {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--space-1) var(--space-3);
    margin: 0;
}

.mc-neighbor-rows dt {
    font-size: var(--text-small);
    color: var(--text-2);
}

.mc-neighbor-rows dd {
    margin: 0;
    font-size: var(--text-small);
    color: var(--text-1);
    text-align: right;
}

.mc-neighbor-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
}

/* ---- Segmented control, text input, seed row (2026-10-07) ------------- */
/*
   Added with the New City popup, which is the first dialog to need any of the
   three. They are generic rather than popup-specific -- a segmented row is how
   this design does "one of a few", and the settings panel will want it -- but
   nothing else uses them yet, so they are sized for these fields.

   **Nothing here is drawn disabled**, per the project's standing rule: a choice
   that cannot be made is not drawn (the river's direction is hidden when there is
   no river), so there is deliberately no `:disabled` styling to get wrong.
*/

.mc-seg {
    display: flex;
    gap: var(--space-1);
    flex-wrap: nowrap;
}

/* The coast row is four words rather than four short ids, so it may wrap on a phone. */
.mc-seg-wrap { flex-wrap: wrap; }

.mc-seg-btn {
    flex: 1 1 auto;
    min-width: 0;
    height: var(--control-h);
    padding: 0 var(--space-3);
    border: 1px solid var(--surface-edge-strong);
    border-radius: var(--radius-control);
    background: rgba(255, 255, 255, 0.05);
    color: var(--text-2);
    font: inherit;
    font-size: var(--text-body);
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: background var(--motion-fast) var(--ease-out),
                color var(--motion-fast) var(--ease-out),
                border-color var(--motion-fast) var(--ease-out);
}

.mc-seg-btn:hover { background: rgba(255, 255, 255, 0.1); color: var(--text-1); }

/*
   Selected is the accent, exactly as the armed tool and the primary button are.
   The class and `aria-pressed` are set together in `newCity.js`, so the drawn state
   and the announced one cannot disagree.
*/
.mc-seg-btn.is-selected {
    background: var(--accent);
    border-color: var(--accent-hover);
    color: var(--text-on-accent);
}

.mc-input {
    width: 100%;
    height: var(--control-h);
    padding: 0 var(--space-3);
    border: 1px solid var(--surface-edge-strong);
    border-radius: var(--radius-control);
    background: rgba(0, 0, 0, 0.35);
    color: var(--text-1);
    font: inherit;
    font-size: var(--text-body);
}

/* The same ring every other focusable control gets; see `.focusable`. */
.mc-input:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
    border-color: var(--accent);
}

/* ---- Engine settings panel (2026-10-15) -------------------------------- */
/*
   Two groups -- graphics and sound. Each group is a stack, so the rows keep the
   switch-row card look the ordinances and disasters panels already use; the
   heading is the only element this panel adds.
*/
.mc-settings-group {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.mc-settings-group + .mc-settings-group {
    margin-top: var(--space-4);
}

.mc-settings-group-title {
    margin: 0 0 var(--space-1);
    font-size: var(--text-micro);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-3);
}

/*
   The volume sliders, and they are **the platform's own range input**.
   `.mc-slider` above is a hand-drawn control because the tax slider needs a tick
   row and gamepad left/right through the panel's key routing; a volume slider
   wants neither, so native is the honest answer rather than a second copy of a
   control that would only drift from the first -- see `ui/settings.js`. The
   focus ring is `.focusable:focus-visible`, like every other control.
*/
.mc-range {
    width: 100%;
    height: var(--control-h);
    margin: 0;
    accent-color: var(--accent);
    cursor: pointer;
}

/* ---- Scrollbars (2026-10-07) ------------------------------------------- */
/*
   **The New City popup was the app's first scrolling surface**, so it was the
   first place a platform scrollbar appeared -- and the user's report was that
   it "just stands out". It did: on a dark translucent sheet, a light grey slab
   is the only element on screen the design does not own.

   **Both spellings, because the two engines do not accept each other's.**
   `scrollbar-width` / `scrollbar-color` are the standard properties and they
   are all Firefox reads; every Chromium and WebKit build still needs the
   `::-webkit-scrollbar` pseudo-elements, which ignore the standard ones
   entirely. Styling one and not the other is the classic way to get this wrong
   -- it works in the browser the author tested and is ignored in the other, so
   it is written as a pair on purpose.

   **The colours are the palette's, not a grey**: the track is the panel's own
   shadow (`--scroll-track`) and the thumb is low-alpha white, so the bar reads
   as part of the glass rather than pasted over it. The thumb is a pill to match
   the controls' radius language, inset 2px so a rounded thumb is not clipped by
   the gutter, and it brightens **when the pointer is anywhere on the panel**
   rather than only when it is exactly on the thumb -- a target you have to find
   is the behaviour this is replacing.

   `.mc-modal-body` is the modal shell's scroll container, so this lands on every
   dialog in the app (the budget panel, and every panel after it) rather than on
   this one popup. It is the only scroller that shows a bar: the dock's own
   compact scrollers hide theirs, which is why they are not listed here.
*/

.mc-modal-body {
    scrollbar-width: thin;
    scrollbar-color: var(--scroll-thumb) var(--scroll-track);
}

.mc-modal-body::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

.mc-modal-body::-webkit-scrollbar-track {
    background: var(--scroll-track);
    border-radius: var(--radius-pill);
}

.mc-modal-body::-webkit-scrollbar-thumb {
    background: var(--scroll-thumb);
    border: 2px solid transparent;
    border-radius: var(--radius-pill);
    background-clip: padding-box;
}

.mc-modal-body:hover::-webkit-scrollbar-thumb {
    background: var(--scroll-thumb-hover);
    background-clip: padding-box;
}

.mc-modal-body::-webkit-scrollbar-corner {
    background: transparent;
}

/* ---- TV: the target grows with the type ------------------------------- */

@media (min-width: 1600px) {
    .mc-slider-knob { width: 24px; height: 24px; }
    .mc-slider-track { height: 8px; }
    /*
        The switch grows with the type for the same reason the slider does: at TV
        distance a 26px track is a target the player has to aim at rather than press.
        The knob's travel is recomputed with it (52px of inner width, less the 24px
        knob and a 2px inset either side) -- a transform left at the base value would
        stop the knob short of the end it is claiming to be at.
    */
    .mc-switch { width: 54px; height: 30px; border-radius: 15px; }
    .mc-switch-knob { width: 24px; height: 24px; }
    .mc-switch.is-on .mc-switch-knob { transform: translateX(24px); }
}

/* ---- Compact: a sheet from the bottom --------------------------------- */

@media (max-width: 700px), (max-height: 520px) {
    .mc-modal {
        top: auto;
        bottom: 0;
        left: 0;
        transform: none;
        width: 100%;
        max-height: 88%;
        /* A sheet rising from the bottom is where the hand already is. */
        border-radius: var(--radius-panel) var(--radius-panel) 0 0;
    }
}

/*
 * Wide enough for the whole row, or it stacks -- and this is a **width** question, which
 * is why it is not in the compact block above.
 *
 * That block fires on `max-height: 520px` as well as on a narrow width, and the two
 * conditions want opposite things from this row. A short viewport is a landscape phone:
 * 844 or 900 px wide, with the effect sentence fitting on one line beside the switch
 * with room to spare, and stacking there makes every row a third taller on the screen
 * with the least height to give. A narrow viewport is a portrait phone, where there is
 * genuinely nowhere for the sentence to go.
 *
 * The threshold is where the sentence stops fitting. The longest note is about 60
 * characters, so side by side a row needs ~340px of text column plus ~140px of cost and
 * switch, and a row is inset from the viewport by roughly 70px of padding and dialog
 * inset. Below this the note either wraps to four or five lines beside the switch or
 * pushes the switch past the edge; stacked, it gets the full width and the price and
 * switch share the line below it, right-aligned against the row's own edge.
 */
@media (max-width: 600px) {
    .mc-switch-row {
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-2);
    }

    .mc-switch-side { justify-content: space-between; }
}

.mc-overlay {
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
    --text-micro: 10px;
    --text-small: 12px;
    --text-body: 13px;
    --text-lead: 15px;
    --text-head: 19px;
    --hud-control: 44px;
    --hud-inset: 12px;
    --hud-inspector: 350px;
    --hud-surface: rgba(17, 21, 27, .95);
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto;
    padding: var(--hud-inset);
    gap: 10px;
    min-height: 0;
    container-type: size;
    container-name: play;
    font-size: var(--text-body);
    letter-spacing: 0;
}

.mc-overlay.is-tv {
    --hud-control: 58px;
    --hud-inset: 28px;
    --hud-inspector: 440px;
    --text-micro: 14px;
    --text-small: 17px;
    --text-body: 20px;
    --text-lead: 22px;
    --text-head: 26px;
}

.mc-topbar {
    grid-row: 1;
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 4px 8px;
    border-radius: 8px;
    min-width: 0;
    background: var(--hud-surface);
}

.mc-topbar .mc-status {
    display: flex;
    flex-direction: row;
    align-items: center;
    flex: 1;
    min-width: 0;
    width: auto;
    max-width: none;
    gap: 20px;
    padding: 0;
    border: 0;
    box-shadow: none;
    background: transparent;
    backdrop-filter: none;
    overflow: visible;
}

.mc-topbar .mc-status .mc-demand,
.mc-topbar .mc-status .mc-speed { display: none; }
.mc-topbar .mc-city-name { max-width: 150px; font-size: var(--text-body); flex: 0 1 auto; }
.mc-topbar .mc-status-funds { flex-direction: column; align-items: flex-start; flex: none; gap: 0; }
.mc-topbar .mc-status-funds .mc-hint { display: none; }
.mc-topbar .mc-funds-value { font-size: var(--text-head); white-space: nowrap; }
.mc-topbar .mc-funds-net { font-size: var(--text-micro); }
.mc-topbar .mc-facts { gap: 20px; min-width: 0; }
.mc-topbar .mc-fact-value { font-size: var(--text-body); white-space: nowrap; }
.mc-topbar .mc-label { font-size: var(--text-micro); }
.mc-top-demand { display: flex; flex-direction: column; gap: 2px; flex: none; padding: 0; }
.mc-top-demand .mc-demand-row { grid-template-columns: 12px 30px 3.4em; gap: 4px; }
.mc-top-right { gap: 2px; flex: none; }
.mc-overlay .mc-icon-btn { width: var(--hud-control); height: var(--hud-control); flex: none; }
.mc-overlay svg { flex: none; }
.mc-overlay.is-tv svg { width: 28px; height: 28px; }
.mc-overlay .adlGamepadSelected,
.mc-overlay .gamepadHighlighted { outline: 3px solid var(--accent); outline-offset: -3px; }

/*
    The same highlight for a modal mounted outside the play overlay.

    Settings, Your Cities, New city and the import notice live in `#titleScreen`,
    whose only previous gamepad rule was the title menu item's box-shadow. Without
    this the controls were selectable -- once they were registered at all -- with no
    visible ring. The play overlay's own rule above still covers the HUD controls.
*/
.mc-modal .adlGamepadSelected,
.mc-modal .gamepadHighlighted { outline: 3px solid var(--accent); outline-offset: -3px; }

/*
    Accent-filled controls need a light ring.

    A switch that is on, a selected segment, a primary button and an armed dock tool
    are already filled with `--accent`; an accent outline on top of that is invisible,
    which is the reported "I can't tell the switch is selected" defect. White with a
    little offset reads on both the dark off state and the gold on state. These come
    after the two generic rules so they win on equal specificity.
*/
.mc-switch.adlGamepadSelected,
.mc-switch.gamepadHighlighted,
.mc-seg-btn.adlGamepadSelected,
.mc-seg-btn.gamepadHighlighted,
.mc-btn.is-primary.adlGamepadSelected,
.mc-btn.is-primary.gamepadHighlighted,
.mc-tool.is-armed.adlGamepadSelected,
.mc-tool.is-armed.gamepadHighlighted,
.mc-cam-btn.is-armed.adlGamepadSelected,
.mc-cam-btn.is-armed.gamepadHighlighted,
.mc-tv-scale-setting input.adlGamepadSelected,
.mc-tv-scale-setting input.gamepadHighlighted,
.mc-modal input[type="range"].adlGamepadSelected,
.mc-modal input[type="range"].gamepadHighlighted,
.mc-slider.adlGamepadSelected,
.mc-slider.gamepadHighlighted {
    outline: 3px solid var(--text-1);
    outline-offset: 3px;
}

/* The title screen's corner account button is outside the menu's own rule. */
.mc-account-btn.adlGamepadSelected,
.mc-account-btn.gamepadHighlighted {
    outline: 3px solid var(--accent);
    outline-offset: 2px;
}

.mc-workspace {
    grid-column: 1;
    grid-row: 2;
    min-width: 0;
    min-height: 0;
    height: 100%;
    align-self: stretch;
    display: flex;
    align-items: flex-end;
    pointer-events: none;
    container-type: size;
    container-name: workspace;
}
/*
    The map's live size: **200px on desktop since 2026-10-20** (was 168px, and 160px before the
    2026-10-19 HUD overhaul moved it here). The panel carries 4px of padding and a 1px border
    per side around the square, so its width is the map plus 10. Raised "across the board" at
    the user's request because the map's one-pixel-per-tile details were hard to read.
*/
.mc-workspace .mc-minimap { width: 210px; pointer-events: auto; background: var(--hud-surface); border-radius: 8px; }
.mc-overlay.is-tv .mc-minimap { width: 290px; }
.mc-overlay.is-tv .mc-minimap-frame { --minimap-size: 280px; }
@container workspace (max-height: 240px) {
    .mc-minimap { display: none; }
}

.mc-overlay .mc-bottom {
    grid-column: 1;
    grid-row: 3;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    min-width: 0;
    border: 1px solid var(--surface-edge);
    border-radius: 8px;
    background: var(--hud-surface);
    color: var(--text-1);
    box-shadow: var(--elev-1), inset 0 1px 0 var(--surface-edge);
    backdrop-filter: blur(var(--blur)) saturate(140%);
    -webkit-backdrop-filter: blur(var(--blur)) saturate(140%);
    pointer-events: auto;
}
.mc-overlay .mc-bottom-left { position: relative; width: 100%; min-width: 0; }
.mc-overlay .mc-toast { position: absolute; bottom: calc(100% + 8px); max-width: 100%; overflow-wrap: anywhere; z-index: 2; background: var(--hud-surface); }
.mc-overlay .mc-dock {
    width: 100%;
    max-height: max(52px, calc(100cqh - 190px));
    overflow-y: auto;
    overscroll-behavior: contain;
    border-radius: 0;
    padding: 6px;
    background: transparent;
    border: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
.mc-overlay .mc-dock.is-collapsed,
.mc-overlay.has-detail .mc-dock { display: none; }
.mc-overlay .mc-dock-toggle { display: none; }
.mc-overlay .mc-dock-groups { min-width: 0; overflow-x: auto; scrollbar-width: thin; }
.mc-overlay .mc-dock-group { min-height: var(--hud-control); padding: 0 12px; font-size: var(--text-body); flex: none; letter-spacing: 0; }
.mc-overlay .mc-dock-tools {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 5px;
    overflow: visible;
    mask-image: none;
    -webkit-mask-image: none;
    padding: 6px 0 0;
}
.mc-overlay .mc-tool { min-width: 0; width: 100%; height: auto; min-height: 70px; padding: 6px; border-radius: 5px; overflow-wrap: anywhere; }
.mc-overlay .mc-tool > span { white-space: normal; font-size: var(--text-small); }
.mc-overlay .mc-tool[hidden],
.mc-overlay .mc-tool.is-unavailable { display: none; }
.mc-utility { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 4px; pointer-events: none; min-width: 0; }
.mc-overlay:not(.has-detail) .mc-bottom:has(.mc-dock:not(.is-collapsed)) .mc-utility { border-top: 1px solid var(--surface-edge); }
.mc-utility > * { pointer-events: auto; }
.mc-selection { display: flex; align-items: center; gap: 4px; padding: 0; border-radius: 0; min-width: 0; }
.mc-overlay .mc-selection,
.mc-overlay .mc-status-strip,
.mc-overlay .mc-camera {
    background: transparent;
    border: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
.mc-overlay .mc-icon-btn,
.mc-overlay .mc-cam-btn,
.mc-overlay .mc-speed-btn,
.mc-overlay .mc-tool {
    background: transparent;
    border-color: transparent;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
.mc-overlay :is(.mc-icon-btn, .mc-cam-btn, .mc-speed-btn, .mc-tool, .mc-dock-group):is(:hover, :active, .is-armed, .is-selected, .is-on, [aria-expanded="true"]) {
    background: transparent;
    box-shadow: none;
}
.mc-overlay .mc-cam-btn.is-armed,
.mc-overlay .mc-speed-btn.is-selected { color: var(--accent); border-bottom: 2px solid var(--accent); }
.mc-overlay .mc-tool.is-armed { color: var(--accent); border-color: var(--accent); }
.mc-overlay .mc-tool.is-on { color: var(--accent); border-color: var(--accent); }
.mc-overlay .mc-toolbox-toggle[aria-expanded="true"] { color: var(--accent); }
.mc-selected-label { display: flex; flex-wrap: wrap; align-items: center; gap: 0 10px; padding-right: 8px; min-width: 0; overflow-wrap: anywhere; }
.mc-selected-label strong { font-size: var(--text-body); }
.mc-selected-cost { font-size: var(--text-small); color: var(--text-2); }
.mc-overlay .mc-mobile-rotate { display: none; }
.mc-selection .mc-pinned { padding: 0; background: none; border: 0; box-shadow: none; }
.mc-selection .mc-pinned-col { flex-direction: row; }
.mc-selection .mc-cam-btn { width: var(--hud-control); height: var(--hud-control); }
.mc-overlay .mc-status-strip { display: flex; flex-wrap: nowrap; padding: 0; border-radius: 0; flex: none; }
.mc-status-strip .mc-speed { background: none; padding: 0; gap: 0; }
.mc-status-strip .mc-speed-btn { width: var(--hud-control); min-width: var(--hud-control); height: var(--hud-control); flex: none; }
.mc-overlay .mc-camera { display: flex; flex-direction: row; padding: 0; margin-left: auto; border-radius: 0; }
.mc-overlay .mc-camera-row { display: flex; }
.mc-overlay .mc-camera .mc-cam-btn { width: var(--hud-control); height: var(--hud-control); }

.mc-overlay > .mc-detail-holder {
    grid-column: 1;
    grid-row: 2;
    position: relative;
    inset: auto;
    min-width: 0;
    min-height: 0;
    height: 100%;
    align-self: stretch;
    align-items: stretch;
    justify-content: flex-end;
    pointer-events: none;
}
.mc-detail-holder .mc-scrim { display: none; }
.mc-detail-holder .mc-modal {
    position: relative;
    inset: auto;
    transform: none;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto;
    width: min(var(--hud-inspector), 100%);
    max-width: 100%;
    height: 100%;
    max-height: 100%;
    min-height: 0;
    border-radius: 8px;
    background: var(--hud-surface);
    pointer-events: auto;
}
.mc-detail-holder .mc-modal-head { min-width: 0; padding: 8px 12px; }
.mc-detail-holder .mc-modal-head > div { min-width: 0; overflow-wrap: anywhere; }
.mc-detail-holder .mc-modal-title { font-size: var(--text-head); letter-spacing: 0; }
.mc-detail-holder .mc-modal-sub:empty { display: none; }
.mc-detail-holder .mc-modal-body { min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 12px 16px; scrollbar-gutter: stable; overflow-wrap: anywhere; }
.mc-detail-holder .mc-modal-foot { padding: 8px 12px; flex-wrap: wrap; gap: 8px; }
.mc-detail-holder .mc-modal-foot:empty { display: none; }
.mc-detail-holder .mc-hints { flex-wrap: wrap; }
.mc-detail-holder .mc-query { width: 100%; min-width: 0; max-width: none; padding: 0; border: 0; box-shadow: none; background: none; backdrop-filter: none; }
.mc-detail-holder .mc-query-title { display: none; }
.mc-detail-holder .mc-query-list { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.mc-detail-holder .mc-query-value { overflow-wrap: anywhere; }
.mc-detail-holder .mc-btn { min-height: var(--hud-control); height: auto; white-space: normal; }
.mc-detail-holder .mc-switch-row { flex-direction: column; align-items: stretch; gap: 8px; }
.mc-detail-holder .mc-switch-side { justify-content: space-between; }
.mc-tv-scale-setting { display: flex; justify-content: space-between; align-items: center; min-height: 44px; gap: 12px; }
.mc-tv-scale-setting input { width: 22px; height: 22px; accent-color: var(--accent); }

@media (min-width: 701px) and (min-height: 541px) and (pointer: fine) {
    .mc-overlay:not(.is-tv) .mc-dock { padding: 4px; }
    .mc-overlay:not(.is-tv) .mc-dock-group { min-height: 32px; padding: 0 10px; font-size: 12px; }
    .mc-overlay:not(.is-tv) .mc-dock-tools { grid-template-columns: repeat(auto-fill, minmax(96px, 112px)); padding-top: 4px; }
    .mc-overlay:not(.is-tv) .mc-tool {
        display: grid;
        grid-template-columns: 20px minmax(0, 1fr);
        grid-template-rows: auto auto;
        align-content: center;
        justify-items: start;
        column-gap: 6px;
        row-gap: 0;
        min-height: 44px;
        padding: 4px;
        text-align: left;
    }
    .mc-overlay:not(.is-tv) .mc-tool svg { grid-column: 1; grid-row: 1 / -1; width: 18px; height: 18px; }
    .mc-overlay:not(.is-tv) .mc-tool > span { grid-column: 2; font-size: 11px; }
    .mc-overlay:not(.is-tv) .mc-tool .mc-tool-cost { font-size: 10px; }
    .mc-overlay:not(.is-tv) .mc-tool[hidden],
    .mc-overlay:not(.is-tv) .mc-tool.is-unavailable { display: none; }
}

@media (max-width: 1050px) {
    .mc-topbar .mc-city-name { display: none; }
    .mc-topbar .mc-facts { gap: 12px; }
    .mc-overlay .mc-camera { display: none; }
}

@media (max-width: 1050px), (max-height: 540px) {
    .mc-overlay .mc-mobile-rotate { display: grid; margin-left: auto; }
}

@media (max-width: 700px) {
    .mc-overlay { --hud-inset: 8px; gap: 8px; }
    .mc-topbar { gap: 6px; padding: 4px 6px; }
    .mc-topbar .mc-status { flex-direction: column; align-items: flex-start; gap: 2px; }
    .mc-topbar .mc-facts { gap: 8px; flex-wrap: wrap; }
    .mc-topbar .mc-label { display: none; }
    .mc-topbar .mc-fact-value { font-size: var(--text-small); }
    .mc-topbar .mc-funds-value { font-size: var(--text-lead); }
    .mc-topbar .mc-funds-net { font-size: 9px; }
    .mc-top-demand .mc-demand-row { grid-template-columns: 10px 24px; }
    .mc-top-demand .mc-demand-value { position: absolute; width: 1px; height: 1px; clip-path: inset(50%); overflow: hidden; }
    .mc-top-right [aria-label="Budget"] { display: none; }
    .mc-workspace .mc-minimap { width: 150px; }
    .mc-workspace .mc-minimap-frame { --minimap-size: 140px; }
    .mc-minimap-overlay { width: 24px; height: 24px; }
    .mc-minimap-overlay svg { width: 14px; height: 14px; }
    .mc-overlay.has-detail .mc-minimap { display: none; }
    .mc-detail-holder .mc-modal { width: 100%; height: min(100%, 460px); align-self: flex-end; }
    .mc-overlay .mc-dock-tools { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .mc-overlay .mc-dock-group { font-size: var(--text-small); padding: 0 10px; }
    .mc-utility { display: grid; grid-template-columns: auto auto minmax(0, 1fr); gap: 4px; }
    .mc-selection { display: contents; }
    .mc-selection .mc-pinned { grid-column: 1; grid-row: 2; }
    .mc-toolbox-toggle { grid-column: 2; grid-row: 2; }
    .mc-overlay .mc-status-strip { grid-column: 3; grid-row: 2; justify-self: end; }
    .mc-selected-label { grid-column: 1 / -1; grid-row: 1; max-width: none; min-height: 18px; padding: 0 4px; align-items: center; gap: 8px; }
    .mc-selected-label strong { font-size: var(--text-small); }
    .mc-selected-cost { font-size: var(--text-micro); }
    .mc-selection { gap: 0; }
    .mc-overlay .mc-icon-btn { width: var(--hud-control); }
}

@media (max-width: 360px) {
    .mc-topbar { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 8px; }
    .mc-topbar .mc-status { grid-column: 1; grid-row: 1; flex-direction: row; flex-wrap: wrap; gap: 4px 12px; }
    .mc-top-demand { grid-column: 2; grid-row: 1; }
    .mc-top-right { grid-column: 1 / -1; grid-row: 2; justify-content: flex-end; }
    .mc-utility { grid-template-columns: auto minmax(0, 1fr); }
    .mc-overlay .mc-status-strip { grid-column: 1 / -1; grid-row: 3; }
    .mc-selected-label { max-width: none; }
    .mc-overlay .mc-dock { max-height: max(52px, calc(100cqh - 300px)); }
}

@media (max-height: 540px) {
    .mc-overlay { --hud-inset: 6px; gap: 6px; }
    .mc-topbar .mc-city-name { display: none; }
    .mc-topbar .mc-funds-net { display: none; }
    .mc-topbar .mc-status { gap: 12px; }
    .mc-topbar .mc-label { display: none; }
    .mc-overlay .mc-camera, .mc-workspace .mc-minimap { display: none; }
    .mc-overlay .mc-dock-tools { grid-template-columns: repeat(6, minmax(0, 1fr)); }
    .mc-detail-holder .mc-modal-head { padding: 2px 8px; }
    .mc-detail-holder .mc-modal { height: 100%; }
    .mc-detail-holder .mc-modal-foot .mc-hints { display: none; }
}

@media (min-width: 701px) and (max-height: 540px) {
    .mc-workspace .mc-minimap:not([hidden]) {
        --short-minimap-size: clamp(0px, calc(100cqh - 10px), 200px);
        display: block;
        width: calc(var(--short-minimap-size) + 10px);
        max-height: 100%;
    }
    .mc-workspace .mc-minimap-frame { --minimap-size: var(--short-minimap-size); }
    .mc-minimap-overlays { max-width: calc(100% - 8px); flex-wrap: wrap; gap: 4px; }
    .mc-minimap-overlay { width: 24px; height: 24px; }
    .mc-minimap-overlay svg { width: 14px; height: 14px; }

    @container workspace (max-height: 64px) {
        .mc-workspace .mc-minimap:not([hidden]) { display: none; }
    }
}
