/* ============================================================================
 * OSADA — mobile browser experience.
 *
 * Loaded AFTER osada-theme.css so it can override desktop geometry by cascade
 * order rather than by an ever-growing pile of !important. Where !important
 * does appear it is for exactly one reason: the element carries an INLINE
 * style written by Kotlin (legacy layout code, makeVisible/makeHidden), and an
 * inline style always beats a stylesheet rule regardless of specificity.
 *
 * The primary control surface is the set of semantic body classes published by
 * MobileLayoutController:
 *
 *   osada-layout-desktop | -tablet | -phone | -compact
 *   osada-orientation-landscape | -portrait
 *   osada-input-coarse | -fine        osada-no-hover | osada-has-hover
 *   osada-density-compact | -large    osada-reduced-effects
 *   osada-drawer-open                 osada-map-zooming
 *
 * The media queries near the end are a FALLBACK for the moment before those
 * classes exist (first paint) and for capability styling that does not depend
 * on layout mode. They deliberately duplicate very little.
 *
 * Sections:
 *   1. tokens                     7. drawer and bottom dock
 *   2. root sizing                8. mobile workspaces
 *   3. touch-action               9. safe area
 *   4. coarse-pointer common     10. reduced motion / reduced effects
 *   5. phone landscape shell     11. capability fallbacks
 *   6. compact + tablet
 * ========================================================================== */

/* ==========================================================================
 * 1. TOKENS
 *
 * Kotlin does NOT repeat any of these as layout constants — it measures the
 * resulting rectangles through ViewportMetrics instead. That is the whole
 * point of MOB-AUDIT-008: one owner per number.
 * ========================================================================== */
:root {
    --osada-safe-top: env(safe-area-inset-top, 0px);
    --osada-safe-right: env(safe-area-inset-right, 0px);
    --osada-safe-bottom: env(safe-area-inset-bottom, 0px);
    --osada-safe-left: env(safe-area-inset-left, 0px);

    /* 44px is the absolute product minimum, 48px the preferred target. Both are
     * larger than WCAG 2.2 AA's 24px floor on purpose: a mis-tap here does not
     * annoy, it moves a division into an ambush. */
    --osada-touch-target-min: 44px;
    --osada-touch-target: 48px;
    --osada-touch-gap: 8px;

    --osada-mobile-bottom-h: 78px;
    --osada-dock-h: var(--osada-mobile-bottom-h);
    --osada-mobile-drawer-w: min(84vw, 360px);

    /* Slotted into osada-theme.css's existing tiers (--z-hud 10, --z-dim 90,
     * --z-window 100, --z-msg 1000). The drawer sits above the HUD but below a
     * modal workspace; the orientation gate and the first-run tutorial sit
     * above everything, because both must be answerable before anything else. */
    --osada-mobile-z-scrim: 78;
    --osada-mobile-z-drawer: 80;
    --osada-mobile-z-gate: 1100;
}

body.osada-density-compact {
    --osada-touch-target: 44px;
    --osada-mobile-bottom-h: 68px;
}

body.osada-density-large {
    --osada-touch-target: 56px;
    --osada-mobile-bottom-h: 92px;
}

/* The top bar is `row + rule`, not a flat number: the row is the interactive strip the plates
 * live in, the rule is the brass hairline that is the HUD's bottom edge. Before 2026-09-04 the
 * token WAS the flat outer height, so a touch-sized plate in a same-sized bar had nowhere to go
 * but through that hairline (reported as "Завершить"/"Штаб" crossing the yellow border).
 *
 * Declared on BODY, not :root, and that is load-bearing. A custom property's `var()` is
 * substituted on the element that DECLARES it, so a `--osada-mobile-topbar-row` living on :root
 * would read :root's --osada-touch-target and never see the density/layout classes below — which
 * is exactly why every one of those blocks used to repeat a hardcoded bar height. */
body {
    --osada-mobile-topbar-rule: 2px;
    --osada-mobile-topbar-row: var(--osada-touch-target);
    --osada-mobile-topbar-h: calc(var(--osada-mobile-topbar-row) + var(--osada-mobile-topbar-rule));
}

/* ==========================================================================
 * 2. ROOT SIZING
 * `100dvh` tracks the dynamic viewport (address bar in or out); the `100vh`
 * line before it is the fallback for engines without dvh, and must stay first.
 * ========================================================================== */
html,
body,
#mainbody {
    width: 100%;
    height: 100%;
    min-height: 100vh;
    overflow: hidden;
}

@supports (min-height: 100dvh) {
    html,
    body,
    #mainbody {
        min-height: 100dvh;
    }
}

/* ==========================================================================
 * 3. TOUCH-ACTION
 * Applied LOCALLY. `touch-action: none` on the document would kill page zoom
 * and every native scroll in the game, which is the opposite of the goal.
 * ========================================================================== */

/* The map claims all of its own gestures: pan, pinch and long press are the
 * game's, so the browser must not also scroll, zoom or open a callout here. */
#cursor,
#game-zoom-wrap {
    touch-action: none;
}

#game {
    overscroll-behavior: contain;
}

.osada-action,
.osada-tb-btn,
.osada-tb-icon,
.osada-et,
.osada-btn,
.smallButton,
button,
[role="button"] {
    touch-action: manipulation;
}

#eqSelClass,
#eqClassTabs,
#eqModeTabs,
.hscroll-container {
    touch-action: pan-x;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
}

.osada-side-section,
#eqListPane,
#eqDetailPane,
#smSettingsContainer,
#osadaSideBody {
    touch-action: pan-y;
    overscroll-behavior-y: contain;
    -webkit-overflow-scrolling: touch;
}

/* ==========================================================================
 * 4. COARSE-POINTER COMMON
 * Applies to phone AND tablet: a finger is a finger regardless of screen size.
 * ========================================================================== */
body.osada-input-coarse .osada-action,
body.osada-input-coarse .osada-tb-btn,
body.osada-input-coarse .osada-tb-icon,
body.osada-input-coarse .osada-sb-toggle,
body.osada-input-coarse .osada-zoom-btn,
body.osada-input-coarse .osada-btn,
body.osada-input-coarse .smallButton {
    min-height: var(--osada-touch-target-min);
    min-width: var(--osada-touch-target-min);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
}

body.osada-input-coarse .osada-tb-icons {
    gap: var(--osada-touch-gap);
}

/* Coarse devices keep a sticky false :hover after a tap, which then blocks the
 * next interaction ("why is this row still highlighted?"). Pressed and selected
 * states carry the feedback instead. */
body.osada-no-hover *:hover {
    transition: none;
}

body.osada-input-coarse .osada-action:active,
body.osada-input-coarse .osada-btn:active,
body.osada-input-coarse .osada-tb-btn:active,
body.osada-input-coarse .smallButton:active {
    filter: brightness(1.25);
}

/* Generic labelled action button used by the mobile-only surfaces (combat
 * confirmation, orientation gate, tutorial). Text is never optional on these —
 * an icon may supplement a critical action, never replace it. */
.osada-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    min-height: var(--osada-touch-target);
    padding: 0 16px;
    border-radius: 4px;
    border: 1px solid var(--osada-metal-600, #3a3d45);
    background: linear-gradient(180deg, rgba(42, 47, 56, .95), rgba(20, 22, 26, .97));
    color: var(--osada-text, #e7e2d4);
    font-family: var(--osada-font-ui, sans-serif), sans-serif;
    font-size: 14px;
    font-weight: bold;
    letter-spacing: .04em;
    text-transform: uppercase;
    cursor: pointer;
    user-select: none;
}

.osada-btn:focus-visible {
    outline: 2px solid var(--osada-brass, #d9b25a);
    outline-offset: 2px;
}

.osada-btn--primary {
    border-color: var(--osada-brass, #d9b25a);
    color: #f4e3c6;
}

.osada-btn--danger {
    border-color: #8f2020;
    background: linear-gradient(180deg, #8f2020, #5c1414);
    color: #f6e6e6;
}

.osada-btn--secondary {
    color: var(--osada-text-dim, #a9a494);
}

/* ==========================================================================
 * 5. PHONE LANDSCAPE SHELL
 * ========================================================================== */

/* ---- Top bar: one interactive row, the brass rule, and the top safe area ----
 * Tablet shares this box. The tablet sidebar/map rules below already offset themselves by
 * --osada-mobile-topbar-h, so leaving the tablet bar at its 40px desktop height left an 8px gap
 * under it AND squeezed the same touch-sized plates the phone bar holds. */
body.osada-layout-phone #statusbar,
body.osada-layout-tablet #statusbar {
    height: calc(var(--osada-mobile-topbar-h) + var(--osada-safe-top)) !important;
    max-height: calc(var(--osada-mobile-topbar-h) + var(--osada-safe-top)) !important;
    padding-top: var(--osada-safe-top);
    padding-left: var(--osada-safe-left);
    padding-right: var(--osada-safe-right);
    box-sizing: border-box;
    display: flex;
    align-items: center;
    gap: 6px;
}

/* Decorative end caps and the large logo are the first things to go: they cost
 * width that End Turn, prestige and ready-unit navigation need (spec §14.2). */
body.osada-layout-phone #statusbar::before,
body.osada-layout-phone #statusbar::after,
body.osada-layout-phone #osadaBrand,
body.osada-layout-phone #weathermsg,
body.osada-layout-phone #locmsg,
body.osada-layout-phone #statusmsg,
body.osada-layout-phone #osadaHqBtn {
    display: none !important;
}

/* A phone cannot afford a scenario title that collapses to a meaningless first
 * letter. Turn, weather and terrain live in the contextual lower rail instead. */
body.osada-layout-phone .osada-tb-op,
body.osada-layout-phone .osadaSaveStatus {
    display: none !important;
}

/* The existing combat-log trigger is the single mobile entry point for reports,
 * objectives and the briefing. Keep it in normal flex flow instead of at the
 * legacy fixed screen centre. */
body.osada-layout-phone #combatLogButton.osada-tb-combatlog {
    display: inline-flex;
    position: static !important;
    inset: auto !important;
    margin: 0 !important;
    transform: none !important;
    flex: 0 0 var(--osada-touch-target-min);
    width: var(--osada-touch-target-min);
    height: var(--osada-touch-target-min);
    align-items: center;
    justify-content: center;
    line-height: normal;
}

/* The phone bar wears the sprite instead of the desktop arrow: it has a 40px touch target to
 * fill, and a 22px glyph in the middle of it reads as a mistake. */
body.osada-layout-phone .osada-tb-combatlog__glyph {
    display: none;
}

body.osada-layout-phone .osada-tb-combatlog__ico {
    display: block;
    width: 40px;
    height: 40px;
}

/* The supply sprite contains the same framed artwork as the neighbouring HUD
 * icons. Let it occupy the plate instead of leaving a tiny 22px picture in the
 * middle of a 48px touch target. */
body.osada-layout-phone .osada-tb-reserves {
    position: relative;
    width: var(--osada-touch-target-min);
    min-width: var(--osada-touch-target-min);
    padding: 2px;
}

body.osada-layout-phone .osada-tb-reserves__label {
    display: none;
}

body.osada-layout-phone .osada-tb-reserves__ico {
    display: block;
    width: 38px;
    height: 38px;
}

body.osada-layout-phone .osada-tb-reserves__badge {
    position: absolute;
    z-index: 1;
    top: -4px;
    right: -4px;
    min-width: 18px;
    height: 18px;
    margin: 0;
    padding: 0 4px;
}

body.osada-layout-phone .osada-tb-spacer {
    display: none;
}

body.osada-layout-phone #osadaEndTurn {
    flex: 0 0 auto;
    min-height: var(--osada-touch-target);
    min-width: 82px;
    /* Separated from Options so a mis-tap cannot end the turn (spec §14.3). */
    margin-left: var(--osada-touch-gap);
}

body.osada-layout-phone .osada-et__label {
    display: inline;
    font-size: 0;
}

body.osada-layout-phone .osada-et__label::after {
    content: attr(data-mobile-label);
    font-size: 12px;
}

/* ---- Map viewport ----
 * `#game` occupies exactly the rectangle between the top bar and the bottom
 * dock, inside the safe area. RenderContext.positionForMobileShell clears its
 * inline width/height/left/top precisely so this rule can own the box.
 *
 * `--osada-dock-h` is the MEASURED dock height, republished by
 * ViewportMetricsService whenever the dock resizes. A static token cannot work
 * here: the dock grows and shrinks with the selected unit's action chips, and
 * guessing low makes it cover the map while guessing high wastes it. The token
 * below is only the pre-measurement fallback for the first paint. */
body.osada-layout-phone #game,
body.osada-layout-tablet #game {
    position: fixed;
    top: calc(var(--osada-safe-top) + var(--osada-mobile-topbar-h));
    right: var(--osada-safe-right);
    bottom: calc(var(--osada-safe-bottom) + var(--osada-dock-h, var(--osada-mobile-bottom-h)));
    left: var(--osada-safe-left);
    width: auto;
    height: auto;
    margin: 0;
}

/* ---- Bottom action dock ---- */
body.osada-layout-phone #osada-bottomzone {
    display: grid;
    right: var(--osada-safe-right);
    left: var(--osada-safe-left);
    height: auto;
    min-height: var(--osada-mobile-bottom-h);
    max-height: 65dvh;
    padding: 0 8px calc(6px + var(--osada-safe-bottom));
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "card";
    gap: 6px;
}

/* With no selected unit the same measured bottom zone becomes a calm context
 * rail. Opening a unit/forecast card replaces it rather than stacking another
 * permanent toolbar above it. */
body.osada-layout-phone #osada-bottomzone:not(.bz--visible) > :not(#osadaMobileContextDock) {
    display: none !important;
}

body.osada-layout-phone #osada-bottomzone.bz--visible #osadaMobileContextDock {
    display: none;
}

.osada-mobile-context {
    display: none;
}

body.osada-layout-phone .osada-mobile-context {
    grid-area: card;
    display: grid;
    grid-template-columns: auto auto minmax(0, 1fr) var(--osada-touch-target-min) var(--osada-touch-target-min);
    /* Two rows: the live turn/weather/terrain line, and under it the scenario's own identity —
     * its in-game date and name. The phone top bar drops `.osada-tb-op` (a scenario title that
     * collapses to one letter is worth nothing), so this rail is where that context lives.
     *
     * Both buttons stand in their own full-height columns on the right, the card toggle outermost
     * and Heroes beside it: the toggle is the control a player reaches for mid-move, so it takes
     * the thumb's own corner. They cost the rail two touch targets of width, which the scenario
     * line pays for — it is the one element here that may lose its tail (it fades out below, and
     * its tap panel still reads the whole title). */
    grid-template-areas:
        "turn weather location heroes card"
        "sub  sub     sub      heroes card";
    align-items: center;
    gap: 2px 8px;
    min-height: 52px;
    padding: 4px 6px;
    box-sizing: border-box;
    border: 1px solid rgba(217, 178, 90, .38);
    border-radius: 5px;
    background: linear-gradient(180deg, rgba(32, 35, 42, .96), rgba(16, 18, 22, .97));
    color: var(--osada-text, #e7e2d4);
    font: 600 12px/1.2 var(--osada-font-ui, sans-serif);
}

.osada-mobile-context__turn {
    grid-area: turn;
    color: var(--osada-brass, #d9b25a);
    white-space: nowrap;
}

/* One quiet line, cut rather than wrapped: an operation name can be arbitrarily long and the rail
 * must keep a predictable height (ViewportMetricsService publishes it as --osada-dock-h and the
 * map's bottom edge tracks that number). */
.osada-mobile-context__scenario {
    grid-area: sub;
    min-width: 0;
    overflow: hidden;
    color: var(--osada-text-dim, #a9a494);
    font-weight: normal;
    font-size: 11px;
    white-space: nowrap;
    /* Fades into the rail instead of ending on an ellipsis. The dots read as a word the line is
     * still trying to say; a fade reads as a line that simply runs on, which is what an operation
     * name does — and the tap panel spells the whole of it. `text-overflow` is left at its `clip`
     * default so no glyph is replaced under the gradient. */
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
}

.osada-mobile-context__scenario:empty {
    display: none;
}

.osada-mobile-context__scenario .osada-mobile-context__date {
    color: var(--osada-text, #e7e2d4);
}

.osada-mobile-context__weather {
    grid-area: weather;
    display: inline-flex;
    align-items: center;
    white-space: nowrap;
}

.osada-mobile-context__weather .osada-tb-weather-img {
    width: 20px;
    height: 20px;
}

.osada-mobile-context__weather .osada-tb-weather-txt {
    display: none;
}

.osada-mobile-context__location {
    grid-area: location;
    min-width: 0;
    overflow: hidden;
    color: var(--osada-text-dim, #a9a494);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.osada-mobile-context__heroes {
    grid-area: heroes;
    align-self: center;
    width: var(--osada-touch-target-min);
    height: var(--osada-touch-target-min);
    cursor: pointer;
}

/* The unit card's fold-away twin, in the rail's outer corner. Sized and placed exactly as Heroes
 * so the pair reads as one control group; `osada-theme.css` keeps the 44px floor the mobile audit
 * requires of it. */
.osada-mobile-context__card-toggle {
    grid-area: card;
    align-self: center;
    width: var(--osada-touch-target-min);
    height: var(--osada-touch-target-min);
    cursor: pointer;
}

/* Target preview and hover forecast both need attacker, forecast and defender
 * side by side; the columns flex so this still fits at 667px. */
body.osada-layout-phone #osada-bottomzone.bz--hover {
    grid-template-columns: minmax(0, 1.2fr) minmax(120px, auto) minmax(0, 1fr);
    grid-template-areas: "card forecast enemy";
}

body.osada-layout-phone #osada-bottomzone.bz--enemy-only {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "enemy";
}

body.osada-layout-phone #uc-portrait {
    width: 48px;
    height: 48px;
}

/* The wrapper is dissolved so phone layouts can place the expander and actions
 * in explicit grid areas without changing the desktop DOM. */
body.osada-layout-phone #uc-side {
    display: contents;
}

/* Actions wrap inside the card. A hidden horizontal tail made Sleep look as if
 * it escaped the panel and forced a precision swipe before a common command. */
body.osada-layout-phone #uc-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--osada-touch-gap);
    min-width: 0;
    overflow: visible;
    touch-action: manipulation;
}

/* The desktop chip is a fixed 52px box with an 8px label, which clips any word
 * longer than "Undo" — and a clipped action label is exactly what a no-hover
 * device cannot recover from. On touch the chip sizes to its text instead. */
body.osada-layout-phone .osada-action {
    flex: 0 0 auto;
    /* Glyph beside the label, not stacked above it: at 48px tall a stacked
     * glyph and a full word have nowhere to go but on top of each other. */
    flex-direction: row;
    gap: 6px;
    width: auto;
    min-width: var(--osada-touch-target-min);
    min-height: var(--osada-touch-target);
    padding: 0 10px;
    scroll-snap-align: start;
}

body.osada-layout-phone .osada-action__glyph {
    font-size: 14px;
    line-height: 1;
}

body.osada-layout-phone .osada-action__label {
    font-size: 10px;
    white-space: nowrap;
}

/* The desktop 165px box exists so the chips WRAP into a two-row block beside
 * the unit stats; on a phone that block would make the dock (and so the map)
 * taller with every extra action. `nowrap` keeps them on one line and lets
 * #uc-actions scroll instead. `display` is deliberately not set here: this
 * element's display is toggled inline by makeVisible/makeHidden, and an inline
 * style always wins — the chips are inline-flex and lay themselves out anyway. */
body.osada-layout-phone #unit-context {
    width: 100%;
    min-width: 0;
    max-width: none;
    flex: 1 1 auto;
    white-space: normal;
}

body.osada-layout-phone .osada-actions {
    justify-content: flex-start;
    gap: var(--osada-touch-gap);
}

/* The dock is subtracted from the map, so every pixel it does not need is a row
 * of hexes the player gets back. */
/* The desktop card sits in a content-sized track with a 420px floor (UnitIdentityStyles.kt), so a
 * short unit name is not paid for in empty card. On a phone the card already owns the whole 1fr
 * row and there is no neighbour to give the space back to: it fills the row, and the 420px floor
 * would overflow a 390px screen outright. Higher specificity, so stylesheet order does not matter. */
body.osada-layout-phone #unit-info {
    padding: 6px 8px;
    width: 100%;
    min-width: 0;
}

body.osada-layout-phone #osadaEnemyCard {
    width: auto;
}

body.osada-layout-compact #uc-inner {
    gap: 8px;
}

body.osada-layout-compact #uc-bars {
    gap: 3px;
}

/* Undo is visually separated: it is the one rescue action in the strip. */
body.osada-layout-phone .osada-action--undo {
    margin-left: 12px;
    border-color: var(--osada-brass, #d9b25a);
}

/* Expanded dock: the full unit card as a bottom sheet, capped so the map is
 * never fully covered. */
body.osada-layout-phone #unit-info.uc--expanded #statsRowContainer {
    position: fixed;
    left: var(--osada-safe-left);
    right: var(--osada-safe-right);
    bottom: calc(var(--osada-dock-h, var(--osada-mobile-bottom-h)) + var(--osada-safe-bottom));
    max-height: 45dvh;
    overflow-y: auto;
    touch-action: pan-y;
    overscroll-behavior-y: contain;
    background: rgba(16, 18, 21, .98);
    border-top: 1px solid var(--osada-brass, #d9b25a);
    z-index: var(--osada-mobile-z-scrim);
}

/* ---- Combat confirmation controls ----
 * NOTE: nothing here may set `display` on #osadaForecast. Its visibility is the
 * bottom-zone state machine's (`.bz--hover` / `.bz--target-preview`), and an
 * unscoped `display: flex` here silently pinned the forecast strip open, which
 * added a phantom grid row under the unit card and pushed the dock 40px taller
 * than its content. */
.osada-fc-actions {
    display: none;
    gap: var(--osada-touch-gap);
    margin-top: 4px;
}

/* Only shown while a target preview is actually open — the desktop hover
 * forecast has no confirmation step and must not sprout buttons. */
#osada-bottomzone.bz--target-preview .osada-fc-actions {
    display: flex;
}

#osada-bottomzone.bz--target-preview .osada-fc-actions .osada-btn {
    flex: 1 1 0;
    min-height: var(--osada-touch-target-min);
    padding: 0 10px;
    font-size: 12px;
}

/* ---- Transient status strip (tap on an empty hex) ---- */
.osada-mobile-status {
    position: fixed;
    top: calc(var(--osada-safe-top) + var(--osada-mobile-topbar-h) + 8px);
    right: calc(var(--osada-safe-right) + 8px);
    bottom: auto;
    left: calc(var(--osada-safe-left) + 8px);
    transform: none;
    max-width: min(calc(100vw - var(--osada-safe-left) - var(--osada-safe-right) - 16px), 520px);
    padding: 6px 12px;
    border-radius: 4px;
    background: rgba(12, 13, 16, .92);
    border: 1px solid var(--osada-metal-600, #3a3d45);
    color: var(--osada-text, #e7e2d4);
    font-family: var(--osada-font-ui, sans-serif), sans-serif;
    font-size: 12px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    opacity: 0;
    pointer-events: none;
    transition: opacity .15s;
    z-index: var(--osada-mobile-z-scrim);
}

.osada-mobile-status--visible {
    opacity: 1;
}

/* ==========================================================================
 * 6. COMPACT-HEIGHT AND TABLET
 * ========================================================================== */
/* A 375px-tall landscape phone cannot spend 58px on a bar. Compact takes the 44px product
 * MINIMUM as its target rather than shrinking the bar under the plates it holds — the row and the
 * bar are one number now, so lowering only the bar would put the plates back through the rule.
 * Deliberately after the density blocks so it also wins over an explicit "large" density. */
body.osada-layout-compact {
    --osada-touch-target: var(--osada-touch-target-min);
    --osada-mobile-bottom-h: 66px;
}

/* Progressive disclosure rather than shrinking everything: at 375px of height
 * the subtitle text is what goes, not the control sizes. */
body.osada-layout-compact #uc-bars .osada-bar--fuel,
body.osada-layout-compact .osada-fc-strengths,
body.osada-layout-compact .osada-ec-sub {
    display: none;
}

body.osada-layout-compact #uc-portrait {
    width: 40px;
    height: 40px;
}

/* Tablet keeps the persistent sidebar — it has the room — but at touch sizes. */
body.osada-layout-tablet #osada-sidebar {
    top: calc(var(--osada-safe-top) + var(--osada-mobile-topbar-h));
    padding-bottom: var(--osada-safe-bottom);
}

body.osada-layout-tablet #game {
    right: calc(var(--osada-safe-right) + 270px);
}

/* ==========================================================================
 * 7. DRAWER
 * ========================================================================== */
#osadaDrawerBtn {
    display: none;
}

body.osada-layout-phone #osadaDrawerBtn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
    min-height: var(--osada-touch-target);
    padding: 0 10px;
}

.osada-tb-drawer__glyph {
    font-size: 18px;
    line-height: 1;
}

body.osada-layout-compact .osada-tb-drawer__label {
    display: none;
}

body.osada-layout-phone #osada-sidebar {
    top: calc(var(--osada-safe-top) + var(--osada-mobile-topbar-h));
    right: 0;
    bottom: 0;
    width: var(--osada-mobile-drawer-w);
    padding-right: var(--osada-safe-right);
    padding-bottom: var(--osada-safe-bottom);
    z-index: var(--osada-mobile-z-drawer);
    transform: translateX(105%);
    transition: transform .18s ease-out;
}

body.osada-layout-phone.osada-drawer-open #osada-sidebar {
    transform: translateX(0);
}

/* The desktop collapse rail is meaningless once the whole panel is a drawer. */
body.osada-layout-phone .osada-sb-collapse,
body.osada-layout-phone .osada-sb-rail {
    display: none !important;
}

body.osada-layout-phone #osada-sidebar.osada-sidebar--collapsed {
    width: var(--osada-mobile-drawer-w);
}

/* The panel stack scrolls; the close control stays pinned below it. Without the
 * `min-height: 0` the flex item refuses to shrink and the panels grow straight
 * through the close button instead of scrolling. */
body.osada-layout-phone #osadaSideBody {
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    touch-action: pan-y;
    overscroll-behavior-y: contain;
}

.osada-drawer-close {
    display: none;
}

body.osada-layout-phone .osada-drawer-close {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 var(--osada-touch-target-min);
    width: var(--osada-touch-target-min);
    height: var(--osada-touch-target-min);
    min-height: var(--osada-touch-target-min);
    /* The flexible `.osada-sb-toggles-spacer` before it does the pushing — X sits on the drawer's
     * right edge, inside the toggle row's own 10px padding, rather than butting against Air. */
    margin: 0;
    border: 1px solid var(--osada-metal-600, #3a3d45);
    border-radius: 4px;
    color: var(--osada-text, #e7e2d4);
    background-size: 400% 300%;
    cursor: pointer;
}

.osada-drawer-scrim {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, .5);
    z-index: var(--osada-mobile-z-scrim);
}

body.osada-layout-phone.osada-drawer-open .osada-drawer-scrim {
    display: block;
}

/* The minimap fills the drawer width but keeps its aspect ratio, and its zoom
 * controls take full touch targets. */
body.osada-layout-phone #osada-minimap {
    width: 100%;
    height: 100%;
    min-height: 0;
}

body.osada-layout-phone .osada-sb-panel--minimap {
    flex: 0 1 42%;
    min-height: 96px;
}

body.osada-layout-phone .osada-minimap-frame {
    flex: 1 1 auto;
    height: auto;
    min-height: 96px;
}

body.osada-layout-phone .osada-sb-panel--objectives {
    flex: 0 1 24%;
    max-height: 24%;
}

body.osada-layout-phone .osada-sb-panel--log {
    flex: 1 1 120px;
    /* Includes the 21px panel heading; leaves roughly two full log rows plus
     * scrolling room even at the 375px landscape acceptance height. */
    min-height: 112px;
}

body.osada-layout-phone .osada-zoom-btn,
body.osada-layout-phone .osada-zoom-pct {
    min-width: var(--osada-touch-target);
    min-height: var(--osada-touch-target);
}

/* ==========================================================================
 * 8. MOBILE WORKSPACES
 * Equipment, dossier, turn report and the settings screen become full-screen
 * surfaces with a scrollable body — a centred 840px box does not exist at
 * 667x375. `!important` here fights UILayout's inline top/width writes.
 * ========================================================================== */
body.osada-layout-phone #equipment,
body.osada-layout-phone #dossier,
body.osada-layout-phone #combatLog,
body.osada-layout-phone #smSettings {
    position: fixed !important;
    top: var(--osada-safe-top) !important;
    right: var(--osada-safe-right) !important;
    bottom: var(--osada-safe-bottom) !important;
    left: var(--osada-safe-left) !important;
    width: auto !important;
    height: auto !important;
    max-width: none !important;
    max-height: none !important;
    margin: 0 !important;
    transform: none !important;
    border-radius: 0 !important;
    overflow: hidden;
}

/* The workspace keeps its own named grid areas — only the ROW HEIGHTS and the
 * list/detail split are retuned. Replacing the template outright collapsed the
 * five areas onto four rows and stacked the header on top of the tabs. */
body.osada-layout-phone #equipment {
    grid-template-rows:
        var(--osada-touch-target-min)
        38px
        var(--osada-touch-target-min)
        minmax(0, 1fr)
        calc(var(--osada-touch-target) + var(--osada-safe-bottom));
    grid-template-columns: minmax(0, 1fr) minmax(0, 40%);
}

/* Short landscape equipment is two screens, not two cramped columns. Card selection opens the
 * detail/action screen; Back restores the list without clearing #eqUserSel. Reserve deployment
 * remains list-only because choosing a reserve hands control straight back to the map. */
body.osada-layout-compact #equipment {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
        "header"
        "modetabs"
        "classtabs"
        "content"
        "footer";
}

body.osada-layout-compact #eqListPane,
body.osada-layout-compact #eqDetailPane {
    grid-area: content;
}

body.osada-layout-compact #equipment:not(.osada-eq--reserve) #eqDetailPane {
    display: none;
}

body.osada-layout-compact #equipment.osada-eq-detail-open:not(.osada-eq--reserve) #eqListPane {
    display: none;
}

body.osada-layout-compact #equipment.osada-eq-detail-open:not(.osada-eq--reserve) #eqDetailPane {
    display: flex;
    grid-column: 1 / -1;
    border-left: 0;
    overflow: hidden;
}

#eqDetailBack {
    display: none;
}

body.osada-layout-compact #equipment.osada-eq-detail-open #eqDetailBack {
    display: inline-flex;
    flex: 0 0 auto;
    align-self: flex-start;
    min-width: var(--osada-touch-target-min);
    min-height: var(--osada-touch-target-min);
    margin: 8px 10px 0;
}

body.osada-layout-phone #eqFooter {
    padding-bottom: var(--osada-safe-bottom);
}

body.osada-layout-phone #eqClassTabs > *,
body.osada-layout-phone #eqModeTabs > * {
    min-height: var(--osada-touch-target-min);
    scroll-snap-align: start;
}

body.osada-layout-phone #eqClassTabs {
    overflow-x: auto;
    scroll-snap-type: x proximity;
    touch-action: pan-x;
    overscroll-behavior-x: contain;
}

body.osada-layout-phone .eqUnitBox {
    min-width: 96px;
    min-height: 72px;
}

/* Briefings and campaign dialogue: the overlay already fills the viewport, so
 * the mobile work is safe-area padding, a body that actually scrolls, one
 * column instead of two, and choices/buttons at touch size. */
body.osada-layout-phone .osada-briefing {
    padding: var(--osada-safe-top) var(--osada-safe-right) var(--osada-safe-bottom) var(--osada-safe-left);
}

body.osada-layout-phone .osada-briefing__orders,
body.osada-layout-phone .osada-briefing__orders-panel {
    padding: 12px;
    touch-action: pan-y;
    overscroll-behavior-y: contain;
}

/* There must be exactly one vertical scroll owner. The legacy briefing made
 * both the stage and the paper panel scroll, which traps a finger at either
 * boundary and can leave the Begin button outside the reachable area. */
body.osada-layout-phone .osada-briefing__orders {
    overflow: hidden;
}

body.osada-layout-phone .osada-briefing__orders-panel {
    box-sizing: border-box;
    height: 100%;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

body.osada-layout-phone .osada-briefing__footer {
    position: sticky;
    bottom: 0;
    z-index: 2;
    background: linear-gradient(180deg, rgba(18, 20, 24, 0), rgba(18, 20, 24, .98) 24%);
}

/* Two columns of order text at 667px leaves ~300px per column — one column
 * reads, two do not. */
body.osada-layout-phone .osada-briefing__orders-content {
    grid-template-columns: minmax(0, 1fr);
}

body.osada-layout-phone .osada-briefing__footer {
    padding-bottom: var(--osada-safe-bottom);
}

body.osada-layout-phone .osada-briefing__button,
body.osada-layout-phone .osada-briefing__decision-option {
    min-height: var(--osada-touch-target);
}

/* Story-mode conversation. osada-briefing.css is lazy-loaded after this file,
 * so these body-qualified selectors intentionally carry higher specificity.
 * Its narrow-screen rule gave the card max-width:100% while a portrait and gap
 * still occupied the same flex row: total width became 100% + portrait and the
 * right side of every line was clipped. */
body.osada-layout-phone .osada-dialogue-stage,
body.osada-layout-phone .osada-dialogue,
body.osada-layout-phone .osada-dialogue__transcript,
body.osada-layout-phone .osada-dialogue__turn,
body.osada-layout-phone .osada-dialogue__controls,
body.osada-layout-phone .osada-dialogue__choices {
    min-width: 0;
    max-width: 100%;
}

/* Grid items default to min-width:auto. A long act/location subtitle therefore
 * enlarged the shell's single grid column even though the dialogue children
 * themselves were capped, shifting the whole stage past the right edge. */
body.osada-layout-phone .osada-briefing__shade,
body.osada-layout-phone .osada-briefing__shell,
body.osada-layout-phone .osada-briefing__header,
body.osada-layout-phone .osada-briefing__title-block,
body.osada-layout-phone .osada-briefing__subtitle {
    min-width: 0;
    max-width: 100%;
}

body.osada-layout-phone .osada-briefing__shade,
body.osada-layout-phone .osada-briefing__shell {
    width: 100%;
}

body.osada-layout-phone .osada-dialogue-stage {
    width: 100%;
    overflow: hidden;
}

body.osada-layout-phone .osada-dialogue {
    width: 100%;
}

body.osada-layout-phone .osada-dialogue__transcript,
body.osada-layout-phone .osada-dialogue__turn,
body.osada-layout-phone .osada-dialogue__controls {
    width: 100%;
}

body.osada-layout-phone .osada-dialogue__turn {
    gap: 8px;
}

body.osada-layout-phone .osada-dialogue__portrait {
    width: 44px;
    height: 44px;
}

body.osada-layout-phone .osada-dialogue__body {
    flex: 1 1 0;
    width: auto;
    min-width: 0;
    max-width: calc(100% - 52px);
}

body.osada-layout-phone .osada-dialogue__turn--player .osada-dialogue__body {
    max-width: 100%;
}

body.osada-layout-phone .osada-dialogue__text,
body.osada-layout-phone .osada-dialogue__choice-text,
body.osada-layout-phone .osada-dialogue__choice-hint,
body.osada-layout-phone .osada-briefing__decision-text,
body.osada-layout-phone .osada-briefing__decision-hint {
    min-width: 0;
    overflow-wrap: anywhere;
}

body.osada-layout-phone .osada-dialogue__choices,
body.osada-layout-phone .osada-briefing__decision-options {
    grid-template-columns: minmax(0, 1fr);
}

/* Long story choices must not push the transcript or the second choice off the
 * physical screen. The decision strip is the sole scroll owner while choosing. */
body.osada-layout-phone .osada-dialogue__controls--deciding {
    max-height: min(44dvh, 360px);
    overflow-x: hidden;
    overflow-y: auto;
    touch-action: pan-y;
    overscroll-behavior-y: contain;
    -webkit-overflow-scrolling: touch;
}

body.osada-layout-phone .osada-dialogue__choice,
body.osada-layout-phone .osada-briefing__decision-option {
    width: 100%;
    min-width: 0;
}

body.osada-layout-phone.osada-orientation-portrait .osada-briefing__header {
    align-items: flex-start;
    gap: 8px;
    padding: 8px 12px;
}

body.osada-layout-phone.osada-orientation-portrait .osada-briefing__title {
    display: -webkit-box;
    overflow: hidden;
    white-space: normal;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

body.osada-layout-phone.osada-orientation-portrait .osada-briefing__date {
    display: none;
}

/* In portrait the sticky gradient sat over the last lines of the paper order
 * and made the red Begin plate look detached from its panel. The panel is the
 * scroll owner, so a normal footer remains reachable without covering text. */
body.osada-layout-phone.osada-orientation-portrait .osada-briefing__footer {
    position: static;
    margin-top: 14px;
    padding: 14px 0 calc(4px + var(--osada-safe-bottom));
    background: transparent;
}

body.osada-layout-phone.osada-orientation-portrait .osada-briefing__button--primary {
    width: 100%;
}

body.osada-layout-phone.osada-orientation-portrait .osada-dialogue-stage {
    padding: 6px;
}

body.osada-layout-phone.osada-orientation-portrait .osada-dialogue {
    padding: 34px 0 2px;
}

body.osada-layout-phone.osada-orientation-portrait .osada-dialogue__body {
    padding: 9px 10px 11px;
}

body.osada-layout-phone.osada-orientation-portrait .osada-dialogue__text {
    font-size: 14px;
    line-height: 1.42;
}

body.osada-layout-phone .osada-dialogue__skip {
    min-width: var(--osada-touch-target-min);
    min-height: var(--osada-touch-target-min);
}

/* The settings list fills the full-screen panel MINUS the "Done" footer button, which flows
 * after it (`#smSetOkBut`: 38px tall + 12px of margin). Without the reservation the list took
 * the whole panel and pushed Done out through the panel's `overflow: hidden` edge. */
body.osada-layout-phone #smSettingsContainer {
    box-sizing: border-box;
    max-height: none;
    height: calc(100% - 52px);
    padding-bottom: 8px;
    overflow-y: auto;
    touch-action: pan-y;
    overscroll-behavior-y: contain;
}

/* Slider rows at phone width: the label takes its own line and the stepper/slider/stepper
 * triple takes a full-width one under it. Keeping the label beside them left ~80px of track
 * between two 44px touch targets, and the desktop arrangement the player asked for (steppers
 * flanking the slider, not stacked above and below it) only fits when the row is the full width
 * of the panel. Checkbox rows are untouched — they still read label-left / control-right. */
body.osada-layout-phone #smSettingsContainer .settingContainer--slider {
    flex-wrap: wrap;
    row-gap: 2px;
    padding-bottom: 8px;
}

body.osada-layout-phone #smSettingsContainer .settingContainer--slider .settingText {
    flex: 1 0 100%;
}

body.osada-layout-phone .settingSlider {
    width: 100%;
    flex: 1 0 100%;
    gap: 10px;
}

/* Steppers keep a real touch target but drop the desktop disc's 26px+6px border bulk. */
body.osada-layout-phone .settingSlider .osada-slider__step {
    min-width: 40px;
    min-height: 40px;
    width: 40px;
    height: 40px;
    border-width: 2px;
}

/* Start menu: it owns its scrolling instead of letting absolutely positioned
 * buttons escape the viewport. */
body.osada-layout-phone #smMain {
    overflow-x: hidden;
    overflow-y: auto;
    touch-action: pan-y;
    overscroll-behavior-y: contain;
    -webkit-overflow-scrolling: touch;
}

body.osada-layout-phone #smLogoText {
    max-height: 56px;
    overflow: hidden;
}

body.osada-layout-phone #smButtons {
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    justify-content: flex-start;
    gap: var(--osada-touch-gap);
}

body.osada-layout-phone #smButtons > * {
    width: 100%;
    min-height: var(--osada-touch-target);
}

/* Mobile keeps the FULL-SIZE main-menu sprite. osada-theme.css switched the desktop default to
 * the hand-reduced `_mini` sheet because an 8.6x downscale shimmered on a large monitor; here the
 * plate is painted on a 2-3x DPR screen, so the device asks for ~126 physical pixels of a 52px
 * source and the mini sheet is the one that would look soft. Same grid, same cell offsets — only
 * the source resolution differs, so nothing else in the sprite geometry changes. */
body.osada-layout-phone .osada-menu-ico,
body.osada-layout-tablet .osada-menu-ico {
    background-image: url('../resources/main_menu_icons_grid.png');
}

/* A quote cannot share the short landscape command rail. Portrait keeps it as a
 * normal-flow footer below every action, where it adds atmosphere without ever
 * becoming an overlay. */
body.osada-layout-phone.osada-orientation-landscape .osada-quote,
body.osada-layout-compact #smCredits {
    display: none;
}

/* Landscape phone: keep the logo and commands compact, and scroll only the
 * command column. This covers both 667x375 and the taller 880x400 P40 viewport. */
body.osada-layout-phone.osada-orientation-landscape #smLogo {
    top: 12px;
    left: calc(var(--osada-safe-left) + 16px);
    width: min(42vw, 360px);
    height: 92px;
}

body.osada-layout-phone.osada-orientation-landscape .smBottomCenter {
    top: 138px;
    bottom: calc(var(--osada-safe-bottom) + 34px);
    left: calc(var(--osada-safe-left) + 16px);
    width: min(360px, calc(48vw - var(--osada-safe-left) - 24px));
}

body.osada-layout-phone.osada-orientation-landscape #smButtons {
    max-height: 100%;
    overflow-y: auto;
    padding-right: 4px;
    scrollbar-width: thin;
}

body.osada-layout-phone.osada-orientation-landscape .smMainButton.osada-menu-btn {
    width: 100%;
}

body.osada-layout-phone.osada-orientation-landscape #smLogoText {
    max-width: 100%;
    font-size: 10px;
    letter-spacing: .08em;
    white-space: nowrap;
}

/* Portrait phone: put the command column back into normal flow so it gives the
 * scroller a real height. Absolute children do not contribute to scrollHeight. */
body.osada-layout-phone.osada-orientation-portrait #smLogo {
    top: 112px;
    left: 16px;
    width: calc(100% - 32px);
    height: 126px;
}

body.osada-layout-phone.osada-orientation-portrait .smBottomCenter {
    position: relative;
    top: auto;
    right: auto;
    bottom: auto;
    left: auto;
    width: auto;
    margin: 286px 16px 0;
}

body.osada-layout-phone.osada-orientation-portrait .smMainButton.osada-menu-btn {
    width: 100%;
}

body.osada-layout-phone.osada-orientation-portrait #smMiscButs {
    position: absolute;
    top: -104px;
    left: 0;
    right: auto;
    bottom: auto;
    justify-content: flex-start;
    margin: 0;
}

body.osada-layout-phone.osada-orientation-portrait .osada-quote {
    position: relative;
    inset: auto;
    transform: none;
    width: auto;
    max-width: calc(100% - 48px);
    margin: 26px auto 64px;
    padding-bottom: var(--osada-safe-bottom);
}

/* The menu is its own scroll surface. A local background follows that scrollable
 * canvas so the desk never ends in a bare browser-coloured strip below the quote. */
body.osada-layout-phone.osada-orientation-portrait #smMain {
    background-attachment: local;
    background-color: #0a0b0d;
}

body.osada-layout-phone.osada-orientation-portrait .osada-language-switch {
    top: 58px;
    right: 16px;
}

body.osada-layout-phone.osada-orientation-portrait #smCredits {
    display: none;
}

/* Campaign/scenario browser: fixed safe-area frame, one scroll owner per list,
 * and a footer that cannot migrate to the top of a short landscape screen. */
body.osada-layout-phone #smCamp,
body.osada-layout-phone #smScen {
    position: fixed;
    inset: var(--osada-safe-top) var(--osada-safe-right) var(--osada-safe-bottom) var(--osada-safe-left);
    width: auto;
    max-width: none;
    height: auto;
    max-height: none;
    margin: 0;
    padding: 8px 12px;
    box-sizing: border-box;
    overflow: hidden;
}

body.osada-layout-phone #smCamp .osadaScreenHeader,
body.osada-layout-phone #smScen .osadaScreenHeader {
    top: 4px;
    left: 12px;
    right: 12px;
    font-size: clamp(17px, 4vw, 24px);
}

body.osada-layout-phone #smCamp .osadaScreenBody,
body.osada-layout-phone #smScen .osadaScreenBody {
    top: 46px;
    right: 12px;
    bottom: 64px;
    left: 12px;
    gap: 10px;
    min-width: 0;
}

body.osada-layout-phone #smCampButtons,
body.osada-layout-phone #smScenButtons {
    position: absolute;
    right: 12px;
    bottom: 8px;
    left: 12px;
    z-index: 4;
    min-height: 48px;
    padding: 0;
    background: rgba(12, 13, 16, .96);
}

body.osada-layout-phone #smCBackBut,
body.osada-layout-phone #smSBackBut,
body.osada-layout-phone #smCPlayBut,
body.osada-layout-phone #smSPlayBut {
    min-width: 0;
    min-height: var(--osada-touch-target-min);
}

body.osada-layout-phone .osadaList {
    overflow-y: auto;
    touch-action: pan-y;
    overscroll-behavior-y: contain;
    -webkit-overflow-scrolling: touch;
}

body.osada-layout-phone.osada-orientation-landscape .osadaRegister {
    flex: 0 0 42%;
}

body.osada-layout-phone.osada-orientation-landscape .osadaDossier {
    overflow-y: auto;
    touch-action: pan-y;
}

body.osada-layout-phone.osada-orientation-landscape .osadaDossierHead {
    height: 132px;
}

/* Portrait uses a single vertical workspace. The register keeps its own list
 * scroll while dossier content is reachable through the body scroll. */
body.osada-layout-phone.osada-orientation-portrait #smCamp .osadaScreenBody,
body.osada-layout-phone.osada-orientation-portrait #smScen .osadaScreenBody {
    display: block;
    overflow-x: hidden;
    overflow-y: auto;
    touch-action: pan-y;
    overscroll-behavior-y: contain;
}

body.osada-layout-phone.osada-orientation-portrait .osadaRegister {
    height: min(44dvh, 360px);
    min-height: 250px;
}

body.osada-layout-phone.osada-orientation-portrait .osadaDossier {
    margin-top: 12px;
    overflow: visible;
}

body.osada-layout-phone.osada-orientation-portrait .osadaDossierHead {
    height: 180px;
}

body.osada-layout-phone.osada-orientation-portrait #smCampButtons,
body.osada-layout-phone.osada-orientation-portrait #smScenButtons {
    display: flex;
    flex-wrap: nowrap;
    gap: 5px;
}

body.osada-layout-phone.osada-orientation-portrait #smCBackBut,
body.osada-layout-phone.osada-orientation-portrait #smSBackBut {
    flex: 0 0 var(--osada-touch-target-min);
    width: var(--osada-touch-target-min);
    padding: 0;
}

body.osada-layout-phone.osada-orientation-portrait #smCBackBut::after,
body.osada-layout-phone.osada-orientation-portrait #smSBackBut::after {
    content: "←";
    font-size: 22px;
    letter-spacing: 0;
}

body.osada-layout-phone.osada-orientation-portrait #smCPlayBut,
body.osada-layout-phone.osada-orientation-portrait #smSPlayBut {
    flex: 1 1 auto;
    width: auto;
    min-width: 76px;
    padding: 0 7px;
}

body.osada-layout-phone.osada-orientation-portrait .osadaCampBackupButton {
    flex: 0 0 var(--osada-touch-target-min);
    width: var(--osada-touch-target-min);
    min-width: var(--osada-touch-target-min);
    padding: 0;
    font-size: 0;
    line-height: var(--osada-touch-target-min);
}

body.osada-layout-phone.osada-orientation-portrait .osadaRulesButton {
    min-height: var(--osada-touch-target-min);
}

body.osada-layout-phone.osada-orientation-portrait .osadaCampBackupButton::after {
    color: var(--osada-brass, #d9b25a);
    font-family: var(--osada-font-ui, sans-serif);
    font-size: 22px;
    line-height: 1;
}

body.osada-layout-phone.osada-orientation-portrait .osadaCampBackupButton--export::after {
    content: "↓";
}

body.osada-layout-phone.osada-orientation-portrait .osadaCampBackupButton--import::after {
    content: "↑";
}

body.osada-layout-phone.osada-orientation-portrait .osadaCampBackupStatus {
    position: absolute;
    right: 0;
    bottom: calc(100% + 4px);
    left: 0;
    min-height: 0;
    padding: 4px 8px;
    background: rgba(12, 13, 16, .96);
}

body.osada-layout-phone.osada-orientation-portrait .osadaCampBackupStatus:empty {
    display: none;
}

/* Phone map scrollbars consume visible map pixels and look like controls.
 * Panning remains available through Pointer Events. */
body.osada-layout-phone #game {
    scrollbar-width: none;
}

body.osada-layout-phone #game::-webkit-scrollbar {
    display: none;
}

/* Hero emergence has denser authored copy than a generic notification. Give
 * it a named responsive layout instead of relying on whichever content happens
 * to fit in a fixed desktop-height body. */
body.osada-layout-phone .uiMessageBox {
    width: calc(100vw - var(--osada-safe-left) - var(--osada-safe-right) - 16px);
    max-width: 760px;
    max-height: calc(var(--osada-vv-h, 100dvh) - var(--osada-safe-top) - var(--osada-safe-bottom) - 16px);
    box-sizing: border-box;
    overflow: hidden;
}

/* The legacy OK control is floated. Contain it without changing `display`,
 * because visibility for this element is still controlled by an inline style. */
body.osada-layout-phone .uiMessageBox::after {
    content: "";
    display: block;
    clear: both;
}

body.osada-layout-phone .uiMessageBoxBody {
    /* Reserve space for a wrapped two-line title and the close/continue control.
     * The former 116px subtraction let a long narrative body consume the whole
     * dialog and clip the only way out below the viewport. */
    max-height: calc(var(--osada-vv-h, 100dvh) - var(--osada-safe-top) - var(--osada-safe-bottom) - 190px);
    overflow-y: auto;
    touch-action: pan-y;
    overscroll-behavior-y: contain;
}

body.osada-layout-phone .uiMessageBoxTitle {
    display: -webkit-box;
    max-height: 3em;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
}

body.osada-layout-phone.osada-orientation-landscape .uiMessageBox--hero .uiMessageBoxBody {
    display: grid;
    grid-template-columns: 112px minmax(0, 1fr);
    column-gap: 14px;
    align-content: start;
}

body.osada-layout-phone.osada-orientation-landscape .uiMessageBox--hero .heroEmergencePortrait {
    grid-column: 1;
    grid-row: 1 / span 12;
    width: 96px;
    height: 128px;
    margin: 0 auto;
}

body.osada-layout-phone.osada-orientation-landscape .uiMessageBox--hero .uiMessageBoxBody > :not(.heroEmergencePortrait) {
    grid-column: 2;
}

body.osada-layout-phone.osada-orientation-portrait .uiMessageBox--hero .heroEmergencePortrait {
    width: min(44vw, 150px);
    height: min(58vw, 198px);
    margin: 0 auto 12px;
}

/* Landscape phone dock: identity stays in its own column, while All Stats is
 * above the wrapped action rows. Nothing can cover the name or escape the card. */
body.osada-layout-phone.osada-orientation-landscape #unit-info {
    max-width: none;
    justify-self: stretch;
}

body.osada-layout-phone.osada-orientation-landscape #uc-inner {
    display: grid;
    grid-template-columns: 48px minmax(180px, 34%) minmax(0, 1fr);
    grid-template-areas:
        "portrait main expand"
        "portrait main actions";
    align-items: center;
    gap: 5px 10px;
}

body.osada-layout-phone.osada-orientation-landscape #uc-portrait { grid-area: portrait; }
body.osada-layout-phone.osada-orientation-landscape #uc-main { grid-area: main; }
body.osada-layout-phone.osada-orientation-landscape #uc-expand {
    grid-area: expand;
    justify-self: start;
    min-height: 28px;
    padding: 5px 8px;
}
body.osada-layout-phone.osada-orientation-landscape #uc-actions { grid-area: actions; }

body.osada-layout-phone.osada-orientation-landscape .osada-action {
    min-height: var(--osada-touch-target-min);
}

/* Portrait combat is a vertical workspace, not three desktop cards squeezed
 * into narrow columns. */
body.osada-layout-phone.osada-orientation-portrait #osada-bottomzone {
    max-height: 58dvh;
    overflow-y: auto;
}

body.osada-layout-phone.osada-orientation-portrait #osada-bottomzone.bz--hover {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "card" "forecast" "enemy";
}

body.osada-layout-phone.osada-orientation-portrait #uc-inner {
    display: grid;
    grid-template-columns: 48px minmax(0, 1fr) auto;
    grid-template-areas:
        "portrait main expand"
        "actions actions actions";
    align-items: center;
    gap: 6px 8px;
}

body.osada-layout-phone.osada-orientation-portrait #uc-portrait { grid-area: portrait; }
body.osada-layout-phone.osada-orientation-portrait #uc-main { grid-area: main; }
body.osada-layout-phone.osada-orientation-portrait #uc-actions { grid-area: actions; }
body.osada-layout-phone.osada-orientation-portrait #uc-expand { grid-area: expand; }

/* Portrait keeps the semantic labels for assistive technology but uses the
 * existing action glyphs visually, fitting common actions on one reachable row. */
body.osada-layout-phone.osada-orientation-portrait .osada-action {
    width: var(--osada-touch-target-min);
    min-width: var(--osada-touch-target-min);
    padding: 0;
}

body.osada-layout-phone.osada-orientation-portrait .osada-action__glyph {
    font-size: 18px;
}

body.osada-layout-phone.osada-orientation-portrait .osada-action__label {
    display: none;
}

body.osada-layout-phone.osada-orientation-portrait #uc-expand {
    width: var(--osada-touch-target-min);
    min-width: var(--osada-touch-target-min);
    height: var(--osada-touch-target-min);
    padding: 0;
    font-size: 0;
}

body.osada-layout-phone.osada-orientation-portrait #uc-expand::after {
    content: "i";
    font-family: Georgia, serif;
    font-size: 20px;
    font-weight: 700;
    text-transform: none;
}

/* Keep End Turn reachable in portrait. Secondary tools stay available from
 * the drawer/settings surfaces instead of pushing the critical action away. */
body.osada-layout-phone.osada-orientation-portrait #zoom,
body.osada-layout-phone.osada-orientation-portrait #osadaNav {
    display: none;
}

body.osada-layout-phone.osada-orientation-portrait #statusbar {
    gap: 4px;
}

body.osada-layout-phone.osada-orientation-portrait #osadaPrestige {
    margin-left: 0;
}

body.osada-layout-phone.osada-orientation-portrait #osadaEndTurn {
    margin-left: auto;
}

/* At 390px the bar's two LABELLED plates plus five 44px targets came to ~418px, so "Завершить"
 * ran off the right edge of the screen. Both labels stay — an unlabelled ≡ and a mystery red
 * plate are worse than tight ones — and the ~30px comes out of padding and tracking instead.
 * Landscape has the width and keeps the roomier plates. */
body.osada-layout-phone.osada-orientation-portrait #osadaEndTurn {
    min-width: 0;
    padding: 0 8px;
    letter-spacing: .01em;
}

body.osada-layout-phone.osada-orientation-portrait #osadaDrawerBtn {
    gap: 4px;
    padding: 0 6px;
}

body.osada-layout-phone .smSelect > * {
    min-height: var(--osada-touch-target-min);
}

/* ---- Orientation gate ---- */
.osada-orientation-gate {
    display: none;
    position: fixed;
    inset: 0;
    z-index: var(--osada-mobile-z-gate);
    align-items: center;
    justify-content: center;
    background: rgba(8, 9, 11, .94);
    padding: calc(16px + var(--osada-safe-top)) calc(16px + var(--osada-safe-right)) calc(16px + var(--osada-safe-bottom)) calc(16px + var(--osada-safe-left));
}

.osada-orientation-gate__card {
    max-width: min(92vw, 420px);
    text-align: center;
    color: var(--osada-text, #e7e2d4);
    font-family: var(--osada-font-ui, sans-serif), sans-serif;
}

.osada-orientation-gate__icon {
    font-size: 40px;
    color: var(--osada-brass, #d9b25a);
}

.osada-orientation-gate__title {
    margin: 10px 0 6px;
    font-size: 18px;
    font-weight: bold;
    text-transform: uppercase;
    letter-spacing: .06em;
}

.osada-orientation-gate__body {
    font-size: 13px;
    color: var(--osada-text-dim, #a9a494);
}

.osada-orientation-gate__actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--osada-touch-gap);
    margin-top: 16px;
}

/* ---- Gesture tutorial ---- */
.osada-tutorial {
    position: fixed;
    inset: 0;
    z-index: var(--osada-mobile-z-gate);
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(8, 9, 11, .9);
    padding: calc(16px + var(--osada-safe-top)) calc(16px + var(--osada-safe-right)) calc(16px + var(--osada-safe-bottom)) calc(16px + var(--osada-safe-left));
}

.osada-tutorial__card {
    max-width: min(92vw, 520px);
    max-height: 80dvh;
    overflow-y: auto;
    touch-action: pan-y;
    padding: 16px;
    background: rgba(18, 20, 24, .98);
    border: 1px solid var(--osada-brass, #d9b25a);
    border-radius: 6px;
    color: var(--osada-text, #e7e2d4);
    font-family: var(--osada-font-ui, sans-serif), sans-serif;
}

.osada-tutorial__title {
    font-size: 16px;
    font-weight: bold;
    text-transform: uppercase;
    letter-spacing: .06em;
    margin-bottom: 10px;
}

.osada-tutorial__list {
    margin: 0 0 14px;
    padding-left: 18px;
}

.osada-tutorial__step {
    font-size: 13px;
    line-height: 1.5;
}

.osada-tutorial__done {
    width: 100%;
}

/* ==========================================================================
 * 9. SAFE AREA
 * Checked in BOTH landscape rotations — the cutout can be on either side, so
 * every fixed control pads from its own inset rather than from a single side.
 * ========================================================================== */
body.osada-layout-phone #statusbar-extension,
body.osada-layout-phone #unitsBarButton,
body.osada-layout-phone #statusBarButton {
    left: auto;
    right: calc(var(--osada-safe-right) + 8px);
    margin-left: 0;
}

/* ==========================================================================
 * 10. REDUCED MOTION AND REDUCED EFFECTS
 * ========================================================================== */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}

/* Explicit player choice, for large scenarios on modest hardware. Gameplay is
 * never reduced — only decoration (spec §48.3). */
body.osada-reduced-effects .animatedBackground {
    display: none;
}

body.osada-reduced-effects *,
body.osada-reduced-effects *::before,
body.osada-reduced-effects *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
}

/* While a pinch preview is running, DOM-positioned map labels are placed in map
 * coordinates and cannot cheaply follow it — hide them rather than let them
 * drift, and show them again at commit (which rebuilds them anyway). */
body.osada-map-zooming #game > div:not(#game-zoom-wrap) {
    visibility: hidden;
}

/* ==========================================================================
 * 11. CAPABILITY FALLBACKS
 * Used before the body classes exist (first paint) and where the rule is a
 * property of the input device rather than of the layout mode.
 * ========================================================================== */
@media (hover: none) and (pointer: coarse) {
    :root {
        --osada-touch-target: 48px;
    }

    /* A `title` tooltip cannot be reached without hover, so it must never be
     * the only explanation for a control. Anything relying on it also carries
     * a visible label or an info action; this just stops the cursor lying. */
    [title] {
        cursor: default;
    }
}

@media (hover: none) and (pointer: coarse) and (orientation: landscape) and (max-height: 600px) {
    /* 44px row + the 2px brass rule, matching what the compact body class resolves to once
     * MobileLayoutController has classified the device. */
    #statusbar {
        height: calc(46px + env(safe-area-inset-top, 0px)) !important;
        max-height: calc(46px + env(safe-area-inset-top, 0px)) !important;
    }
}
