/* incursion-board.css: Incursion board (.ib2-*). */
/* ==========================================================================
   Incursion board v2 (the beta presentation channel)

   Namespaced `.ib2-*` and appended as one block rather than woven through the live
   incursion styles above, so the two channels cannot fight in the cascade and so promoting
   or deleting v2 is a contiguous edit rather than an archaeological one.

   The identity is a tactical instrument panel: recessed wells, hairline rules, mono numerals,
   and one type-coloured band per encounter. Every colour is an existing token, so it survives
   all ten Legacy palettes, both Frontier modes and the per-theme contrast tests; nothing here
   opts out of theming.

   Motion is decoration only. Nothing here gates server state or a next action, and the global
   reduced-motion clamp already flattens it.
   ========================================================================== */

.ib2-page { --ib2-gap: 14px; }

/* ---- header ---- */
.ib2-header {
    display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between;
    gap: 6px 16px; margin-bottom: 10px;
}
.ib2-header__titles { min-width: 0; }
.ib2-title {
    margin: 2px 0 0; font-family: var(--font-display); font-size: var(--font-size-xl);
    line-height: 1.1; outline-offset: 3px;
}
/* Programmatic focus is not reliably :focus-visible, and restoreFocus() targets this when the
   room a player just resolved is gone. Without a ring they have no idea where they landed. */
.ib2-title:focus { outline: 2px solid var(--color-primary); }
.ib2-floor-condition {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; margin: 8px 0 0;
}
.ib2-floor-condition__blurb { font-size: var(--font-size-xs); color: var(--text-secondary); }
/* The floor's frozen route promise (FloorCard.route_guarantee). Kept at every width: it is
   the one sentence that says this floor can be finished from the hand. */
.ib2-route-promise { margin: 6px 0 0; font-size: var(--font-size-sm); color: var(--text-secondary); }
.ib2-header__actions {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px; justify-content: flex-start;
}
@media (min-width: 768px) {
    .ib2-header__actions { justify-content: flex-end; }
    .ib2-header__note { text-align: right; margin-left: auto; }
}
/* Full width and left-aligned rather than trailing the buttons: right-aligned it wrapped to
   three lines on a phone, and this note sits above the play surface. */
.ib2-header__note { flex-basis: 100%; margin: 0; max-width: 70ch; }

/* ---- command bar ---------------------------------------------------------
   Sticky because the haul and the forfeit are the two figures a player re-checks while
   deciding whether to push on, and a strip you have to scroll up to read is one you stop
   reading. --z-sticky, so it sits under the site header rather than over it. */
.ib2-bar {
    position: sticky; top: calc(var(--header-height) + 6px);
    /* One BELOW --z-sticky, which .site-header already occupies. At equal z-index the later
       element in the DOM wins, so any overlap (a taller header, a safe-area inset) would put
       this strip over the site navigation rather than under it. */
    z-index: calc(var(--z-sticky) - 1);
    display: flex; flex-direction: column; gap: 0;
    background: var(--bg-elevated);
    border: 1px solid var(--border-color); border-radius: var(--radius-md);
    box-shadow: var(--shadow-sm);
    margin-bottom: var(--ib2-gap);
}
.ib2-bar__cells {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1px; background: var(--border-subtle);
    border-radius: var(--radius-md) var(--radius-md) 0 0; overflow: hidden;
}
.ib2-bar__cell {
    display: flex; flex-direction: column; gap: 2px; min-width: 0;
    padding: 8px 12px; background: var(--bg-elevated);
}
.ib2-bar__label {
    font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--text-muted); white-space: nowrap;
}
.ib2-bar__value {
    font-family: var(--font-mono); font-size: 1.15rem; font-weight: 700; line-height: 1.15;
    color: var(--text-primary); overflow-wrap: anywhere;
}
.ib2-bar__value--haul { font-size: 0.86rem; font-weight: 700; color: var(--color-primary); }
.ib2-bar__value--loss { color: var(--color-warning-text); }
.ib2-bar__sub { font-size: 0.7rem; color: var(--text-muted); overflow-wrap: anywhere; }
/* Stress that is actually costing something reads as a warning; zero stays neutral, so the
   tint means "this is escalating" rather than decorating a number that has not moved. */
.ib2-bar__cell--loss:not([data-stress="0"]) .ib2-bar__value--loss { color: var(--color-error-text); }

.ib2-bar__detail { border-top: 1px solid var(--border-subtle); }
.ib2-bar__detail-summary {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    padding: 7px 12px; cursor: pointer; list-style: none;
    font-size: var(--font-size-xs); color: var(--text-secondary);
}
.ib2-bar__detail-summary::-webkit-details-marker { display: none; }
.ib2-bar__chevron { transition: transform var(--dur) var(--ease); }
.ib2-bar__detail[open] .ib2-bar__chevron { transform: rotate(180deg); }
.ib2-bar__detail-body { padding: 0 12px 10px; display: flex; flex-direction: column; gap: 5px; }
.ib2-bar__detail-row {
    display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
    gap: 4px 12px; margin: 0; font-size: var(--font-size-xs);
}
.ib2-bar__detail-label { color: var(--text-muted); }
.ib2-bar__detail-value { font-family: var(--font-mono); color: var(--text-primary); }
.ib2-bar__detail-note { margin: 2px 0 0; font-size: var(--font-size-xs); color: var(--text-muted); }

/* ---- layout ---- */
.ib2-shell { display: flex; flex-direction: column; gap: var(--ib2-gap); }
.ib2-layout { display: grid; grid-template-columns: 1fr; gap: var(--ib2-gap); align-items: start; }
.ib2-main, .ib2-rail { display: flex; flex-direction: column; gap: var(--ib2-gap); min-width: 0; }
.ib2-rail > .field-notes > summary {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    min-height: 44px; padding: 8px 12px; margin: 0; cursor: pointer; list-style: none;
    background: var(--bg-surface); border: 1px solid var(--border-color);
    border-radius: var(--radius-md);
}
.ib2-rail > .field-notes > summary::-webkit-details-marker { display: none; }
.ib2-rail .field-notes__chevron { color: var(--text-muted); transition: transform var(--dur) var(--ease); }
.ib2-rail > .field-notes[open] > summary .field-notes__chevron { transform: rotate(180deg); }

/* ---- encounter ---- */
.ib2-encounter {
    background: var(--bg-surface); border: 1px solid var(--border-color);
    border-radius: var(--radius-md); overflow: hidden;
}
/* The band replaces a full-width art panel that cost ~190px. It still reserves a fixed shape,
   so real art can drop in later without shifting layout, but it costs a line not a panel. */
.ib2-encounter__band {
    display: flex; align-items: center; gap: 8px; padding: 7px 12px;
    background: var(--bg-subtle);
    border-bottom: 1px solid var(--ib2-band, var(--border-color));
}
.ib2-encounter__glyph {
    flex: 0 0 auto; display: grid; place-items: center;
    width: 32px; height: 32px; border-radius: var(--radius-sm);
    font-size: 1rem; background: var(--bg-elevated);
    box-shadow: inset 0 0 0 1px var(--ib2-band, var(--border-color));
}
.ib2-encounter__ident { min-width: 0; flex: 1 1 auto; }
.ib2-encounter__title {
    margin: 0; font-family: var(--font-heading); font-size: var(--font-size-lg); line-height: 1.2;
}
.ib2-encounter__type {
    display: block; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.08em;
    color: var(--text-muted); margin-top: 2px;
}
.ib2-encounter__chip { flex: 0 0 auto; }
.ib2-encounter__body { padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 8px; }
.ib2-encounter__flavour { margin: 0; font-size: var(--font-size-sm); color: var(--text-secondary); line-height: 1.45; }
.ib2-encounter__moment { margin: 0; font-size: var(--font-size-xs); color: var(--text-muted); font-style: italic; }

/* One band colour per node type, from the existing signal tokens. The type is also stated in
   words directly beneath the title, so this is emphasis and never the only signal. */
.ib2-band--boss           { --ib2-band: var(--color-reward); }
.ib2-band--battle         { --ib2-band: var(--color-error); }
.ib2-band--vault          { --ib2-band: var(--color-warning); }
.ib2-band--merchant       { --ib2-band: var(--color-primary); }
.ib2-band--faction_supply { --ib2-band: var(--color-info); }
.ib2-band--hazard         { --ib2-band: var(--color-error); }
.ib2-band--camp           { --ib2-band: var(--color-success); }

/* ---- stakes ---- */
.ib2-stakes {
    list-style: none; margin: 0; padding: 0;
    display: grid; grid-template-columns: 1fr; gap: 8px;
}
.ib2-stake {
    display: flex; flex-direction: column; gap: 2px;
    padding: 8px 10px; border-radius: var(--radius-sm);
    background: var(--bg-subtle); border-left: 3px solid var(--border-color);
}
.ib2-stake--reward { border-left-color: var(--color-success); }
.ib2-stake--penalty { border-left-color: var(--color-warning); }
.ib2-stake__label {
    font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-muted);
}
.ib2-stake__value { font-size: var(--font-size-sm); font-weight: 600; color: var(--text-primary); }
.ib2-stake__note { font-size: var(--font-size-xs); color: var(--text-muted); }

/* ---- solve block: hand, then slots, then commit ---- */
.ib2-solve {
    display: flex; flex-direction: column; gap: 10px;
    padding: 12px; border-radius: var(--radius-sm);
    background: var(--bg-subtle); border: 1px solid var(--border-subtle);
}
.ib2-solve__head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.ib2-solve__title {
    margin: 0; font-size: 0.68rem; text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--text-muted);
}
.ib2-solve__count { font-size: var(--font-size-xs); color: var(--text-secondary); margin-right: auto; }
.ib2-solve__count strong { font-family: var(--font-mono); color: var(--text-primary); }
.ib2-solve__hint { margin: 0; font-size: var(--font-size-xs); color: var(--text-muted); line-height: 1.4; }
/* Auto-place's stated decline: why it won't fill this lock, naming the dice the boss needs. */
.ib2-solve__declined { margin: 0; font-size: var(--font-size-sm); color: var(--text-secondary); line-height: 1.4; }
.ib2-hand { display: flex; flex-wrap: wrap; gap: 6px; }
.ib2-solve__unlocks { margin: 0; font-size: var(--font-size-xs); color: var(--text-muted); }
.ib2-solve__unlock { margin-left: 2px; }

.ib2-retreat { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }

/* ---- route ---- */
.ib2-route {
    background: var(--bg-surface); border: 1px solid var(--border-color);
    border-radius: var(--radius-md); padding: 12px 14px;
    display: flex; flex-direction: column; gap: 10px;
}
.ib2-route__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.ib2-route__title { margin: 2px 0 0; font-family: var(--font-heading); font-size: var(--font-size-base); }
.ib2-route__progress { font-family: var(--font-mono); font-size: var(--font-size-xs); color: var(--text-secondary); white-space: nowrap; }
/* The plot is the containing block for every absolutely-positioned node: see
   game/test_css_positioning.py, which fails if this loses position: relative. */
.ib2-route__plot {
    position: relative; border-radius: var(--radius-sm);
    /* 52px per row: the tallest node is 44px (a reachable one, at the coarse-pointer floor)
       plus breathing room. A fixed height packed a deep floor tighter than its own nodes. */
    min-height: calc(var(--ib2-route-rows, 5) * 52px);
    background: var(--bg-cool); border: 1px solid var(--border-subtle); overflow: hidden;
}
.ib2-route__edges { position: absolute; inset: 0; width: 100%; height: 100%; }
.ib2-route__edge { fill: none; stroke: var(--border-color); stroke-width: 1.2; vector-effect: non-scaling-stroke; }
.ib2-route__edge.is-open { stroke: var(--color-primary); opacity: 0.7; }
.ib2-route__nodes { position: absolute; inset: 0; }
.ib2-route__node {
    position: absolute; transform: translate(-50%, -50%);
    display: grid; place-items: center; gap: 1px;
    min-width: 34px; min-height: 34px; padding: 3px 5px;
    border-radius: var(--radius-sm); font-size: 0.95rem; line-height: 1;
    background: var(--bg-elevated); border: 1px solid var(--border-color); color: var(--text-secondary);
}
.ib2-route__node-name {
    font-size: 0.58rem; letter-spacing: 0.02em; color: var(--text-muted);
    max-width: 9ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ib2-route__node--available {
    cursor: pointer; color: var(--text-primary);
    border-color: var(--color-primary); box-shadow: var(--glow-accent);
}
.ib2-route__node--available.is-selected {
    background: var(--color-primary); color: var(--text-inverse);
}
.ib2-route__node--available.is-selected .ib2-route__node-name { color: var(--text-inverse); }
.ib2-route__node--cleared { color: var(--color-success-text); border-color: var(--color-success-border); }
.ib2-route__node--failed { color: var(--color-error-text); border-color: var(--color-error-border); }
.ib2-route__node--skipped { color: var(--text-muted); border-color: var(--border-subtle); border-style: dashed; opacity: 0.62; }
.ib2-route__node--locked { opacity: 0.62; }
.ib2-route__node:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; }
@media (hover: hover) {
    .ib2-route__node--available:not(.is-selected):hover { background: var(--bg-hover); }
}
.ib2-route__help, .ib2-route__boss { margin: 0; font-size: var(--font-size-xs); color: var(--text-secondary); line-height: 1.45; }
.ib2-route__legend { display: flex; flex-wrap: wrap; gap: 4px 12px; font-size: var(--font-size-xs); color: var(--text-muted); }
.ib2-route__swatch { font-style: normal; margin-right: 3px; }
.ib2-route__swatch--cleared { color: var(--color-success-text); }
.ib2-route__swatch--available { color: var(--color-primary); }
.ib2-route__swatch--failed { color: var(--color-error-text); }
.ib2-route__swatch--skipped { color: var(--text-muted); }
.ib2-route__promises-title {
    margin: 0 0 5px; font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--text-muted);
}
.ib2-route__promise-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.ib2-route__promise {
    display: flex; flex-direction: column; gap: 2px;
    padding: 6px 9px; border-radius: var(--radius-sm);
    background: var(--bg-subtle); border-left: 3px solid var(--border-color);
}
.ib2-route__promise-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }
.ib2-route__promise-name { font-size: var(--font-size-sm); font-weight: 600; }
.ib2-route__promise-kind {
    font-size: 0.62rem; text-transform: uppercase; letter-spacing: 0.07em; color: var(--text-muted);
}
.ib2-route__promise-text { font-size: var(--font-size-xs); color: var(--text-secondary); line-height: 1.4; }

/* The boss lock, revealed from floor start. Uses the same .slot-req chips the room lock
   itself does, so a requirement reads identically wherever it appears. */
.ib2-boss-lock {
    display: flex; flex-direction: column; gap: 6px;
    padding: 9px 10px; border-radius: var(--radius-sm);
    background: var(--bg-subtle); border-left: 3px solid var(--color-reward);
}
.ib2-boss-lock__title {
    margin: 0; font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--text-muted);
}
.ib2-boss-lock__slots { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.ib2-boss-lock__note { margin: 0; font-size: var(--font-size-xs); color: var(--text-secondary); line-height: 1.4; }

/* ---- tablet ---- */
@media (min-width: 768px) {
    .ib2-bar__cells { grid-template-columns: repeat(5, minmax(0, 1fr)); }
    .ib2-stakes { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ---- desktop: the route moves beside the encounter, so it costs no vertical space ---- */
@media (min-width: 1024px) {
    .ib2-layout { grid-template-columns: minmax(0, 1fr) 320px; }
    .ib2-rail {
        position: sticky;
        top: calc(var(--header-height) + 14px);
        max-height: calc(100vh - var(--header-height) - 28px);   /* fallback */
        max-height: calc(100dvh - var(--header-height) - 28px);
        overflow-y: auto; scrollbar-gutter: stable;
    }
    .ib2-route__plot { min-height: calc(var(--ib2-route-rows, 5) * 56px); }
}

/* ---- beta launch screen -------------------------------------------------
   Same content as the live launch screen, split by how often each part is needed. The live
   one measured 3.2 screens at 375x812 with Begin 2.58 screens down, because a 459px loadout
   fragment sat between the party and a 949px commit block of full-prose mode and track cards.
   Nothing is removed: attribution, the bench, the beta brief and the world context are
   disclosures, and a launch blocker is never behind one. */
.ib2-launch { display: flex; flex-direction: column; gap: var(--ib2-gap); max-width: 1120px; }
.ib2-launch__panel {
    display: flex; flex-direction: column; gap: 10px;
    padding: clamp(14px, 2vw, 22px); background:
        linear-gradient(145deg, color-mix(in srgb, var(--color-primary) 5%, var(--bg-surface)), var(--bg-surface));
    border: 1px solid color-mix(in srgb, var(--color-primary) 26%, var(--border-color));
    border-radius: var(--radius-lg); box-shadow: 0 10px 30px color-mix(in srgb, var(--color-primary) 7%, transparent);
}
.ib2-launch__title {
    margin: 0; font-family: var(--font-heading); font-size: clamp(1.1rem, 2vw, 1.35rem);
    letter-spacing: 0.01em;
}
.ib2-launch__left,
.ib2-launch__intro,
.ib2-launch__party-block,
.ib2-launch__commit { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.ib2-launch__left { gap: 16px; }
.ib2-launch__intro { padding-bottom: 2px; }
.ib2-launch__preflight { min-width: 0; }
.ib2-launch__squad { margin: 0; }
.ib2-launch__stats { margin: 0; font-size: var(--font-size-sm); color: var(--text-secondary); }
.ib2-launch__stats strong { color: var(--text-primary); font-family: var(--font-mono); }
.ib2-launch__section-head {
    display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
    padding-bottom: 8px; border-bottom: 1px solid var(--border-subtle);
}
.ib2-launch__section-head h3 { margin: 2px 0 0; font-family: var(--font-heading); font-size: var(--font-size-base); }
.ib2-launch__section-count,
.ib2-launch__section-mark {
    flex: 0 0 auto; color: var(--color-primary); font-family: var(--font-mono);
    font-size: var(--font-size-xs); font-weight: 700;
}
.ib2-launch__section-mark { font-size: 1.1rem; }
.ib2-launch__party { margin: 0; }
.ib2-launch__commit {
    padding-top: 12px; border-top: 1px solid color-mix(in srgb, var(--color-primary) 28%, var(--border-subtle));
}
.ib2-launch__commit .hint { margin-top: 0; }
.ib2-launch__go { align-self: stretch; min-height: 48px; margin-top: 2px; font-size: var(--font-size-base); letter-spacing: 0.01em; }

/* The shared roster card also serves wider squad-manager surfaces. On the beta launch bench,
   the card is a compact explanation of one selectable pet: let its name and metadata each use
   the full text column instead of making them compete for one wrapped flex line. */
.ib2-launch .squad-pet-pick { align-items: flex-start; }
.ib2-launch .squad-pet-pick__body {
    display: grid; grid-template-columns: minmax(0, 1fr); align-content: center;
    gap: 4px; width: 100%;
}
.ib2-launch .squad-pet-pick__name,
.ib2-launch .squad-pet-pick__meta { width: 100%; min-width: 0; }

/* The loadout fragment: headline in the open, attribution behind one disclosure. */
.ib2-loadout {
    display: flex; flex-direction: column; gap: 8px;
    padding: 10px 12px; border-radius: var(--radius-sm);
    background: var(--bg-subtle); border: 1px solid var(--border-subtle);
}
.ib2-loadout__headline {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
}
.ib2-loadout__total { display: flex; align-items: baseline; gap: 5px; }
.ib2-loadout__total strong {
    font-family: var(--font-mono); font-size: 1.5rem; font-weight: 700; line-height: 1;
    color: var(--color-primary);
}
.ib2-loadout__total-label {
    font-size: 0.66rem; text-transform: uppercase; letter-spacing: 0.09em; color: var(--text-muted);
}
.ib2-loadout__meta { font-size: var(--font-size-xs); color: var(--text-secondary); }
.ib2-loadout__meta strong { color: var(--text-primary); }
.ib2-loadout__dice { margin: 0; }
.ib2-loadout__detail { border-top: 1px solid var(--border-subtle); padding-top: 6px; }
.ib2-loadout__detail-summary {
    display: flex; align-items: center; justify-content: space-between; gap: 8px;
    cursor: pointer; list-style: none;
    font-size: var(--font-size-xs); color: var(--text-secondary); min-height: 32px;
}
.ib2-loadout__detail-summary::-webkit-details-marker { display: none; }
.ib2-loadout__detail[open] .ib2-bar__chevron { transform: rotate(180deg); }
.ib2-loadout__detail-body { display: flex; flex-direction: column; gap: 8px; padding-bottom: 4px; }

/* Mode and track: the same radio-card pattern, two across from 560px, with the long prose
   trimmed to the sentence that actually decides the choice. */
.ib2-modes { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
/* minmax(0, 1fr), never a bare 1fr. A bare `1fr` is `minmax(auto, 1fr)`, so a track's
   min-content width leaks out of the grid and widens the whole document: browser-measured,
   the beta track's "EXPERIMENTAL TRACK - RULES MAY CHANGE - SIMULATED REWARDS ONLY" chip
   pushed a 375px page to 551px. The chip is also allowed to wrap for the same reason: a
   nowrap chip sets a floor no column can go under. */
.ib2-modes__options { display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px; }
.ib2-mode { margin: 0; min-width: 0; }
.ib2-mode .mode-option__body { min-width: 0; }
.ib2-mode .state-chip { white-space: normal; }
.ib2-modes__note { margin: 0; }
.ib2-world { display: flex; flex-direction: column; gap: 8px; margin-top: var(--ib2-gap); }

@media (min-width: 560px) {
    .ib2-modes__options { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
    /* Keep the launch decision in authored groups. Auto-placing every warning, disclosure and
       fieldset into alternating columns was the source of the desktop jank. */
    .ib2-launch__panel {
        display: grid; grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
        gap: 18px 24px; align-items: start;
    }
    .ib2-launch__title { grid-column: 1 / -1; }
    .ib2-launch__left { grid-column: 1; grid-row: 2; }
    .ib2-launch__commit { grid-column: 2; grid-row: 2; padding-top: 0; border-top: 0; }
}

@media (pointer: coarse) {
    .ib2-loadout__detail-summary { min-height: 44px; }
}

/* ---- coarse pointer ------------------------------------------------------
   At the END of the v2 block for the same cascade reason the live board has its own floor:
   several of these also carry .btn-xs / .btn-sm, whose min-height is declared later in this
   file at the same specificity, so a coarse rule written earlier silently loses. */
@media (pointer: coarse) {
    .ib2-route__node--available,
    .ib2-bar__detail-summary,
    .ib2-header__actions .btn { min-height: 44px; }
    .ib2-route__node--available { min-width: 44px; }
    .ib2-header__actions .btn { display: inline-flex; align-items: center; justify-content: center; }
}

/* ---- active-run focus shell and phone controls -------------------------
   The drawer remains the escape hatch to every destination. During an active run the fixed
   bar therefore carries only the current Deeps destination and Menu, returning vertical room
   to the tactical surface without removing navigation. The phone HUD also leaves normal flow:
   a sticky card must not obscure an encounter while a player scrolls through it. */
@media (width < 768px) {
    body.incursion-focus-shell .mobile-tabbar {
        justify-content: center;
        gap: 8px;
        padding: 4px 12px calc(4px + env(safe-area-inset-bottom, 0px));
    }
    body.incursion-focus-shell .mobile-tabbar .mobile-tab:not(.is-active):not(.mobile-tab--menu) {
        display: none;
    }
    body.incursion-focus-shell .mobile-tabbar .mobile-tab {
        flex: 0 1 9rem;
        min-height: 44px;
        padding: 4px 8px;
    }

    .ib2-bar {
        position: static;
        margin-bottom: 0;
    }
    .ib2-bar__cells { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .ib2-bar__cell--floor { display: none; }
    .ib2-bar__cell--dice { grid-column: 1; grid-row: 1; }
    .ib2-bar__cell--loss { grid-column: 2; grid-row: 1; }
    .ib2-bar__cell--haul { grid-column: 1 / -1; grid-row: 2; }
    .ib2-bar__cell--xp { grid-column: 1 / -1; grid-row: 3; }
    .ib2-bar__sub,
    .ib2-floor-condition__blurb { display: none; }
    .ib2-bar__detail-summary { min-height: 44px; }

}

/* Keyboard shortcuts (UserPreferences.incursion_shortcuts). The sheet button and the key hints
   exist only for fine pointers with hover: phones and tablets see nothing new. The sheet itself
   reuses .form-modal and only opens from that button or the ? key. */
.ib2-header__actions .btn.ib2-shortcuts-btn,
.ib2-key-hint { display: none; }
@media (hover: hover) and (pointer: fine) {
    .ib2-header__actions .btn.ib2-shortcuts-btn { display: inline-flex; align-items: center; }
    .ib2-key-hint { display: inline-flex; align-items: center; align-self: center; margin-inline: 4px; }
}
.ib2-kbd {
    display: inline-block;
    min-width: 1.6em;
    padding: 1px 6px;
    border: 1px solid var(--border-color);
    border-bottom-width: 2px;
    border-radius: var(--radius-sm);
    background: var(--bg-subtle);
    color: var(--text-primary);
    font: 600 .75rem/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    text-align: center;
    white-space: nowrap;
}
.ib2-shortcuts__card { width: min(560px, 100%); gap: 12px; }
.ib2-shortcuts__lede { margin: 0; }
.ib2-shortcuts__list {
    display: grid;
    gap: 8px;
    margin: 0;
    padding: 0;
    list-style: none;
}
.ib2-shortcuts__list li {
    display: grid;
    grid-template-columns: minmax(7.5rem, auto) minmax(0, 1fr);
    gap: 12px;
    align-items: baseline;
}
.ib2-shortcuts__keys { white-space: nowrap; }
.ib2-shortcuts__note { margin: 0; }

/* The hand remains dense, but each die and staging control is a real touch target. The width
   branch keeps phone layouts safe in browsers that report an imprecise pointer; the coarse
   branch covers touch tablets at and above the 768px layout breakpoint. */
@media (pointer: coarse), (width < 768px) {
    .ib2-hand { gap: 6px; }
    .ib2-hand .die-group { width: 44px; height: 44px; min-height: 44px; }
    .ib2-solve .btn-xs,
    .ib2-solve .slot-arm,
    .ib2-solve .slot-pick { min-height: 44px; }
    .ib2-solve .btn-xs,
    .ib2-solve .slot-arm { display: inline-flex; align-items: center; justify-content: center; }
    .ib2-solve .stacked-die { width: 44px; height: 44px; }
}

