/* ============================================================
   MOBILE OVERRIDES — applies on top of wireframe.css + shell.css
   Breakpoints (from user spec):
     ≤1024px → tablet (sidebar becomes drawer, rails stack, tables scroll-x)
     ≤640px  → phone  (compact topbar, FAB, card-tables, bottom sheets)
   ============================================================ */

/* ============================================================
   FAB, HAMBURGER, SCRIM — base styles (hidden on desktop)
   ============================================================ */
.m-hamburger,
.m-fab,
.m-scrim,
.m-ol-cards,
.m-action-bar,
.m-mobile-only {
  display: none;   /* .m-ol-cards / .m-action-bar revealed (flex) only at ≤640px — keeps the desktop view clean (Tweak 045) */
}

/* Tweak 059: the ctx-card "Details" collapse toggle is a phone-only affordance —
   its styling AND the collapse behaviour both live in the ≤640px block below.
   mobile.js injects the button on every viewport, so hide it on desktop;
   otherwise it renders as an unstyled, no-op button beside the context card. */
.ctx-card__toggle { display: none; }

.m-hamburger {
  appearance: none;
  background: rgba(255,255,255,0.06);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.10);
  color: #fff;
  width: 44px; height: 44px;   /* ADR 0005: >=44px tap target (UX-24) */
  border-radius: var(--radius-md);
  padding: 0;
  border: 0;
  cursor: pointer;
  align-items: center;
  justify-content: center;
  flex: none;
}
.m-hamburger:hover { background: rgba(255,255,255,0.12); }
.m-hamburger svg { width: 18px; height: 18px; display: block; }

.m-scrim {
  position: fixed; inset: 0;
  background: rgba(10,13,11,0.45);
  opacity: 0;
  pointer-events: none;
  transition: opacity 220ms var(--ease-out);
  z-index: var(--z-scrim);
}
.m-scrim.is-open {
  opacity: 1;
  pointer-events: auto;
}

/* Body scroll-lock while a drawer / bottom sheet / hamburger menu is open
   (UX-25). mobile.js adds .m-locked on open and removes it on close so the
   page underneath can't scroll behind the scrim/backdrop. */
.m-locked {
  overflow: hidden;
}

.m-fab {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: var(--z-floating);
  appearance: none;
  border: 0;
  cursor: pointer;
  /* a11y-visual-02: white 14px label needs >=4.5:1 — green-700 CTA fill (5.64:1). */
  background: var(--cta-bg);
  color: #fff;
  font: inherit;
  font-weight: 700;
  font-size: 14px;
  letter-spacing: -0.005em;
  padding: 0 20px 0 18px;
  height: 56px;
  min-width: 56px;
  border-radius: var(--radius-pill);
  box-shadow: 0 8px 24px rgba(0,170,61,0.35), 0 2px 6px rgba(10,13,11,0.14);
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-decoration: none;
}
.m-fab:hover { background: var(--cta-bg-hover); }
.m-fab:active { transform: translateY(1px); }
.m-fab svg { width: 18px; height: 18px; flex: none; }
.m-fab--icon-only { padding: 0; width: 56px; }
.m-fab--icon-only .m-fab__lbl { display: none; }

/* Account for FAB-covered content at bottom of pages */
.m-fab-pad { display: none; }

/* Account for sticky-action-bar-covered content on order detail (UX-09) */
.m-action-bar-pad { display: none; }

/* ============================================================
   TABLET (≤1024) — sidebar becomes drawer, rails stack
   ============================================================ */
@media (max-width: 1024px) {

  /* ----- Shell: collapse to single column, sidebar becomes drawer ----- */
  .hf-shell {
    /* `minmax(0, 1fr)`, not a bare `1fr` — a bare `1fr` is `minmax(auto, 1fr)`,
       so the track takes the MIN-CONTENT of its widest grid item and any child
       that cannot shrink widens the whole shell. mobile.js inserts
       `.m-cal-subbar` as a direct grid child here, and at 320 its cloned month
       cluster resolved the track to 331.08px: the sticky `.hf-header` and
       `.hf-main` measured 331 in a 320 window and the entire app shell panned
       sideways by 11px. Same idiom as the desktop rail at shell.css:1204.
       Swept at 320/390/768/1024 across 11 pages: only /calendar/ changed. */
    grid-template-columns: minmax(0, 1fr) !important;
    /* `auto`, not the shell's 55px header track (2026-08-31, sticky calendar
       weekday row). The 641–1024 header below is `height: auto !important` so
       its controls can wrap to a second line, and mobile.js measures that
       height into --topbar-h for every sticky consumer — but a grid item
       cannot outgrow a fixed 55px track, so the wrapped line was overflowing
       the ink bar onto the page (measured at 900/768/641: header 0–55,
       controls 60–94, over the calendar's weekday row at 69–121) and
       --topbar-h stayed 55px. An auto track lets the bar grow to its content.
       min-height: 55px is only a FLOOR the padded flex layout already clears:
       measured, the single-line case in this band is 57px (45px of content
       plus the 6px+6px padding), so --topbar-h is 57px on /orders/list/,
       /dashboard/, /activity/, /documents/ and /fuel-terms/ at 900 wide, and
       every page in the band sits 2px lower than it did. The ≤640 block below
       now declares the SAME auto track for the same reason — see it for the
       phone case this comment used to record as unfixed. */
    grid-template-rows: auto 1fr;
  }

  .hf-side {
    position: fixed !important;
    top: 0 !important;
    left: 0 !important;
    /* The DYNAMIC viewport, with `vh` as the fallback for a browser that has no
       `dvh`. A fixed element's containing block is the LAYOUT viewport, which on
       a phone browser with a retracting toolbar is the LARGE viewport — so
       `100vh` alone drew the drawer ~86px taller than the screen and put
       `.hf-side__account` (`position: sticky; bottom: 0`, and the app's ONLY
       Sign out / Notification settings — base.html) below the fold. Not
       recoverable by scrolling either: mobile.js locks <body> to
       `overflow: hidden` while the drawer is open, and a mobile browser retracts
       its toolbar only on a PAGE scroll, never inside a nested scroller. Same
       ruling as `.fbw__panel` (feedback-widget.css, pinned by
       tests/conformance/test_feedback_widget_placement.py) and the login page's
       own `min-height` pair. `!important` on BOTH lines is load-bearing: the
       base rule is `height: calc(100vh - 55px)` (shell.css) and would
       otherwise win the second declaration outright. */
    height: 100vh !important;
    height: 100dvh !important;
    width: 280px !important;
    z-index: calc(var(--z-drawer) - 10) !important;
    transform: translateX(-100%);
    /* a11y-code-03: the closed drawer was only translated off-screen, leaving
       ~8 invisible Tab stops (nav links + Sign out sit BEFORE <main> in DOM
       order) on every authenticated mobile page. visibility:hidden removes
       them from the Tab order; the 0s visibility flip is DELAYED 240ms on
       close so the slide-out still animates, and undelayed on open. Media-
       scoped, so the always-visible desktop sidebar is untouched. */
    visibility: hidden;
    transition: transform 240ms var(--ease-out),
                visibility 0s linear 240ms;
    box-shadow: 8px 0 32px rgba(10,13,11,0);
    border-right: 0 !important;
  }
  .hf-side.is-open {
    transform: translateX(0);
    visibility: visible;
    transition-delay: 0s;
    box-shadow: 8px 0 32px rgba(10,13,11,0.18);
  }

  /* Show hamburger in the topbar */
  .m-hamburger { display: inline-flex; }
  .m-scrim { display: block; }

  /* feat-operator-05: quick-find swaps home — the topbar input hides (the
     header is too tight next to page CTAs at this width) and the
     sidebar/drawer Find form shows instead (the sidebar IS the drawer here). */
  .hf-find { display: none; }
  .hf-side__find { display: block; }

  /* The brand title in topbar — keep visible since sidebar is hidden */
  .hf-header__title { display: inline-flex; align-items: center; gap: 0; }

  /* ----- Order detail: action rail moves below content (we'll FAB the primary) ----- */
  .od-grid,
  [data-rail="right"] .od-grid,
  [data-rail="left"]  .od-grid {
    grid-template-columns: 1fr !important;
  }
  .od-rail { order: 2; }
  [data-rail="left"] .od-rail { order: 2; }

  /* ----- Place order / form pages: rail goes to top, becomes collapsible summary ----- */
  .form-grid {
    grid-template-columns: 1fr;
    max-width: 100%;
  }
  .form-grid > .ctx-card {
    order: -1;
    position: static;
    top: auto;
  }
  /* Instructional ctx-cards (no .ctx-card__id summary anchor; not collapsed) drop below
     the form on mobile so the form comes first rather than a wall of help text. */
  .form-grid > .ctx-card:not(:has(.ctx-card__id)) {
    order: 1;
  }
  .form-intro {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    /* Match the base rule's zero inline padding so the heading keeps sharing the
       form-card's edges at mobile widths too (was 0 6px, a small re-inset). */
    padding: 0 0 var(--space-3);
    max-width: 100%;
  }
  .form-intro__actions { flex-wrap: wrap; }
  /* Confirmation/done/invalid auth pages keep their lone CTA full-width on
     mobile (UX-32), matching the prior .form-card__actions .btn behaviour. */
  .form-intro__actions .btn { width: 100%; justify-content: center; }

  /* Dashboard side rail / two-col grids stack */
  .db-grid {
    grid-template-columns: 1fr !important;
  }
  .db-today-strip {
    flex-wrap: wrap;
    gap: 4px 14px;
    padding-bottom: 8px;
  }
  .db-today-strip .greeting { margin-left: 0; }
  .db-today-strip h1.db-day { margin: 0; }
  .db-today-strip h1.db-day span { font-size: 24px !important; line-height: 1.15 !important; }

  /* Place-order page split panes stack */
  .po-grid,
  .po-split,
  .po-row--split,
  .po-row--3 {
    grid-template-columns: 1fr !important;
  }

  /* Export filter rail stacks */
  .exp-grid,
  .ex-grid,
  .ex-layout {
    grid-template-columns: 1fr !important;
  }

  /* Amendment / cancellation review: stack decision/meta panes */
  .ar-decision,
  .cr-decision,
  .cr-meta {
    grid-template-columns: 1fr !important;
  }
  /* Per-field diff: keep each field's label + before + after together so the
     current-vs-proposed pairing survives the narrow layout (UX-22). The two
     pane headers stack, and each row becomes a single labelled block. The
     !important mirrors the .ar-diff/.ar-decision rules above: amendment-review.css
     loads after mobile.css, so its base grid-template-columns would otherwise win. */
  .ar-diff__head { grid-template-columns: 1fr !important; }
  .ar-diff__hd--left { grid-column: auto !important; border-right: 0 !important; }
  .ar-diff__hd--right { grid-column: auto !important; border-top: 1px solid var(--border-1); }
  .ar-row { grid-template-columns: 1fr !important; gap: 6px; }
  .ar-row__after {
    border-left: 0 !important;
    padding-left: 0 !important;
    margin-left: 0 !important;
  }

  /* Accept-internal corrections diff: stack */
  .acc-diff,
  .am-row[data-on="true"] .am-row__body {
    grid-template-columns: 1fr !important;
  }
  .acc-diff .arr,
  .am-row .arr { display: none; }

  /* Request location coords + Use current */
  .rl-coords { grid-template-columns: 1fr 1fr !important; }
  .rl-coords .btn { grid-column: 1 / -1; }

  /* Import errors row */
  .imp-err-row { grid-template-columns: 1fr; gap: 6px; }
  .imp-stats { grid-template-columns: 1fr; }

  /* Dashboard rows */
  .db-today-strip h1.db-day span { font-size: 20px !important; }
  .db-today__row,
  .db-recent__row,
  .db-docs__row {
    grid-template-columns: 1fr !important;
    align-items: flex-start !important;
  }
  /* Stacked single-column cards: keep the date with the rest of the card body
     instead of orphaning it right-aligned at the far edge (UX-37). The compound
     selector out-specifies dashboard.css's `.db-today__ref { text-align:right }`,
     which loads after mobile.css (page CSS is appended via head_extra). */
  .db-today__row .db-today__ref { text-align: left; }
  .db-ctas { grid-template-columns: 1fr 1fr; }

  /* Tables: horizontal scroll on tablet */
  .ol-table-wrap,
  .surface table,
  table.ex-preview,
  .am-table-wrap,
  .cn-table-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .ol-table,
  .ex-preview {
    min-width: 920px;
  }

  /* Mobile responsive repair (MINORs): wrap two flex rows before they overflow in the
     640–1024 landscape gap / on narrow phones. Both only wrap when content can't fit,
     so wider widths are unchanged.
       - .ol-toolbar overran 667px landscape (Export button clipped 76px).
       - .form-card__hd title/subtitle competed on one row below ~360px. */
  .ol-toolbar { flex-wrap: wrap; }
  .form-card__hd { flex-wrap: wrap; }

  /* The house `.drawer` bottom-sheet adaptation: every CONTENT drawer that
     used to need it migrated onto `ui_drawer`'s own bottom-sheet presentation
     (overlays.css, `.enr-drawer` under the `shell` breakpoint) with the
     rest of the `.drawer`/`setupDrawers()` contract (frontend-overlays,
     2026-09, D13). What is LEFT here is `openActionSheet()`'s "More" action
     sheet (mobile.js — G4/X9, its own dismissal, deliberately not migrated),
     which only ever opens at <=640 (its trigger, `.m-action-bar__more`, does
     not exist in the DOM above that width) — so it always wants the
     bottom-sheet shape, never the desktop right-panel default (shell.css). */
  .drawer {
    top: auto;
    bottom: 0;
    right: 0;
    left: 0;
    width: 100%;
    max-width: 100%;
    height: auto;
    max-height: 85vh;
    border-top-left-radius: 20px;
    border-top-right-radius: 20px;
    transform: translateY(100%);
  }
  /* `none`, not `translateY(0)` — see the note on `.drawer.open` in
     shell.css. The bottom sheet sits at `bottom: 0; height: 85vh`, so an
     identity transform here made every fixed-positioned popup inside it resolve
     against an origin 15vh down the page. */
  .drawer.open { transform: none; }
  .drawer__hd {
    padding: 18px 18px 14px;
    position: relative;
  }
  .drawer__hd::before {
    content: "";
    position: absolute;
    top: 6px; left: 50%;
    transform: translateX(-50%);
    width: 36px; height: 4px;
    background: var(--enroute-ink-200);
    border-radius: var(--radius-pill);
  }

  /* Make header center area collapse gracefully — convert grid to flex so it wraps */
  .hf-header {
    display: flex !important;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    padding: 6px 14px !important;
    min-height: 55px;
    height: auto !important;
    grid-template-columns: none !important;
  }
  .hf-header__title { flex: 0 0 auto; }
  .hf-header__center {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .hf-header__controls {
    flex: 0 1 auto;
    flex-wrap: wrap;
    gap: 6px;
  }
  .hf-search { min-width: 0; max-width: 100%; }

  /* Calendar: hide redundant "Show by" label, keep the toggle */
  body[data-page="calendar"] .hf-header__controls .v4-label {
    display: none;
  }

  /* Main padding reduction */
  .hf-main { padding: 14px; }

  /* Tweaks panel: hide on mobile (desktop-only review tool) */
  #tweaks-root,
  .tweaks-root,
  .tweaks-panel,
  [class*="tweaks-panel"] {
    display: none !important;
  }
}

/* ============================================================
   PHONE (≤640) — compact topbar, FAB, card tables, agenda calendar
   ============================================================ */
@media (max-width: 640px) {

  /* ----- Compact 48px topbar ----- */
  /* Keep the sticky-dock offset in step with the compact header height
     (shell.css owns the 55px default; mobile.js overrides with the
     measured value). */
  :root { --topbar-h: 48px; }
  .hf-header {
    height: 48px;
    padding: 0 10px;
    gap: 8px;
    grid-template-columns: auto auto 1fr auto;
  }
  .hf-shell {
    /* `auto`, not 48px — the same change the ≤1024 block above made, finally
       applied to phones. The `height: 48px` two rules up LOSES to that block's
       `height: auto !important`, so the phone bar really measures 55px+ and a
       grid item cannot outgrow a fixed 48px track: on /price-book/, whose three
       44px header controls do not fit the header's 300px content box once the
       feedback-widget gutter is reserved, the cluster wrapped to a second flex
       line at y=58..102 and rendered OUTSIDE the bar, on top of the Rate book /
       Compare tab strip — and kept hit-testing, so `elementFromPoint` at the
       Rate book pill returned `#plr-add-btn` and the page's default tab could
       not be tapped at all. Do not "fix" that by shrinking the controls back
       below 44px; that is ADR 0005's floor, set here deliberately on 2026-08-26.
       Blast radius, measured: every other phone header goes 55 -> 57px (the
       padded flex layout's real single-line height) and its page sits 2px
       lower, exactly as the ≤1024 band already accepted. --topbar-h follows,
       because mobile.js measures the rendered header. */
    grid-template-rows: auto 1fr;
  }
  .hf-header__title {
    font-size: 15px;
    letter-spacing: -0.02em;
  }
  /* Hide breadcrumb/search center on phone — title visible in sidebar's pagetitle */
  .hf-header__center { display: none; }

  /* Header right controls: hide non-essential ghost buttons, hide primary (moved to FAB) */
  .hf-header__controls .hf-hlabel { display: none; }
  .hf-header__controls .hf-hbtn:not(.m-keep) {
    /* Will be cloned to FAB by mobile.js; hide originals labelled as such */
    display: none;
  }
  /* ADR 0005's 44x44 floor. These were 36x36 until 2026-08-26 — a house-shell breach,
     not a page one: every `m-keep-ghost` control on every page inherited it (the price
     book's Import CSV and Export book, the calendar's and the orders list's). Each
     page's own ADR 0005 sweep missed it for the same reason, having only ever looked at
     that page's stylesheet. Same move this file already made for `.drawer__close`. */
  .hf-header__controls .hf-hbtn--ghost.m-keep-ghost {
    display: inline-flex;
    padding: 6px 8px;
    width: 44px; height: 44px;
    justify-content: center;
  }
  .hf-header__controls .hf-hbtn--ghost.m-keep-ghost span,
  .hf-header__controls .hf-hbtn--ghost.m-keep-ghost .hf-hbtn__lbl { display: none; }

  /* FAB on phone (cloned): kept in the layout so it can fade in/out as mobile.js makes
     it scroll-aware (hidden at the top and while the page's own primary action is on
     screen, so it stops covering first-screen and form content). */
  .m-fab {
    display: inline-flex;
    opacity: 0;
    transform: translateY(10px);
    pointer-events: none;
    transition: opacity 0.18s ease, transform 0.18s ease;
  }
  .m-fab.is-active {
    opacity: 1;
    transform: none;
    pointer-events: auto;
  }
  .m-fab-pad { display: block; height: 80px; }

  /* ----- Main padding: tighter ----- */
  .hf-main { padding: 12px 10px; }

  /* ----- Dashboard tighter ----- */
  .db-kpis { grid-template-columns: 1fr 1fr; gap: 8px; }
  .db-kpi { padding: 12px 14px; }
  .db-kpi__num { font-size: 24px; }
  .db-attn { grid-template-columns: 1fr !important; }

  /* ----- Form rows: stack to single column always ----- */
  .form-row--2,
  .form-row--3 {
    grid-template-columns: 1fr;
  }
  .form-card {
    padding: 18px 16px;
  }
  .form-card__actions {
    flex-direction: column;
    align-items: stretch;
  }
  .form-card__actions .btn { width: 100%; justify-content: center; }
  .form-intro__copy h1 { font-size: 22px; }

  /* ----- ctx-card / po-summary become collapsible summary at top ----- */
  .ctx-card.m-collapsible,
  .po-summary.m-collapsible {
    /* Default to collapsed look */
    padding: 12px 14px;
    position: relative;
  }
  .ctx-card.m-collapsible[data-collapsed="true"] > *:not(.ctx-card__id):not(.po-summary__title):not(.ctx-card__toggle):not(.ctx-card__hd):not(.po-summary__hd),
  .po-summary.m-collapsible[data-collapsed="true"] > *:not(.po-summary__title):not(.po-summary__id):not(.ctx-card__toggle):not(.po-summary__hd):not(h2):not(h3) {
    display: none;
  }
  .ctx-card.m-collapsible[data-collapsed="true"] .ctx-card__id,
  .po-summary.m-collapsible[data-collapsed="true"] h2,
  .po-summary.m-collapsible[data-collapsed="true"] h3 { margin-bottom: 0; }
  .ctx-card__toggle {
    display: inline-flex;
    align-items: center; gap: 6px;
    appearance: none; background: transparent; border: 0; cursor: pointer;
    position: absolute;
    top: 12px; right: 14px;
    font: inherit; font-size: 12px; font-weight: 600;
    color: var(--enroute-cobalt-500);
    padding: 4px 8px;
    border-radius: 6px;
    z-index: calc(var(--z-base) + 1);
  }
  .ctx-card__toggle:hover { background: var(--enroute-ink-100); }
  .ctx-card.m-collapsible[data-collapsed="false"] .ctx-card__toggle svg,
  .po-summary.m-collapsible[data-collapsed="false"] .ctx-card__toggle svg { transform: rotate(180deg); }

  /* ----- Orders list: hide native table, show card list ----- */
  /* order-list-htmx: the mobile cards are now ALWAYS server-rendered (no scraper,
     so no runtime .m-has-cards class), so hide the desktop table unconditionally
     at <=640 — the .m-ol-cards partial is the mobile surface. */
  .ol-table-wrap .ol-table { display: none; }
  /* FP-3: the bulk assign-transporter bar is a desktop ops tool. Once the table
     is rebuilt into cards (no row checkboxes), hide it — graceful omission. */
  .ol-table-wrap.m-has-cards .ol-bulk-bar { display: none; }

  /* ----- Amendment / cancellation queues: same card treatment (buildQueueCardView) ----- */
  .q-tablewrap.m-has-cards .q-table { display: none; }
  .q-tablewrap.m-has-cards {
    background: transparent;
    box-shadow: none;
    border-radius: 0;
    overflow: visible;        /* no table to scroll once cards are shown */
  }
  .m-q-card__fields { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
  /* The DOCUMENTS card reuses .m-ol-card__id for the document type, which is
     prose ("Bill of Lading"), not an identifier — the desktop table sets the
     same values in Manrope and --font-mono is for ids, money and quantities
     (CHEATSHEET). Re-type just that use; the ORDERS card's .m-ol-card__id
     really does hold an id and stays mono. The selector is COMPOUND on purpose:
     `.m-ol-card__id` is declared further down this same block, so a bare
     `.m-q-card__type` ties on specificity and loses on order. */
  .m-ol-card__id.m-q-card__type { font-family: var(--font-sans); }
  .m-q-card__action { color: var(--enroute-cobalt-600); font-weight: 700; white-space: nowrap; }
  /* The footer is [who uploaded it] [the row's actions]. The action span is
     `white-space: nowrap`, so its automatic minimum size is its full min-content
     width and it cannot shrink; the by-span's `min-width: 0` then let it absorb
     the entire deficit. Measured at 390x800 before this rule: a Release Note row
     carries 6 controls, the action span measured 326.1px against a 326px footer
     and "Uploaded by" rendered 0px wide — invisible, with no room left to draw
     even the ellipsis. Let the row WRAP instead, and give the by-span a floor so
     it truncates at a readable length rather than vanishing. */
  .m-ol-card__foot { flex-wrap: wrap; row-gap: var(--space-2); }
  .m-ol-card__foot > span:first-child {
    /* basis 0, not auto: the by-span's content is a full email address, so an
       `auto` basis measured it at ~140px and pushed EVERY row onto two lines
       (card height 73 -> 98.3px, on a phone list). From 0 it takes only the
       slack that is actually left, and the 12ch floor is what decides whether
       the row can hold both at all. */
    flex: 1 1 0;
    min-width: 12ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .ol-toolbar { flex-wrap: wrap; padding: 8px 4px 12px; gap: 8px; row-gap: 10px; }
  .ol-toolbar > * { flex: 0 1 auto; min-width: 0; }
  .ol-toolbar__count { font-size: 12px; flex-basis: 100%; order: -1; }
  .ol-toolbar .ol-pager { flex-basis: 100%; order: 99; justify-content: space-between; }
  .ol-toolbar .ol-filter-chip { font-size: 12px; }
  /* Hide Columns/Filters/Export overflow actions to keep toolbar slim — they're rarely used on phone */
  .ol-toolbar .ol-tb-action { display: none; }

  .m-ol-cards {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 8px;
  }
  .m-ol-card {
    background: var(--bg-paper);
    border-radius: var(--radius-lg);
    padding: 14px 14px 12px;
    box-shadow: var(--shadow-card), inset 0 0 0 1px var(--border-1);
    display: flex; flex-direction: column;
    gap: 8px;
    text-decoration: none;
    color: var(--fg-1);
    position: relative;   /* bulk-select S2: anchor for the per-card checkbox */
  }
  /* bulk-select S2: per-card checkbox. Hidden until selection mode; in mode it
     shifts the card content right so it does not overlap the order id. The
     label is a 44px tap target. */
  .m-ol-card__sel { display: none; }
  body.is-selecting .m-ol-card { padding-left: 50px; }
  body.is-selecting .m-ol-card__sel {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: absolute;
    left: 4px;
    top: 8px;
    width: 44px;
    height: 44px;
    cursor: pointer;
  }
  body.is-selecting .m-ol-card__sel .ol-rowcb {
    width: 20px;
    height: 20px;
    margin: 0;
    cursor: pointer;
    accent-color: var(--brand);
  }
  /* The charcoal selection bar spans the mobile card list too (it is rendered
     above .ol-table-wrap, so it already sits above the cards). Give it a bit of
     side margin to line up with the card gutter. */
  .ol-selbar { margin: 8px; }
  .m-ol-card__hd {
    display: flex; align-items: center; justify-content: space-between;
    gap: 8px;
  }
  .m-ol-card__idgrp {
    display: flex; align-items: baseline; gap: 7px;
    min-width: 0;
    /* The header's `justify-content: space-between` was written when it had
       exactly TWO children. Features have since accreted four more DIRECT
       siblings into it (the party badge, the missing-docs icon, the status pill,
       the finance dots), so space-between spread the status chain into islands —
       measured at 390 on an internal card: 55/61/61px of dead space, with the
       amber missing-docs icon marooned mid-row reading as unrelated decoration
       rather than as a warning about that order's status.

       An auto margin absorbs the free space BEFORE justify-content runs, so the
       id group pins left and every later child packs right, separated only by
       the header's own 8px gap. Done here rather than by wrapping the chain in a
       new element: `.m-ol-card*` is contract-pinned markup (list-view-contract
       2.3.0, tests/integration/test_order_cards_partial.py). */
    margin-right: auto;
  }
  .m-ol-card__id {
    font-family: var(--font-mono);
    font-size: 13px;
    font-weight: 700;
    color: var(--fg-1);
    flex: 0 0 auto;
  }
  /* T-054: customer reference, muted, beside the order id on the mobile card. */
  .m-ol-card__cref {
    font-family: var(--font-mono);
    font-size: 11px;
    font-weight: 500;
    color: var(--fg-3);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
  }
  .m-ol-card__route {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--fg-1);
    line-height: 1.3;
    min-width: 0;
  }
  .m-ol-card__route-from,
  .m-ol-card__route-to {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1 1 0;
  }
  .m-ol-card__route-arrow {
    flex: none;
    color: var(--fg-3);
    font-weight: 400;
  }
  .m-ol-card__meta {
    display: flex; flex-wrap: wrap;
    align-items: center;
    gap: 4px 8px;
    font-size: 12px;
    color: var(--fg-3);
    font-weight: 500;
  }
  /* ui-tweaks: ATOMIC meta items — the "·" separator lives INSIDE each
     .m-ol-card__mi (as .m-ol-card__dot), so a flex wrap moves dot + value
     together and can never strand a separator at a line edge (the old
     sibling-separator form did, on every wrapped card — 2026-08-03 sweep).
     The first item's dot is hidden so the row never opens with one. Both
     producers emit this shape: _order_cards.html and mobile.js's
     queue/document card builders. */
  .m-ol-card__mi { white-space: nowrap; min-width: 0; }
  .m-ol-card__mi .m-ol-card__dot {
    color: var(--enroute-ink-200);
    margin-right: 8px;
  }
  .m-ol-card__mi:first-child .m-ol-card__dot { display: none; }
  .m-ol-card__meta b { color: var(--fg-2); font-weight: 600; }
  .m-ol-card__foot {
    display: flex; justify-content: space-between; align-items: center;
    margin-top: 2px;
    padding-top: 8px;
    border-top: 1px solid var(--border-1);
    font-size: 11.5px;
    color: var(--fg-3);
    font-variant-numeric: tabular-nums;
  }
  /* V0.11 transporter card: a labelled detail grid for the date/ETA sublines
     (Loading date, Delivery date, ETA) that the route line can't carry. Each
     .m-ol-card__kv is a [label, value] column; .m-ol-card__k is the small
     uppercase eyebrow, matching the table-header label treatment. */
  .m-ol-card__detail {
    display: flex; flex-wrap: wrap;
    gap: 8px 16px;
    margin-top: 2px;
    padding-top: 8px;
    border-top: 1px solid var(--border-1);
    font-size: 12px;
    color: var(--fg-1);
    font-variant-numeric: tabular-nums;
  }
  .m-ol-card__kv {
    display: flex; flex-direction: column;
    gap: 2px;
    min-width: 0;
  }
  .m-ol-card__k {
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--fg-3);
  }
  /* fuel-sales: the money band on a fuel order's card. Same labelled kv grid as
     .m-ol-card__detail (it carries both classes) — but the money reads BEFORE the
     dates, so it gets no top rule of its own and sits flush under the meta line. */
  .m-ol-card__fuel {
    border-top: 0;
    padding-top: 0;
  }

  /* ----- Order detail ----- */
  .od-head { padding: 16px 16px 14px; }
  .od-head__title { font-size: 22px; }
  .od-head__title .num { font-size: 18px; }
  .od-head__sub { font-size: 11.5px; gap: 8px; }
  /* Timeline: hide labels on phone, keep dots+line */
  .od-tl-step__lbl { display: none; }
  .od-tl-step__num { font-size: 10px; }

  /* Status bottom-bar: primary action + overflow */
  .m-action-bar {
    display: none;
    position: fixed;
    bottom: 0; left: 0; right: 0;
    background: var(--bg-paper);
    border-top: 1px solid var(--border-1);
    padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
    z-index: var(--z-floating);
    gap: 8px;
    align-items: center;
    box-shadow: 0 -4px 12px rgba(10,13,11,0.04);
  }
  .m-action-bar.is-active { display: flex; }
  /* Spacer so the fixed bar never covers the last detail row (mirrors .m-fab-pad).
     Bar height ≈ 10px + 44px primary + 10px padding = 64px, plus the safe-area inset. */
  .m-action-bar-pad { display: block; height: calc(64px + env(safe-area-inset-bottom)); }
  .m-action-bar .btn { flex: 1; }
  .m-action-bar .btn--primary { min-height: 44px; }
  /* `margin-left:auto` so the overflow control is ALWAYS at the right edge.
     The bar is a flex row with no justify-content and this child is `flex:none`,
     so on a page with no cloned primary it sat at the container's 12px left
     padding — measured 390x65 with one 44x44 child at x=12 and 322px of empty
     white beside it, for transporter and client on /loads/12/ and /loads/14/,
     transporter2 on /loads/7/. That reads as a bar that failed to render rather
     than as a deliberate "more actions" affordance, and it puts the only control
     under the wrong thumb. Costs nothing when a primary IS cloned: that clone is
     `flex:1` (mobile.js sets it), so it absorbs the free space and the auto
     margin resolves to 0 — measured unchanged on internal /loads/12/.

     Deliberately NOT fixed by loosening the promotion rule in mobile.js: its
     comment records that promoting an arbitrary first action (Cancel order,
     say) into the page's one-tap button is worse than promoting none. */
  .m-action-bar__more {
    appearance: none; border: 0;
    background: var(--enroute-ink-100);
    color: var(--enroute-ink-900);
    width: 44px; height: 44px;
    border-radius: var(--radius-md);
    cursor: pointer;
    flex: none;
    margin-left: auto;
    display: inline-flex; align-items: center; justify-content: center;
  }
  .m-action-bar__more:hover { background: var(--enroute-ink-200); }

  /* Hide desktop action rail on phone, show m-action-bar instead */
  .od-rail { display: none !important; }

  /* ----- Calendar: hide month grid, show agenda ----- */
  .hf-cal__weekdays,
  .hf-cal__grid {
    display: none !important;
  }
  .hf-cal {
    background: transparent !important;
    border: 0 !important;
    padding: 0 !important;
    box-shadow: none !important;
  }
  .m-cal-agenda { display: block; }

  /* Hide the originals INSIDE the topbar (not the cloned ones in the sub-bar) */
  body[data-page="calendar"] .hf-header > .hf-header__center,
  body[data-page="calendar"] .hf-header > .hf-header__controls {
    display: none !important;
  }

  /* Sub-bar with month nav + Show-by toggle, visible only on phone */
  .m-cal-subbar { display: flex; }

  /* 2026-08-03 sweep: mobile.js inserts .m-cal-subbar as a DIRECT grid child of
     .hf-shell (right after the header), where it auto-places into the shell's
     1fr row and .hf-main falls into an implicit auto row below. On a month with
     orders the tall agenda absorbs the free space, but on an EMPTY month the
     subbar row ballooned to fill the viewport — its two wrapped flex lines
     (month nav / basis toggle) spread across the void and the empty-state card
     pinned to the bottom. Give the calendar shell an explicit auto row for the
     subbar and hand the 1fr to main, so header, subbar and content stack
     contiguously from the top. */
  body[data-page="calendar"] .hf-shell {
    grid-template-rows: 48px auto 1fr;
  }

  /* ----- Place order: stack split panes ----- */
  .po-split,
  .po-grid { grid-template-columns: 1fr; }

  /* ----- Export ----- */
  .ex-filters { padding: 12px; }

  /* ----- Notification prefs table -----
     Card layout moved up to the ≤1024px block so landscape phones get it too
     (mobile responsive repair). */

  /* ----- Drawer adjustments -----
     Every CONTENT drawer this used to serve migrated onto `ui_drawer`'s own
     `.enr-drawer__body` (overlays.css) and its `.btn--icon` close control —
     already floored to 44px below, generically, for every icon-only button
     in the tree (frontend-overlays, 2026-09, D13). What is LEFT is
     `openActionSheet()`'s sheet (mobile.js — G4/X9, not migrated): its close
     button carries NO `.btn`/`.btn--icon` class (it is a bare
     `.drawer__close`, styled only in shell.css, 30x30), so it needs its
     OWN floor here, same as it always did. */
  .drawer__body { padding: 14px; }
  .drawer__close { width: 44px; height: 44px; }

/* ----- Buttons: tap targets -----
   The width-gated block is CLOSED here and reopened below so these three floors
   can carry a wider condition without moving in source order (ADR 0079: the
   floor follows the input device; widened in place so every specificity
   relationship that holds for the width arm is preserved exactly). A phone in
   landscape, a tablet and a touch convertible all render this desktop layout
   with a finger, and measured 37.5px `.btn` boxes on the place-order form. */
}

/* controller-role mobile pass: raised 40 → 44 to meet the WCAG/V05 44px
   tap-target floor (controllers are phone-first; the e2e suite enforces it). */
@media (max-width: 640px), (hover: none) and (pointer: coarse) {
  .btn { min-height: 44px; }
  .btn--lg { min-height: 48px; }
  /* front-end-framework D5 fix, 2026-09-09: the rule above raises every
     .btn's HEIGHT to the floor, but .btn--icon (app-shell.css) fixes its
     WIDTH at 36px unconditionally and nothing else touches it at any
     breakpoint — so on phone it measured 36x44 (E2E06's real-browser
     getBoundingClientRect() proof), still short of ADR 0005's 44x44 floor
     on the width axis even though the taller 44px height made it look
     already fixed. */
  .btn--icon { width: 44px; }
}

@media (max-width: 640px) {

  /* Hide table-only columns marked with .m-hide-mobile */
  .m-hide-mobile { display: none !important; }

  /* Generic mobile-only blocks (e.g., m-summary cards) */
  .m-mobile-only { display: block; }
}

/* ============================================================
   AGENDA VIEW — used on phone for calendar
   ============================================================ */
.m-cal-agenda {
  display: none;
  background: transparent;
  padding: 0;
}
.m-cal-agenda__hd {
  /* Hide the agenda's own month header — sub-bar above already shows it */
  display: none;
}
.m-cal-agenda__day {
  background: var(--bg-paper);
  border-radius: var(--radius-lg);
  padding: 12px 14px;
  margin: 0 0 8px;
  box-shadow: inset 0 0 0 1px var(--border-1);
}
.m-cal-agenda__day-hd {
  display: flex; align-items: baseline; gap: 8px;
  margin-bottom: 8px;
}
.m-cal-agenda__day-num {
  font-size: 17px;
  font-weight: 800;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.m-cal-agenda__day-num.is-today {
  background: var(--enroute-ink-900);
  color: #fff;
  width: 30px; height: 30px;
  border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 14px;
}
.m-cal-agenda__day-name {
  font-size: 12px;
  color: var(--fg-3);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.m-cal-agenda__day-count {
  margin-left: auto;
  font-size: 12px;
  color: var(--fg-3);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  background: var(--enroute-ink-100);
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  min-width: 18px;
  text-align: center;
  flex: none;
}
.m-cal-agenda__day-empty {
  font-size: 12.5px;
  color: var(--fg-4);
  font-style: italic;
}
.m-cal-agenda__chip {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 0;
  /* F115: agenda chips are the primary tap (and long-press) target to open a
     load on the phone calendar; they measured 32px. Raise to the 44px touch
     minimum (ADR 0005 / UX-24). */
  min-height: 44px;
  box-sizing: border-box;
  border-top: 1px solid var(--border-1);
  text-decoration: none;
  color: var(--fg-1);
}
.m-cal-agenda__chip:first-of-type { border-top: 0; padding-top: 4px; }
.m-cal-agenda__chip-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--enroute-ink-400);
  flex: none;
}
.m-cal-agenda__chip-dot.s-pending,
.m-cal-agenda__chip-dot.s-accepted { background: var(--enroute-amber-500); }
.m-cal-agenda__chip-dot.s-loading,
.m-cal-agenda__chip-dot.s-transit  { background: var(--enroute-green-500); }
.m-cal-agenda__chip-dot.s-cancelled { background: var(--enroute-rose-500); }
/* draft-orders (ADR 0046): without a rule of its own an s-draft row fell through
   to the base --enroute-ink-400 above — the SAME ink s-delivered and s-invoiced
   land on, so a draft was pixel-identical in status signalling to a finished
   load (both measured rgb(107,117,111)). The desktop chip carries the dashed
   outline that says "provisional"; the agenda rebuild keeps only this dot, so
   the cue has to live here. A hollow RING, not another hue: shape is the
   load-bearing signal (tokens.css, --draft-*). --enroute-ink-400 rather than
   --draft-border (ink-300): the desktop border is paired with --bg-sunken, but
   here the ring is the only shape and ink-300 measures ~2.6:1 on paper, under
   the WCAG 1.4.11 3:1 floor for a non-text cue; ink-400 clears it at ~4.8:1. */
.m-cal-agenda__chip-dot.s-draft {
  background: var(--bg-paper);
  box-shadow: inset 0 0 0 2px var(--enroute-ink-400);
}
.m-cal-agenda__chip-id {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 700;
  color: var(--fg-2);
  flex: none;
}
/* UX-18: customer reference carried from the desktop chip (.hf-chip__ref). */
.m-cal-agenda__chip-ref {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  color: var(--fg-3);
  flex: none;
  white-space: nowrap;
}
/* emergency badge (#31 SP5): the urgent marker carried into the mobile agenda
   from the desktop chip's .hf-chip__urgent sub-element. Dedicated rose marker,
   never an s-* status class (the dot already carries lifecycle). */
.m-cal-agenda__chip-urgent {
  font-size: 9px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0 5px;
  border-radius: var(--radius-pill);
  background: var(--emergency-bg);
  color: var(--emergency-fg);
  flex: none;
  white-space: nowrap;
}
.m-cal-agenda__chip-route {
  font-size: 13px;
  font-weight: 600;
  color: var(--fg-1);
  letter-spacing: -0.005em;
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.m-cal-agenda__chip-time {
  font-size: 11.5px;
  color: var(--fg-3);
  font-variant-numeric: tabular-nums;
  flex: none;
  white-space: nowrap;
}
/* UX-17: cancelled agenda rows are muted + struck so they don't read as live. */
.m-cal-agenda__chip--cancelled { opacity: 0.7; }
.m-cal-agenda__chip--cancelled .m-cal-agenda__chip-id {
  text-decoration: line-through;
  color: var(--fg-3);
}
.m-cal-agenda__chip--cancelled .m-cal-agenda__chip-route { color: var(--fg-3); }
.m-cal-agenda__chip--cancelled .m-cal-agenda__chip-ref { color: var(--fg-4); }

/* ============================================================
   CALENDAR SUB-BAR — month nav + show-by toggle below topbar (phone only)
   ============================================================ */
.m-cal-subbar {
  display: none;
  flex-wrap: wrap;          /* month-nav + toggle wrap to two rows on narrow phones */
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  row-gap: 8px;
  padding: 8px 12px;
  background: var(--bg-paper);
  border-bottom: 1px solid var(--border-1);
  position: sticky;
  /* The MEASURED header height, not 48px (2026-08-31). The <=1024 header
     rules above are `min-height: 55px; height: auto !important`, and the
     !important beats the <=640 `height: 48px`, so the phone header really
     measures 55px: docking at 48 put this bar's top 7px under the opaque
     ink bar (measured at 390x800 on /calendar/2026/8/ - header rect [0,55],
     sub-bar rect [48,179]). 48px stays as the no-JS fallback, matching the
     <=640 `:root { --topbar-h: 48px }`.
     Pinned by tests/conformance/test_sticky_dock_contract.py. */
  top: var(--topbar-h, 48px);
  z-index: calc(var(--z-base) + 10);
}
.m-cal-subbar__nav {
  display: flex; align-items: center; gap: 4px;
  min-width: 0;
  flex: 1 1 auto;
}
.m-cal-subbar__nav .hf-header__center,
.m-cal-subbar__nav .mc-min {
  display: inline-flex !important;
  align-items: center;
  gap: 4px;
  color: var(--fg-1) !important;
}
/* `.mc-min` keeps the default `min-width: auto`, so its AUTOMATIC MINIMUM SIZE
   — 44 + 4 + month + 4 + 44, the icons being the ADR 0005 tap targets
   pages/calendar.css:505 gives them at ≤640 — is what stops flex-shrink
   absorbing the deficit at 320, whatever `.m-cal-subbar__nav`'s own
   `min-width: 0` says. Measured there: mc-min 241 + Today 58 + gaps = 307.1
   against a 296px content box. */
.m-cal-subbar__nav .mc-min { min-width: 0; }
/* Last-resort wrap for a width no shrinking can save (a longer month name in
   another locale, a future control). It does not bite at 320 once the month can
   shrink — measured identical geometry at 320/360/375/390. Two things it must
   NOT be: folded into the grouped rule above, which also matches `.mc-min` and
   would let the prev/next arrows break away from the label they bracket; or
   paired with `justify-content: center`, which re-centres the row at every
   phone width that was never broken (mc-min's left offset 12 -> 26.5/34/41.5 at
   360/375/390). */
.m-cal-subbar__nav .hf-header__center { flex-wrap: wrap; }
.m-cal-subbar__nav .mc-icon {
  appearance: none; border: 0;
  background: transparent;
  color: var(--fg-2) !important;
  width: 30px; height: 30px;
  display: inline-flex; align-items: center; justify-content: center;
  cursor: pointer;
  border-radius: 6px;
  padding: 0;
}
.m-cal-subbar__nav .mc-icon:hover { background: var(--enroute-ink-100); color: var(--fg-1) !important; }
.m-cal-subbar__nav .mc-icon svg { width: 14px; height: 14px; }
/* The `!important`s are the same cascade problem the .mc-today rule below
   documents, and they are load-bearing here too: pages/calendar.css loads AFTER
   this sheet (calendar.html's head_extra) and its `.mc-min .mc-month` ties on
   specificity, so its `padding: 0 10px` and `min-width: 92px` silently beat the
   values this rule already intended — 12px of dead width the cloned sub-bar
   cannot afford at 320. `white-space: nowrap` STAYS: it is what makes the
   ellipsis a clean truncation rather than a mid-phrase break, and with the
   min-width gone the label fits in full at 320 anyway (measured: month 121px,
   nav 295.1 inside 296, no ellipsis). The overflow/ellipsis pair is the safety
   net for a longer month name in another locale. */
.m-cal-subbar__nav .mc-month {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--fg-1) !important;
  white-space: nowrap;
  padding: 0 var(--space-1) !important;
  min-width: 0 !important;
  overflow: hidden;
  text-overflow: ellipsis;
}
.m-cal-subbar__nav .mc-month .y {
  color: var(--fg-3) !important;
  font-weight: 500;
  margin-left: 4px;
}
/* 2026-08-03 sweep: the cloned Today link wore cobalt — reading as default-link
   blue, the only off-palette control in the app. Links here are brand-green
   (tokens.css --link, F023: green-700 clears AA on a light surface where the
   green-500 fill does not); the !important pattern is kept because
   calendar.css's dark-header .mc-today rules load after this sheet. */
.m-cal-subbar__nav .mc-today {
  appearance: none; border: 0;
  background: transparent;
  font: inherit;
  font-size: 12px; font-weight: 600;
  color: var(--link) !important;
  padding: 5px 8px;
  border-radius: 6px;
  cursor: pointer;
  margin-left: 4px;
}
.m-cal-subbar__nav .mc-today:hover { background: var(--brand-soft); color: var(--link-hover) !important; }

.m-cal-subbar__ctrl {
  display: flex; align-items: center;
  flex: none;
}
.m-cal-subbar__ctrl .hf-header__controls {
  display: inline-flex !important;
  align-items: center;
  gap: 8px;
}
.m-cal-subbar__ctrl .v4-label { display: none; }
.m-cal-subbar__ctrl .odt { transform: scale(0.92); transform-origin: right center; }
/* The toggle is cloned out of the dark page header (with its .hf-header__controls
   wrapper) onto this white sub-bar, so its dark-header styling — transparent-white
   track, white inset borders and near-white inactive text — renders "Upliftment"
   invisibly. Restyle for a LIGHT background: visible track + dark inactive-option
   text, dropping the white insets. The green active pill stays as-is. */
.m-cal-subbar__ctrl .hf-header__controls .odt {
  background: var(--bg-sunken);
  box-shadow: inset 0 0 0 1px var(--border-2);
}
.m-cal-subbar__ctrl .hf-header__controls .odt__thumb { box-shadow: none; }
/* Stream C (Feature 3): the Filters trigger (.hf-hbtn--ghost) is cloned out of
   the dark page header the same way the basis toggle is, above — its white
   text/near-white fill is invisible on this light sub-bar. Same treatment. */
.m-cal-subbar__ctrl .hf-header__controls .hf-hbtn--ghost {
  background: var(--bg-sunken);
  color: var(--fg-2);
  box-shadow: inset 0 0 0 1px var(--border-2);
}
.m-cal-subbar__ctrl .hf-header__controls .hf-hbtn--ghost:hover {
  background: var(--enroute-ink-100);
  color: var(--fg-1);
}
.m-cal-subbar__ctrl .odt__option {
  color: var(--fg-2);
  /* F114: the basis toggle is the ONLY way to switch date basis on a phone and
     measured ~25px. Make each option a >=44px touch target. The .odt is scaled
     to 0.92 on the sub-bar, so 48px box-height renders ~44px. */
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.m-cal-subbar__ctrl .odt__option:not([aria-current="true"]):hover { color: var(--fg-1); }

/* ============================================================
   Calendar agenda + sub-bar: REVEAL on phone (source-order fix)
   ------------------------------------------------------------
   .m-cal-agenda / .m-cal-subbar default to display:none (desktop). The reveal
   rules inside the ≤640px block far above are overridden by these component base
   styles, which follow them in source order — and an @media query adds no
   specificity, so the later display:none won and the agenda never appeared (page 05
   rendered blank on phones even though mobile.js builds the markup). Re-assert the
   phone reveal here, AFTER the base styles, so it wins.
   ============================================================ */
@media (max-width: 640px) {
  .m-cal-agenda { display: block; }
  .m-cal-subbar { display: flex; }
}

/* ============================================================
   TOUCH DEVICES — form controls large enough not to trigger the iOS zoom
   ------------------------------------------------------------
   iOS Safari zooms the viewport when a focused text control computes under
   16px, and it does NOT zoom back out: the page stays magnified and pans, which
   on the placement form takes the sticky .pl-cta bar and the drawer trigger off
   screen. Every `.fld` control is 14px (shell.css:695) and the drawer's
   quick-find is 13px, so this fires on every field of every authenticated form.
   The anonymous login page has been protected all along (login.css:353); the
   app shell never was.

   `(pointer: coarse)`, NOT `max-width: 640px`: measured with a ≤640 rule in
   place, `#id_old_password` computes 16px at 390x844 and 14px at 844x390 — the
   same iPhone held sideways, where iOS zooms exactly the same. The width is not
   the property that decides this; the input device is.

   AT THE END OF THE FILE on purpose: a media query adds no specificity, so a
   same-specificity rule LATER in this sheet would win. Same reason as the
   agenda reveal block above. The selector list is shell.css:679-693
   verbatim, so each one ties and wins on load order (base.html links this sheet
   after shell.css). `.fld select` stays in the list even though
   custom-select.js hides the native control — that is the JS-off fallback path.
   ============================================================ */
@media (hover: none) and (pointer: coarse) {
  .fld__ctrl,
  .fld input[type="text"],
  .fld input[type="number"],
  .fld input[type="date"],
  .fld input[type="time"],
  .fld input[type="email"],
  .fld input[type="tel"],
  .fld input[type="search"],
  .fld input[type="password"],
  .fld input[type="url"],
  .fld input[type="datetime-local"],
  .fld input[type="month"],
  .fld input[type="week"],
  .fld select,
  .fld textarea,
  .fld__ctrl--combo > input,
  .hf-side__find-input {
    font-size: var(--text-field-touch);
  }
}
