/* ============================================================
   SmartOS Mobile Patterns — mobile.css
   Loaded on every page alongside theme.css. Desktop/tablet
   (≥ 640px) is unaffected unless a rule is explicitly scoped
   below the mobile breakpoint.

   Convention:
     <  640px  → mobile: card-list transforms, bottom tab bar, sheets.
     640–1024  → tablet: keep desktop density.
     ≥ 1024px  → desktop.

   Sections:
     §1  Shared utilities                       — PR 3 (placeholder)
     §2  Mobile top bar                         — PR 2 ✓
     §3  Bottom tab bar                         — PR 2 ✓
     §4  Hamburger drawer                       — PR 2 ✓
     §5  Card-list pattern                      — PR 3 (placeholder)
     §6  Bottom sheet primitive                 — PR 6 (placeholder)
     §7  Mobile shell offsets                   — PR 2 ✓
   ============================================================ */

/* ── Shared: hide shell chrome at desktop ──────────────────── */
.mtopbar,
.mtabbar,
.mdrawer,
.mdrawer-overlay { display: none; }

/* ── §1 Shared utilities ───────────────────────────────────── */

/* Horizontal snap-scrolling stat/chip row. Behaves identically on desktop
   (no change) but when the container overflows on mobile, items scroll with
   snap alignment and the scrollbar is hidden. */
.stats-scroll,
.chip-scroll {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x mandatory;
}
.stats-scroll::-webkit-scrollbar,
.chip-scroll::-webkit-scrollbar { display: none; }
.stats-scroll > *,
.chip-scroll > * {
  scroll-snap-align: start;
  flex: 0 0 auto;
}
.stats-scroll > * { min-width: 160px; }
.chip-scroll { flex-wrap: nowrap; }
.chip-scroll > * { white-space: nowrap; }

/* ── §2 Mobile top bar ─────────────────────────────────────── */
/* Fixed at top of viewport, dark chrome, left button + title + actions slot.
   Rendered by nav.js as <header class="mtopbar">. */

/* ── §3 Bottom tab bar ─────────────────────────────────────── */
/* Fixed at bottom, 5 tabs, dark chrome. Rendered by nav.js. */

/* ── §4 Hamburger drawer ───────────────────────────────────── */
/* Slides in from left, 85% width, dimmed overlay. */

/* ── §5 Card-list pattern ──────────────────────────────────── */
/* Tables opt in by adding class `mobile-card-table`. At <640 each <tr>
   renders as a stacked card (border, radius, padding). Cells with the
   existing `.hide-mobile` class are hidden as before; remaining cells
   stack with their text-align reset. Add `.primary-cell` to the cell
   that should render larger (campaign name, user email, etc.). Put row
   actions in a `<td class="actions-cell">` — they wrap onto a new line. */
@media (max-width: 639px) {
  .mobile-card-table,
  .mobile-card-table thead,
  .mobile-card-table tbody,
  .mobile-card-table tr,
  .mobile-card-table td,
  .mobile-card-table th { display: block; }
  .mobile-card-table thead { display: none; }

  .mobile-card-table { border-collapse: separate; border-spacing: 0; }

  .mobile-card-table tr {
    background: var(--canvas-raised);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-md);
    padding: 14px 16px;
    margin-bottom: 10px;
    box-shadow: 0 1px 2px rgba(0,0,0,0.03);
  }
  .mobile-card-table tr:hover { background: var(--canvas-raised); }

  .mobile-card-table td {
    padding: 3px 0 !important;
    text-align: left !important;
    white-space: normal !important;
    max-width: none !important;
    border-bottom: none !important;
    font-size: 13.5px;
  }
  .mobile-card-table td:empty { display: none; }

  .mobile-card-table td.primary-cell {
    font-family: var(--font-display);
    font-size: 15px;
    font-weight: 600;
    color: var(--text-primary);
    margin-bottom: 4px;
  }

  /* Row actions: always visible on mobile, stacked below the summary with a
     top separator. Wrappable, touch-friendly. */
  .mobile-card-table td.actions,
  .mobile-card-table td.actions-cell {
    margin-top: 10px;
    padding-top: 10px !important;
    border-top: 1px solid var(--hairline) !important;
    display: flex !important;
    flex-wrap: wrap;
    gap: 6px;
  }
  .mobile-card-table td.actions button,
  .mobile-card-table td.actions-cell button,
  .mobile-card-table td.actions .so-btn,
  .mobile-card-table td.actions-cell .so-btn {
    margin: 0 !important;
    min-height: 36px;
    font-size: 12.5px;
  }

  /* The whole card is tappable — give it a clear pressed/hover state. */
  .mobile-card-table tbody tr { cursor: pointer; }
  .mobile-card-table tbody tr:active { background: var(--canvas-sunken); }
}

/* ── §5c Screen picker polish (PR 5) ──────────────────────── */
@media (max-width: 639px) {

  /* Basket bar: ensure it clears the mobile tab bar (already applied in §7
     for flush pages, but reinforce here for specificity). */
  body.so-mobile-shell.mobile-shell-flush #basket {
    padding-bottom: calc(10px + var(--mobile-tabbar-h) + env(safe-area-inset-bottom)) !important;
  }

  /* Map/List segmented toggle — show the hidden #tb-map-toggle button and
     place it prominently in the card header area. */
  #tb-map-toggle {
    display: inline-flex !important;
    align-items: center;
    gap: 4px;
    background: var(--accent);
    border-color: transparent;
    color: #fff;
    min-height: 36px;
    padding: 0 14px;
    border-radius: 8px;
    font-size: 12px;
    font-weight: 600;
    order: -1; /* float before other tool buttons */
  }
  #tb-map-toggle.map-active {
    background: var(--surface2, #EEEDE6);
    color: var(--text, #18211D);
    border: 1px solid var(--border, #E4E2D8);
  }

  /* Touch-action: let the card list pan vertically without fighting the map */
  #card-grid { touch-action: pan-y; }
  #map       { touch-action: none; } /* let Leaflet own all gestures */

  /* Card-add button: bump to 44×44 minimum tap target */
  .card-add {
    width: 44px !important;
    height: 44px !important;
    font-size: 20px !important;
  }

  /* Popup "Add to basket" button: ≥40px tall touch target */
  .pop-add {
    padding: 10px 16px !important;
    font-size: 13px !important;
    min-height: 40px;
    display: inline-flex;
    align-items: center;
  }

  /* Toolbar filter buttons: slightly taller for touch */
  .tool-btn, .filter-dd-btn {
    min-height: 36px;
    display: inline-flex;
    align-items: center;
  }

  /* Basket dropdown: already positioned above basket bar in §7; ensure it
     also clears the tab bar on mobile. */
  #basket-dropdown {
    bottom: calc(56px + var(--mobile-tabbar-h) + env(safe-area-inset-bottom)) !important;
  }
}

/* ── §6 Bottom sheet primitive ─────────────────────────────── */
/* On mobile, .modal-bg is transformed into a bottom sheet:
   - Backdrop dims the top portion; .modal slides up from the bottom.
   - A drag handle (::before on .modal) signals swipe-down to dismiss.
   - JS in mobile.js adds swipe-down and focus-trap behaviour.
   Desktop (≥640px) is entirely unaffected. */
@media (max-width: 639px) {
  .modal-bg {
    align-items: flex-end !important;
    padding: 0 !important;
  }
  .modal-bg .modal {
    width: 100% !important;
    max-width: 100% !important;
    max-height: 92dvh;
    overflow-y: auto;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0 !important;
    padding: 20px 20px calc(20px + env(safe-area-inset-bottom)) !important;
    /* Sheet slides up when .show is added */
    transform: translateY(100%);
    transition: transform .3s cubic-bezier(.32,.72,0,1);
  }
  .modal-bg.show .modal {
    transform: translateY(0);
  }
  /* Drag handle */
  .modal-bg .modal::before {
    content: '';
    display: block;
    width: 32px;
    height: 4px;
    background: var(--hairline-strong);
    border-radius: 2px;
    margin: 0 auto 16px;
  }
  /* Inputs ≥16px to prevent iOS zoom */
  .modal-bg .modal input,
  .modal-bg .modal select,
  .modal-bg .modal textarea { font-size: 16px !important; }
}

/* ── §5b Wizard polish (PR 4) ──────────────────────────────── */
@media (max-width: 639px) {

  /* ── Progress bar: replace wrapping pill row with "Step N of M" + bar ── */
  body.so-mobile-shell .progress {
    flex-wrap: nowrap;
    overflow-x: hidden;
    gap: 0;
    padding: 10px 16px 8px;
    flex-direction: column;
    align-items: stretch;
  }
  /* Hide individual pills on mobile; show compact label instead */
  body.so-mobile-shell .progress .pill { display: none; }
  body.so-mobile-shell .progress::before {
    content: attr(data-step-label);
    display: block;
    font-family: var(--font-display);
    font-size: 12px;
    font-weight: 600;
    color: var(--text-secondary);
    margin-bottom: 6px;
  }
  body.so-mobile-shell .progress::after {
    content: '';
    display: block;
    height: 2px;
    border-radius: 1px;
    background: var(--hairline-strong);
    position: relative;
  }
  /* Progress fill via CSS custom property set by JS */
  body.so-mobile-shell .progress {
    --progress-pct: 0%;
  }
  body.so-mobile-shell .progress::after {
    background: linear-gradient(
      to right,
      var(--accent) var(--progress-pct),
      var(--hairline-strong) var(--progress-pct)
    );
  }

  /* ── Wizard footer: full-width primary action ── */
  body.so-mobile-shell footer.wizard-footer {
    flex-direction: column-reverse;
    gap: 8px;
    padding: 10px 16px;
    padding-bottom: calc(10px + var(--mobile-tabbar-h) + env(safe-area-inset-bottom));
  }
  body.so-mobile-shell footer.wizard-footer .footer-actions {
    width: 100%;
    flex-direction: row-reverse;
    gap: 8px;
  }
  body.so-mobile-shell footer.wizard-footer button.primary {
    flex: 1;
    min-height: 48px;
    font-size: 15px;
  }
  body.so-mobile-shell footer.wizard-footer button.ghost {
    min-height: 48px;
    padding: 10px 16px;
  }
  body.so-mobile-shell footer.wizard-footer .footer-info {
    width: 100%;
    text-align: center;
  }

  /* ── Pricing table (step 5): each line item as a card ── */
  body.so-mobile-shell table.lineitems,
  body.so-mobile-shell table.lineitems thead,
  body.so-mobile-shell table.lineitems tbody,
  body.so-mobile-shell table.lineitems tr,
  body.so-mobile-shell table.lineitems td,
  body.so-mobile-shell table.lineitems th { display: block; }
  body.so-mobile-shell table.lineitems thead { display: none; }
  body.so-mobile-shell table.lineitems tbody tr {
    background: var(--canvas-raised);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-sm);
    padding: 12px;
    margin-bottom: 8px;
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 6px 10px;
    align-items: center;
  }
  /* Label cell spans full width, inputs are right-aligned */
  body.so-mobile-shell table.lineitems td:first-child { grid-column: 1 / -1; }
  body.so-mobile-shell table.lineitems td.num { text-align: right; font-weight: 700; }
  body.so-mobile-shell table.lineitems td input { font-size: 16px; } /* ≥16px stops iOS zoom */
  body.so-mobile-shell table.lineitems td:last-child { grid-column: 1 / -1; text-align: right; }
  /* Field labels via data-label attribute */
  body.so-mobile-shell table.lineitems td[data-label]::before {
    content: attr(data-label);
    display: block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: var(--text-tertiary);
    font-weight: 700;
    margin-bottom: 4px;
    font-family: var(--font-display);
  }

  /* ── All wizard inputs ≥16px to block iOS zoom-on-focus ── */
  body.so-mobile-shell .field input,
  body.so-mobile-shell .field textarea,
  body.so-mobile-shell .field select { font-size: 16px; }

  /* ── Screens step (Step 4 embedded iframe) ── */
  /* The iframe height must fill remaining viewport after: mobile-topbar +
     progress bar + embed-bar + sticky footer + tab bar + safe areas. */
  body.so-mobile-shell #step-screens {
    height: calc(
      100dvh
      - var(--mobile-topbar-h)
      - env(safe-area-inset-top)
      - 48px   /* compact progress bar */
      - 20px   /* main padding top */
      - 120px  /* wizard footer (incl. tab bar safe area) */
    ) !important;
  }

  /* Embed bar: wrap onto multiple lines, hide the verbose hint text */
  body.so-mobile-shell .embed-bar {
    flex-wrap: wrap;
    gap: 8px 12px;
    padding: 8px 12px;
  }
  body.so-mobile-shell .embed-bar > [style*="flex:1"] { display: none; }
  body.so-mobile-shell .embed-bar .stat:last-of-type { display: none; } /* hide "Use filter & map…" hint */
  body.so-mobile-shell .embed-bar select { font-size: 16px; max-width: 100%; }
  body.so-mobile-shell .embed-bar .stat { font-size: 13px; }

  /* ── File drop-zone: "Choose file" on mobile ── */
  body.so-mobile-shell .logo-drop .drop-hint-desktop { display: none; }
  body.so-mobile-shell .logo-drop .drop-hint-mobile  { display: block; }
  .logo-drop .drop-hint-mobile { display: none; }
}

/* ── §7 Mobile shell offsets ───────────────────────────────── */

@media (max-width: 639px) {
  /* Hide the desktop sidebar & per-page desktop top bars on mobile. */
  body.so-mobile-shell .so-sidebar,
  body.so-mobile-shell .so-topbar,
  body.so-mobile-shell .desktop-header { display: none !important; }

  /* ── Flex-column shell: eliminates iOS Safari position:fixed hover bug ──
     Instead of padding + position:fixed for the top/bottom bars we make the
     mobile body a viewport-anchored flex column. The bars are normal flex
     items (position:relative); only the drawer overlay stays fixed.
     nav.js appends .mtopbar and .mtabbar AFTER .so-canvas, so we use CSS
     `order` to resequence them: topbar=-1, canvas=0, tabbar=1.             */

  body.so-mobile-shell {
    /* Anchor to viewport — avoids iOS dvh resize-on-scroll issues.         */
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    /* Flex column: topbar · content · tabbar                               */
    display: flex !important;
    flex-direction: column;
    /* No padding needed — flex items handle their own safe-area offsets.   */
    padding: 0 !important;
    overflow: hidden;
    box-sizing: border-box;
    max-width: 100vw;
  }
  /* html sits behind the fixed body — colour it dark so any sub-pixel gap
     at the bottom safe-area zone shows the bar colour, not white.           */
  html { overflow-x: hidden; background: var(--ink-900); }

  /* Override any page-level height/overflow that assumed body was block.   */
  body.so-mobile-shell.so-shell,
  body.so-mobile-shell .so-shell { display: flex; min-height: unset; }

  /* Top bar: first slot (order: -1 so it precedes .so-canvas in the DOM). */
  body.so-mobile-shell > .mtopbar {
    position: relative !important;
    top: auto !important; left: auto !important; right: auto !important;
    order: -1;
    flex-shrink: 0;
    /* height already set by §top-bar rule; safe-area padding is there too. */
  }

  /* Content area: grows to fill whatever the bars leave.                   */
  body.so-mobile-shell > .so-canvas {
    order: 0;
    flex: 1 1 0 !important;
    height: auto !important;       /* flex determines height, not 100%      */
    min-height: 0 !important;
    overflow: hidden;
  }

  /* Flush pages (wizard, screen-picker) don't use .so-canvas — their root
     content div becomes the flex:1 child automatically. Give it a name-
     agnostic rule via `:not(.mtopbar):not(.mtabbar)`:                      */
  body.so-mobile-shell.mobile-shell-flush > :not(.mtopbar):not(.mtabbar):not(.mdrawer):not(.mdrawer-overlay) {
    flex: 1 1 0 !important;
    min-height: 0 !important;
    overflow: hidden;
  }

  /* Tab bar: last slot.                                                     */
  body.so-mobile-shell > .mtabbar {
    position: relative !important;
    bottom: auto !important; left: auto !important; right: auto !important;
    order: 1;
    flex-shrink: 0;
    /* Ensure background fills the home-indicator zone. The height rule from
       §tab-bar adds env(safe-area-inset-bottom) to the height, but guard
       with a matching padding so the colour always covers the full zone.    */
    padding-bottom: max(env(safe-area-inset-bottom), 0px) !important;
  }

  /* Drawer and overlay are full-screen overlays — keep them fixed.         */
  body.so-mobile-shell > .mdrawer,
  body.so-mobile-shell > .mdrawer-overlay { position: fixed !important; }

  /* Pages with an internal flex-chain layout (index.html: body→canvas→
     #main-layout→…) still need the canvas flex:1 (handled above) and the
     #tab-analyse block override.                                            */
  body.so-mobile-shell #tab-analyse.active { display: block; }

  /* Guard against any child that exceeds the viewport width. */
  body.so-mobile-shell .so-canvas,
  body.so-mobile-shell main,
  body.so-mobile-shell .step { max-width: 100%; }
  body.so-mobile-shell img,
  body.so-mobile-shell video,
  body.so-mobile-shell canvas,
  body.so-mobile-shell iframe { max-width: 100%; }

  /* ── Mobile top bar ──────────────────────────────────────── */
  body.so-mobile-shell .mtopbar {
    display: flex;
    position: fixed;
    top: 0; left: 0; right: 0;
    height: calc(var(--mobile-topbar-h) + env(safe-area-inset-top));
    padding-top: env(safe-area-inset-top);
    padding-left: 4px;
    padding-right: 4px;
    background: var(--ink-900);
    color: #fff;
    align-items: center;
    gap: 4px;
    z-index: 900;
    border-bottom: 1px solid rgba(255,255,255,0.06);
  }
  .mtopbar-btn {
    width: 44px; height: 44px;
    flex-shrink: 0;
    display: inline-flex; align-items: center; justify-content: center;
    background: transparent; border: none; cursor: pointer;
    color: #fff; text-decoration: none;
    border-radius: 8px;
    padding: 0;
  }
  .mtopbar-btn:hover { background: rgba(255,255,255,0.08); }
  .mtopbar-btn svg  { width: 22px; height: 22px; }

  .mtopbar-title {
    flex: 1; min-width: 0;
    margin: 0;
    font-family: var(--font-display);
    font-size: 17px;
    font-weight: 600;
    text-align: center;
    color: #fff;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding: 0 8px;
  }
  .mtopbar-actions {
    display: flex; align-items: center; gap: 2px;
    flex-shrink: 0;
    min-width: 44px; /* balance the left button so title stays centred */
    justify-content: flex-end;
  }
  .mtopbar-actions .mtopbar-btn { color: #fff; }

  /* ── Bottom tab bar ──────────────────────────────────────── */
  body.so-mobile-shell .mtabbar {
    display: flex;
    position: fixed;
    bottom: 0; left: 0; right: 0;
    height: calc(var(--mobile-tabbar-h) + env(safe-area-inset-bottom));
    padding-bottom: env(safe-area-inset-bottom);
    background: var(--ink-900);
    z-index: 900;
    border-top: 1px solid rgba(255,255,255,0.06);
  }
  .mtab {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: 6px 2px 6px;
    background: transparent;
    border: none;
    cursor: pointer;
    color: var(--ink-400);
    text-decoration: none;
    font-family: var(--font-display);
    position: relative;
    min-height: 44px;
  }
  .mtab .mtab-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px; height: 22px;
  }
  .mtab .mtab-icon svg { width: 22px; height: 22px; }
  .mtab .mtab-label {
    font-size: 10.5px;
    font-weight: 500;
    line-height: 1;
    letter-spacing: 0.02em;
  }
  .mtab.active {
    color: #fff;
  }
  .mtab.active::before {
    content: '';
    position: absolute;
    top: 0; left: 50%;
    transform: translateX(-50%);
    width: 28px; height: 2px;
    background: var(--accent);
    border-radius: 0 0 2px 2px;
  }
  .mtab:active { background: rgba(255,255,255,0.06); }

  /* ── Hamburger drawer ────────────────────────────────────── */
  body.so-mobile-shell .mdrawer-overlay {
    display: block;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.45);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s ease;
    z-index: 950;
  }
  body.so-mobile-shell.mdrawer-open .mdrawer-overlay {
    opacity: 1;
    pointer-events: auto;
  }

  body.so-mobile-shell .mdrawer {
    display: flex;
    flex-direction: column;
    position: fixed;
    top: 0; bottom: 0; left: 0;
    width: 85%;
    max-width: 340px;
    background: var(--ink-900);
    color: #fff;
    z-index: 960;
    transform: translateX(-100%);
    transition: transform 0.22s ease;
    padding-top: env(safe-area-inset-top);
    padding-bottom: env(safe-area-inset-bottom);
    box-shadow: 0 0 40px rgba(0,0,0,0.35);
    overflow-y: auto;
  }
  body.so-mobile-shell.mdrawer-open .mdrawer {
    transform: translateX(0);
  }

  .mdrawer-header {
    display: flex;
    align-items: center;
    padding: 16px 12px 16px 20px;
    border-bottom: 1px solid rgba(255,255,255,0.08);
    gap: 12px;
  }
  .mdrawer-user { flex: 1; min-width: 0; }
  .mdrawer-user-name {
    font-family: var(--font-display);
    font-size: 14px;
    font-weight: 600;
    color: #fff;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .mdrawer-user-org {
    font-size: 12px;
    color: var(--ink-400);
    margin-top: 2px;
  }
  .mdrawer-close {
    width: 44px; height: 44px;
    flex-shrink: 0;
    background: transparent; border: none; cursor: pointer;
    color: #fff;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 8px;
  }
  .mdrawer-close:hover { background: rgba(255,255,255,0.08); }
  .mdrawer-close svg { width: 22px; height: 22px; }

  .mdrawer-nav { padding: 8px; }
  .mdrawer-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    min-height: 48px;
    color: var(--ink-300);
    text-decoration: none;
    border-radius: 8px;
    font-family: var(--font-display);
    font-size: 15px;
    font-weight: 500;
  }
  .mdrawer-item:hover,
  .mdrawer-item:focus-visible { background: rgba(255,255,255,0.08); color: #fff; }
  .mdrawer-item.active {
    background: rgba(255,255,255,0.08);
    color: #fff;
    box-shadow: inset 2px 0 0 var(--accent);
  }
  .mdrawer-item .mdrawer-icon {
    width: 20px; height: 20px;
    display: inline-flex; align-items: center; justify-content: center;
    flex-shrink: 0;
  }
  .mdrawer-item .mdrawer-icon svg { width: 20px; height: 20px; }
  .mdrawer-sep {
    border: none;
    border-top: 1px solid rgba(255,255,255,0.08);
    margin: 8px 6px;
  }
  .mdrawer-signout { color: var(--ink-400); }

  /* ── Per-page overrides: pages with 100dvh flex layouts ──── */
  /* Flush layouts (wizard, screen-picker) manage their own top/bottom
     offsets because body is a flex viewport. */
  body.so-mobile-shell.mobile-shell-flush {
    padding-top: 0;
    padding-bottom: 0;
  }

  /* Wizard / package-wizard — push progress row below the top bar; pad
     sticky footer above the tab bar. */
  body.so-mobile-shell.mobile-shell-flush .progress {
    padding-top: calc(10px + var(--mobile-topbar-h) + env(safe-area-inset-top));
  }
  body.so-mobile-shell.mobile-shell-flush footer.wizard-footer {
    padding-bottom: calc(10px + var(--mobile-tabbar-h) + env(safe-area-inset-bottom));
  }

  /* Screen-picker — card header clears the top bar; basket clears tab bar. */
  body.so-mobile-shell.mobile-shell-flush #card-header {
    padding-top: calc(var(--mobile-topbar-h) + env(safe-area-inset-top)) !important;
  }
  body.so-mobile-shell.mobile-shell-flush #basket {
    padding-bottom: calc(10px + var(--mobile-tabbar-h) + env(safe-area-inset-bottom));
  }
  body.so-mobile-shell.mobile-shell-flush #basket-dropdown {
    bottom: calc(56px + var(--mobile-tabbar-h) + env(safe-area-inset-bottom)) !important;
  }

  /* Plan shared view (plan.html): it has its own .so-light-header already
     sticky at the top. nav.js doesn't inject the shell there (public page),
     but if someone wires it later, this keeps the light header visible. */

  /* Keep any index.html FAB above the tab bar. */
  body.so-mobile-shell .fab,
  body.so-mobile-shell [class*="fab-"] {
    bottom: calc(16px + var(--mobile-tabbar-h) + env(safe-area-inset-bottom)) !important;
  }

  /* Estate-map FABs (index.html: #fab-poi, #fab-loc, #fab-plan) stack above
     the tab bar and float above it in the stacking order. */
  /* Force fixed positioning so the FABs anchor to the viewport rather than
     their (absolutely-positioned) map container, which on mobile extends
     behind the tab bar. */
  body.so-mobile-shell .map-fab {
    position: fixed !important;
    z-index: 910 !important;
    right: calc(14px + env(safe-area-inset-right)) !important;
  }
  body.so-mobile-shell #fab-poi {
    bottom: calc(14px + var(--mobile-tabbar-h) + env(safe-area-inset-bottom)) !important;
  }
  body.so-mobile-shell #fab-loc {
    bottom: calc(74px + var(--mobile-tabbar-h) + env(safe-area-inset-bottom)) !important;
  }
  body.so-mobile-shell #fab-plan {
    bottom: calc(134px + var(--mobile-tabbar-h) + env(safe-area-inset-bottom)) !important;
  }
  /* The map-control popover anchors above the stacked FABs. */
  body.so-mobile-shell #map-ctrl {
    bottom: calc(14px + var(--mobile-tabbar-h) + env(safe-area-inset-bottom)) !important;
    left: 12px !important;
    right: calc(72px + env(safe-area-inset-right)) !important; /* clear the FAB column */
    max-height: calc(100dvh - var(--mobile-topbar-h) - env(safe-area-inset-top)
                     - var(--mobile-tabbar-h) - env(safe-area-inset-bottom) - 28px) !important;
  }

  /* Leaflet controls (zoom, attribution) sit inside the map element which,
     on some iOS Safari states, can extend beneath the fixed top bar. Push
     the top-left controls down so they always clear the mobile top bar. */
  body.so-mobile-shell .leaflet-top {
    top: calc(var(--mobile-topbar-h) + env(safe-area-inset-top) + 8px) !important;
  }
  body.so-mobile-shell .leaflet-bottom {
    bottom: calc(var(--mobile-tabbar-h) + env(safe-area-inset-bottom) + 4px) !important;
  }
  /* Shift the bottom-right attribution clear of the FAB column so it doesn't
     sit on top of the Plan Builder / Location / POI buttons. */
  body.so-mobile-shell .leaflet-bottom.leaflet-right {
    right: calc(72px + env(safe-area-inset-right)) !important;
  }

  /* Toast host (utils.js) must clear the bottom tab bar on mobile. */
  body.so-mobile-shell #so-toast-host {
    bottom: calc(16px + var(--mobile-tabbar-h) + env(safe-area-inset-bottom)) !important;
  }
}
