/* ==========================================================================
   Mobile TabBar — Fixed bottom navigation for <768px
   2 fixed tabs (Home | Fergi AI) + Context controls slot above tabs.
   Hidden on desktop. Hidden during PresentationPlayer playback.

   UNIVERSAL MOBILE-MODAL BOTTOM RESERVATION
   (deliberate-flooding-meridian I3, 2026-04-19)
   --------------------------------------------------------------------------
   Tab-bar z-index 11000 sits above ALL non-msp mobile overlays. Any overlay
   that needs bottom-edge content visible MUST reserve --mobile-control-panel-height
   in its bottom padding. Use the .ffa-mobile-modal-bottom-reserved utility
   class, OR set padding-bottom directly on the overlay root.
   Exception: overlays using MobileSheet3Panel (.msp-root) — the scaffold hides
   tab-bar automatically via body:has(.msp-root) so no reservation needed.
   Exception: .mobile-context-controls (z 10999) — sits just below tab-bar
   so chain-nav RETURN button can render there during a chain over msp-root.
   ========================================================================== */

:root {
  /* Universal reservation height for non-msp mobile modals.
     Tab-bar (48px per realizing-mobile-chrome-contract S1 D3, 2026-05-14)
     + safe-area-inset-bottom + 16px breathing room.
     Used by .mmi-overlay, .mft-overlay, .mvt-overlay, .mpft-overlay,
     .msds-overlay — see each modal's CSS for application.
     KEEP IN SYNC with --mobile-tab-bar-height in mobile.css.

     excavating-mobile-bookends S2 D2 (2026-05-15) — cascade-default desync
     audit pattern per [[feedback_top_nav_white_smoking_gun]]: every
     `var(--mobile-tab-bar-height, <fallback>)` instance in this file MUST
     carry `48px` as its fallback to match the canonical `:root` value at
     `mobile.css:78`. Sibling stylesheets (mobile-library.css,
     mobile-presentation.css, mobile-story-scrub.css,
     show-edges-modal.css, mobile-sheet-3panel.css) still carry stale `56px`
     fallbacks; they are latent smoking guns (var is currently defined so the
     fallback is dormant) flagged for SR Refinement Delta out-of-scope sweep.
     Iron-Law: the fallback is its OWN audit surface independent of var-
     definition state. */
  --mobile-control-panel-height: calc(48px + env(safe-area-inset-bottom, 0px));
  --mobile-modal-bottom-reservation: calc(var(--mobile-control-panel-height) + 16px);
}

/* Utility class for mobile modal body containers that scroll independently
   of the modal root. Scrollable bodies use this to ensure their last item
   is reachable above the tab-bar. */
.ffa-mobile-modal-bottom-reserved {
  padding-bottom: var(--mobile-modal-bottom-reservation) !important;
}

/* ── Desktop: never show ── */
.mobile-tab-bar,
.mobile-context-controls {
  display: none !important;
}

@media (max-width: 767px) {

  /* ── Universal Modal Chrome Contract — bottom-band palette
        (reflective-swimming-ripple S1 D1-D, 2026-05-11 — Issue #1;
         sweeping-unifying-canopy S2 — Issues #4 + #19b, 2026-05-11) ──
     The bottom band color-matches the active modal palette. Each tab sets
     body[data-modal-palette="<id>"] via tab-bar.js onNavigate; the selectors
     below tint the bg + accent vars. This is the universal "modal-aware
     facilitator" surface — bottom panel reads as part of whatever main modal
     is foregrounded.

     Modal-specific facilitator catalog (per Joe directive 2026-05-11 /listen
     "the FFA mobile control panel is a universal facilitator that complements
     and extends whatever main modal it is appearing in"):
       stories  → indigo tint  + narrator slot (when story open)
       media    → indigo tint  + (UMI overlays mount narrator slot)
       library  → indigo tint  + Bundle download in header slot (S1 D4)
       family   → warm amber border on near-white + Search affordance inline
       fergi-ai → PINK band (S2 D3, Issue #19b) — matches top band #831843
       hamburger→ TabBar persists below drawer (S2 D1, Issue #3)
  */
  :root {
    --mobile-tab-bar-bg: #ffffff;
    --mobile-tab-bar-border-top: rgba(0, 0, 0, 0.06);
    --mobile-tab-bar-shadow: 0 -2px 8px rgba(0, 0, 0, 0.08);
    --mobile-tab-bar-fg-inactive: #9ca3af;
    --mobile-tab-bar-fg-active: var(--ffa-tier-1-base, #4338CA);
  }

  /* Stories / Media / Library — closing-realization-gate S2 D5
     (2026-05-13 PM) distinct-palette ship. Joe-approved per S1 D8 lock:
     Stories=plum #2d1b3d, Media=forest #1a3d2e, Library=espresso #2d1f15.
     Splits the prior shared-indigo rule into three single-modal rules so
     each modal carries its OWN bottom-band palette continuous with its
     OWN top-band (universal-pattern rules #7 + #8: per-modal DISTINCT
     palette + chrome COMPLEMENTS content). Active-tab accent stays soft
     cream `#fde68a` across all three so the active tab read remains
     legible against any saturated deep-tone bookend. Shadows tinted to
     match each hue. */
  body[data-modal-palette="stories"] {
    --mobile-tab-bar-bg: #2d1b3d;
    --mobile-tab-bar-border-top: rgba(255, 255, 255, 0.10);
    --mobile-tab-bar-fg-inactive: rgba(255, 255, 255, 0.65);
    --mobile-tab-bar-fg-active: #fde68a;
    --mobile-tab-bar-shadow: 0 -2px 8px rgba(45, 27, 61, 0.45);
  }
  body[data-modal-palette="media"] {
    --mobile-tab-bar-bg: #1a3d2e;
    --mobile-tab-bar-border-top: rgba(255, 255, 255, 0.10);
    --mobile-tab-bar-fg-inactive: rgba(255, 255, 255, 0.65);
    --mobile-tab-bar-fg-active: #fde68a;
    --mobile-tab-bar-shadow: 0 -2px 8px rgba(26, 61, 46, 0.45);
  }
  body[data-modal-palette="library"] {
    --mobile-tab-bar-bg: #2d1f15;
    --mobile-tab-bar-border-top: rgba(255, 255, 255, 0.10);
    --mobile-tab-bar-fg-inactive: rgba(255, 255, 255, 0.65);
    --mobile-tab-bar-fg-active: #fde68a;
    --mobile-tab-bar-shadow: 0 -2px 8px rgba(45, 31, 21, 0.45);
  }

  /* Family — deep teal bookend matching the #0a2e2e top band
     (realizing-mobile-modals S1 D5, 2026-05-13). Same Joe directive as
     above; the prior warm-amber-on-near-white tab bar read as "white"
     against the dark-teal substrate above it. The deep-teal bookend
     completes the modal envelope so Family modal reads as one
     continuous palette: dark teal top band, dark teal main panel
     substrate (`.fnav-substrate`), dark teal bottom band. Active-tab
     accent uses the Family warm-amber `--fnav-amber` so the tab read
     stays brand-coherent. */
  body[data-modal-palette="family"] {
    --mobile-tab-bar-bg: #0a2e2e;
    --mobile-tab-bar-border-top: rgba(232, 160, 64, 0.32);
    --mobile-tab-bar-fg-inactive: rgba(255, 255, 255, 0.55);
    --mobile-tab-bar-fg-active: #e8a040;
    --mobile-tab-bar-shadow: 0 -2px 8px rgba(10, 46, 46, 0.45);
  }

  /* Portraits (Greg G-146) — the room's ground, with its own amber accent (--ffvr-accent #D49A38). */
  body[data-modal-palette="portraits"] {
    --mobile-tab-bar-bg: #071c1e;
    --mobile-tab-bar-border-top: rgba(63, 184, 178, 0.28);
    --mobile-tab-bar-fg-inactive: rgba(240, 252, 251, 0.62);
    --mobile-tab-bar-fg-active: #d49a38;
    --mobile-tab-bar-shadow: 0 -2px 8px rgba(3, 47, 49, 0.45);
  }

  /* Fergi AI — DEEP PINK bottom band matching the #831843 top band
     (sweeping-unifying-canopy S2 D3, Issue #19b, 2026-05-11). Joe directive
     /listen "Fergi AI modal: header + bottom panel pink." Was a subtle
     near-white tint pre-S2; now a saturated brand band that visually
     bookends the modal between matched pink chrome top + bottom. Active
     tab accent flips to soft cream-on-pink so the active-tab read remains
     legible against the dark band. */
  body[data-modal-palette="fergi-ai"] {
    --mobile-tab-bar-bg: #831843;
    --mobile-tab-bar-border-top: rgba(255, 255, 255, 0.18);
    --mobile-tab-bar-fg-inactive: rgba(255, 255, 255, 0.65);
    --mobile-tab-bar-fg-active: #fde68a;
    --mobile-tab-bar-shadow: 0 -2px 8px rgba(131, 24, 67, 0.45);
  }

  /* cementing-modal-chrome-fleet S1 D2 (2026-05-13) — Settings deep slate
     bottom band matching the #1e293b top band. Settings stands on its own
     palette so the modal envelope reads as one continuous deep-slate
     surface from iPhone status bar through .top-nav, across the msv-wrap
     content body, and bookending in the bottom tab-bar. Active tab accent
     uses soft cream so it remains legible against the slate. */
  body[data-modal-palette="settings"] {
    --mobile-tab-bar-bg: #1e293b;
    --mobile-tab-bar-border-top: rgba(255, 255, 255, 0.12);
    --mobile-tab-bar-fg-inactive: rgba(255, 255, 255, 0.55);
    --mobile-tab-bar-fg-active: #fde68a;
    --mobile-tab-bar-shadow: 0 -2px 8px rgba(30, 41, 59, 0.45);
  }

  /* cementing-modal-chrome-fleet S1 D5 (2026-05-13) — Train Station bottom
     band. Pre-S1 TS palette set .top-nav blue (#0f3057) + theme-color blue
     but the bottom tab-bar inherited the near-white default — visually
     broke the modal envelope (blue top + white bottom = unmatched chrome).
     This completes the TS universal-contract envelope by tinting the
     bottom band TS-blue to bookend the in-page header. Active-tab accent
     uses soft cream so it remains legible against the saturated band. */
  body[data-modal-palette="train-station"] {
    --mobile-tab-bar-bg: #0f3057;
    --mobile-tab-bar-border-top: rgba(255, 255, 255, 0.12);
    --mobile-tab-bar-fg-inactive: rgba(255, 255, 255, 0.55);
    --mobile-tab-bar-fg-active: #fde68a;
    --mobile-tab-bar-shadow: 0 -2px 8px rgba(15, 48, 87, 0.45);
  }

  /* cementing-modal-chrome-fleet S1 D3 (2026-05-13) — 'default' palette
     bottom band, dark teal #0a2e2e matches the .top-nav default fallback.
     Applied when tab-bar.js onNavigate() lands on a view without a
     VIEW_TO_TAB entry (dashboard, admin, about, leaderboard, sandbox, etc.)
     — the palette is set to 'default' rather than stripped, so this rule
     paints the bottom band continuous with the top band's dark-teal
     fallback. Result: every mobile view has a coherent two-band envelope,
     even views without their own brand palette. */
  body[data-modal-palette="default"] {
    --mobile-tab-bar-bg: var(--ffa-band-default, #0a2e2e);
    --mobile-tab-bar-border-top: rgba(255, 255, 255, 0.08);
    --mobile-tab-bar-fg-inactive: rgba(255, 255, 255, 0.55);
    --mobile-tab-bar-fg-active: #fde68a;
    --mobile-tab-bar-shadow: 0 -2px 8px rgba(10, 46, 46, 0.45);
  }

  .mobile-tab-bar {
    display: flex !important;
    position: fixed;
    /* Base rule: bottom:0 anchors to physical screen bottom in Safari tab mode.
       PWA standalone mode pulls the bar through the safe area — see
       @media (display-mode: standalone) override at end of file (2026-05-16
       Iron-Law fix #2). Height + padding-bottom use env() inline (not through
       --mobile-safe-area-bottom var) because iOS Safari resolves env() inside
       a custom property ONCE at :root parse time, leaving it stuck at the
       0px fallback on cold mount + visualViewport changes (2026-05-16 fix #1). */
    bottom: 0;
    left: 0;
    right: 0;
    height: calc(var(--mobile-tab-bar-height, 48px) + env(safe-area-inset-bottom, 0px));
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: var(--mobile-tab-bar-bg, #ffffff);
    border-top: 1px solid var(--mobile-tab-bar-border-top, #e5e7eb);
    z-index: 11000;
    /* realizing-mobile-chrome-contract S1 D3 (2026-05-14) — Iron-Law tightening.
       `align-items: stretch` previously stretched each .mobile-tab-item button
       to the full bar height, surfacing internal flex slack as palette-colored
       band above the icons (especially visible at the wider 56px height).
       `align-items: center` keeps the icon column locked at its natural
       content height (~44px icon+label) and centers it vertically — the bar
       reads as a tight icon row at exactly the new 48px height with no
       distributed-slack band above. */
    align-items: center;
    justify-content: space-around;
    box-shadow: var(--mobile-tab-bar-shadow, 0 -2px 8px rgba(0, 0, 0, 0.08));
    transition: background 220ms ease, border-top-color 220ms ease;
  }

  /* ── Context controls slot (above tab bar) ── */
  .mobile-context-controls {
    display: block !important;
    position: fixed;
    /* Base rule: positions context-controls above the tab bar in Safari mode
       (env-additive offset). PWA standalone override below removes the env()
       addition because in PWA mode the tab bar is pulled through the safe
       area (see @media display-mode:standalone override at end of file). */
    bottom: calc(var(--mobile-tab-bar-height, 48px) + env(safe-area-inset-bottom, 0px));
    left: 0;
    right: 0;
    z-index: 10999;
    background: #f8fafc;
    border-top: 1px solid #e5e7eb;
    padding: 8px 16px;
  }

  .mobile-context-controls[style*="display: none"] {
    display: none !important;
  }

  /* Chain RETURN button retired quiet-pinning-keystone S1 D11 (2026-04-21 PM)
     — the action moved to an MCP middle slot ("Back to <origin>") via
     tab-bar.js setChainReturn → MobileControlPanel.pushSlots. The purple
     horizontal bar above the tab bar created visual clutter outside the
     primary control surface. */

  /* ── Segment toggle (pill-style, used by Stories Narratives/Galleries) ── */
  .context-segment {
    display: flex;
    background: #e5e7eb;
    border-radius: 8px;
    padding: 2px;
    gap: 2px;
  }

  .context-segment-btn {
    flex: 1;
    padding: 7px 0;
    border: none;
    border-radius: 6px;
    background: transparent;
    font-size: 13px;
    font-weight: 600;
    color: #6b7280;
    cursor: pointer;
    transition: all 0.15s ease;
    -webkit-tap-highlight-color: transparent;
  }

  .context-segment-btn.active {
    background: #ffffff;
    color: #1f2937;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  }

  /* Extra bottom padding when context controls are showing */
  body.has-context-controls.has-mobile-tab-bar .main-content {
    padding-bottom: calc(var(--mobile-tab-bar-height, 48px) + var(--mobile-safe-area-bottom, 0px) + 52px) !important;
  }

  /* ── Tab items — equal-width columns, count varies (standardizing-mobile-chrome
        S1, #2). The bar is no longer a fixed 5-column nav; at rest it carries
        just Stories + Import, and the modal-dynamic middle (MCP slots) grows it.
        `flex: 1 1 0` distributes width evenly across whatever N items are present
        (2 → 50/50, 3 → thirds, …) — the same behavior the .mcp-has-extras rule
        below already used, unified here so rest + extras share one flex model. ── */
  .mobile-tab-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    flex: 1 1 0;
    padding: 6px 0 4px;
    border: none;
    background: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    /* sweeping-unifying-canopy S2 D2 (Issue #4, 2026-05-11) — color reads from
       the palette-driven --mobile-tab-bar-fg-inactive var so Fergi AI's pink
       bottom band lifts the icon glyphs to cream-on-pink instead of the
       default mid-gray (which would vanish into the dark band). */
    color: var(--mobile-tab-bar-fg-inactive, #9ca3af);
    text-decoration: none;
    min-width: 0;
    position: relative;
    transition: color 0.15s ease;
  }

  .mobile-tab-item:active {
    background: rgba(0, 0, 0, 0.04);
  }

  /* ── Icons (filled + outline variants, swap on active) ── */
  .mobile-tab-icon {
    width: 24px;
    height: 24px;
    margin-bottom: 3px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  .mobile-tab-icon svg {
    width: 24px;
    height: 24px;
    color: currentColor;
  }

  .mobile-tab-icon-filled {
    display: none;
  }
  .mobile-tab-icon-outline {
    display: inline-flex;
  }
  .mobile-tab-item.active .mobile-tab-icon-filled {
    display: inline-flex;
  }
  .mobile-tab-item.active .mobile-tab-icon-outline {
    display: none;
  }

  /* ── Label (always visible in State 1) ── */
  .mobile-tab-label {
    font-size: 10px;
    font-weight: 500;
    line-height: 1;
    letter-spacing: 0.02em;
    opacity: 1;
    max-height: 14px;
  }

  /* ── Active state — accent underline + palette-aware fg colour.
        sweeping-unifying-canopy S2 D2 (Issue #4, 2026-05-11) — active tab
        accent reads from --mobile-tab-bar-fg-active so each modal palette
        keeps the active-tab read legible (indigo on near-white default,
        cream on pink for Fergi AI). ── */
  .mobile-tab-item.active {
    color: var(--mobile-tab-bar-fg-active, var(--ffa-tier-1-base, #4338CA));
    font-weight: 600;
  }
  .mobile-tab-item.active::after {
    content: '';
    position: absolute;
    left: 50%;
    bottom: 0;
    transform: translateX(-50%);
    width: 22px;
    height: 2px;
    border-radius: 1px;
    background: var(--mobile-tab-bar-fg-active, var(--ffa-tier-1-base, #4338CA));
  }
  .mobile-tab-item.active .mobile-tab-label {
    font-weight: 600;
  }

  /* ── Fergi AI legacy class (retained for any other surface that targets it) ── */
  .mobile-tab-ai {
    color: var(--ffa-tier-2-base, #7c3aed);
  }

  /* ── Body padding so content doesn't sit under TabBar ── */
  body.has-mobile-tab-bar .main-content {
    padding-bottom: calc(var(--mobile-tab-bar-height, 48px) + var(--mobile-safe-area-bottom, 0px) + 8px) !important;
  }

  /* ── Mobile Control Panel dynamic slots (nimble-harmonizing-compass S1) ──
     mobile-control-panel.js injects .mcp-slot, .mcp-done-btn, .mcp-cancel-btn
     buttons into .mobile-tab-bar alongside Home + Fergi AI. They re-use
     .mobile-tab-item layout (icon + label, flex:1 1 50%), so adding N buttons
     simply shrinks each to 1/(2+N) of bar width. The bar gains .mcp-has-extras
     once any slot or Done/Cancel is registered so label visibility can scale
     sensibly. Bookend buttons (Cancel | ... | Done) get a violet accent. */
  .mobile-tab-bar.mcp-has-extras .mobile-tab-item {
    flex: 1 1 0;
    min-width: 44px;
  }
  .mobile-tab-bar.mcp-has-extras .mobile-tab-label {
    opacity: 1;
    max-height: 14px;
    font-size: 9px;
  }
  /* Greg Stage 0 (rendering-nonbio-episodes, 2026-10-15, FergiPhone glass): a control-panel slot was hard-coded indigo
     #4338CA, the accent of the old WHITE bar. Since the per-modal palettes went dark (closing-realization-gate S2 D5)
     every slot on the Stories / Media / Library / Family bars read indigo-on-dark — the biography folio's "More" measured
     #4338CA on #2d1b3d, 2.0:1. A slot is a live control, so it takes the palette's ACTIVE accent: indigo on the white
     default (unchanged), the locked cream #fde68a on the dark bands. Pinned by tests/mobile/mcp-slot-palette-contrast.test.js. */
  .mobile-tab-bar .mcp-slot {
    color: var(--mobile-tab-bar-fg-active, #4338CA);
    animation: mcp-slot-fade-in 0.18s ease-out;
  }
  .mobile-tab-bar .mcp-bookend {
    color: #7c3aed;
    animation: mcp-slot-fade-in 0.18s ease-out;
  }
  .mobile-tab-bar .mcp-cancel-btn {
    color: #6b7280;
  }
  .mobile-tab-bar .mcp-done-btn {
    color: #ffffff;
    background: linear-gradient(135deg, #4338CA 0%, #7c3aed 100%);
  }
  .mobile-tab-bar .mcp-done-btn .mobile-tab-label {
    color: #ffffff;
  }
  @keyframes mcp-slot-fade-in {
    from { opacity: 0; transform: translateY(4px); }
    to   { opacity: 1; transform: translateY(0); }
  }

  /* ── Popup-mode slot surface (luminous-branching-compass S1) ──
     A slot with `popup: {render, onOpen, onClose}` opens a consumer-rendered
     dialog above the tab bar on tap. Backdrop z 11400 dims the page and handles
     tap-outside dismiss; container z 11500 hosts consumer content. Both sit
     ABOVE the tab bar (11000) and above .mobile-stop-talking (11001) but
     remain BELOW the welcome floater narration slot's active state (which
     is itself the popup trigger, not a competing surface).

     Prefers-reduced-motion honored: the slide/fade animation is skipped when
     the user asks for reduced motion — the popup just pops in at rest. */
  .mcp-popup-backdrop {
    position: fixed;
    inset: 0;
    z-index: 11400;
    background: rgba(0, 0, 0, 0.48);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    animation: mcp-popup-fade 140ms ease-out;
  }
  .mcp-popup {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: calc(var(--mobile-tab-bar-height, 48px) + var(--mobile-safe-area-bottom, 0px) + 14px);
    z-index: 11500;
    width: min(92vw, 420px);
    max-height: calc(100vh - var(--mobile-tab-bar-height, 48px) - var(--mobile-safe-area-bottom, 0px) - 40px);
    overflow-y: auto;
    background: #ffffff;
    border-radius: 16px;
    box-shadow: 0 18px 48px rgba(0, 0, 0, 0.35), 0 2px 6px rgba(0, 0, 0, 0.12);
    padding: 18px 16px;
    animation: mcp-popup-rise 180ms ease-out;
  }
  /* The toolkit's ✕ on a sheet-sized popup (G-154): pinned to the top-right while the content scrolls. */
  .mcp-popup-close {
    position: sticky; top: -8px; float: right; z-index: 2;
    width: 44px; height: 44px; margin: -10px -8px 0 8px; padding: 0;
    display: grid; place-items: center; border: none; border-radius: 50%;
    background: rgba(0, 0, 0, 0.32); color: #f2f4f5;
    -webkit-tap-highlight-color: transparent;
  }
  .mcp-popup-close:active { background: rgba(0, 0, 0, 0.55); }
  .mcp-popup:focus {
    outline: none;
  }
  .mcp-popup .mcp-popup-title {
    margin: 0 0 12px 0;
    font-size: 16px;
    font-weight: 700;
    color: #1f2937;
    text-align: center;
    letter-spacing: 0.01em;
  }
  @keyframes mcp-popup-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
  @keyframes mcp-popup-rise {
    from { transform: translate(-50%, 12px); opacity: 0; }
    to   { transform: translate(-50%, 0);    opacity: 1; }
  }
  @media (prefers-reduced-motion: reduce) {
    .mcp-popup-backdrop,
    .mcp-popup {
      animation: none;
    }
  }

  /* ── Hide during presentation playback ── */
  body.presentation-active .mobile-tab-bar,
  body.presentation-active .mobile-context-controls {
    display: none !important;
  }

  /* anchoring-mobile-stagecraft S4 — #3/#6: the Pocket Stage is explicitly NOT
     a presentation-active hide exception (Joe directive 2026-07-05 — "the
     universal control panel should appear at the bottom of this modal, as it
     should at the bottom of most mobile modals"). On the produced Pocket Stage
     (body.pocket-restack) the bar stays visible even if a stray
     presentation-active class is set.
     standardizing-mobile-chrome S1 (#2): the old body.pocket-stage-panel
     [data-tab="media"|"family"] suppression rule that used to hide those nav
     tabs in-modal is REMOVED — Media/Family/Library are no longer bar tabs at
     all (drawer-only now), so there is nothing to suppress. The bar already
     reads Stories · [dynamic middle] · Import structurally. */
  body.pocket-restack.presentation-active .mobile-tab-bar { /* anchoring-mobile-stagecraft S4 */
    display: flex !important;
  }

  /* ── The Pocket Stage's own bottom band (Joe directive 2026-08-30, live walk:
     "the white bar should be dark in story playback") ──

     The rule directly above makes the bar VISIBLE during a Pocket story; it was
     never given a palette to be visible IN. With no `--mobile-tab-bar-bg` set
     for this surface the base rule falls through to its `#ffffff` default, so a
     solid white slab sat under a dusk-toned story — the same defect the Family
     modal had, described at the top of this file: "the prior warm-amber-on-
     near-white tab bar read as 'white' against the dark-teal substrate above
     it." A visibility exception was added without the matching envelope.

     Values are taken from the Pocket top bar (`pocket-stage.css` § pocket top
     bar) rather than invented, so the story is bookended by one continuous
     palette — deep teal above, deep teal below — per universal-pattern rules #7
     (per-modal DISTINCT palette) and #8 (chrome COMPLEMENTS content). The
     active accent stays the system's cream `#fde68a`, as on every other
     saturated deep-tone band, and the inactive tone is drawn from the top bar's
     own `#cfe6e6` control colour so both bands read as one surface.

     Scoped to `.presentation-active` deliberately: only PLAYBACK is dark. Out
     of playback the Pocket Stage keeps whatever palette its modal sets, so this
     cannot leak into the browse chrome. Two classes also beat any competing
     single-attribute `body[data-modal-palette=...]` on specificity, which a
     bare `.pocket-restack` would only tie with — and lose to by source order. */
  body.pocket-restack.presentation-active {
    --mobile-tab-bar-bg: rgba(9, 31, 36, 0.96);
    --mobile-tab-bar-border-top: rgba(109, 212, 212, 0.16);
    --mobile-tab-bar-fg-inactive: rgba(207, 230, 230, 0.68);
    --mobile-tab-bar-fg-active: #fde68a;
    --mobile-tab-bar-shadow: 0 -2px 10px rgba(9, 31, 36, 0.5);
  }

  body.presentation-active .main-content {
    padding-bottom: 0 !important;
  }

  /* ── Stop Talking floating control (mobile-stop-talking.js) ──
     Shown whenever NarratorAudio is actively playing. Sits above the tab-bar
     (z 11001 > tab-bar 11000) so it remains thumb-reachable in any view or
     modal, yet never obscures the persistent Home / Fergi AI controls. */
  .mobile-stop-talking {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: calc(var(--mobile-tab-bar-height, 48px) + var(--mobile-safe-area-bottom, 0px) + 12px);
    z-index: 11001;
    display: none;
    align-items: center;
    gap: 8px;
    min-height: 44px;
    padding: 10px 18px;
    border: none;
    border-radius: 999px;
    background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
    color: #ffffff;
    font-size: 14px;
    font-weight: 700;
    letter-spacing: 0.02em;
    cursor: pointer;
    box-shadow: 0 8px 24px rgba(220, 38, 38, 0.45), 0 2px 6px rgba(0, 0, 0, 0.25);
    -webkit-tap-highlight-color: transparent;
    animation: mst-pulse 1.4s ease-in-out infinite;
  }
  .mobile-stop-talking:active {
    transform: translateX(-50%) scale(0.96);
  }
  .mobile-stop-talking .mst-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .mobile-stop-talking .mst-icon svg {
    display: block;
  }
  @keyframes mst-pulse {
    0%, 100% { box-shadow: 0 8px 24px rgba(220, 38, 38, 0.45), 0 2px 6px rgba(0, 0, 0, 0.25); }
    50%      { box-shadow: 0 10px 30px rgba(220, 38, 38, 0.65), 0 2px 6px rgba(0, 0, 0, 0.25); }
  }

  /* ── Stop Talking suppression prompt (S2 D2.6b) ──
     Two-button scope picker that appears after tapping Stop Talking. Backdrop
     dims the page; card floats just above the tab bar. Outside-tap dismisses
     without persisting any preference (one-time silence). z-index 11050 sits
     above the tab bar (11000) and above .mobile-stop-talking (11001) but below
     the bookend-modal stack used by Story full-screen playback (11200+). */
  .mst-prompt {
    position: fixed;
    inset: 0;
    z-index: 11050;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    padding-bottom: calc(var(--mobile-tab-bar-height, 48px) + var(--mobile-safe-area-bottom, 0px) + 12px);
    pointer-events: none;
  }
  .mst-prompt-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.45);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
    pointer-events: auto;
    animation: mst-fade-in 140ms ease-out;
  }
  .mst-prompt-card {
    position: relative;
    z-index: 1;
    width: min(92vw, 360px);
    background: rgba(20, 22, 28, 0.98);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 16px;
    padding: 16px;
    box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55);
    pointer-events: auto;
    animation: mst-slide-up 180ms ease-out;
  }
  .mst-prompt-title {
    text-align: center;
    color: #f5f6f8;
    font-size: 15px;
    font-weight: 600;
    margin-bottom: 14px;
    letter-spacing: 0.01em;
  }
  .mst-prompt-actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
    margin-bottom: 10px;
  }
  .mst-prompt-btn {
    appearance: none;
    border: 1px solid rgba(255, 255, 255, 0.16);
    background: rgba(255, 255, 255, 0.06);
    color: #f5f6f8;
    font-size: 14px;
    font-weight: 600;
    min-height: 46px;
    padding: 10px 14px;
    border-radius: 12px;
    cursor: pointer;
  }
  .mst-prompt-btn:active { background: rgba(255, 255, 255, 0.14); }
  .mst-prompt-btn-all  { background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); border-color: rgba(0,0,0,0.0); color: #ffffff; }
  .mst-prompt-btn-item { background: linear-gradient(135deg, #6d6cff 0%, #9a69ff 100%); border-color: rgba(0,0,0,0.0); color: #ffffff; }
  .mst-prompt-btn[disabled],
  .mst-prompt-btn[aria-disabled="true"] {
    opacity: 0.4;
    cursor: not-allowed;
  }
  .mst-prompt-cancel {
    appearance: none;
    width: 100%;
    background: transparent;
    border: none;
    color: #9aa0a8;
    font-size: 13px;
    padding: 6px 0 2px;
    cursor: pointer;
  }
  .mst-prompt-cancel:active { color: #f5f6f8; }
  @keyframes mst-fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
  @keyframes mst-slide-up {
    from { transform: translateY(12px); opacity: 0; }
    to   { transform: translateY(0);    opacity: 1; }
  }
}

/* ============================================================================
   iOS PWA bottom-flush — RETIRED CSS APPROACH (2026-05-16 Iron-Law fix #3).
   ============================================================================

   8+ CSS-based attempts failed across multiple trains because iOS PWA
   standalone mode has undocumented interactions between viewport-fit=cover,
   env() resolution, apple-mobile-web-app-status-bar-style:black-translucent,
   and `position: fixed` anchor reference. The CSS approach was abandoned
   in favor of a JS measure-and-pin loop in mobile-shell.js::
   _forceBottomFlushReflow which:
     1. Measures the actual rendered tab bar geometry via getBoundingClientRect
     2. Computes the gap between bar.bottom and window.innerHeight
     3. Applies inline transform/height/padding-bottom to close the gap
     4. Same treatment for .mobile-context-controls
   This works regardless of env() value, regardless of @media (display-mode:
   standalone) matching, and regardless of which Safari quirk fires. See the
   diagnostic Watcher observation written on first mount for the actual
   measured values from each user device. */

/* Desktop: never show the floating Stop Talking button or its suppression
   prompt — narration stop lives inline in the UMI narrator row on desktop.
   Popup-mode surfaces (backdrop + container) are mobile-only too; the toolkit
   short-circuits openPopup on desktop via isMobile, but the CSS gate ensures
   a stray element never renders. */
@media (min-width: 768px) {
  .mobile-stop-talking,
  .mst-prompt,
  .mcp-popup-backdrop,
  .mcp-popup {
    display: none !important;
  }
}

/* ── Stories-slot filter toggle (translating-mobile-stagecraft LS-3/LS-4,
      2026-07-05, Joe directive) ──
   In the Stories view the Stories tab morphs into the Narratives/Bios filter
   cycle (All → Narratives → Bios). The old white .mobile-context-controls
   segment row above the bar is retired — the slot carries the filter now.
   filter-mode = the slot is a toggle; filter-narrowed = a non-All filter is
   applied (accent ring communicates "the list is filtered"). */
@media (max-width: 767px) {
  .mobile-tab-item.filter-mode .mobile-tab-label {
    font-weight: 700;
    letter-spacing: 0.02em;
  }
  .mobile-tab-item.filter-mode .mobile-tab-icon {
    opacity: 0.85;
  }
  .mobile-tab-item.filter-mode.filter-narrowed .mobile-tab-label {
    color: var(--mobile-tab-bar-fg-active);
  }
  .mobile-tab-item.filter-mode.filter-narrowed::after {
    content: '';
    position: absolute;
    top: 6px;
    right: 50%;
    transform: translateX(22px);
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--mobile-tab-bar-fg-active);
  }
  .mobile-tab-item.filter-mode {
    position: relative;
  }
}

/* ==========================================================================
   THE 44 px TOUCH-TARGET FLOOR — a mobile-shell contract invariant
   proving-mobile-promises S3 (2026-09-17), Cell C6.

   MEASURED (webkit-iphone 390×844, served v1.1.229, real family session):
   seven controls the contract layer renders were below 44 px in their own
   drawn box —

     .unified-ai-minimize            32×32   Fergi AI modal header
     .unified-ai-new-convo-btn       32×32   Fergi AI modal header
     #unifiedAISayItBtn              38×38   Fergi AI composer row
     #unifiedAIVoiceBtn              40×40   Fergi AI composer row   ⛔ RETIRED 2026-09-20
     #unifiedAIConversationBtn       40×40   Fergi AI composer row   ⛔ RETIRED 2026-09-20
     #unifiedAISendBtn               40×40   Fergi AI composer row
     .mcp-slot                      130×37   universal control panel

   ⚠️ The two RETIRED rows are kept, struck, rather than deleted: this block is a
   MEASUREMENT of a particular tree at v1.1.229, and silently editing a measurement
   to match a later tree is how a record stops being evidence. They were removed by
   ceding-welcome-precedence S1 (issue #3). The floor is unaffected — it is declared
   once as `--ffa-min-touch-target` and applied to the chrome, not to a list of ids,
   so five controls inherit it where seven did.

   ⛔ AND THE FLOOR BOUNDS ANY LATER ATTEMPT TO SHORTEN THIS ROW (issue #4b, S3):
   vertical space is recovered by REMOVING controls and collapsing chrome, never by
   shrinking targets. A height edit that turns C6 red has traded an accessibility
   invariant for polish.

   ── WHY A HIT EXPANDER AND NOT A BIGGER BUTTON ────────────────────────────
   The harm is the MIS-TAP, not the drawing. Growing seven buttons to 44 px
   would redesign family-facing chrome — the Fergi AI header and composer row
   are laid out to a visual rhythm that is Joe's call (/uiux), not a test
   suite's. Expanding the HIT AREA removes the whole harm and changes no
   pixel the member sees. This is the standard iOS remedy and it is why the
   C6 cell was rewritten the same day to measure the EFFECTIVE target (a
   44×44 box centred on the control, hit-tested at its corners) rather than
   `getBoundingClientRect()` — a probe that reads only the drawn box cannot
   tell a genuinely small target from a correctly expanded one.

   ── WHY ONE RULE AND NOT SEVEN ────────────────────────────────────────────
   Iron Law Q2: seven per-button fixes would not stop the eighth. The floor
   is declared ONCE as a token and applied to the chrome these contract
   modules render, so a control added tomorrow to the same chrome inherits it.

   ── CLEARANCE, MEASURED, NOT ASSUMED ──────────────────────────────────────
   An expander that overlaps its neighbour steals taps — a worse defect than
   the one being fixed. Measured gaps at 390 px: header buttons 133 px apart;
   composer row buttons 8 px apart (a 40→44 expansion consumes 2 px a side,
   leaving 4); .mcp-slot is 37 px tall inside a 48 px bar. No pair overlaps.
   `.mobile-tab-item.filter-mode::after` already carries the Stories filter
   dot, so the expander is ::before throughout.
   ========================================================================== */

:root {
  /* The floor, in one place. The C6 cell cites this token; the token does not
     cite the cell (`squaring-oblique-paper`: a value in prose is not a rule). */
  --ffa-min-touch-target: 44px;
}

@media (max-width: 767px) {
  .mobile-tab-bar .mobile-tab-item,
  body.fv-mobile #unifiedAIModal .unified-ai-header button,
  body.fv-mobile #unifiedAIModal .unified-ai-input-row button {
    position: relative;
  }

  .mobile-tab-bar .mobile-tab-item::before,
  body.fv-mobile #unifiedAIModal .unified-ai-header button::before,
  body.fv-mobile #unifiedAIModal .unified-ai-input-row button::before {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    /* Never SHRINK a target that is already big enough — max() keeps a 130 px
       wide slot 130 px wide and only raises the short axis. */
    width: max(100%, var(--ffa-min-touch-target, 44px));
    height: max(100%, var(--ffa-min-touch-target, 44px));
    /* The expander is the tap surface; it must not paint and must not block
       the button's own children from being read by elementFromPoint at the
       centre (the button is the hit target either way, since ::before is a
       child of the button and inherits its click). */
    background: transparent;
    pointer-events: auto;
    z-index: 0;
  }
}

/* ==========================================================================
   ONE DOOR PER SCREEN — the composer's Say It stands down where the control
   panel already carries one (ceding-welcome-precedence follow-up, Joe
   2026-09-20: "fix everything that you do not have a good reason not to fix",
   answering walk-note question (vi)).

   `ceding-welcome-precedence` S1 deliberately KEPT `#unifiedAISayItBtn` in the
   composer row, because `.unified-ai-input-row` is ONE shared row and deleting
   it would have stripped the desktop of its only Say It door. Correct — and it
   left the phone showing TWO doors at once: this one and the universal control
   panel's slot, the very control Joe had pointed at. Same job, twice.

   ⛔ THE GATE IS THE MOBILE SHELL (`body.fv-mobile`), NOT `pointer: coarse` AND
   NOT A WIDTH QUERY — and the difference is a real device, not pedantry. The
   iPad runs the DESKTOP layout even as a Home Screen app (mobile-detect reads
   tablets as 'desktop'; see styles.css § pointer:coarse), so it has NO control
   panel and NO panel door. A `pointer: coarse` or `max-width` rule would hide
   the iPad's only door and leave a touch device with no voice input at all.
   `body.fv-mobile` is set by the shell that MOUNTS the panel, which is exactly
   the condition "a panel door exists". Same gate the two rules above use for
   this same row.

   Fails safe by being stateless: it is a media-free class rule with no JS to
   leak, so the desktop and the iPad can never inherit it, and a phone whose
   modal never opened cannot be left mid-state.
   ========================================================================== */
/* ⛔ REVERSED 2026-10-26 (streamlining-sayit-stories S2; Joe, Fergi AI report 8d9312e9: "The
   Fergi AI modal needs a SayIt control to the left of the input field … A small SayIt
   control", and the 2026-10-08 rule: every voice-accepting field carries Say It on its LEFT).
   The rule that stood here hid the composer's door on every phone and left the bar slot as
   the only one — and under `?fergi_embed=1` the bar itself is hidden (script.js), so the
   embedded chat (a story's Fergi overlay, the welcome iframe) had NO Say It anywhere.
   One door per screen still holds: the door now lives in the row on EVERY host, phone
   included, and `unified-ai-modal.js` no longer pushes a Say It slot into the bar while the
   row's door is live. Size on the phone: the touch-target floor + hit expander declared above. */
body.fv-mobile #unifiedAIModal .unified-ai-input-row .unified-ai-sayit-btn {
  display: inline-flex;
  flex: 0 0 auto;
}

/* Joe ruling 2026-09-17 PM (proving-mobile-promises S5-F2): the Stories home
   anchor is not rendered while a modal owns the control panel. tab-bar.js
   toggles the class from the ownership gate; nothing else sets it. */
.mobile-tab-bar .mobile-tab-item.mcp-anchor-hidden {
  display: none !important;
}
