/* ============================================================
   enr SHELL — shared by Orders list, Order detail, Place order
   and the Calendar month (which has its own variant inline).

   Locked-in defaults (cohesive across pages):
     • Ink sidebar (.hf-side, --sidebar-bg = ink-900)
     • Ink topbar (.hf-header, --topbar-bg = ink-900)
     • Warm canvas (--page-bg = #E8EAE6)
     • Dark-white active sidebar pill
     • Page title sits at top of sidebar (not in content)
     • Account block pinned to bottom of sidebar

   Per-page Tweaks can override these via :root inside the page.
   ============================================================ */

:root {
  /* Sidebar */
  --sidebar-bg: var(--enroute-ink-900);
  --sidebar-fg: rgba(255,255,255,0.65);
  --sidebar-fg-mute: rgba(255,255,255,0.40);
  --sidebar-fg-strong: #fff;
  --sidebar-hover-bg: rgba(255,255,255,0.06);
  --sidebar-border: #000;
  --sidebar-icon-opacity: 0.7;

  /* Topbar */
  --topbar-bg: var(--enroute-ink-900);
  --topbar-fg: rgba(255,255,255,0.55);
  --topbar-fg-strong: #fff;
  --topbar-fg-mute: rgba(255,255,255,0.50);
  --topbar-border: #000;
  /* Sticky elements dock at this offset (the .hf-header height). CSS carries
     the per-breakpoint fallbacks (48px ≤640 in mobile.css); mobile.js keeps it
     equal to the MEASURED header height, because the 641–1024 header is
     height:auto and can wrap taller than any constant. */
  --topbar-h: 55px;

  /* Canvas */
  --page-bg: #E8EAE6;

  /* Motion */
  --dur-fast: 120ms;
  --dur-base: 220ms;
  /* front-end-framework SP1: --ease-out moved to tokens.css (same value,
     single source). --dur-fast/--dur-base stay as the recorded
     KNOWN_DUPLICATES pair — resolving them is a design call, not a move. */
}

html, body { background: var(--page-bg); }
body { font-family: var(--font-sans); }

/* a11y-visual-06: strong DEFAULT keyboard-focus indicator. Any control without
   its own :focus-visible rule gets a 2px solid green-700 outline (5.64:1 on
   white, computed). Controls that DO define one are more specific and keep
   their tuned look (white outlines on the dark chrome, var(--focus-ring) —
   itself now a hard ring — on the light-surface buttons). */
:focus-visible {
  outline: 2px solid var(--enroute-green-700);
  outline-offset: 2px;
}

/* ---------- Shell grid ---------- */
.hf-shell {
  display: grid;
  grid-template-columns: 240px 1fr;
  grid-template-rows: 55px 1fr;
  min-height: 100vh;
  background: var(--page-bg);
}

/* ---------- Header (top chrome) ---------- */
.hf-header {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 1fr max-content 1fr;
  align-items: center;
  gap: 28px;
  padding: 0 28px;
  background: var(--topbar-bg);
  border-bottom: 1px solid var(--topbar-border);
  position: sticky; top: 0; z-index: var(--z-header);
  height: 55px;
}
.hf-header__title {
  font-size: 20px;
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--topbar-fg-strong);
  line-height: 1.05;
  margin: 0;
  text-decoration: none;
}
.hf-header__title .dot { color: var(--enroute-green-400); }
.hf-header__center {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  justify-self: center;
  min-width: 0;
  max-width: 100%;
}
.hf-header__controls {
  display: flex;
  align-items: center;
  gap: 12px;
  justify-self: end;
}

/* F147 top-right brand emblem slot REMOVED (JJ, 2026-06-10) — the .hf-emblem /
   .hf-emblem__ph placeholder styles were retired with the markup in base.html. */

/* Header utility label ("SHOW BY", "ROLE", etc.) */
.hf-hlabel {
  color: var(--topbar-fg);
  font-size: 11px;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  font-weight: 700;
  white-space: nowrap;
}

/* Header primary action (lives in topbar right cluster) */
.hf-hbtn {
  appearance: none;
  font: inherit;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px 7px 11px;
  border-radius: var(--radius-md);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: -0.005em;
  /* a11y-visual-02: white 13px label needs >=4.5:1 — green-700 CTA fill (5.64:1). */
  background: var(--cta-bg);
  color: #fff;
  border: 0;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}
.hf-hbtn:hover { background: var(--cta-bg-hover); }
.hf-hbtn svg { width: 14px; height: 14px; }

.hf-hbtn--ghost {
  background: rgba(255,255,255,0.06);
  color: #fff;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.15);
}
.hf-hbtn--ghost:hover { background: rgba(255,255,255,0.12); }
/* F120/F099 (critical): the header CTA lives on the dark ink-900 topbar where a
   green ring is barely perceivable (≈1.x:1), so use a LIGHT high-contrast outline
   plus the brand ring. Covers both the primary header button and its ghost variant. */
.hf-hbtn:focus-visible {
  outline: 2px solid var(--sidebar-fg-strong);   /* #fff on the dark topbar */
  outline-offset: 2px;
  box-shadow: var(--focus-ring);
}

/* Header search (used by Orders list + Order detail) */
.hf-search {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  background: rgba(255,255,255,0.06);
  border-radius: var(--radius-pill);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.10);
  min-width: 320px;
  max-width: 100%;
}
.hf-search:focus-within {
  background: rgba(255,255,255,0.10);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.22);
}
.hf-search svg { width: 14px; height: 14px; color: rgba(255,255,255,0.5); flex: none; }
.hf-search input {
  background: transparent;
  border: 0;
  outline: 0;
  color: #fff;
  font: inherit;
  font-size: 13px;
  flex: 1;
  min-width: 0;
}
.hf-search input::placeholder { color: rgba(255,255,255,0.45); }

/* feat-operator-05: internal quick-find — compact topbar variant of .hf-search.
   Desktop only; mobile.css hides it ≤1024 (the sidebar/drawer Find form takes
   over there). Compact so it never crowds page-specific header CTAs. */
.hf-find { min-width: 200px; max-width: 260px; }

/* feat-operator-05: sidebar quick-find (the ≤1024 drawer home of the jump box).
   Hidden on desktop — mobile.css reveals it inside the drawer. Reuses the
   sidebar label style for its "Find" caption. */
.hf-side__find { display: none; padding: 0 10px 4px; }
.hf-side__find .hf-side__label { padding: 14px 0 6px; }
.hf-side__find-input {
  width: 100%;
  box-sizing: border-box;
  padding: 8px 10px;
  font: inherit;
  font-size: 13px;
  color: var(--sidebar-fg-strong);
  background: rgba(255,255,255,0.06);
  border: 0;
  border-radius: var(--radius-md);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.10);
  outline: 0;
}
.hf-side__find-input:focus-visible {
  background: rgba(255,255,255,0.10);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.22);
}
.hf-side__find-input::placeholder { color: var(--sidebar-fg-mute); }

/* Breadcrumb / title cluster in header center for non-calendar pages */
.hf-bc {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  color: var(--topbar-fg);
  font-size: 13px;
  font-weight: 600;
}
.hf-bc strong {
  color: var(--topbar-fg-strong);
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -0.01em;
}
.hf-bc .sep { color: var(--topbar-fg-mute); font-weight: 400; }

/* ---------- Sidebar ---------- */
.hf-side {
  background: var(--sidebar-bg);
  border-right: 1px solid var(--sidebar-border);
  /* NO bottom padding. The account block below is `position:sticky; bottom:0`,
     which pins it to the scrollport's PADDING edge, not the border edge, so a
     bottom pad leaves a band UNDER the pinned block through which the scrolling
     nav shows — a half-row sliced across the rail's foot as soon as the nav
     overflows ("Export orders", photographed 2026-08-26). The account block
     carries the foot spacing itself instead. */
  padding: 0 10px 0;
  display: flex; flex-direction: column;
  gap: 2px;
  /* The shell grid (.hf-shell) reserves a 55px header row, and the sidebar lives
     in the row BELOW it. height:100vh made the sidebar a full viewport tall while
     offset 55px down, pushing its bottom (the account block / avatar) below the
     fold. Fill only the space under the header and stick there. */
  position: sticky; top: 55px;
  height: calc(100vh - 55px);
  overflow-y: auto;
  /* `.hf-side__account` is `position: sticky; bottom: 0`, opaque and z-index 2,
     and this scrollport deliberately carries NO bottom padding (the note above),
     so its padding edge IS its border edge. Moving focus to a row near the foot
     of the nav therefore parks that row flush with the bottom edge — entirely
     behind the pinned block, focus ring and all (WCAG 2.2 AA 2.4.11). Reserve
     the block's full height so the focus scroll stops short of it: the 44px tap
     floor of `.hf-acct-trigger` (ADR 0005) + its own --space-3 padding top and
     bottom + its 1px `border-top`. Dropping the border leaves a 1px sliver.
     This is the same clearance `sidebar-rail.js::revealActive()` nudges for, and
     that covers only the ACTIVE row at load and on resize — never traversal. */
  scroll-padding-bottom: calc(44px + var(--space-3) * 2 + 1px);
}
.hf-side__logo {
  /* flex: none — .hf-side is a fixed-height column flex container; when the
     nav overflows, shrinkable items get crushed (the logo squashed to 0px,
     clipping the whole top section). The sidebar must scroll, never squash. */
  flex: none;
  height: 55px;
  /* Pinned to the TOP of the scrolling sidebar for the same reason the account
     block is pinned to the bottom: on a viewport too short for the nav the rail
     scrolls, and an unpinned wordmark scrolls away — taking the page title with
     it, since `.hf-side__pagetitle` IS this element. Opaque fill so nav rows
     pass under it, not through it. */
  position: sticky; top: 0; z-index: calc(var(--z-base) + 2);
  background: var(--sidebar-bg);
  display: flex; align-items: center;
  padding: 0 8px;
  font-weight: 800;
  font-size: 22px;
  letter-spacing: -0.02em;
  color: var(--sidebar-fg-strong);
  text-decoration: none;
  margin-bottom: 4px;
}
.hf-side__logo .dot { color: var(--enroute-green-400); }
/* The rail wordmark's collapsed form. base.html emits BOTH the -full and -mini
   spans on EVERY authenticated page; the rules that choose between them live in
   the desktop rail block far below (@media (min-width: 1025px)). Without this
   base rule the mini span falls back to the UA default under that breakpoint,
   so the <=1024 nav drawer rendered the pair concatenated -- "Dashboarde.",
   "Orderse.", "enroute.deliverye.". Hide it here; the rail rule flips it back
   to `inline` when collapsed. Pinned by
   tests/integration/test_static_assets.py::test_sidebar_mini_wordmark_hidden_by_default
   -- do not delete. (The -full span needs no base rule: a bare span is already
   inline, which is the wanted default everywhere but the collapsed rail.) */
.hf-side__logo-mini { display: none; }
.hf-side__pagetitle { font-size: 20px; }
.hf-side__pagetitle small {
  display: block;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sidebar-fg-mute);
  margin-top: -1px;
}

.hf-side__spacer { flex: 1 1 auto; min-height: 12px; }

.hf-side__account {
  /* Pin the account block to the bottom of the scrolling sidebar so the avatar
     is ALWAYS reachable, even when the nav list overflows a short viewport
     (sticky also establishes the containing block the popped-above menu anchors
     to). The opaque sidebar fill keeps scrolling nav rows from showing through. */
  position: sticky;
  bottom: 0;
  z-index: calc(var(--z-base) + 2);
  background: var(--sidebar-bg);
  display: flex; flex-direction: column;
  /* Owns the sidebar's foot spacing (see the `padding: 0 10px 0` note on
     .hf-side) so the pinned block meets the rail's bottom edge exactly. */
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
  /* 0.06 (--sidebar-hover-bg) was effectively invisible on ink-900 chrome;
     0.12 makes the account block a perceptible, deliberate break from the nav. */
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  margin-top: var(--space-2);
}

/* account-menu feat: the avatar + name button that toggles the account dropdown.
   Its OWN class (not .hf-side__item) so mobile.js does not auto-close the drawer
   when the avatar is tapped. Resets native <button> chrome. */
.hf-acct-trigger {
  display: flex; align-items: center; gap: 11px;
  width: 100%; box-sizing: border-box;
  padding: 8px 10px;
  min-height: 44px;   /* ADR 0005: >=44px tap target (UX-24) */
  border-radius: var(--radius-md);
  background: none; border: 0; cursor: pointer;
  color: var(--sidebar-fg); font-family: inherit; text-align: left;
}
.hf-acct-trigger:hover { background: var(--sidebar-hover-bg); }
.hf-acct-trigger:hover .hf-acct-trigger__name { color: var(--sidebar-fg-strong); }
/* F120: the dark sidebar makes the UA outline ~1.03:1; use the light inset
   outline so keyboard focus is visible (matches .hf-side__item). */
.hf-acct-trigger:focus-visible {
  outline: 2px solid var(--sidebar-fg-strong);
  outline-offset: -2px;
  border-radius: var(--radius-md);
}
.hf-acct-trigger__name {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 13px; font-weight: 600; color: var(--sidebar-fg);
}
.hf-acct-trigger__caret {
  width: 14px; height: 14px; flex-shrink: 0;
  opacity: 0.6; transition: transform var(--dur-base);
}
.hf-acct-trigger[aria-expanded="true"] .hf-acct-trigger__caret { transform: rotate(180deg); }

/* The initials chip. Decorative (aria-hidden) — the name text is the
   accessible identity, so the white-on-gradient contrast is not load-bearing. */
.hf-avatar {
  width: 28px; height: 28px; flex-shrink: 0;
  border-radius: var(--radius-pill);
  background: var(--avatar-gradient);
  color: var(--avatar-fg);
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; letter-spacing: 0.02em;
}

/* The account dropdown: a paper card popping ABOVE the trigger (the account
   block sits at the sidebar bottom). Mirrors the .oactx-menu recipe so the
   app's two menus stay visually consistent.
   NOTE: position:absolute inside .hf-side (overflow-y:auto) means a much TALLER
   menu could be clipped by the scroll container on a short viewport. Fine for
   the current 2 items; if this grows past ~4, switch to position:fixed + a JS
   clamp like order-actions-menu.js place(). */
.hf-acct-menu {
  position: absolute;
  bottom: calc(100% + 6px); left: 0; right: 0;
  z-index: var(--z-dialog);
  background: var(--bg-paper);
  border: 1px solid var(--border-1);
  border-radius: 10px;
  box-shadow: 0 8px 28px rgba(10, 13, 11, 0.16);
  padding: 6px;
}
.hf-acct-menu[hidden] { display: none; }
.hf-acct-menu form { margin: 0; }
.hf-acct-menu__item {
  display: flex; align-items: center; gap: 10px;
  width: 100%; box-sizing: border-box;
  padding: 8px 10px;
  min-height: 44px;   /* ADR 0005: >=44px tap target (UX-24) */
  border: 0; border-radius: 7px;
  background: transparent; color: var(--fg-1);
  font: inherit; text-align: left; text-decoration: none; cursor: pointer;
}
.hf-acct-menu__item svg { width: 17px; height: 17px; flex-shrink: 0; color: var(--fg-3); }
.hf-acct-menu__item:hover { background: var(--enroute-ink-100); }
.hf-acct-menu__item:hover svg { color: var(--fg-2); }
/* Paper-surface focus ring (the menu pops onto paper, not the dark sidebar).
   box-shadow form — `outline: var(--focus-ring)` is invalid (it's a shadow). */
.hf-acct-menu__item:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.hf-side__label {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em;
  color: var(--sidebar-fg-mute); font-weight: 700;
  padding: 14px 10px 6px;
}
.hf-side__label:first-child { padding-top: 4px; }
.hf-side__item {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 10px;
  min-height: 44px;   /* ADR 0005: >=44px tap target (UX-24) */
  box-sizing: border-box;
  border-radius: var(--radius-md);
  font-size: 13.5px; font-weight: 500;
  color: var(--sidebar-fg);
  text-decoration: none;
  white-space: nowrap;
  transition: background 100ms, color 100ms;
}
.hf-side__item:hover { background: var(--sidebar-hover-bg); color: var(--sidebar-fg-strong); }
/* F120 (critical): keyboard focus on the sidebar nav was invisible — the UA
   outline on the ink-900 sidebar computes to ~1.03:1. Use a LIGHT (white) outline
   so the focused item is clearly visible against the dark sidebar. */
.hf-side__item:focus-visible {
  outline: 2px solid var(--sidebar-fg-strong);
  outline-offset: -2px;   /* inset so the ring stays within the 240px sidebar */
  border-radius: var(--radius-md);
}
/* The active item has a paper-white background, so a white ring would blend —
   draw it in dark ink instead. */
.hf-side__item--active:focus-visible {
  outline-color: var(--enroute-ink-900);
}
.hf-side__item svg { width: 17px; height: 17px; flex-shrink: 0; opacity: var(--sidebar-icon-opacity); }
.hf-side__item:hover svg { opacity: 1; }
.hf-side__item--active,
.hf-side__item--active:hover {
  background: var(--bg-paper);
  color: var(--enroute-ink-900);
  font-weight: 700;
}
.hf-side__item--active svg { opacity: 1; }
.hf-side__item__count {
  margin-left: auto;
  font-size: 11px;
  font-weight: 600;
  background: rgba(255,255,255,0.10);
  color: var(--sidebar-fg-strong);
  padding: 2px 7px;
  border-radius: var(--radius-pill);
  font-variant-numeric: tabular-nums;
}
.hf-side__item--active .hf-side__item__count {
  background: rgba(10,13,11,0.08);
  color: var(--enroute-ink-900);
}
/* live-badges-poll D11: the badge span is now rendered UNCONDITIONALLY
   (empty at zero) so a later poll always has a target to write into — this
   rule is what makes an empty span invisible again. Relies on the rule above
   setting no `display` (verified at spec time, re-verified as audit B2). */
.hf-side__item__count:empty { display: none; }

/* ---------- Main content shell ---------- */
.hf-main { padding: 18px; min-width: 0; }

/* ---------- Status pill (3-bucket palette, locked) ---------- */
.st-pill {
  display: inline-flex; align-items: center;
  gap: 6px;
  padding: 3px 9px 3px 8px;
  border-radius: var(--radius-pill);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  background: var(--st-bg);
  color: var(--st-fg);
  white-space: nowrap;
  --st-bg: var(--enroute-ink-100);
  --st-fg: var(--enroute-ink-700);
  --st-dot: var(--enroute-ink-400);
}
.st-pill .dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--st-dot);
}
.st-pill.s-pending,
.st-pill.s-accepted   { --st-bg: var(--enroute-amber-50); --st-fg: var(--enroute-amber-800); --st-dot: var(--enroute-amber-500); }
.st-pill.s-loading,
.st-pill.s-transit    { --st-bg: var(--enroute-green-50); --st-fg: var(--enroute-green-800); --st-dot: var(--enroute-green-500); }
.st-pill.s-delivered,
.st-pill.s-invoiced   { --st-bg: var(--enroute-ink-100); --st-fg: var(--enroute-ink-700); --st-dot: var(--enroute-ink-400); }
.st-pill.s-cancelled  { --st-bg: var(--enroute-rose-100); --st-fg: var(--enroute-rose-700); --st-dot: var(--enroute-rose-500); }
/* draft-orders (ADR 0046): the pre-placement "Draft" pill — muted neutral with a
   DASHED border (provisional, not yet live), token-driven (no raw values). */
.st-pill.s-draft {
  --st-bg: var(--draft-bg);
  --st-fg: var(--draft-fg);
  --st-dot: var(--enroute-ink-400);
  border: 1px dashed var(--draft-border);
}

/* Quiet inline status (used in tables): just dot + label, no chip background */
.st-inline {
  display: inline-flex; align-items: center;
  gap: 7px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--fg-1);
  white-space: nowrap;
}
.st-inline .dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--enroute-ink-400);
  flex: none;
}
.st-inline.s-pending .dot,
.st-inline.s-accepted .dot   { background: var(--enroute-amber-500); }
.st-inline.s-loading .dot,
.st-inline.s-transit .dot    { background: var(--enroute-green-500); }
.st-inline.s-delivered .dot,
.st-inline.s-invoiced .dot   { background: var(--enroute-ink-400); }
.st-inline.s-cancelled .dot  { background: var(--enroute-rose-500); }

/* ---------- Primary surface (cards / panels) ---------- */
.surface {
  background: var(--bg-paper);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
}
.surface--quiet {
  background: var(--bg-canvas);
  border-radius: var(--radius-lg);
  box-shadow: inset 0 0 0 1px var(--border-1);
}
/* `.surface--quiet` carries no padding: every other user pairs it with a page class
   that supplies one (`.lp-row`). A bare `ul.surface--quiet` therefore falls back to
   the browser's own list indent, which insets the content from the left rule with
   markers and pushes it PAST the right one — the panel overflows at phone width
   (price-book's multidrop prompt, whose two hosts link no page stylesheet between
   them, so this cannot be scoped to a page). Pad it and drop the markers. */
ul.surface--quiet {
  list-style: none;
  margin: 0;
  padding: var(--space-4);
  display: grid;
  gap: var(--space-4);
}

/* ---------- Buttons ---------- */
.btn {
  appearance: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 14px;
  border-radius: var(--radius-md);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
  border: 0;
  background: var(--enroute-ink-100);
  color: var(--enroute-ink-900);
  text-decoration: none;
  white-space: nowrap;
  transition: background 100ms;
  min-height: 36px;
}
.btn:hover { background: var(--enroute-ink-200); }
/* submit-once guard: a submitted form's buttons disable until the response
   lands (8s disarm) — dim + lock the cursor so the wait state is visible. */
.btn:disabled, .btn:disabled:hover, .hf-hbtn:disabled { opacity: 0.55; cursor: not-allowed; }
.btn svg { width: 14px; height: 14px; }
.btn--primary {
  /* a11y-visual-02: white 13px label needs >=4.5:1 — green-700 CTA fill (5.64:1). */
  background: var(--cta-bg);
  color: #fff;
}
.btn--primary:hover { background: var(--cta-bg-hover); }
.btn--outline {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--border-2);
  color: var(--fg-1);
}
.btn--outline:hover { background: var(--enroute-ink-100); box-shadow: inset 0 0 0 1px var(--border-3); }
.btn--danger {
  background: transparent;
  box-shadow: inset 0 0 0 1px var(--enroute-rose-200);
  color: var(--enroute-rose-700);
}
.btn--danger:hover { background: var(--enroute-rose-50); box-shadow: inset 0 0 0 1px var(--enroute-rose-400); }
/* Tweak 061: amber "Change <doc type>" button shown once a document exists. */
.btn--change {
  background: var(--enroute-amber-500);
  color: var(--enroute-amber-800);
}
.btn--change:hover { filter: brightness(0.95); }
.btn--ghost {
  background: transparent;
  color: var(--fg-2);
}
.btn--ghost:hover { background: var(--enroute-ink-100); color: var(--fg-1); }
.btn--lg { padding: 12px 18px; font-size: 14px; min-height: 44px; }
.btn--block { width: 100%; justify-content: center; }
/* F120/F008/F054 (critical): visible keyboard focus on every button.
   `.btn` had no :focus-visible, so keyboard focus fell back to the UA outline
   (invisible on dark chrome / brand fills). Brand-press outline (≈5.6:1 on paper)
   + the soft brand ring is the canonical pattern (see .ol-qf-chip). Variants that
   carry a brand-green or rose fill swap to a LIGHT/tinted outline so the ring is
   perceivable against the coloured fill rather than blending in. */
.btn:focus-visible {
  outline: 2px solid var(--brand-press);
  outline-offset: 2px;
  box-shadow: var(--focus-ring);
}
.btn--primary:focus-visible,
.btn--change:focus-visible {
  outline-color: var(--enroute-green-50);
}
.btn--danger:focus-visible {
  outline-color: var(--enroute-rose-700);
}

/* ---------- Form atoms ---------- */
.fld { display: flex; flex-direction: column; gap: 6px; }
.fld__lbl {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  /* F128: fg-3 (ink-400) is ≈4.6:1 at 11px — borderline AA with no margin. Use
     fg-2 (ink-600) for a comfortable AA pass at this small uppercase size. */
  color: var(--fg-2);
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.fld__lbl em {
  font-style: normal;
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  font-size: 11px;
  /* JJ, 2026-08-31: "the required/optional tags are too faint". At 11px the AA
     floor is the normal-text 4.5:1 — the 3:1 relaxation wants 18.66px bold or
     24px regular — and --fg-4 (ink-300 #9AA39D) measured 2.59:1 on card paper
     and 2.34:1 on the --bg-sunken inset the fuel drop row uses. --fg-3
     (ink-500 #4A524D) is 8.06:1 and 6.85:1 on the same two grounds, and stays a
     step quieter than the label's own --fg-2 above, so the hierarchy holds.
     Guarded by tests/conformance/test_a11y_pass1.py, which recomputes both
     ratios off the live palette. */
  color: var(--fg-3);
}
/* UX-37: distinguish the "required" marker from the muted "optional" one so
   mandatory fields are scannable. Required reads in the brand green at a
   slightly heavier weight; optional keeps the quieter treatment above.
   🔴 GREEN-700, NOT --brand. --brand is green-500 #00AA3D, which is 3.08:1 on
   white and 2.78:1 on the sunken inset — the same failure --link and --cta-bg
   already step down from (see the notes beside those tokens). green-700
   #00782B is 5.64:1 and 5.10:1 on the two grounds a `.fld` renders on. */
.fld__lbl em.req {
  color: var(--enroute-green-700);
  font-weight: 600;
}
/* F056: make the required marker brand-green GLOBALLY (a control-layer style),
   not just where a template remembered to add `class="req"`. Any `.fld` whose
   control is [required] gets a brand-green, slightly heavier label marker —
   so auth/onboarding/location forms that still use the plain `<em>required</em>`
   pick up the cue without per-template edits. Optional fields (no [required])
   keep the quieter --fg-3 treatment above. Same green-700 as `em.req`, and for
   the same reason: --brand cannot clear AA at 11px on either ground. */
.fld:has(:required) .fld__lbl em {
  color: var(--enroute-green-700);
  font-weight: 600;
}
.fld__hint {
  font-size: 12px;
  color: var(--fg-3);
}
.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 {
  font: inherit;
  font-size: 14px;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  border: 0;
  background: var(--bg-paper);
  box-shadow: inset 0 0 0 1px var(--border-2);
  color: var(--fg-1);
  outline: 0;
  min-height: 44px;   /* ADR 0005: >=44px tap target (UX-24) */
  min-width: 0;
  width: 100%;
}
.fld textarea { min-height: 88px; resize: vertical; line-height: 1.5; }
.fld select { appearance: none; padding-right: 32px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='none' stroke='%23232825' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M1 1l4 4 4-4'/></svg>");
  background-repeat: no-repeat; background-position: right 12px center;
}
/* a11y-code-14: the focused field's outer ring was 3px at 6%-alpha black —
   imperceptible on the white card, leaving only a 1px border-colour change as
   the focus cue. Keep the soft 1px ink inner border, wire the OUTER ring
   through the shared var(--focus-ring) (now a hard white+green-700 pair),
   matching .app-input:focus and the rest of the design system. */
.fld input:focus,
.fld select:focus,
.fld textarea:focus {
  box-shadow: inset 0 0 0 1px var(--enroute-ink-700), var(--focus-ring);
}
/* fuel-sales (S5): a FROZEN field — a value that is still worth showing and can no
   longer be changed (the ordered volume of an uplifted fuel delivery). A disabled input
   that renders exactly like an editable one is an affordance that lies: the user retypes
   the number and waits for a save that will never come. So it reads as a fact, not as a
   control — paper background, no inset border ring, quiet ink. Tokens only.

   ledger-cutover (#5, SP-F family 7): extended to `select`. The placement page's
   order type is DERIVED from the two billing answers and is rendered disabled, so
   it needs the identical treatment — and a `<select>` that keeps its border ring
   while refusing to open is the same lie the comment above describes, one control
   type over. */
.fld--frozen input,
.fld--frozen select,
.fld--frozen input:disabled,
.fld--frozen select:disabled {
  background: var(--bg-sunken);
  box-shadow: none;
  color: var(--fg-2);
  cursor: not-allowed;
  -webkit-text-fill-color: var(--fg-2);   /* Safari greys a :disabled input's text */
  opacity: 1;
}
/* ...and the STYLED twin of that select. `custom-select.js` (ADR 0005 progressive
   enhancement) hides the native control and renders a `.cs__btn` in its place, so
   the rule above reaches the element nobody can see. `.cs.is-disabled`'s 0.6 opacity
   alone still draws a paper card with a border ring — a control that looks pressable
   and is not. Same three tokens as the input, same reason. */
.fld--frozen .cs__btn,
.fld--frozen .cs__btn:disabled {
  background: var(--bg-sunken);
  border-color: var(--border-1);
  color: var(--fg-2);
  cursor: not-allowed;
}
/* The chevron IS the "this opens" promise. A control that cannot open must not
   make it. (`aria-hidden` already, so nothing is lost to a screen reader.) */
.fld--frozen .cs__chev { display: none; }
/* …and the case with NO control at all. Where the "value" is a sentence about
   what happens later (the client placement form's transporter, assigned by
   enroute after submit), a `<input disabled>` is the same lie one step further:
   it is announced as a text box, it has nothing to name it, and there is nothing
   to type. Render it as static text carrying `.fld__ctrl` — that class already
   draws the control box, so the field keeps its rhythm beside the real inputs in
   a `.pl-frow`, while the accessibility tree sees prose. `cursor: default`, not
   the `not-allowed` above: nothing here was ever pressable.

   `div.fld__ctrl`, not the bare class (fix lane, 2026-09-09): `ui_form_field`
   used to wrap every widget in its OWN `<div class="fld__ctrl">`, so this rule
   only ever matched that static-text div. The double-box fix moved
   `.fld__ctrl` onto the WIDGET itself (`frontend/tags/form_field.py`), which
   means a frozen `<select>`/`<input>` now also carries the class — and
   `display: flex` on a real form control is a layout change this rule was
   never meant to make (a `<div>` needs it to vertically-centre text; a native
   widget already does that on its own). Scoping to the tag keeps this rule
   matching only the one case it was written for. */
.fld--frozen div.fld__ctrl {
  background: var(--bg-sunken);
  box-shadow: none;
  color: var(--fg-2);
  cursor: default;
  display: flex;
  align-items: center;
}
.fld--inline { display: grid; grid-template-columns: minmax(120px, 180px) 1fr; gap: 12px 16px; align-items: center; }
.fld--inline .fld__lbl { text-transform: none; letter-spacing: 0; font-size: 13px; font-weight: 600; color: var(--fg-1); }

/* Native checkbox / radio row: brand-coloured control + a same-line label.
   Use .fld--check for [checkbox | label] rows; .fld--inline's grid columns
   assume the opposite order ([label | input]) and misalign the box. */
.fld--check { display: flex; align-items: center; gap: 10px; flex-direction: row; }
.fld--check .fld__lbl { text-transform: none; letter-spacing: 0; font-size: 14px; font-weight: 500; color: var(--fg-1); margin: 0; }
.fld input[type="checkbox"],
.fld input[type="radio"] {
  width: 18px; height: 18px; flex: none; margin: 0;
  accent-color: var(--brand); cursor: pointer;
}

/* Inline composite control: two inputs glued together (eg "R/L" prefix) */
.fld__ctrl--combo {
  display: flex; align-items: stretch;
  background: var(--bg-paper);
  box-shadow: inset 0 0 0 1px var(--border-2);
  border-radius: var(--radius-md);
  overflow: hidden;
  /* Five of the eight usages write `class="fld__ctrl fld__ctrl--combo"`, and
     `.fld__ctrl` (the control group far above) hands the wrapper `padding: 10px
     12px` this block never reset — which floated a 44px input inside a 64px
     white gutter and pushed the affix off the ring. The combo owns its own box;
     the affix must butt it, so the padding lives on the inner input instead. */
  padding: 0;
  min-height: 40px;
}
/* The inner control draws NO box of its own — the wrapper IS the box. Zeroing
   `border` is not enough: `.fld input[type="number"]` in the control group above
   is (0,2,1) and supplies both a corner radius and an inset ring, which the old
   (0,1,1) reset could not outrank — so the number input kept its own rounded ring
   and the wrapper's `overflow: hidden` clipped it into a stray radius mid-pill,
   a box inside a box. The `.fld` prefix lifts this to (0,2,1), a TIE that source
   order then settles in this block's favour (it sits below that group in this
   same file). It is therefore load-bearing, not decorative — and a combo placed
   OUTSIDE a `.fld` would not match at all and would lose `flex: 1` and collapse.
   All eight current usages sit inside one. */
.fld .fld__ctrl--combo > input {
  border: 0; outline: 0; background: transparent;
  box-shadow: none;
  border-radius: 0;
  padding: 10px 12px;
  font-size: 14px;
  flex: 1;
  min-width: 0;
}
/* Winning the resting state is not enough. `.fld input:focus` (above, (0,2,1))
   and app-shell.css's `.fld input[aria-invalid="true"]` / `…:focus` ((0,2,1) and
   (0,3,1), in a LATER sheet, so they win every tie) all reach this input too, and
   would put the ring back the moment the field is focused or errors. Each state
   is therefore out-specified by name: (0,3,1), (0,3,1) and (0,4,1). */
.fld .fld__ctrl--combo > input:focus,
.fld .fld__ctrl--combo > input[aria-invalid="true"],
.fld .fld__ctrl--combo > input[aria-invalid="true"]:focus { box-shadow: none; }
/* …and because that reset also kills the focus and error rings the input would
   otherwise paint, both move to the WRAPPER, where they belong: the ring is the
   whole pill's, and an element's own box-shadow is not clipped by its own
   `overflow: hidden`, so the outer white+green halo finally renders (on the
   input it was being swallowed). Specificity, in the order they must resolve:
   `:has(…)` takes its argument's, so invalid is (0,2,1), focus-within (0,2,0),
   and the pair (0,3,1) — the pair must exist or a focused invalid combo shows
   only rose (`box-shadow` is one property; see app-shell.css's twin note). */
.fld__ctrl--combo:has(> input[aria-invalid="true"]) {
  box-shadow: inset 0 0 0 1.5px var(--enroute-rose-500);
}
.fld__ctrl--combo:focus-within {
  box-shadow: inset 0 0 0 1px var(--enroute-ink-700), var(--focus-ring);
}
.fld__ctrl--combo:has(> input[aria-invalid="true"]):focus-within {
  box-shadow: inset 0 0 0 1.5px var(--enroute-rose-500), var(--focus-ring);
}
.fld__ctrl--combo > .fld__affix {
  display: inline-flex; align-items: center; padding: 0 12px;
  background: var(--bg-sunken);
  color: var(--fg-3);
  font-size: 12.5px; font-weight: 600;
  border-left: 1px solid var(--border-1);
  font-family: var(--font-mono);
}
.fld__ctrl--combo > .fld__affix--lead {
  border-left: 0; border-right: 1px solid var(--border-1);
}

/* The house `.drawer`/`.drawer-backdrop` CONTENT-drawer contract (the
   `data-drawer-open`/`setupDrawers()` mechanism, mobile.js) retired with its
   last consumer — frontend-overlays (2026-09, D13) migrated every content
   drawer (the rate editor, the group drawer, the shared orders-list/calendar
   filter drawer, activity's own filter drawer) onto `{% ui_drawer %}`
   (`.enr-drawer*`, `src/static/css/framework/overlays.css`).
   What is LEFT here is what `openActionSheet()` (mobile.js, the "More"
   action sheet — G4/X9, its own dismissal, deliberately NOT migrated) still
   renders its markup with: `.drawer`/`.drawer.open`/`.drawer-backdrop`,
   `.drawer__hd`, `.drawer__close`, `.drawer__body`. It shares the class
   vocabulary with the retired content-drawer contract by historical
   coincidence, not by using the same JS mechanism — do not read a live
   `.drawer` element in the DOM as evidence `setupDrawers()` survived. */
.drawer-backdrop {
  position: fixed; inset: 0;
  background: rgba(10,13,11,0.32);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out);
  z-index: calc(var(--z-drawer) - 10);
}
.drawer-backdrop.open { opacity: 1; pointer-events: auto; }
.drawer {
  position: fixed; top: 0; right: 0;
  height: 100vh;
  width: 380px;
  max-width: 100vw;
  background: var(--bg-paper);
  box-shadow: -12px 0 32px rgba(10,13,11,0.18);
  transform: translateX(100%);
  /* a11y-code-03: a closed off-canvas drawer kept all its fields/buttons in
     the Tab order (it was only translated off-screen). visibility:hidden drops
     them out; the 0s visibility flip is DELAYED by --dur-base on close so the
     slide-out animation still plays, and undelayed on open. */
  visibility: hidden;
  transition: transform var(--dur-base) var(--ease-out),
              visibility 0s linear var(--dur-base);
  z-index: var(--z-drawer);
  display: flex; flex-direction: column;
}
/* `none`, NOT `translateX(0)`. An identity transform still establishes a
   containing block for `position: fixed` descendants — see custom-select.css's
   own note on this trap. The closed state keeps its transform, so the slide
   still animates — a transition from `translateX(100%)` to `none` interpolates
   against the identity matrix. */
.drawer.open { transform: none; visibility: visible; transition-delay: 0s; }
.drawer__hd {
  padding: 16px 18px;
  border-bottom: 1px solid var(--border-1);
  display: flex; align-items: center; justify-content: space-between;
}
.drawer__hd h2 { margin: 0; font-size: 16px; font-weight: 700; }
.drawer__close {
  appearance: none; background: transparent; border: 0; cursor: pointer;
  width: 30px; height: 30px; border-radius: var(--radius-md);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--fg-2);
}
.drawer__close:hover { background: var(--enroute-ink-100); color: var(--fg-1); }
.drawer__body { flex: 1; overflow-y: auto; padding: 18px; display: flex; flex-direction: column; gap: 16px; }

/* ============================================================
   FORM SHELL — used by Request location / Import orders / Export
   orders / Document upload / Accept / Amend / Cancel pages.
   Single centered card for "context-free" forms; +sticky right rail
   for order-scoped forms.
   ============================================================ */

.form-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 18px;
  align-items: start;
  max-width: 1100px;
  margin: 0 auto;
}
.form-grid--solo {
  display: block;
  max-width: 720px;
  margin: 0 auto;
}
/* `--solo` is display:block, so the container's `gap` above never applies. Two
   stacked cards therefore touched edge to edge and the upper card's rounded bottom
   corners notched into the lower one's square top — visible on the two import pages
   that render a validation report under the upload form (price-book, fuel-terms).
   Only a SECOND card is spaced; a page with one card is unchanged. */
.form-grid--solo > .form-card + .form-card {
  margin-top: var(--space-4);
}
.form-intro {
  display: flex; justify-content: space-between; align-items: flex-end;
  gap: var(--space-5);
  /* No inline padding: the intro must share the form-card's exact left/right
     edges (both are max-width:1100px + margin:0 auto inside .hf-main). The old
     0 24px pushed the heading 24px off the card's left rule, visible on wide
     screens once both bands reach max-width and centre. */
  padding: 0 0 var(--space-3);
  max-width: 1100px;
  margin: 0 auto;
  width: 100%;
}
.form-intro--solo { max-width: 720px; }
/* Full-bleed page header: left-aligns to the content edge instead of centring.
   Use on pages whose body is NOT a centred .form-grid — lists, tables,
   dashboards, a bare full-width .form-card. The default .form-intro centres
   itself at max-width:1100px to share a centred .form-grid's edges; over
   full-bleed content that centring makes the heading drift RIGHT of everything
   below it by (content_width - 1100)/2, worsening as the screen widens. --flush
   removes the cap + centring so the heading sits on the content's left rule.
   The intro's band MUST match its body's band — guarded by
   tests/conformance/test_form_intro_alignment.py. */
.form-intro--flush { max-width: none; margin-inline: 0; }
.form-intro__copy h1 {
  font-size: 28px;
  font-weight: 800;
  letter-spacing: -0.025em;
  margin: 0 0 4px;
}
.form-intro__copy p {
  font-size: 13.5px;
  color: var(--fg-3);
  margin: 0;
  max-width: 64ch;
}
.form-intro__actions { display: flex; gap: var(--space-2); }
/* F012: shared mono/muted record id for a page-intro H1 (e.g. "Update ETA #1234").
   Promoted here from loads-cancel.css so EVERY form page — loads and orders — shares
   one source instead of re-inlining the font/colour/size run (which had drifted). */
.form-intro__id {
  font-family: var(--font-mono);
  color: var(--fg-3);
  font-weight: 500;
  font-size: 22px;
}

.form-card {
  background: var(--bg-paper);
  border-radius: var(--radius-lg);
  padding: 22px 24px;
  box-shadow: var(--shadow-card);
  display: flex; flex-direction: column;
  gap: 18px;
  min-width: 0;
}
.form-card__hd {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px;
  margin-bottom: -2px;
}
.form-card__title {
  font-size: 15px;
  font-weight: 700;
  color: var(--fg-1);
  letter-spacing: -0.01em;
  margin: 0;
  display: inline-flex; align-items: baseline; gap: 8px;
}
.form-card__title .step {
  display: inline-flex;
  width: 22px; height: 22px;
  border-radius: 50%;
  background: var(--enroute-ink-900);
  color: #fff;
  font-size: 11px; font-weight: 700;
  align-items: center; justify-content: center;
  letter-spacing: 0;
}
.form-card__sub {
  font-size: 12px;
  color: var(--fg-3);
  font-weight: 500;
}
.form-card__actions {
  display: flex; gap: 10px;
  justify-content: flex-end;
  margin-top: 6px;
}
.form-card__actions .btn { min-height: 44px; }
.form-card__empty {
  margin: 0;
  font-size: 13px;
  color: var(--fg-3);
}

.form-row { display: grid; gap: 14px 18px; grid-template-columns: 1fr; }
.form-row--2 { grid-template-columns: 1fr 1fr; }
.form-row--3 { grid-template-columns: 1fr 1fr 1fr; }

/* Context card (sticky right rail, parent-order summary etc.) */
.ctx-card {
  background: var(--bg-paper);
  border-radius: var(--radius-lg);
  padding: 16px 18px;
  box-shadow: var(--shadow-card);
  display: flex; flex-direction: column;
  gap: 4px;
  position: sticky; top: 73px;
}
.ctx-card__lbl {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: 700;
  color: var(--fg-3);
  margin: 0 0 6px;
}
.ctx-card__id {
  font-family: var(--font-mono);
  font-size: 17px;
  font-weight: 700;
  color: var(--fg-1);
  letter-spacing: -0.01em;
  margin-bottom: 6px;
}
.ctx-row {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 7px 0;
  border-bottom: 1px solid var(--border-1);
  gap: 12px;
  font-size: 12.5px;
}
.ctx-row:last-of-type { border-bottom: 0; }
.ctx-row .lbl { color: var(--fg-3); font-weight: 500; }
.ctx-row .val { color: var(--fg-1); font-weight: 600; text-align: right; max-width: 60%; }
.ctx-row .val.mono { font-family: var(--font-mono); }

/* ============================================================
   ROLE-SCOPED HIDING (shared so every page can use it)
   Add .role-internal-only, .role-client-only, .role-transporter-only,
   or the inverse hide-* utilities to any element. The page's <body>
   needs data-role="internal" | "client" | "transporter".
   ============================================================ */
[data-role="client"]      .role-hide-client      { display: none !important; }
[data-role="transporter"] .role-hide-transporter { display: none !important; }
[data-role="internal"]    .role-hide-internal    { display: none !important; }

.role-internal-only { display: none; }
[data-role="internal"] .role-internal-only { display: revert; }

.role-client-only { display: none; }
[data-role="client"] .role-client-only { display: revert; }

.role-transporter-only { display: none; }
[data-role="transporter"] .role-transporter-only { display: revert; }

/* Role pill (e.g. shown next to a field that's role-gated) */
.role-pill {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  vertical-align: middle;
  margin-left: 6px;
  white-space: nowrap;
}
.role-pill--internal   { background: var(--enroute-cobalt-100); color: var(--enroute-cobalt-700); }
.role-pill--client     { background: var(--enroute-amber-100);  color: var(--enroute-amber-800); }
.role-pill--transporter{ background: var(--enroute-violet-100);                    color: var(--enroute-violet-700); }

/* ============================================================================
   org-admin-lens SP9 — acting-hat chrome (banner + switcher). Only ever
   rendered for an org admin (base.html guards on `org_admin_chrome`), so a
   plain user's page is byte-identical. user-hat-grants SP4: the merged
   read-only view (and its banner state, its switcher "clear" control, and
   the per-row party badge) is retired — see the deletion inventory.
   ========================================================================== */

/* The persistent acting-hat banner atop <main> on every page. */
.oal-banner {
  box-sizing: border-box;
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  width: 100%;
  max-width: 100%;
  padding: var(--space-3) var(--space-4);
  margin: 0 0 var(--space-4);
  border: 1px solid var(--border-2);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-sm);
}
/* Under an active hat: loud amber — attention, "you are acting". */
.oal-banner--acting {
  background: var(--enroute-amber-50);
  border-color: var(--enroute-amber-200);
  color: var(--enroute-amber-800);
}
.oal-banner__dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  margin-top: 6px;
  border-radius: var(--radius-pill);
  background: currentColor;
}
.oal-banner__text { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-1) var(--space-3); }
.oal-banner__label { font-weight: 700; }

/* The sidebar hat switcher (user-hat-grants SP6): a native <details>
   accordion at the TOP of .hf-side, one submit button per EFFECTIVE hat.
   Reshaped from its earlier account-menu-popup version — it now lives on
   the dark sidebar chrome (same tokens as .hf-side__item), not the paper
   .hf-acct-menu, and is its own row rather than a menu entry. `flex: none`
   is the same anti-squash guard `.hf-side__group, .hf-side__find` already
   carry (below) — an overflowing nav must scroll, never crush this row to
   0px. */
.oal-switcher {
  /* flex: none — see the anti-squash rule at the foot of this file
     (.hf-side__group, .hf-side__find, .oal-switcher). */
  margin-bottom: 2px;
}
.oal-switcher__summary {
  display: flex;
  align-items: center;
  gap: 11px;
  list-style: none;
  padding: 9px 10px;
  min-height: 44px;   /* ADR 0005: >=44px tap target (UX-24) */
  box-sizing: border-box;
  border-radius: var(--radius-md);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--sidebar-fg);
  cursor: pointer;
  user-select: none;
}
/* Suppress the native disclosure triangle in both engines — the chevron SVG
   is the only marker. */
.oal-switcher__summary::-webkit-details-marker { display: none; }
.oal-switcher__summary::marker { content: ""; }
.oal-switcher__summary:hover { background: var(--sidebar-hover-bg); color: var(--sidebar-fg-strong); }
.oal-switcher__summary:hover .oal-switcher__summary-icon { opacity: 1; }
/* F120: light inset outline for visibility on the dark sidebar (matches
   .hf-side__item / .hf-acct-trigger). */
.oal-switcher__summary:focus-visible {
  outline: 2px solid var(--sidebar-fg-strong);
  outline-offset: -2px;
  border-radius: var(--radius-md);
}
.oal-switcher__summary-icon { width: 17px; height: 17px; flex-shrink: 0; opacity: var(--sidebar-icon-opacity); }
.oal-switcher__summary-label { flex: 1 1 auto; }
.oal-switcher__chevron { width: 14px; height: 14px; flex-shrink: 0; opacity: 0.6; transition: transform var(--dur-base); }
.oal-switcher[open] .oal-switcher__chevron { transform: rotate(180deg); }

/* The disclosed body: the trigger buttons live HERE, not in <summary> —
   nesting a <form>/<button> inside <summary> is invalid HTML. */
.oal-switcher__body { padding: 2px 0 6px; }
.oal-switcher__body form { display: flex; flex-direction: column; gap: 2px; }
.oal-switcher__opt {
  display: flex;
  align-items: center;
  width: 100%;
  box-sizing: border-box;
  text-align: left;
  padding: 8px 10px 8px 31px;   /* aligns under the summary label, past its icon */
  min-height: 44px;   /* ADR 0005 */
  border: 0;
  border-radius: var(--radius-md);
  background: none;
  color: var(--sidebar-fg);
  font: inherit;
  font-size: 13.5px;
  cursor: pointer;
}
.oal-switcher__opt:hover { background: var(--sidebar-hover-bg); color: var(--sidebar-fg-strong); }
.oal-switcher__opt:focus-visible {
  outline: 2px solid var(--sidebar-fg-strong);
  outline-offset: -2px;
  border-radius: var(--radius-md);
}
.oal-switcher__opt.is-active { position: relative; background: var(--bg-paper); color: var(--enroute-ink-900); font-weight: 700; }
/* Spec §5.4: "a dot marking the active entry" — centred in the icon column. */
.oal-switcher__opt.is-active::before {
  content: "";
  position: absolute;
  left: 16px;
  top: 50%;
  width: 6px;
  height: 6px;
  margin-top: -3px;
  border-radius: 50%;
  background: currentColor;
}

/* olr sidebar redesign (2026-08-18): the nav renders as labelled groups
   (Operate / Activity / Library / Manage — config.context_processors._grouped).
   Each group reuses .hf-side__label for its heading; this only spaces the
   groups apart. The first group sits flush under the Find block. */
.hf-side__group + .hf-side__group { margin-top: 6px; }
/* The internal nav (15 rows + 4 headings) can outgrow a short viewport; the
   sidebar already scrolls (.hf-side overflow-y) with the account block sticky-
   pinned, so the tail stays reachable — the V1 icon-rail pattern (design spec)
   is the long-term answer and is deliberately NOT implemented in V0. */

/* ============================================================
   Auto-collapsing rail sidebar (olr sidebar redesign, 2026-08-18).
   Canonical rules: the Design project's _system.css foot ("V1
   ADDITION — auto-collapsing rail") + components/sidebar.html demo.
   Desktop-only: ≤1024 the mobile drawer (mobile.css) owns .hf-side
   unchanged, and sidebar-rail.js guards on the same media query.
   Rest state is a 64px icon rail; .is-expanded (pointerenter/focusin,
   hysteresis collapse in sidebar-rail.js) overlays the content at
   240px — the shell grid track stays 64px via the negative margin.
   ============================================================ */
@media (min-width: 1025px) {
  .hf-shell--rail { grid-template-columns: 64px minmax(0, 1fr); }
  .hf-side--rail {
    width: 64px;
    overflow-x: hidden;
    z-index: var(--z-floating);
    transition: width var(--dur-base) var(--ease-out);
  }
  .hf-side--rail.is-expanded {
    width: 240px;
    margin-right: -176px;
    box-shadow: var(--shadow-md);
  }
  .hf-side--rail .hf-side__item,
  .hf-side--rail .hf-acct-trigger,
  .hf-side--rail .oal-switcher__summary { padding-left: 14px; white-space: nowrap; }
  .hf-side--rail .hf-acct-trigger,
  .hf-side--rail .oal-switcher__summary { transition: padding-left var(--dur-base) var(--ease-out); }
  .hf-side--rail:not(.is-expanded) .hf-acct-trigger,
  .hf-side--rail:not(.is-expanded) .oal-switcher__summary { padding-left: 8px; }
  .hf-side--rail .hf-side__item > span,
  .hf-side--rail .hf-acct-trigger__name,
  .hf-side--rail .hf-acct-trigger__caret,
  .hf-side--rail .oal-switcher__summary-label,
  .hf-side--rail .oal-switcher__chevron {
    opacity: 0;
    transition: opacity var(--dur-fast) var(--ease-out);
  }
  .hf-side--rail.is-expanded .hf-side__item > span,
  .hf-side--rail.is-expanded .hf-acct-trigger__name,
  .hf-side--rail.is-expanded .hf-acct-trigger__caret,
  .hf-side--rail.is-expanded .oal-switcher__summary-label,
  .hf-side--rail.is-expanded .oal-switcher__chevron { opacity: 1; transition-delay: 60ms; }
  /* Collapsed AND (still) open — the mouse left before the details closed —
     the disclosed option labels get the same fade rather than wrapping/
     clipping at 64px. */
  .hf-side--rail:not(.is-expanded) .oal-switcher__opt {
    white-space: nowrap;
    overflow: hidden;
  }
  .hf-side--rail:not(.is-expanded) .oal-switcher__opt-hat { opacity: 0; }
  .hf-side--rail .hf-side__logo { white-space: nowrap; overflow: hidden; }
  .hf-side--rail .hf-side__logo-full { display: none; }
  .hf-side--rail.is-expanded .hf-side__logo-full { display: inline; }
  .hf-side--rail .hf-side__logo-mini { display: inline; padding-left: 6px; }
  .hf-side--rail.is-expanded .hf-side__logo-mini { display: none; }
  .hf-side--rail .hf-side__find { display: none; }
  .hf-side__group { display: flex; flex-direction: column; }
  .hf-side--rail .hf-side__group > .hf-side__label {
    box-sizing: border-box;
    height: 32px;
    display: flex; align-items: flex-end;
    padding: 0 10px 6px;
    margin: 0;
    white-space: nowrap; overflow: hidden;
    transition: opacity var(--dur-fast) var(--ease-out);
  }
  .hf-side--rail .hf-side__group:first-of-type > .hf-side__label { height: 22px; }
  /* Collapsed, a group label reads as a divider line (text yields to the rule). */
  .hf-side--rail:not(.is-expanded) .hf-side__group > .hf-side__label {
    font-size: 0; line-height: 0;
    padding: 0; margin: 0 12px;
    background: linear-gradient(rgba(255,255,255,0.12), rgba(255,255,255,0.12)) center / 100% 1px no-repeat;
  }
  .hf-side--rail:not(.is-expanded) .hf-side__group:first-of-type > .hf-side__label { background: none; }
  .hf-side--rail .hf-side__item { position: relative; }
  /* Collapsed, a count pill shrinks to a dot pinned to the icon's corner. */
  .hf-side--rail:not(.is-expanded) .hf-side__item__count {
    position: absolute;
    top: 7px; left: 26px;
    min-width: 8px; height: 8px;
    padding: 0;
    border-radius: 50%;
    background: var(--enroute-green-500);
    font-size: 0; line-height: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .hf-side--rail,
  .hf-side--rail .hf-side__item > span { transition: none; }
}

/* Anti-squash (all widths): .hf-side is a fixed-height column flex container,
   so an overflowing nav crushes shrinkable children instead of scrolling —
   the logo went to 0px (clipped top section). Groups and the find form get
   the same guard; the spacer alone absorbs slack. */
.hf-side__group, .hf-side__find, .oal-switcher { flex: none; }

/* ---------------------------------------------------------------------------
   Horizontal scroll affordance for table wrappers
   ---------------------------------------------------------------------------
   Three independent UI passes reported the same defect on three different pages
   (the price book's Compare tab, the clients table, the fleet units table): the table
   is wider than its wrapper, the wrapper scrolls, and NOTHING says so. The last column
   is clipped flush at the card edge and a reader has no reason to think it continues.
   It is not the hard horizontal-page-scroll bug — the page body never moves — which is
   why it survived so long.

   The technique is the `background-attachment: local` pair. Two "cover" gradients are
   attached `local`, so they travel WITH the content and slide away from an edge only
   once you have scrolled off it; two shadow gradients are attached `scroll`, so they
   stay pinned to the wrapper. Net effect: a shadow appears on precisely the edges that
   have more content behind them, and vanishes at each end. No JavaScript.

   Two approaches were measured and rejected first, and the reasons matter:
     * A `mask-image` fade, which this system already uses on `.ol-qf-row__chips`. It is
       permanent and fades content to transparent — over these tables that means a
       gradient laid across money figures, and contrast here is load-bearing.
     * A styled `::-webkit-scrollbar`. Rendered and screenshotted: this Chromium draws
       overlay scrollbars, reserved ZERO layout space (offsetHeight − clientHeight = 0),
       and the capture was pixel-identical to no rule at all. It would have shipped as a
       fix that does nothing, and could not have been tested either.

   The cover gradients must match the wrapper's own background or they read as a band,
   which is why the two `--bg-sunken` wrappers are split out below rather than folded in.

   Each class is DOUBLED (`.cl-tablewrap.cl-tablewrap`) on purpose. Every one of these
   wrappers sets `background:` — the SHORTHAND — in its own page stylesheet, and the
   shorthand resets `background-image` to none. Page sheets load from `head_extra`, after
   this one, so at equal specificity they win and the affordance silently vanished:
   measured on /clients/ as 0 gradient layers before the doubling. Doubling the class
   outranks them without touching six page stylesheets or reordering the cascade.
   `.ol-qf-row__chips` is deliberately absent: it has its own edge mask already.
--------------------------------------------------------------------------- */
.q-tablewrap.q-tablewrap,
.cl-tablewrap.cl-tablewrap,
.mr-tablewrap.mr-tablewrap,
.ol-table-wrap.ol-table-wrap,
.od-table-wrap.od-table-wrap,
.exp-table-wrap.exp-table-wrap {
  background-image:
    linear-gradient(to right, var(--bg-paper) 40%, rgba(255, 255, 255, 0)),
    linear-gradient(to left,  var(--bg-paper) 40%, rgba(255, 255, 255, 0)),
    linear-gradient(to right, var(--border-2), rgba(10, 13, 11, 0)),
    linear-gradient(to left,  var(--border-2), rgba(10, 13, 11, 0));
  background-position: left center, right center, left center, right center;
  background-repeat: no-repeat;
  background-size: 34px 100%, 34px 100%, 16px 100%, 16px 100%;
  background-attachment: local, local, scroll, scroll;
}
/* Same treatment, sunken ground — the price book's and fuel terms' scroll panes sit on
   `--bg-sunken`, so covers painted in `--bg-paper` would show as two pale bands. */
.plr-scroll.plr-scroll,
.ftr-scroll.ftr-scroll {
  background-image:
    linear-gradient(to right, var(--bg-sunken) 40%, rgba(255, 255, 255, 0)),
    linear-gradient(to left,  var(--bg-sunken) 40%, rgba(255, 255, 255, 0)),
    linear-gradient(to right, var(--border-2), rgba(10, 13, 11, 0)),
    linear-gradient(to left,  var(--border-2), rgba(10, 13, 11, 0));
  background-position: left center, right center, left center, right center;
  background-repeat: no-repeat;
  background-size: 34px 100%, 34px 100%, 16px 100%, 16px 100%;
  background-attachment: local, local, scroll, scroll;
}
