/* nav-unify.css — canonical full site header + mobile menu + premium footer for
   the static marketing/legal pages. Injected by applyFullSiteHeader() in
   src/server/app.ts so every non-homepage marketing/legal page ships the SAME
   navigation as landing.html (Solutions dropdown, Pricing, Compare, Demo, theme
   toggle, Sign in, CTA, mobile hamburger) plus the full premium footer.

   The homepage header lives in landing.html and is styled by the landing-*.css
   bundle scoped to `body.qf-wft.landing-v2`. Those pages don't carry that body
   class, so this file re-expresses the SAME design token-first (theme-aware via
   style.css :root / html[data-theme="light"]) and unscoped by body — the
   .site-header / .nav-dd / .site-mobile-menu / .premium-footer classes are
   unique to this injected chrome, so unscoped rules are safe. */

/* ───────────────────────── Header shell ───────────────────────── */
.site-header {
  position: sticky;
  top: 0;
  z-index: 70;
  padding: 12px 20px 0;
  background: transparent;
}
.site-header-inner {
  max-width: 1180px;
  margin: 0 auto;
  min-height: 52px;
  padding: 10px 22px;
  display: flex;
  align-items: center;
  gap: 20px;
  border-radius: 14px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  box-shadow: var(--qf-shadow-md);
}
html[data-theme="light"] .site-header-inner {
  box-shadow: var(--qf-shadow-sm), var(--qf-shadow-md);
}

/* Brand */
.site-brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--ink);
  font-weight: 800;
  letter-spacing: -0.03em;
  font-size: 19px;
  text-decoration: none;
  white-space: nowrap;
}
.site-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.site-logo .qf-brand-mark {
  display: block;
  width: auto;
  height: 30px;
  transform-origin: center;
  transition: all .2s ease;
}
.site-brand:hover .qf-brand-mark,
.site-brand:focus-visible .qf-brand-mark {
  transform: scale(1.08);
}

/* Primary nav */
.site-nav {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 4px;
}
.site-nav a,
.site-actions .signin {
  color: var(--muted);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.005em;
  text-decoration: none;
  padding: 8px 12px;
  border-radius: 8px;
  transition: all .2s ease;
}
.site-nav a:hover,
.site-nav a:focus-visible,
.site-actions .signin:hover,
.site-actions .signin:focus-visible {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* Solutions dropdown */
.nav-dd { position: relative; }
.nav-dd-trigger {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--muted);
  background: transparent;
  border: 0;
  cursor: pointer;
  padding: 8px 12px;
  border-radius: 8px;
  transition: all .2s ease;
}
.nav-dd-trigger:hover,
.nav-dd-trigger:focus-visible,
.nav-dd.is-open .nav-dd-trigger {
  color: var(--accent);
}
.nav-dd-caret {
  width: 12px;
  height: 12px;
  transition: all .2s ease;
}
.nav-dd.is-open .nav-dd-caret { transform: rotate(180deg); }
.nav-dd-panel {
  position: absolute;
  top: calc(100% + 12px);
  left: 0;
  display: grid;
  grid-template-columns: max-content max-content;
  gap: 16px 32px;
  margin: 0;
  padding: 16px 24px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: var(--qf-shadow-md);
  z-index: 80;
}
html[data-theme="light"] .nav-dd-panel {
  box-shadow: var(--qf-shadow-md);
}
.nav-dd-panel[hidden] { display: none; }
.nav-dd-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.nav-dd-head {
  margin: 0 0 8px;
  padding: 0 12px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--muted);
}
.nav-dd-panel a {
  display: block;
  color: var(--ink-soft);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  padding: 8px 12px;
  border-radius: 8px;
}
.nav-dd-panel a:hover,
.nav-dd-panel a:focus-visible {
  color: var(--ink);
  background: var(--surface-2);
}
/* One-line descriptor inside a panel group (e.g. For Importers → what it does) */
.nav-dd-sub {
  display: block;
  padding: 4px 12px 0;
  max-width: 230px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted);
  white-space: normal;
}
/* The rightmost dropdown (For Importers) anchors to the right edge of its
   trigger so a menu near the end of the bar never overflows the viewport. */
.nav-dd-panel--end { left: auto; right: 0; }

/* Actions cluster */
.site-actions {
  display: inline-flex;
  align-items: center;
  gap: 14px;
}
.site-actions .btn-secondary {
  display: inline-flex;
  min-height: 40px;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: transparent;
  border: 1px solid var(--border-strong);
  color: var(--ink);
  /* 8px, not 10: the radius scale is 6 / 8 / 12 and this is a button, so it
     takes --radius-btn — the same value its neighbours in the cluster (the
     theme toggle and "Sign in") already compute to. */
  border-radius: var(--radius-btn);
  padding: 9px 18px;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  transition: all .2s ease;
}
.site-actions .btn-secondary .arr { color: currentColor; }
.site-actions .btn-secondary:hover {
  background: var(--surface-3);
  border-color: var(--border-strong);
  transform: translateY(-1px);
}

/* Burger — hidden on desktop */
.site-burger { display: none; }
.site-mobile-menu { display: none; }

/* ────────────── Collapsed nav (≤ 1023px) — see EOF note ────────────── */
@media (max-width: 1023px) {
  .site-header {
    padding: 0;
  }
  .site-header-inner {
    max-width: none;
    margin: 0;
    padding: 12px 16px;
    gap: 12px;
    justify-content: space-between;
    border-radius: 0;
    border: 0;
    border-bottom: 1px solid var(--border);
    box-shadow: none;
    background: var(--surface-2);
  }
  html[data-theme="light"] .site-header-inner {
    background: var(--surface);
  }
  .site-nav,
  .site-actions .signin,
  .site-actions .btn-secondary {
    display: none;
  }
  .site-actions { gap: 8px; }
  .site-burger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    padding: 0;
    background: transparent;
    border: 1px solid var(--border-strong);
    border-radius: 10px;
    color: var(--ink);
    cursor: pointer;
    flex: none;
  }
  .site-burger svg { width: 20px; height: 20px; display: block; }
  .site-burger .ico-close { display: none; }
  .site-burger[aria-expanded="true"] .ico-open { display: none; }
  .site-burger[aria-expanded="true"] .ico-close { display: block; }

  .site-mobile-menu:not([hidden]) {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 8px 12px 0;
    padding: 8px;
    border-radius: 14px;
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: var(--qf-shadow-md);
  }
  html[data-theme="light"] .site-mobile-menu:not([hidden]) {
    box-shadow: var(--qf-shadow-md);
  }
  .site-mobile-menu a {
    font-size: 14px;
    font-weight: 500;
    letter-spacing: -0.005em;
    color: var(--ink-soft);
    text-decoration: none;
    padding: 12px 14px;
    border-radius: 10px;
    transition: all .2s ease;
  }
  .site-mobile-menu a:hover,
  .site-mobile-menu a:focus-visible {
    color: var(--accent);
    background: var(--surface-2);
  }
  /* Collapsible audience group: <details class="mm-group"><summary class="mm-head"> */
  .site-mobile-menu .mm-group { border-top: 1px solid var(--border); }
  .site-mobile-menu .mm-group:first-child { border-top: 0; }
  .site-mobile-menu .mm-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin: 0;
    padding: 12px 16px;
    min-height: 44px;
    list-style: none;
    cursor: pointer;
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--muted);
  }
  .site-mobile-menu .mm-head::-webkit-details-marker { display: none; }
  .site-mobile-menu summary.mm-head::after {
    content: '▾';
    font-size: 11px;
    color: var(--muted);
    transition: all .2s ease;
  }
  .site-mobile-menu .mm-group[open] > summary.mm-head::after { transform: rotate(180deg); }
  .site-mobile-menu summary.mm-head:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: 10px; }
  .site-mobile-menu .mm-group > a { display: block; padding-left: 24px; }
  .site-mobile-menu .mm-account {
    margin-top: 8px;
    border-top: 1px solid var(--border);
    padding-top: 12px;
    font-weight: 600;
  }
  @media (prefers-reduced-motion: reduce) {
    .site-mobile-menu summary.mm-head::after { transition: none; }
  }
}

/* Tablet: tighten nav item padding so it never overflows */
@media (min-width: 641px) and (max-width: 900px) {
  .site-nav a,
  .nav-dd-trigger { padding-left: 8px; padding-right: 8px; }
  .site-actions .signin { white-space: nowrap; }
}

/* ───────────────────────── Premium footer ───────────────────────── */
.premium-footer {
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  padding: 54px 24px 34px;
}
.premium-footer-inner {
  max-width: 1120px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 32px;
}
.premium-footer .footer-brand {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 16px;
}
.premium-footer .qf-footer-brand {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  margin: 0;
  text-decoration: none;
}
.premium-footer .footer-brand .qf-footer-logo {
  width: auto;
  height: 60px;
  max-width: 100%;
}
.premium-footer .qf-footer-brandtext {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.premium-footer .qf-footer-wordmark {
  /* font-size is LOAD-BEARING: the wordmark is a baked SVG outline sized in
     `em` off this value (final-pass block at EOF). font-weight and
     letter-spacing left with the text node — the weight is the outline and
     the -0.02em tracking is baked into the path positions. */
  font-size: 19px;
  color: var(--ink);
  text-decoration: none;
}
.premium-footer .footer-brand .qf-footer-tagline {
  margin: 0;
  max-width: 340px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--muted);
}
.footer-col h4 {
  color: var(--ink);
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin: 0 0 12px;
}
.footer-col a {
  display: block;
  color: var(--muted);
  text-decoration: none;
  padding: 5px 0;
  font-size: 14px;
}
.footer-col a:hover {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.qf-footer-trustbar {
  max-width: 1120px;
  margin: 32px auto 0;
  padding: 16px 0 0;
  border-top: 1px solid var(--border);
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 12px 24px;
}
.qf-footer-trustbar li {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  color: var(--muted);
}
.qf-footer-trustbar svg {
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
  opacity: 0.85;
}
.premium-footer .footer-bottom {
  max-width: 1120px;
  margin: 34px auto 0;
  border-top: 1px solid var(--border);
  padding-top: 18px;
  display: flex;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  font-size: 11.5px;
  color: var(--muted);
}
.premium-footer .footer-bottom .qf-foot-operator,
.premium-footer .footer-bottom strong {
  color: var(--muted);
  font-weight: 400;
}

@media (max-width: 980px) {
  .premium-footer-inner { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .premium-footer .footer-brand { grid-column: 1 / -1; }
}
@media (max-width: 640px) {
  .premium-footer { padding: 42px 18px 28px; }
  .premium-footer-inner { gap: 24px 16px; }
  .premium-footer .footer-brand { gap: 12px; }
  .premium-footer .footer-brand .qf-footer-logo { height: 52px; }
  .qf-footer-trustbar { gap: 8px 16px; }
  .premium-footer .footer-bottom { display: grid; font-size: 11.5px; }
}

/* ── Auth-gated nav items (hydrated by /nav-auth.js) ────────────────────────
   Personal-workspace links (data-nav-auth="user") ship `hidden` from the
   server and are revealed client-side once /api/directory/auth/me confirms a
   session. The server HTML stays byte-identical for anonymous and signed-in
   visitors, which is what keeps the CDN-cached directory pages safe to share.

   `[hidden]`'s UA rule is only `display:none` at element specificity, so an
   author rule like `.nav-dd-panel a{display:block}` or
   `.site-mobile-menu a{display:block}` silently beats it and the link stays on
   screen for logged-out visitors. Make the attribute authoritative. */
.site-nav [hidden],
.site-actions [hidden],
.nav-dd-panel a[hidden],
.site-mobile-menu a[hidden] { display: none !important; }

/* ═══════════════════════════════════════════════════════════════════════════
   NAV / IA REBUILD — appended at EOF on purpose.

   The spacing + color guards baseline by FILE:LINE, so an edit in the middle of
   this file renumbers 14 tolerated entries and turns a green run red. Everything
   below is therefore additive and appended; the only in-place edit was widening
   one existing media query's max-width (same line count, no renumbering).

   ── THE BREAKPOINT BUG ──────────────────────────────────────────────────────
   The header used to collapse to the burger only at ≤640px, but the full bar
   (brand + three mega-menus + Pricing + theme + Sign in + CTA) needs ~1000px.
   Between 641 and ~960px `.site-actions` therefore ran off the end of
   `.site-header-inner` — measured at +288px on /directory@660 and +170px on
   /pricing@660, still +2px at 960. There is now ONE collapse point: the bar is
   inline at ≥1024px and a burger below it, so the overflow band cannot exist.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Tablet (641–1023px): the nav is a burger here, but there is still plenty of
   room for the floating card header and the primary CTA — only the phone
   layout goes full-bleed. Restores what the ≤1023px block turns off. */
@media (min-width: 641px) and (max-width: 1023px) {
  .site-header { padding: 12px 24px 0; }
  .site-header-inner {
    max-width: 1180px;
    margin: 0 auto;
    padding: 12px 24px;
    gap: 16px;
    justify-content: flex-start;
    border: 1px solid var(--border);
    border-radius: 14px;
    background: var(--surface-2);
    box-shadow: var(--qf-shadow-md);
  }
  html[data-theme="light"] .site-header-inner {
    background: var(--surface-2);
    box-shadow: var(--qf-shadow-sm), var(--qf-shadow-md);
  }
  /* Push the action cluster to the trailing edge now that .site-nav (which
     carried `margin-left:auto`) is hidden. */
  .site-actions { margin-left: auto; }
  .site-actions .btn-secondary { display: inline-flex; }
  .site-mobile-menu:not([hidden]) { margin: 8px 24px 0; }
}

/* ── Mega-menu panel: column count is declared by the panel, not guessed ──
   The base rule hard-codes two `max-content` tracks, so a panel with three
   groups silently wrapped its third column onto a second row. Each panel now
   states how many columns it has. */
.nav-dd-panel--cols1 { grid-template-columns: max-content; }
.nav-dd-panel--cols3 { grid-template-columns: repeat(3, max-content); }

/* ── Mobile drawer sub-headings ──────────────────────────────────────────
   The drawer used to be a flat 11-link list under one heading. Each desktop
   panel column is now a `.mm-sub` inside its <details>, so the phone menu has
   the same structure as the desktop one. */
.site-mobile-menu .mm-sub {
  margin: 8px 0 0;
  padding: 8px 16px 4px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  font-weight: 700;
  color: var(--muted);
}
.site-mobile-menu .mm-group > .mm-sub:first-of-type { margin-top: 0; }
.site-mobile-menu .mm-flat {
  border-top: 1px solid var(--border);
  font-weight: 600;
}

/* ── Footer: five columns that mirror the three header menus + Company/Legal.
   The brand block takes its own full-width row so each link column keeps a
   readable measure instead of being squeezed into a sixth track.

   ── THE TRACK LADDER IS ARITHMETIC, NOT TASTE ───────────────────────────────
   Standing rule: a group never wraps so that ONE item sits alone on a line
   (DESIGN-SYSTEM.md §8). For a footer that means: with N link columns laid out
   in T tracks, the last row holds `N mod T` columns, so any T where
   `N mod T === 1` strands a column on a row of its own. N is 5 here, so:

       T=5 → 5            ✓ one full row
       T=4 → 4 + 1        ✗ orphan
       T=3 → 3 + 2        ✓ (a 5-group wrapping 3+2 is explicitly allowed)
       T=2 → 2 + 2 + 1    ✗ orphan  ← what shipped: LEGAL alone at ≤760px,
                                       stranded next to ~620px of dead space
       T=1 → 1 + 1 + …    ✓ a deliberate full stack, not a wrap remainder

   So the ladder steps 5 → 3 → 1 and SKIPS 4 and 2 entirely. Three tracks is
   held all the way down to 641px (≈165px per column at the low end — the links
   wrap a word, nothing overflows) rather than dropping to two, because two is
   the arithmetic that produces the orphan. Below 641px every column goes
   full-bleed, which is a stack rather than a wrap and leaves no dead track.

   Deriving T from N is why this is stated once, here, instead of being
   re-guessed per breakpoint: change the column count and the only question to
   re-answer is which T values still satisfy `N mod T !== 1`.

   These rules sit AFTER the older `@media (max-width: 980px)` block above, so
   at equal specificity they win for every width — that block's `1fr 1fr` is
   the two-track layout this ladder deliberately replaces.
   navInformationArchitecture.test.ts measures the rendered result at every
   width from 320 to 1600 and fails on any row holding exactly one column. */
.premium-footer-inner { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.premium-footer .footer-brand { grid-column: 1 / -1; }
@media (max-width: 1100px) {
  .premium-footer-inner { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .premium-footer-inner { grid-template-columns: minmax(0, 1fr); }
}

/* ── Wide mega-panels anchor to the HEADER CARD, not to their trigger ─────
   A three-column panel hung off its own trigger ran past the right edge of the
   viewport — measured +100px at 1024px and +70px at 1280px on the For Shippers
   menu. Making the wrapper `static` promotes `.site-header-inner` to the
   containing block, so `left:0` / `right:0` pin the panel inside the header card
   at every desktop width (card ≥982px at the 1024px collapse point; the widest
   panel is ~745px). The panel stays a DOM child of `[data-nav-dd]`, so the
   hover/focus/Escape handlers in HEADER_SCRIPTS are untouched. */
.site-header-inner { position: relative; }
.nav-dd--wide { position: static; }
.nav-dd--wide > .nav-dd-panel { top: calc(100% + 8px); }

/* ═══════════════════════════════════════════════════════════════════════════
   NAV POLISH — appended at EOF for the same reason as the block above: the
   spacing + colour guards baseline by FILE:LINE, so an in-place edit here
   renumbers 14 tolerated entries and turns a green run red. Everything below
   is additive.

   ── 1. A BUTTON NEVER WRAPS ITS OWN LABEL ───────────────────────────────────
   Between 1024 and 1100px the four nav controls broke their own labels across
   two lines — "For Carriers &/Brokers", "For/Shippers", "Free/Tools",
   "Sign/in" — on /directory, /carrier/*, /city/* and /compliance (77 widths),
   and 1024–1032px on the homepage. It is a flex-shrink artefact, not a copy
   problem: `.site-nav` is a flex item that shrinks, and each control shrinks
   with it until the label has to wrap.

   `white-space: nowrap` alone would only trade one defect for a worse one. A
   nowrap flex item's automatic minimum size is its FULL label width, so the
   controls stop shrinking and the header card overflows instead — which is
   exactly the defect the 1024px collapse point was introduced to remove. So
   nowrap ships WITH the compaction below, and both halves are measured
   together: zero header overflow at every width 1024–1600, anonymous and
   signed in, or neither ships.

   ── 2. THE COMPACTION IS SIZED FROM THE ARITHMETIC ──────────────────────────
   At the 1024px collapse point the header card's content box is 938px, and it
   has to hold brand (132) + nav + actions. The nav's natural width is 491px
   and the directory's action cluster is 337px — 960px of demand against 938,
   which is the 22px shortfall the labels were absorbing by wrapping. Stepping
   the inter-item gaps to 8/16px, the control padding to 8px and the label to
   13px takes the nav to ~420px and the cluster to ~312px, i.e. ~884px against
   938 — the labels fit on one line with room to spare, and nothing overflows.
   The band ends at 1140px, above which the natural sizes fit unaided.
   ═══════════════════════════════════════════════════════════════════════════ */
.site-nav .nav-dd-trigger,
.site-nav > a,
.site-actions .signin { white-space: nowrap; }

@media (min-width: 1024px) and (max-width: 1140px) {
  .site-header-inner { gap: 16px; }
  .site-nav { gap: 0; }
  .site-nav > a,
  .site-nav .nav-dd-trigger { padding-left: 8px; padding-right: 8px; font-size: 13px; }
  .site-actions { gap: 8px; }
  .site-actions .btn-secondary { padding-left: 12px; padding-right: 12px; }
}

/* ── 3. THE FOUR-ITEM TRUST BAR GOES 2×2, NEVER 3+1 ────────────────────────
   `.qf-footer-trustbar` holds exactly FOUR claims in a wrapping flex row, so
   between 459 and 673px (homepage) / 495 and 697px (every PREMIUM_FOOTER page)
   the fourth wrapped onto a line of its own — the orphan the standing rule
   forbids (DESIGN-SYSTEM.md §8: an even count wraps 2×2, never 3+1).

   Four items in T tracks strand `4 mod T` on the last row, so T=3 is the one
   forbidden count; the ladder is 4 → 2, skipping 3. Above 720px all four fit
   on one line on both surfaces (they need ≤650px of the ≥673px available), so
   flex-wrap can only ever produce the single full row it already does. At and
   below 720px an explicit two-track grid makes the wrap 2×2 by construction
   instead of by luck — including at 375px, where the flex row happened to land
   on 2+2 already.

   NOT TOUCHED, deliberately: `.qf-payrow-trust`, whose one-per-line stack at
   ≤720px is documented as intentional at style.css:1638. Three items stacking
   fully is a stack, not a wrap remainder. */
@media (max-width: 720px) {
  .qf-footer-trustbar {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   THE OOG CTA — appended at EOF, like everything else here, because the
   spacing and colour guards baseline by FILE:LINE and a mid-file insert
   renumbers every tolerated entry below it.

   ONE AFFORDANCE, TWO BARS. `.site-oog` is a quiet text link in the header's
   action cluster; `.qf-foot-oog` is the same claim as a muted line in the
   footer bar. Neither is a coloured banner and neither is a second primary
   button — the header already carries exactly one of those per surface.

   THE HEADER COPY HIDES AT ≤1023px, alongside `.signin` and the primary
   button, because 1024px is the single collapse point and below it the bar is
   brand + theme + burger. Between 1024 and 1140px the bar is measured tight
   (nav ~420px + cluster ~312px against a 938px content box), so the CTA joins
   the existing compaction step rather than adding width the band has not got.
   ═══════════════════════════════════════════════════════════════════════════ */
.site-actions .site-oog {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
  transition: all .2s ease;
}
.site-actions .site-oog:hover,
.site-actions .site-oog:focus-visible {
  color: var(--ink);
  text-decoration: underline;
}
/* HIDDEN BELOW 1141px, AND THAT IS MEASURED RATHER THAN CAUTIOUS.
   #476/#477 removed a 641-960px band where the action cluster ran off the end
   of the header card, and left the 1024-1140px band running with about 50px of
   slack. This link is ~70px. Measured at 1024px it put the homepage header
   39px over its content box and the directory header 25px over -- exactly the
   defect those PRs fixed -- so it does not ship into that band. Below 1024px
   the bar is brand + theme + burger and `.signin` and the primary button are
   already gone, so nothing changes there either. The FOOTER copy carries every
   width, phones included. */
@media (max-width: 1140px) {
  .site-actions .site-oog { display: none; }
}

/* The footer bar copy. Always present, phones included — it is the only copy a
   phone gets, and the row it sits in already wraps to its own line there. */
.premium-footer .footer-bottom .qf-foot-oog,
.dirfoot-oog .qf-foot-oog {
  color: var(--muted);
  text-decoration: none;
  font-weight: 500;
}
.premium-footer .footer-bottom .qf-foot-oog:hover,
.premium-footer .footer-bottom .qf-foot-oog:focus-visible,
.dirfoot-oog .qf-foot-oog:hover,
.dirfoot-oog .qf-foot-oog:focus-visible {
  color: var(--ink);
  text-decoration: underline;
}
.premium-footer .footer-bottom .qf-foot-oog .arr,
.dirfoot-oog .qf-foot-oog .arr { color: currentColor; }
.dirfoot-oog {
  margin: 8px 0 0;
  text-align: center;
  font-size: 12px;
  line-height: 1.5;
}

/* THE COMPACTION STEP, EXTENDED TO COVER THE WIDTH THE CTA FIRST APPEARS AT.
   #476/#477 sized a 1024-1140px step from the arithmetic of the widest header
   (brand 132 + nav 491 + cluster 337 against a 938px content box). The
   directory's cluster is the widest on the site — "Claim your listing — free"
   — and at 1141px, the first width the OOG link renders at, it ran 18px past
   its content box. The same declarations that fixed the band below it fix
   this: they return about 76px, which is four times the shortfall. Above
   1199px the natural sizes fit unaided and nothing here applies. */
@media (min-width: 1141px) and (max-width: 1199px) {
  .site-header-inner { gap: 16px; }
  .site-nav { gap: 0; }
  .site-nav > a,
  .site-nav .nav-dd-trigger { padding-left: 8px; padding-right: 8px; font-size: 13px; }
  .site-actions { gap: 8px; }
  .site-actions .btn-secondary { padding-left: 12px; padding-right: 12px; }
}

/* ── PHONE FOOTER — TWO COLUMNS, NOT ONE TALL STACK ────────────────────────
   Alex, 2026-09: "I don't like that footer it is 1 long stacked column in a
   mobile view. optimize it. make 2 columns."

   THE ARITHMETIC IS THE WHOLE PROBLEM. PREMIUM_FOOTER has N=5 link columns and
   the standing rule (DESIGN-SYSTEM.md §8, measured by
   navInformationArchitecture.test.ts at every width 320→1600) is that no row
   may hold exactly one column: with N columns in T tracks the last row holds
   `N mod T`, so every T where `N mod T === 1` is forbidden. 5 mod 2 === 1, so a
   plain two-track phone grid strands LEGAL alone — which is exactly why the
   phone step shipped as a single full-bleed stack in the first place.

   THE FIX IS TO TAKE ONE COLUMN OUT OF THE WRAP. The last link column spans
   both tracks and lays its own links out in two CSS columns, so it is a
   deliberate full-bleed row (the same case the rule already blesses at T=1),
   not a wrap remainder. That leaves 4 columns wrapping into 2 tracks —
   4 mod 2 === 0 — and every row of the footer holds two things.

   Appended at EOF on purpose: the spacing guard's baseline is line-numbered,
   so an in-place edit above would renumber ~6 tolerated entries in this file.
   Later + equal specificity beats the `minmax(0, 1fr)` step above at ≤640px;
   that older rule stays legal on its own terms (T=1 is always allowed). */
@media (max-width: 640px) {
  .premium-footer-inner {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 16px;
    row-gap: 24px;
  }
  .premium-footer .footer-brand { grid-column: 1 / -1; }
  .premium-footer-inner > .footer-col:last-child {
    grid-column: 1 / -1;
    display: block;
    columns: 2;
    column-gap: 16px;
  }
  .premium-footer-inner > .footer-col:last-child h4 { column-span: all; }
  .premium-footer-inner > .footer-col:last-child a { break-inside: avoid; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   WAVE 3 — THE CHROME, ON THE DESIGN SYSTEM.

   Appended at EOF for the reason every other block here is: the spacing and
   colour guards baseline by FILE:LINE, so a mid-file edit renumbers the 18
   tolerated entries above and turns a green run red for no reason
   (docs/design-refactor/baseline-protocol.md rule 1). Everything below is
   additive and, at equal specificity, later — so it is what actually renders.

   WHAT CHANGES, AND WHY EACH ONE
   • THE HEADER IS A BAR, NOT A FLOATING CARD. It was a 52px pill inset 12px
     from the top with a 14px radius, a 1px border and a shadow, sitting on a
     transparent strip — five decorative decisions doing the work of one. It is
     now 83px, flat, opaque, full-bleed, with NO border, NO blur and NO
     scroll-state change: identical at scrollTop 0 and 4000. A header that
     restyles itself as you scroll is a second header to design, test and get
     wrong; there is one now.
   • THE OPAQUE GROUND IS WHAT MAKES IT LEGIBLE. The old bar was translucent
     over whatever section happened to be under it, so its contrast was a
     function of the page. On --surface the ink pairs are fixed: 20.16:1 for
     the brand, 7.69:1 for a nav label, 5.67:1 for the accent (light), and
     18.02 / 7.17 / 7.78:1 for the same three on the dark theme's #0C111D.
   • THE MEGA-PANEL'S DIVIDERS ARE GAPS. A 12px shell with 4px of padding and
     4px between 8px inner cards: every line you see between two groups is the
     canvas showing through, not a border. One shadow (--qf-shadow-md), no
     second border, no blur.
   • THE FOOTER SITS ON #0C111D IN BOTH THEMES, so its ink comes from the
     --footer-* tokens rather than --ink (which is near-black on light and
     would be invisible). Values and their measured ratios: style.css EOF.

   WHAT DOES NOT CHANGE — the interaction contract is untouched. The single
   1023px collapse point, #site-burger / #site-mobile-menu and their
   aria-expanded/aria-controls wiring, .qf-theme-btn[aria-pressed], the
   .site-header stickiness directory/pages.ts depends on, #year, and every
   href. Selection/focus stays a 2px border-colour swap so nothing reflows.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Header shell ───────────────────────────────────────────────────────── */
.site-header {
  position: sticky;
  top: 0;
  z-index: 70;
  padding: 0;
  background: var(--chrome-bg);
  border: 0;
  box-shadow: none;
  backdrop-filter: none;
  transition: none;
}
.site-header-inner {
  max-width: var(--container-max);
  margin: 0 auto;
  min-height: var(--chrome-h);
  padding: 0 var(--container-gutter);
  gap: var(--space-3);
  justify-content: flex-start;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
html[data-theme="light"] .site-header-inner { box-shadow: none; }

/* Below the ONE collapse point the bar is brand + theme + burger, so it takes
   the compact height and the gutter phones actually have. */
@media (max-width: 1023px) {
  .site-header { padding: 0; background: var(--chrome-bg); }
  .site-header-inner {
    max-width: var(--container-max);
    margin: 0 auto;
    min-height: var(--chrome-h-compact);
    padding: 0 var(--space-2);
    gap: var(--space-1-5);
    justify-content: space-between;
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  html[data-theme="light"] .site-header-inner { background: transparent; }
  .site-mobile-menu:not([hidden]) { margin: var(--space-1) var(--space-2) var(--space-2); }
}
@media (min-width: 641px) and (max-width: 1023px) {
  .site-header { padding: 0; }
  .site-header-inner {
    max-width: var(--container-max);
    margin: 0 auto;
    padding: 0 var(--container-gutter);
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  html[data-theme="light"] .site-header-inner { background: transparent; box-shadow: none; }
  .site-mobile-menu:not([hidden]) { margin: var(--space-1) var(--container-gutter) var(--space-2); }
}

/* ── Mega-menu panel: 12px shell, 4px padding, 8px cards, gap dividers ──── */
.nav-dd-panel {
  padding: var(--chrome-panel-pad);
  gap: var(--chrome-panel-pad);
  background: var(--chrome-panel-bg);
  border: 0;
  border-radius: var(--chrome-panel-radius);
  box-shadow: var(--qf-shadow-md);
  backdrop-filter: none;
}
html[data-theme="light"] .nav-dd-panel { box-shadow: var(--qf-shadow-md); }
.nav-dd-group {
  padding: var(--space-1-5);
  background: var(--chrome-card-bg);
  border-radius: var(--chrome-card-radius);
  gap: 0;
}
.nav-dd-head { padding: 0 var(--space-1); margin: 0 0 var(--space-1); }
.nav-dd-panel a { padding: var(--space-1); border-radius: var(--radius-sm); }
.nav-dd-panel a:hover,
.nav-dd-panel a:focus-visible { color: var(--accent); background: var(--chrome-panel-bg); }
.nav-dd-sub { padding: var(--space-1) var(--space-1) 0; }

/* ── Mobile drawer: the same shell/card idiom, no hairlines ─────────────── */
.site-mobile-menu:not([hidden]) {
  gap: var(--chrome-panel-pad);
  padding: var(--chrome-panel-pad);
  background: var(--chrome-panel-bg);
  border: 0;
  border-radius: var(--chrome-panel-radius);
  box-shadow: var(--qf-shadow-md);
}
.site-mobile-menu .mm-group {
  border-top: 0;
  background: var(--chrome-card-bg);
  border-radius: var(--chrome-card-radius);
  padding: var(--space-1) 0;
}
.site-mobile-menu .mm-flat,
.site-mobile-menu .mm-account {
  border-top: 0;
  margin-top: 0;
  padding-top: var(--space-1-5);
  background: var(--chrome-card-bg);
  border-radius: var(--chrome-card-radius);
}
.site-mobile-menu a { border-radius: var(--radius-md); }

/* -- Footer: the dark band, as a TOKEN ISLAND ----------------------------
   The footer sits on #0C111D in BOTH themes, so every token it inherits from
   :root is wrong on light: --ink is near-black, --border is #E2E8F0 (a bright
   hairline on a near-black ground) and --muted lands at 3.1:1 there.
   Re-pointing those tokens ON the footer element fixes the whole subtree in
   one place -- including rules in OTHER sheets written as var(--muted) and
   carrying !important, which no per-element override here could reach. It also
   means this block never has to name a class it must not touch: the payment
   trust list's deliberate one-per-line stack (style.css:1698, pinned by
   navInformationArchitecture.test.ts) recolours without a rule block of its
   own, which is exactly what that rule is protecting.

   Measured on #0C111D: heading/ink 18.02:1, link 12.69:1, quiet 7.17:1, hover
   accent 7.78:1 -- all AA, and the accent is the LIGHTENED one because #3356EE
   is 3.33:1 on this ground. */
.premium-footer {
  --ink: var(--footer-ink);
  --ink-soft: var(--footer-link);
  --muted: var(--footer-quiet);
  --border: var(--footer-rule);
  --border-strong: var(--footer-rule);
  --accent: var(--footer-accent);
  background: var(--footer-bg);
  border-top: 0;
  color: var(--footer-ink);
  padding: var(--space-7-5) var(--container-gutter) var(--space-4);
}
.premium-footer-inner { max-width: var(--container-max); }
.premium-footer .qf-footer-wordmark { color: var(--footer-ink); }
.premium-footer .footer-brand .qf-footer-tagline { color: var(--footer-link); }
.premium-footer .footer-col h4 { color: var(--footer-ink); }
.premium-footer .footer-col a { color: var(--footer-link); }
.premium-footer .footer-col a:hover,
.premium-footer .footer-col a:focus-visible { color: var(--footer-accent); }
.premium-footer .qf-footer-trustbar {
  max-width: var(--container-max);
  border-top: 1px solid var(--footer-rule);
}
.premium-footer .qf-footer-trustbar li { color: var(--footer-link); }
.premium-footer .footer-bottom {
  max-width: var(--container-max);
  border-top: 1px solid var(--footer-rule);
  color: var(--footer-quiet);
}
.premium-footer .footer-bottom .qf-foot-oog { color: var(--footer-link); }
.premium-footer .footer-bottom .qf-foot-oog:hover,
.premium-footer .footer-bottom .qf-foot-oog:focus-visible { color: var(--footer-accent); }
.premium-footer .qf-footer-payrow { border-top: 1px solid var(--footer-rule); }
.premium-footer .qf-paymark,
.premium-footer .qf-pm { color: var(--footer-ink); fill: currentColor; }

/* ── The footer track ladder, keyed on the count the markup declares ─────
   qf-footer-ladder: columns=5 wide=5 mid=3 phone=1 span-last=no

   siteChrome.ts builds the columns from FOOTER_COLUMNS and writes their COUNT
   into `data-cols` (plus `data-cols-odd` when the count is odd), and
   footerTrackLadder() derives the three track counts from it: T is legal only
   where `N mod T !== 1`, because a remainder of one strands a column alone on
   the last row (DESIGN-SYSTEM.md §8). The sentinel line above states what that
   function returns for today's array; siteChromeSingleSource.test.ts reads it
   back and fails, naming the sheet and the required numbers, the moment a
   column is added or removed. That is the whole point: the count used to be
   hard-coded here AND in the markup AND again in nav-ia.css, so the three
   could disagree silently — and did.

   For N=5 the ladder is 5 -> 3 -> 2-with-the-last-column-spanning. Keying the
   rules on `[data-cols="5"]` means a changed count stops matching them rather
   than inheriting a grid derived for a different number. */
.premium-footer-inner[data-cols="5"] { grid-template-columns: repeat(5, minmax(0, 1fr)); }
@media (max-width: 1100px) {
  .premium-footer-inner[data-cols="5"] { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .premium-footer-inner[data-cols="5"] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 16px;
    row-gap: 24px;
  }
  .premium-footer-inner[data-cols-odd] > .footer-col:last-child {
    grid-column: 1 / -1;
    display: block;
    columns: 2;
    column-gap: 16px;
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   WAVE 4 — FLYOUT ELEVATION. Appended at EOF for the usual reason: the two CI
   guards baseline by `file:line` (docs/design-refactor/baseline-protocol.md
   rule 1), so a mid-file edit renumbers every tolerated entry below it.

   REPORTED: "the mega-menu is too transparent — the hero headline and the
   product image show straight through it and the menu text is hard to read."

   MEASURED, THE FILL WAS NEVER TRANSLUCENT. On every surface that carries the
   nav — homepage, /directory, a carrier profile — the panel already resolved
   to `background-color: rgb(242,244,247)` at alpha 1 (rgb(26,35,51) on dark),
   `opacity: 1`, `backdrop-filter: none`. A pixel diff of the panel's interior,
   captured once as the page ships and once with a magenta/green marker painted
   over every element behind the header, came back byte-identical. Nothing was
   reaching the screen through the fill.

   WHAT WAS MISSING IS THE EDGE. The shell is --surface-2 and the page ground
   is --bg — measured 1.05:1 apart on light and 1.20:1 on dark. The only other
   separation cue was --qf-shadow-md (`0 8px 24px rgba(0,0,0,.04)`), 4% black,
   which over a near-white page darkens the ground by roughly one 8-bit step
   and over the dark theme is not visible at all — plus `border: 0`. A surface
   with no edge and no shadow does not read as floating above the page; it
   reads as a hole in it, and "the page shows through" is exactly how a hole
   looks. So the fix is ELEVATION, not opacity:

   • --qf-shadow-lift is the LARGEST of the three shadow tokens
     (`0 4px 24px rgba(165,176,204,.20)` — 5x the alpha of --qf-shadow-md). It
     is named directly rather than through the --shadow-lg alias because the
     dark theme re-points --shadow-lg at --qf-shadow-md: on a dark ground the
     lavender tint reads as the soft halo dark-mode elevation is made of,
     whereas 4% black reads as nothing at all.
   • A 1px hairline in --border-strong gives a hard boundary that does not
     depend on how the shadow renders. --border-strong, not --border: measured
     against the page's own ground with the content behind replaced by a flat
     fill, the boundary's median adjacent-pixel step went 13 -> 16 with
     --border (#E2E8F0 is only 20 channels off the #F6F8FA ground, and the
     shadow ramp softens even that) against 13 -> 44 with --border-strong. It
     is the same token the system already gives chrome that has to read as a
     distinct object on the page — .btn-secondary, .site-burger. It is the shell's OUTER edge only — the dividers
     BETWEEN groups are still the 4px canvas gap over --chrome-panel-bg, never
     a rule. That contract is unchanged and still pinned by
     siteChromeSingleSource.test.ts.
   • The fill is restated as the `background-color` LONGHAND plus
     `background-image: none`, so a sheet that lands later cannot slide a
     gradient, an image or a translucent shorthand back under the link text —
     which is what --glass-card-solid (0.98) was doing before this wave.
   • `opacity: 1` is defensive: a translucent ANCESTOR is the one thing that
     would make the fill see-through no matter how opaque its own colour is.

   STACKING. The flyout is a child of .site-header, which is `position: sticky`
   with `z-index: 70` — a stacking context, so the panel's own z-index is only
   ever compared inside it and nothing in the page's root context below 70 can
   paint over it. The drawer had NO z-index at all (`auto`), which left it
   ordering against its header siblings by document order; it is pinned to the
   same 80 as the desktop panel so both sit above the brand, the action cluster
   and the theme button at every breakpoint.

   MOTION. `.2s ease` on open, as a keyframe rather than a transition because
   the panel is revealed by toggling `hidden` and `display` is not animatable.
   Opacity reaches 1 at 40% of the 200ms, so the panel is fully opaque for the
   settle — a see-through open would be the reported bug in miniature. Disabled
   wholesale under prefers-reduced-motion.

   UNCHANGED: every href, the three triggers, the ONE 1023px collapse point,
   #site-burger / #site-mobile-menu and their aria wiring, .qf-theme-btn
   [aria-pressed], header stickiness, and the 12 / 4 / 8 shell-pad-card ramp.
   ═══════════════════════════════════════════════════════════════════════════ */

.nav-dd-panel,
.site-mobile-menu:not([hidden]) {
  background-color: var(--chrome-panel-bg);
  background-image: none;
  opacity: 1;
  border: 1px solid var(--border-strong);
  box-shadow: var(--qf-shadow-lift);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  z-index: 80;
}
html[data-theme="light"] .nav-dd-panel,
html[data-theme="light"] .site-mobile-menu:not([hidden]) {
  box-shadow: var(--qf-shadow-lift);
}

/* Open motion — see the MOTION note above. */
@keyframes qf-flyout-in {
  from { opacity: 0; transform: translateY(-4px); }
  40%  { opacity: 1; }
  to   { opacity: 1; transform: translateY(0); }
}
.nav-dd-panel:not([hidden]),
.site-mobile-menu:not([hidden]) {
  animation: qf-flyout-in .2s ease;
}
@media (prefers-reduced-motion: reduce) {
  .nav-dd-panel:not([hidden]),
  .site-mobile-menu:not([hidden]) { animation: none; }
}

/* THE ONE THING THAT DOES PAINT OVER THE FLYOUT. The marketing chat launcher
   is `position: fixed` at `z-index: 2147483000` in the page's ROOT stacking
   context, i.e. above .site-header's 70 and therefore above anything the
   header can contain. On desktop it never meets the panel, but at 375px the
   drawer is full-width and runs the height of the viewport, and the measured
   overlap was ~4,000px of drawer covered by the launcher — a menu item with a
   chat button sitting on top of it. Raising the header instead is the wrong
   direction: the header would then cover the chat PANEL and its backdrop when
   chat is open. So the launcher stands down for as long as the drawer is open,
   keyed on the burger's own aria state (already the source of truth for open /
   closed) — no JS, no new class, and a browser without :has() simply keeps
   today's behaviour. marketing-chat.js's `.qf-mc-fab` is a single class, so
   this out-specifies it without !important however late its <style> lands. */
body:has(#site-burger[aria-expanded="true"]) .qf-mc-fab { display: none; }

/* ═══════════════════════════════════════════════════════════════════════════
   WAVE 5 — PREMIUM FOOTER, LIGHTER AND SMALLER.

   Alex, 2026-09: the footer "looks too heavy and big". Measured before this
   block, on every server-rendered marketing page: 816px tall at 1440, 1686px
   at 375 and 1854px at 320 — a footer taller than the phone viewport, under
   every page on the site.

   THREE THINGS MAKE THE HEIGHT, and this block takes all three:
     1. PADDING AND GUTTERS. 60px top / 32px bottom with a 32px grid gap on a
        block whose job is a site map. 32 / 24 / 24-16 reads identically and is
        ~60px shorter before a single link moves.
     2. THE BRAND BLOCK. A 60px-tall logo, a 19px wordmark and a full-sentence
        tagline sat on their OWN full-bleed row (`.footer-brand` is pinned
        `1 / -1`), so the footer opened with ~100px of masthead. The logo drops
        to 32px — the height of the wordmark beside it — and the type steps
        down with it. The tagline SENTENCE is unchanged: it is marketing copy,
        not mine to rewrite.
     3. THE LINK WALL, which is the phone story and is handled by the
        disclosures in style.css rather than here.

   PADDING-BOTTOM IS NOT ZERO, deliberately. The sub-bar sitting on the last
   pixel of the document reads as a truncation bug rather than as a finished
   page, so the footer keeps a real 24px floor under it.
   ═══════════════════════════════════════════════════════════════════════════ */
.premium-footer { padding: var(--space-4) var(--container-gutter) var(--space-3); }
.premium-footer-inner { gap: var(--space-3) var(--space-2); }

.premium-footer .footer-brand { gap: var(--space-1-5); align-items: center; }
.premium-footer .footer-brand .qf-footer-logo { height: 32px; }
.premium-footer .qf-footer-brandtext { gap: 0; }
.premium-footer .qf-footer-wordmark { font-size: 15px; }
.premium-footer .footer-brand .qf-footer-tagline {
  font-size: 12px;
  line-height: 1.45;
  max-width: 60ch;
}

/* The heading is the <summary> now, so its old bottom margin became the
   disclosure's padding — otherwise the gap doubled. */
.premium-footer .footer-col h4 {
  margin: 0;
  font-size: 11px;
  letter-spacing: .1em;
  /* Kept from PR #560 and extended to this sheet: the homepage had the
     balance fix, every OTHER marketing page did not, so "For Carriers &
     Brokers" still stranded "Brokers" alone at 375 and 320 on /pricing,
     /oversize and every tool page. Declared on the SELECTOR so a longer label
     added to FOOTER_COLUMNS inherits it. */
  text-wrap: balance;
}
.premium-footer .qf-fdisc-sum { padding: var(--space-0-5) 0; }
.premium-footer .footer-col a { font-size: 13px; padding: var(--space-0-5) 0; }
.premium-footer .qf-fdisc-sum:hover h4 { color: var(--footer-ink); }
.premium-footer .qf-fdisc-chev { color: var(--footer-quiet); }

/* ── WCAG 2.5.8 target size, bought with HIT AREA and not with HEIGHT ───────
   MEASURED ON THE RENDERED HOMEPAGE at 1440, not inferred from the rules:

     .footer-col a    252.80 x 28.14   PASSES (>=24 both ways), and the gap
                                       between consecutive links is 0.00px, so
                                       a column is already one unbroken tap
                                       strip with no dead lane to reclaim.
     .qf-fdisc-sum    252.80 x 20.64   FAILS  (13px h4 on 4px/4px padding)
     .qf-foot-oog     336.52 x 16.09   FAILS  (11.5px on 16.1px leading)
     .qf-footer-wordmark 77.59 x 23.25 FAILS  by 0.75px

   So the 28px link rows were never the defect — the three controls around them
   were. The <=640px block below already lifts the links and the summaries to
   44px; it never covered the OOG line or the wordmark, which fail at BOTH
   widths.

   WHY ::after AND NOT PADDING. Alex asked for this footer lighter and smaller,
   and PRs #565-567 cut it ~60% to 613px; padding that grows a control grows
   the band it sits in, so paying for target size in layout height would hand
   part of that back. An absolutely-positioned pseudo-element enlarges the
   POINTER TARGET — which is what 2.5.8 measures — while the box, the baseline
   and the footer's total height stay byte-identical. Verified 613.00px before
   and after at 1440, and 650.16px before and after at 375.

   EVERY EXTENSION IS SIZED TO DEAD SPACE THAT WAS ALREADY THERE, so no two
   targets overlap (2.5.8 is not satisfied by targets that collide):
     • summary — .qf-fdisc-body carries 8px of padding-top, measured as an 8px
       gap to the first link. Taking 4px of it (and 4px of the grid gap above)
       makes the summary 28.64px and still leaves 4px of clearance before the
       first link.
     • OOG line — measured 8px of clear band between it and .qf-footer-payrow,
       whose payment marks are <svg> in <li> and not targets. Extending DOWN
       ONLY, by that 8px, makes it 24.09px. Deliberately NOT extended upward:
       the legal <p> sits 8px above and an overlay there would swallow that
       sentence's text selection for no target gain.
   NOTE ON INTENT: above 641px style.css stands the disclosure affordance down
   (cursor:default, chevron hidden) because desktop must not hide links behind
   a click — but <summary> stays focusable and Enter/Space/click still toggle
   it, so it is an operable control and 2.5.8 measures it as one. */
.premium-footer .qf-fdisc-sum,
.premium-footer .footer-bottom .qf-foot-oog { position: relative; }
.premium-footer .footer-bottom .qf-foot-oog::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: calc(-1 * var(--space-1));
}
@media (min-width: 641px) {
  /* Phone keeps the 44px rows the block below already gives it; this is the
     desktop step, where the summary is the only sub-24px control left. */
  .premium-footer .qf-fdisc-sum::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: calc(-1 * var(--space-0-5));
    bottom: calc(-1 * var(--space-0-5));
  }
}
/* 15px wordmark on 1.55 leading lands at 23.25px — three quarters of a pixel
   short of 24. Raising its min-height is NOT available: the `min-height: 1lh`
   rule further down this sheet deliberately pins this anchor to exactly one
   line box because the brand block (58.03px) and every page's footer height
   are measured against it, and that comment calls a shift here a Core Web
   Vitals regression rather than a cosmetic one. So the box is left alone and
   only the hit area grows, upward into the brand row's own slack — not
   downward, where .qf-footer-tagline's text would lose its selection to an
   overlay. 23.25 + 4 = 27.25px. The logo link sits BESIDE it, not above
   (measured: the two overlap vertically), so nothing collides — and both
   resolve to "/" in any case, which 2.5.8 permits to share a target. */
.premium-footer .qf-footer-wordmark { position: relative; }
.premium-footer .qf-footer-wordmark::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  top: calc(-1 * var(--space-0-5));
}

.premium-footer .footer-bottom {
  /* 16 + 12, down from 24 + 16. This rule outranks the (0,1,0) `.footer-bottom`
     in style.css, so the legal block's band gap is set HERE for every marketing
     surface — tightening it there alone would have changed nothing. */
  margin: var(--space-2) auto 0;
  padding: var(--space-1-5) 0 0;
  display: block;
  border-top: 1px solid var(--footer-rule);
}
.premium-footer .footer-bottom .qf-foot-oog {
  /* PLAIN BLOCK, shrink-wrapped — deliberately NOT inline-flex or flex.
     • inline-flex picked up an 8px indent from the inline formatting context
       after the legal <p> on the directory footer (measured left 168 against a
       160 content edge) while sitting flush on the marketing one, AND dropped
       the markup space before the trailing arrow <span> ("…quote→").
     • flex fixed both but made the arrow its own flex item beside the text
       box, so at 375px the text wrapped INSIDE that box and the arrow stayed
       up on line one: "…OOG trucking →" / "quote".
     A block keeps normal inline flow, so the arrow is just the last word of
     the sentence and wraps with it; `fit-content` keeps the hover/focus target
     on the text instead of the full footer width. */
  display: block;
  width: fit-content;
  margin-top: var(--space-1);
}
/* THE SUB-BAR LINES UP WITH THE SITE MAP ABOVE IT. `.premium-footer-inner` and
   `.footer-bottom` are both capped at --container-max (1328px); the pay row
   kept the older hard-coded 1120px, so on a 1440 viewport it sat 104px inside
   both of them — a strip that looked centred under left-aligned content. */
.premium-footer .qf-footer-payrow { max-width: var(--container-max); }

@media (max-width: 640px) {
  /* 16px side gutters on phones, not the 24px container gutter — which is what
     the homepage and the directory footer already use at this width. Beyond
     consistency it buys 16px of measure back: at 375px the carrier-marks note
     was wrapping to one line more here than on the homepage purely because the
     content box was 327px instead of 343px. */
  .premium-footer { padding: var(--space-3) var(--space-2) var(--space-3); }
  .premium-footer-inner { gap: var(--space-1-5) var(--space-2); }
  .premium-footer .footer-brand { gap: var(--space-1); }
  .premium-footer .footer-brand .qf-footer-logo { height: 28px; }
  /* A collapsed column is a control, so give it a real target to tap. 44px is
     the target check-spacing.mjs measures against; 40 was four short. */
  .premium-footer .qf-fdisc-sum { min-height: 44px; padding: var(--space-1) 0; }
  /* AND SO IS EVERY LINK INSIDE AN OPENED COLUMN — they measured 28.14px
     (13px on 1.55 leading + 4px either side), a good reading row and a poor
     thumb target.

     SCOPED TO THE PHONE STEP, and the reason is arithmetic rather than
     laziness. Above 640px these columns never collapse, so their links are
     permanently rendered and 38 x 15.86px would add ~602px to a 613px footer —
     most of the ~60% cut PRs #565-567 bought, handed straight back. At this
     step the columns ship CLOSED, so the taller rows live inside a panel the
     browser does not render until the reader opens it: the collapsed footer
     measures the same to the pixel, and the opened panel is taller only while
     a thumb is already on its way to it. Measured both ways before shipping. */
  .premium-footer .qf-fdisc-body a {
    display: flex;
    align-items: center;
    min-height: 44px;
  }
  /* THE SPANNED LAST COLUMN PAGINATES ITS LINKS, NOT ITS DISCLOSURE.
     `.footer-col:last-child` is pinned `1 / -1` at this step and carried
     `columns: 2` so its links filled the full-bleed row instead of running
     down one side (nav-unify.css:792, and again at :1025 keyed on
     `[data-cols-odd]`). With the links now inside a <details>, that rule would
     paginate the DETAILS ELEMENT — summary and all — and a collapsed column
     would try to flow one summary row across two tracks. So the multi-column
     context moves down onto the links wrapper, where it always belonged.
     Matched at the same (0,4,0) specificity as the `[data-cols-odd]` rule and
     declared later, so it wins in both sheets. */
  .premium-footer-inner[data-cols-odd] > .footer-col:last-child { columns: auto; }
  .premium-footer-inner[data-cols-odd] > .footer-col:last-child .qf-fdisc-body {
    columns: 2;
    column-gap: var(--space-2);
  }
  .premium-footer-inner[data-cols-odd] > .footer-col:last-child a { break-inside: avoid; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   WAVE 5b — THE PHONE FOOTER STACKS, AND CLEARS THE CHAT LAUNCHER.

   1. ONE COLUMN AT <=640px, not two. The ladder's phone step is now
      `columns % 2 === 0 ? 2 : 1` (footerTrackLadder), so N=5 stacks. The
      two-track step needed the FIFTH column pinned `grid-column: 1 / -1`, and
      once the columns became collapsed disclosures that read as one lone
      summary bar sitting under two tidy rows of two — the orphan shape the
      no-orphan rule exists to prevent, even though the arithmetic was legal.
      Stacking also makes every <summary> a full-width tap target instead of a
      half-width one, which is the better phone control.

      The older `[data-cols-odd]` span/pagination rules above are now INERT
      rather than overridden: `phoneSpansLast` is false, so siteChrome.ts stops
      emitting the attribute and nothing matches them. Left in place because
      deleting mid-file renumbers the line-keyed guard baselines; they go with
      the trust-bar rules in the next wave that regenerates them.

   2. THE FOOTER CLEARS THE CHAT LAUNCHER. `.qf-mc-fab` is
      `position: fixed; right: 12px; bottom: 12px; 56x56` at z-index 2147483000
      in the page's ROOT stacking context, so at 375px — where the sub-bar's
      claims run the full width — the last line rendered UNDER the button:
      "Per-tenant dat[a isolation]" with the rest covered, on every page.

      THE FOOTER MOVES, NOT THE BUTTON. The launcher's geometry is settled: a
      bottom lift was removed from it after four separate content collisions,
      and PR #555 measured this exact report on the directory listing and
      proved the launcher was NOT the cause there. So the fix is clearance the
      footer owns — 12 + 56 + 12 = 80px, the button's own footprint plus the
      gap it already keeps, on the 8px ramp — plus the iOS home-indicator
      inset, which is 0 on every other platform.

      …AND THE CLEARANCE IS NOW SHAPED LIKE THE BUTTON, NOT LIKE THE FOOTER.
      The 80px above was reserved as FULL-WIDTH bottom padding for a 56px
      button in ONE corner, so it bought its corner by opening a band of air
      across the whole footer: 80px of nothing under the last line at 375,
      against 24px on desktop. Owner, 2026-09: "too big empty space gap at the
      very bottom of the footer."

      Measured with the band dropped to the desktop 24px, worst case (every
      disclosure open, scrolled to the bottom), 6 pages x 8 widths x 2 themes:
      exactly TWO things ever enter the button's 56px column — the carrier-
      marks note, the last element on the page, at <=414px; and the pay row's
      last claim at 900px. Nothing else, at any width, in either theme.

      So the clearance is spent on those two and nowhere else, and it is spent
      HORIZONTALLY — which is the currency style.css:2422 already chose for
      >900px, with the same 80px number. The note is capped at 78ch and stays
      left-aligned, so a right gutter only re-flows its own last lines; the pay
      row gets the gutter from 641px up, where it is still a single horizontal
      run that reaches the corner (below that it stacks and never gets near).
      Re-measured across the same 96 combinations: zero overlap, zero
      horizontal scroll, and the bottom gap is 24px at every width.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 640px) {
  /* row-gap rides the SAME [data-cols="5"] selector, not a bare
     `.premium-footer-inner`: the two-track rule it replaces (nav-unify.css:1020)
     also sets `row-gap: 24px` at (0,2,0), so a (0,1,0) override lost and the
     stack shipped with the two-track gutter — measured 24px between five
     stacked summary bars, 80px of pure air. A stack's rows are adjacent
     controls, not grid cells that need a gutter to read apart. */
  .premium-footer-inner[data-cols="5"] {
    grid-template-columns: repeat(1, minmax(0, 1fr));
    row-gap: var(--space-0-5);
  }
  /* THE LAST COLUMN STOPS PAGINATING. Two rules gave it `columns: 2` so its
     links filled the old full-bleed row: the [data-cols-odd] one (now inert —
     the attribute is no longer emitted) AND this older attribute-free form at
     nav-unify.css:792, which still matches. Measured with only the first
     neutralised: LEGAL's single collapsed summary was balanced into the SECOND
     of two CSS columns and rendered indented halfway across its row, reading
     as a centred heading among four left-aligned ones. Matched at the same
     (0,3,1) and declared later. */
  .premium-footer-inner > .footer-col:last-child { columns: auto; }
  .premium-footer .footer-brand { margin-bottom: var(--space-1); }
}
@media (max-width: 900px) {
  /* The band is now the same 24px the desktop footer already ended on; the
     launcher's column is bought by the two horizontal gutters at the foot of
     this file. The safe-area inset stays — it is the iOS home indicator, not
     the button, and it is 0 everywhere else. */
  .premium-footer { padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px)); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   FOOTER WORDMARK — BAKED OUTLINE, NOT TYPE
   Asset + rationale: FOOTER_WORDMARK_SVG in siteChrome.ts.
   Appended at EOF on purpose: the spacing and colour guards baseline this file
   by FILE:LINE, so a mid-file insert renumbers ~1000 baseline entries.

   HOLD THE LINE BOX. The anchor used to wrap a 15px/1.55 text node, so its box
   was exactly one line box tall — 23.25px — and the brand block (58.03px), and
   therefore every page's footer, was measured against that. `1lh` IS that line
   box; the em value in front of it is the same number for engines without the
   unit (1.55 is the inherited line-height).

   SIZE IN `em`, NOT px. The mark then tracks the font-size rule above instead
   of pinning a second magic number somewhere else: 5.173em x 1.22em resolves
   to 77.59 x 18.30px at the shipping 15px. BOTH axes are declared so the box
   is known before the paths parse — this footer is on every page, and a shift
   here is a Core Web Vitals regression, not a cosmetic one.

   SHRINK-WRAP. Stretched, the anchor ran the full column (454px at 1440) and
   the focus ring drew a rectangle around mostly empty space.
   ═══════════════════════════════════════════════════════════════════════════ */
.premium-footer .qf-footer-wordmark {
  display: flex;
  align-items: center;
  width: fit-content;
  min-height: 1.55em;
  min-height: 1lh;
}
.premium-footer .qf-footer-wordmark .qf-footer-wordmark-svg {
  display: block;
  width: 5.173em;
  height: 1.22em;
}
/* The focus indicator was the UA default. It IS visible — Chrome's `auto` ring
   auto-contrasts, so it painted light on this near-black band, and the
   computed `outline-color: rgb(16,16,16)` is misleading about what actually
   gets drawn. What was wrong is that it wrapped the STRETCHED anchor: a 454px
   rectangle around a 78px mark. Shrink-wrapped above, it now hugs the mark, in
   the footer's own accent (--accent is re-pointed to --footer-accent on
   .premium-footer, 7.78:1 on this ground). Hover stays opacity and never draws
   a ring, so the two states cannot be mistaken for each other. */
.premium-footer .qf-footer-wordmark:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
  opacity: 1;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE TWO CROSS-HOST BUTTONS

   `quotefleet.net` and `app.quotefleet.net` are one product on two hosts, and
   each needs exactly one visible door to the other:

     .qf-alltools  "See all tools"  marketing host -> app host
     .qf-mainsite  "Main site"      app host       -> marketing host

   WHY THEY BOTH NEED A BLOCK HERE AT ALL. `.site-nav` and
   `.site-actions .btn-secondary` are both `display: none` at <=1023px, where
   the burger takes over. For an ordinary marketing CTA that is right - the
   drawer carries it. Neither of these is in the drawer: the marketing drawer
   is deliberately byte-identical to the one on main and gains no row, and the
   app host's rail lists tools rather than hosts. So each button opts out of
   the collapse rule, and the sizing below is what keeps that affordable down
   to 320px.
   ══════════════════════════════════════════════════════════════════════════ */

.site-actions .qf-alltools {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 40px;
  padding: 8px 12px;
  /* 2px, the system's border width. The neighbouring .btn-secondary predates
     that rule at 1px; this is a new element and takes the current law. */
  border: 2px solid var(--accent);
  border-radius: var(--radius-btn);
  background: transparent;
  color: var(--accent);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.4;
  text-decoration: none;
  white-space: nowrap;              /* two words, one line, at every width */
  /* THE DRAWER'S OWN TOKENS, not the `all .2s ease` its .btn-secondary
     neighbour still carries. `siteChromeSingleSource.test.ts` enumerates every
     easing function in this region and fails on a second one - correctly: the
     system allows two durations and ONE curve, and a bare `ease` keyword is a
     different curve from `--qf-ease`. Named properties rather than `all`, so
     hovering cannot animate a layout property by accident. */
  transition:
    background-color var(--qf-dur-fast, 140ms) var(--qf-ease, cubic-bezier(.2, .7, .2, 1)),
    transform var(--qf-dur-fast, 140ms) var(--qf-ease, cubic-bezier(.2, .7, .2, 1));
}

/* OUTLINE AT REST, TINT ON HOVER - NEVER A BRIGHT FILL. A solid accent button
   here would be the loudest thing in the header on every page of the site,
   out-shouting the actual conversion CTA two controls to its right. The
   outline is enough to read as the one non-menu control in the cluster. */
.site-actions .qf-alltools:hover {
  background: var(--accent-soft);
  transform: translateY(-1px);
}
.site-actions .qf-alltools:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.qf-alltools__ico {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  display: block;
}

/* ══════════════════════════════════════════════════════════════════════════
   MAKING ROOM FOR "SEE ALL TOOLS" - MEASURED AT EVERY WIDTH, NOT AT FOUR.

   The requirement is that a visitor sees a control that SAYS WHAT IT DOES, at
   every width including 320. Everything here serves that one sentence.

   THE FIRST VERSION OF THIS BLOCK WAS CHECKED AT 1440/411/375/320 AND WAS
   WRONG IN BETWEEN. Sweeping 320-1440 in 20px steps found the header
   overflowing across 440-540 and again across 1024-1160 - two bands the four
   spot-widths stepped straight over. The sweep is now the check; the numbers
   below come out of it.

   HOW MUCH ROOM THE HEADER ACTUALLY HAS (main, with no button, px free):

        400 -> 112     440 ->  31     480 ->  51     540 -> 111
        600 -> 171     640 -> 211     900 -> 459    1000 -> 559
       1024 ->  25    1040 ->  41    1100 -> 101    1160 ->  78
       1200 ->  94    1280 -> 174

   Two troughs, and they are not this change's doing - they are where the bar
   already runs closest to full:

     440   the 117px "View demo" CTA reappears (it is hidden below 440), and
           31px is what is left over after it.
     1024  the three mega-menus come back AND "Sign in" appears, all while the
           burger has not yet gone away.

   A labelled button is 66px at its smallest. Neither trough can take one, so
   in each trough exactly one control stands down, and it is the one whose
   absence costs least:

     <=559px   "View demo" yields. It is ALREADY hidden below 440, so this
               extends an existing behaviour by 120px rather than introducing
               a new kind of loss, and the drawer carries "See a Live Demo"
               at every one of these widths.
     >=1024    the THEME TOGGLE yields, up to where the bar has room again.
               A preference control loses to navigation - and unlike the demo
               CTA it degrades to something correct on its own, because an
               un-stamped visitor follows `prefers-color-scheme` anyway.
     <=359px   the theme toggle yields for the same reason: at 320 there are
               48px free and the shortest button is 66.

   Not the brand wordmark, at any width - it is a locked asset. Not a row in
   the mobile drawer either: that would change `SITE_MOBILE_MENU_HTML`, which
   is hashed in toolPortal.test.ts precisely so the marketing menu cannot drift
   from main, and breaking that guarantee to save header pixels is backwards.

   THE LABEL SHORTENS, IT NEVER DISAPPEARS. "See all tools" is 95px and
   "All tools" is 66px; the icon adds 20px on top and goes first, since it only
   repeats what the words already say.
   ══════════════════════════════════════════════════════════════════════════ */

/* One label on screen at a time. `display: none`, not a clip, so the hidden
   one leaves the accessibility tree too and the button has exactly one
   accessible name. */
.qf-alltools__t--short { display: none; }

/* THE ICON EARNS ITS PLACE ONLY WHERE THERE IS SPARE ROOM. */
@media (max-width: 1279px) {
  .qf-alltools__ico { display: none; }
  .site-actions .qf-alltools { gap: 0; }
}

/* THE SHORT SPELLING, in the two bands where the long one does not fit:
   the narrowest phones, and the 1024-1279 desktop trough. */
@media (max-width: 399px) {
  .qf-alltools__t { display: none; }
  .qf-alltools__t--short { display: block; }
}
@media (min-width: 1024px) and (max-width: 1279px) {
  .qf-alltools__t { display: none; }
  .qf-alltools__t--short { display: block; }
}

/* THE 440 TROUGH: the demo CTA stands down until the bar can hold both.

   THE SELECTOR IS LONG FOR A REASON. `landing-home-fixes.css` ships
   `body.qf-wft .site-actions .btn-secondary { display: inline-flex !important }`
   - (0,3,1) and `!important`, so an `!important` of equal or lower specificity
   loses to it silently. Measured: the first attempt here was (0,2,2), the rule
   never applied, and the 440-540 band went on overflowing while every other
   width in the ladder behaved. This is (0,4,3) and out-ranks it.

   `:not(.qf-mainsite)` keeps it off the app host's "Main site" button, which
   is the same base class but a different control with its own rules below. */
@media (max-width: 559px) {
  html body .site-header .site-actions a.btn.btn-secondary:not(.qf-mainsite) {
    display: none !important;
  }
}

/* THE 1024 TROUGH: the theme toggle stands down until the bar can hold both.
   Sibling combinator on the button, so a header WITHOUT "See all tools" - the
   app host's - keeps its toggle at every width. */
@media (min-width: 1024px) and (max-width: 1279px) {
  .site-actions .qf-alltools ~ .qf-theme-btn { display: none; }
}

@media (max-width: 1023px) {
  /* Both cross-host buttons survive the collapse that hides .signin and
     .btn-secondary at this breakpoint. */
  .site-actions .qf-alltools { display: inline-flex; }
  .site-actions .btn-secondary.qf-mainsite { display: inline-flex; }
}

@media (max-width: 479px) {
  /* `!important`: `landing-home-fixes.css` ships
     `body.qf-wft .site-actions { gap: 14px !important }`, which no selector
     can out-rank from here. Measured on the real homepage at 320px - the rest
     of this block applied while the gap stayed 14, and 2 x 10px of surplus gap
     was most of the overflow. Scoped to the widths where that sheet's 14px was
     never designed to apply. */
  html body .site-header-inner .site-actions { gap: 4px !important; }
  .site-actions .qf-alltools { padding: 8px; min-height: 36px; }
  /* The app host's "Main site" button leaves the bar here and the RAIL picks
     it up - see `toolPortalHtml`. Unlike "See all tools" it has somewhere
     better to go: the rail IS this host's navigation. */
  .site-actions .btn-secondary.qf-mainsite { display: none; }
}

@media (max-width: 359px) {
  html body .site-header-inner { gap: 8px; }
  .site-actions .qf-alltools { padding: 8px 4px; }
  .site-actions .qf-alltools ~ .qf-theme-btn { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   WAVE 6 — THE MOBILE DRAWER STOPS PUSHING THE PAGE AND BECOMES AN OVERLAY.

   THE BUG, EXACTLY. `#site-mobile-menu` is the last child of `.site-header`
   and it was laid out IN NORMAL FLOW. Opening it therefore grew the header's
   flow box and shoved the whole document down by the drawer's height; and
   because `.site-header` is `position: sticky`, the page underneath was still
   free to scroll, so the hero slid up BEHIND the drawer while it was open.
   Both halves of the report ("it pushes the website down", "I can see the hero
   under the menu") are that one fact.

   WHAT REPLACES IT. A `position: fixed` sheet anchored under the header, a
   fixed scrim under it, and a scroll lock on the root element. Nothing about
   the drawer is in flow any more, so open and close cannot move a single pixel
   of the page.

   WHY IT IS ADDRESSED BY ID. Seven sheets style `.site-mobile-menu`
   (nav-unify, nav-ia, landing-home-fixes, landing-glass, landing-nav-
   consistency, landing-wefixtrades-cleanup, landing-footer-polish), most of
   them at `html body.qf-wft ...:not([hidden])` with `!important`, and some are
   injected by script AFTER every static <link>. A class-level rule here loses
   to at least one of them on at least one surface — which is how the drawer
   ended up with a different fill on / than on /directory in the first place.
   `#site-mobile-menu` is (1,0,1): it outranks every one of them on specificity
   alone, so this block is the single description of the drawer on all four
   shells (static marketing pages, landing.html, the directory subsite and the
   OS/OW + pilot-car tool shells) regardless of load order, and no later sheet
   can quietly put it back in flow without also using the id.

   THE STACKING ORDER IS DELIBERATE. `.site-header` is the drawer's ancestor
   and carries `z-index: 70`, so it opens a stacking context the drawer cannot
   escape: raising the drawer alone would have done nothing. The header itself
   is promoted for as long as the drawer is open — above the marketing chat
   launcher's 2147483000 — and dropped back the moment it closes, so the chat
   PANEL is never covered when chat is the thing that is open. The launcher
   also still stands down via the `:has()` rule above; this is the belt for
   browsers without `:has()`.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* Scrim: the page ink at 44%, i.e. inside Material's 0.32-0.48 scrim band,
     keyed to --ink (#020618) rather than pure black so it tints with the
     palette. */
  --qf-menu-scrim: rgba(2, 6, 24, 0.44);
  /* THE FROST. Tint alpha is set by MEASUREMENT, not by taste. The hero wash's
     top band is a saturated azure (#0179AD to #026692, see
     landing-home-grid.css) and the drawer's body text is near-black in the
     light theme, so this is the one place on the site where the glass really
     is between body copy and a dark ground.

     Every menu item was pixel-sampled against the RENDERED blur — the panel
     photographed twice at 375px, once painted and once with the type made
     transparent, and each row's worst background pixel taken from the second —
     on /, /directory and a tool page, in all three theme states:

       0.86  worst row 5.77:1   (the frost barely reads)
       0.80  worst row 5.05:1   ← WAVE 6 shipped this

     RAISED TO 0.92 IN WAVE 7, AND THE REASON IS GEOMETRY RATHER THAN TASTE.
     At WAVE 6 the panel was a card under the bar, so what showed through it
     was a card's worth of page. Full-bleed, the SAME alpha is now frosting
     the entire viewport — and the report that opened WAVE 6 was "when I
     scroll down I can actually see the hero section of the website under the
     menu". At 0.80 across 100dvh the hero's own headline is still legible
     through the blur in the empty space below a collapsed menu, which is that
     complaint again with more surface. 0.92 keeps the tint and the saturate —
     you can still see there IS a page behind it, and the blur still bends the
     colour of whatever is under each row — without any of it being readable.
     It also moves every contrast number the right way. Re-measured the same
     way — panel photographed with the ink made transparent, worst background
     pixel per element, 1,152 samples over /, /directory and a tool page at
     four scroll offsets in all three theme states — the worst role is the
     12px subtitle at 6.39:1 dark and 6.64:1 light, against a 4.5 floor; the
     glyph's worst is 13.05:1 against a 3.0 floor. Nothing fails. */
  --qf-menu-tint: rgba(255, 255, 255, 0.92);
  --qf-menu-hair: rgba(255, 255, 255, 0.62);
  --qf-menu-hover: rgba(2, 6, 24, 0.06);
  /* 24px in WAVE 6, 40px in WAVE 7 — for the same reason the tint went up.
     Blur radius is what decides whether a SHAPE survives, and a 48px hero
     headline blurred at 24px is still a readable silhouette once the panel is
     covering the whole viewport rather than a card's worth of it. At 40px the
     large forms dissolve into colour, which is what frost is supposed to do.
     It costs one composite, not one per frame: the page behind is scroll-
     locked for as long as the panel is open, so the backdrop never changes. */
  --qf-menu-blur: 40px;
  --qf-menu-sat: 180%;
  /* WAVE 7 — the icon tile. A tint OF the ink rather than a surface token,
     because both --surface-2 (the tile's natural candidate) and
     --chrome-panel-bg resolve to the same value: a tile painted in either is
     invisible on the panel it sits on. Alpha over the frost instead, so the
     tile tracks whatever the panel resolves to in all three theme states. */
  --qf-menu-tile: rgba(2, 6, 24, 0.05);
  --qf-menu-tile-edge: rgba(2, 6, 24, 0.10);
  /* The two durations the design law allows, used the conventional way round:
     the longer one enters, the shorter one exits. */
  --qf-menu-dur-in: .3s;
  --qf-menu-dur-out: .2s;
  /* THE ONE CURVE IN THIS DRAWER, AND WHY IT IS NOT `ease`. The system ships a
     single easing (`ease` = cubic-bezier(.25,.1,.25,1)), which is symmetric:
     it starts slowly, and a panel that starts slowly reads as one being
     dragged rather than one arriving. Material's "emphasised decelerate" is
     the opposite shape — most of the distance is covered in the first third,
     then it settles — and that snap IS what the report calls a premium Android
     effect. Scoped to the drawer, the scrim and the burger, and used nowhere
     else on the site: the leak test below is what holds that.

     IT IS NOW THE ONLY CURVE IN HERE. WAVE 6 introduced it for the entrances
     and left `ease` on the exits and on every small in-panel state change —
     the fold, the chevron, the tile, the row tint — which shipped the drawer
     with two easings against a law that allows one. Unified on this one, and
     the fold is the motion worth checking because it is the one a visitor
     watches most. SAMPLED FRAME BY FRAME at 375px on the nine-row "For
     Carriers & Brokers" group (600px of travel), driven by a real summary
     click: both curves move on the same frame and both settle in the same 9
     frames, the emphasised one at 205ms and `ease` at 232ms against a declared
     200ms. The difference is the distribution — four frames in, the emphasised
     curve has covered 82% of the height against 58% — which is the arrival
     shape it was chosen for, at the size where it is least disruptive.
     Nothing about it reads wrong at 0.2s, so there is no second curve and no
     deviation to declare. */
  --qf-menu-ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
}
html[data-theme="dark"] {
  --qf-menu-scrim: rgba(2, 6, 24, 0.58);
  --qf-menu-tint: rgba(19, 26, 40, 0.92);
  --qf-menu-hair: rgba(226, 232, 240, 0.16);
  --qf-menu-hover: rgba(226, 232, 240, 0.10);
  --qf-menu-tile: rgba(226, 232, 240, 0.09);
  --qf-menu-tile-edge: rgba(226, 232, 240, 0.16);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --qf-menu-scrim: rgba(2, 6, 24, 0.58);
    --qf-menu-tint: rgba(19, 26, 40, 0.92);
    --qf-menu-hair: rgba(226, 232, 240, 0.16);
    --qf-menu-hover: rgba(226, 232, 240, 0.10);
    --qf-menu-tile: rgba(226, 232, 240, 0.09);
    --qf-menu-tile-edge: rgba(226, 232, 240, 0.16);
  }
}

/* ── THE ROOT HEIGHT RULE, AND WHY BODY IS `min-height` ──────────────────
   style.css used to say `html, body { height: 100% }`. The `body` half of that
   was a nav-killing bug, because BODY IS THE STICKY HEADER'S CONTAINING BLOCK:
   `.site-header` is a direct child of body, and a sticky box may never travel
   past the bottom edge of its containing block, whatever the scrollport does.
   A body pinned to one viewport therefore gave the header a travel budget of
   `bodyHeight - headerHeight` and then cut it loose.

   MEASURED AT 375px WIDE, ON /, /directory AND /tools/oversize-permits, the
   header's top went negative at exactly `innerHeight - 64` on all three —
   568 -> 505, 667 -> 604, 720 -> 657, 800 -> 737, 844 -> 781, 1024 -> 961 —
   and the burger cleared the top of the screen 54px after that. The permits
   page is 6,367px of scroll at 800px tall: the nav was gone for 88% of it, and
   a tap on the burger at scrollY 1400 could not land at all.

   `min-height: 100%` gives the same guarantee the rule was written for — a
   short page still fills the window, so the footer does not float in the
   middle of the screen and the body background still covers it — while
   letting body grow to the document on a long one. `html` KEEPS `height:
   100%`: it is what the percentage in `min-height: 100%` resolves against,
   and it is not a containing block for anything sticky.

   WHAT THIS DOES NOT CHANGE, RE-MEASURED AFTER:
     • The homepage is still the odd one out. `.landing-v2 { overflow-x:
       hidden }` is what makes body's computed `overflow-y` `auto` there (a
       hidden axis forces the visible axis to `auto`) — it is a derived value,
       not a declaration, and it does not depend on the height rule at all.
       The lock below still has to survive it.
     • The lock itself already overrode the height rule: the open state sets
       `height: auto !important; min-height: 0 !important` on body, so it was
       written against an auto-height body from the start.
     • Every `height: 100%` in the codebase resolves inside a parent with its
       own definite height (tiles, logos, media, `.qf-cz-frame`); none of them
       chain up to body. The whole-site box fingerprint — 9 pages x 5 widths x
       all three theme states — is byte-identical before and after except for
       body's own height on pages shorter than the viewport.

   ── The scroll lock, and why it is the three-part one ───────────────────
   MEASURED, because the cheap version was tried first and both cheap versions
   fail on THIS document. `html { height: 100% }` is in force site-wide, and on
   the homepage `body` additionally carries `overflow-y: auto`, so the
   viewport's scroller is propagated from BODY there and from the ROOT
   elsewhere — two different scroll containers on two pages of the same site:

     • `body { overflow: hidden }` — propagates to the viewport, the viewport
       stops being scrollable, and Chrome CLAMPS the offset to 0. Measured on
       /: scrollY 900 -> 0 the instant the menu opened. That is precisely the
       "loses its place" failure this lock exists to avoid.
     • `html { overflow: hidden }` — same clamp on / (900 -> 0), and on
       /directory it did not even lock: measured scrollY 328 -> 728 on a wheel
       with the drawer open, because the root's `height: 100%` leaves the real
       overflow on body.

   So the body is taken out of the scrollport and PINNED at its current offset
   — the only lock that keeps a pixel-identical picture on both — and the two
   things that technique normally breaks are fixed here rather than left:

     1. THE STICKY HEADER GOES WITH IT. A fixed body renders the header at
        `-lockY`, i.e. off screen at any scroll depth, taking the burger with
        it. The header is therefore pinned too, for exactly as long as the
        drawer is open.
     2. PINNING THE HEADER PULLS IT OUT OF FLOW, so everything under it jumps
        up by its height. --qf-lock-pad is the body's own top padding PLUS the
        header's height, measured an instant before the lock, which puts the
        content back where it was — measured as 0px of movement and 0 CLS on
        open AND on close.

   --qf-sbw is the classic scrollbar's width, measured before the lock (when
   documentElement.clientWidth still excludes it) and re-added as body padding
   so losing the bar cannot shift the page sideways. It is 0px on a phone. And
   the script still calls `scrollTo(0, lockY)` on release: the pin is what
   keeps the picture, the explicit restore is what iOS needs. */
html[data-qf-menu="open"] {
  overflow: hidden !important;
}
html[data-qf-menu="open"] body {
  position: fixed !important;
  top: calc(var(--qf-lock-y, 0px) * -1) !important;
  left: 0 !important;
  right: 0 !important;
  bottom: auto !important;
  width: auto !important;
  height: auto !important;
  min-height: 0 !important;
  /* Not `hidden`: a fixed body with `overflow-y: auto` becomes its own scroll
     container, complete with its own scrollbar and its own scrollable content
     — the lock would leak straight back in through it. */
  overflow: visible !important;
  padding-top: var(--qf-lock-pad, 0px) !important;
  padding-right: var(--qf-sbw, 0px) !important;
}

/* THE HEADER'S PIN IS THE ONE THING NOT DECLARED HERE, and deliberately.
   nav-ia.css states the homepage header four times, the widest of them at
   `html:not([data-theme="light"]) body.qf-wft.landing-v2 .site-header` — (0,4,2)
   with `position: sticky !important` — and it is injected after this sheet, so
   matching it would mean either an id this header cannot carry (three tests pin
   the literal `<header class="site-header">`) or a selector padded with repeated
   classes until it outranks a rule it has no quarrel with. Measured with the
   CSS version in place: the header stayed sticky on / and rode the
   compensation padding 64px DOWN the page.

   So the pin is four inline `!important` properties set by the script for
   exactly as long as the drawer is open, and removed with it — an inline
   `!important` is the one thing that outranks every sheet on the page whatever
   its specificity or load order. See HEADER_SCRIPTS in siteChrome.ts. */

/* ── The scrim ────────────────────────────────────────────────────────────
   Full-viewport from y=0 since WAVE 7, because the panel is: the panel's
   backdrop-filter blurs whatever is painted behind it in this stacking
   context, and a scrim that stopped at the bar's bottom edge would have put a
   seam across the frost 69px down. The bar's own contents are lifted ABOVE
   both (see THE BAR FLOATS below), so nothing it carries is dimmed. Fades on
   the SAME durations as the panel: one gesture, not two. */
#site-menu-scrim {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1;
  background-color: var(--qf-menu-scrim);
  opacity: 0;
  /* Inert while it is fading OUT, so a second tap lands on the page rather
     than on a scrim the visitor can no longer see. */
  pointer-events: none;
  transition: opacity var(--qf-menu-dur-out) var(--qf-menu-ease-emphasized);
}
#site-menu-scrim[data-state="open"] {
  opacity: 1;
  pointer-events: auto;
  transition: opacity var(--qf-menu-dur-in) var(--qf-menu-ease-emphasized);
}

/* ═══════════════════════════════════════════════════════════════════════════
   WAVE 7 — THE PANEL GOES FULL-BLEED AND THE INTERIOR BECOMES A ROW.

   Reported as: "it's very basic, you didn't add any premium folding/unfolding
   effect. I don't see any clear separation between categories, I don't like
   the large gaps on the left and right sides and between menu container and
   the top bar."

   THE GAPS WERE GEOMETRY, AND THEY ARE THE CHEAPEST THING HERE TO FIX. WAVE 6
   left the panel inset `var(--space-2)` (16px) left and right and dropped it
   `var(--space-1)` (8px) below the bar, with a 12px radius and a hairline —
   i.e. a card that landed ON the page. Measured at 375px: 16 / 16 / 16 left,
   right and top-gap. A menu that reads as "the app changed mode" instead is
   `inset: 0` with the BAR'S CONTROLS LIFTED OVER IT, which costs nothing and
   deviates from nothing:

     • The panel is `inset: 0`, `100dvh`, radius 0, no border, no shadow —
       there is no edge left to inset. Measured after: 0 / 0 / 0 / 0 and 0px
       to the bar at 320, 375, 390.
     • `.mm-barpad` is an opaque `aria-hidden` spacer INSIDE the panel, the
       height of the bar the script already measures into --qf-menu-top. The
       content clears the bar without the panel ever starting lower.
     • THE BAR FLOATS: `.site-header-inner` takes z-index 3 over the panel's 2
       while the drawer is open, so the logo, the theme toggle and the burger
       stay hit-testable on top of a full-screen sheet. The burger is the
       close control — it has to be reachable, which is precisely why the
       panel cannot simply be raised above the whole header.
     • The 16px gutter moves to the SCROLL AREA's padding, so the surface runs
       edge to edge while the rows and the category rules sit 16px in.

   THE SHADOW IS DROPPED at phone width rather than restated: nothing is
   visible outside a full-bleed panel, so it would spend one of the system's
   three shadow tokens on pixels no one sees. It is restored for the 600-1023
   band, where the drawer really is a floating 360px sheet.

   CATEGORY SEPARATION IS A 1px HAIRLINE, WHICH IS A DECLARED DEVIATION. The
   house rule is that dividers are GAPS over the canvas (see the mega-panel
   contract in siteChromeSingleSource.test.ts, which is unchanged and still
   governs the desktop flyout). WAVE 6 applied that rule here — an 8px gap
   between transparent groups — and the report is the evidence that it does
   not read on a frosted surface where every group shares one fill. So the
   drawer, and only the drawer, delimits its top-level rows with a
   --border-strong hairline across the full 343px track. --border-strong
   rather than --border for the same reason the panel's own edge uses it: on
   the dark theme --border is rgba(202,213,226,0.14), which over the frost is
   the invisible boundary this change exists to remove.
   ═══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 1023px) {
  /* ── The panel ─────────────────────────────────────────────────────────
     `position: fixed` is what fixed the WAVE 6 bug; `inset: 0` is what fixes
     the WAVE 7 one. The panel itself no longer scrolls — `.mm-scroll` does,
     so the bar spacer stays pinned at the top of the sheet. */
  #site-mobile-menu:not([hidden]) {
    position: fixed !important;
    top: 0 !important;
    left: 0 !important;
    right: 0 !important;
    bottom: 0 !important;
    height: 100vh;
    height: 100dvh;
    z-index: 2 !important;
    margin: 0 !important;
    padding: 0 !important;
    max-height: none !important;
    display: flex !important;
    flex-direction: column;
    gap: 0 !important;
    overflow: hidden;
    overscroll-behavior: contain;
    border-radius: 0 !important;
    border: 0 !important;
    box-shadow: none !important;
    /* nav-ia.css runs `animation: qf-flyout-in .2s ease !important` on this
       element at (0,4,2). It is a THIRD motion on top of the two below, and
       at full bleed its `translateY(-4px)` opens a 4px seam along the bottom
       edge for the length of the keyframe. The id out-specifies it. */
    animation: none !important;
    /* THE FALLBACK IS THE BASE DECLARATION. A browser with backdrop-filter
       disabled or unimplemented never enters the @supports block below and
       gets a fully opaque --chrome-panel-bg sheet — never body text floating
       on the hero. Progressive enhancement, so the fallback cannot rot. */
    background-color: var(--chrome-panel-bg) !important;
    background-image: none !important;
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    /* Closed-but-rendered: the frame the entrance animates FROM. */
    opacity: 0;
    transform: translateY(calc(var(--space-1-5) * -1)) scale(0.98);
    transform-origin: top center;
    transition:
      opacity var(--qf-menu-dur-out) var(--qf-menu-ease-emphasized),
      transform var(--qf-menu-dur-out) var(--qf-menu-ease-emphasized);
    will-change: transform, opacity;
  }
  #site-mobile-menu[data-state="open"] {
    opacity: 1;
    transform: translateY(0) scale(1);
    transition:
      opacity var(--qf-menu-dur-in) var(--qf-menu-ease-emphasized),
      transform var(--qf-menu-dur-in) var(--qf-menu-ease-emphasized);
  }
  @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    #site-mobile-menu:not([hidden]) {
      background-color: var(--qf-menu-tint) !important;
      backdrop-filter: blur(var(--qf-menu-blur)) saturate(var(--qf-menu-sat)) !important;
      -webkit-backdrop-filter: blur(var(--qf-menu-blur)) saturate(var(--qf-menu-sat)) !important;
    }
  }

  /* ── THE BAR FLOATS OVER THE PANEL ─────────────────────────────────────
     Only while the drawer is open. `.site-header` is the stacking context
     (z-index 70); inside it the scrim is 1, the panel 2 and the bar 3, so the
     logo / theme toggle / burger are painted on top of a full-screen sheet
     and stay hit-testable. `.site-header-inner` already carries
     `position: relative` (see the Header shell block), so this is the z-index
     and nothing else. */
  html[data-qf-menu="open"] .site-header-inner {
    z-index: 3;
  }

  /* ── The shell: a fixed bar spacer over a scrolling track ──────────────
     The spacer is the reference's trick and the reason the panel can start at
     y=0: the surface is full-bleed, the CONTENT starts below the bar. Its
     height is --qf-menu-top, which the script measures from the real bar on
     every open and every resize, so it cannot drift from the header it is
     reserving space for. */
  #site-mobile-menu .mm-barpad {
    flex: 0 0 auto;
    height: var(--qf-menu-top, var(--chrome-h-compact));
  }
  #site-mobile-menu .mm-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    /* The gutter lives HERE, not on the panel — that is what lets the surface
       run to the screen edge while the rows sit 16px in. */
    padding: 0 var(--space-2) var(--space-4);
  }

  /* ── Category separation: one hairline, full track, no inset ───────────
     On every TOP-LEVEL row — the three collapsible groups and the two plain
     links — so the rule delimits the GROUP, not the heading: expanded, it
     falls below the last tool row and binds the heading and its children into
     one block; collapsed, it sits directly under the heading. The last row in
     the track has nothing below it to separate from. */
  #site-mobile-menu .mm-group {
    background: transparent !important;
    border: 0 !important;
    border-top: 0 !important;
    border-bottom: 1px solid var(--border-strong) !important;
    border-radius: 0 !important;
    margin: 0 !important;
    padding: 0 !important;
  }
  /* .mm-flat (Pricing) and .mm-account (Sign in) are the <a> elements
     THEMSELVES, not wrappers — which is why THEIR hairline is a pseudo-element
     and the groups' is a border. A border on a row participates in layout: it
     put Pricing at 61px against every other row's 60px, because the 36px tile
     plus 12px of padding each side already fills the box and the extra pixel
     has nowhere to go. Measured before and after; every row is now 60.00. */
  #site-mobile-menu .mm-flat,
  #site-mobile-menu .mm-account {
    position: relative;
    background: transparent !important;
    border: 0 !important;
    border-top: 0 !important;
    border-bottom: 0 !important;
    border-radius: 0 !important;
    margin: 0 !important;
  }
  #site-mobile-menu .mm-flat::after,
  #site-mobile-menu .mm-account::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 1px;
    background: var(--border-strong);
  }
  #site-mobile-menu .mm-scroll > :last-child { border-bottom: 0 !important; }
  #site-mobile-menu .mm-scroll > :last-child::after { content: none; }

  /* ── The group heading ─────────────────────────────────────────────────
     16px / 500 / sentence case, the same size and weight as a plain link:
     this is a navigation row that happens to open, not a section label. It
     replaces an 11px mono uppercase eyebrow in --muted, which was a second
     type system inside a menu that now has three text roles already.

     RAMP SNAP: the reference pads 14px top and bottom. 14 is off our 8px
     ramp, so it is 16 (--space-2). Reviewed on screen at 320/375/390: the
     extra 2px per side is an improvement, not a cost — it puts the 52px
     heading at 56px, comfortably over the 44px tap floor, and gives the
     collapsed state a rhythm that reads as rows rather than as a dense list. */
  #site-mobile-menu .mm-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-1);
    margin: 0 !important;
    padding: var(--space-2) 0 !important;
    min-height: var(--tap-target);
    list-style: none;
    cursor: pointer;
    font-family: var(--font-sans);
    font-size: 16px;
    font-weight: 500;
    line-height: 24px;
    letter-spacing: -0.02em;
    text-transform: none;
    color: var(--ink) !important;
    border-radius: 0;
  }
  #site-mobile-menu .mm-head::-webkit-details-marker { display: none; }
  #site-mobile-menu .mm-head::marker { content: ''; }
  /* The old '▾' text caret. Replaced by a real 16px chevron in the markup so
     it can rotate on a transform rather than restyle a glyph. */
  #site-mobile-menu summary.mm-head::after { content: none !important; }
  #site-mobile-menu .mm-chev {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
    opacity: .7;
    color: var(--ink);
    transition: transform var(--qf-menu-dur-out) var(--qf-menu-ease-emphasized);
  }
  /* `[open]` is on for the whole exit (the script holds it so the collapse can
     be seen), so it leads the fold by a frame and trails it by 200ms. Where
     `:has()` exists the chevron is keyed to the FOLD instead and the two move
     together; `[open]` stays the fallback, and is also the no-JS answer. */
  #site-mobile-menu .mm-group[open] > .mm-head .mm-chev { transform: rotate(180deg); }
  @supports selector(:has(*)) {
    #site-mobile-menu[data-fold-js] .mm-group[open] > .mm-head .mm-chev { transform: none; }
    #site-mobile-menu[data-fold-js] .mm-group:has(> .mm-fold[data-fold="open"]) > .mm-head .mm-chev {
      transform: rotate(180deg);
    }
  }

  /* ── The fold ──────────────────────────────────────────────────────────
     `0fr -> 1fr` resolves to the content's intrinsic height, so a two-row
     group and a nine-row group both land on their own height with no
     max-height guess to clip one or overshoot the other. The inner div is
     required: it is the grid ITEM, and it is what clips.

     GATED ON `[data-fold-js]`, WHICH THE CONTROLLER SETS ON ITSELF. Only the
     script ever writes `data-fold`, so an unconditional collapsed state would
     mean a group that opens to nothing for anyone the script does not reach —
     scripting off, or a throw earlier in the same inline block. Unset, the
     <details> is just a <details> and the browser's own disclosure works. */
  #site-mobile-menu[data-fold-js] .mm-fold {
    display: grid;
    grid-template-rows: 0fr;
    opacity: 0;
    transition:
      grid-template-rows var(--qf-menu-dur-out) var(--qf-menu-ease-emphasized),
      opacity var(--qf-menu-dur-out) var(--qf-menu-ease-emphasized);
  }
  #site-mobile-menu[data-fold-js] .mm-fold[data-fold="open"] {
    grid-template-rows: 1fr;
    opacity: 1;
  }
  #site-mobile-menu[data-fold-js] .mm-fold__in {
    overflow: hidden;
    min-height: 0;
  }
  /* The 16px between the last row and the group's hairline. A MARGIN on the
     last child rather than padding on `.mm-fold__in`, because padding on a
     grid item in a 0fr track does not collapse with the track. */
  #site-mobile-menu .mm-fold__in > :last-child { margin-bottom: var(--space-2) !important; }

  /* ── In-group sub-heading ──────────────────────────────────────────────
     Kept, because our groups run to nine rows where the reference's runs to
     five, and nine undifferentiated rows is the "unscannable dump" the
     sub-headings were added to fix. Re-cut to the house type so it cannot be
     mistaken for either the 16px heading above it or the 12px subtitle
     beside it: same size as the subtitle, but --ink at 600 against the
     subtitle's --muted at 400. */
  #site-mobile-menu .mm-sub {
    margin: 0 !important;
    padding: var(--space-2) 0 var(--space-1) !important;
    font-family: var(--font-sans);
    font-size: 12px;
    line-height: 16px;
    font-weight: 600;
    letter-spacing: -0.01em;
    text-transform: none;
    color: var(--ink) !important;
  }
  #site-mobile-menu .mm-fold__in > .mm-sub:first-child { padding-top: 0 !important; }

  /* ── The row ───────────────────────────────────────────────────────────
     60px = 12 + 36 + 12, driven by the icon tile rather than the text, which
     is why the pitch survives any copy length. Flush left to the gutter, no
     left padding: the tile IS the left edge.

     RAMP SNAP: the reference's right padding is 28px, off our ramp; 24
     (--space-3) is the nearest and loses nothing — it is dead space that only
     keeps a long subtitle off the screen edge, and at 320px it BUYS 4px of
     subtitle track, which is where the margin is tightest. */
  #site-mobile-menu .mm-scroll a {
    display: flex !important;
    align-items: center;
    gap: var(--space-1-5);
    min-height: 60px;
    margin: 0 !important;
    padding: var(--space-1-5) var(--space-3) var(--space-1-5) 0 !important;
    border-radius: var(--radius-sm) !important;
    color: var(--ink) !important;
    text-decoration: none;
    transition: background-color var(--qf-menu-dur-out) var(--qf-menu-ease-emphasized);
  }
  #site-mobile-menu .mm-tile {
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-sm);
    background: var(--qf-menu-tile);
    border: 1px solid var(--qf-menu-tile-edge);
    color: var(--ink);
    transition: background-color var(--qf-menu-dur-out) var(--qf-menu-ease-emphasized), color var(--qf-menu-dur-out) var(--qf-menu-ease-emphasized);
  }
  /* 20-in-36, drawn at 1:1 — the ratio that stops the glyphs reading as
     scaled-down clip art. Same 24px box and 1.3px stroke as the homepage tool
     bento, so the menu and the page agree on what a tool looks like. */
  #site-mobile-menu .mm-tile svg {
    width: 20px;
    height: 20px;
    display: block;
  }
  #site-mobile-menu .mm-txt {
    display: flex;
    flex-direction: column;
    gap: var(--space-0-5);
    min-width: 0;
  }
  #site-mobile-menu .mm-t {
    display: block;
    font-size: 14px;
    font-weight: 500;
    line-height: 16px;
    letter-spacing: -0.02em;
    color: var(--ink);
  }
  /* THE ONE-LINE SUBTITLE IS EDITORIAL — every string in SITE_MOBILE_MENU_HTML
     is written to a <=28 character budget, which is what actually keeps it on
     one line down to 320px. These three declarations are a BACKSTOP for a
     future long string, so a bad edit degrades to an ellipsis instead of
     wrapping and breaking the 60px pitch. If the ellipsis is ever visible,
     the copy is wrong, not the sheet. */
  #site-mobile-menu .mm-s {
    display: block;
    font-size: 12px;
    font-weight: 400;
    line-height: 16px;
    letter-spacing: -0.02em;
    color: var(--muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  /* 16 + 4 + 16 = 36, the tile's own height, so the text block is optically
     centred against it and the row lands on 60 without a magic number.
     RAMP SNAP: the reference's title/subtitle gap is 6px; 4 (--space-0-5) is
     the nearest ramp value. Reviewed on screen — it reads BETTER, not worse:
     the pair binds harder into one object, which is the whole point of the
     gap being small. */

  /* THE HOVER CUE IS THE TILE, not the row — the reference's one genuinely
     good state, and it costs nothing. Gated on a real pointer because on a
     phone it is either invisible or sticky-after-tap. The row's own tint
     stays for the tap/active feedback that touch DOES see. */
  #site-mobile-menu .mm-scroll a:hover {
    color: var(--ink) !important;
    background-color: var(--qf-menu-hover) !important;
  }
  @media (hover: hover) and (pointer: fine) {
    #site-mobile-menu .mm-scroll a:hover .mm-tile {
      background: var(--ink);
      border-color: var(--ink);
      color: var(--chrome-panel-bg);
    }
  }
  #site-mobile-menu .mm-head:hover { color: var(--accent) !important; }
  /* The focus ring is an OUTLINE, the hover cue is a FILL — the two states are
     never mistaken for one another, which is the point of :focus-visible. */
  #site-mobile-menu a:focus-visible,
  #site-mobile-menu summary:focus-visible {
    outline: 2px solid var(--accent) !important;
    outline-offset: -2px;
    background-color: transparent !important;
  }
  /* [hidden] stays authoritative over the display rules above (nav-auth.js
     gates /importers/saved on a session). */
  #site-mobile-menu a[hidden],
  #site-mobile-menu .mm-group > a[hidden],
  #site-mobile-menu .mm-fold__in > a[hidden] { display: none !important; }
}

/* ── Tablet: still full-bleed, but the track stops widening ──────────────
   WAVE 6 turned the 600-1023 band into a 360px sheet floating off the
   burger's own corner, on the reasoning that a full-bleed drawer at 1000px is
   a wall holding a 200px column of bare links. WAVE 7 removes the exception
   for two reasons: the rows are no longer bare links (a 60px tile + title +
   subtitle needs ~340px, not 200px), and an inset sheet at ONE band is the
   same "a dropdown landed on the page" reading the report is about — it would
   have meant the menu changed shape at 600px for no reason a visitor can see.

   So the surface is `inset: 0` at every width the burger exists at, and the
   only thing the extra room buys is a centred track: the rows keep a readable
   measure instead of stranding against the left edge of a 1000px pane. Below
   600px the viewport is narrower than the cap, so the rule does nothing. */
@media (min-width: 600px) and (max-width: 1023px) {
  #site-mobile-menu .mm-scroll {
    max-width: 560px;
    margin-left: auto;
    margin-right: auto;
    width: 100%;
  }
}

/* Above the collapse point there is no burger, so there is no drawer. Stated
   because the id out-specifies the `display: none` the other sheets carry. */
@media (min-width: 1024px) {
  #site-mobile-menu { display: none !important; }
  #site-menu-scrim { display: none !important; }
}

/* ── The burger becomes an X ──────────────────────────────────────────────
   Three spans, not two swapped glyphs: the bars are the SAME three bars
   before and after, so the change is a rotation the eye can follow rather
   than a substitution it can only notice. Laid out with space-between so the
   only numbers involved are the transforms.

   AND THE CONTROL ITSELF WAS UNDER THE FLOOR. Every sheet that shows the
   burger sizes it 40x40 — nav-unify at the collapse point, nav-ia at (0,3,2)
   with !important, landing-home-fixes at 640 — and the house rule, Apple's
   44pt and Material's 48dp all put the minimum at 44. It is the one control
   this whole round is about, so it goes to 44 rather than staying 4px short.
   min-* rather than width/height, so no other sheet has to be edited and the
   box cannot shrink back; measured at 320px the header still fits with 0
   horizontal overflow. */
#site-burger {
  min-width: var(--tap-target);
  min-height: var(--tap-target);
}
#site-burger .qf-burger-box {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  width: 20px;
  height: 16px;
  pointer-events: none;
}
#site-burger .qf-burger-bar {
  display: block;
  width: 100%;
  height: 2px;
  border-radius: var(--radius-pill);
  background-color: currentColor;
  transition:
    transform var(--qf-menu-dur-out) var(--qf-menu-ease-emphasized),
    opacity var(--qf-menu-dur-out) var(--qf-menu-ease-emphasized);
}
#site-burger[aria-expanded="true"] .qf-burger-bar {
  transition:
    transform var(--qf-menu-dur-in) var(--qf-menu-ease-emphasized),
    opacity var(--qf-menu-dur-in) var(--qf-menu-ease-emphasized);
}
#site-burger[aria-expanded="true"] .qf-burger-bar:nth-child(1) { transform: translateY(7px) rotate(45deg); }
#site-burger[aria-expanded="true"] .qf-burger-bar:nth-child(2) { opacity: 0; transform: scaleX(0.2); }
#site-burger[aria-expanded="true"] .qf-burger-bar:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
#site-burger:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── prefers-reduced-motion: the frost stays, the movement goes ──────────
   Not "shorter" and not "fade only" — instant, both ways. The script reads the
   same query so the close is not held open waiting for a transitionend that
   will never arrive. */
@media (prefers-reduced-motion: reduce) {
  #site-mobile-menu:not([hidden]),
  #site-mobile-menu[data-state="open"],
  #site-menu-scrim,
  #site-menu-scrim[data-state="open"],
  #site-burger .qf-burger-bar,
  #site-burger[aria-expanded="true"] .qf-burger-bar,
  /* WAVE 7: the fold and its chevron ride the same rule. The script reads the
     same query and drops its 200ms hold to 0, so a collapse is not held open
     waiting for a transition that will never run. */
  #site-mobile-menu[data-fold-js] .mm-fold,
  #site-mobile-menu[data-fold-js] .mm-fold[data-fold="open"],
  #site-mobile-menu .mm-chev,
  #site-mobile-menu .mm-tile,
  #site-mobile-menu .mm-scroll a {
    transition: none !important;
    animation: none !important;
  }
  #site-mobile-menu:not([hidden]) { transform: none !important; }
}


/* ═══════════════════════════════════════════════════════════════════════════
   THE HEADER BRAND LOCKUP — THE FULL MARK, THE FOOTER'S WORDMARK, AND A CUT
   THAT IS CHOSEN BY CSS RATHER THAN BY SCRIPT.

   Owner, 2026-09: "you didn't update the top bar business logo with our latest
   one, but ensure that dark and bright versions of the website have correct
   logos."

   WHAT WAS ACTUALLY SHIPPING, measured on all four page/theme combinations
   before this block existed:
     • the mark was /brand/mark-keys-ondark.png at 28x30 — the calculator keys
       ALONE, no truck — while the footer had moved to the full keys+truck
       lockup in #567/#568. Two marks for one brand, often in one scroll.
     • the word beside it was a plain Inter 800 / 19px text node, against the
       footer's baked Instrument Sans 600 outline. Two faces for one word.
     • the cut WAS theme-paired, but only by JavaScript: theme-toggle.js
       rewrites any /brand/*-ondark src to its light cut when data-theme is
       "light". With scripting off nothing stamps data-theme and nothing
       rewrites the src, so the page took its COLOURS from prefers-color-scheme
       while the mark stayed frozen on the on-dark cut. Measured in that state
       on a light system: white-outline mark on the #F8FAFC header, 1.06:1.

   THE CUT IS A BACKGROUND-IMAGE ON AN EMPTY SPAN, and that is what makes the
   three theme states expressible at all. CSS is render-blocking, so the
   winning rule is settled before first paint and there is no wrong-cut flash;
   the browser fetches ONLY the cut whose rule won, so this is one request
   rather than the two an <img> pair would cost (and one FEWER than the old
   path, which fetched the on-dark cut from markup and then the light cut again
   from script). width/height are declared on the span and background-size is
   "contain", so the box is known before the bytes land and the 374x252 source
   letterboxes inside 45x30 (44.52x30 painted) instead of distorting.

   THE TWO HEADERS DO NOT SHARE A GROUND, so they do not share a default.
   Sampled from the live pages, not assumed:
     .site-header   data-theme=dark  #0C111D · light #F8FAFC · un-stamped:
                    follows prefers-color-scheme, because --chrome-bg is --bg.
     .topnav (auth) data-theme=dark  rgb(34,40,42) · light #FFFFFF ·
                    un-stamped: rgb(34,40,42) in BOTH system schemes — the
                    white bar is painted only under an explicit
                    data-theme="light".
   So the marketing/directory header defaults to the LIGHT cut and darkens
   under [data-theme="dark"] or a dark system scheme; the auth bar defaults to
   the DARK cut and lightens only under [data-theme="light"]. Copying one rule
   to both would have put a white mark on a white bar on /login for every
   visitor with scripting off.

   THE THREE STATES ARE ALL DECLARED. The bare-:root rule is the un-stamped
   default, the prefers-color-scheme block is guarded with
   :not([data-theme="…"]) so an explicit choice always wins, and the
   [data-theme] rules state the toggle's answer in both directions. No cut has
   its only definition inside a media query.

   THE WORDMARK IS currentColor and inherits the anchor's ink, which is already
   correct in all three states (--ink resolves per scheme), so the word's
   contrast is exactly what the text node it replaces had. Asset, kerning trap
   and the accessible-name split: HEADER_WORDMARK_SVG in siteChrome.ts.

   HEIGHT IS UNCHANGED ON PURPOSE. The mark keeps the old 30px height, so the
   brand box stays 30-32px tall against a --chrome-h of 83px (64px compact) and
   the header measures the same 83/64px it always did. This is in the header of
   every page; a shift here is a Core Web Vitals regression, not a cosmetic one.

   Appended at EOF, like every wave in this file: check-spacing.mjs and
   check-hardcoded-colors.mjs baseline by FILE:LINE. NOTE that neither guard
   reads CSS or SVG inside .ts template literals, which is where the header
   markup and the wordmark live — this block was hand-audited against the
   design law, not signed off by a green guard run.
   ═══════════════════════════════════════════════════════════════════════════ */

/* THE WRAPPER IS A BOX, NOT A TILE. landing-conversion.css:32 gives a bare
   .site-logo a 32px accent plate and landing-wefixtrades-cleanup.css:43 gives
   `body.qf-wft .site-logo` a 1px rgba(255,255,255,.30) frame at 8px radius.
   Both were written for a near-square 28x30 keys mark; around the wide
   keys+truck lockup the frame reads as a crop, the footer's copy of the same
   lockup has no frame, and /directory never had one — so the homepage and the
   directory were showing the same brand two different ways.

   It was also already theme-broken: a white-at-30% frame is visible on the
   #0C111D dark header and invisible on the #F8FAFC light one, so the "box"
   only existed in one theme.

   `!important` is MATCHED here, not escalated — both declarations this
   replaces carry it. The specificity is NOT matched, because a tie would be
   settled by load order: `body.qf-wft .site-logo` is (0,2,1) in TWO sheets
   (landing-home-fixes.css turns the frame off, landing-wefixtrades-cleanup.css
   turns it back on and wins by loading later), and a plain `.site-brand
   .site-logo` at (0,2,0) lost to both — measured, the frame was still painting
   after the first attempt. `.site-header .site-brand .site-logo` is (0,3,0),
   which beats them by class count rather than by luck, and reaches the
   directory shell too since both headers are `.site-header`. */
.site-header .site-brand .site-logo {
  width: auto !important;
  height: auto !important;
  background: none !important;
  border: 0 !important;
  border-radius: 0 !important;
  box-shadow: none !important;
  padding: 0 !important;
  overflow: visible !important;
}

/* 45x30 is the 374x252 source at the mark's existing 30px height (44.52
   painted, letterboxed by `contain`). The height is the number that matters:
   it is what the old 28x30 <img> occupied, so the brand box and therefore the
   header measure exactly what they measured before. */
.site-brand .qf-brand-cut {
  display: block;
  flex: 0 0 auto;
  width: 45px;
  height: 30px;
  background-repeat: no-repeat;
  background-position: left center;
  background-size: contain;
}

/* Marketing + directory header: ground follows the theme in all three states. */
.site-brand .qf-brand-cut { background-image: url("/brand/logo-full.png"); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .site-brand .qf-brand-cut {
    background-image: url("/brand/logo-full-ondark.png");
  }
}
:root[data-theme="dark"] .site-brand .qf-brand-cut {
  background-image: url("/brand/logo-full-ondark.png");
}
:root[data-theme="light"] .site-brand .qf-brand-cut {
  background-image: url("/brand/logo-full.png");
}

/* The auth bar's copy of all of this lives in public-auth-wefixtrades.css:
   /login, /signup and /reset-password load ONLY style.css and that sheet — no
   nav-unify.css — so a rule written here would never reach them. Verified by
   fetching the rendered /login head, not assumed from the injector.

   The word. Block display kills the inline baseline gap that would otherwise
   make the lockup taller than the mark; both axes come from the asset's own
   em sizing off the anchor's font-size, so nothing is pinned twice. */
.site-brand .qf-header-wordmark-svg {
  display: block;
  flex: 0 0 auto;
}

/* ═══ THE MARKETING FOOTER'S LAUNCHER GUTTERS ═══════════════════════════════
   The pair that replaces the full-width 80px band; reasoning and the
   96-combination measurement are in the block above the <=900px padding rule
   earlier in this file. Stated at (0,2,0) so they clear the bare-class rules
   in style.css that carry the same idiom above 900px. */
@media (max-width: 900px) {
  .premium-footer .qf-foot-marks { padding-right: 80px; }
}
@media (min-width: 641px) and (max-width: 900px) {
  .premium-footer .qf-footer-payrow { padding-right: 80px; }
}
