/* ─────────────────────────────────────────────────────────────────
   QuoteFleet — design system.

   Light default (Maersk-clean: white surfaces, deep-navy ink, brand
   blue accent). Dark theme available via toggle or OS preference.

   Used for: landing, login, signup, pricing, app dashboard, admin,
   chat. The embedded widget keeps its own light theme — see
   widget-style.css.

   Restyled to the WeFixTrades (DOSS) design language: self-hosted
   Satoshi, brand blue accent (#3356EE), hairline borders. Dark mode
   retains the warm near-black canvas (#161616) with cream CTAs.
   ─────────────────────────────────────────────────────────────── */

/* ── Site-wide minimal scrollbar (Alex — match the widget's premium look).
   Thin, no visible track, translucent rounded gray thumb that reads on both the
   dark marketing/portal surfaces and any light data-theme. The embedded widget
   keeps its own identical copy in widget-ux-fixes.css. ── */
html { scrollbar-width: thin; scrollbar-color: rgba(135, 138, 150, 0.38) transparent; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background-color: rgba(135, 138, 150, 0.38); border-radius: 8px; }
::-webkit-scrollbar-thumb:hover { background-color: rgba(135, 138, 150, 0.6); }

/* ── Self-hosted Inter (wave 2 — replaces Satoshi). Two variable-weight
   subsets served same-origin from /fonts, so the whole 100–900 range costs
   two requests instead of five static weights. font-display: swap keeps
   FOUT, never FOIT. No @import — a render-blocking third-party font
   request is exactly what self-hosting exists to avoid. */
@font-face {
  font-family: "Inter";
  font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
    U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
    U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal; font-weight: 100 900; font-display: swap;
  src: url("/fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}
/* Mono is a PLATFORM stack, not a webfont. The reference design ships no
   monospace at all, but the carrier directory renders USDOT/MC numbers in
   mono across hundreds of thousands of pages where tabular figures genuinely
   aid reading — so --font-mono keeps working without a font download.
   See --font-mono in :root below. */

:root {
  /* Surfaces — measured target palette (wave 2). White page, slate recess. */
  --bg:           #F8FAFC;
  --surface:      #FFFFFF;
  --surface-2:    #F2F4F7;
  --surface-3:    #EAECF0;

  /* Borders — 1px hairline everywhere; 2px only for selection / focus. */
  --border:        #E2E8F0;
  --border-strong: #CAD5E2;

  /* Ink ramp — primary / secondary / muted / de-emphasis. */
  --ink:        #020618;
  --ink-soft:   #314158;
  --muted:      #475467;
  --muted-soft: #90A1B9;

  /* THE accent — one blue, used sparingly (selection, focus, link hover). */
  --accent:        #3356EE;
  --accent-strong: #2440C4;
  --accent-soft:   rgba(51, 86, 238, 0.08);
  --accent-fill:   #3356EE;
  --accent-ink:    #FFFFFF;

  /* Secondary accent — same blue; the system has no second hue. */
  --accent-2:      #3356EE;
  --accent-2-soft: rgba(51, 86, 238, 0.08);

  /* CTA — dark surface fill with white text. */
  --cta-bg:              #0C111D;
  --cta-bg-hover:        #020618;
  --cta-text:            #FFFFFF;
  --cta-sec-text:        #020618;
  --cta-sec-border:      #CAD5E2;
  --cta-sec-border-hover:#90A1B9;
  --cta-sec-bg-hover:    rgba(51, 86, 238, 0.06);

  /* Status (semantic; tuned for light backgrounds). */
  --success:    #059669;
  --success-bg: rgba(5, 150, 105, 0.10);
  --warn:       #B45309;
  --warn-bg:    rgba(180, 83, 9, 0.10);
  --error:      #B91C1C;
  --error-bg:   rgba(185, 28, 28, 0.10);

  /* Effects — exactly three shadows; radii are 6 / 8 / 12 (+ the pill). */
  --shadow-sm: var(--qf-shadow-sm);
  --shadow-md: var(--qf-shadow-md);
  --shadow-lg: var(--qf-shadow-lift);
  --glow-cyan: 0 0 0 1px rgba(51, 86, 238, 0.18), var(--qf-shadow-lift);
  --radius:    12px;
  --radius-lg: 12px;
  --radius-xl: 12px;
  --radius-btn: 8px;
  --radius-pill: 9999px;

  /* ── Spacing ramp (8px grid) — canonical source: design-tokens/spacing.json.
     The ONLY allowed spacing values. New rules must reference these tokens, not
     off-grid literals (enforced by scripts/check-spacing.mjs). Existing legacy
     literals are tolerated via the guard baseline and migrated over time. */
  --space-0: 0;
  --space-0-5: 4px;
  --space-1: 8px;
  --space-1-5: 12px;
  --space-2: 16px;
  --space-3: 24px;
  --space-4: 32px;
  --space-6: 48px;
  --space-7-5: 60px;
  --space-10: 80px;
  --space-15: 120px;
  /* Semantic aliases (mobile values set in the max-width media queries). */
  --space-outer-margin: 32px;
  --space-gutter: 24px;
  --space-section: 80px;
  --space-card-padding: 24px;
  --tap-min: 24px;
  --tap-target: 44px;
  --tap-gap: 8px;
  --label-gap: 8px;
  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, system-ui, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* ── Light theme (explicit) — same values as the new light :root default.
   Kept so that existing html[data-theme="light"] component selectors across
   the codebase continue to fire and maintain specificity.
   Toggle lives in the dashboard sidebar; choice persists to localStorage.
   Only color tokens flip — radius / shadow / typography stay identical
   so layout is pixel-stable across themes.  */
html[data-theme="light"] {
  /* Measured target palette (wave 2): white page, slate recess, one blue.
     Mirrors :root exactly so the explicit toggle and the default agree. */
  --bg:           #F8FAFC;
  --surface:      #FFFFFF;
  --surface-2:    #F2F4F7;
  --surface-3:    #EAECF0;
  --border:        #E2E8F0;
  --border-strong: #CAD5E2;
  --ink:        #020618;   /* near-black ink */
  --ink-soft:   #314158;
  --muted:      #475467;
  --muted-soft: #90A1B9;
  /* On light surfaces the brand blue reads as itself (no lightened step). */
  --accent:        #3356EE;
  --accent-strong: #2440C4;
  --accent-soft:   rgba(51, 86, 238, 0.08);
  --accent-fill:   #3356EE;
  --accent-ink:    #FFFFFF;
  --accent-2:      #3356EE;
  --accent-2-soft: rgba(51, 86, 238, 0.08);
  /* Light: primary CTA is the dark surface with white text. Ghost secondary
     stays outlined in the control border. */
  --cta-bg:              #0C111D;
  --cta-bg-hover:        #020618;
  --cta-text:            #FFFFFF;
  --cta-sec-text:        #020618;
  --cta-sec-border:      #CAD5E2;
  --cta-sec-border-hover:#90A1B9;
  --cta-sec-bg-hover:    rgba(51, 86, 238, 0.06);
  --success:    #059669;
  --success-bg: rgba(5, 150, 105, 0.10);
  --warn:       #B45309;
  --warn-bg:    rgba(180, 83, 9, 0.10);
  --error:      #B91C1C;
  --error-bg:   rgba(185, 28, 28, 0.10);
  --shadow-sm: var(--qf-shadow-sm);
  --shadow-md: var(--qf-shadow-md);
  --shadow-lg: var(--qf-shadow-lift);
  --glow-cyan: 0 0 0 1px rgba(51, 86, 238, 0.18), var(--qf-shadow-lift);
}

/* ── Dark theme — opted in explicitly or via OS preference.
   Explicit toggle wins; system-preference applies only when no explicit choice. */
html[data-theme="dark"] {
  --bg:           #0C111D;
  --surface:      #131A28;
  --surface-2:    #1A2333;
  --surface-3:    #232E42;
  --border:        rgba(202, 213, 226, 0.14);
  --border-strong: rgba(202, 213, 226, 0.28);
  --ink:        #F8FAFC;
  --ink-soft:   #E2E8F0;
  --muted:      #90A1B9;
  --muted-soft: #64748B;
  --accent:        #8DA2F9;
  --accent-strong: #B4C2FC;
  --accent-soft:   rgba(51, 86, 238, 0.22);
  --accent-fill:   #3356EE;
  --accent-ink:    #FFFFFF;
  --accent-2:      #8DA2F9;
  --accent-2-soft: rgba(51, 86, 238, 0.18);
  --cta-bg:              #F8FAFC;
  --cta-bg-hover:        #E2E8F0;
  --cta-text:            #0C111D;
  --cta-sec-text:        #F8FAFC;
  --cta-sec-border:      rgba(226, 232, 240, 0.34);
  --cta-sec-border-hover:rgba(226, 232, 240, 0.62);
  --cta-sec-bg-hover:    rgba(226, 232, 240, 0.08);
  --success:    #34D399;
  --success-bg: rgba(52, 211, 153, 0.12);
  --warn:       #F59E0B;
  --warn-bg:    rgba(245, 158, 11, 0.12);
  --error:      #F87171;
  --error-bg:   rgba(248, 113, 113, 0.12);
  --shadow-sm: var(--qf-shadow-sm);
  --shadow-md: var(--qf-shadow-md);
  --shadow-lg: var(--qf-shadow-md);
  --glow-cyan: 0 0 0 1px rgba(141, 162, 249, 0.28), var(--qf-shadow-lift);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:           #0C111D;
    --surface:      #131A28;
    --surface-2:    #1A2333;
    --surface-3:    #232E42;
    --border:        rgba(202, 213, 226, 0.14);
    --border-strong: rgba(202, 213, 226, 0.28);
    --ink:        #F8FAFC;
    --ink-soft:   #E2E8F0;
    --muted:      #90A1B9;
    --muted-soft: #64748B;
    --accent:        #8DA2F9;
    --accent-strong: #B4C2FC;
    --accent-soft:   rgba(51, 86, 238, 0.22);
    --accent-fill:   #3356EE;
    --accent-ink:    #FFFFFF;
    --accent-2:      #8DA2F9;
    --accent-2-soft: rgba(51, 86, 238, 0.18);
    --cta-bg:              #F8FAFC;
    --cta-bg-hover:        #E2E8F0;
    --cta-text:            #0C111D;
    --cta-sec-text:        #F8FAFC;
    --cta-sec-border:      rgba(226, 232, 240, 0.34);
    --cta-sec-border-hover:rgba(226, 232, 240, 0.62);
    --cta-sec-bg-hover:    rgba(226, 232, 240, 0.08);
    --success:    #34D399;
    --success-bg: rgba(52, 211, 153, 0.12);
    --warn:       #F59E0B;
    --warn-bg:    rgba(245, 158, 11, 0.12);
    --error:      #F87171;
    --error-bg:   rgba(248, 113, 113, 0.12);
    --shadow-sm: var(--qf-shadow-sm);
    --shadow-md: var(--qf-shadow-md);
    --shadow-lg: var(--qf-shadow-md);
    --glow-cyan: 0 0 0 1px rgba(141, 162, 249, 0.28), var(--qf-shadow-lift);
  }
}

/* Theme-toggle button in the sidebar — small, low-key, lives next to nav. */
.qf-theme-toggle {
  display: inline-flex; align-items: center; gap: 8px;
  width: 100%; padding: 8px 12px; margin-top: 6px;
  border: 1px solid var(--border); border-radius: 8px;
  background: transparent; color: var(--muted);
  font-size: 12px; font-family: var(--font-mono); letter-spacing: 0.06em;
  text-transform: uppercase; cursor: pointer;
  transition: all .2s ease;
}
.qf-theme-toggle:hover { background: var(--surface-2); color: var(--ink); border-color: var(--border-strong); }

/* ── Header theme toggle — compact icon button for the marketing / directory
   topnav shells. Sun shows in dark (click → light); moon shows in light
   (click → dark). Hairline, low-key, tracks currentColor. ── */
.qf-theme-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; flex: 0 0 auto;
  border: 1px solid var(--border); border-radius: var(--radius-btn);
  background: transparent; color: var(--muted);
  cursor: pointer; opacity: 0.5;
  transition: all .2s ease;
}
.qf-theme-btn:hover { background: var(--surface-2); color: var(--ink); border-color: var(--border-strong); opacity: 1; }
.qf-theme-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; opacity: 1; }
.qf-theme-btn svg { width: 15px; height: 15px; display: block; }
/* Default (light): show moon (click → dark); hide sun. */
.qf-theme-btn .qf-ico-moon { display: block; }
.qf-theme-btn .qf-ico-sun  { display: none; }
html[data-theme="light"] .qf-theme-btn .qf-ico-sun  { display: none; }
html[data-theme="light"] .qf-theme-btn .qf-ico-moon { display: block; }
/* Dark: show sun (click → light); hide moon. */
html[data-theme="dark"] .qf-theme-btn .qf-ico-sun  { display: block; }
html[data-theme="dark"] .qf-theme-btn .qf-ico-moon { display: none; }

/* Lucide-style inline line icons — inherit currentColor so they track the
   theme + active/hover state. One consistent 18px weight across the shell. */
.qf-ico { width: 18px; height: 18px; flex: 0 0 auto; display: inline-block; vertical-align: middle; }
.sidebar .qf-ico { opacity: 0.92; }
.qf-theme-toggle #qf-theme-icon { display: inline-flex; align-items: center; }
.qf-theme-toggle .qf-ico { width: 16px; height: 16px; }
.qf-tool-ico { display: inline-flex; align-items: center; vertical-align: -3px; }
.qf-tool-ico .qf-ico { width: 15px; height: 15px; }

/* ── Reset ─────────────────────────────────────────────────────── */
* { box-sizing: border-box; }

/* MEDIA NEVER SETS THE PAGE WIDTH.
 *
 * The landing page injects `landing-wefixtrades-cleanup.css` from JavaScript
 * (landing-motion.js), so between DOMContentLoaded and that sheet arriving the
 * `.qf-included-media` images have no width rule and lay out at their intrinsic
 * 1280px — inside a 320px viewport. The document then scrolls sideways by
 * ~1016px until the sheet lands. On a phone on a slow connection that is a
 * visible sideways lurch on first paint, and it is the same class of bug for
 * the 1100px `.qf-browser-vid` elements.
 *
 * This sheet is render-blocking on every surface, so the clamp is in effect
 * before any content paints and no late stylesheet is needed to keep the page
 * inside the viewport. It is `max-width`, not `width`, so every explicit sizing
 * rule downstream still wins; it only ever removes overflow. Nothing in the
 * codebase deliberately paints media wider than its container (no `100vw`
 * media rule exists), so there is nothing for it to break. */
img, video, svg, canvas, iframe { max-width: 100%; }
html { height: 100%; } body { min-height: 100%; } /* NEVER `height: 100%` on body: it is the sticky header's containing block, so a one-viewport body unsticks the nav at scrollY = vh - 64 on every page. See THE ROOT HEIGHT RULE in nav-unify.css. */
body {
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--ink-soft);
  margin: 0;
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
}

/* Subtle dot pattern — Effortel uses this on their card backgrounds. */
.dot-grid {
  background-image: rgba(255, 255, 255, 0.04);
  background-size: 22px 22px;
  background-position: 0 0;
}

a { color: var(--accent); text-decoration: none; transition: all .2s ease; }
a:hover { color: var(--accent-strong); }
button { font: inherit; cursor: pointer; border: 0; background: transparent; color: inherit; }
input, textarea, select { font: inherit; color: var(--ink); }
::selection { background: var(--accent-soft); color: var(--ink); }

h1, h2, h3, h4 {
  color: var(--ink);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin: 0;
}
h1 { font-weight: 800; letter-spacing: -0.035em; }

p { margin: 0; }

/* ── Layout containers ─────────────────────────────────────────── */
.container { max-width: 1360px; margin: 0 auto; padding: 0 var(--space-3); }
.container-narrow { max-width: 780px; margin: 0 auto; padding: 0 var(--space-3); }

/* ── Tag — bracketed monospace small-caps label.
     Use: <span class="tag">[ Recognized as the best MVNE globally ]</span>
   ─────────────────────────────────────────────────────────────── */
/* Eyebrow — WeFixTrades style: mono uppercase, letter-spaced, brand accent
   on dark (#8DA2F9). No brackets (that was the old Effortel treatment). */
.tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
  padding: 4px 0;
}
.tag.accent { color: var(--accent); }
.tag.muted  { color: var(--muted); }

/* ── Top nav (minimal — most navigation lives in the bottom dock) ── */
.topnav {
  position: sticky; top: 0; z-index: 60;
  background: rgb(34, 40, 42);
  border-bottom: 1px solid var(--border);
}
.topnav-inner {
  max-width: 1280px; margin: 0 auto; padding: var(--space-1-5) var(--space-3);
  display: flex; align-items: center; gap: 24px;
}
.brand-mark {
  display: inline-flex; align-items: center; gap: var(--space-1);
  font-weight: 700; font-size: 17px; letter-spacing: -0.02em;
  color: var(--ink);
}
.brand-mark:hover { color: var(--ink); }
.brand-mark .logo {
  width: auto; height: auto;
  background: transparent;
  border-radius: 0;
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: none;
}
.brand-mark .logo svg { width: 26px; height: 26px; display: block; }
.qf-brand-mark { display: block; height: 26px; width: auto; }
.brand-mark .logo .qf-brand-mark { height: 26px; width: auto; }
.topnav-spacer { flex: 1; }
.topnav a.nav-link {
  color: var(--muted);
  font-size: 13px; font-weight: 500; padding: var(--space-0-5) 0;
  letter-spacing: 0.05em; text-transform: uppercase;
  font-family: var(--font-mono);
  transition: all .2s ease;
}
.topnav a.nav-link:hover { color: var(--ink); }
.topnav a.nav-link.active { color: var(--ink); }
/* Nav CTA cluster — cream primary + ghost secondary, WeFixTrades spacing. */
.topnav .btn { padding: var(--space-1) var(--space-2); font-size: 13px; }
@media (max-width: 720px) {
  .topnav a.nav-link:not(.always-show) { display: none; }
  /* Ghost secondary hands off to the bottom dock on mobile so the bar
     stays uncrowded at 375px. */
  .topnav .btn-secondary { display: none; }
}

/* ── Display-currency switch (marketing nav) ───────────────────────
   Injected by currency.js into .site-actions (landing) or .topnav-inner
   (pricing/signup/tools). Display only — billing stays USD. Tokens only,
   theme-aware; native select arrow keeps it OS/theme-consistent. */
.qf-currency-switch {
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-btn);
  padding: var(--space-0-5) var(--space-1);
  cursor: pointer;
  transition: all .2s ease;
}
.qf-currency-switch:hover { color: var(--ink); border-color: var(--border-strong); }
.qf-currency-switch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-currency-switch option { color: var(--ink); background: var(--surface); }

/* Footer-mounted currency control (mobile ≤720px) — compact + labeled so the
 * switch stays visible/usable without crowding the mobile header row. */
.qf-currency-footer {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin-top: var(--space-2);
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--muted);
}
.qf-currency-footer-label { color: var(--muted); }

/* "Billed in USD" note — small, muted, shown only for non-USD. */
.qf-usd-note { font-size: 13px; color: var(--muted); }

/* ── Buttons ───────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: var(--space-1-5) var(--space-3);
  font-size: 14px; font-weight: 600;
  font-family: var(--font-sans);
  letter-spacing: 0;
  text-transform: none;
  border-radius: var(--radius-btn);
  border: 1px solid transparent;
  white-space: nowrap;
  transition: all .2s ease;
  cursor: pointer;
}
.btn .arr {
  display: inline-block;
  font-size: 15px; line-height: 1;
  transition: all .2s ease;
}
.btn:hover .arr { transform: translateX(3px); }

/* Primary — WeFixTrades cream CTA (warm off-white fill, dark ink). */
.btn-primary {
  background: var(--cta-bg); color: var(--cta-text);
  border-color: var(--cta-bg);
  box-shadow: var(--qf-shadow-sm);
}
.btn-primary .arr { color: var(--cta-text); }
.btn-primary:hover {
  background: var(--cta-bg-hover); border-color: var(--cta-bg-hover);
  transform: translateY(-1px);
  box-shadow: inset 0 0 0 1.5px rgba(51, 86, 238, 0.45), var(--qf-shadow-lift), var(--qf-shadow-sm);
}
.btn-primary:active { transform: translateY(0) scale(0.985); }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; box-shadow: none; transform: none; }

/* Secondary — ghost / outline (transparent, hairline border, light label). */
.btn-secondary {
  background: transparent; color: var(--cta-sec-text);
  border-color: var(--cta-sec-border);
}
.btn-secondary .arr { color: var(--cta-sec-text); }
.btn-secondary:hover {
  background: var(--cta-sec-bg-hover);
  border-color: var(--cta-sec-border-hover);
  color: var(--cta-sec-text);
}

.btn-ghost {
  background: transparent; color: var(--ink-soft);
  border-color: var(--cta-sec-border);
}
.btn-ghost:hover { background: var(--cta-sec-bg-hover); border-color: var(--cta-sec-border-hover); color: var(--ink); }

.btn-danger {
  background: transparent; color: var(--error); border-color: var(--border-strong);
}
.btn-danger:hover { background: var(--error-bg); border-color: var(--error); }

.btn-sm  { padding: var(--space-1) var(--space-1-5);  font-size: 11px; }
.btn-lg  { padding: var(--space-2) var(--space-3); font-size: 13.5px; }
.btn-block { width: 100%; }
.btn-pill { /* alias — matches default */ }

/* Older inline form buttons that don't want the all-caps treatment */
.btn-plain {
  font-family: var(--font-sans); text-transform: none; letter-spacing: 0;
  font-size: 14px; padding: var(--space-1) var(--space-2);
}

/* ── Cards ─────────────────────────────────────────────────────── */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
  box-shadow: var(--shadow-sm);
  position: relative;
}
.card.compact { padding: var(--space-2); }
.card.elevated { box-shadow: var(--shadow-md); }
.card.glow {
  box-shadow: var(--glow-cyan);
  border-color: rgba(51, 86, 238, 0.30);
}
.card-title {
  font-size: 16px; font-weight: 700; margin: 0 0 4px 0;
  color: var(--ink); letter-spacing: -0.01em;
}
.card-subtitle {
  font-size: 13.5px; color: var(--muted); margin: 0 0 var(--space-2) 0;
}
.card-row {
  display: flex; align-items: center; gap: 12px; justify-content: space-between;
  padding: var(--space-1-5) 0; border-bottom: 1px solid var(--border);
}
.card-row:last-child { border-bottom: 0; }

/* ── Form elements ─────────────────────────────────────────────── */
.field { display: flex; flex-direction: column; gap: var(--space-1); }
.field-label {
  font-family: var(--font-mono);
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--muted);
}
.field-label.required::after { content: ' *'; color: var(--error); }
.input, .select, .textarea {
  width: 100%;
  padding: var(--space-1-5) var(--space-2);
  font-size: 14.5px;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  background: var(--surface-2);
  color: var(--ink);
  transition: all .2s ease;
}
.input::placeholder, .textarea::placeholder { color: var(--muted-soft); }
.input:focus, .select:focus, .textarea:focus {
  outline: 0;
  border-color: var(--accent);
  background: var(--surface);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.textarea { font-family: var(--font-sans); resize: vertical; min-height: 90px; }
.field-hint { font-size: 12px; color: var(--muted); }
.field-hint.error { color: var(--error); }
.field-hint.ok    { color: var(--success); }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2); }
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: var(--space-2); }
@media (max-width: 640px) {
  .grid-2, .grid-3 { grid-template-columns: 1fr; }
}

/* Input "URL builder" — paired field + dropdown that compose into a
   live preview, used at signup for `<slug>.<host>`. */
.url-builder {
  display: grid; grid-template-columns: 1.4fr 1fr; gap: var(--space-1);
}
.url-builder-preview {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--muted);
  background: var(--surface-2);
  border: 1px dashed var(--border-strong);
  border-radius: 10px;
  padding: var(--space-1) var(--space-2);
  margin-top: 8px;
}
.url-builder-preview .live { color: var(--accent); }
@media (max-width: 640px) {
  .url-builder { grid-template-columns: 1fr; }
}

/* ── Tables ──────────────────────────────────────────────────────
   Compact sizing — earlier we had 12px row padding + 11px input padding
   stacking up, which made the rate-card grid feel airy and forced lots
   of vertical scroll for ~10 rows. Tighter padding + smaller in-cell
   inputs lets the operator scan + edit ~16 rows at a time without
   scrolling. */
.table {
  width: 100%; border-collapse: collapse; font-size: 13.5px;
  background: var(--surface); border-radius: var(--radius-lg); overflow: hidden;
}
.table th, .table td {
  text-align: left; padding: 6px 12px;
  border-bottom: 1px solid var(--border);
  color: var(--ink-soft);
  vertical-align: middle;
}
.table thead th {
  background: var(--surface-2);
  font-family: var(--font-mono);
  font-size: 11px; font-weight: 600; letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--muted);
  padding: 8px 12px;
}
.table tbody tr:hover { background: var(--surface-2); }
/* Mobile: wide multi-column tables scroll inside their own box instead of
   forcing the page wider than the viewport. Lives here (shared base) so the
   admin shell — which doesn't load dashboard-polish.css — is covered too.
   Mirrors the app dashboard's 900px rule. */
@media (max-width: 900px) {
  .table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
}
/* Inside-table inputs: lighter padding so a row stays single-line height. */
.table .input, .table .select, .table .textarea {
  padding: 6px 10px;
  font-size: 13px;
  border-radius: 6px;
}

/* Service tabs (rate cards page, eventually re-used for accessorials/zones). */
.qf-tabs {
  display: flex; flex-wrap: wrap; gap: 4px;
  margin: 14px 0 0 0;
  border-bottom: 1px solid var(--border);
  padding-bottom: 0;
}
.qf-tab {
  padding: 8px 14px;
  font-family: var(--font-mono); font-size: 11.5px;
  letter-spacing: 0.08em; text-transform: uppercase;
  border: 1px solid transparent; border-bottom: none;
  border-radius: 8px 8px 0 0;
  background: transparent; color: var(--muted);
  cursor: pointer; margin-bottom: -1px;
  transition: all .2s ease;
}
.qf-tab:hover { color: var(--ink); background: var(--surface-2); }
.qf-tab.active {
  color: var(--accent);
  border-color: var(--border); border-bottom-color: var(--surface);
  background: var(--surface);
}

/* ── Compliance trust strip ─────────────────────────────────────
   Shown on landing + repeated in footer + a thin variant under the
   header CTA. We deliberately use generic shield/lock icons (NOT
   official AICPA / ISO logos) — those marks cannot lawfully be shown
   without an active attestation. The chip text itself carries the
   "live" vs "in progress" status; tooltip on hover explains. */
.qf-trust { background: var(--surface-2); }
.qf-trust-link { display: inline-block; text-decoration: none; color: var(--ink); margin-bottom: 12px; }
.qf-trust-headline {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 14px; color: var(--ink-soft);
  font-family: var(--font-mono); letter-spacing: 0.04em;
}
.qf-trust-headline svg { color: var(--accent); }
.qf-trust-row {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 8px;
  margin: var(--space-1) 0 var(--space-1);
}
.qf-trust-chip {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: var(--space-0-5) var(--space-1-5); border-radius: 999px;
  font-family: var(--font-mono); font-size: 11px;
  letter-spacing: 0.04em; text-transform: uppercase;
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--muted);
  cursor: help;
}
.qf-trust-chip.live { color: var(--success); border-color: rgba(52, 211, 153, 0.3); }
.qf-trust-chip.live svg { color: var(--success); }
.qf-trust-chip.wip { color: var(--muted); border-style: dashed; }
.qf-trust-foot { font-size: 12.5px; color: var(--muted); margin-top: var(--space-1); }
.qf-trust-foot a { color: var(--accent); }

/* Subtle mini-strip directly under the top nav. One-line, scrollable
   horizontally on narrow screens. Not a section — just a thin band. */
.qf-trust-mini {
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}
.qf-trust-mini-inner {
  max-width: 1200px; margin: 0 auto;
  padding: var(--space-1) var(--space-3);
  display: flex; align-items: center; gap: var(--space-1);
  overflow-x: auto; white-space: nowrap;
  font-family: var(--font-mono); font-size: 10.5px;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--muted);
}
.qf-trust-mini-inner svg { color: var(--accent); flex-shrink: 0; }
.qf-trust-mini-inner a { color: var(--muted); text-decoration: none; }
.qf-trust-mini-inner a:hover { color: var(--ink); }
.qf-trust-mini-sep { color: var(--border-strong); }
.qf-trust-mini-tag { color: var(--success); }
.qf-trust-mini-tag.wip { color: var(--muted); border-bottom: 1px dashed var(--border-strong); padding-bottom: 1px; }

/* ── Badges ────────────────────────────────────────────────────── */
.badge {
  display: inline-block;
  padding: var(--space-0-5) var(--space-1);
  font-family: var(--font-mono);
  font-size: 10.5px; font-weight: 600;
  letter-spacing: 0.10em; text-transform: uppercase;
  border-radius: 4px;
}
.badge-success { background: var(--success-bg); color: var(--success); }
.badge-warn    { background: var(--warn-bg);    color: var(--warn); }
.badge-error   { background: var(--error-bg);   color: var(--error); }
.badge-muted   { background: var(--surface-2);  color: var(--muted); }
.badge-info    { background: var(--accent-soft);color: var(--accent); }

/* ── Tabs ──────────────────────────────────────────────────────── */
.tabs {
  display: flex; gap: 4px; border-bottom: 1px solid var(--border);
  margin-bottom: var(--space-3); overflow-x: auto;
}
.tab {
  padding: var(--space-1-5) var(--space-2); font-size: 13px; font-weight: 600;
  font-family: var(--font-mono);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted);
  border-bottom: 2px solid transparent;
  background: transparent; white-space: nowrap;
}
.tab:hover { color: var(--ink-soft); }
.tab.active { color: var(--accent); border-bottom-color: var(--accent); }
.tab-pane { display: none; }
.tab-pane.active { display: block; }

/* ── Hero (Cargowise-inspired bold heading + cyan halo) ─────── */
.hero {
  position: relative;
  padding: 96px 28px 80px;
  text-align: center;
  overflow: hidden;
}.hero > * { position: relative; }
.hero .tag { margin-bottom: var(--space-3); }
.hero h1 {
  font-size: 64px; font-weight: 800; letter-spacing: -0.04em;
  margin: 0 0 var(--space-3) 0; line-height: 1.05;
  color: var(--ink);
}
.hero h1 .accent {
  /* AA-safe accent span on dark — stays in the #6E8BFF→#93A9FF range so the
     headline word never drops below contrast (brand-blue #0D3CFC as text on
     near-black fails AA). */
  background: #93A9FF;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}
html[data-theme="light"] .hero h1 .accent {
  background: var(--accent-fill);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}
.hero p.lead {
  font-size: 18px; color: var(--muted);
  max-width: 660px; margin: 0 auto 38px auto; line-height: 1.6;
}
.hero-cta { display: inline-flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
.hero-meta {
  margin-top: var(--space-4);
  display: inline-flex; gap: var(--space-3); flex-wrap: wrap; justify-content: center;
  font-family: var(--font-mono); font-size: 11.5px;
  letter-spacing: 0.10em; text-transform: uppercase; color: var(--muted-soft);
}
.hero-meta span::before { content: '✓ '; color: var(--accent); }
@media (max-width: 720px) {
  .hero { padding: 64px 22px 56px; }
  .hero h1 { font-size: 40px; }
  .hero p.lead { font-size: 16px; }
}

/* ── Section header ────────────────────────────────────────────── */
.section { padding: 72px 0; position: relative; }
.section-head {
  max-width: 760px; margin: 0 auto 48px auto; text-align: center;
}
.section-head .tag { margin-bottom: var(--space-2); }
.section-head h2 {
  font-size: 38px; font-weight: 700;
  letter-spacing: -0.025em; margin-bottom: var(--space-2);
}
.section-head p {
  font-size: 16px; color: var(--muted); max-width: 640px; margin: 0 auto;
}

/* ── Feature grid ──────────────────────────────────────────────── */
.features {
  display: grid; gap: var(--space-2);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.feature-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
  position: relative;
  transition: all .2s ease;
  overflow: hidden;
}.feature-card:hover {
  border-color: rgba(51, 86, 238, 0.35);
  box-shadow: var(--qf-shadow-lift);
}
.feature-card .icon {
  width: 38px; height: 38px; border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent);
  margin-bottom: var(--space-2);
  position: relative;
}
.feature-card h3 { margin: 0 0 8px 0; font-size: 17px; font-weight: 700; color: var(--ink); }
.feature-card p { margin: 0; color: var(--muted); font-size: 14px; line-height: 1.55; }
.feature-card .stat {
  margin-top: 16px;
  font-family: var(--font-mono);
  font-size: 11.5px; letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--accent); display: inline-flex; align-items: center; gap: var(--space-1);
}

/* ── Compare table (TMX/DrayMaster vs. us) ─────────────────────── */
.compare {
  width: 100%; border-collapse: separate; border-spacing: 0;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); overflow: hidden; font-size: 14px;
}
.compare th, .compare td {
  padding: var(--space-2); text-align: left;
  border-bottom: 1px solid var(--border);
}
.compare thead th {
  font-family: var(--font-mono);
  font-size: 11px; letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--muted); background: var(--surface-2);
}
.compare thead th.us { color: var(--accent); }
.compare td.us { color: var(--ink); font-weight: 500; }
.compare td.us::before { content: '✓ '; color: var(--accent); font-weight: 700; }
.compare td.them { color: var(--muted); }
.compare tbody tr:last-child td { border-bottom: 0; }

/* ── Bottom dock nav ─────────────────────────────────────────── */
.dock {
  position: fixed; left: 50%; bottom: 24px;
  transform: translateX(-50%);
  z-index: 70;
  display: flex; gap: 4px;
  background: rgb(27, 33, 35);
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  padding: var(--space-1);
  box-shadow: var(--shadow-lg);
}
.dock-item {
  display: inline-flex; align-items: center; gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  font-family: var(--font-mono);
  font-size: 11.5px; font-weight: 600;
  letter-spacing: 0.10em; text-transform: uppercase;
  color: var(--ink-soft);
  border-radius: var(--radius-btn);
  background: transparent;
  cursor: pointer;
  text-decoration: none;
}
.dock-item:hover { background: rgba(255, 255, 255, 0.04); color: var(--ink); }
.dock-item.active { background: var(--surface-3); color: var(--ink); }
.dock-item.cta {
  background: var(--cta-bg); color: var(--cta-text);
  margin-left: 4px;
}
.dock-item.cta:hover { background: var(--cta-bg-hover); }
.dock-item .plus { font-weight: 800; opacity: 0.7; }
.dock-item.up {
  width: 36px; padding: var(--space-1) 0; justify-content: center; color: var(--ink);
  background: var(--surface-3);
}

/* Tray that opens above the dock when an item is clicked. */
.dock-tray {
  position: fixed; left: 50%; bottom: 86px;
  transform: translateX(-50%);
  z-index: 69;
  width: min(92vw, 880px);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xl);
  padding: var(--space-3);
  box-shadow: var(--shadow-lg);
  display: none;
}
.dock-tray.open { display: block; }
.dock-tray-grid {
  display: grid; gap: var(--space-2);
  grid-template-columns: 1fr 1fr;
}
@media (max-width: 640px) {
  .dock-tray-grid { grid-template-columns: 1fr; }
}
.dock-tray-card {
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-2) var(--space-3);
  text-decoration: none; color: var(--ink-soft);
  transition: all .2s ease;
  display: block;
}
.dock-tray-card:hover {
  border-color: var(--accent);
}
.dock-tray-card h4 {
  margin: 0 0 4px 0; font-size: 15.5px; font-weight: 700; color: var(--ink);
}
.dock-tray-card p { margin: 0; font-size: 13px; color: var(--muted); line-height: 1.5; }

@media (max-width: 720px) {
  .dock { gap: 0; padding: 4px; }
  .dock-item { padding: var(--space-1) var(--space-1); font-size: 10.5px; letter-spacing: 0.06em; }
  .dock-item.up { width: 30px; }
}

/* ── Pricing ───────────────────────────────────────────────────── */
.pricing {
  display: grid; gap: var(--space-3); grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  margin: 24px 0;
}
.price-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-4) var(--space-3);
  position: relative;
}
.price-card.featured {
  border-color: rgba(51, 86, 238, 0.40);
  box-shadow: var(--glow-cyan);
}
.price-card.featured::after {
  content: 'Most popular';
  position: absolute; top: -8px; left: 50%; transform: translateX(-50%);
  background: var(--accent-fill); color: var(--accent-ink);
  font-family: var(--font-mono); font-size: 10.5px;
  padding: 4px 12px; border-radius: 999px;
  font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
}
.price-card h3 { margin: 0; font-size: 16px; color: var(--ink); }
.price-card .price {
  font-size: 42px; font-weight: 800; color: var(--ink); margin: var(--space-2) 0;
  letter-spacing: -0.03em;
}
.price-card .price .per { font-size: 14px; color: var(--muted); font-weight: 500; }
.price-card ul { list-style: none; padding: 0; margin: 0 0 var(--space-3) 0; }
.price-card li {
  font-size: 13.5px; color: var(--ink-soft); padding: var(--space-1) 0;
  display: flex; gap: var(--space-1); align-items: flex-start;
}
.price-card li::before { content: '✓'; color: var(--accent); font-weight: 700; flex-shrink: 0; }

/* ── App shell (dashboard) ─────────────────────────────────────── */
.app-shell {
  display: grid;
  grid-template-columns: 240px 1fr;
  min-height: 100vh;
  background: var(--bg);
}
.sidebar {
  background: var(--surface);
  color: var(--ink-soft);
  padding: 18px 0;
  position: sticky; top: 0; height: 100vh;
  overflow-y: auto;
  border-right: 1px solid var(--border);
}
.sidebar .brand-block {
  padding: 6px 20px 18px 20px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 14px;
}
.sidebar .brand-block .name { font-weight: 700; font-size: 16px; color: var(--ink); min-width: 0; overflow-wrap: anywhere; word-break: break-word; }
.sidebar .brand-block .sub { font-size: 11.5px; color: var(--muted); margin-top: 4px; min-width: 0; max-width: 100%; white-space: normal; overflow-wrap: anywhere; word-break: break-word; }
.sidebar a, .sidebar button {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 20px;
  min-height: 44px; box-sizing: border-box; /* min touch target (was ~40px) */
  color: var(--muted); font-size: 13px; font-weight: 600;
  font-family: var(--font-mono); letter-spacing: 0.06em; text-transform: uppercase;
  text-decoration: none; cursor: pointer;
  width: 100%; text-align: left;
  background: transparent; border: 0;
  border-left: 3px solid transparent;
}
.sidebar a:hover, .sidebar button:hover {
  background: var(--surface-2); color: var(--ink); text-decoration: none;
}
.sidebar a.active, .sidebar button.active {
  background: var(--surface-2); color: var(--accent);
  border-left-color: var(--accent);
}
/* New-item count pill on Leads / Callbacks nav — subtle brand-blue,
   readable in both themes. Pushed to the row's right edge. */
.sidebar .nav-item .qf-nav-badge {
  margin-left: auto;
  min-width: 18px;
  height: 18px;
  padding: 0 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 9px;
  background: var(--accent-fill);
  color: var(--accent-ink);
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0;
  line-height: 1;
}
.sidebar .nav-item .qf-nav-badge[hidden] { display: none; }
.sidebar .group {
  font-family: var(--font-mono);
  font-size: 10px; color: var(--muted-soft);
  letter-spacing: 0.10em; text-transform: uppercase;
  padding: 18px 20px 8px 20px;
}
.app-main {
  padding: 32px 36px; min-width: 0; max-width: 1200px;
}
.app-main h1 {
  font-size: 27px; margin: 0 0 4px 0; letter-spacing: -0.032em; font-weight: 800;
}
.app-main h2 {
  font-size: 17px; margin: 0 0 14px 0; font-weight: 700;
}
.app-main .page-sub { color: var(--muted); margin: 0 0 26px 0; }
@media (max-width: 800px) {
  .app-shell { grid-template-columns: 1fr; }
  .sidebar { position: static; height: auto; }
  .app-main { padding: 22px; }
}

/* ── Inline AI chat panel ──────────────────────────────────────── */
.chat-panel {
  display: flex; flex-direction: column; gap: 12px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 18px;
  height: 480px; overflow: hidden;
}
.chat-messages {
  flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 12px;
  padding-right: 4px;
}
.chat-bubble {
  padding: 11px 15px; border-radius: 14px; max-width: 85%;
  font-size: 13.5px; line-height: 1.55; white-space: pre-wrap;
}
.chat-bubble.user {
  align-self: flex-end; background: var(--accent-fill); color: var(--accent-ink);
  border-bottom-right-radius: 4px;
}
.chat-bubble.assistant {
  align-self: flex-start; background: var(--surface-2); color: var(--ink-soft);
  border-bottom-left-radius: 4px;
  border: 1px solid var(--border);
}
.chat-bubble.tool {
  align-self: flex-start; font-size: 12px; color: var(--muted);
  font-family: var(--font-mono); background: transparent;
  padding: 4px 0;
}
.chat-input-row { display: flex; gap: 8px; align-items: flex-end; }
.chat-input-row .input { flex: 1; }

/* ── Misc ──────────────────────────────────────────────────────── */
.muted        { color: var(--muted); }
.muted-small  { color: var(--muted); font-size: 12.5px; }
.flex         { display: flex; gap: 12px; align-items: center; }
.flex-end     { display: flex; gap: 12px; justify-content: flex-end; align-items: center; }
.spacer       { flex: 1; }
.code {
  font-family: var(--font-mono); font-size: 12.5px;
  background: var(--surface-2); padding: 14px; border-radius: 10px;
  border: 1px solid var(--border); white-space: pre-wrap; word-break: break-all;
  color: var(--ink-soft);
}
.notice {
  padding: 12px 16px; border-radius: 10px;
  background: var(--accent-soft); color: var(--accent);
  font-size: 13.5px;
  border: 1px solid rgba(51, 86, 238, 0.22);
}
.notice.warn  { background: var(--warn-bg);  color: var(--warn);  border-color: rgba(245, 158, 11, 0.20); }
.notice.error { background: var(--error-bg); color: var(--error); border-color: rgba(248, 113, 113, 0.20); }
.divider { height: 1px; background: var(--border); margin: var(--space-2) 0; }
.hidden  { display: none !important; }

/* ── Loading dots ─────────────────────────────────────────────── */
.dots { display: inline-block; }
.dots::after { content: ''; animation: dots 1.4s infinite; display: inline-block; }
@keyframes dots {
  0%, 20% { content: '.'; }
  40%     { content: '..'; }
  60%, 100% { content: '...'; }
}

/* ── Footer — WeFixTrades dark surface (#22282a), warm muted links,
   hairline top border. Copy uses Satoshi; the legal/compliance strip keeps
   the mono treatment for the small-caps utility look. ─────────────── */
.site-footer {
  padding: 44px 28px 104px;
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  margin-top: 72px;
  font-family: var(--font-sans);
  font-size: 12.5px;
  letter-spacing: 0.02em; text-transform: none;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.55);
  text-align: center;
}
.site-footer a {
  color: rgba(255, 255, 255, 0.62);
  display: inline-block; margin: 0 var(--space-1); padding: var(--space-0-5) 0;
  text-decoration: none;
  transition: all .2s ease;
}
.site-footer a:hover { color: var(--ink); }
/* Company / product line + any explicitly muted note stays subtler. */
.site-footer strong { color: rgba(255, 255, 255, 0.78); font-weight: 600; }

/* ── AI rate-change proposal card (confirm-before-apply, audit H1) ───────
   Rendered inside the rate-chat message list. A structured before→after diff
   the owner must Apply before it touches live pricing. Theme-aware tokens
   only; 8px spacing ramp; readable at 375px. */
.qf-rate-proposal {
  align-self: stretch;
  display: flex; flex-direction: column; gap: var(--space-1-5);
  background: var(--surface-2); border: 1px solid var(--border-strong);
  border-radius: var(--radius); padding: var(--space-2);
}
.qf-rp-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
.qf-rp-badge {
  display: inline-flex; align-items: center;
  padding: var(--space-0-5) var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--accent-soft); color: var(--accent);
  font-size: 11px; font-weight: 700; letter-spacing: 0.02em;
  text-transform: uppercase;
}
.qf-rp-title { color: var(--ink); font-weight: 700; font-size: 14px; line-height: 1.4; }
.qf-rp-changes {
  display: flex; flex-direction: column; gap: var(--space-1);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-btn); padding: var(--space-1-5);
}
.qf-rp-change {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: var(--space-1);
}
.qf-rp-field { color: var(--muted); font-size: 12.5px; line-height: 1.5; font-weight: 600; }
.qf-rp-values {
  display: inline-flex; align-items: baseline; flex-wrap: wrap;
  gap: var(--space-1); font-size: 13px; line-height: 1.5;
}
.qf-rp-from { color: var(--muted); text-decoration: line-through; }
.qf-rp-arrow { color: var(--muted-soft); font-weight: 700; }
.qf-rp-to { color: var(--ink); font-weight: 700; }
.qf-rp-reason { color: var(--muted); font-size: 12px; line-height: 1.5; font-style: italic; }
.qf-rp-foot {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: flex-end; gap: var(--space-1);
}
.qf-rp-status {
  display: inline-flex; align-items: center;
  padding: var(--space-0-5) var(--space-1);
  border-radius: var(--radius-pill); font-size: 12px; font-weight: 600;
}
.qf-rp-status-applied  { background: var(--success-bg); color: var(--success); }
.qf-rp-status-discarded { background: var(--surface-3); color: var(--muted); }
.qf-rp-status-invalid  { background: var(--error-bg); color: var(--error); }
.qf-rate-proposal[data-status="applied"]   { border-color: var(--success); }
.qf-rate-proposal[data-status="discarded"] { opacity: 0.72; }

/* ── Copilot Phase-2: inline field-prefill (useCopilotForm parity) ───────
   The copilot writes proposed values into the REAL on-screen inputs; this
   pending highlight + the .qf-fill-review Confirm/Undo card make the change
   obviously reviewable before it's kept. Theme-aware tokens only; the ring
   uses box-shadow (not a spacing/border prop) so it reads in both themes. */
.qf-copilot-pending {
  border-color: var(--accent) !important;
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.qf-fill-review {
  align-self: stretch;
  display: flex; flex-direction: column; gap: var(--space-1-5);
  background: var(--surface-2); border: 1px solid var(--accent);
  border-radius: var(--radius); padding: var(--space-2);
}
.qf-fr-head { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-1); }
.qf-fr-badge {
  display: inline-flex; align-items: center;
  padding: var(--space-0-5) var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--accent-soft); color: var(--accent);
  font-size: 11px; font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase;
}
.qf-fr-title { color: var(--ink); font-weight: 700; font-size: 13.5px; line-height: 1.4; }
.qf-fr-changes {
  display: flex; flex-direction: column; gap: var(--space-1);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-btn); padding: var(--space-1-5);
}
.qf-fr-change {
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: space-between; gap: var(--space-1);
}
.qf-fr-field { color: var(--muted); font-size: 12.5px; line-height: 1.5; font-weight: 600; }
.qf-fr-values {
  display: inline-flex; align-items: baseline; flex-wrap: wrap;
  gap: var(--space-1); font-size: 13px; line-height: 1.5;
}
.qf-fr-from { color: var(--muted); text-decoration: line-through; }
.qf-fr-arrow { color: var(--muted-soft); font-weight: 700; }
.qf-fr-to { color: var(--ink); font-weight: 700; }
.qf-fr-foot {
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: flex-end; gap: var(--space-1);
}
.qf-fr-status {
  display: inline-flex; align-items: center;
  padding: var(--space-0-5) var(--space-1);
  border-radius: var(--radius-pill); font-size: 12px; font-weight: 600;
}
.qf-fr-status-confirmed { background: var(--success-bg); color: var(--success); }
.qf-fr-status-undone    { background: var(--surface-3); color: var(--muted); }
.qf-fill-review[data-status="confirmed"] { border-color: var(--success); }
.qf-fill-review[data-status="undone"]    { opacity: 0.72; }

/* ── Admin overview stat tiles ─────────────────────────────────────
   The admin dashboard's overview counts (Tenants / Users / Total leads /
   MRR) live in `.features` cells with the class `.feature`. Give those
   tiles real card chrome — surface + hairline + radius + padding — so they
   read as an intentional, consistent set instead of flat text. The parent
   grid already stretches cells (default align-items), so the taller MRR
   tile sets the row height and the counts match it. Non-interactive by
   design (no hover lift), and a plain block container so each tile's own
   internal spacing (e.g. the MRR breakdown lines) is left intact. Tokens
   only. */
.feature {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-3);
}

/* ── Get paid (Stripe Connect Express onboarding) ──────────────────
   The Account-page "Connect a way to get paid" card. `.qf-getpaid-options`
   is a responsive grid that holds ONE provider today (Stripe) and drops a
   second (PayPal) right beside it later with no layout change — auto-fit
   columns wrap to a single column on narrow / 375px screens. Each option is
   a self-contained tile (surface + hairline + radius) with a brand row, a
   status pill, and an action button. Tokens + 8px ramp only; theme-aware
   via var(--*), so light + dark both read correctly. */
.qf-getpaid-options {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-2);
}
.qf-getpaid-option {
  display: flex;
  flex-direction: column;
  gap: var(--space-1-5);
  align-items: flex-start;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-2);
}
.qf-getpaid-option.is-soon {
  background: transparent;
  border-style: dashed;
  opacity: 0.7;
}
.qf-getpaid-brand {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}
.qf-getpaid-logo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--accent);
}
.qf-getpaid-name {
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.qf-getpaid-desc {
  font-size: 12px;
  color: var(--muted);
  line-height: 1.4;
}
.qf-getpaid-status {
  display: inline-flex;
  align-items: center;
  padding: var(--space-0-5) var(--space-1);
  border-radius: var(--radius-pill);
  font-size: 12px;
  font-weight: 600;
}
.qf-getpaid-status.is-ready {
  background: var(--success-bg);
  color: var(--success);
}
.qf-getpaid-status.is-pending {
  background: var(--warn-bg, var(--surface-3));
  color: var(--warn, var(--muted));
}
.qf-getpaid-status.is-off {
  background: var(--surface-3);
  color: var(--muted);
}
.qf-getpaid-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-top: var(--space-0-5);
}
@media (max-width: 480px) {
  .qf-getpaid-options { grid-template-columns: 1fr; }
  .qf-getpaid-actions .btn { width: 100%; justify-content: center; }
}

/* Email-provenance chip — marks an ingest job that arrived by forwarded email
   (vs a manual upload). Brand-tinted, theme-aware; sits inline in the review
   header and the ingest queue's File column. Appended at EOF so the line-based
   spacing/color baselines above don't renumber. */
.qf-email-badge {
  display: inline-flex; align-items: center; gap: var(--space-0-5);
  padding: var(--space-0-5) var(--space-1);
  background: var(--accent-soft); color: var(--accent);
  font-size: 11px; font-weight: 600;
  border-radius: 999px; white-space: nowrap;
}

/* ── Floating AI copilot bubble (portal-wide) ─────────────────────
   Persistent launcher + docked chat popover on every authed portal route.
   Reuses the owner rate-chat surface (.chat-panel) inside .qf-copilot-panel.
   Mounted in #app-shell so it survives SPA route swaps. Theme-aware via tokens;
   both light + dark are covered because every color is a var(--*). */
.qf-copilot-launcher {
  position: fixed; right: 24px; bottom: 24px; z-index: 1200;
  width: 56px; height: 56px; border-radius: var(--radius-pill);
  display: flex; align-items: center; justify-content: center;
  background: var(--cta-bg); color: var(--cta-text);
  border: 1px solid var(--cta-bg);
  box-shadow: var(--shadow-md); cursor: pointer;
  transition: all .2s ease;
}
.qf-copilot-launcher:hover {
  background: var(--cta-bg-hover); border-color: var(--cta-bg-hover);
box-shadow: var(--shadow-lg);
}
.qf-copilot-launcher:active { transform: translateY(0) scale(0.96); }
.qf-copilot-launcher:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.qf-copilot-launcher .qf-ico { width: 26px; height: 26px; }

/* Backdrop — invisible on desktop (the popover is non-blocking), dimmed on
   mobile where the panel becomes a bottom sheet. */
.qf-copilot-scrim {
  position: fixed; inset: 0; z-index: 1190;
  background: rgba(8, 11, 20, 0.5);
  opacity: 0; visibility: hidden;
  transition: all .2s ease;
}

.qf-copilot-panel {
  position: fixed; right: 24px; bottom: calc(56px + var(--space-3) + var(--space-1));
  z-index: 1200;
  width: 384px; max-width: calc(100vw - 48px);
  height: 560px; max-height: calc(100vh - 120px);
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  overflow: hidden;
  opacity: 0; transform: translateY(12px) scale(0.98);
  transform-origin: bottom right;
  transition: all .2s ease;
}
.qf-copilot-panel.is-open { opacity: 1; transform: translateY(0) scale(1); }

.qf-copilot-head {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-1); padding: var(--space-1-5) var(--space-2);
  border-bottom: 1px solid var(--border); background: var(--surface-2);
}
.qf-copilot-title {
  display: flex; align-items: center; gap: var(--space-1);
  font-weight: 700; font-size: 14px; color: var(--ink);
}
.qf-copilot-spark { display: inline-flex; color: var(--accent); }
.qf-copilot-spark .qf-ico { width: 18px; height: 18px; }
.qf-copilot-close {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; border-radius: var(--radius-btn);
  background: transparent; border: 1px solid transparent; color: var(--muted);
  cursor: pointer; transition: all .2s ease;
}
.qf-copilot-close:hover { background: var(--surface-3); color: var(--ink); }
.qf-copilot-close:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.qf-copilot-close .qf-ico { width: 18px; height: 18px; }

/* The shared chat surface fills the popover — override the standalone card's
   fixed 480px box, border, and radius so it docks flush inside the panel. */
.qf-copilot-panel .qf-copilot-chat {
  flex: 1 1 auto; min-height: 0; height: auto;
  border: 0; border-radius: 0; background: transparent;
  padding: var(--space-2);
}

@media (max-width: 640px) {
  .qf-copilot-scrim { opacity: 0; }
  body.qf-copilot-open .qf-copilot-scrim { opacity: 1; visibility: visible; }
  .qf-copilot-panel {
    right: 0; left: 0; bottom: 0; top: auto;
    width: 100%; max-width: 100%;
    height: 88vh; max-height: 88vh;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    border-left: 0; border-right: 0; border-bottom: 0;
    transform-origin: bottom center;
    transform: translateY(100%);
  }
  .qf-copilot-panel.is-open { transform: translateY(0); }
  .qf-copilot-launcher { right: 16px; bottom: 16px; }
}

/* ── Mobile hamburger for the .topnav shell (directory / glossary / services /
   compliance and other secondary pages). Appended at EOF to avoid shifting the
   line-based guard baselines. On mobile the .nav-links above are display:none;
   without this they simply vanish with no way to reach them. The burger toggles a
   dropdown that reveals them. Theme-aware via --surface (adapts light/dark). */
.topnav-burger { display: none; }
.topnav-mobile { display: none; }
@media (max-width: 720px) {
  .topnav-burger {
    display: inline-flex; align-items: center; justify-content: center;
    width: 40px; height: 40px; padding: 0; margin-left: var(--space-1); flex: none;
    background: transparent; border: 1px solid var(--border);
    border-radius: var(--radius-btn); color: var(--ink); cursor: pointer;
  }
  .topnav-burger svg { width: 20px; height: 20px; display: block; }
  .topnav-burger .ico-close { display: none; }
  .topnav-burger[aria-expanded="true"] .ico-open { display: none; }
  .topnav-burger[aria-expanded="true"] .ico-close { display: block; }
  .topnav-mobile:not([hidden]) {
    display: flex; flex-direction: column; gap: var(--space-0-5);
    position: absolute; top: 100%; left: var(--space-1-5); right: var(--space-1-5);
    margin-top: var(--space-1); padding: var(--space-1);
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius-lg); box-shadow: var(--qf-shadow-md);
    z-index: 70;
  }
  .topnav-mobile a {
    font-family: var(--font-mono); font-size: 13px; font-weight: 500;
    text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--ink-soft); text-decoration: none;
    padding: var(--space-1-5) var(--space-2); border-radius: var(--radius-btn);
    transition: all .2s ease;
  }
  .topnav-mobile a:hover, .topnav-mobile a:focus-visible {
    color: var(--accent); background: var(--accent-soft);
  }
  .topnav-mobile .tn-cta { color: var(--accent); font-weight: 700; }
  /* On shells that carry the burger menu, the "List your company" CTA lives inside
     the dropdown (.tn-cta), so hide the inline button on mobile — keeps the bar to
     brand + burger and prevents overflow at 375px. Scoped to .topnav--mobile-menu
     so plain .topnav pages (login, marketplace) keep their always-show CTA. */
  .topnav--mobile-menu .btn.always-show { display: none; }
}

/* ── Light-theme shell overrides ──────────────────────────────────────
   The marketing / directory topnav and the site-footer bake in dark
   translucent backgrounds and white text. Under data-theme="light" they
   must read as clean white surfaces with navy ink. These selectors are
   [data-theme]-scoped, so the hardcoded-color guard exempts them, and they
   live at EOF so the line-based guard baselines above stay stable. */
html[data-theme="light"] .topnav {
  background: rgba(255, 255, 255, 0.78);
}
html[data-theme="light"] .site-footer { color: var(--muted); }
html[data-theme="light"] .site-footer a { color: var(--ink-soft); }
html[data-theme="light"] .site-footer a:hover { color: var(--accent); }
html[data-theme="light"] .site-footer strong { color: var(--ink); }

/* ── Footer — mobile tightening (Alex: "too big empty space in the footer area
   of the mobile version"). The base .site-footer carries generous marketing
   spacing — a 72px top gap and a tall 104px bottom padding — that reads as a
   dead band on phones (a ~110px empty strip below the © / links row). Trim it
   on ≤640px only; desktop marketing spacing is untouched. Values on the 8px
   ramp. Directory pages tighten the top gap further to 16px in DIRECTORY_CSS
   (directory/pages.ts) so every directory footer matches the profile. ─────── */
@media (max-width: 640px) {
  .site-footer { margin-top: 32px; padding: 24px 16px 32px; }
}

/* ── Tools topnav CTA — narrow-phone overflow fix ──────────────────────────
   At ≤430px the always-show primary CTA ("Free for carriers →") plus the brand
   + theme toggle overran the sticky bar by ~30px at 375. Tighten the row gap
   and collapse the CTA's secondary label to just "Free →" — the button stays
   fully tappable and keeps its primary meaning, with 0 horizontal overflow.
   Appended at EOF to keep the line-based spacing/color baselines stable. */
@media (max-width: 430px) {
  .topnav-inner { gap: var(--space-1-5); }
  .topnav .topnav-cta .topnav-cta-label { display: none; }
}

/* Affiliate tier select — keep the tier label visible (not caret-only) on
   narrow/mobile widths where the admin affiliates table scrolls horizontally. */
.table .select.aff-tier {
  min-width: 96px;
}

/* ══ Footer accepted-payment + trust strip (FOOTER_PAY_ROW) ═══════════════════
   The very-bottom strip in siteChrome.ts's PREMIUM_FOOTER, landing.html and the
   directory subsite footer. ONE rule set for all three: style.css is the only
   sheet every one of those surfaces loads.

   MONOCHROME BY TOKEN, not by two hardcoded colours. The payment marks inherit
   `currentColor` from `--ink` — near-white (#F9F9F9) on the dark theme, deep
   navy (#0A2540) under html[data-theme="light"] — so they invert with the theme
   from a single declaration. Marks are inline SVG: no external request, nothing
   for the CSP to block, and they stay crisp at any DPR.

   Appended at EOF so the LINE-NUMBER-keyed spacing/colour guard baselines don't
   renumber (same convention as landing-footer-polish.css). All spacing uses the
   --space-* ramp, so check-spacing.mjs skips it as token usage. */
.qf-footer-payrow {
  max-width: 1120px;
  margin: var(--space-3) auto 0;
  padding: var(--space-2) 0 0;
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-1-5) var(--space-3);
}
.qf-payrow-methods {
  display: flex;
  align-items: center;
  gap: var(--space-1-5);
  /* Never let the label+marks group be the thing that forces a scrollbar. */
  min-width: 0;
}
.qf-payrow-label {
  font-size: 11.5px;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.qf-paymarks {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  /* NO WRAP: six marks at 40px + five 8px gaps = 280px, which fits inside the
     narrowest supported viewport (375 - 36 padding = 339). Keeping the row
     unwrapped is what guarantees a mark is never orphaned onto its own line.
     Adding a SEVENTH mark would be 328px — still under 339, but with no slack
     left, so re-measure before ever adding one. */
  flex-wrap: nowrap;
  gap: var(--space-1);
}
.qf-paymark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 26px;
  border: 1px solid var(--border);
  border-radius: 6px;
}
/* The mark itself: white on dark, black-navy on light — ONE token, both themes.
   MEASURED, not assumed. The marks are <li>s, and the public-pages skin ships
   TWO blanket !important rules that every <li> on an injected marketing page
   matches, which were repainting them muted grey instead of the ink/paper
   monochrome this strip is specified as:
     • `body.qf-public-wft li { color: var(--qf-color-muted) !important }`  (0,1,2)
       → #B1C5CE on dark
     • `html[data-theme="light"] body.qf-public-wft li { color: var(--muted)
       !important }`                                                        (0,2,3)
       → #5A6B7B on light
   An !important can only be beaten by a MORE SPECIFIC !important, so this rule
   carries one and reaches (0,3,0) — three classes outrank both. Verified by
   CDP getMatchedStylesForNode on /pricing and the marketing shell in both
   themes, not by eyeballing. The trust row below needs only (0,2,0): the light
   override there resolves to var(--muted), which is the tone it wants anyway.
   The existing .qf-footer-trustbar deliberately keeps the muted tone — untouched. */
.qf-footer-payrow .qf-paymarks .qf-paymark { color: var(--ink) !important; }
.qf-paymark .qf-pm {
  width: 32px;
  height: 16px;
  display: block;
  /* FULL ink, no opacity hold-back: the spec for this strip is a true monochrome
     mark — white on dark, black on light — and any opacity here would grey the
     marks against the crisp footer type sitting beside them. */
  fill: currentColor;
}
.qf-payrow-trust {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
}
.qf-payrow-trust li {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  font-size: 12px;
  line-height: 1.5;
}
/* Same blanket-!important story as the marks — pin the badges to the footer's
   own muted tone so they read identically on the homepage and on the injected
   marketing pages instead of drifting to the skin's --qf-color-muted. */
.qf-footer-payrow .qf-payrow-trust li { color: var(--muted) !important; }
.qf-payrow-trust svg {
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
  opacity: .85;
}
/* Directory subsite: its .site-footer is a single centred link row, so the strip
   needs its own left-aligned gutter there rather than inheriting the premium
   footer's grid. */
.site-footer .qf-footer-payrow {
  margin-top: var(--space-2);
  text-align: left;
}
@media (max-width: 720px) {
  /* Stack label above marks so the 5-mark row keeps its full width and stays on
     ONE line — the label is what would otherwise push a mark down alone. */
  .qf-payrow-methods {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-1);
  }
  .qf-footer-payrow { gap: var(--space-2); }
  /* One badge per line is a deliberate stack, not an orphaned wrap. */
  .qf-payrow-trust {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-1);
  }
}

/* ══ Payment PROCESSOR attribution ═══════════════════════════════════════════
   "Powered by Stripe", sitting under the accepted-payment marks. Every mark in
   that row IS Stripe (Link is Stripe's own wallet), so without this line the
   processor is invisible and a reader reasonably asks where Stripe went. Plain
   text in the strip's own monochrome treatment — no wordmark, no brand colour,
   no external asset — and the same --muted tone as the trust badges beside it.
   Carries the same specificity-3 !important as its neighbours because the
   public-pages skin ships blanket overrides on this subtree. */
.qf-footer-payrow .qf-payrow-methods .qf-payrow-proc {
  font-size: 11.5px;
  line-height: 1.5;
  letter-spacing: .02em;
  color: var(--muted) !important;
  white-space: nowrap;
}

/* ══ Directory DATA-SOURCE attribution strip ══════════════════════════════════
   siteChrome.ts#DIRECTORY_DATA_SOURCES, rendered in the directory subsite footer
   above the payment strip. Same monochrome contract as FOOTER_PAY_ROW: glyphs
   inherit currentColor, text sits on --muted, nothing here is a hardcoded colour
   and nothing fetches an image. These are ATTRIBUTION badges, never seals.

   COLUMN COUNTS ARE EXPLICIT, NOT auto-fit, because there are FIVE badges and an
   auto-fit track count of 4 would strand the fifth alone on its own line — the
   no-orphan rule. 5 across on desktop (one clean row), 3 in the tablet band
   (3+2, last line keeps two), and a deliberate single-column stack on phones,
   the same treatment .qf-payrow-trust already uses there. */
.qf-datasources {
  max-width: 1120px;
  margin: var(--space-3) auto 0;
  padding: var(--space-2) 0 0;
  border-top: 1px solid var(--border);
  text-align: left;
}
.qf-ds-label {
  display: block;
  font-size: 11.5px;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--space-1-5);
}
.qf-ds-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--space-1) var(--space-2);
}
.qf-datasources .qf-ds-list li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-1);
  min-width: 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-soft) !important;
}
.qf-ds-list svg {
  width: 15px;
  /* Box height = the 12px/1.45 line box, so the glyph optically centres on the
     FIRST line when a long source name wraps to two. preserveAspectRatio keeps
     the drawing itself square inside it, so no off-grid margin is needed. */
  height: 17.4px;
  flex: 0 0 auto;
  opacity: .85;
}
.qf-datasources .qf-ds-note {
  margin: var(--space-1-5) 0 0;
  max-width: 78ch;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--muted) !important;
}
@media (max-width: 1000px) {
  .qf-ds-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 720px) {
  /* One source per line is a deliberate stack, not an orphaned wrap. */
  .qf-ds-list { grid-template-columns: minmax(0, 1fr); }
}

/* ── Tool promo CTA ─────────────────────────────────────────────── */
.qf-tool-promo {
  max-width: 880px;
  margin: 48px auto 0;
  padding: 0 24px;
}
.qf-tool-promo-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 24px 32px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}
.qf-tool-promo-text {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--ink);
}
.qf-tool-promo-btn {
  flex-shrink: 0;
  padding: 12px 24px;
  border-radius: var(--radius-btn);
  background: var(--accent-fill);
  color: var(--accent-ink);
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  letter-spacing: 0.02em;
  transition: all .2s ease;
}
.qf-tool-promo-btn:hover { opacity: 0.85; }
@media (max-width: 600px) {
  .qf-tool-promo-inner { flex-direction: column; text-align: center; padding: 24px; }
}

/* ── Shadow ramp (design refactor, wave 1) ────────────────────────────────
   The measured reference design uses exactly three box-shadows. Every ad-hoc
   elevation in this codebase is mapped onto one of them. Defined as custom
   properties so the later token wave can re-point them in one place.
   Focus rings (0 0 0 Npx) and `inset` inner-borders are NOT elevation and are
   deliberately left alone.
   ───────────────────────────────────────────────────────────────────────── */
:root {
  --qf-shadow-sm: 0 2px 6px rgba(0, 0, 0, .04);
  --qf-shadow-md: 0 8px 24px rgba(0, 0, 0, .04);
  --qf-shadow-lift: 0 4px 24px rgba(165, 176, 204, .20);
}

/* ── Wave 2 additions ─────────────────────────────────────────────────────
   New tokens the measured target needs and the legacy set did not have.
   Appended at EOF on purpose: the two CI guards key their baselines on
   `file:line`, so inserting these into the :root block at the top of this
   file would renumber every baselined entry below it (see
   docs/design-refactor/baseline-protocol.md rule 1).
   ───────────────────────────────────────────────────────────────────────── */
:root {
  /* Surfaces the legacy set had no name for. */
  --surface-hero:  #FFFFFF;   /* = the hero wash raster's ENCODED pale tail */
  --surface-dark:  #0C111D;   /* the dark surface (CTA fills, dark bands) */

  /* Focus. 2px is reserved for selection + focus; everything else is 1px. */
  --border-focus:  var(--accent);
  --focus-ring:    0 0 0 2px var(--accent-soft);

  /* Badge / pill pair — quiet by default; the accent stays scarce. */
  --badge-bg:      var(--surface-2);
  --badge-ink:     var(--ink-soft);
  --badge-border:  var(--border);

  /* Radius ramp — 6 / 8 / 12 only, plus the pill idiom. */
  --radius-sm:     6px;
  --radius-md:     8px;

  /* The three shadows, reachable by their semantic names. */
  --shadow-lift:   var(--qf-shadow-lift);

  /* Container: 1328 outer / 24 gutter / 1280 content. */
  --container-max: 1328px;
  --container-gutter: var(--space-3);

  /* Motion — `all .3s ease` default, `.2s ease` for state changes. */
  --motion:        all .3s ease;
  --motion-state:  all .2s ease;
}
html[data-theme="dark"],
:root:not([data-theme="light"]) {
  --surface-hero: #131A28;    /* the blue wash has no legible dark twin */
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) { --surface-hero: #FFFFFF; }
}

/* Container — the measured 1328/24/1280 geometry. Declared here rather than
   edited in place at .container so the baseline line keys stay stable. */
.container { max-width: var(--container-max); padding: 0 var(--container-gutter); }

/* ── Wave 3: SITE CHROME TOKENS ───────────────────────────────────────────
   The header and footer are now styled in exactly two places — nav-unify.css
   for every injected page, and an EOF block in nav-ia.css that re-states the
   same result at the homepage bundle's own specificity, because that bundle
   carries seven sheets of `!important` chrome overrides this wave did not set
   out to delete. Two rule blocks, ONE set of values: both read the tokens
   below, so the header height or the footer ground changes here and nowhere
   else.

   Appended at EOF for the usual reason (baseline-protocol.md rule 1): the two
   CI guards key on `file:line`, so editing the :root block at the top of this
   file would renumber every baselined entry under it.
   ───────────────────────────────────────────────────────────────────────── */
:root {
  /* 83px, flat, opaque. No border, no blur, no scroll-state change — the bar
     looks the same at scrollTop 0 as it does at 4000.

     THE BAR SITS ON THE PAGE GROUND, NOT ON `--surface`. It carried no border
     and no shadow already, yet it still read as a bordered strip: `--surface`
     is #FFFFFF and the page under it is `--bg` #F8FAFC, so the full-width
     colour step at y=83 drew the hairline all by itself — and in dark it was
     worse, a #131A28 bar over a #0C111D page, i.e. the bar was LIGHTER than
     everything around it. On `--bg` there is no step, so there is no edge, and
     the header, the rail either side of the hero card and the page are one
     continuous ground with the card floating in it.

     Opaque, not transparent: the bar is sticky, so it has to stay legible over
     whatever scrolls beneath it. Ink pairs on the new ground (light #F8FAFC /
     dark #0C111D): brand 19.15:1 / 18.02:1, nav label 7.30:1 / 7.17:1,
     accent 5.39:1 / 7.78:1 — every pair still clears AA. */
  --chrome-h:          83px;
  --chrome-h-compact:  64px;          /* below the 1023px collapse point */
  --chrome-bg:         var(--bg);
  --chrome-ink:        var(--ink);

  /* Mega-menu / drawer: a 12px canvas shell with 4px of padding holding 8px
     inner cards, so the 4px gap between cards IS the divider. No hairlines. */
  --chrome-panel-radius: var(--radius);
  --chrome-panel-pad:    var(--space-0-5);
  --chrome-card-radius:  var(--radius-md);
  --chrome-panel-bg:     var(--surface-2);
  --chrome-card-bg:      var(--surface);

  /* The footer sits on the dark surface in BOTH themes, so its ink cannot come
     from --ink (near-black on light). Measured against #0C111D:
     heading 18.02:1, link 12.69:1, quiet line 7.17:1, hover 7.78:1 — every
     pair clears AA, and the hover uses the LIGHTENED accent because #3356EE is
     only 3.33:1 on this ground. */
  --footer-bg:      var(--surface-dark);
  --footer-ink:     #F8FAFC;
  --footer-link:    #CAD5E2;
  --footer-quiet:   #90A1B9;
  --footer-accent:  #8DA2F9;
  --footer-rule:    rgba(226, 232, 240, 0.14);
}

/* ── Wave 5: THE HERO BACKDROP ────────────────────────────────────────────
   The hero card's wash is a saturated-AZURE-to-near-white VERTICAL FADE. Our
   design law forbids CSS gradients, so it is a DECORATIVE RASTER laid over the
   flat `--surface-hero` base. If the image 404s or WebP is unsupported the card
   falls back to that flat tint; nothing renders white.

   The assets are generated, not licensed: scripts/make-hero-wash.mjs computes
   the field per-pixel in float, adds a low-frequency mesh so it is never a pure
   ramp, and applies ±1 LSB triangular dither, which is what keeps a long ramp
   from banding at 8 bits. 1440×1240, 18.6 KB light / 15.1 KB dark. (They grew
   6 KB / 3.5 KB when the transition went from 110px to 340px: three times as
   many rows are now MOVING, and a moving, dithered row is the only thing in
   the file a lossy encoder cannot compress away.)

   THE FADE IS PINNED IN PIXELS, NOT IN PERCENT, and the card paints it
   `100% 1240px` at `0 0` rather than with `cover`. Both of those are forced.
   `cover` crops the surplus axis, so at 2560 (card 2509×839) a 2560-wide field
   anchored at `50% 100%` showed only its bottom 65% — a saturated top would
   not have been on screen at all. And percentages do not work either: measured
   at 375/1024/1440/2560 in both audience panels, the card's height ranges
   727→1200px while the copy's ABSOLUTE offsets barely move (the headline
   starts at 111px at every width), so a proportional fade has no solution and
   a pixel-pinned one has an easy one. Stops: the band CLIMBS #026A98→#0475A8
   over 0→420px, opens up through #60CBFA over 420→760px (one smoothstep, the
   sky as a waypoint at p=0.28 ≈ y 557), then flat `--surface-hero` — TRUE
   WHITE — from 760 to 1240. The first cut held a flat #0171A2 to 475 and
   crossed to an off-white in 110px; the owner read that as too dark, too flat
   and too abrupt, and sampled it is exactly that. The band is now bounded by
   contrast rather than by taste: #0475A8 is the lightest azure that still
   measures 4.5:1 for white body copy with the grain's brightest pixel on it,
   and the deepest white copy on the wash ends at y=417.

   THE BAND IS AZURE, NOT OUR BRAND BLUE. The owner's hero reference samples
   #0179AD at hue 198 with saturation 0.99; `--accent` is #3356EE at hue 229.
   The accent now marks the one primary CTA on the page ("Start free"), which
   is why the two blues meet: see §8 of landing-home-grid.css for the pair's
   measured contrast and for the hairline that carries the control's boundary.
   That is 32 degrees apart — a different colour, not a near-duplicate — and
   the hero panel is the surface he is judging, so the raster follows it. The
   raster introduces no token and trips no guard, but the hero card no longer
   matches the accent the buttons and icon tiles inside it use. Whether the
   site's accent should follow is raised in the PR rather than decided here.

   THE INK ON THE BAND IS INVERTED — see the Wave 8 block below for the tokens
   and landing-home-grid.css §8 for which elements and why those.

   --surface-hero-grain / --surface-hero-grain-ink are the TEXTURE: a matched
   PAIR of isotropic film-grain tiles, one white and one black, exactly one of
   which is opaque at any pixel. They are a second, tiled layer rather than
   part of the wash for a mechanical reason: the wash is resampled to the
   card's width and resampling averages per-pixel texture out of existence,
   while a 1:1 tile renders identically at every card width and carries on over
   the flat base colour. Measured amplitude after encoding: sigma 5.14 8-bit
   levels on the blue band, 3.84 on the pale tail, 5.60 on the dark band —
   against the 4.6-6.0 sampled off the owner's reference. 18.8 KB + 17.2 KB.

   WHY TWO FILES AND NOT ONE RGBA FIELD. Only because of the encoder: WebP
   stores alpha losslessly and colour lossily, so a random black-and-white
   colour plane is the most expensive thing it can be handed. The identical
   field costs 67.3 KB as one tile and 36.0 KB as two constant-colour ones.

   WHY BIDIRECTIONAL AT ALL. The previous tile was white-only, which can only
   lighten — and over a near-white wash that is 0.7 of a level, i.e. nothing.
   That, not the alpha, is why the old grain was invisible, and it is why
   raising the alpha could never have fixed it. It also changes the contrast
   argument: a white-only layer could not cost light-theme dark ink anything,
   whereas this pair has a worst case at BOTH ends, so both are bounded in the
   generator and both are measured on the rendered page.

   Split light/dark by the SAME cascade `--surface-hero` uses further up, so
   one switch drives the base colour and the image together. The dark variant
   is a real sibling rather than a suppression — a flat rectangle in dark next
   to a faded card in light reads as the unfinished theme — and it inverts the
   idea rather than copying it: teal-navy #042031 at the top to the dark
   `--surface-hero` #131A28 at the bottom, i.e. azure to near-black, at the
   SAME hue as the light band. It took the re-tune's SHAPE (a climbing band and
   a 340px ramp) and not its values: every anchor moved by at most five levels,
   because a dark theme that follows a light theme's lightening is just the
   light theme. Its envelope is bounded at the TOP instead of the bottom,
   because dark `--muted` #90A1B9 needs the ground under luminance 0.0387; it
   runs 0.0118 → 0.0215 bare and ~0.035 under the brightest grain pixel, which
   is 4.6:1.

   Appended at EOF for baseline-protocol.md rule 1 (the guards key file:line).
   ───────────────────────────────────────────────────────────────────────── */
:root {
  --surface-hero-image: url("/brand/hero-wash-light.webp");
  --surface-hero-grain: url("/brand/hero-grain.webp");
}
html[data-theme="dark"],
:root:not([data-theme="light"]) {
  --surface-hero-image: url("/brand/hero-wash-dark.webp");
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --surface-hero-image: url("/brand/hero-wash-light.webp");
  }
}

/* ── Wave 6: THE ACCENT THAT SURVIVES A DARK GROUND, AND THE ICON TILE ────
   `--accent` is not usable as a glyph colour on this site's dark surfaces, and
   the reason is a cascade fact rather than a theory: style.css defines it as
   the lightened #8DA2F9 under html[data-theme="dark"], but
   landing-wefixtrades-cleanup.css re-declares `--accent: #3356EE !important`
   at :root for the whole marketing bundle, so on the landing page it resolves
   to the FILL blue in both themes. #3356EE measures 2.50:1 against the hero
   card's dark ground (#1A2333 with the tile tint over it) — under the 3:1 AA
   floor for non-text, and well under 4.5:1 for a label.

   `--accent-legible` is the accent chosen by GROUND rather than by theme
   variable: the brand fill where the ground is light, and the lightened step
   (the same value the footer already uses for exactly this reason) where it is
   dark. Measured on the composited tile: 4.87:1 light / 5.08:1 dark.

   `--icon-tile-bg` is the tint behind a card icon. It is stated here rather
   than borrowed from `--accent-soft` because that token is itself rewritten
   with !important by two sheets in this bundle, so a tile built on it would
   change opacity depending on which page it appeared on. Dark needs the
   heavier alpha: the same 10% over #1A2333 is a 3-level lift and reads as
   nothing at all.

   Appended at EOF for baseline-protocol.md rule 1 (the guards key file:line).
   ───────────────────────────────────────────────────────────────────────── */
:root {
  --accent-legible: var(--accent-fill);
  --icon-tile-bg:   rgba(51, 86, 238, 0.10);
}
html[data-theme="dark"],
:root:not([data-theme="light"]) {
  --accent-legible: var(--footer-accent);
  --icon-tile-bg:   rgba(51, 86, 238, 0.28);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --accent-legible: var(--accent-fill);
    --icon-tile-bg:   rgba(51, 86, 238, 0.10);
  }
}

/* ── Wave 8: THE HERO'S LIGHT INK, AND THE SECOND GRAIN TILE ──────────────
   The hero wash stopped being a pale field and became a SATURATED AZURE one at
   the top (scripts/make-hero-wash.mjs explains the fade and the two grain
   tiles). Dark ink cannot sit on that, so the copy inside the blue band is
   inverted — and the tokens below are what it inverts TO.

   THE INK IS WHITE, AND THAT IS FROM THE REFERENCE'S OWN PIXELS RATHER THAN
   FROM HABIT. The owner supplied three panels and they are two different
   systems, which the sampling makes unarguable:

     hero panel   ground #0179AD (hue 198, azure)  type #FFFFFE  <- pure white
     palette panel ground #4660E9 (hue 230, periwinkle) type #EDEBE9
     palette panel ground #F6F2E9 (warm cream)      type #17120E

   So "their white is not #FFFFFF" is true of the PERIWINKLE panel and false of
   the hero one. This card follows the hero panel, so the ink is white — and it
   is `--accent-ink`, which is already #FFFFFF in both themes and is already
   this codebase's name for "ink on a saturated accent ground". No new colour.

   The warm cream #F6F2E9 is NOT adopted. It is genuinely warm where `--bg`
   #F8FAFC, `--surface` #FFFFFF and `--surface-2` are cool, so taking it on
   would be a site-wide identity change rather than a hero tweak — see the PR
   body for the blast radius. It also belongs to their periwinkle system, not
   to the azure hero this card is copying.

   `--hero-wash-line` is the ghost button's outline on the band. 0.70 alpha is
   not a taste call: it is where the line clears the 3:1 a UI boundary needs
   (0.62 measures 3.05:1, 0.70 measures 3.50:1).

   `--hero-wash-focus` replaces `--focus-ring` inside the card. The site ring is
   `--accent-soft`, an 8%-opacity BLUE — nearly invisible on a saturated blue
   ground, which would have made the hero the one place on the site where you
   cannot see what you have tabbed to.

   IN DARK THEME ALL OF THEM RESOLVE TO THE ORDINARY DARK-THEME VALUES, because
   the dark hero needs no inversion: its ground runs deep teal-navy to
   near-black and its ink was already light. One set of rules, two themes.

   Appended at EOF for baseline-protocol.md rule 1 (the guards key file:line).
   ───────────────────────────────────────────────────────────────────────── */
:root {
  --hero-wash-ink-rgb: 255, 255, 255;
  --hero-wash-ink:   var(--accent-ink);
  --hero-wash-line:  rgba(var(--hero-wash-ink-rgb), 0.70);
  --hero-wash-wipe:  rgba(var(--hero-wash-ink-rgb), 0.14);
  --hero-wash-focus: 0 0 0 2px rgba(var(--hero-wash-ink-rgb), 0.85);
  --surface-hero-grain-ink: url("/brand/hero-grain-ink.webp");
}
html[data-theme="dark"],
:root:not([data-theme="light"]) {
  --hero-wash-ink:   var(--ink);
  --hero-wash-line:  var(--border-strong);
  --hero-wash-wipe:  var(--cta-sec-bg-hover);
  --hero-wash-focus: var(--focus-ring);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --hero-wash-ink:   var(--accent-ink);
    --hero-wash-line:  rgba(var(--hero-wash-ink-rgb), 0.70);
    --hero-wash-wipe:  rgba(var(--hero-wash-ink-rgb), 0.14);
    --hero-wash-focus: 0 0 0 2px rgba(var(--hero-wash-ink-rgb), 0.85);
  }
}

/* ── Wave 9: THE CARD ICON FOLLOWS THE HERO'S AZURE ───────────────────────
   Wave 8 moved the hero wash to the owner's reference azure — hue 198, the
   band's body sampled at #0179AD and rendered at #0171A2 (the generator's
   `base` anchor, scripts/make-hero-wash.mjs). The fifteen card icons under it
   were still `--accent`/#3356EE at hue 229. Thirty-two degrees apart on one
   surface is a mismatch you can see without measuring it, and the owner did.
   The icons follow the band.

   ONE SOURCE, NOT A SECOND HARD-CODED BLUE. Everything below is the hero
   wash generator's own two anchors for the LIGHT band, restated as tokens so
   the tile and the raster cannot drift:

     --hero-azure-rgb    4, 117, 168   = `base`, the body of the band
     --hero-azure-deep   #026A98       = `deep`, the band's top edge

   Both moved when the band was re-tuned lighter (see the stop table in the
   generator): the band's body went #0171A2 -> #0475A8 and its top edge
   #016490 -> #026A98, so the tiles went with them. That is the whole point of
   deriving them — the re-tune could not leave the icons behind.

   Both are hue 198 (198.7 and 198.4). `src/server/home/heroWashAzure.test.ts` reads the
   generator's VARIANTS table and fails if either token stops matching it, so
   a future re-tune of the wash cannot leave the icons behind.

   WHY THE GLYPH IS `deep` AND THE TILE IS `base`. The tile is the band colour
   at 10% (28% in dark, the same alphas #545 measured for the indigo tile), so
   a glyph painted in `base` itself would sit on a 10%-dilution of itself:
   4.18:1 on the bento card's `--surface-2` ground — over the 3:1 floor for a
   non-text graphic but under AA, which is the bar #545 set for this glyph and
   is the bar the owner's brief restates. `deep` is one step down the same band
   and clears it: 4.75:1 light. That margin is thin ON PURPOSE and it is the
   second of the two bounds that stopped the band going as light as the
   reference's: `deep` IS the top edge, so lifting the top edge one more step
   takes the glyph under AA.

   DARK IS A LIFT OF THE SAME BAND, BY THE SAME FACTOR THE SITE ALREADY USES.
   The dark wash runs teal-navy to near-black (#062B42), so it has no azure
   light enough to draw a glyph with — exactly the situation `--accent-legible`
   was created for on the indigo side, where dark swaps `--accent-fill` for the
   lightened `--footer-accent`. That swap is a x2.83 move in relative
   luminance (0.1353 -> 0.3834). Applying the same factor to the band's
   luminance (0.1558) targets 0.4413; `--hero-azure-lift` #45BDF3 is hue 198.6
   at luminance 0.4413, i.e. the band lifted the way this site already lifts
   its accent rather than a colour chosen by eye. 5.58:1 on the dark tile.

   SCOPE. These tokens are used by the card icon tiles and the partner
   banner's matching tile, and by nothing else. `--accent`, `--accent-fill`,
   `--accent-legible`, `--icon-tile-bg`, links, buttons and focus rings are
   untouched and still indigo — whether the whole palette should follow the
   hero is the owner's call, raised in the PR and not decided here. In
   particular `--icon-tile-bg` / `--accent-legible` keep their indigo values
   because the directory's carrier-profile tiles read them too.

   Appended at EOF for baseline-protocol.md rule 1 (the guards key file:line).
   ───────────────────────────────────────────────────────────────────────── */
:root {
  --hero-azure-rgb:     4, 117, 168;
  --hero-azure-deep:    #026A98;
  --hero-azure-lift:    #45BDF3;
  --icon-azure:         var(--hero-azure-deep);
  --icon-tile-azure-bg: rgba(var(--hero-azure-rgb), 0.10);
}
html[data-theme="dark"],
:root:not([data-theme="light"]) {
  --icon-azure:         var(--hero-azure-lift);
  --icon-tile-azure-bg: rgba(var(--hero-azure-rgb), 0.28);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --icon-azure:         var(--hero-azure-deep);
    --icon-tile-azure-bg: rgba(var(--hero-azure-rgb), 0.10);
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   WAVE 5 — THE FOOTER, LIGHTER AND SMALLER (Alex, 2026-09: "looks too heavy
   and big"). SHARED MECHANICS ONLY — both footers load this sheet, so the
   disclosure behaviour, the consolidated trust strip and the bottom legal
   block are declared ONCE here and merely toned per-surface in nav-unify.css
   (marketing), nav-ia.css (homepage) and DIRECTORY_CSS (directory subsite).

   APPENDED AT EOF, like every other wave in this file: check-spacing.mjs and
   check-hardcoded-colors.mjs baseline by FILE:LINE and this sheet carries
   tolerated entries, so a mid-file insert would renumber them all.

   NOTE FOR THE NEXT CLEANUP WAVE: `.qf-footer-trustbar` rules still sit at
   nav-unify.css:415-437/671-676 and nav-ia.css:287/506-510. The ELEMENT is
   gone (its one non-duplicate claim set moved into .qf-payrow-trust below), so
   those rules are dead. They are deliberately NOT deleted here because removing
   them renumbers 71 line-keyed guard-baseline entries across these sheets; drop
   them in the next wave that regenerates baselines anyway.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── The link columns as native disclosures ─────────────────────────────────
   <details>/<summary>: the browser owns the toggle, Enter/Space work for free,
   and the links STAY IN THE DOM when closed so every internal SEO link is
   still crawled. Markup ships `open`; HEADER_SCRIPTS closes them at <=640px.
   With JS off nothing collapses and the footer renders as it always has. */
.qf-fdisc { min-width: 0; }
.qf-fdisc-sum {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-1);
  cursor: pointer;
  list-style: none;
  border-radius: 6px;
}
.qf-fdisc-sum::-webkit-details-marker { display: none; }
.qf-fdisc-sum::marker { content: ""; }
/* A focus ring that is unmistakably NOT the hover state: hover only lifts the
   heading to --footer-ink, focus draws a real 2px offset outline. */
.qf-fdisc-sum:focus-visible {
  outline: 2px solid var(--footer-accent, var(--accent));
  outline-offset: 2px;
}
.qf-fdisc-chev {
  width: 14px;
  height: 14px;
  flex: 0 0 auto;
  opacity: .7;
  transition: transform .2s ease;
}
.qf-fdisc[open] > .qf-fdisc-sum .qf-fdisc-chev { transform: rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .qf-fdisc-chev { transition: none; }
}
.qf-fdisc-body { display: flex; flex-direction: column; min-width: 0; }
/* DESKTOP KEEPS ITS NAVIGATION VISIBLE. Above the phone step the columns are
   open and the affordance stands down entirely — no chevron, no pointer, no
   toggle — so a wide screen never hides links behind a click. */
@media (min-width: 641px) {
  .qf-fdisc-sum { cursor: default; }
  .qf-fdisc-chev { display: none; }
}

/* ── ONE consolidated sub-bar, not three stacked bands ──────────────────────
   The claims are ordinary list items separated by a typographic middot, so six
   of them reflow as a sentence does. No per-claim icon, no inline-flex block
   that can wrap as a unit, and therefore no claim can be stranded on a line of
   its own — the no-orphan rule is satisfied by construction, not by a track
   count that has to be re-derived every time a claim is added. */
.qf-payrow-trust {
  display: block;
  font-size: 11.5px;
  line-height: 1.4;
}
.qf-payrow-trust li { display: inline; }
.qf-payrow-trust li + li::before {
  content: "·";
  margin: 0 var(--space-0-5);
}
.qf-footer-payrow {
  margin: var(--space-1) auto 0;
  padding: var(--space-1) 0 0;
  gap: var(--space-1) var(--space-3);
  line-height: 1.4;
}
/* style.css:1685 sets this band's top margin for the directory subsite at
   (0,2,0), which outranks the (0,1,0) above — match it so the directory
   footer gets the same tightened gap as the marketing one. */
.site-footer .qf-footer-payrow { margin-top: var(--space-1); }
.qf-payrow-methods { gap: var(--space-1); }
.qf-paymark { width: 34px; height: 22px; }
.qf-paymark .qf-pm { width: 28px; height: 14px; }

/* ── The always-visible legal block ─────────────────────────────────────────
   Copyright, operator attribution, the carrier names/marks note and the OOG
   line. NONE of it is a disclosure and none of it is display:none at any
   width — a compliance statement a reader must click to reveal has not been
   made.

   TIGHTER, NOT SMALLER (Alex, 2026-09). The ask was for this block to feel
   lighter, and the type was never where its weight came from: 11.5px against
   13px links is already a step below body, and shrinking a compliance
   statement is the one move here with real downside. The weight was in the
   BLOCK — 1.5 leading, a 4px gap turning two legal lines into two paragraphs,
   and 40px of dead space opening the band. So the budget is spent there:

     • leading 1.5 -> 1.4, a 16.1px line box instead of 17.25. 1.4 is the FLOOR
       check-spacing.mjs allows for body text and where fine print is set; it
       is not a licence to go lower.
     • the copyright line and the carrier-marks note stop being two spaced
       paragraphs and become one tight run (4px -> 0). They are one statement.
     • the band opens at 16 + 12 instead of 24 + 16.

   NOT ONE WORD OF ANY COMPLIANCE STATEMENT CHANGED, and nothing here is
   smaller than it was. Two runs got BIGGER — see `.qf-ds-note` below. */
.footer-bottom {
  margin: var(--space-2) auto 0;
  padding: var(--space-1-5) 0 0;
  font-size: 11.5px;
  line-height: 1.4;
}
.footer-bottom .qf-foot-line { margin: 0; }
.footer-bottom .qf-foot-line + .qf-foot-line { margin-top: 0; }
.footer-bottom .qf-foot-links a { margin-left: var(--space-1); }
/* MEASURE. This cap was written for `.qf-foot-marks` — a class the markup has
   never emitted: siteChrome.ts#footerBottomHtml ships the carrier-marks note
   as `qf-foot-line qf-foot-operator`. So it never applied, and the note ran
   877px / ~165 characters on ONE line at 1440. Re-keyed to the class the
   markup actually carries (`.qf-foot-line` alone is the copyright line, which
   is short and wants no cap), at the same 78ch its sibling `.qf-ds-note` has
   used all along. Costs one line above ~1000px; 78 characters is a line a
   reader can track back to the start of. */
.footer-bottom .qf-foot-line.qf-foot-operator { max-width: 78ch; }

/* ── The FMCSA attribution, tightened ──────────────────────────────────────
   Same claims, same five sources, same non-affiliation sentence — this only
   pulls the band in. The 5/3/1 track ladder above is untouched: it is what
   keeps the fifth source off a line of its own. */
.qf-datasources {
  margin: var(--space-1) auto 0;
  padding: var(--space-1) 0 0;
}
.qf-ds-label { margin-bottom: var(--space-0-5); font-size: 11.5px; line-height: 1.4; }
.qf-datasources .qf-ds-list li { font-size: 11.5px; line-height: 1.4; }
/* Box height = the 11.5px/1.4 line box, so the glyph optically centres on the
   FIRST line when a source name wraps. Tracks the line-height above it. */
.qf-ds-list svg { width: 13px; height: 16.1px; }
/* THIS RUN CAME UP, NOT DOWN. It is the FMCSA non-affiliation + self-declared
   sentence — the most legally loaded line in the footer — and it was sitting
   at 11px, half a step under every other compliance run, while `.qf-ds-label`
   above it (11px) undercut its own peer `.qf-payrow-label` (11.5px). Both
   join the rest of the block at 11.5px. The height this costs is paid for
   several times over by the leading and the band gaps. */
.qf-datasources .qf-ds-note { margin: var(--space-0-5) 0 0; font-size: 11.5px; line-height: 1.4; }

/* ═══ WAVE 5b — TWO GROUPED CLAIM RUNS, AND LAUNCHER CLEARANCE ═══════════════
   `.qf-payrow-platform` is the second of the strip's two claim lists. It takes
   the same inline-with-a-middot treatment as `.qf-payrow-trust` — the styling
   is deliberately identical, because the two runs are peers, not a heading and
   a subheading. What separates them is the gap, not a rule, a bullet or a
   weight change: five claims joined end to end read as one crawl, three plus
   two read as two facts.
   ═══════════════════════════════════════════════════════════════════════════ */
.qf-payrow-platform {
  list-style: none;
  margin: 0;
  padding: 0;
  display: block;
  font-size: 11.5px;
  line-height: 1.4;
}
/* THE TWO CLAIM RUNS, SIZED IN ONE DECLARATION — which is what "peers with
   deliberately identical styling" was always supposed to mean. They have in
   fact been rendering at 12px and 11.5px: `.qf-payrow-trust` declares 11.5 at
   (0,1,0), but `.qf-payrow-trust li` at style.css:1665 is (0,1,1) and carries
   `font-size: 12px`, so it won on the payment run and never applied to the
   platform one. Matching that specificity here, for BOTH runs at once, repairs
   the accident and makes a future divergence impossible to introduce by
   editing one of them. 11.5px is the floor every reading run in this block
   shares; nothing in it is smaller. */
.qf-payrow-trust li,
.qf-payrow-platform li { font-size: 11.5px; line-height: 1.4; }
/* The processor line sets its own 1.5 at (0,3,0) in the block above, so the
   1.4 on `.qf-footer-payrow` never reached it and it was the one run in the
   strip still on the old leading. Same size, same colour — leading only. */
.qf-footer-payrow .qf-payrow-methods .qf-payrow-proc { line-height: 1.4; }
.qf-footer-payrow .qf-payrow-platform li { color: var(--muted) !important; }
.qf-payrow-platform li { display: inline; }
.qf-payrow-platform li + li::before {
  content: "·";
  margin: 0 var(--space-0-5);
}

/* THE DIRECTORY FOOTER CLEARS THE CHAT LAUNCHER TOO — HORIZONTALLY NOW.
   `.qf-mc-fab` is fixed 56x56 at right/bottom 12px. This used to buy its
   clearance as a full-width 80px band, which is a corner's problem solved with
   the whole footer's height; it is now the same 24px the desktop footer ends
   on, with the 80px gutter moved onto the two elements that actually enter the
   button's column. Measurement and reasoning: foot of nav-unify.css. */
@media (max-width: 900px) {
  .site-footer { padding-bottom: calc(24px + env(safe-area-inset-bottom, 0px)); }
}

/* ── ABOVE THE PHONE BAND, THE SUB-BAR GIVES UP WIDTH INSTEAD OF HEIGHT ──────
   Clearing a fixed 56x56 launcher anchored to the bottom-right corner can be
   bought two ways, and the right currency depends on what the viewport has
   spare.

   <=900px it is bought VERTICALLY (padding-bottom, in nav-unify.css /
   nav-ia.css / DIRECTORY_CSS): a phone has no horizontal room to give — a 68px
   right gutter at 375px would cut the claim run from 343px to 275px and wrap
   it further — but it can afford 56px of scroll.

   >900px it is bought HORIZONTALLY, here. The strip is a flex row with
   `justify-content: space-between`, so its LAST run hugs the right edge of a
   container that reaches past the launcher: measured at 1440 the platform run
   ended at 1384 against a launcher starting at 1372, and at 1024 the overlap
   was 44px of "Per-tenant data isolation". A right gutter of 12 + 56 + 12 =
   80px — the launcher's own footprint plus the gap it keeps — stops the run
   before the corner and costs ZERO height, which is the whole point of this
   wave. Desktop has the width to spare; it does not have height to spare. */
@media (min-width: 901px) {
  .qf-footer-payrow { padding-right: 80px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   WAVE 5c — FOOTER POLISH, ROUND 2 (Alex, 2026-09). Five asks, all of them
   about how the footer READS rather than what it says: the disclosures should
   open the way a phone's do, the marks disclaimer should be the quietest thing
   in the block instead of the second-loudest, the processor line should look
   like the marks it stands next to, and a column heading should not be
   mistakable for one of its own links.

   APPENDED AT EOF, like every wave in this file — check-spacing.mjs and
   check-hardcoded-colors.mjs baseline by FILE:LINE, so a mid-file insert
   renumbers every tolerated entry below it. NOTE: neither guard reads CSS
   living in a .ts template literal, so the matching DIRECTORY_CSS block was
   hand-audited against the same rules; a green guard run says nothing about it.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. THE DISCLOSURES ANIMATE, NATIVELY ───────────────────────────────────
   Still <details>/<summary>. Nothing about the semantics moves: the browser
   owns the toggle, Enter/Space still work, the disclosure state is still
   exposed without an aria-* attribute of ours, find-in-page still reaches a
   closed column, and every internal link STAYS IN THE DOM when collapsed —
   which is the whole reason these are native disclosures and not an accordion.

   HEIGHT IS ANIMATED BY THE UA, NOT BY US. `interpolate-size: allow-keywords`
   makes `block-size: 0 -> auto` a real interpolation, and `::details-content`
   is the box the UA already wraps the panel in, so there is no wrapper div to
   measure, no ResizeObserver, no inline style written from script, and nothing
   to go stale when a column's content changes. `content-visibility` rides along
   with `transition-behavior: allow-discrete` so the panel stays painted for the
   whole closing pass instead of vanishing on frame one.

   THE @supports GATE IS THE FALLBACK. A browser missing either feature skips
   this block entirely and gets exactly today's behaviour — instant open, instant
   close, links present either way. There is no JS path to degrade, because
   there is no JS in the animation.

   MOTION BUDGET: .2s ease, which is `--motion-state` (style.css:1875) — the
   same duration and the same easing the chevron beside it already uses. The
   token cannot be used directly because it expands to `all .2s ease` and this
   transition names its properties, so the value is restated, not invented. No
   third duration, no bespoke curve.

   `interpolate-size` IS INHERITED, so it is switched back off at the panel
   contents. Leaving it on would hand `allow-keywords` to every link inside —
   and `.premium-footer a` runs `transition: all .2s ease`, which is exactly the
   shape that starts animating intrinsic sizes nobody asked to animate. */
.qf-fdisc { interpolate-size: allow-keywords; }
.qf-fdisc-body { interpolate-size: numeric-only; }
@supports (interpolate-size: allow-keywords) and (selector(::details-content)) {
  .qf-fdisc::details-content {
    block-size: 0;
    /* clip, not hidden: hidden would make the panel a scroll container. */
    overflow: clip;
    transition: block-size .2s ease, content-visibility .2s ease;
    transition-behavior: allow-discrete;
  }
  .qf-fdisc[open]::details-content { block-size: auto; }
  /* HEADER_SCRIPTS closes the columns on phones as soon as the page parses.
     That one pass must be instant, or the footer plays a collapse on load and
     drags the legal block up with it. The flag is dropped a frame later. */
  :root[data-fdisc-boot] .qf-fdisc::details-content { transition: none; }
  @media (prefers-reduced-motion: reduce) {
    .qf-fdisc::details-content { transition: none; }
  }
}

/* ── 4. HEADINGS THAT READ AS HEADINGS ──────────────────────────────────────
   Open a column and the label sat 8px above its first link — the SAME 8px the
   links sit apart from each other — so the eye had only an 11px/13px size step
   and a colour step to go on, and the heading read as row zero of the list.

   THE FIX IS MOSTLY THE GAP, because in this system a divider IS a gap: no
   rule, no border, no tinted bar under the label. 8px of panel padding puts the
   heading 16px off its first link against 8px between links — twice the
   spacing, which is the thing an eye reads without being told. Weight goes
   700 -> 800 and tracking .10em -> .14em on top, so the label also carries a
   different texture, not just a different position.

   The padding lives INSIDE `::details-content`, so a closed column still
   measures zero and the phone footer keeps the height PR #565 bought it.
   Headings stay left-aligned and keep `text-wrap: balance` (nav-unify.css:1196,
   DIRECTORY_CSS) so none of them orphans a word at 375 or 320.

   The selectors carry the summary so they outrank the per-surface tone rules in
   sheets that load AFTER this one — `.premium-footer .footer-col h4` at (0,2,0)
   in nav-unify.css and `.site-footer .dirfoot-head` at (0,2,0) in DIRECTORY_CSS
   both declare letter-spacing, and this file is linked first on every surface. */
.qf-fdisc-body { padding-top: var(--space-1); }
/* MEASURED SPECIFICITY, NOT GUESSED. `.premium-footer .footer-col h4` in
   nav-unify.css declares `letter-spacing: .1em` at (0,2,1) — the same weight as
   `.premium-footer .qf-fdisc-sum h4`, and that sheet is linked AFTER this one on
   every marketing surface, so a tie loses. `.footer-col` is carried here purely
   to reach (0,3,1). The directory's `.dirfoot-head` sits at (0,2,0) in
   DIRECTORY_CSS, which (0,3,0) already outranks. Verified with
   CSS.getMatchedStylesForNode on / and /directory, not by reading the sheets. */
.premium-footer .footer-col .qf-fdisc-sum h4,
.site-footer .qf-fdisc-sum .dirfoot-head {
  font-weight: 800;
  letter-spacing: .14em;
}

/* ── 3. THE PROCESSOR MARK ──────────────────────────────────────────────────
   siteChrome.ts#STRIPE_MARK explains why this is a badge beside the list rather
   than a seventh item in it. Here it only has to LOOK like one: the tile is the
   `.qf-paymark` tile, so it inherits the same 1px --border hairline, the same
   6px radius and — the part the ask is actually about — the same 22px height as
   its six siblings, from the same declaration.

   WIDTH IS THE ONLY THING THAT CHANGES. The tile sizes to its content with the
   strip's own 8px token either side, and the glyph takes `width: auto` against
   the height it already inherits from `.qf-paymark .qf-pm` (style.css:2288), so
   the rendered HEIGHT is byte-identical to the other marks and the width falls
   out of the viewBox. `aspect-ratio` states the same ratio a second time,
   because an inline SVG carrying no width/height attributes has no intrinsic
   size to fall back on if a UA declines to derive one.

   The colour !important is the same story as `.qf-paymark` above it: the
   public-pages skin ships blanket overrides across this subtree, and an
   !important is only beaten by a more specific !important. (0,3,0) here. */
.qf-paymark--proc { width: auto; padding: 0 var(--space-1); }
.qf-paymark--proc .qf-pm { width: auto; aspect-ratio: 82 / 16; }
.qf-footer-payrow .qf-payrow-methods .qf-paymark--proc { color: var(--ink) !important; }

/* ── 2. THE CARRIER-MARKS DISCLAIMER, MOVED AND QUIETED ─────────────────────
   Last element in the footer now, under the GDPR/CCPA and per-tenant claims.
   PRESENT, IN FLOW, VISIBLE WITH NO INTERACTION, AT EVERY WIDTH AND IN BOTH
   THEMES — it is a nominative-use disclaimer and it only works if it is read.
   It is not in a <details>, it is not display:none, it is not opacity-0 and it
   is not behind a disclosure at any breakpoint. Do not move it into one.

   ONE STEP OF TYPE, AND THAT IS THE WHOLE BUDGET: 11.5px -> 11px, the floor
   this block is allowed. Everything else comes from POSITION and WEIGHT, which
   is where "less prominent" is supposed to come from for a legal line.

   THE MAILTO STOPS SHOUTING. It was rendering pure #FFF against a --footer-quiet
   sentence — a 2.5x luminance step that read as bold even at font-weight 400,
   which is what made the line the loudest thing in the block. It now takes the
   sentence's own tone and declares an underline instead, so it is unmistakably
   a link without being the brightest run in the footer. Measured on the footer
   ground: 7.17:1, well over the 4.5 floor.

   TWO BOXES, because they do different jobs: the outer div takes the same
   `--container-max` + auto-margin geometry as `.footer-bottom` and
   `.qf-footer-payrow` so the note's left edge lines up with the site map above
   it, and the inner p keeps the 78ch measure its sibling `.qf-ds-note` uses. A
   single box cannot do both — capping the outer box at 78ch would centre a
   narrow block in a wide footer and break that alignment. */
.qf-foot-marks {
  max-width: var(--container-max);
  margin: var(--space-1) auto 0;
  text-align: left;
}
.qf-foot-marks-line {
  max-width: 78ch;
  margin: 0;
  font-size: 11px;
  font-weight: 400;
  line-height: 1.4;
}
/* THE NUMBERS THIS SELECTOR IS BUILT TO BEAT, read off the live page with
   CSS.getMatchedStylesForNode rather than inferred from the sheets:
     body.qf-wft .premium-footer a            color: #B4C2FC !important   (0,2,2)
     body.qf-wft a, body.qf-public-wft a …    color: --qf-color-white !imp (0,1,2)
     body.qf-wft .premium-footer a            text-decoration-color: rgba(255,255,255,0)
   The first is why the mailto rendered as bright accent blue and the third is
   why an underline declared at (0,2,1) painted in fully transparent ink. The
   `.qf-foot-marks-line` class on the <p> exists to reach (0,3,1), which clears
   all three; an !important is only ever beaten by a more specific !important.
   `currentColor` on the decoration keeps the underline tied to the text so a
   future tone change cannot separate them again. */
.premium-footer .qf-foot-marks .qf-foot-marks-line { color: var(--footer-quiet) !important; }
.premium-footer .qf-foot-marks .qf-foot-marks-line a {
  color: var(--footer-quiet) !important;
  font-weight: 400;
  text-decoration: underline;
  text-decoration-color: currentColor !important;
  text-underline-offset: 2px;
}
/* Above the launcher band the pay row buys its clearance horizontally
   (style.css:2416). The note now sits BELOW it and must not give that width
   back — it is left-aligned and capped at 78ch, so it never reaches the corner,
   but the gutter is stated so a future measure change cannot walk into it. */
@media (min-width: 901px) {
  .qf-foot-marks { padding-right: 80px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   WAVE 10 — THE HERO'S PRIMARY CTA AND ITS AUDIENCE TOGGLE, ON THE BAND'S
   OWN HUE (owner, 2026-09: "the blue color choice for CTA button, not sure
   about that. It doesn't match the background color" + "the toggle for
   carriers and shippers, let's make it minimal, without white fill color").

   ── 1. WHY THE CTA IS NOT `--accent-fill` ANY MORE ────────────────────────

   #570 painted the hero's "Start free" in the brand indigo `--accent-fill`
   #3356EE, hue 229. The band it sits on is the reference azure, hue 198. That
   is the SAME 31-degree mismatch Wave 9 already fixed once for the card icons,
   and the owner reported it again for the button.

   IT WAS ALSO NOT SEPARATING FROM ITS GROUND. #570 measured the indigo on the
   band at 1.5:1 and accepted it because the rim carried the boundary. Measured
   live off the rendered page at four widths (Playwright, pixel-sampled, the
   button hidden so the sample is the raster and not the button), it is worse
   than that: the darkest band pixel behind the button is #016FA0 and the
   indigo measures **1.02:1** against it in light at 1440/768/375. The page's
   single most important control was, in luminance terms, invisible.

   ── 2. WHAT IT IS NOW, AND HOW IT IS DERIVED ──────────────────────────────

   THE CTA IS `--cta-bg` RE-HUED ONTO THE BAND. The site already has a primary
   button colour that inverts per theme: `--cta-bg` / `--cta-text` (near-black
   on white ink in light; near-white on near-black ink in dark). The hero CTA
   is that same object moved onto the wash's hue and stepped away from the band
   in whichever direction the band leaves room. So `--hero-cta-ink` is simply
   `var(--cta-text)` — it already inverts correctly, in both themes, with no
   second declaration.

   THE DIRECTION OF THE STEP FLIPS WITH THE THEME, BECAUSE THE BAND DOES.
   Light's band is a mid azure (measured #016F9F -> #42ADDA behind the button),
   so the CTA steps DOWN from it. Dark's band is teal-navy near-black (#03273E
   -> #18394D), where "darker" does not exist — 3:1 below it is off the bottom
   of the gamut — so the CTA steps UP instead. That is not two buttons: it is
   one rule, and it is the same answer `--hero-azure-lift` already gives the
   card glyphs for exactly the same reason (see Wave 9).

   THE LIGHT FILL IS THE BAND'S `base` HUE EXACTLY. #003E5A is hue 198.67; the
   generator's `base` anchor [4,117,168] is hue 198.66. Same colour story, one
   step down in luminance — which is the whole of the owner's note.

   ── 2a. WHY THE RIM, AND NOT THE FILL, CARRIES 3:1 ────────────────────────

   THE FIRST ATTEMPT AT THIS CHANGE GOT IT WRONG, AND THE MISTAKE IS WORTH
   KEEPING. It required 3:1 between the FILL and the band. On this band that
   forces the fill to near-black: `base` is pinned as "the lightest azure white
   BODY copy can sit on" (heroWashAzure.test.ts) because an 18px lead runs
   across it, so a step 3:1 darker than it lands at #001F2C — which measured
   1.11:1 against the sixteen `--cta-bg` tool-card buttons and simply read as
   black. It stopped clashing by ceasing to be blue.

   WCAG 1.4.11 asks for 3:1 on the visual information that IDENTIFIES the
   component, not on its fill. For a button with a visible rim that is the RIM.
   So the rim is now 2px of `--hero-cta-rim` and is the load-bearing boundary,
   and the fill is free to be a colour the owner actually asked for.

   MEASURED, light theme, worst case across 320/375/768/1440:

     fill   #003E5A   hue 198.67   2.06:1 vs the darkest band pixel
                                  11.44:1 for the white label
                                   1.65:1 vs `--cta-bg` (was 1.11 at #001F2C)
     rim    white     2px         11.44:1 vs the fill
                                   4.24:1 vs the darkest band pixel
     hover  #00344B   hue 198.40   2.37:1 vs band, 13.18:1 for the label

   IN DARK THE FILL STILL CARRIES IT ON ITS OWN — #0496D7 measures 3.61-3.69:1
   against the dark band — so dark's rim is decorative rather than load-bearing
   and stays the quiet `--border-strong`. One token, two jobs, stated here so
   nobody "fixes" the dark rim to match the light one.

   THE ONE PLACE THE RIM DOES NOT REACH 3:1, STATED RATHER THAN HIDDEN. At 320
   the button sits low enough that the ramp has opened up behind it and the
   band reaches #42ADDA. A white rim measures 2.55:1 against THAT pixel (3.52:1
   against the band's modal colour there). No single rim colour can fix it: a
   lighter rim does not exist, and a rim dark enough to clear 3:1 against a
   #42ADDA ground would have to be darker than the fill it also has to
   separate from. It is one width, against the lightest pixel of a grainy
   ramp, with the fill's own 4.62:1 against that same pixel underneath it.

   HOVER DARKENS IN LIGHT AND LIGHTENS IN DARK — IT NEVER CHANGES FAMILY. This
   is the trap #570 documented and dodged: `--accent-strong` is #2440C4 in
   light but a PALE LAVENDER #B4C2FC in dark (and `landing-s-polish.css`
   re-declares it as the lavender on `.landing-v2` in BOTH themes), so any
   hover that reaches for it puts a white label on a pale ground at 1.4:1. The
   tokens below never leave the 197-199 hue window, so every state is AA by
   construction rather than by review.

   ── 3. THE TOGGLE ─────────────────────────────────────────────────────────

   The selected segment was a WHITE PILL (9999px radius, #FFFFFF flood,
   near-black label) inside a white glass track. That is a straight violation
   of design-system rule 4 — selected is a 2px border colour swap plus a tint,
   never a bright fill — and it is what the owner asked to remove.

   It is now two rectangles: radius 8, a 2px rim in BOTH states so nothing
   reflows when you switch, and selection carried by the rim colour plus a
   10% white tint.

   THE TINT IS 10%, NOT 14%, AND THAT IS A MEASURED CEILING rather than a taste
   call. `--hero-wash-wipe` (14%) composited over the band behind the toggle
   takes the selected segment's ground to luminance 0.196, where the white
   label measures 4.26:1 — an AA failure. 10% lands at 0.178 and 4.61:1. The
   tint cannot go higher while the label stays white, so it gets its own token
   rather than borrowing the wipe.

   Appended at EOF for baseline-protocol.md rule 1 (the guards key file:line).
   Both guards are blind to this anyway — `check-hardcoded-colors.mjs` and
   `check-spacing.mjs` only walk .css/.html and this file is .css, so they DO
   see it; the CSS that lives in .ts template literals elsewhere is what they
   miss. Hand-audited either way: no raw #fff/#000 below, every radius on the
   6/8/12 ramp, every length on the 8px ramp.
   ───────────────────────────────────────────────────────────────────────── */
:root {
  --hero-cta-fill:       #003E5A;
  --hero-cta-fill-hover: #00344B;
  --hero-cta-ink:        var(--cta-text);
  --hero-cta-rim:        var(--hero-wash-ink);
  --hero-toggle-rim:     var(--hero-wash-line);
  --hero-toggle-rim-on:  var(--hero-wash-ink);
  --hero-toggle-tint:    rgba(var(--hero-wash-ink-rgb), 0.10);
}
html[data-theme="dark"],
:root:not([data-theme="light"]) {
  --hero-cta-fill:       #0496D7;
  --hero-cta-fill-hover: #1EA6E3;
  --hero-cta-rim:        var(--hero-wash-line);
  --hero-toggle-rim:     rgba(var(--hero-wash-ink-rgb), 0.38);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --hero-cta-fill:       #003E5A;
    --hero-cta-fill-hover: #00344B;
    --hero-cta-rim:        var(--hero-wash-ink);
    --hero-toggle-rim:     var(--hero-wash-line);
  }
}
/* THE BRACE ABOVE WAS MISSING (found Wave 12, introduced by the CTA block in
   #572). Everything below it — `.qf-proc-band` from #573 and BOTH hero-switch
   token blocks — was therefore nested inside `@media (prefers-color-scheme:
   light)` and simply did not exist for a visitor with no `data-theme` stamped
   and a dark OS. Wave 11's switch fell back to its light `:root` values on a
   near-black band in that state; Wave 12's knob resolved to nothing at all and
   the control rendered invisible, which is how it was caught. The rules below
   are unchanged — they only apply in every colour scheme now, as each was
   written to. */

/* ══ THE PROCESSOR BADGE'S OWN LABELLED BAND ═════════════════════════════════
   siteChrome.ts#FOOTER_PAY_ROW. Measured defect: at 320/375/390/414 in both
   themes the strip wrapped SIX marks then the Stripe badge alone — a 6+1 break,
   which is exactly the no-orphan rule ("a pill/badge group never wraps leaving
   a single item alone") the six-tile list's own `flex-wrap: nowrap` exists to
   honour. It was never a wrap in the CSS sense: `.qf-payrow-methods` is a
   COLUMN below 720px and the badge simply took the third line. It READ as an
   orphan because the badge wears the same 1px/6px/22px `.qf-paymark` tile as
   the six above it, so the eye counts seven identical tiles and sees one left
   behind.

   THE FIX IS A LABEL, NOT A 4+3 BREAK. Folding the badge into `.qf-paymarks`
   for a 4+3 wrap would put the PROCESSOR inside the list that answers "what can
   I pay with", which is the one thing STRIPE_MARK's note argues at length that
   we must not say — Stripe is not a payment method. So the badge keeps its
   place outside the list and gains the strip's own `.qf-payrow-label` idiom,
   the same treatment "Accepted payments" already has. The line now carries TWO
   elements, the separation is STATED rather than inferred from a gap, and the
   six-tile row keeps nowrap. Verified 6 / 1+1 at 320, 375, 390 and 414 and
   7 on one row at 768 and 1440, in dark, light and both system states.

   No new tokens, no new radius, no shadow: the band is pure layout. */
.qf-proc-band {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
}

/* ── Wave 12: THE AUDIENCE SWITCH IS ONE OPAQUE KNOB ──────────────────────
   The owner on Wave 11: "It doesn't look crisp and minimalistic. Do something
   with it."

   Wave 11 drew the control out of three TRANSLUCENT layers stacked on a
   generated raster with visible film grain — a 10% black track, a 22% black
   capsule, a 70% white rim — and translucency over grain has no hard edge by
   construction. Pixel-sampled on the band the capsule measured 1.45:1 (light)
   / 1.63:1 (dark) against the ground it abuts, which is a smudge of shadow
   rather than an object, and the rim composited to #B6D1DD, a hazy blue-grey.
   Wave 12 deletes the track and the rim and makes the one remaining layer
   opaque. Three soft shapes become one hard one.

   ── THE KNOB IS THE INK RAMP, NOT THE BAND ────────────────────────────────

   Wave 11's capsule was capped at 22% because 32% composited to #003D59, and
   the hero CTA is #003E5A: the two measured 1.01:1 and the switch became a
   second dark button above the conversion button. That cap is the reason it
   could never be crisp, and the escape is not a different alpha — it is
   leaving the band's hue entirely.

   The CTA is a SATURATED hue-198 fill; the knob is the page's own NEAR-NEUTRAL
   ink ramp. Pixel-sampled off the rendered page, in CIE Lab:

     light   knob --ink        #020618  L*  1.9  chroma  9.8
             CTA  #003E5A               L* 24.3  chroma 22.5   dE2000 19.25
     dark    knob --muted-soft #64748B  L* 48.3  chroma 14.5
             CTA  #0496D7               L* 58.7  chroma 43.4   dE2000 18.24

   A dE2000 of ~18-19 is far past the "clearly different colours" threshold
   (~5), against the 1.01:1 near-identity Wave 11 was avoiding and the
   1.14-1.30:1 the translucent capsule shipped with. A near-neutral and a
   saturated azure are not read as the same object, which is what lets the knob
   be fully opaque without competing for primacy. Note the knob-vs-CTA
   LUMINANCE ratio (1.76 light / 1.44 dark) is a dead axis here and is
   deliberately not quoted as the separation: the light CTA is so dark that
   pure black is only 1.87:1 from it, so NO fill of any colour could score well
   on that measure. Hue and chroma are what separate these two.

   Both values are existing ramp tokens, so this adds zero new hues — the same
   discipline Wave 11 claimed, reached by stepping out of the band rather than
   down into it.

   ── THE KNOB CARRIES WCAG 1.4.11 ON ITS OWN, WHICH IS WHAT PAYS FOR ───────
      DELETING THE RIM

   Wave 11 kept a rim because a 22% capsule cannot identify a control; the rim
   measured 3.34-3.82:1 and did that job. Opaque, the knob itself measures
   3.0-3.7:1 against the band pixel it abuts, sampled at 320/375/1440 in both
   themes. So the rim is not removed and hoped for — it is removed because the
   thing it was covering for now clears the bar without it. Dark is the tight
   end (3.0): the band there is near-black, so the knob has to step UP, and the
   window where it is both >=3:1 on the band and still carries a white label at
   >=4.5:1 is narrow. `--muted-soft` sits at the bottom of that window, which is
   the point in it with the most headroom on the LABEL — the reading that gets
   checked on every visit.

   ── BOTH LABELS ARE FULL HERO INK ────────────────────────────────────────

   Wave 11 stepped the inactive label to 86% white. That measured 5.03:1 only
   because it sat on the track's own 10% black ground; on the BARE band the
   same 86% is 4.35:1 at 375/light (an AA failure) and 88% is exactly 4.50. So
   there is no room for an opacity step here — and no need for one. An opaque
   black knob does not need a second, weaker voice repeating what it says. One
   measured signal instead of two, one of which was failing.

   BOTH LABELS ARE PURE WHITE, NOT `--hero-wash-ink`. In light the two are the
   same value (`--accent-ink` is #FFFFFF), but in DARK `--hero-wash-ink`
   resolves to `--ink` = #F8FAFC, and #F8FAFC on the #64748B knob measures
   4.48:1 — under AA by two hundredths. Pure white on the same knob is 4.70:1.
   `--hero-wash-ink-rgb` is the 255,255,255 the whole hero-wash block is built
   on and is not theme-switched, so `rgb(...)` of it is the one expression that
   is white in both themes. It is a token, not a hex literal at a call site.

   Appended at EOF for baseline-protocol.md rule 1 (the guards key file:line).
   Hand-audited: no raw #fff/#000 below, no gradients, no shadows, no lengths at
   all (this block is colour only), and the pill radius is unchanged and still
   the system's own fourth idiom (style.css:1862; RADIUS_ALLOWED in
   check-spacing.mjs lists 9999 explicitly).
   ───────────────────────────────────────────────────────────────────────── */
:root {
  --hero-switch-knob:     var(--ink);
  --hero-switch-ink:      rgb(var(--hero-wash-ink-rgb));
  --hero-switch-ink-on:   rgb(var(--hero-wash-ink-rgb));
}
html[data-theme="dark"],
:root:not([data-theme="light"]) {
  --hero-switch-knob:     var(--muted-soft);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --hero-switch-knob:   var(--ink);
  }
}

/* ─────────────────────────────────────────────────────────────────────────
   WAVE 13: THE AUDIENCE SWITCH IS FROSTED — the knob's tint, blur and
   saturation, per theme. The shape, the travel and the labels are unchanged
   from Wave 12; only the material moves.

   WHY THIS IS NOT WAVE 11 AGAIN. Wave 11 made the knob 22% black and the owner
   rejected it as not crisp: the hero band is a RASTER with grain, and at 22%
   the grain read straight through a shape that then had no edge. Frost is a
   different mechanism — `backdrop-filter: blur()` flattens the grain BEFORE the
   tint composites over it — so the failure mode Wave 11 hit does not exist
   here. The tint is still 92% opaque, which is the safety margin: only 8% of an
   already-blurred backdrop reaches the surface.

   THE OPAQUE KNOB IS STILL THE BASE DECLARATION. `--hero-switch-knob` is
   untouched and unconditional in landing-hero-fixes-v2.css; these tokens are
   consumed only inside `@supports (backdrop-filter: ...)`. A browser without
   backdrop-filter renders Wave 12 exactly. That is why the tint tokens are
   allowed to carry alpha where `--hero-switch-knob` may not: one is an
   enhancement, the other is the floor.

   THE VALUES ARE THE MOBILE MENU'S. 0.92 tint / 40px blur / 180% saturate are
   `--qf-menu-tint` / `--qf-menu-blur` / `--qf-menu-sat` (nav-unify.css:1490,
   1500-1501), so the site has ONE glass treatment rather than two. The tint's
   rgb triplets are the two values `--hero-switch-knob` already resolves to —
   light `--ink` #020618 and dark `--muted-soft` #64748B — written out because
   `rgba()` needs the channels, not the colour. Same three-block theme structure
   as Wave 12 above: default (light), dark-or-unstamped, then the
   `prefers-color-scheme: light` correction for the unstamped visitor.

   ── `--hero-switch-gloss` IS AN EDGE TOKEN, NOT AN ELEVATION SHADOW ──────

   The tint alone is nearly invisible against a near-uniform band; what makes a
   surface read as glass is its LIT EDGE. `--hero-switch-gloss` is that edge: a
   1px specular highlight painted with `outline` (see landing-hero-fixes-v2.css
   for why `outline` rather than `box-shadow` or `border`).

   It is filed here, next to the material, and NOT in the elevation ramp. The
   three shadows this system allows are `--qf-shadow-sm`, `--qf-shadow-md` and
   `--qf-shadow-lift` (style.css:1836-1838) — each an offset plus a blur, each
   one saying "this surface is lifted above that one". A zero-offset, zero-blur
   1px line at an edge is not making that claim and is not a fourth entry in
   that ramp. Those three tokens are unchanged, and nothing in the audience
   switch references any of them. Keep it that way: if a future edit wants
   ELEVATION on this control it must take one of the three, not extend this.

   White in both themes, at two alphas, because it is a highlight — the light
   catching a raised edge — rather than a colour: 0.38 over the near-black
   light-theme knob, 0.30 over the lighter dark-theme knob, so the rim reads at
   the same strength on both. Alpha is fine here for the same reason it is fine
   on the frost tint: this whole block is consumed only inside `@supports`.

   Appended at EOF for baseline-protocol.md rule 1 (the guards key file:line).
   Hand-audited: no gradients, no shadows, no lengths off the ramp (40px is a
   filter radius and 1px an edge, not spacing), no raw hex, and no radius.
   ───────────────────────────────────────────────────────────────────────── */
:root {
  --hero-switch-frost:      rgba(2, 6, 24, 0.92);
  --hero-switch-frost-blur: 40px;
  --hero-switch-frost-sat:  180%;
  --hero-switch-gloss:      rgba(255, 255, 255, 0.38);
}
html[data-theme="dark"],
:root:not([data-theme="light"]) {
  --hero-switch-frost:      rgba(100, 116, 139, 0.92);
  --hero-switch-gloss:      rgba(255, 255, 255, 0.30);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --hero-switch-frost:    rgba(2, 6, 24, 0.92);
    --hero-switch-gloss:    rgba(255, 255, 255, 0.38);
  }
}
