/* ============================================================================
   LAGO Operations Portal — Mobile Shell (Phase 0)
   ----------------------------------------------------------------------------
   Shared mobile primitives for the manager surface ONLY:
     index.html, reporting.html, purchasing.html, engineering.html,
     security.html, superadmin.html, database.html.

   Academy (training-*.html) and POS (order*.html) must NEVER load this file.

   Contract:
     - Every rule below is gated by EITHER
         (a) @media (max-width: 768px) { ... }, OR
         (b) selector starts with `body.has-mobile-shell`.
       Above 768px (desktop) this file must be a no-op.
     - All visible primitives use the gold accent (#C49A3A) introduced by
       commits ce70c74 / abf1c28 to stay visually consistent with the rest
       of the redesign.
     - No external font/icon network calls — icons are inline SVG, drawn by
       admin-mobile.js when the bottom nav / FAB markup is inserted.

   Breakpoint: 768px (matches the dominant breakpoint already in styles.css).
   ========================================================================== */

:root {
  /* Mobile shell tokens. Safe to read on desktop; nothing references them
     above 768px so the values cost nothing. */
  --m-nav-h: 64px;              /* Bottom-nav height before safe-area */
  --m-fab-size: 56px;
  --m-fab-gap: 14px;            /* Distance between FAB and bottom-nav */
  --m-tap-min: 44px;            /* iOS / Material minimum tap target */
  --m-gold: #C49A3A;
  --m-gold-soft: rgba(196, 154, 58, 0.12);
  --m-gold-soft-2: rgba(196, 154, 58, 0.22);
  --m-safe-bottom: env(safe-area-inset-bottom, 0px);
  --m-page-pad: 16px;
  --m-page-gap: 18px;
}

/* ─── Bottom nav ────────────────────────────────────────────────────────── */
/* HTML pattern (inserted once per page, sibling of #app-main):
     <nav class="mobile-bottom-nav" role="navigation">
       <button class="mobile-bottom-nav__item" data-panel="home">…</button>
       …
     </nav>
   Hidden by default; only displays when body has .has-mobile-shell AND
   viewport ≤768px. As a flex-sibling under <body>, it pushes #app-main
   upward so panels naturally shrink — no fragile padding tricks. */

.mobile-bottom-nav { display: none; }

@media (max-width: 768px) {
  body.has-mobile-shell .mobile-bottom-nav {
    display: flex;
    align-items: stretch;
    justify-content: space-around;
    flex-shrink: 0;
    background: #050505;
    border-top: 1px solid #1a1a1a;
    /* Internal padding-bottom carries the iOS home-indicator safe area. */
    padding-bottom: var(--m-safe-bottom);
    height: calc(var(--m-nav-h) + var(--m-safe-bottom));
    position: relative;
    z-index: 100;
  }
  .mobile-bottom-nav__item {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    background: none;
    border: 0;
    color: #777;
    font-family: inherit;
    font-size: 9.5px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    cursor: pointer;
    padding: 6px 4px;
    min-height: var(--m-tap-min);
    transition: color 0.15s;
  }
  .mobile-bottom-nav__item:hover { color: #BBB; }
  .mobile-bottom-nav__item.is-active { color: var(--m-gold); }
  .mobile-bottom-nav__icon {
    width: 22px; height: 22px;
    color: inherit;
    display: block;
  }
  .mobile-bottom-nav__icon svg { width: 100%; height: 100%; display: block; stroke: currentColor; }
  .mobile-bottom-nav__label { font-size: 9.5px; letter-spacing: 0.18em; }
}

/* ─── Quick-actions FAB ─────────────────────────────────────────────────── */
.mobile-fab,
.mobile-fab-sheet { display: none; }

@media (max-width: 768px) {
  body.has-mobile-shell .mobile-fab {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    right: 16px;
    bottom: calc(var(--m-nav-h) + var(--m-safe-bottom) + var(--m-fab-gap));
    width: var(--m-fab-size);
    height: var(--m-fab-size);
    border-radius: 50%;
    background: var(--m-gold);
    color: #1a1208;
    border: 0;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4), 0 2px 6px rgba(196, 154, 58, 0.32);
    cursor: pointer;
    z-index: 110;
    transition: transform 0.15s, box-shadow 0.15s;
  }
  .mobile-fab:active { transform: scale(0.95); }
  .mobile-fab svg { width: 24px; height: 24px; display: block; stroke: currentColor; }

  /* Sheet — invisible & non-interactive by default. Pattern matches piège #36:
     a hidden but positioned overlay MUST be pointer-events:none until open. */
  body.has-mobile-shell .mobile-fab-sheet {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 200;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s;
  }
  body.has-mobile-shell.mobile-fab-sheet-open .mobile-fab-sheet {
    opacity: 1;
    pointer-events: auto;
  }
  .mobile-fab-sheet__backdrop {
    position: absolute; inset: 0;
    background: rgba(0, 0, 0, 0.55);
  }
  .mobile-fab-sheet__panel {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    background: #0a0a0a;
    border-top: 1px solid #1a1a1a;
    border-radius: 16px 16px 0 0;
    padding: 12px 14px calc(20px + var(--m-safe-bottom));
    transform: translateY(24px);
    transition: transform 0.22s ease-out;
    max-height: 70vh;
    overflow-y: auto;
  }
  body.has-mobile-shell.mobile-fab-sheet-open .mobile-fab-sheet__panel { transform: translateY(0); }
  .mobile-fab-sheet__handle {
    width: 40px; height: 4px;
    background: #2a2a2a;
    border-radius: 2px;
    margin: 0 auto 14px;
  }
  .mobile-fab-sheet__title {
    font-family: 'Playfair Display', Georgia, serif;
    font-size: 18px;
    color: #F7F5F2;
    text-align: center;
    letter-spacing: 0.01em;
    margin-bottom: 14px;
  }
  .mobile-fab-sheet__actions {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }
  .mobile-fab-sheet__action {
    display: flex; align-items: center; gap: 10px;
    background: #111;
    border: 1px solid #1f1f1f;
    border-radius: 10px;
    padding: 14px 12px;
    color: #F7F5F2;
    font-family: inherit;
    font-size: 13px;
    letter-spacing: 0.02em;
    text-align: left;
    cursor: pointer;
    min-height: var(--m-tap-min);
    text-decoration: none;
  }
  .mobile-fab-sheet__action:active { background: #161616; }
  .mobile-fab-sheet__action-icon {
    width: 22px; height: 22px;
    flex-shrink: 0;
    color: var(--m-gold);
  }
  .mobile-fab-sheet__action-icon svg { width: 100%; height: 100%; display: block; stroke: currentColor; }
  .mobile-fab-sheet__action-label { flex: 1; font-weight: 500; }
  .mobile-fab-sheet__action-meta {
    display: block;
    margin-top: 2px;
    font-size: 11px;
    color: #888;
    font-weight: 400;
    letter-spacing: 0.02em;
  }
}

/* ─── Sticky bottom action bar ──────────────────────────────────────────── */
/* HTML pattern:
     <div class="mobile-sticky-cta">
       <button class="is-primary">Approve</button>
       <button>Cancel</button>
     </div>
   Place as the LAST child of a vertically-scrolling container (e.g. the
   page shell scroll region). On mobile it sticks above the bottom nav; on
   desktop it behaves as a normal flex row (no positioning). */

.mobile-sticky-cta {
  display: flex;
  gap: 10px;
}

@media (max-width: 768px) {
  body.has-mobile-shell .mobile-sticky-cta {
    position: sticky;
    bottom: 0;
    z-index: 60;
    background: linear-gradient(180deg, rgba(5, 5, 5, 0) 0%, #050505 28%);
    padding: 14px var(--m-page-pad) calc(14px + var(--m-safe-bottom));
    margin: 18px calc(-1 * var(--m-page-pad)) 0;
  }
  body.has-mobile-shell .mobile-sticky-cta > .is-primary,
  body.has-mobile-shell .mobile-sticky-cta > .mobile-sticky-cta__primary {
    flex: 1 1 0;
  }
}

/* ─── Table → cards primitive ───────────────────────────────────────────── */
/* HTML pattern:
     <table data-mobile="cards">
       <thead><tr><th>Item</th><th>Qty</th></tr></thead>
       <tbody><tr><td>Beer Bucket</td><td>4</td></tr></tbody>
     </table>
   admin-mobile.js copies each header label onto matching <td>s via
   data-label. Below 768px the table is repainted as stacked cards; the
   <th> labels appear as left-side eyebrows on each row.

   Pure-CSS chosen over JS-rewrite because:
     - Existing tables in index.html / reporting.html / purchasing.html are
       generated by app.js into static <tbody>s — a CSS-only transform
       keeps the DOM intact (no risk of breaking event handlers).
     - The only JS we need is the one-shot data-label backfill, which is
       also idempotent so it survives table rerenders.
   Above 768px the table renders normally (display: table). */

@media (max-width: 768px) {
  body.has-mobile-shell table[data-mobile="cards"] {
    display: block;
    width: 100%;
    background: transparent;
    border: 0;
    border-collapse: collapse;
  }
  body.has-mobile-shell table[data-mobile="cards"] thead { display: none; }
  body.has-mobile-shell table[data-mobile="cards"] tbody,
  body.has-mobile-shell table[data-mobile="cards"] tfoot { display: block; }
  body.has-mobile-shell table[data-mobile="cards"] tr {
    display: block;
    background: #0c0c0c;
    border: 1px solid #1a1a1a;
    border-radius: 10px;
    padding: 12px 14px;
    margin-bottom: 10px;
  }
  body.has-mobile-shell table[data-mobile="cards"] td {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 14px;
    padding: 6px 0;
    border: 0;
    font-size: 14px;
    color: #F7F5F2;
  }
  body.has-mobile-shell table[data-mobile="cards"] td:before {
    content: attr(data-label);
    flex-shrink: 0;
    color: #888;
    font-size: 10px;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    font-weight: 600;
  }
  /* td[data-label=""] (e.g. an action column) suppresses the eyebrow. */
  body.has-mobile-shell table[data-mobile="cards"] td[data-label=""]:before { content: none; }
  body.has-mobile-shell table[data-mobile="cards"] td[data-label=""] { justify-content: flex-end; }
}

/* ─── Collapsible primitive ─────────────────────────────────────────────── */
/* Pure <details>/<summary>-based; works everywhere. The gold +/− glyph is
   the only visual customisation, so collapsibles look identical on desktop
   and mobile — they ARE a useful pattern at every width. */

.m-collapsible {
  border: 1px solid #1a1a1a;
  border-radius: 10px;
  background: #0a0a0a;
}
.m-collapsible + .m-collapsible { margin-top: 10px; }
.m-collapsible[open] { background: #0d0d0d; }
.m-collapsible > summary {
  list-style: none;
  cursor: pointer;
  padding: 14px 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  color: #F7F5F2;
  font-size: 14px;
  letter-spacing: 0.02em;
  min-height: var(--m-tap-min, 44px);
}
.m-collapsible > summary::-webkit-details-marker { display: none; }
.m-collapsible > summary:after {
  content: "+";
  font-size: 20px;
  line-height: 1;
  color: var(--m-gold);
  width: 20px;
  text-align: center;
  flex-shrink: 0;
}
.m-collapsible[open] > summary:after { content: "−"; }
.m-collapsible__body {
  padding: 0 16px 16px;
  color: #CCBFB0;
  font-size: 13px;
  line-height: 1.55;
}

/* ─── Page shell ────────────────────────────────────────────────────────── */
/* HTML pattern (for full-page mobile views):
     <div class="m-page">
       <header class="m-page__topbar">
         <button class="m-page__back" data-back>‹</button>
         <h1 class="m-page__title">Engineering</h1>
         <button class="m-page__action is-primary">New</button>
       </header>
       <div class="m-page__scroll">… content …</div>
     </div>
   Desktop: invisible — `.m-page` is a transparent wrapper; the topbar
   stays hidden because it's gated on `body.has-mobile-shell` AND a
   max-width media query. Use this for any page that needs a back chevron
   or sticky top-bar on mobile. */

@media (max-width: 768px) {
  body.has-mobile-shell .m-page {
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
  }
  body.has-mobile-shell .m-page__topbar {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    background: rgba(8, 8, 8, 0.92);
    -webkit-backdrop-filter: blur(8px);
            backdrop-filter: blur(8px);
    border-bottom: 1px solid #1a1a1a;
    min-height: 48px;
    position: sticky;
    top: 0;
    z-index: 50;
  }
  body.has-mobile-shell .m-page__back {
    width: 36px; height: 36px;
    border-radius: 50%;
    border: 1px solid #1a1a1a;
    background: transparent;
    color: #CCBFB0;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    flex-shrink: 0;
    font-size: 20px; line-height: 1;
  }
  body.has-mobile-shell .m-page__back:active { background: #161616; }
  body.has-mobile-shell .m-page__title {
    flex: 1;
    font-family: 'Playfair Display', Georgia, serif;
    font-size: 18px;
    color: #F7F5F2;
    letter-spacing: 0.01em;
    line-height: 1.2;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin: 0;
    font-weight: 500;
  }
  body.has-mobile-shell .m-page__action {
    background: transparent;
    border: 1px solid #1a1a1a;
    color: #CCBFB0;
    padding: 7px 12px;
    border-radius: 999px;
    font-size: 10.5px;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    cursor: pointer;
    font-family: inherit;
    min-height: var(--m-tap-min);
  }
  body.has-mobile-shell .m-page__action.is-primary {
    background: var(--m-gold);
    color: #1a1208;
    border-color: var(--m-gold);
    font-weight: 600;
  }
  body.has-mobile-shell .m-page__scroll {
    flex: 1;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: var(--m-page-pad) var(--m-page-pad)
             calc(var(--m-page-pad) + var(--m-fab-size) + var(--m-fab-gap));
  }
}

/* ─── Status pill row primitive ─────────────────────────────────────────── */
/* HTML pattern (Phase 1b):
     <div class="m-status-pills" role="tablist" aria-label="Status filter">
       <button class="m-status-pills__pill is-active" data-value="">All</button>
       <button class="m-status-pills__pill" data-value="pending">Pending</button>
       …
     </div>
   Horizontal-scroll row of small pills. Sticky-friendly. Designed for
   status / priority / dept filters on list views where a dropdown would
   hide options behind two taps. Desktop: behaves as a normal flex row. */

.m-status-pills {
  display: flex;
  gap: 6px;
  align-items: center;
}
.m-status-pills__pill {
  flex-shrink: 0;
  background: #161616;
  border: 1px solid #2a2a2a;
  color: #BBB;
  font-family: inherit;
  font-size: 12px;
  letter-spacing: 0.04em;
  padding: 6px 12px;
  border-radius: 999px;
  cursor: pointer;
  min-height: 32px;
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.m-status-pills__pill:hover { color: #F7F5F2; border-color: #3a3a3a; }
.m-status-pills__pill.is-active {
  background: var(--m-gold-soft);
  border-color: var(--m-gold-soft-2);
  color: var(--m-gold);
  font-weight: 600;
}

@media (max-width: 768px) {
  body.has-mobile-shell .m-status-pills {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding-bottom: 4px;
    -webkit-mask-image: linear-gradient(to right, black 0, black calc(100% - 18px), transparent 100%);
            mask-image: linear-gradient(to right, black 0, black calc(100% - 18px), transparent 100%);
  }
  body.has-mobile-shell .m-status-pills::-webkit-scrollbar { display: none; }
  body.has-mobile-shell .m-status-pills__pill { min-height: 36px; padding: 7px 14px; font-size: 13px; }
}

/* ─── Quantity stepper primitive ────────────────────────────────────────── */
/* HTML pattern (Phase 1b):
     <div class="m-qty-stepper">
       <button class="m-qty-stepper__btn" data-qty-step="-1" aria-label="Decrease">−</button>
       <input class="m-qty-stepper__input" type="number" inputmode="decimal" />
       <button class="m-qty-stepper__btn" data-qty-step="+1" aria-label="Increase">+</button>
     </div>
   Or — auto-wrap by tagging an existing input with data-qty-stepper:
     <input type="number" data-qty-stepper inputmode="decimal" />
   admin-mobile.js scans for [data-qty-stepper] and replaces it with the
   structured stepper at init time (idempotent). Steps default to 1; pass
   data-qty-step="0.1" to override. */

.m-qty-stepper {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid #2a2a2a;
  border-radius: 8px;
  overflow: hidden;
  background: #1e1e1e;
}
.m-qty-stepper__btn {
  background: #262626;
  border: 0;
  color: #F7F5F2;
  font-family: inherit;
  font-size: 18px;
  font-weight: 600;
  line-height: 1;
  width: 40px;
  cursor: pointer;
  min-height: var(--m-tap-min, 44px);
  user-select: none;
  -webkit-user-select: none;
  transition: background 0.1s;
}
.m-qty-stepper__btn:hover { background: #303030; }
.m-qty-stepper__btn:active { background: #1a1a1a; }
.m-qty-stepper__input {
  background: transparent;
  border: 0;
  color: #F7F5F2;
  font-family: inherit;
  font-size: 15px;
  text-align: center;
  width: 80px;
  outline: none;
  -moz-appearance: textfield;
}
.m-qty-stepper__input::-webkit-outer-spin-button,
.m-qty-stepper__input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

@media (max-width: 768px) {
  body.has-mobile-shell .m-qty-stepper { width: 100%; }
  body.has-mobile-shell .m-qty-stepper__btn { width: 52px; font-size: 20px; }
  body.has-mobile-shell .m-qty-stepper__input { flex: 1 1 0; font-size: 17px; }
}

/* ─── Density / type tokens (mobile only) ───────────────────────────────── */
/* These are utility classes Phase 1+ can reach for. Documented in
   MOBILE_SHELL.md — every value below is exposed as a CSS var so pages
   can override (e.g. for a denser admin list). */

@media (max-width: 768px) {
  body.has-mobile-shell {
    --m-text-xs: 11px;
    --m-text-sm: 13px;
    --m-text: 15px;
    --m-text-lg: 17px;
    --m-text-display: 22px;
  }
  body.has-mobile-shell .m-text-xs { font-size: var(--m-text-xs); }
  body.has-mobile-shell .m-text-sm { font-size: var(--m-text-sm); }
  body.has-mobile-shell .m-text    { font-size: var(--m-text); }
  body.has-mobile-shell .m-text-lg { font-size: var(--m-text-lg); }
  body.has-mobile-shell .m-display {
    font-family: 'Playfair Display', Georgia, serif;
    font-size: var(--m-text-display);
    font-weight: 500;
    letter-spacing: 0.01em;
  }

  /* Floor for tap targets inside opted-in scopes. */
  body.has-mobile-shell .m-tap-target,
  body.has-mobile-shell .m-page button,
  body.has-mobile-shell .m-page a[role="button"] {
    min-height: var(--m-tap-min);
  }
}

/* ════════════════════════════════════════════════════════════════════════
   PHASE 1a — Per-panel mobile rules for index.html (manager home)
   ────────────────────────────────────────────────────────────────────────
   Every block below is `body.has-mobile-shell` + `@media (max-width: 768px)`
   gated. Desktop must remain pixel-identical to the 8accf30 redesign.

   Pattern: each panel's selector tree is kept short so future renames stay
   straightforward. New primitives go to the section above; cross-panel
   utilities live in MOBILE_SHELL.md. Anything one-off lives here.
   ──────────────────────────────────────────────────────────────────────── */

/* ─── Overview / panel-home ─────────────────────────────────────────────── */
@media (max-width: 768px) {
  /* Hero — calmer padding, slightly smaller headline, no horizontal jitter. */
  body.has-mobile-shell #panel-home .home-hero { padding: 18px 16px; }
  body.has-mobile-shell #panel-home .home-hero-num { font-size: 28px; }
  body.has-mobile-shell #panel-home .home-hero-spark svg { width: 100%; }

  /* Queue strip → single-column stack with the metric pulled gold on the right.
     (Phase 0 demo; kept here intentionally as the canonical Overview pattern.) */
  body.has-mobile-shell #panel-home .home-queues {
    grid-template-columns: 1fr;
    gap: 8px;
  }
  body.has-mobile-shell #panel-home .home-card {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-rows: auto auto;
    grid-template-areas:
      "label num"
      "meta  num";
    align-items: center;
    padding: 14px 16px;
    min-height: var(--m-tap-min);
    border-left-width: 3px;
    border-left-color: var(--m-gold-soft-2);
  }
  body.has-mobile-shell #panel-home .home-card-label { grid-area: label; font-size: 10px; }
  body.has-mobile-shell #panel-home .home-card-meta  { grid-area: meta;  font-size: 11px; }
  body.has-mobile-shell #panel-home .home-card-num   {
    grid-area: num;
    font-size: 28px;
    text-align: right;
    color: var(--m-gold);
  }

  /* Attention queue — slimmer, friendlier; the gold arrow already exists. */
  body.has-mobile-shell #panel-home .home-attention-item { padding: 12px 14px; }
  body.has-mobile-shell #panel-home .home-attention-label { font-size: 14px; }
  body.has-mobile-shell #panel-home .home-attention-hint  { font-size: 12px; }

  /* Recap / activity / footer — tighten container padding so the FAB never
     visually crowds the last row, and lift letter-spacing for legibility. */
  body.has-mobile-shell #panel-home .home-recap-body { padding: 14px 16px; }
  body.has-mobile-shell #panel-home .home-recap-headline { font-size: 19px; line-height: 1.25; }
  body.has-mobile-shell #panel-home .home-activity-row { padding: 12px 14px; font-size: 13px; }
  body.has-mobile-shell #panel-home .home-activity-time { width: 50px; font-size: 10.5px; }
  /* Bottom nav already covers these jump shortcuts, so hide them on mobile. */
  body.has-mobile-shell #panel-home .home-footer { display: none; }
}

/* ─── Roster / panel-roster — "Your team" ──────────────────────────────── */
@media (max-width: 768px) {
  /* Controls bar: too dense as one wrap-row. Collapse it into a compact
     two-column form, then float the venue tabs above so users see the
     biggest filter first. Refresh becomes a full-width primary button. */
  body.has-mobile-shell #roster-controls {
    display: grid;
    grid-template-columns: 1fr 1fr;
    padding: 12px 14px;
    gap: 8px;
    align-items: end;
  }
  body.has-mobile-shell #roster-controls .roster-control-group {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
  }
  body.has-mobile-shell #roster-controls .roster-control-group:has(#roster-search) {
    grid-column: 1 / -1;
  }
  body.has-mobile-shell #roster-controls .roster-label {
    font-size: 9.5px;
    letter-spacing: 0.22em;
    color: #888;
    text-transform: uppercase;
  }
  body.has-mobile-shell #roster-controls #roster-refresh-btn {
    grid-column: 1 / -1;
    min-height: var(--m-tap-min);
    margin-top: 4px;
  }
  body.has-mobile-shell #roster-controls #roster-drop-zone {
    grid-column: 1 / -1;
  }
  /* Venue tabs — horizontal scroll strip, sticky once you scroll. */
  body.has-mobile-shell #roster-venue-tabs {
    overflow-x: auto;
    flex-wrap: nowrap;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    padding: 8px 12px;
    position: sticky;
    top: 0;
    z-index: 6;
    background: #0a0a0a;
    border-bottom: 1px solid #1a1a1a;
  }
  body.has-mobile-shell #roster-venue-tabs::-webkit-scrollbar { display: none; }
  body.has-mobile-shell #roster-venue-tabs .roster-venue-tab {
    flex-shrink: 0;
    min-height: 36px;
    padding: 0 14px;
  }
  /* Grid — keep horizontal scroll, slightly bigger touch targets. The sticky
     name column already works from styles.css. */
  body.has-mobile-shell #roster-grid-container { padding-bottom: 8px; }
  body.has-mobile-shell .roster-table tbody td { padding: 10px 8px; font-size: 11px; }
  body.has-mobile-shell .roster-table tbody td.col-name { font-size: 12px; min-width: 140px; padding: 10px 12px; }
  body.has-mobile-shell .roster-table tbody td.col-dept { min-width: 90px; font-size: 10px; }
}

/* ─── Revenue / panel-revenue ──────────────────────────────────────────── */
@media (max-width: 768px) {
  /* Controls strip → wrap into a single-column form with clear groups.
     Sticky at the top of the scrolling panel so the filters never get lost
     when the user is deep in the pivot table. */
  body.has-mobile-shell #revenue-controls {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px 14px;
    position: sticky;
    top: 0;
    z-index: 5;
    background: #0a0a0a;
    border-bottom: 1px solid #1a1a1a;
  }
  body.has-mobile-shell #revenue-controls .revenue-view-toggle {
    display: flex; gap: 0; align-self: stretch;
  }
  body.has-mobile-shell #revenue-controls .revenue-view-toggle .rev-toggle-btn {
    flex: 1 1 0;
    min-height: var(--m-tap-min);
  }
  body.has-mobile-shell #revenue-controls .rev-search-filter,
  body.has-mobile-shell #revenue-controls .rev-date-filter,
  body.has-mobile-shell #revenue-controls .rev-month-filter {
    display: flex;
    align-items: stretch;
    width: 100%;
  }
  body.has-mobile-shell #revenue-controls .rev-search-input,
  body.has-mobile-shell #revenue-controls .rev-month-select {
    flex: 1 1 0;
    min-height: var(--m-tap-min);
  }
  /* Upload zone gets full width and explicit min height so it doesn't look
     like a stray label. Tap target = the whole zone. */
  body.has-mobile-shell #revenue-controls #rev-drop-zone {
    width: 100%;
    min-height: 52px;
    justify-content: center;
  }
  body.has-mobile-shell #revenue-controls .rev-intel-link {
    align-self: stretch;
    text-align: center;
    min-height: var(--m-tap-min);
    line-height: var(--m-tap-min);
  }

  /* KPI strip — three cards stack vertical with metric on the right edge. */
  body.has-mobile-shell #revenue-cards {
    grid-template-columns: 1fr;
    gap: 8px;
    padding: 12px 14px 0;
  }
  body.has-mobile-shell #revenue-cards .rev-card {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-rows: auto auto;
    grid-template-areas: "label val" "range val";
    align-items: center;
    padding: 14px 16px;
  }
  body.has-mobile-shell #revenue-cards .rev-card-label { grid-area: label; }
  body.has-mobile-shell #revenue-cards .rev-card-range { grid-area: range; }
  body.has-mobile-shell #revenue-cards .rev-card-value {
    grid-area: val;
    font-size: 22px;
    text-align: right;
    color: var(--m-gold);
  }

  /* Moments toggle pill row — wrap and breathe. */
  body.has-mobile-shell .moments-toggles { gap: 8px 12px; }
  body.has-mobile-shell .moments-toggle-divider { display: none; }

  /* Revenue table toolbar — Bigger/Fullscreen are still useful; sit them
     to the right and shrink labels to icons. */
  body.has-mobile-shell #revenue-table-toolbar { padding: 6px 10px; gap: 6px; }
  body.has-mobile-shell #revenue-table-toolbar #revenue-table-label { font-size: 11px; }
  body.has-mobile-shell .rev-table-size-btn { padding: 6px 8px; font-size: 11px; min-height: 36px; }
}

/* ─── Ops / Floor / panel-ops ──────────────────────────────────────────── */
@media (max-width: 768px) {
  /* Intelligence strip already wraps; just tune density so the three cards
     never overlap with the period bar. */
  body.has-mobile-shell #ops-intel-cards { grid-template-columns: 1fr; gap: 10px; padding: 12px 14px 0; }
  body.has-mobile-shell .intel-card { padding: 14px 16px; }
  body.has-mobile-shell .intel-title { font-size: 15px; }
  body.has-mobile-shell .intel-body  { font-size: 13px; line-height: 1.45; }

  /* Sub-tabs strip — must scroll horizontally without clipping. */
  body.has-mobile-shell #ops-subtabs {
    overflow-x: auto;
    flex-wrap: nowrap;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    position: sticky;
    top: 0;
    z-index: 6;
    background: #0a0a0a;
    padding: 6px 12px;
    border-bottom: 1px solid #1a1a1a;
  }
  body.has-mobile-shell #ops-subtabs::-webkit-scrollbar { display: none; }
  body.has-mobile-shell #ops-subtabs .ops-subtab {
    flex-shrink: 0;
    min-height: 36px;
  }
  /* Period bar — wrap, full-width selects. */
  body.has-mobile-shell #ops-period-bar {
    flex-wrap: wrap;
    gap: 8px;
    padding: 10px 12px;
  }
  body.has-mobile-shell #ops-period-bar #ops-period-btns { flex: 1 1 100%; display: flex; }
  body.has-mobile-shell #ops-period-bar .ops-period-btn { flex: 1 1 0; min-height: var(--m-tap-min); }
  body.has-mobile-shell #ops-period-bar .ops-month-wrap { flex: 1 1 60%; }
  body.has-mobile-shell #ops-period-bar #ops-month-select { width: 100%; min-height: var(--m-tap-min); }
  body.has-mobile-shell #ops-period-bar #ops-tax-toggle { flex: 1 1 35%; display: flex; }
  body.has-mobile-shell #ops-period-bar .ops-tax-btn { flex: 1 1 0; min-height: var(--m-tap-min); }

  /* KPI row — single column, the metric pulled gold (matches Home cards). */
  body.has-mobile-shell #ops-kpi-row,
  body.has-mobile-shell .ops-staff-kpi-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    padding: 12px 14px;
  }
  body.has-mobile-shell .ops-kpi { padding: 12px 14px; min-width: 0; }
  body.has-mobile-shell .ops-kpi-value { font-size: 18px; color: var(--m-gold); }

  /* Charts — full width, comfortable height, no min-width fighting flex. */
  body.has-mobile-shell .ops-charts-grid { padding: 0 14px; }
  body.has-mobile-shell .ops-chart-card { min-width: 0; }
  body.has-mobile-shell .ops-chart-card canvas { max-height: 280px; }

  /* Venue efficiency mini-cards — 1 col on small phones. */
  body.has-mobile-shell .ops-venue-eff-grid { grid-template-columns: 1fr 1fr; padding: 0 14px; }
}

/* ─── Admin / panel-admin ──────────────────────────────────────────────── */
@media (max-width: 768px) {
  body.has-mobile-shell #panel-admin { overflow-y: auto; }
  body.has-mobile-shell #panel-admin .admin-dashboard { padding: 22px 14px 28px; }
  body.has-mobile-shell #panel-admin .admin-dashboard-title { font-size: 26px; }
  body.has-mobile-shell #panel-admin .admin-grid { gap: 10px; }
  body.has-mobile-shell #panel-admin .admin-card { padding: 18px 18px 16px; min-height: var(--m-tap-min); }
  body.has-mobile-shell #panel-admin .admin-card-title { font-size: 16px; }
  body.has-mobile-shell #panel-admin .admin-card-desc { font-size: 13px; }

  /* Manager invitations section: invite form stacks, table → cards via
     data-mobile="cards" (markup updated in index.html). */
  body.has-mobile-shell #admin-managers-section { padding: 18px 14px 24px; margin-top: 18px; }
  body.has-mobile-shell #admin-invite-form {
    grid-template-columns: 1fr;
    gap: 8px;
  }
  body.has-mobile-shell #admin-invite-form input,
  body.has-mobile-shell #admin-invite-form select,
  body.has-mobile-shell #admin-invite-form button { min-height: var(--m-tap-min); }

  /* Mobile-card-cast managers table — the data-mobile="cards" transform
     turns each row into a stacked card; we just tune the per-cell density
     and let .badge / .row-action sit on their own line. */
  body.has-mobile-shell table[data-mobile="cards"].admin-managers-table tr {
    padding: 14px 16px;
  }
  body.has-mobile-shell table[data-mobile="cards"].admin-managers-table td {
    font-size: 13px;
    padding: 7px 0;
  }
  body.has-mobile-shell table[data-mobile="cards"].admin-managers-table td[data-label="Actions"] {
    flex-wrap: wrap;
    gap: 8px;
    padding-top: 10px;
  }
  body.has-mobile-shell table[data-mobile="cards"].admin-managers-table td[data-label="Actions"] .row-action {
    min-height: 38px;
  }
}

/* ─── Bottom nav: gold ring accent + slightly more breathing on small phones */
@media (max-width: 380px) {
  body.has-mobile-shell .mobile-bottom-nav__item { font-size: 9px; letter-spacing: 0.12em; }
  body.has-mobile-shell .mobile-bottom-nav__icon { width: 20px; height: 20px; }
}
@media (max-width: 768px) {
  body.has-mobile-shell .mobile-bottom-nav__item.is-active {
    background: linear-gradient(180deg, rgba(196,154,58,0) 60%, var(--m-gold-soft) 100%);
  }
}

/* ════════════════════════════════════════════════════════════════════════
   PHASE 1b — purchasing.html (PR → PO → GR)
   ────────────────────────────────────────────────────────────────────────
   purchasing.html has its own layout (sidebar + topbar + content) that
   pre-dates the shell. Below 768px:
     1. Hide the sidebar entirely — its sub-nav (PR / PO / GR / Suppliers)
        moves into a horizontal pill row at the top of the content area
        (rendered by the page's own #pur-mobile-subnav element; styled
        below).
     2. Topbar collapses into a slim title bar; search input moves under it.
     3. Filters drop into a sticky bar with full-width search + pill row.
     4. List tables → cards via data-mobile="cards" (markup added in JS).
     5. Detail layout drops the right rail under the main card; the page
        renders a .mobile-sticky-cta with the next-step action (Approve /
        Reject for pending PRs is the highlight).
     6. The modal expands to full-screen on mobile with a sticky save bar.
   Desktop ≥769px — every rule below is body.has-mobile-shell-gated and
   inside the max-width media query, so this whole block is no-op.
   ──────────────────────────────────────────────────────────────────────── */

@media (max-width: 768px) {
  /* ── Layout: sidebar gone, main fills viewport. */
  body.has-mobile-shell .sidebar { display: none; }
  body.has-mobile-shell .main { margin-left: 0; }

  /* ── Topbar: slimmer, stacked. The desktop topbar is a single row with
       title + search + Add button; on mobile we hide the inline search
       (the filter bar below has its own) and ensure the Add button stays
       reachable. */
  body.has-mobile-shell .topbar { padding: 10px 14px; gap: 10px; flex-wrap: wrap; }
  body.has-mobile-shell .topbar-title { font-size: 15px; }
  body.has-mobile-shell .topbar-sub { display: none; }
  body.has-mobile-shell .topbar .search-wrap { display: none; }
  body.has-mobile-shell .topbar #btn-add {
    min-height: var(--m-tap-min);
    padding: 8px 14px;
    font-size: 13px;
  }

  /* ── Mobile sub-nav strip. Page injects a #pur-mobile-subnav element
       below the topbar that hosts the same 5 destinations the sidebar had,
       as scrollable pills. Renders only on mobile. */
  body.has-mobile-shell #pur-mobile-subnav {
    display: flex;
    position: sticky;
    top: 0;
    z-index: 45;
    background: #0d0d0d;
    border-bottom: 1px solid #2a2a2a;
    padding: 8px 12px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    gap: 6px;
    -webkit-mask-image: linear-gradient(to right, black 0, black calc(100% - 18px), transparent 100%);
            mask-image: linear-gradient(to right, black 0, black calc(100% - 18px), transparent 100%);
  }
  body.has-mobile-shell #pur-mobile-subnav::-webkit-scrollbar { display: none; }
  body.has-mobile-shell #pur-mobile-subnav .pur-mn-item {
    flex-shrink: 0;
    background: #161616;
    border: 1px solid #2a2a2a;
    color: #BBB;
    padding: 8px 14px;
    border-radius: 999px;
    font-size: 13px;
    font-family: inherit;
    cursor: pointer;
    min-height: 36px;
    display: inline-flex;
    align-items: center;
    gap: 6px;
  }
  body.has-mobile-shell #pur-mobile-subnav .pur-mn-item.is-active {
    background: var(--m-gold-soft);
    border-color: var(--m-gold-soft-2);
    color: var(--m-gold);
    font-weight: 600;
  }
  body.has-mobile-shell #pur-mobile-subnav .pur-mn-count {
    background: #262626;
    color: #888;
    border-radius: 999px;
    padding: 1px 7px;
    font-size: 11px;
  }
  body.has-mobile-shell #pur-mobile-subnav .pur-mn-item.is-active .pur-mn-count {
    background: var(--m-gold-soft-2);
    color: var(--m-gold);
  }
  /* Hide on desktop — sidebar serves the same destinations there. */
  #pur-mobile-subnav { display: none; }

  /* ── Content padding tightens; bottom padding leaves room for the
       sticky CTA and the iOS home indicator. */
  body.has-mobile-shell .content {
    padding: 14px 12px calc(14px + var(--m-safe-bottom));
  }

  /* ── KPI grid: 2-col, metric gold-pulled to mirror Phase 1a. */
  body.has-mobile-shell .kpi-row { grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 16px; }
  body.has-mobile-shell .kpi-card { padding: 12px 14px; min-height: var(--m-tap-min); }
  body.has-mobile-shell .kpi-value { font-size: 22px; color: var(--m-gold); }
  body.has-mobile-shell .kpi-value.danger  { color: #e05252; }
  body.has-mobile-shell .kpi-value.warning { color: #f0c040; }
  body.has-mobile-shell .kpi-value.info    { color: #4b8fe0; }
  body.has-mobile-shell .kpi-value.success { color: #52b788; }
  body.has-mobile-shell .kpi-label { font-size: 10px; }
  body.has-mobile-shell .kpi-sub { font-size: 10.5px; }

  /* Workflow strip — horizontal scroll, no clipping. */
  body.has-mobile-shell .workflow-strip { padding: 12px 14px; margin-bottom: 14px; }

  /* Dashboard 2-col side-by-side breaks down to 1-col on mobile. */
  body.has-mobile-shell .content > div[style*="grid-template-columns:1fr 1fr"] {
    grid-template-columns: 1fr !important;
    gap: 12px !important;
  }
  body.has-mobile-shell .content > div[style*="grid-template-columns:repeat(3,1fr)"] {
    grid-template-columns: 1fr 1fr !important;
    gap: 8px !important;
  }

  /* ── Filters bar: stack with full-width search + pill status row. */
  body.has-mobile-shell .filters-bar {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    padding: 0 0 10px;
    position: sticky;
    top: 52px;        /* below the sub-nav strip (~52px tall) */
    z-index: 40;
    background: #0d0d0d;
  }
  body.has-mobile-shell .filters-bar .filter-input {
    width: 100%;
    min-height: var(--m-tap-min);
    padding: 10px 14px;
    font-size: 14px;
  }
  body.has-mobile-shell .filters-bar .filter-select {
    min-height: var(--m-tap-min);
    padding: 10px 12px;
    font-size: 14px;
  }
  /* Hide the legacy Apply/Clear buttons — pill row applies on tap. */
  body.has-mobile-shell .filters-bar #f-apply,
  body.has-mobile-shell .filters-bar .filter-select { display: none; }

  /* ── Table → cards: per-page tuning of the shared primitive. */
  body.has-mobile-shell .table-wrap { background: transparent; border: 0; border-radius: 0; }
  body.has-mobile-shell .table-wrap .table-header {
    padding: 8px 4px 12px;
    border-bottom: 0;
  }
  body.has-mobile-shell table[data-mobile="cards"] tr {
    background: var(--surface, #161616);
    border: 1px solid var(--border, #2a2a2a);
    border-radius: 10px;
    padding: 14px 16px;
    margin-bottom: 10px;
  }
  body.has-mobile-shell table[data-mobile="cards"] tr:hover td { background: transparent; }
  body.has-mobile-shell table[data-mobile="cards"] td { padding: 7px 0; font-size: 13.5px; gap: 12px; }
  body.has-mobile-shell table[data-mobile="cards"] td:before {
    color: #777;
    font-size: 10px;
    letter-spacing: 0.16em;
  }
  body.has-mobile-shell table[data-mobile="cards"] td[data-label="Actions"] {
    flex-wrap: wrap;
    gap: 8px;
    padding-top: 10px;
    border-top: 1px solid #1f1f1f;
    margin-top: 6px;
    justify-content: flex-end;
  }
  body.has-mobile-shell table[data-mobile="cards"] td[data-label="Actions"] .btn { min-height: 38px; padding: 8px 12px; }
  body.has-mobile-shell table[data-mobile="cards"] td[data-label="Actions"]:before { display: none; }

  /* Empty state pads less so it doesn't push everything offscreen. */
  body.has-mobile-shell .empty-state { padding: 32px 16px; }

  /* Alert bar reads as a friendly heads-up on mobile. */
  body.has-mobile-shell .alert-bar { padding: 12px 14px; line-height: 1.4; }

  /* ── Detail layout: stack right rail under main card. */
  body.has-mobile-shell .detail-layout { grid-template-columns: 1fr; gap: 12px; }
  body.has-mobile-shell .detail-card { padding: 16px; border-radius: 12px; }
  body.has-mobile-shell .detail-title { font-size: 20px; line-height: 1.2; }
  body.has-mobile-shell .field-grid { grid-template-columns: 1fr; gap: 12px; }
  body.has-mobile-shell .field-item .fv { font-size: 14px; word-break: break-word; }
  body.has-mobile-shell .related-item { padding: 12px 14px; }

  /* The "← Back" button at the top of the detail page becomes a clearer
     back chevron — full-width tap zone, larger glyph. */
  body.has-mobile-shell .content > .btn-ghost.btn-sm:first-child {
    display: inline-flex;
    align-items: center;
    min-height: var(--m-tap-min);
    padding: 9px 14px;
    font-size: 14px;
    margin-bottom: 12px;
  }

  /* ── Quick-action sticky CTA bar (rendered by purchasing.html JS).
       Lives at the bottom of the detail view; the safe-area padding and
       background gradient already come from the shared .mobile-sticky-cta
       primitive — we only tune the button sizing here. */
  body.has-mobile-shell .mobile-sticky-cta .btn {
    flex: 1 1 0;
    min-height: 48px;
    font-size: 14px;
    font-weight: 600;
    justify-content: center;
  }
  /* When a sticky CTA renders inside the .content scroll area, the FAB
     clearance from .m-page__scroll doesn't apply (purchasing.html isn't
     wrapped in .m-page). Compensate with explicit padding. */
  body.has-mobile-shell .content { padding-bottom: calc(var(--m-fab-size) + var(--m-fab-gap) + var(--m-safe-bottom) + 80px); }

  /* ── Modal → full-screen sheet on mobile. */
  body.has-mobile-shell .modal-overlay { padding: 0; align-items: flex-end; }
  body.has-mobile-shell .modal {
    width: 100%;
    max-width: 100%;
    max-height: 100vh;
    height: 100vh;
    border-radius: 0;
    border: 0;
    border-top: 1px solid #2a2a2a;
  }
  body.has-mobile-shell .modal-header {
    padding: 14px 16px;
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--surface, #161616);
  }
  body.has-mobile-shell .modal-title { font-size: 17px; }
  body.has-mobile-shell .modal-body { padding: 14px 16px; }
  body.has-mobile-shell .form-grid { grid-template-columns: 1fr; gap: 10px; }
  body.has-mobile-shell .form-row { margin-bottom: 10px; }
  body.has-mobile-shell .form-row input,
  body.has-mobile-shell .form-row select,
  body.has-mobile-shell .form-row textarea { min-height: var(--m-tap-min); padding: 10px 12px; font-size: 14px; }
  body.has-mobile-shell .form-row textarea { min-height: 84px; }
  body.has-mobile-shell .form-row label { font-size: 12px; margin-bottom: 5px; }
  body.has-mobile-shell .modal-footer {
    padding: 12px 16px calc(12px + var(--m-safe-bottom));
    position: sticky;
    bottom: 0;
    z-index: 5;
    background: var(--surface, #161616);
    box-shadow: 0 -8px 18px rgba(0,0,0,0.45);
  }
  body.has-mobile-shell .modal-footer .btn { flex: 1 1 0; min-height: 48px; font-size: 14px; font-weight: 600; justify-content: center; }
  body.has-mobile-shell .modal-footer .btn-primary { order: 2; }
  body.has-mobile-shell .modal-footer .btn-ghost { order: 1; }
}

/* ============================================================================
   PHASE 1c — Notifications (mobile bell + inbox sheet)
   ----------------------------------------------------------------------------
   Operations Portal has no notifications bell on desktop — managers see the
   Attention Queue inline on Home (#home-attention, server-driven by
   /api/admin/overview). On mobile a walking manager may be on any panel,
   so we add a bell in the top nav that opens a full-sheet inbox listing
   the same items. Bell is mobile-only (display:none on desktop) → desktop
   markup is unchanged from Phase 1b.

   Includes a generic `.mobile-bottom-sheet` primitive — distinct from the
   `.mobile-fab-sheet` quick-actions sheet so the two can be open in
   different contexts without colliding.
   ========================================================================== */

/* Bell button: invisible on desktop (>768px). The privacy-toggle + role
   badge + signout already crowd .nav-right on small screens, so the bell
   sits leftmost to remain thumb-friendly on the right edge while still
   visible alongside them. */
.mobile-notif-bell { display: none; }

@media (max-width: 768px) {
  body.has-mobile-shell .mobile-notif-bell {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    min-height: var(--m-tap-min);
    min-width: var(--m-tap-min);
    border: 1px solid #2a2a2a;
    border-radius: 8px;
    background: transparent;
    color: var(--off-white, #F7F5F2);
    cursor: pointer;
    padding: 0;
    transition: border-color 0.15s ease, background-color 0.15s ease;
  }
  body.has-mobile-shell .mobile-notif-bell:hover,
  body.has-mobile-shell .mobile-notif-bell:focus-visible {
    border-color: var(--m-gold);
    background: var(--m-gold-soft);
  }
  body.has-mobile-shell .mobile-notif-bell[data-has-crit="1"] {
    border-color: rgba(214, 99, 99, 0.55);
  }
  body.has-mobile-shell .mobile-notif-bell__badge {
    position: absolute;
    top: -4px;
    right: -4px;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: 9px;
    background: var(--m-gold);
    color: #000;
    font-size: 10.5px;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
    letter-spacing: 0;
    box-shadow: 0 0 0 2px #050505;
    pointer-events: none;
  }
  body.has-mobile-shell .mobile-notif-bell[data-has-crit="1"] .mobile-notif-bell__badge {
    background: #d66363;
    color: #fff;
  }
}

/* ─── Generic bottom-sheet primitive ──────────────────────────────────────
   HTML pattern:
     <div class="mobile-bottom-sheet" id="..." role="dialog" aria-modal="true" aria-hidden="true">
       <div class="mobile-bottom-sheet__backdrop" data-sheet-close></div>
       <div class="mobile-bottom-sheet__panel">
         <header class="mobile-bottom-sheet__header">
           <div class="mobile-bottom-sheet__handle"></div>
           <h2 class="mobile-bottom-sheet__title">…</h2>
           <button class="mobile-bottom-sheet__close" data-sheet-close>✕</button>
         </header>
         <div class="mobile-bottom-sheet__body">…</div>
       </div>
     </div>

   Opened by adding `.is-open` to the sheet element. Per piège #36 in
   CLAUDE.md, pointer-events stay none until open. Distinct from
   .mobile-fab-sheet so quick-actions + notif sheets cannot conflict. */

.mobile-bottom-sheet {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: none;
  pointer-events: none;
}
.mobile-bottom-sheet.is-open { display: block; pointer-events: auto; }

@media (max-width: 768px) {
  body.has-mobile-shell .mobile-bottom-sheet__backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    opacity: 0;
    transition: opacity 0.18s ease;
  }
  body.has-mobile-shell .mobile-bottom-sheet.is-open .mobile-bottom-sheet__backdrop { opacity: 1; }

  body.has-mobile-shell .mobile-bottom-sheet__panel {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    max-height: 90vh;
    background: #101010;
    border-top: 1px solid #2a2a2a;
    border-radius: 18px 18px 0 0;
    box-shadow: 0 -16px 36px rgba(0, 0, 0, 0.6);
    display: flex;
    flex-direction: column;
    transform: translateY(100%);
    transition: transform 0.22s ease;
    padding-bottom: var(--m-safe-bottom);
  }
  body.has-mobile-shell .mobile-bottom-sheet.is-open .mobile-bottom-sheet__panel { transform: translateY(0); }

  body.has-mobile-shell .mobile-bottom-sheet__header {
    position: relative;
    padding: 14px 16px 10px;
    border-bottom: 1px solid #1c1c1c;
    flex-shrink: 0;
  }
  body.has-mobile-shell .mobile-bottom-sheet__handle {
    width: 42px;
    height: 4px;
    border-radius: 2px;
    background: #3a3a3a;
    margin: 0 auto 10px;
  }
  body.has-mobile-shell .mobile-bottom-sheet__title {
    font-family: 'Playfair Display', Georgia, serif;
    font-size: 19px;
    color: var(--off-white, #F7F5F2);
    margin: 0;
    padding-right: 36px;
    font-weight: 400;
  }
  body.has-mobile-shell .mobile-bottom-sheet__close {
    position: absolute;
    top: 12px;
    right: 12px;
    width: 32px;
    height: 32px;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: #888;
    font-size: 18px;
    cursor: pointer;
  }
  body.has-mobile-shell .mobile-bottom-sheet__close:hover { color: var(--off-white, #F7F5F2); }

  body.has-mobile-shell .mobile-bottom-sheet__body {
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    flex: 1 1 auto;
    padding: 6px 12px 18px;
  }
}

/* ─── Notif inbox content (rendered inside #mobile-notif-sheet) ───────── */
@media (max-width: 768px) {
  body.has-mobile-shell .mobile-notif-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
  }
  body.has-mobile-shell .mobile-notif-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 12px;
    padding: 14px 14px;
    background: #161616;
    border: 1px solid #232323;
    border-left-width: 3px;
    border-left-color: var(--m-gold-soft-2);
    border-radius: 10px;
    color: inherit;
    text-align: left;
    cursor: pointer;
    min-height: var(--m-tap-min);
    font: inherit;
    width: 100%;
  }
  body.has-mobile-shell .mobile-notif-row.is-crit { border-left-color: #d66363; }
  body.has-mobile-shell .mobile-notif-row.is-warn { border-left-color: var(--m-gold); }
  body.has-mobile-shell .mobile-notif-row.is-info { border-left-color: var(--sand, #CCBFB0); }
  body.has-mobile-shell .mobile-notif-row:active { background: #1c1c1c; }

  body.has-mobile-shell .mobile-notif-row__sev {
    width: 28px;
    height: 28px;
    border-radius: 14px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--m-gold-soft);
    color: var(--m-gold);
    flex-shrink: 0;
  }
  body.has-mobile-shell .mobile-notif-row.is-crit .mobile-notif-row__sev {
    background: rgba(214, 99, 99, 0.18);
    color: #d66363;
  }
  body.has-mobile-shell .mobile-notif-row.is-info .mobile-notif-row__sev {
    background: rgba(204, 191, 176, 0.12);
    color: var(--sand, #CCBFB0);
  }

  body.has-mobile-shell .mobile-notif-row__main { min-width: 0; }
  body.has-mobile-shell .mobile-notif-row__label {
    font-size: 14px;
    line-height: 1.3;
    color: var(--off-white, #F7F5F2);
    margin: 0 0 3px;
    font-weight: 500;
  }
  body.has-mobile-shell .mobile-notif-row__hint {
    font-size: 12px;
    color: #999;
    line-height: 1.35;
    margin: 0;
  }
  body.has-mobile-shell .mobile-notif-row__arrow {
    color: #666;
    font-size: 18px;
    line-height: 1;
    flex-shrink: 0;
  }

  /* Empty state — calm, generous whitespace */
  body.has-mobile-shell .mobile-notif-empty {
    text-align: center;
    padding: 40px 24px 32px;
    color: #888;
  }
  body.has-mobile-shell .mobile-notif-empty__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 64px;
    height: 64px;
    border-radius: 50%;
    background: var(--m-gold-soft);
    color: var(--m-gold);
    margin: 0 auto 14px;
  }
  body.has-mobile-shell .mobile-notif-empty__title {
    font-family: 'Playfair Display', Georgia, serif;
    font-size: 22px;
    color: var(--off-white, #F7F5F2);
    margin-bottom: 6px;
  }
  body.has-mobile-shell .mobile-notif-empty__sub {
    font-size: 13px;
    color: #888;
    line-height: 1.4;
  }
}

/* ============================================================================
   PHASE 2 — engineering.html (maintenance tickets)
   ----------------------------------------------------------------------------
   engineering.html is the maintenance/engineering ticket tracker. Unlike the
   rest of the Operations Portal it has a LIGHT theme (white + navy + sand),
   not the dark Operations look — so this block introduces:

   (a) A new `.m-card-band` shell primitive: severity-coloured left edge
       accent for any tappable card. Already exists ad-hoc inside the
       engineering ticket card (`.ticket-card.critical`) and the Phase 1c
       notif rows; this is the shared, named version future pages can opt
       into (Reporting? Roster?). Tokens are CSS variables so a page can
       override them per theme.

   (b) PHASE 2 page-specific rules, scoped via
       `body[data-page="engineering"]`. Higher specificity than the
       shared Phase 1b modal rules so engineering's light theme wins on
       mobile without us having to !important anything.

   Desktop is still byte-identical above 768px (the engineering page
   already had its own ≥640px breakpoint that resets to the
   centered-modal look — and the data-page selector only matches when the
   page chooses to opt in).
   ========================================================================== */

/* ─── .m-card-band primitive ──────────────────────────────────────────────
   Adds a 3-4px left edge band to a tap-card. Set `data-band="crit|warn|sage|info"`
   (or override `--m-band` per node) to pick the colour. Works on any card-like
   element; harmless on desktop. */

.m-card-band {
  --m-band: var(--m-gold-soft-2);
  border-left: 3px solid var(--m-band);
}
.m-card-band[data-band="crit"]  { --m-band: #d66363; }
.m-card-band[data-band="warn"]  { --m-band: var(--m-gold, #C49A3A); }
.m-card-band[data-band="sage"]  { --m-band: #96AA96; }
.m-card-band[data-band="info"]  { --m-band: #6C97D3; }
.m-card-band[data-band="muted"] { --m-band: #aaa; }

/* On mobile, bump the band to 4px so it stays visible next to a 14-15px
   padding card without disappearing into the border noise. */
@media (max-width: 768px) {
  body.has-mobile-shell .m-card-band { border-left-width: 4px; }
}

/* ─── engineering.html scope ──────────────────────────────────────────── */

/* The shared Phase 1b modal rules write the modal background as
   var(--surface, #161616). Engineering aliases --surface to var(--white)
   in its own :root so those rules pick up the right colour automatically.
   What we still have to fix per page: the dark border-top, the dark
   modal-footer shadow, and a few light-theme-specific tunings. */
@media (max-width: 768px) {
  body.has-mobile-shell[data-page="engineering"] .modal {
    border-top: 1px solid var(--border, #e2ddd8);
    color: var(--text, #1a1a1a);
  }
  body.has-mobile-shell[data-page="engineering"] .modal-header {
    background: var(--white, #fff);
    border-bottom: 1px solid var(--border, #e2ddd8);
  }
  body.has-mobile-shell[data-page="engineering"] .modal-body { padding: 14px 16px 18px; }
  body.has-mobile-shell[data-page="engineering"] .modal-footer {
    background: var(--white, #fff);
    border-top: 1px solid var(--border, #e2ddd8);
    box-shadow: 0 -8px 16px rgba(15, 23, 42, 0.06);
  }
  body.has-mobile-shell[data-page="engineering"] .modal-footer .btn-primary { width: 100%; }
  /* The shared Phase 1b form-row rules target inputs/selects/textareas in
     form-rows; engineering's single-column form-groups also need bigger
     tap targets. */
  body.has-mobile-shell[data-page="engineering"] .form-control { min-height: var(--m-tap-min); padding: 11px 12px; font-size: 14px; }
  body.has-mobile-shell[data-page="engineering"] textarea.form-control { min-height: 100px; }
  body.has-mobile-shell[data-page="engineering"] .form-row {
    grid-template-columns: 1fr;
    gap: 0;
  }
  body.has-mobile-shell[data-page="engineering"] .form-row > .form-group { margin-bottom: 14px; }

  /* Header: trim padding so the title + new-ticket button breathe. */
  body.has-mobile-shell[data-page="engineering"] .header { padding: 0 12px; height: 52px; }
  body.has-mobile-shell[data-page="engineering"] .header-title { font-size: 12px; }
  body.has-mobile-shell[data-page="engineering"] .btn-create {
    padding: 9px 14px;
    min-height: var(--m-tap-min);
    font-size: 13px;
  }

  /* Stats bar: tighten padding, drop label letter-spacing so 4 cards fit
     comfortably at 375px. */
  body.has-mobile-shell[data-page="engineering"] .stats-bar {
    padding: 10px 12px;
    gap: 6px;
  }
  body.has-mobile-shell[data-page="engineering"] .stat-card { padding: 8px 6px; }
  body.has-mobile-shell[data-page="engineering"] .stat-num { font-size: 22px; }
  body.has-mobile-shell[data-page="engineering"] .stat-label { font-size: 9.5px; letter-spacing: .02em; }

  /* Status pill row: light-theme variant of .m-status-pills. The shared
     primitive uses dark fills; here we re-paint for the white-background
     filter strip and use the navy accent. */
  body.has-mobile-shell[data-page="engineering"] .filter-status-pills {
    padding: 10px 12px 4px;
    background: var(--white, #fff);
    border-bottom: 1px solid var(--border, #e2ddd8);
  }
  body.has-mobile-shell[data-page="engineering"] .m-status-pills__pill {
    background: var(--bg, #f5f3f0);
    border-color: var(--border, #e2ddd8);
    color: var(--muted, #6b7280);
    min-height: 38px;
    padding: 8px 16px;
    font-size: 13px;
    font-weight: 600;
  }
  body.has-mobile-shell[data-page="engineering"] .m-status-pills__pill:hover {
    color: var(--text, #1a1a1a);
    border-color: var(--muted, #6b7280);
    background: var(--bg, #f5f3f0);
  }
  body.has-mobile-shell[data-page="engineering"] .m-status-pills__pill.is-active {
    background: var(--navy, #0E2557);
    border-color: var(--navy, #0E2557);
    color: var(--white, #fff);
  }
  /* On mobile, hide the now-redundant status select. The venue + priority
     selects stay because they have more options (5 + 4) and would make a
     pill row too crowded. */
  body.has-mobile-shell[data-page="engineering"] .filter-bar #filterStatus { display: none; }
  body.has-mobile-shell[data-page="engineering"] .filter-bar {
    padding: 8px 12px;
    gap: 6px;
  }
  body.has-mobile-shell[data-page="engineering"] .filter-select { min-height: 38px; font-size: 13px; }
  body.has-mobile-shell[data-page="engineering"] .search-input { min-height: 38px; font-size: 14px; }

  /* Ticket cards: bigger tap targets, calmer borders, and the existing
     `.ticket-card.critical/.high/.medium/.low` left-edge accents now
     conceptually match the .m-card-band primitive. */
  body.has-mobile-shell[data-page="engineering"] .ticket-list { padding: 12px 12px; gap: 10px; }
  body.has-mobile-shell[data-page="engineering"] .ticket-card {
    padding: 14px 14px 12px;
    min-height: 64px;
    border-left-width: 4px;
  }
  body.has-mobile-shell[data-page="engineering"] .ticket-title { font-size: 15px; }
  body.has-mobile-shell[data-page="engineering"] .ticket-meta { gap: 8px; }
  body.has-mobile-shell[data-page="engineering"] .ticket-id { font-size: 10.5px; }
  body.has-mobile-shell[data-page="engineering"] .badge { font-size: 9.5px; padding: 3px 8px; }

  /* Empty state: more vertical breathing room, calmer copy already done
     in the JS (renderTickets). */
  body.has-mobile-shell[data-page="engineering"] .empty-state { padding: 56px 20px 32px; }
  body.has-mobile-shell[data-page="engineering"] .empty-state .icon { font-size: 44px; }

  /* Detail modal — Status / Priority quick rows */
  body.has-mobile-shell[data-page="engineering"] .status-actions { gap: 6px; }
  body.has-mobile-shell[data-page="engineering"] .btn-status {
    min-width: 0;
    flex: 1 1 0;
    min-height: var(--m-tap-min);
    padding: 11px 8px;
    font-size: 13px;
  }
  body.has-mobile-shell[data-page="engineering"] .inline-edit { gap: 6px; }
  body.has-mobile-shell[data-page="engineering"] .inline-select { min-height: var(--m-tap-min); font-size: 14px; padding: 10px 12px; }
  body.has-mobile-shell[data-page="engineering"] .btn-save-inline {
    min-height: var(--m-tap-min);
    padding: 10px 16px;
    font-size: 13px;
  }

  /* Update input row: keep the input + paperclip + send button on one
     line. The paperclip is the photo picker label (acts as a button). */
  body.has-mobile-shell[data-page="engineering"] .update-input-row {
    gap: 6px;
  }
  body.has-mobile-shell[data-page="engineering"] .update-input { min-height: var(--m-tap-min); font-size: 14px; }
  body.has-mobile-shell[data-page="engineering"] .btn-send {
    min-height: var(--m-tap-min);
    padding: 10px 16px;
    font-size: 13px;
  }
  body.has-mobile-shell[data-page="engineering"] .update-photo-btn {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--m-tap-min);
    min-height: var(--m-tap-min);
    border: 1px solid var(--border, #e2ddd8);
    background: var(--bg, #f5f3f0);
    border-radius: 8px;
    font-size: 18px;
    cursor: pointer;
  }

  /* Detail body padding-bottom so the iOS keyboard doesn't cover the
     update input row — leave room for the soft keyboard's height while
     also clearing the safe-area bottom. */
  body.has-mobile-shell[data-page="engineering"] .modal-body { padding-bottom: 24px; }
}

/* The update-photo-btn exists on desktop too (just shorter) so the input
   row is consistent above the breakpoint. Mobile-only sizing above. */
body[data-page="engineering"] .update-photo-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--border, #e2ddd8);
  background: var(--bg, #f5f3f0);
  border-radius: 8px;
  font-size: 16px;
  cursor: pointer;
  flex-shrink: 0;
}
body[data-page="engineering"] .update-photo-btn:hover { background: var(--white, #fff); }

/* The pill row needs a default desktop look too (we render the markup
   on all sizes; only hide above the breakpoint). */
body[data-page="engineering"] .filter-status-pills { display: none; }
@media (max-width: 768px) {
  body[data-page="engineering"] .filter-status-pills { display: flex; }
}

/* ─── Photo picker + thumbs (used in create modal + update row) ──────── */
body[data-page="engineering"] .photo-picker__btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  background: var(--bg, #f5f3f0);
  border: 1px dashed var(--border, #e2ddd8);
  border-radius: 8px;
  color: var(--navy, #0E2557);
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  user-select: none;
  min-height: 44px;
}
body[data-page="engineering"] .photo-picker__btn:hover {
  background: var(--white, #fff);
  border-color: var(--navy, #0E2557);
}
body[data-page="engineering"] .photo-picker__icon { font-size: 17px; }
body[data-page="engineering"] .photo-picker__hint {
  margin-top: 6px;
  font-size: 12px;
  color: var(--muted, #6b7280);
}
body[data-page="engineering"] .photo-thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}
body[data-page="engineering"] .photo-thumb {
  position: relative;
  width: 72px;
  height: 72px;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--border, #e2ddd8);
  background: var(--bg, #f5f3f0);
}
body[data-page="engineering"] .photo-thumb img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
body[data-page="engineering"] .photo-thumb__remove {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 22px;
  height: 22px;
  border: 0;
  border-radius: 50%;
  background: rgba(0,0,0,.65);
  color: #fff;
  font-size: 14px;
  line-height: 22px;
  text-align: center;
  cursor: pointer;
  padding: 0;
}
body[data-page="engineering"] .photo-thumb--link { display: block; }
body[data-page="engineering"] .photo-thumbs--detail .photo-thumb,
body[data-page="engineering"] .photo-thumbs--update .photo-thumb {
  width: 88px;
  height: 88px;
}

/* On mobile, give the thumbs in the create modal a bit more breathing
   room and let them fill the row width. */
@media (max-width: 768px) {
  body.has-mobile-shell[data-page="engineering"] .photo-picker__btn {
    width: 100%;
    justify-content: center;
    min-height: var(--m-tap-min);
  }
  body.has-mobile-shell[data-page="engineering"] .photo-thumb { width: 80px; height: 80px; }
}

/* ════════════════════════════════════════════════════════════════════════
   PHASE 3 — reporting.html (Revenue / PNL / Intelligence)
   ────────────────────────────────────────────────────────────────────────
   reporting.html is the heaviest analytics surface — KPIs, charts, and
   genuinely tabular grids (Staffing days × shift codes, Operations daily
   efficiency, Costs editor with inline cost-input). The mobile treatment:

   1. Headline KPIs first  — 2-col grid, value pulled gold and bumped to
      28px so they read in 3 seconds between meetings.
   2. Sticky filter bar    — pins below the header (52px) so changing
      period / venue stays one-tap-away while scrolling through results.
   3. Type tabs            — 44px tap-target floor; the rest of the desktop
      pill row stays as-is (already horizontal-scroll).
   4. Tables               — per-surface design call:
        - Staffing daily, Operations daily, Intelligence Costs → keep as
          horizontal-scroll grids (multi-dim, trend-scanning matters);
          wrap in `.m-scroll-table` for sticky first column + fade hint.
        - Revenue venue table, Top Items, Covers/Avg-Spend, Engineering
          tickets, Outstanding tickets, Maintenance, Overdue → cards via
          `data-mobile="cards"` (list-style, only 5-20 rows).
   5. Sticky CTA           — Run + Export CSV + Export PDF pinned bottom.
   6. Charts               — Mix donut already responsive; Margin quadrant
      capped at 260px; Venue comparison bars stack at narrow widths.

   Desktop (>768px) is byte-identical — every rule below is gated by both
   `body.has-mobile-shell` and `@media (max-width: 768px)`.

   New primitive added in this phase: `.m-scroll-table` (below). */

/* ─── .m-scroll-table primitive ──────────────────────────────────────────
   Wraps a `<table>` to keep horizontal-scroll readable on a phone:
     - Overflow-x: auto with momentum scrolling on iOS.
     - First column (`th:first-child` + `td:first-child`) sticks to the
       left edge so the row anchor (date, venue, item name) stays visible.
     - Soft gradient on the right edge signals "more content this way" —
       drops on mobile only because that's where it adds value.
     - 44px tap-target floor on table rows so single-row tap actions
       (sort, inline edit) read as tap-able.

   Markup:
     <div class="m-scroll-table"><table>…</table></div>

   Or alias dynamically: a renderer that owns the wrap can
   `wrap.classList.toggle('m-scroll-table', shouldUseGrid)` per tab.

   Desktop: the class is a no-op — `overflow-x: auto` falls through to
   the parent (which usually doesn't need scroll anyway above 768px). */

@media (max-width: 768px) {
  body.has-mobile-shell .m-scroll-table {
    position: relative;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    /* Right-edge fade. `background-attachment: scroll` (default) means the
       gradient is positioned relative to this element's own viewport, not
       the overflowing content, so it stays pinned to the right edge as
       the user scrolls horizontally. We use the dark page surface
       (rgba(10,10,10)) as the fade colour so the gradient blends. */
    background: linear-gradient(to right, transparent 0%, transparent 88%, rgba(10,10,10,0.8) 100%);
    background-repeat: no-repeat;
    background-size: 100% 100%;
  }
  body.has-mobile-shell .m-scroll-table > table {
    border-collapse: separate;
    border-spacing: 0;
    width: max-content;
    min-width: 100%;
  }
  /* Sticky first column. We sticky both <th> and <td> at column 1 so the
     row anchor (date / venue / item) survives a horizontal scroll. The
     <th> background is darker than <td> (matches the existing .rpt-table
     thead colour), so we lift each to its own background. */
  body.has-mobile-shell .m-scroll-table > table thead th:first-child {
    position: sticky;
    left: 0;
    z-index: 2;
    background: #0d0d0d;
  }
  body.has-mobile-shell .m-scroll-table > table tbody td:first-child,
  body.has-mobile-shell .m-scroll-table > table tfoot td:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    background: #111;
  }
  /* Tap-target floor on rows. We push it on <td> rather than <tr> because
     the cell is the actual scroll-target on phones. */
  body.has-mobile-shell .m-scroll-table > table tbody td {
    min-height: var(--m-tap-min);
  }
}

/* ─── reporting.html scope ───────────────────────────────────────────── */

@media (max-width: 768px) {
  /* Header — keep the logo, title hides below 900px via existing rule.
     Type tabs need a 44px tap-target floor and a slightly bigger font. */
  body.has-mobile-shell[data-page="reporting"] .rpt-header {
    padding: 0 10px;
    gap: 8px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-logo { height: 24px; }
  body.has-mobile-shell[data-page="reporting"] .rpt-type-tab {
    min-height: var(--m-tap-min);
    padding: 10px 12px;
    font-size: 11.5px;
    letter-spacing: 0.18em;
  }
  /* Desktop hides the export buttons under 600px — we replace them with
     the sticky CTA below, so the rule continues to apply. */

  /* ─ Sticky filter bar ─────────────────────────────────────────────── */
  body.has-mobile-shell[data-page="reporting"] .rpt-filters {
    position: sticky;
    top: 52px;          /* below the .rpt-header (52px tall) */
    z-index: 90;
    padding: 10px 12px;
    gap: 8px;
    flex-direction: column;
    align-items: stretch;
    /* Slight shadow so the row separates from scrolling content. */
    box-shadow: 0 6px 12px rgba(0,0,0,0.35);
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-filter-group {
    flex-wrap: wrap;
    gap: 6px 10px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-filter-divider { display: none; }
  body.has-mobile-shell[data-page="reporting"] .rpt-presets {
    flex-wrap: wrap;
    gap: 4px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-preset {
    min-height: 36px;
    padding: 7px 14px;
    font-size: 12px;
    flex: 1 1 auto;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-tax-btn {
    min-height: 36px;
    padding: 7px 14px;
    font-size: 12px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-date-input,
  body.has-mobile-shell[data-page="reporting"] .rpt-select {
    min-height: 36px;
    padding: 6px 10px;
    font-size: 13px;
    flex: 1 1 auto;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-dynamic-filters {
    flex-wrap: wrap;
    gap: 6px 12px;
    width: 100%;
  }
  /* The top-line Run button moves to the sticky CTA; hide it in the
     filter bar on mobile so the bar doesn't carry a second action. */
  body.has-mobile-shell[data-page="reporting"] .rpt-run-wrap { display: none; }

  /* ─ Main padding so sticky CTA never covers the last row ──────────── */
  body.has-mobile-shell[data-page="reporting"] .rpt-main {
    padding: 12px 12px calc(72px + var(--m-safe-bottom));
  }

  /* ─ KPI row: headline-sized values, 2-col, gold pull ──────────────── */
  body.has-mobile-shell[data-page="reporting"] .rpt-kpi-row {
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin-bottom: 14px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-kpi-card {
    padding: 12px 14px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-kpi-label {
    font-size: 9.5px;
    margin-bottom: 6px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-kpi-value {
    font-size: 26px;
    color: var(--m-gold, #C49A3A);
    font-family: 'Playfair Display', Georgia, serif;
    font-weight: 500;
    letter-spacing: 0.01em;
    line-height: 1.05;
    margin-bottom: 6px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-kpi-sub {
    font-size: 11px;
  }

  /* ─ Table section trim ─────────────────────────────────────────────── */
  body.has-mobile-shell[data-page="reporting"] .rpt-table-section {
    margin-bottom: 14px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-table-header {
    padding: 11px 14px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-section-title {
    font-size: 11px;
    letter-spacing: 0.16em;
  }

  /* When the wrap is in scroll-table mode (staffing / operations), let the
     primitive's rules drive layout. The legacy `overflow-x:auto` on
     .rpt-table-wrap already aligns with what .m-scroll-table needs, so
     adding the class to the existing wrap is non-destructive. */
  body.has-mobile-shell[data-page="reporting"] .rpt-table-wrap.m-scroll-table {
    /* Override the legacy 16px padding so the fade gradient lines up
       with the visible right edge of the scrollable area. */
    padding: 0;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-table-wrap.m-scroll-table > table {
    font-size: 12px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-table-wrap.m-scroll-table > table th,
  body.has-mobile-shell[data-page="reporting"] .rpt-table-wrap.m-scroll-table > table td {
    padding: 9px 10px;
  }

  /* Card-mode tables inside reporting.html: lift the row to a tappable
     surface with the same anchor look as engineering tickets. */
  body.has-mobile-shell[data-page="reporting"] table[data-mobile="cards"] tr {
    background: #0f0f0f;
    border: 1px solid #1f1f1f;
    border-radius: 10px;
    padding: 12px 14px;
    margin-bottom: 10px;
  }
  body.has-mobile-shell[data-page="reporting"] table[data-mobile="cards"] td {
    font-size: 13.5px;
    padding: 6px 0;
    gap: 12px;
  }
  body.has-mobile-shell[data-page="reporting"] table[data-mobile="cards"] td:before {
    color: #888;
    font-size: 10px;
    letter-spacing: 0.18em;
  }
  /* The first cell in each card (the row anchor — venue / date / item) is
     pulled to a larger size with no eyebrow so it reads as a heading. */
  body.has-mobile-shell[data-page="reporting"] table[data-mobile="cards"] tbody td:first-child {
    font-size: 15px;
    font-family: 'Playfair Display', Georgia, serif;
    font-weight: 500;
    letter-spacing: 0.02em;
    padding-top: 0;
    margin-bottom: 4px;
    border-bottom: 1px solid #1a1a1a;
    padding-bottom: 8px;
  }
  body.has-mobile-shell[data-page="reporting"] table[data-mobile="cards"] tbody td:first-child:before {
    display: none;
  }
  /* Numeric cells stay right-aligned per the desktop convention so the
     metric reads after the eyebrow label. */
  body.has-mobile-shell[data-page="reporting"] table[data-mobile="cards"] td.num {
    text-align: right;
  }
  /* TOTAL row: keep the same card look but tint border gold so it
     stands apart from the per-venue rows. */
  body.has-mobile-shell[data-page="reporting"] table[data-mobile="cards"] tr:last-child:has(.highlight) {
    border-color: var(--m-gold-soft-2, rgba(196,154,58,0.22));
    background: rgba(196,154,58,0.04);
  }

  /* ─ AI Summary section ─────────────────────────────────────────────── */
  body.has-mobile-shell[data-page="reporting"] .rpt-ai-section {
    margin-bottom: 14px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-ai-header {
    flex-wrap: wrap;
    gap: 10px;
    padding: 12px 14px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-ai-header .rpt-btn-sky {
    min-height: var(--m-tap-min);
    padding: 8px 16px;
    font-size: 12px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-ai-body {
    padding: 14px;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-ai-content {
    font-size: 14px;
    line-height: 1.65;
  }

  /* ─ Sticky CTA ─────────────────────────────────────────────────────── */
  /* Mobile-only — the markup is gated by JS-injected element + this CSS.
     The CTA sits below the AI summary but visually sticks to the bottom
     of the scroll container; on iOS the safe-area is reserved. */
  body.has-mobile-shell[data-page="reporting"] .rpt-mobile-cta {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-mobile-cta .rpt-btn {
    min-height: var(--m-tap-min);
    padding: 10px 16px;
    font-size: 12px;
    letter-spacing: 0.12em;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-mobile-cta .rpt-btn-primary {
    flex: 1 1 100%;
  }
  body.has-mobile-shell[data-page="reporting"] .rpt-mobile-cta .rpt-btn-ghost {
    flex: 1 1 auto;
  }

  /* ─ Intelligence view (Mix / Margin / Costs) ──────────────────────── */
  /* When body has .intel-mode, .rpt-filters and the sticky CTA hide —
     the Intelligence view drives its own period/venue selectors and
     the Run button doesn't apply. */
  body.intel-mode.has-mobile-shell[data-page="reporting"] .rpt-mobile-cta {
    display: none;
  }

  /* Sub-nav pills — pull to full width so the 3 tabs split evenly. */
  body.has-mobile-shell[data-page="reporting"] .intel-subnav {
    width: 100%;
    overflow-x: auto;
    scrollbar-width: none;
  }
  body.has-mobile-shell[data-page="reporting"] .intel-subnav::-webkit-scrollbar { display: none; }
  body.has-mobile-shell[data-page="reporting"] .intel-subnav-pill {
    min-height: var(--m-tap-min);
    padding: 8px 18px;
    flex: 1 1 0;
  }

  /* Intel filter bar (period + venue + meta) stacks at narrow widths. */
  body.has-mobile-shell[data-page="reporting"] .intel-filter-bar {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    padding: 12px 12px;
  }
  body.has-mobile-shell[data-page="reporting"] .intel-filter-group {
    flex-wrap: wrap;
    gap: 8px;
  }
  body.has-mobile-shell[data-page="reporting"] .intel-period-toggle {
    flex: 1 1 auto;
    flex-wrap: wrap;
  }
  body.has-mobile-shell[data-page="reporting"] .intel-period-btn {
    min-height: 36px;
    padding: 7px 14px;
    font-size: 12px;
    flex: 1 1 auto;
  }
  body.has-mobile-shell[data-page="reporting"] .intel-venue-select {
    min-height: 36px;
    padding: 7px 10px;
    font-size: 13px;
    flex: 1 1 auto;
  }

  /* Intelligence cards are already responsive but tighten paddings. */
  body.has-mobile-shell[data-page="reporting"] .intel-card {
    padding: 14px;
  }
  body.has-mobile-shell[data-page="reporting"] .intel-card-head {
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 10px;
  }
  /* Refresh button inside the AI Insights card head wraps to its own row;
     give it a 44px tap target. */
  body.has-mobile-shell[data-page="reporting"] .intel-insights-card .rpt-btn-sky {
    min-height: var(--m-tap-min);
    padding: 8px 16px;
  }

  /* Mix donut + legend already stack via existing @media at line 960.
     Keep the donut tight so it doesn't dominate the viewport. */
  body.has-mobile-shell[data-page="reporting"] .intel-donut {
    width: 180px;
    height: 180px;
  }
  body.has-mobile-shell[data-page="reporting"] .intel-legend-row {
    grid-template-columns: 12px 1fr auto auto;
    font-size: 12px;
  }

  /* Margin quadrant: the existing @media drops to 260px; we re-state with
     the data-page selector to win specificity when both rules apply. */
  body.has-mobile-shell[data-page="reporting"] .margin-quadrant {
    height: 260px;
  }
  body.has-mobile-shell[data-page="reporting"] .margin-pair-row {
    grid-template-columns: 90px 1fr;
    gap: 8px;
  }

  /* Costs editor — the table is already a scroll-table by virtue of
     `.intel-costs-tablewrap { overflow-x: auto }`. Promote to the
     primitive so the first column (Item) sticks, the row tap-target is
     44px, and the cost-input doesn't overflow on tap. */
  body.has-mobile-shell[data-page="reporting"] .intel-costs-tablewrap {
    position: relative;
    -webkit-overflow-scrolling: touch;
  }
  /* The existing @media (line 970+) hides Venue/Category/Updated cols so
     the visible columns become: Item (1st) · Price · Cost · Margin.
     Sticky the Item column. */
  body.has-mobile-shell[data-page="reporting"] .intel-costs-table tbody td:nth-child(3),
  body.has-mobile-shell[data-page="reporting"] .intel-costs-table thead th:nth-child(3) {
    position: sticky;
    left: 0;
    background: #111;
    z-index: 1;
  }
  body.has-mobile-shell[data-page="reporting"] .intel-costs-table thead th:nth-child(3) {
    background: #0d0d0d;
    z-index: 2;
  }
  /* Cost input keeps a 36px touch height. */
  body.has-mobile-shell[data-page="reporting"] .intel-costs-table .cost-input {
    min-height: 36px;
    padding: 6px 8px;
    font-size: 13px;
  }
  /* Coverage banner tightens its paddings on mobile. */
  body.has-mobile-shell[data-page="reporting"] .intel-coverage-banner {
    padding: 10px 12px;
    font-size: 12.5px;
    line-height: 1.45;
  }

  /* Insight cards stack tighter on mobile. */
  body.has-mobile-shell[data-page="reporting"] .intel-insight {
    padding: 11px 13px;
  }
  body.has-mobile-shell[data-page="reporting"] .intel-insight-body {
    font-size: 13px;
    line-height: 1.55;
  }

  /* Modal (Costs CSV import result) — full-screen on mobile so the
     unmatched-rows list has room. */
  body.has-mobile-shell[data-page="reporting"] .intel-modal-body {
    max-width: 100%;
    width: 100%;
    height: 100vh;
    max-height: 100vh;
    border-radius: 0;
    border-left: 0;
    border-right: 0;
    padding: 16px 16px calc(16px + var(--m-safe-bottom));
  }
}

/* Hide the mobile CTA on desktop without any specificity contest. The CTA
   block is mobile-only, so the default state is `display: none`. */
.rpt-mobile-cta { display: none; }

/* ════════════════════════════════════════════════════════════════════════
   PHASE 4 — Desktop-only notice (admin tools: superadmin / security /
   database)
   ────────────────────────────────────────────────────────────────────────
   These three pages are low-frequency admin surfaces — auth reset, audit
   logs, raw DB browsing. Mobile-ifying them properly isn't worth the
   build (no one resets a user password from a phone), but managers might
   genuinely need to glance, so we don't hard-gate.

   Pattern:
     1. Page opts in with <body data-mobile-notice="best-on-desktop">.
     2. admin-mobile.js injects .m-desktop-notice as the first body child
        below 768px, unless sessionStorage says it was dismissed this tab
        session.
     3. The notice sits in flow at the top, pushing everything down — the
        existing topbar / hero / sidebar (sidebar already display:none on
        mobile via the Phase 1b rule) stays put underneath.
     4. "Continue anyway" dismisses for the tab session. "Take me home"
        navigates to '/'.

   Desktop (≥769px) — the DOM element may exist but never renders. The
   default-collapsed rule below covers any stray markup, and the only
   active rules are inside the max-width media query.
   ──────────────────────────────────────────────────────────────────────── */
.m-desktop-notice { display: none; }

@media (max-width: 768px) {
  body.has-mobile-shell .m-desktop-notice {
    display: block;
    position: relative;
    z-index: 100;
    background: #0C1525;
    color: #E6E1D8;
    border-bottom: 1px solid #1f2937;
    padding:
      calc(18px + env(safe-area-inset-top, 0))
      18px
      18px;
    font-family: 'Helvetica Neue', system-ui, sans-serif;
  }
  body.has-mobile-shell .m-desktop-notice__title {
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 17px;
    font-weight: 500;
    color: #fff;
    margin: 0 0 10px;
    letter-spacing: 0.01em;
  }
  body.has-mobile-shell .m-desktop-notice__body {
    font-size: 13.5px;
    line-height: 1.55;
    color: #B5AC9E;
    margin: 0 0 14px;
  }
  body.has-mobile-shell .m-desktop-notice__actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
  }
  body.has-mobile-shell .m-desktop-notice__btn {
    flex: 1 1 calc(50% - 4px);
    min-height: var(--m-tap-min, 44px);
    padding: 10px 14px;
    border-radius: 6px;
    border: 1px solid #2a3548;
    background: transparent;
    color: #E6E1D8;
    font-family: inherit;
    font-size: 13px;
    letter-spacing: 0.04em;
    cursor: pointer;
  }
  body.has-mobile-shell .m-desktop-notice__btn:active {
    background: rgba(255, 255, 255, 0.04);
  }
  body.has-mobile-shell .m-desktop-notice__btn--primary {
    background: #F7F5F2;
    color: #000;
    border-color: transparent;
    font-weight: 600;
  }
}

/* ════════════════════════════════════════════════════════════════════════
   PHASE 5 — Real-device polish (414px iPhone QA pass)
   ────────────────────────────────────────────────────────────────────────
   Three issues surfaced on a real phone after Phases 0–4 shipped:

     1. The desktop top-bar tabs (.nav-primary) were still rendering on
        mobile as a cramped horizontal scroll strip. The bottom-nav
        already covers panel switching (Today / Floor / Team / Reports /
        More), so the top strip was duplicative and visually noisy. We
        hide it entirely; the mobile top bar collapses to logo + bell +
        privacy + sign-out.

     2. The NANDA AI floating toggle (.ops-ai-mobile-btn) was anchored
        bottom-right, colliding with the FAB and lying on top of the
        BUDGET % indicator. Mirror it to bottom-LEFT and lift it above
        the bottom-nav + safe-area with the same clearance math the FAB
        uses, so the two floating buttons share the same baseline and
        the page content underneath stays untouched.

     3. .ops-subtab labels rendered at 11px uppercase with 0.2em letter-
        spacing — below the 13px / 44px floor we agreed in Phase 0.
        Drop uppercase, bump font-size to --m-text-sm, and bump vertical
        padding so each pill comfortably clears 44px.

   Desktop (>768px) is byte-identical: every rule sits inside the media
   query and is gated by body.has-mobile-shell.

   Trade-off worth flagging (see report): hiding .nav-primary entirely
   means QR Orders and Training Guide aren't reachable from the mobile
   manager portal — they aren't in the bottom-nav and aren't yet in the
   FAB sheet. Direct URLs still work. If we get a real-world request to
   reach them from a phone, add them to the FAB sheet as a follow-up.
   ──────────────────────────────────────────────────────────────────────── */

@media (max-width: 768px) {
  /* ── (1) Hide the legacy nav-tabs row on mobile ─────────────────────── */
  body.has-mobile-shell #main-nav .nav-primary { display: none; }
  /* Without nav-primary in the middle, the top bar reads as:
       [ logo ] ……………………… [ bell · privacy · sign-out ]                */
  body.has-mobile-shell #main-nav { justify-content: space-between; gap: 8px; }
  body.has-mobile-shell #main-nav .nav-right { margin-left: auto; }

  /* ── (2) NANDA AI floating toggle — mirror to bottom-left ───────────── */
  /* Geometry mirrors .mobile-fab so the two floating buttons share the
     same baseline; z-index sits below the FAB (110) and the FAB sheet
     (200) so the sheet covers the toggle cleanly when opened. */
  body.has-mobile-shell .ops-ai-mobile-btn {
    right: auto;
    left: 16px;
    bottom: calc(var(--m-nav-h) + var(--m-safe-bottom) + var(--m-fab-gap));
    z-index: 105;
    min-height: var(--m-tap-min);
    padding: 10px 18px;
  }
  /* When the AI sidebar is open, the toggle should not poke through —
     the sidebar's own ↓ close button is the active control. The desktop
     CSS toggles `.mobile-open` on #ops-ai-sidebar; use :has() to hide
     the toggle when that's the case. Safari ≥15.4, Chrome ≥105 — fine
     in 2026 (cf. CLAUDE.md piège 15). */
  body.has-mobile-shell:has(#ops-ai-sidebar.mobile-open) .ops-ai-mobile-btn {
    display: none;
  }

  /* ── (3) Ops sub-tabs — readable, tappable ──────────────────────────── */
  body.has-mobile-shell #ops-subtabs {
    padding: 0 12px;
    gap: 2px;
  }
  body.has-mobile-shell .ops-subtab {
    font-size: var(--m-text-sm, 13px);
    letter-spacing: 0.02em;
    text-transform: none;
    padding: 14px 14px;
    min-height: 48px;
  }
}

/* ════════════════════════════════════════════════════════════════════════
   PHASE 7 — AI Intelligence cluster error state
   ────────────────────────────────────────────────────────────────────────
   The three `.intel-card` blocks (Today's Highlight / Watch Out For /
   Take Action Now) at the top of #panel-ops hit a Claude server-side
   generator that takes 10–30s normally. Pre-Phase-7, if the request
   hung or errored, the cards sat on "Analyzing…" forever. Phase 7 adds
   a 45s client-side abort + a graceful empty state.

   This modifier desaturates the cluster when JS toggles
   .intel-card--error onto each card: the coloured left-edge accents
   (sage / orange / sky) collapse to a muted grey so the trio reads as
   "we tried, didn't get there" rather than "live insight." Title goes
   from bold off-white to a softer regular weight; body italicizes for
   the "Tap refresh to try again." line.

   No media query — this is a JS-driven UI state, not a viewport thing.
   The selector uses the chained class form (.intel-card.intel-card--error)
   so it has the same specificity as the per-card colour rules in
   styles.css (.intel-highlight / .intel-watch / .intel-action) and
   wins by source order (admin-mobile.css is linked second).
   ──────────────────────────────────────────────────────────────────────── */
.intel-card.intel-card--error {
  border-left-color: rgba(150, 150, 150, 0.4);
}
.intel-card--error .intel-label {
  color: rgba(150, 150, 150, 0.55);
}
.intel-card--error .intel-title {
  color: rgba(230, 225, 216, 0.6);
  font-weight: 400;
}
.intel-card--error .intel-body {
  color: rgba(150, 150, 150, 0.7);
  font-style: italic;
}
.intel-card--error .intel-metric {
  color: rgba(150, 150, 150, 0.5);
}

/* ════════════════════════════════════════════════════════════════════════
   PHASE 8 — Scroll-aware hide/reveal for floating buttons
   ────────────────────────────────────────────────────────────────────────
   On #panel-ops (Floor) the period selector + sub-tabs + tax toggle
   sit directly underneath the two floating buttons (.mobile-fab in
   bottom-right, .ops-ai-mobile-btn in bottom-left, both lifted above
   the bottom-nav by the Phase 0 / Phase 5 clearance math). On a real
   phone the floating buttons can intercept thumb taps that were meant
   for the controls underneath.

   Standard mobile FAB pattern: hide on scroll-down, reveal on scroll-
   up. admin-mobile.js#wireScrollHide adds/removes `is-scrolling-down`
   on <body> from a capture-phase scroll listener. This block consumes
   that class.

   Scope:
   - .mobile-fab (Quick actions FAB) — yes
   - .ops-ai-mobile-btn (NANDA AI toggle) — yes
   - .mobile-bottom-nav — NO. The bottom-nav is a navigation surface,
     not an action; hiding it on scroll would be hostile to mobile UX.
   - .mobile-notif-bell — NO. Lives in the top bar, not a floating
     button, and is always relevant.

   The transition on the base selectors is the same value used during
   the .is-scrolling-down hide — declaring it on both states means the
   reveal animates at the same pace as the hide instead of snapping
   back at the Phase 0 default of 0.15s. Source order beats Phase 0
   for the same .mobile-fab selector (Phase 8 is later in the file).
   ──────────────────────────────────────────────────────────────────────── */
@media (max-width: 768px) {
  body.has-mobile-shell .mobile-fab,
  body.has-mobile-shell .ops-ai-mobile-btn {
    transition:
      transform 220ms cubic-bezier(.4, 0, .2, 1),
      opacity   200ms ease;
  }
  body.has-mobile-shell.is-scrolling-down .mobile-fab,
  body.has-mobile-shell.is-scrolling-down .ops-ai-mobile-btn {
    transform: translateY(140%);
    opacity: 0;
    /* Required: the slide-out window is ~220ms during which the button
       is invisible but still in flow. Without pointer-events:none it
       can intercept taps on the period selector underneath. */
    pointer-events: none;
  }
}

/* ════════════════════════════════════════════════════════════════════════
   PHASE 9 — Unified Floor scroll (#panel-ops)
   ────────────────────────────────────────────────────────────────────────
   The actual blocker behind the floating-button complaints: on mobile,
   the Floor panel has TWO scrolling regions stacked inside one viewport-
   height parent:

     #panel-ops              (.panel.active is height:100%, overflow:hidden)
       #ops-intelligence     (flex-shrink: 0 — pinned)
       #ops-body
         #ops-subtabs        (sticky)
         #ops-period-bar
         #ops-split          (overflow-y: auto ← inner scroller)
           #ops-dashboard
             …KPIs, charts…

   Once Phase 7's intel cards fill in (~600px of content stacked
   vertically on a phone), the pinned strip eats the viewport. #ops-split
   is left with a 100–150px slit to scroll the whole dashboard inside.
   The period selector, tax toggle, sub-tabs, KPIs, and the chart all
   become hard or impossible to reach with a thumb.

   The fix is structural: lift the scroll to #panel-ops itself and let
   everything inside flow as one natural column. The intel strip, the
   sub-tabs, the period bar, the KPIs, and the chart all share one
   scroll surface. Phase 8's capture-phase listener picks up the
   relocated scroll automatically (capture catches scroll on any
   descendant of document).

   Padding-bottom reserves clearance for the FAB + bottom-nav stack so
   the last KPI/chart row doesn't end up underneath the floating
   buttons. Math matches the FAB geometry from Phase 0:

     bottom-nav (--m-nav-h)            64
   + safe-area (--m-safe-bottom)    0–34 (iOS)
   + FAB size  (--m-fab-size)          56
   + 2× FAB gap (--m-fab-gap × 2)      28
                                    ----
                                     ~148–182 px

   Desktop (≥769px) is byte-identical — every rule below sits inside
   the max-width 768px media query.
   ──────────────────────────────────────────────────────────────────────── */
@media (max-width: 768px) {
  /* The panel itself is now the only scrolling container. */
  body.has-mobile-shell #panel-ops {
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
    padding-bottom: calc(
      var(--m-nav-h) +
      var(--m-safe-bottom) +
      var(--m-fab-size) +
      var(--m-fab-gap) * 2
    );
  }

  /* Intelligence strip — flows naturally as the first column item.
     The desktop flex-shrink:0 stays effectively in force (we don't
     need it to shrink), but we explicitly clear any internal overflow
     so the strip can't accidentally clip its own card content. */
  body.has-mobile-shell #ops-intelligence {
    flex: 0 0 auto;
    overflow: visible;
  }

  /* Ops body + split: parent owns the scroll now. These two used to
     compete for height with `flex: 1` + `overflow: hidden`; on mobile
     they're just inline blocks in the column. */
  body.has-mobile-shell #ops-body {
    flex: 0 0 auto;
    overflow: visible;
    min-height: 0;
  }
  body.has-mobile-shell #ops-split {
    flex: 0 0 auto;
    overflow: visible;
  }

  /* Dashboard already had `overflow-y: visible` on mobile from
     styles.css:3159, but it's still `flex: 1` from the desktop rule.
     Pin to its natural height so the parent scroll governs. */
  body.has-mobile-shell #ops-dashboard {
    flex: 0 0 auto;
    overflow: visible;
  }

  /* #ops-subtabs (sticky top:0) inherits its sticky context from the
     nearest scrolling ancestor — now #panel-ops. The chain between
     subtabs and panel-ops is all overflow:visible (per the rules
     above), so sticky pins to the panel scroll edge as expected. No
     extra rule needed. */
}



