/* ============================================================
   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-action-bar,
.m-mobile-only {
  display: none;   /* .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; }

/* Chrome-less on the dark bar, site-wide (JJ review 2026-10-03): no resting
   fill, no inset ring — the icon alone reads as the control. The 44px floor,
   the radius and a hover/active feedback fill stay. */
.m-hamburger {
  appearance: none;
  background: transparent;
  box-shadow: none;
  color: var(--fg-on-ink);
  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,
.m-hamburger:active { background: var(--bg-on-ink-hover); }
.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;
  }

  /* M5b: the export preview's own two-column grid is retired (its filter
     rail is now the ui_data_view drawer, single-column throughout) — only
     `.ex-grid`/`.ex-layout` remain here, pre-existing and unrelated to this
     lane's own dead-class list. */
  .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.
     The amendment and cancellation list wrappers were removed from this rule
     by the frontend-data-views M2a wave, and the order list's own retired
     table-wrapper/toolbar selectors by M9: all three pages render
     `.enr-tablewrap` now, which brings its own scroll behaviour, and no
     markup carries the retired wrappers any more.
     `tests/conformance/frontend-data-views/test_dead_classes_deleted.py` pins
     the absence of an earlier, unrelated set of retired names by scanning
     this file's raw text — F4 requires deletion, not migration, and the
     same rule is followed here: no retired selector is written even in
     prose. */
  .surface table,
  table.ex-preview {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .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.
       - .form-card__hd title/subtitle competed on one row below ~360px. */
  .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; }

  /* ...which is exactly why the control cluster needs an auto margin here.
     At desktop `.hf-header` is a `1fr max-content 1fr` grid and the cluster is
     `justify-self: end`. The <=1024 block converts the header to flex (it has
     to, to beat the shell grid), and from there the thing holding the space
     open is `.hf-header__center` at `flex: 1 1 auto`. Hiding the centre one
     rule up removes the only spacer, so the cluster falls back against the
     title: measured on /orders/list/ at 390px, the Financial-view toggle sat
     at x=192..236 with the title ending at 184 and 154px of empty bar to its
     right (JJ, 2026-09-28: "the Financial view button should be right
     justified. It keeps hugging the platform logo").

     `margin-left: auto` on the flex item, not `justify-content: flex-end` on
     the header — the header also holds the hamburger and the title, which
     must stay left.

     It cannot collide with the feedback launcher: `framework/feedback-widget.
     css` reserves that gutter as `padding-right` on `.hf-header` at every
     width <=1024, so the content-box edge this docks against already stops
     short of it. That reservation's own comment describes this cluster as
     "right-aligned at every width" — true of every width except this one,
     until now. */
  .hf-header__controls { margin-left: auto; }

  /* 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; }

  /* Chrome-less ink icon buttons (JJ ruling 2026-10-05; frontend-contract
     4.7.0): the phone bar's icon-only controls wear `.m-hamburger`'s look —
     a kept ghost control (label hidden above) and the feedback launcher.
     `.hf-header` keeps the light `.m-cal-subbar` clone out; a primary CTA
     (non-ghost `.hf-hbtn`) is not chrome and keeps its fill. The focus ring is
     a box-shadow this rule outranks, so it is restored. */
  .hf-header .hf-hbtn--ghost.m-keep-ghost,
  .hf-hbtn--ghost.fbw__launch {
    background: transparent;
    box-shadow: none;
    color: var(--fg-on-ink);
  }
  .hf-header .hf-hbtn--ghost.m-keep-ghost:hover,
  .hf-header .hf-hbtn--ghost.m-keep-ghost:active,
  .hf-hbtn--ghost.fbw__launch:hover,
  .hf-hbtn--ghost.fbw__launch:active { background: var(--bg-on-ink-hover); }
  .hf-header .hf-hbtn--ghost.m-keep-ghost:focus-visible,
  .hf-hbtn--ghost.fbw__launch:focus-visible { box-shadow: var(--focus-ring); }

  /* 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: mobile card content moved out -----
     order-list-htmx / M9 (frontend-data-views): this section used to carry
     the old scrape-card family's mobile-card styling, shared by every
     scrape-card consumer (document centre, the amendment/cancellation
     lists, my requests) as well as the order list itself. The other three
     migrated onto the component's own `.enr-card` (data-cards.css) first, on
     their own lanes; M9 finished the job — the order list now server-renders
     `.enr-card` too, with its own inner content (`.ol-card__*`) in
     `css/pages/orders-list.css` (page-tier per data-cards.css's "chrome
     only" contract, spec §10's anti-gaming carve-out). None of the old
     table-wrapper/card/toolbar selectors style any live page any more —
     deleted, not migrated, per AC21; none is written here even in prose.

     `.ol-selbar`'s own margin stays: the load-detail documents tab's
     bulk-download feature (`documents-bulk-download.js`) still reuses the
     shared selection-bar markup/styling, unrelated to the order list. */
  .ol-selbar { margin: 8px; }

  /* ----- 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;
}
/* mobile-calendar-feed (D18): the M8 agenda's day header and chip rules
   (`.m-cal-agenda__day-*`, `.m-cal-agenda__chip*`) are deleted — the phone
   feed is the framework day-grouped feed (data-feed.css) around the
   calendar's own `.cal-feedcard` (pages/calendar.css). This wrapper stays
   as the ≤640 display gate. */

/* ============================================================
   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);
  }
}

/* ============================================================
   LANDSCAPE PHONE — the header's control cluster folds behind a chevron
   ------------------------------------------------------------
   JJ, 2026-09-28, on the calendar: "collapse the Delivery/Upliftment toggle,
   feedback button, drafts button and filters button behind a chevron
   disclosure row".

   WHY THIS CANNOT HANG OFF THE PHONE BLOCK ABOVE
   A phone held sideways is ~852 CSS px WIDE, so it is ABOVE the 640px phone
   breakpoint and takes the ≤1024 tablet header — `flex-wrap: wrap`, which puts
   the page's control cluster on a SECOND line. Measured as internal on
   /calendar/ at 852x393: header 109px, page padding 14px, weekday head 52px —
   174px of a 393px screen (44%) was chrome, leaving 219px of month grid, about
   1.5 of six week rows. The width is not the property that decides this; the
   HEIGHT is, exactly as `pages/fuel-grid.css` already found for its matrix
   reflow. Its shape is reused — height-keyed, with a width ceiling to keep a
   merely short desktop window out — plus the ADR 0079 input-device pair,
   because hiding a page's controls is phone chrome and a mouse user who drags
   a window short should keep their header.

   THE WIDTH CEILING IS 1024, NOT fuel-grid's 900. A landscape phone is wider
   than it looks: iPhone 15 Pro Max 932x430, iPhone 16 Pro Max 956x440, Galaxy
   S24 Ultra ~915x412. A 900px ceiling silently drops every one of them — the
   exact devices this is for. 1024 catches no tablet, because the height arm
   does that work: an iPad is 1024x768 sideways and 768x1024 upright, and a
   1024x600 Android tablet is 600 tall, so all three fail `max-height: 520px`.
   1024 and 520 are both canonical contract breakpoint edges.

   OPT-IN, NEVER BLANKET. `.hf-header__controls` carries each page's primary
   CTA and `mobile.js` clones that CTA into the FAB only ≤640 — in landscape
   there is no second copy of it. Folding every page's header would hide a
   primary action on ~20 surfaces to win height on one, which is the shape of
   defect the "framework CSS for one page breaks 24" rule exists to stop. A
   page asks for the treatment with `data-header-disclosure` on <body>;
   `js/framework/header-disclosure.js` injects the button and toggles
   `data-header-disclosed` there.

   THE `body[…]` PREFIX IS LOAD-BEARING, not decoration: `feedback-widget.css`
   sets `.hf-header`'s `padding-right` `!important` at ≤1024 and is linked
   AFTER this sheet, so a bare `.hf-header` rule here — important or not — ties
   on specificity and loses on order. The attribute selector outranks it.

   AFTER the touch block above, which asks to stay last for its own reason (a
   media query adds no specificity, so a later same-specificity rule wins).
   Nothing here selects a `.fld*` control, so the two cannot collide.
   ============================================================ */

/* Inert everywhere but the landscape-phone block below: the button is injected
   on every viewport of an opted-in page, and only this makes it appear. */
.hf-hdisc { display: none; }

@media (max-width: 1024px) and (max-height: 520px) and (hover: none) and (pointer: coarse) {

  /* One line. The vertical inset drops 6px → `--space-1`, and `row-gap: 0`
     stops the ≤1024 `gap: 10px` from re-inflating the band when the cluster
     IS disclosed. */
  body[data-header-disclosure] .hf-header {
    padding: var(--space-1) var(--space-4) !important;
    row-gap: 0;
  }

  /* The chevron. `margin-left: auto` is belt-and-braces — `.hf-header__center`
     is `flex: 1 1 auto` and already absorbs the free space — but it is what
     keeps the button docked right on a page whose centre slot is empty. */
  body[data-header-disclosure] .hf-hdisc {
    display: inline-flex;
    flex: none;
    align-items: center;
    justify-content: center;
    margin-left: auto;
    width: var(--space-8);
    height: var(--space-8);
    padding: 0;
  }
  /* `.hf-hbtn svg` is 14px, sized for a button that also carries a word. This
     one is icon-only, so the glyph is the whole control — same treatment, and
     the same 18px, as `.fbw__launch svg`. */
  body[data-header-disclosure] .hf-hdisc svg {
    width: 18px;
    height: 18px;
    transition: transform var(--dur-base) var(--ease-out);
  }
  body[data-header-disclosure] .hf-hdisc[aria-expanded="true"] svg {
    transform: rotate(180deg);
  }

  /* Collapsed. */
  body[data-header-disclosure]:not([data-header-disclosed]) .hf-header__controls {
    display: none;
  }

  /* Disclosed: the cluster becomes the header's own second line. `flex-basis:
     100%` is what breaks it there — and is why `header-disclosure.js` inserts
     the chevron BEFORE the cluster rather than after it, so the button is not
     pushed onto a third line. `flex-wrap` stays inherited (wrap): a cluster
     too wide for the bar grows the band, which `--topbar-h` re-measures, and
     that is a better failure than a clipped control. */
  body[data-header-disclosure][data-header-disclosed] .hf-header__controls {
    flex: none;
    flex-basis: 100%;
    align-items: center;
    min-height: var(--space-8);
  }
}
