/* mobile.css — tier 5 responsive.
 * Owns: phone layout for everything (@media (max-width: 820px) and narrower)
 * and the .mobile-* tab bar / install chrome. */

/* Carried from the old mobile.css header: comprehensive mobile redesign.
 * Activates at ≤820px. Desktop is untouched.
 * Pairs with: js/app/pwa-install.js (4-tab nav),
 * js/app/mobile-shell.js (page title, back nav, sticky pts pill). */

.ios-install-sheet { max-width: 380px; }
.ios-install-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp-7); counter-reset: ios-install-step; }
.ios-install-steps li { counter-increment: ios-install-step; display: flex; align-items: flex-start; gap: var(--sp-6); font-size: var(--fs-base); line-height: 1.5; color: var(--text); }
.ios-install-steps li::before {
  content: counter(ios-install-step);
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: var(--radius-pill);
  background: rgba(var(--accent-rgb), 0.15);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.ios-install-step-glyph { flex: 0 0 auto; width: 20px; height: 20px; color: var(--accent); margin-top: 1px; }
@keyframes waha-slide {
  from { transform: translateX(18px); opacity: 0; }
  to { transform: none; opacity: 1; }
}

@media (min-width: 821px) {
  /* ── Hide mobile-only chrome on desktop */
  .mobile-tabbar, .mobile-pts-pill { display: none !important; }
}

@media (max-width: 820px) {
  .modal-close { width: 44px; height: 44px; font-size: var(--fs-3xl); display: inline-flex; align-items: center; justify-content: center; }

  /* Touch: the Units panel is a primary mobile surface, so this needs a 44x44
   * target. Growing the painted box to 44px would drag the whole wrapping chip
   * row (chips are ~19px tall) up to 44px and eat a chunk of the roster. So the
   * visible circle only goes to 32px and an invisible ::after carries the hit
   * area out to 44x44. That is safe HERE specifically because roster.js appends
   * this button LAST inside .filter-chips — nothing follows it on its line, so
   * the 6px of bleed cannot swallow a chip's own target. */
  .filter-chips-clear { width: 32px; height: 32px; font-size: var(--fs-lg); margin-left: var(--sp-5); }
  .filter-chips-clear::after { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 44px; height: 44px; border-radius: 50%; }
  .filter-chip { font-size: var(--fs-sm); padding: var(--sp-4) var(--sp-6); min-height: 32px; }

  /* Filter chips. The chip's background is repainted by at least four
   * later files (style.css `.active`/`.excluded`, favorites.css
   * `.fav-chip.active`), the
   * highest at (0,3,0) — and favorites.css is being edited concurrently.
   * Rather than chase every variant, paint over whatever background is
   * there with a full-bleed inset box-shadow: nothing in css/ sets
   * `box-shadow` or `transform` on .filter-chip, so (0,2,0) is enough and
   * stays correct if a new chip variant is added later. The inset shadow
   * honours the chip's 999px border-radius. */
  .filter-chip:active { box-shadow: inset 0 0 0 999px rgba(255, 255, 255, 0.16); transform: scale(0.94); }

  /* Keep the colour feedback under reduced motion — drop only the squash. */
  body.reduce-motion .filter-chip:active { transform: none; }

  /* Dropdown menus may be wider than the column on mobile; allow viewport
   * width minus padding. */
  #tools-dropdown-menu, #more-dropdown-menu, #export-dropdown-menu { max-width: calc(100vw - 32px); }

  /* ── Reset perf-heavy effects on mobile */
  .topbar, .modal-backdrop, .settings-drawer-scrim { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; }
  .topbar {
    background: #0d0d0d !important;
    /* ── Mobile (≤820px) — topbar is hidden entirely.
     * Brand, page title, account, settings, help, and export are surfaced
     * through the bottom-nav More tab (which opens the settings drawer).
     * Saves ~48px of vertical space on phones. */
    display: none !important;
  }

  /* Hide the wordmark; the rune glyph alone is enough to mark home. */
  .topbar-brand-mark { display: none; }

  /* Brand glyph must stay visible even when the wordmark is hidden. */
  .topbar-brand { padding: var(--sp-2) var(--sp-2); }

  /* Smaller search target so action buttons are never pushed off. */
  .topbar-search { max-width: 220px; }

  /* Hide the help label early — the "?" glyph is universally legible. */
  .topbar-action-label-help { display: none; }

  /* Action buttons collapse to icon-only; ensure the gear glyph isn't
   * crammed against the help glyph. */
  .topbar-action-btn { padding: 0 var(--sp-4); min-width: 32px; }

  /* Mode tabs use full labels by default. Hidden short-label variants are
   * revealed at narrow widths. Markup may inject `.topbar-mode-tab-short`
   * via JS, but we also offer this attribute-based fallback so the tab
   * button text element stays the simple word. */
  /* ─── Tablet (≤960px) — already in topbar.css; we tighten gaps so the
   * search bar doesn't visually butt against the brand+modes. */
  /* ─── Intermediate (≤820px) — the band where the original layout
   * starts dropping items off the right edge. Hide the brand wordmark
   * here (kept the glyph), shrink mode-tab horizontal padding, and clamp
   * search width so Settings + Help stay visible. */
  /* ─── Mobile (≤820px) — full collapse. topbar.css already hides the
   * action labels and shrinks the search to an icon button; we add the
   * mode-tab "B / C / P" short-label switch and ensure the brand glyph
   * never disappears. */
  /* NOTE: this block was once max-width 720px; it now uses 820, the app's phone cutoff (the JS agrees). The topbar and these
   * mode tabs are display:none at <= 820 (topbar.css), so the block is inert either way. */
  /* Reveal short labels, hide long ones. The visible mode-tab text node
   * is the tab button's direct text, so we use a swap: shrink the
   * existing label via overflow + tighten padding. */
  .topbar-mode-tab {
    padding: 0 var(--sp-3);
    font-size: var(--fs-2xs);
    letter-spacing: var(--tr-wide);
    /* Clamp width so 3 tabs always fit alongside icon search + actions. */
    min-width: 28px;
    max-width: 56px;
    overflow: hidden;
    text-overflow: clip;
    white-space: nowrap;
  }

  /* ── Viewport stack (no wrap, single 48px topbar) */
  /* Prevent iOS Safari auto-zoom on input focus (needs >= 16px). */
  input, select, textarea, .form-input, .form-select, .search-input { font-size: var(--fs-lg) !important; }

  /* No topbar on mobile — only safe-area top inset (notch). */
  body {
    padding-bottom: 0;
    padding-left: var(--m-safe-left);
    padding-right: var(--m-safe-right);
    padding-top: env(safe-area-inset-top, 0px) !important;
  }

  /* App-main fills viewport between safe-top and the bottom tab bar
   * (which itself absorbs safe-bottom). 100% here = body content area. */
  .app-main {
    display: flex !important;
    flex-direction: column;
    grid-template-columns: none !important;
    grid-template-rows: none !important;
    height: calc(100% - var(--m-tabbar-h) - var(--m-safe-bottom)) !important;
    min-height: 0;
    overflow: hidden;
  }
  body.build-mode-enhanced #app-main { height: calc(100% - var(--m-tabbar-h) - var(--m-safe-bottom)) !important; }

  /* The mode-page section wraps the .app-main; make sure it doesn't
   * impose its own height. mode-shell.css gives it min-height: 100vh on
   * desktop, which forces a tall flow that breaks our fixed-height
   * mobile stack. */
  .mode-page {
    min-height: 0 !important;
    /* Mobile: topbar is hidden entirely (see css/topbar.css), so the mode
     * page consumes the full viewport (minus the bottom tab bar, which the
     * inner #app-main subtracts in css/mobile.css). */
    height: 100%;
  }

  /* build-mode.css turns #build-mode into a 2-row grid (auto 1fr) for
   * the desktop build-hero. On mobile the hero is hidden but the empty
   * 1fr row still claims space — leaving a dead strip below app-main
   * that the user sees as "the panel ends halfway down the screen."
   * Drop the grid on mobile so app-main's calc(100% - tabbar - safe)
   * drives the layout directly.
   * The :not([hidden]) guard is load-bearing: this rule is (1,1,0)
   * !important, which beats mode-shell.css's `.mode-page[hidden]
   * { display: none !important }` at (0,2,0) — so without the guard,
   * #build-mode stayed VISIBLE on mobile while Cards / Play
   * was active, and the active mode rendered a full viewport below the
   * fold (body overflow:hidden made it look simply blank). */
  body.build-mode-enhanced #build-mode:not([hidden]) { display: block !important; grid-template-rows: none !important; }

  /* Compact at phone widths (820 = app-wide cutoff): wrap the subtitle and
   * stack points below. */
  .build-hero { grid-template-columns: 1fr; gap: var(--sp-3); }
  .build-hero-main { flex-wrap: wrap; }
  .build-hero-points { grid-column: 1; }

  /* ── Sibling mode pages: subtract the tab bar too
   * Only #build-mode's inner .app-main was compensating for the fixed
   * bottom tab bar (see the calc() above). The other top-level
   * sections in index.html — #cards-mode, #play-mode, #waha-mode —
   * are siblings of #build-mode, not children, so they inherited
   * `.mode-page { height: 100% }` and ran their last ~56px of content
   * underneath the tab bar where it could be neither read nor tapped.
   * ID selectors are deliberate: `.mode-page { height: 100% }` lives in
   * mode-shell.css, which loads after this file, so a class-level rule here
   * would lose. `min-height: 0` is restated for self-documentation. */
  #cards-mode, #play-mode, #waha-mode { height: calc(100% - var(--m-tabbar-h) - var(--m-safe-bottom)); min-height: 0; }
  /* Cards mode intentionally has NO .cards-shell height rule here.
   * css/cards-mode.css (its 760px block) owns the narrow-viewport layout: it
   * makes #cards-mode itself the scroll container and lets .cards-shell run to
   * its natural `min-height: 100dvh`, so the stacked rail + preview scroll.
   * A competing `#cards-mode > .cards-shell { height: 100%; min-height: 0 }`
   * used to live here, and because (1,1,0) beats cards-mode.css's (0,1,0) it
   * won — pinning the shell to the section height while the rail still claimed
   * up to 50dvh, which squeezed .cards-preview-wrap (the actual datacards, and
   * therefore every image on them) down to nothing. The `height` rule on
   * #cards-mode above is enough: it keeps the section clear of the tab bar,
   * and cards-mode.css scrolls inside it. */
  body[data-mobile-panel="army"] .panel-left, body[data-mobile-panel="units"] .panel-center, body[data-mobile-panel="detail"] .panel-right { display: flex; flex: 1; }
  .panel-header, .panel-controls { flex-shrink: 0; }

  /* ── Compact panel chrome */
  /* The panel header just duplicates the active bottom-tab label
   * ("Army", "Units", "Details"). Remove it on mobile to reclaim ~44px
   * of vertical space. The Detail panel's back-arrow is no longer
   * needed — users navigate via the bottom tab bar. */
  .panel-header { display: none !important; }
  .panel-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto !important;
    max-height: none !important;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding: var(--sp-6) var(--sp-6) var(--sp-10);
    /* Override atmosphere.css `background-attachment: local`. With local
     * the hex-grid bg paints only behind the content area; when the
     * content is shorter than the panel (e.g. a short stratagem), the
     * portion below shows a solid panel-bg with no pattern, which
     * reads as "the box ends halfway down the screen." `scroll` (default)
     * tiles the bg across the whole panel-body so the pattern is
     * continuous to the tab bar. */
    background-attachment: scroll !important;
    /* Pin a solid panel-bg so the `#build-mode::before` fixed accent
     * bloom (theming-pass.css:46-48 — top-anchored radial gradient
     * that fades by 75% of viewport) can't show through. On desktop
     * the panel-right's own solid bg covers the bloom uniformly; on
     * mobile the bloom was bleeding through and making the upper half
     * visibly lighter than the lower half — which read as "the box
     * only covers the top half of the screen." */
    background-color: var(--panel-bg) !important;
  }

  /* Make the Army panel-body a flex column so the entry list can grow
   * to consume any remaining space below the setup/rules sections. With
   * no units added, the empty state ("No units added yet") expands to
   * fill the rest of the panel — same visual whether the army is empty
   * or stuffed. */
  .panel-left .panel-body { display: flex; flex-direction: column; }

  /* The army panel-footer toolbar (New/Save/Load/Import/Export/Undo) is
   * hidden on mobile — those actions are surfaced in the More sheet
   * (settings drawer's "Army" section) so the panel-body can fill the
   * entire visible area with no empty chrome below. */
  .panel-footer.army-toolbar { display: none !important; }
  .panel-controls { padding: var(--sp-4) var(--sp-6); gap: var(--sp-3); }

  /* Mobile: hide the expand toggle entirely — irrelevant on phones. */
  .panel-expand-btn { display: none !important; }

  /* Drag handles are useless on touch. */
  .resize-handle { display: none !important; }

  /* ── Bottom tab bar (mobile only) */
  .mobile-tabbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: calc(var(--m-tabbar-h) + var(--m-safe-bottom));
    padding-bottom: var(--m-safe-bottom);
    padding-left: var(--m-safe-left);
    padding-right: var(--m-safe-right);
    display: flex;
    align-items: stretch;
    background: #0a0a0a;
    border-top: 1px solid var(--border);
    z-index: 1200 !important;
  }
  .mobile-tabbar button {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--sp-1);
    background: transparent;
    color: var(--text-muted);
    border: none;
    font: inherit;
    padding: var(--sp-3) var(--sp-2) var(--sp-2);
    cursor: pointer;
    min-height: 44px;
    position: relative;
  }
  .mobile-tabbar button[aria-current="page"]::before {
    content: "";
    position: absolute;
    top: 0;
    left: 16%;
    right: 16%;
    height: 2px;
    background: var(--accent);
    border-radius: 0 0 2px 2px;
  }

  /* ── Mobile (single breakpoint, same as the rest of the app) */
  /* Play mode owns the whole screen: the bottom tab bar disappears (the
   * header's Exit button is the way out, and the hardware Back button is
   * still trapped to Build), and #play-mode reclaims the tab-bar height
   * that mobile.css's sibling rule normally subtracts. The scrolling
   * panels keep the home-indicator safe area as bottom padding. */
  body[data-mode="play"] .mobile-tabbar { display: none; }

  /* ── Mobile-only chrome */
  /* Sticky points pill that mobile-shell.js mounts at top of Army panel.
   * Always visible while user scrolls the Army body — gives running total. */
  .mobile-pts-pill {
    position: sticky;
    top: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    gap: var(--sp-5);
    padding: var(--sp-5) var(--sp-6);
    margin: calc(-1 * var(--sp-6)) calc(-1 * var(--sp-6)) var(--sp-6);
    background: linear-gradient(180deg, #1a1a20 0%, #16161b 100%);
    border-bottom: 1px solid var(--border);
    min-height: var(--m-pts-pill-h);
  }
  .mobile-pts-pill-faction {
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: var(--tr-widest);
    color: var(--text-muted);
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .mobile-pts-pill-spacer { flex: 1 1 auto; }
  .mobile-pts-pill-value { font-family: var(--font-display); font-size: var(--fs-base); font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; flex: 0 0 auto; }
  .mobile-pts-pill-value.over { color: #ff6b6b; }
  .mobile-pts-pill-bar { flex: 0 0 70px; height: 6px; background: var(--ov-3); border-radius: var(--radius-xs); overflow: hidden; }
  .mobile-pts-pill-bar-fill { height: 100%; width: 0%; background: var(--accent); transition: width 0.2s ease, background 0.2s ease; }
  .mobile-pts-pill-bar-fill.over { background: #ff5b5b; }
  .mobile-pts-pill-bar-fill.near { background: #ffa84a; }
  .mobile-pts-pill-units {
    position: static;
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    margin: 0;
    padding: var(--sp-3) var(--sp-5);
    gap: var(--sp-4);
    border: 1px solid var(--border);
    border-bottom-color: var(--border);
    border-radius: var(--radius);
    background: var(--ov-1);
  }

  /* The bar gives way first; the "N left" text never truncates. */
  .mobile-pts-pill-units .mobile-pts-pill-bar { flex: 1 1 24px; min-width: 16px; }
  .mobile-pts-pill-units .mobile-pts-pill-spacer { display: none; }
  .mobile-pts-pill-units .mobile-pts-pill-faction { flex: 0 0 auto; overflow: visible; text-transform: none; letter-spacing: 0; font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
  .mobile-pts-pill-units .mobile-pts-pill-faction.over { color: #ff6b6b; }

  /* Units panel copy: shares the view-toggle row (mobile-shell.js wraps
   * both in .mobile-units-head) and fills the blank right half of it. */
  .mobile-units-head { display: flex; align-items: center; gap: var(--sp-4); }
  .mobile-tabbar .mtab-icon { line-height: 1; pointer-events: none; }

  /* Match the desktop topbar mode-tab type style: Cinzel display font,
   * uppercase, generous letter-spacing. Tighter font-size + spacing for
   * mobile constraints. */
  .mobile-tabbar .mtab-label {
    font-family: var(--font-display);
    font-size: var(--fs-2xs);
    font-weight: 600;
    line-height: 1;
    letter-spacing: var(--tr-widest);
    text-transform: uppercase;
    pointer-events: none;
  }
  .mobile-tabbar .mtab-badge {
    position: absolute;
    top: var(--sp-2);
    right: calc(50% - var(--sp-9));
    min-width: 16px;
    height: 16px;
    padding: 0 var(--sp-2);
    border-radius: var(--radius);
    background: var(--accent);
    color: #0a0a0a;
    font-size: var(--fs-2xs);
    font-weight: 700;
    line-height: 16px;
    text-align: center;
    pointer-events: none;
  }
  .mobile-tabbar .mtab-badge:empty { display: none; }
  .mobile-units-head .reserves-view-toggle { margin: 0; flex: 0 0 auto; }
  .search-input { padding: var(--sp-5) var(--sp-6); min-height: 44px; }

  /* ── Unit grid: 2-col tablet, 1-col phone */
  .unit-grid { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; gap: var(--sp-5); }
  .unit-card-name, .unit-card-name-primary { font-size: var(--fs-md) !important; }
  .unit-card-pts, .unit-card-pts-badge { font-size: var(--fs-sm); padding: var(--sp-2) var(--sp-4); }
  #unit-detail-panel > .unit-detail-content, #unit-detail-panel > .unit-detail-empty { flex: 1 0 auto; min-height: 100%; }
  .stat-cell { padding: var(--sp-2) var(--sp-1); }
  .keyword-tag { font-size: var(--fs-2xs); padding: var(--sp-1) var(--sp-3); }

  /* .army-entry-stat is `align-items: baseline` (build-mode.css). With a
   * 44px-tall input in the Qty cell that drags the Pts/Total labels off
   * the optical centre, so re-centre — mobile-only, where the tall input
   * is. (0,2,0) beats build-mode.css's (0,1,0). */
  .army-entry .army-entry-stat { align-items: center; }

  /* Left at 32x32 on purpose. 32 clears the WCAG 2.2 AA 24x24 floor without
   * making accidental deletion easier on a list the user scrolls with their
   * thumb. A row now holds MORE than one button — .army-entry-remove plus any
   * App.hooks.armyEntryActions button (today the split control) — so
   * split-entry.css widens .army-entry-actions' gap at this width to keep the
   * constructive and destructive targets from sitting thumb-adjacent. */
  .army-entry button { min-height: 32px; min-width: 32px; }

  /* Phone: mobile.css already floors .army-entry button at 32x32 for WCAG 2.2
   * AA. Widen the gap so the constructive and destructive targets are not
   * thumb-adjacent. */
  .army-entry-actions { gap: var(--sp-3); }
  .army-toolbar .toolbar-status-row { font-size: var(--fs-xs); }
  .army-toolbar .toolbar-spacer { flex-basis: 100%; height: 0; }
  .army-toolbar .btn { flex: 1 1 30%; }
  .modal-footer .btn { flex: 1 1 auto; }
  .form-label { font-size: var(--fs-sm); margin-bottom: var(--sp-3); }
  .form-group { margin-bottom: var(--sp-7); }

  /* ── Toast container: keep above tab bar */
  .toast-container { bottom: calc(var(--m-tabbar-h) + var(--m-safe-bottom) + var(--sp-4)) !important; left: var(--sp-4) !important; right: var(--sp-4) !important; }
  .toast { font-size: var(--fs-md); padding: var(--sp-5) var(--sp-7); }

  /* ── Settings drawer (More sheet) — keep tab bar visible */
  /* Drawer root + scrim stop above the tab bar so the tab bar stays
   * fully visible and clickable. The tab bar's z-index is bumped above
   * the drawer so taps pass through cleanly. */
  .settings-drawer-root, .settings-drawer-scrim { bottom: calc(var(--m-tabbar-h) + var(--m-safe-bottom)) !important; }
  .settings-drawer {
    height: 100% !important;
    max-height: none !important;
    /* ── Mobile: full-screen drawer */
    width: 100vw;
    max-width: 100vw;
    border-left: none;
  }

  /* Drawer drops its scrim blur on mobile (matches .topbar / .modal-
   * backdrop perf rule earlier in this file). */
  .settings-drawer-scrim { -webkit-backdrop-filter: none !important; backdrop-filter: none !important; }

  /* ── Mobile: close button must be a real touch target */
  /* At <= 820px the drawer is `width: 100vw` (above), so the scrim is entirely
   * covered and tap-outside-to-dismiss does not exist — this X is the only way
   * to close the sheet from inside it. It computed to roughly 28x30 from
   * font-size + padding, under the WCAG 2.2 AA 24x24 floor in one axis and well
   * under Apple's 44x44. Scoped to 820px, where
   * mobile.css hides the topbar, i.e. wherever this drawer is the sole
   * navigation surface. Only min-* grows, so the header's `align-items: center`
   * keeps the glyph optically where it was; the header is the row that grows. */
  .settings-drawer-close { min-width: 44px; min-height: 44px; font-size: var(--fs-3xl); }
  .settings-drawer-body {
    /* Extra bottom padding so the last (often-danger) action sits
     * comfortably above the home indicator on iOS. */
    padding-bottom: max(48px, env(safe-area-inset-bottom));
  }

  /* Responsive: stack the rail above the preview on phone-layout viewports
   * (820 = the app-wide cutoff; the tab-bar layout starts there). */
  /* The stacked shell is taller than one viewport, but its ancestor
   * #cards-mode is a .mode-page, which mode-shell.css gives `overflow:
   * hidden` + a fixed height (and mobile.css pins to `height: 100%`). The
   * shell's overflow was therefore CLIPPED, not scrolled — the bottom of the
   * preview was unreachable. Make cards-mode itself the scroll container.
   * `#cards-mode.mode-page` is id+class, so it out-specifies mode-shell.css's
   * bare `.mode-page` no matter that mode-shell.css loads after
   * cards-mode.css — specificity, not order, is what wins this one.
   * The print block later in this file already forces `overflow: visible`
   * under `body.cards-printing`, so printing is unaffected. */
  #cards-mode.mode-page { overflow-y: auto; -webkit-overflow-scrolling: touch; }
  .signed-out-banner { top: var(--m-safe-top, 0px); padding-left: calc(var(--sp-7) + var(--m-safe-left, 0px)); padding-right: calc(var(--sp-7) + var(--m-safe-right, 0px)); }

  /* !important: topbar.css:492 pins body padding-top with !important. */
  body.has-signed-out-banner { padding-top: calc(var(--m-safe-top, 0px) + var(--sob-h)) !important; }
  .play-strat-row { align-items: flex-start; }
  body[data-mode="play"] #play-mode { height: 100%; }
  .play-panel {
    padding-bottom: calc(var(--sp-6) + env(safe-area-inset-bottom, 0px));
    padding: var(--sp-5);
  }
  .play-header { gap: var(--sp-4); padding: var(--sp-4) var(--sp-5); flex-wrap: wrap; }
  .play-title { flex-basis: 100%; }
  .play-cp { margin-right: auto; }
  .play-layout-toggle { display: none; }

  /* Switcher becomes a horizontal chip strip pinned above the sheet. */
  .play-layout { flex-direction: column; }
  .play-tabs { padding: 0 var(--sp-3); }
  .play-tab { padding: var(--sp-5) var(--sp-5); font-size: var(--fs-sm); }
  .play-switcher {
    flex: 0 0 auto;
    flex-direction: row;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    border-right: 0;
    border-bottom: 1px solid var(--border);
    padding: var(--sp-3) var(--sp-4);
    gap: var(--sp-3);
  }
  .play-switcher::-webkit-scrollbar { display: none; }
  .play-unit-chip { flex: 0 0 auto; width: auto; max-width: 52vw; padding: var(--sp-4) var(--sp-5); border-left: 1px solid var(--border); border-bottom: 3px solid transparent; }
  .play-unit-chip.is-on { border-left-color: var(--accent); border-bottom-color: var(--accent); }
  .play-unit-chip.is-leader { margin-left: 0; width: auto; }

  /* Horizontal strip: the separator sits on the leading edge instead. */
  .play-unit-chip[data-first-dead] { margin-top: 0; margin-left: var(--sp-4); border-top: 1px solid var(--border); border-left-style: dashed; }
  .play-card-list { grid-template-columns: 1fr; gap: var(--sp-5); }
  .play-strat-row .rule-item-right { max-width: 55%; }

  /* ── Phone: single column, results screen <-> reader screen */
  .waha-header { flex-wrap: wrap; gap: var(--sp-4); padding: var(--sp-4) var(--sp-5); }
  .waha-wordmark { order: 1; flex: 1 1 auto; }
  .waha-exit { order: 2; }
  .waha-exit-long { display: none; }
  .waha-exit-short { display: inline; }
  .waha-q { order: 3; flex: 1 1 100%; height: 40px; font-size: var(--fs-lg); }
  .waha-faction { order: 4; flex: 1 1 100%; width: auto; max-width: none; height: 38px; font-size: var(--fs-base); }
  .waha-tabs { padding: 0 var(--sp-3); }
  .waha-tab { padding: var(--sp-5) var(--sp-5); font-size: var(--fs-sm); }
  .waha-layout { flex-direction: column; }
  .waha-results { flex: 1 1 auto; border-right: 0; padding: var(--sp-5) var(--sp-5) calc(var(--sp-6) + env(safe-area-inset-bottom, 0px)); }
  .waha-reader { flex: 1 1 auto; padding: 0 var(--sp-5) calc(var(--sp-6) + env(safe-area-inset-bottom, 0px)); }
  .waha-root[data-screen="results"] .waha-reader,
  .waha-root[data-screen="reader"] .waha-results,
  .waha-root[data-screen="reader"] .waha-header,
  .waha-root[data-screen="reader"] .waha-tabs {
    display: none;
  }
  .waha-reader-bar { position: sticky; top: 0; z-index: 2; margin: 0 calc(-1 * var(--sp-5)) var(--sp-5); padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--border); background: var(--panel-bg); }
  .waha-root[data-screen="reader"] .waha-reader { animation: waha-slide 180ms ease-out; }
  .waha-back {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    height: 34px;
    padding: 0 var(--sp-7);
    border: 1px solid var(--border-light);
    border-radius: var(--radius);
    background: var(--card-bg);
    color: var(--text);
    font-size: var(--fs-md);
    font-weight: 600;
    cursor: pointer;
  }
  .waha-back:hover { background: var(--card-hover); border-color: var(--accent); }
  .waha-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .waha-enh-for { flex-direction: column; align-items: stretch; gap: var(--sp-2); }
  .waha-enh-select { max-width: none; height: 38px; font-size: var(--fs-base); }
  .unit-detail-content .weapons-section-banner { font-size: var(--fs-xs); letter-spacing: var(--tr-widest); }

  /* ── Touch sizing */
  /* The −/+ steppers were 20x20 with a 2px gap, so on a phone the two targets
   * sat ~22px apart centre-to-centre and a thumb aimed at "remove a model"
   * routinely landed on "add" instead. A transparent expanded hit area is NOT
   * usable here: the two buttons are immediate siblings, so overlapping
   * overlays would make the mis-tap worse, not better. Instead the pill grows
   * to real 44x44 (Apple HIG) targets and the count between them widens to
   * ~26px of dead space, which is what actually separates the two actions.
   * Width check: 44 + 26 + 44 + (2 x 4px gap) + (2 x 4px pad) = ~130px. Inside
   * a 320px viewport the detail pane leaves ~276px for .wgp-row, so the
   * stepper (flex-shrink: 0) still leaves ~138px for the label, which has
   * min-width: 0 and wraps. No horizontal overflow at any supported width. */
  .wgp-row { padding: var(--sp-2) 0; gap: var(--sp-5); }
  .wgp-stepper { gap: var(--sp-2); padding: var(--sp-1) var(--sp-2); }
  .wgp-btn { width: 44px; height: 44px; font-size: var(--fs-xl); }
  .wgp-count { min-width: 26px; font-size: var(--fs-base); }
  #glossary-pop, #glossary-pop.is-sheet {
    left: 0 !important;
    right: 0 !important;
    top: auto !important;
    bottom: 0 !important;
    width: 100%;
    max-width: none;
    max-height: 60vh;
    z-index: 1250;
    /* above .mobile-tabbar (1200), below full-screen modals (1300) */
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    border-bottom: 0;
    padding-bottom: env(safe-area-inset-bottom, 0);
  }
  .glossary-pop-close { width: 36px; height: 36px; }
  .glossary-pop-body { font-size: var(--fs-base); }
}

@media (max-width: 640px) {
  /* ── mobile tightening
   * 640, not the 820 phone-layout cutoff, on purpose: these are control-size
   * squeezes for genuinely narrow screens, not a layout switch. */
  .reserves-view-btn { padding: var(--sp-3) var(--sp-5); font-size: var(--fs-sm); }
  .reserves-view-label { display: none; }

  /* icon + count only on phones */
  .stockpile-step { width: 30px; height: 30px; font-size: var(--fs-xl); }
  .stockpile-qty { min-width: 30px; font-size: var(--fs-base); }
}

@media (max-width: 480px) {
  /* Narrow viewports: anchor to the top so the keyboard popping up doesn't
   * push the modal off-screen, and tighten padding to give the form room. */
  .modal-backdrop { align-items: flex-start; padding: var(--sp-6); }
}

@media (max-width: 820px) {
  /* ── Modals: full-screen with sticky header/footer */
  .modal-backdrop {
    padding: 0 !important;
    align-items: stretch;
    justify-content: stretch;
    /* The mobile tab bar is z-index:1200 (bumped to sit above the settings
     * drawer). A full-screen modal is z-index:1000 by default, so the fixed
     * tab bar overlaid the modal's bottom — hiding the .modal-footer and its
     * submit button (e.g. "Send report"). Lift modals above the tab bar; the
     * full-screen modal covers it cleanly while open. */
    z-index: 1300 !important;
  }
}

@media (max-width: 480px) {
  .topbar-offline-pip { padding: 0 var(--sp-4); }

  /* Collapses "Offline · 3 queued" to "Offline · 3", and "3 queued" to
   * "3" — the count span stays, only the word goes. */
  .topbar-offline-pip .topbar-offline-word { display: none; }

  .unit-grid { grid-template-columns: 1fr !important; gap: var(--sp-4); }
}

@media (max-width: 380px) {
  .topbar-inner { gap: var(--sp-3); padding: 0 var(--sp-4); }
  .topbar-modes { padding: var(--sp-1); gap: 1px; }

  /* ─── Very narrow (≤380px) — phones in portrait. The mode tab group
   * was the last thing fighting for room; ensure it stays inline rather
   * than wrapping under the brand. */
  .topbar-mode-tab { padding: 0 var(--sp-2); font-size: var(--fs-2xs); }
  .panel-body { padding: var(--sp-5) var(--sp-5) var(--sp-10); }

  /* ── Smaller phones: tighten further */
  .mobile-tabbar .mtab-label { font-size: var(--fs-2xs); }
  .army-toolbar .btn { flex-basis: 45%; }
}

@media (max-width: 820px) and (prefers-reduced-motion: reduce) {
  .filter-chip:active { transform: none; }
}
