/* ============================================================================
   withstocked.com — the marketing site's one stylesheet.

   ⚠️ PROVENANCE. Everything above the "MARKETING LAYER" heading is the PRODUCT's
   design system, copied verbatim:

     tokens   ← Stocked Design System/tokens/{colors,typography,spacing,
                elevation,motion}.css, which are themselves verbatim from
                web/src/index.css
     base     ← Stocked Design System/tokens/base.css, MINUS the app-only
                utilities (.field, .btn-*, .stock-table, .cell-view, trays,
                modals). A marketing page has no toolbar and no workspace table.

   **If these ever disagree with `web/src/index.css`, that file wins** — it is the
   one CI guards with `npm run check:palette`, and the guard checks both themes
   plus that DESIGN.md §1 still agrees with it.

   ⚠️ Inlined into ONE file on purpose. An @import chain costs a round-trip
   before first paint, and this is the page paid traffic lands on. Fonts are
   SELF-HOSTED from assets/fonts/ for the same reason — see the @font-face block
   below. ⚠️ This is where the site DIVERGES from web/index.html, which still
   links Google's CDN; the app is behind a login and does not pay for LCP.

   ⚠️ NO `vh`/`vw` RULE DOES NOT APPLY HERE. That rule exists because the APP
   scales itself with CSS `zoom` on <html> and `vh` does not track `zoom`. This
   site has no display-size control, so plain viewport units are correct — and
   unlike the app, this page IS a scroller.
   ============================================================================ */

/* ============================================================================
   FONTS — self-hosted, 2026-08-04.

   ⚠️ Previously three connections to Google before first paint (preconnect to
   fonts.googleapis.com, preconnect to fonts.gstatic.com, then the stylesheet
   itself — and that stylesheet has to ARRIVE before the font files can even be
   requested). That is a serial round-trip chain on the page paid traffic lands
   on. These now come from the same origin as the HTML.

   ⚠️ THE BLOCKS BELOW MIRROR GOOGLE'S OWN CSS EXACTLY — one @font-face per
   family × weight × subset, with Google's unicode-range strings unaltered. IBM
   Plex Sans is served as a VARIABLE font (one file answers 400/500/600, proven
   by identical md5s), so it was tempting to collapse those three into
   `font-weight: 400 600`. That changes how the browser resolves a weight, and
   the point of this change is fewer connections, NOT different rendering. The
   three blocks share one file; only the URL changed.
   Verified by measuring one string at each weight before and after:
   400/500/600 = 782.61 / 794.78 / 802.80px, unchanged.

   ⚠️ Mono is three real static files. Its weights CANNOT be checked by width,
   because a monospace advance does not vary with weight — three distinct md5s
   is the evidence there.

   Only latin + latin-ext ship. Google's cyrillic, greek and vietnamese subsets
   are never selected by this content, and unicode-range means a browser would
   not download them anyway.

   `font-display: swap` is Google's default here and is kept: text paints
   immediately in the fallback and reflows, which on a marketing page beats a
   blank hero.

   ⚠️ Licence: OFL 1.1, assets/fonts/LICENSE.txt — it must ship WITH the fonts.
   To regenerate: fetch the css2 URL with a modern UA, keep latin + latin-ext,
   rewrite the urls. Do not hand-edit the unicode-ranges.
   ============================================================================ */

@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-sans-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: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-sans-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;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-sans-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: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-sans-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;
}
@font-face {
  font-family: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-sans-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: 'IBM Plex Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-sans-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;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-mono-400-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: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-mono-400-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;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-mono-500-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: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-mono-500-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;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-mono-600-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: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('assets/fonts/ibm-plex-mono-600-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;
}

/* ============================================================================
   TOKENS — colour
   Light = :root, dark = [data-theme="dark"].

   ⚠️ THE RULE THAT CATCHES EVERYONE: a coloured indicator sitting on --canvas
   uses the *-text hue, never the raw one. Raw --accent measures 2.52:1 on the
   canvas and fails WCAG 1.4.11's 3:1. Raw hues are for fills INSIDE a card.
   ============================================================================ */
:root {
  /* Surfaces — the canvas is the DARKEST light surface. Chrome recedes,
     content comes forward. canvas↔surface ΔL* 7.6 in light, 4.4 in dark.
     A white card on a white page does not look like Stocked. */
  --canvas: #EBE9E6;
  --surface: #FFFFFF;
  --subtle: #F7F5F2;
  --hover: #F5F3F0;
  --sel: #EAF6F0;
  --zebra: #FCFAF6;

  /* Borders — elevation is border-first. */
  --border: #DAD8D5;
  --border-strong: #908F8D;

  /* Text tiers — every tier clears 4.5:1 on all six backdrops. */
  --t1: #17221E;
  --t2: #4C5D57;
  --t3: #5E6C66;

  /* Accent — petrol green. Green means money kept, not "success". */
  --accent: #14A870;
  --accent-hover: #0F915F;
  --accent-soft: #E4F4ED;
  --accent-text: #0C784F;
  --accent-solid: #0E8556;
  --accent-solid-hover: #0C7A50;

  /* Gold — warning, "low" */
  --gold: #BD831F;
  --gold-soft: #F7EEDC;
  --gold-text: #8C6011;
  --gold-cell: #FBF5E8;

  /* Red — critical, "out" */
  --red: #C74A4A;
  --red-soft: #F9E9E9;
  --red-text: #A33636;
  --red-cell: #FCF0F0;

  /* Blue — information and orientation, NEVER selection */
  --blue: #648FFF;
  --blue-soft: #E7EEFB;
  --blue-text: #2C55B4;

  --grid: #DAD8D5;
  --overlay: rgba(23, 34, 30, 0.40);
  --scroll-thumb: #B0BBB6;
  --scroll-thumb-hover: #828F8A;
  color-scheme: light;
}

[data-theme="dark"] {
  color-scheme: dark;
  --canvas: #0F161D;
  --surface: #151F29;
  --subtle: #1B2733;
  --hover: #1C2934;
  --sel: #16302B;
  --zebra: #131B24;

  --border: #26343F;
  --border-strong: #38495A;

  --t1: #E7EDF2;
  --t2: #9AABB8;
  --t3: #75889B;

  --accent: #17B87B;
  --accent-hover: #22C98A;
  --accent-soft: rgba(23, 184, 123, .15);
  --accent-text: #43D096;
  --accent-solid: #0E8556;
  --accent-solid-hover: #0C7A50;

  --gold: #CE9A3C;
  --gold-soft: rgba(206, 154, 60, .16);
  --gold-text: #E0AE5F;
  --gold-cell: rgba(206, 154, 60, .10);

  --red: #E06060;
  --red-soft: rgba(224, 96, 96, .14);
  --red-text: #F09090;
  --red-cell: rgba(224, 96, 96, .09);

  --blue: #3E6DD6;
  --blue-soft: rgba(100, 143, 255, .15);
  --blue-text: #89A9FF;

  --grid: #26343F;
  --overlay: rgba(4, 10, 16, 0.55);
  --scroll-thumb: #405264;
  --scroll-thumb-hover: #5A6F86;
}

/* ============================================================================
   TOKENS — type, spacing, elevation, motion
   ⚠️ Weights 400 / 500 / 600 only. NEVER above 600 — the app has no black
   weight and a marketing page set in 800 looks like a different product.
   ============================================================================ */
:root {
  --font-display: "IBM Plex Sans", system-ui, sans-serif;
  --font-body: "IBM Plex Sans", system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, monospace;

  --text-meta: 11px;
  --text-label: 12px;
  --text-table: 13px;
  --text-body: 14px;
  --text-subtitle: 16px;
  --text-section: 18px;

  --weight-body: 400;
  --weight-ui: 500;
  --weight-emphasis: 600;

  --tracking-label: 0.06em;
  --tracking-body: -0.01em;

  /* Grid: 4 · 8 · 12 · 16 · 24 · 32 · 48 · 64 */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;
  --space-16: 64px;

  /* Radius: 6 controls · 8 medium containers · 12 cards/panels · 999 pills only */
  --radius-field: 6px;
  --radius-md: 8px;
  --radius-card: 12px;
  --radius-pill: 999px;

  --shadow: 0 8px 24px rgba(23, 34, 30, .10);
  --shadow-card: 0 1px 2px rgba(19, 35, 44, 0.07), 0 6px 20px rgba(19, 35, 44, 0.12);

  /* Two durations, one easing. Colour and opacity only — never layout. */
  --dur-1: 120ms;
  --dur-2: 180ms;
  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
}

[data-theme="dark"] {
  --shadow: 0 12px 32px rgba(0, 0, 0, .45);
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 28px rgba(0, 0, 0, 0.45);
}

/* ============================================================================
   BASE — the slice of the app's utility layer a marketing page actually uses.
   Class names and every numeric value are unchanged from the product.
   ============================================================================ */

/* THE ledger rule — every numeral renders in IBM Plex Mono. The mono numerals
   are a BRAND ASSET, not a technical detail: they are why a column of prices
   lines up, and they signal "this tool takes numbers seriously" before a word
   is read. Use them in marketing too — headlines, pricing, screenshots. */
.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* Section / table headers — 12px uppercase, 0.06em tracking. */
.hdr { font-size: var(--text-label); font-weight: 600; letter-spacing: var(--tracking-label); text-transform: uppercase; }

/* A lighter table for small, non-sortable displays. */
.table-plain { width: 100%; border-collapse: collapse; font-size: var(--text-table); }
.table-plain th { padding: 0.5rem 1rem; font-size: var(--text-label); font-weight: 600; letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--t2); text-align: left; white-space: nowrap; border-bottom: 1px solid var(--border); }
.table-plain td { padding: 0.5rem 1rem; border-bottom: 1px solid var(--border); vertical-align: middle; }
.table-plain tbody tr:last-child td, .table-plain tbody tr:last-child th { border-bottom: 0; }

/* Scrollbars — the OS overlay paints a near-white thumb over the dark canvas. */
* { scrollbar-width: auto; scrollbar-color: var(--scroll-thumb) transparent; }
*::-webkit-scrollbar { width: 14px; height: 14px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background-color: var(--scroll-thumb); border-radius: 9999px; border: 2px solid transparent; background-clip: padding-box; }
*::-webkit-scrollbar-thumb:hover { background-color: var(--scroll-thumb-hover); }

@media (prefers-reduced-motion: no-preference) {
  button, a, summary { transition: background-color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease), opacity var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease); }
  body { transition: background-color var(--dur-2) var(--ease); }
}

/* --accent-text, NOT --accent: the ring sits at a 2px offset so its backdrop is
   whatever is behind the control — often --canvas, where raw --accent is 2.52:1
   and fails WCAG 1.4.11. This exact defect shipped once already. */
:where(button, a, input, select, textarea, summary, [role="button"], [tabindex]):focus-visible {
  outline: 2px solid var(--accent-text);
  outline-offset: 2px;
}

/* ============================================================================
   MARKETING LAYER
   Palette unchanged from the tokens above; only the type gets bigger.
   Weight never exceeds 600 (BRAND.md §5).
   ============================================================================ */

:root { --site-max: 1160px; --site-pad: 32px; }

/* Skip link — 37 focusable elements sit between the top of the page and the
   content, and a keyboard user met every one of them on every page. Off-screen
   until focused, then a normal button in the top-left. */
.skip {
  position: absolute; left: 8px; top: -60px; z-index: 100;
  background: var(--accent-solid); color: #fff; text-decoration: none;
  padding: 10px 16px; border-radius: var(--radius-field); font-size: 15px; font-weight: 500;
  transition: top var(--dur-1) var(--ease);
}
.skip:focus { top: 8px; text-decoration: none; }
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--canvas);
  color: var(--t1);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}
img { display: block; max-width: 100%; }
.wrap { max-width: var(--site-max); margin: 0 auto; padding: 0 var(--site-pad); }
section { position: relative; }

/* ---- type ---- */
/* ⚠️ Marketing headlines are 34–48px at weight 600 — the design system's own
   scale (readme §3). The DS marketing layer shipped h1 at clamp(44,6.2vw,76)
   and h2 at up to 52, which overruns it: at 76px the positioning sentence took
   FIVE lines in the hero's 540px column and pushed the lede, both CTAs and the
   trust strip below the fold on a 1440×900 laptop. That headline was two
   rotating words when the scale was set; ours is a 61-character sentence. */
h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -.03em; line-height: 1.05; }
h1 { font-size: clamp(34px, 3.4vw, 48px); line-height: 1.1; }
h2 { font-size: clamp(28px, 2.8vw, 38px); letter-spacing: -.025em; line-height: 1.12; }
h3 { font-size: 21px; letter-spacing: -.015em; line-height: 1.25; }
.lede { margin: 20px 0 0; font-size: clamp(17px, 1.5vw, 20px); line-height: 1.55; color: var(--t2); max-width: 620px; }
.eyebrow { display: inline-flex; align-items: center; gap: 9px; margin: 0 0 20px; font-family: var(--font-mono); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: .1em; color: var(--accent-text); }
.eyebrow::before { content: ""; width: 20px; height: 2px; background: currentColor; }
.big-num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-weight: 500; letter-spacing: -.04em; line-height: .9; }
a { color: var(--accent-text); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---- nav ---- */
.nav { position: sticky; top: 0; z-index: 60; background: color-mix(in srgb, var(--canvas) 88%, transparent); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); border-bottom: 1px solid transparent; transition: border-color var(--dur-1) var(--ease); }
.nav.scrolled { border-bottom-color: var(--border); }
.nav-in { display: flex; align-items: center; justify-content: space-between; gap: 24px; height: 68px; }
.brand { display: flex; align-items: center; gap: .35em; font-size: 20px; font-weight: 600; letter-spacing: -.02em; color: var(--t1); }
.brand:hover { text-decoration: none; }
.nav-links { display: flex; gap: 26px; list-style: none; margin: 0; padding: 0; }
.nav-links a { font-size: 15px; font-weight: 500; color: var(--t2); }
.nav-links a:hover { color: var(--t1); text-decoration: none; }
.nav-cta { display: flex; align-items: center; gap: 12px; }
@media (max-width: 900px) { .nav-links { display: none; } }

/* ---- buttons (marketing scale — taller than the app's 32px) ---- */
.b { display: inline-flex; align-items: center; justify-content: center; gap: 8px; height: 46px; padding: 0 22px; border: none; border-radius: 8px; font-family: inherit; font-size: 15.5px; font-weight: 500; white-space: nowrap; cursor: pointer; transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease); }
.b:hover { text-decoration: none; }
.b-primary { background: var(--accent-solid); color: #fff; }
.b-primary:hover { background: var(--accent-solid-hover); }
.b-secondary { background: var(--surface); border: 1px solid var(--border-strong); color: var(--t1); }
.b-secondary:hover { background: var(--hover); }
.b-ghost { background: transparent; color: var(--t2); }
.b-ghost:hover { color: var(--t1); }
.b-lg { height: 54px; padding: 0 28px; font-size: 17px; }
.b-sm { height: 38px; padding: 0 16px; font-size: 14px; }
.b-on-dark { background: var(--accent); color: #08130E; }
.b-on-dark:hover { background: #22C98A; }
.b-outline-dark { background: transparent; border: 1px solid rgba(255, 255, 255, .22); color: #E7EDF2; }
.b-outline-dark:hover { border-color: rgba(255, 255, 255, .5); }

/* ---- surfaces ---- */
.paper { background: var(--surface); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.pad { padding: clamp(64px, 8vw, 112px) 0; }
.pad-sm { padding: clamp(48px, 5vw, 72px) 0; }
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-card); box-shadow: var(--shadow-card); }
.dark { background: #0F161D; color: #E7EDF2; }
.dark h2 { color: #fff; }
.dark .lede { color: #9AABB8; }
.dark .eyebrow { color: #43D096; }

/* ---- hero ---- */
.hero { padding: clamp(56px, 7vw, 92px) 0 clamp(48px, 5vw, 72px); }
.hero-grid { display: grid; grid-template-columns: minmax(0, 1.02fr) minmax(0, .98fr); gap: 56px; align-items: center; }
@media (max-width: 1000px) { .hero-grid { grid-template-columns: 1fr; gap: 40px; } }
/* The "with Stocked, <clause>" device, DEMOTED from the <h1> to a sub-line.
   ⚠️ It used to BE the h1, so first paint read "with Stocked, the bricks carry
   $1.80 of freight" and the positioning sentence existed only in <title> —
   BRAND revision §12 defect #3. The h1 is now static and declarative; this
   keeps the wordplay and the rule that every clause ends in a real number. */
.rot-line { display: flex; flex-wrap: wrap; gap: .35em; margin-top: 18px; font-size: clamp(18px, 1.7vw, 22px); font-weight: 500; letter-spacing: -.02em; line-height: 1.3; }
.rot-line .with { color: var(--t3); }
.rot { display: block; color: var(--accent-text); }
.rot span { display: inline-block; transition: opacity 200ms var(--ease), transform 200ms var(--ease); }
.rot span.out { opacity: 0; transform: translateY(-8px); }
.trust { display: flex; gap: 20px; flex-wrap: wrap; margin-top: 28px; font-size: 14.5px; color: var(--t2); }
.trust span { display: inline-flex; align-items: center; gap: 7px; }

/* ---- mini workspace ---- */
.mini { overflow: hidden; padding: 0; }
.mini-bar { display: flex; align-items: center; gap: 8px; padding: 11px 14px; border-bottom: 1px solid var(--border); background: var(--subtle); }
.mini-bar i { width: 8px; height: 8px; border-radius: 50%; background: var(--border-strong); opacity: .55; }
.mini-bar .t { margin-left: 6px; font-family: var(--font-mono); font-size: 11px; color: var(--t3); }
/* ⚠️ The table scrolls INSIDE the card; the page never scrolls sideways.
   .mini carries overflow:hidden for the rounded corners, which on a phone
   silently CLIPPED the last three columns — a truncated table with no way to
   reach the rest reads as a broken screenshot, not a preview. */
/* ⚠️ A SCROLLER MUST LOOK LIKE ONE. These three tables scroll inside their own
   card, which is the right pattern for a wide table — but on TOUCH an overlay
   scrollbar only appears WHILE scrolling, so a phone user sees a table that
   simply stops. Measured hidden width: `.cmp-wrap` 387px at 375px on pricing,
   `.mini-scroll` 187px. That is the same "gesture nobody advertised" the
   gallery scroller was removed for, and it would have been inconsistent to fix
   one and leave these.

   The classic pure-CSS answer: two `local` gradients that scroll away with the
   content, and two `scroll` shadows pinned to the edges. The shadow at an edge
   is visible only while there is more content past it, so it appears exactly
   when it means something and never on a table that already fits. No JS, no
   resize listener, nothing to keep in sync. */
.scroll-x { overflow-x: auto; }
.mini-scroll { overflow-x: auto; }
.mini table { width: 100%; min-width: 520px; border-collapse: collapse; font-size: 13px; }
/* 10px, not 14: six columns × two sides, the hero card's own column is ~508px
   wide at 1440, and at 14px the table needed 565 — so the Risk chips sat behind
   a scrollbar on a desktop hero. It still scrolls on a phone, by design. */
.mini th { padding: 9px 10px; font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--t2); text-align: left; background: var(--subtle); border-bottom: 1px solid var(--border); white-space: nowrap; }
.mini td { padding: 0 10px; height: 42px; border-bottom: 1px solid var(--border); white-space: nowrap; }
.mini tbody tr:nth-child(even) { background: var(--zebra); }
.mini tbody tr:last-child td { border-bottom: 0; }
.mini .r { text-align: right; }
.chip { display: inline-flex; align-items: center; gap: 5px; height: 20px; padding: 0 8px 0 6px; border-radius: 999px; font-size: 11.5px; font-weight: 500; white-space: nowrap; }
.chip svg { flex-shrink: 0; }
.chip-ok { background: var(--accent-soft); color: var(--accent-text); }
.chip-low { background: var(--gold-soft); color: var(--gold-text); }
.chip-out { background: var(--red-soft); color: var(--red-text); }
.mini-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 14px; border-top: 1px solid var(--border); background: var(--subtle); font-size: 12px; color: var(--t3); }

/* ---- the three-panel sequence ----
   ⚠️ This was a 300vh sticky crossfade. It is now a static row, per the
   2026-08-03 BRAND revision §12 defect #7 and the design system's own motion
   standard: colour and opacity only, no scroll reveals, no entrance animation
   on content. The three panels still carry the before → receiving → after
   story; they just all exist at once, which also means a reader who lands
   mid-page is never looking at a frame that has not faded in yet. */
.seq { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 48px; align-items: start; }
@media (max-width: 1000px) { .seq { grid-template-columns: 1fr; } }
.seq-panel { display: flex; flex-direction: column; gap: 14px; }
.seq-panel .step { font-family: var(--font-mono); font-size: 12px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--t3); }
.seq-panel h3 { font-size: 20px; }
.seq-panel p { margin: 0; font-size: 15px; color: var(--t2); line-height: 1.55; }

/* ---- the workspace shot, at a size it can actually be read at ----
   ⚠️ This used to sit INSIDE the third panel of the row above, in a slot the
   same size as the two still-life photographs beside it: 352px wide for a
   1600px capture — 22% scale, at which the table's 11px type renders at 2.4px.
   The one asset a merchant is here to evaluate was decoration.
   §7a ranks a real screenshot ABOVE photography and says imagery "may never
   stand in for a screenshot of the workspace" — so the photographs came out
   and the screenshot got the full column. */
.shot { margin: 48px 0 0; }
.shot img { width: 100%; height: auto; border: 1px solid var(--border); border-radius: var(--radius-card); box-shadow: var(--shadow-card); background: var(--surface); }
.shot figcaption { margin-top: 14px; font-size: 13.5px; color: var(--t3); line-height: 1.6; max-width: 760px; }
.shot figcaption b { color: var(--t2); font-weight: 500; }

/* ---- the screenshot gallery ----
   A merchant evaluating an operational tool wants to see the screen THEY would
   live in, and it is not always the one we lead with — so the screens are
   named and pickable rather than hidden behind a swipe.
   ⚠️ CROSSFADE, NOT A SLIDE. The design system's motion standard is colour and
   opacity only, never layout (readme §3). Panels are stacked in one grid cell
   so the container sizes to the tallest and nothing reflows on switch.
   ⚠️ Every panel stays in the DOM with its alt text — hiding by opacity rather
   than removing keeps all eight images readable to a crawler, which a carousel
   that swaps one `src` does not. */
.gallery { margin: 44px 0 0; }
.gal-tabs { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.gal-tabs button {
  border: 1px solid var(--border); background: var(--surface); color: var(--t2);
  border-radius: var(--radius-pill); padding: 8px 15px; font-family: inherit;
  font-size: 14px; font-weight: 500; cursor: pointer; white-space: nowrap;
  transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.gal-tabs button:hover { background: var(--hover); color: var(--t1); }
.gal-tabs button[aria-selected="true"] { background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; }
/* ⚠️ THE STACKED VIEW IS THE DEFAULT, and the overlap is the enhancement.
   This used to be the other way round — panels were absolutely stacked and
   hidden by default, undone by a `.no-js` class that NOTHING SET. With
   scripting off you got one screenshot, seven `display:none`, and eight dead
   pills. Everything below is therefore scoped to `.js`, which the inline
   script in <head> adds before first paint. */
.gal-stage { display: grid; }
.gal-panel { margin: 0 0 32px; }
.gal-panel:last-child { margin-bottom: 0; }
.js .gal-panel { grid-area: 1 / 1; opacity: 0; visibility: hidden; transition: opacity var(--dur-2) var(--ease); margin: 0; }
.js .gal-panel[data-active] { opacity: 1; visibility: visible; }
.gal-panel img { width: 100%; height: auto; border: 1px solid var(--border); border-radius: var(--radius-card); box-shadow: var(--shadow-card); background: var(--surface); }
.gal-panel figcaption { margin-top: 14px; font-size: 13.5px; color: var(--t3); line-height: 1.6; max-width: 820px; }
.gal-panel figcaption b { color: var(--t2); font-weight: 500; }
/* ⚠️ The tab strip is USELESS without scripting, so it does not render without
   it. The old rule keyed on `.no-js` and therefore never fired — the pills
   shipped to every no-JS visitor, looking clickable and doing nothing.
   `html:not(.js)` fires when the inline script did not run, which is the
   condition actually meant. */
html:not(.js) .gal-tabs { display: none; }

/* ⚠️ WINDOWS HIGH CONTRAST: which tab is selected was conveyed ONLY by
   `background: var(--accent-solid)` + white text, and forced-colors overrides
   both. Measured with forcedColors:'active' — selected and unselected came back
   byte-identical: bg rgb(255,255,255), fg rgb(0,0,0), border rgb(0,0,0). A
   low-vision user could reach all eight and not tell which one they were on.

   Two signals, deliberately, because each covers a different failure: the
   system `Highlight` pair is honoured inside forced-colors and carries the
   colour, and the thicker border survives even where a UA ignores the first.
   ⚠️ No `forced-color-adjust: none` — that opts out of the user's palette,
   which is the whole thing they asked for.

   This is the site's ONLY forced-colors rule; the rest of the page conveys
   state with an icon beside the colour (chips carry ✓/✗/⚠), so it reads. */
@media (forced-colors: active) {
  .gal-tabs button[aria-selected="true"] {
    background: Highlight;
    color: HighlightText;
    border: 3px solid Highlight;
  }
  .gal-tabs button { border-width: 1px; }
}
@media (prefers-reduced-motion: reduce) { .gal-panel { transition: none; } }
/* ⚠️ THIS WAS A ONE-ROW HIDDEN-SCROLLBAR SCROLLER, AND IT MADE MOST OF THE
   GALLERY UNREACHABLE. Measured on the shipped page: at 375px SIX of the eight
   tabs sat outside the visible box, at 480px five, at 600px four, at 720px
   three. The row scrolled, but `scrollbar-width: none` removed the only thing
   telling anyone it did — so on touch you had to guess to drag, and on a
   desktop window narrowed below 720px there was no way to reach them at all.
   Keyboard users were fine (the tablist handles ArrowLeft/Right/Home/End),
   which is exactly why this survived: the accessible path worked and the two
   ordinary ones did not.

   The original problem it solved was real — eight full-size pills wrapped to
   FOUR rows, 168px of chrome before the reader reaches the screenshot they came
   for. So the pills get smaller instead of the row getting cleverer: wrapping
   costs a little height, and nothing is hidden behind a gesture nobody
   advertised.

   ⚠️ `site:check` passed 73/73 throughout, and structurally had to: it asserts
   the PAGE never scrolls sideways, and `inScroller()` deliberately exempts
   anything inside an overflow-x container. It was built to ignore this element.
   Reachability is a different question and now has its own guard. */
@media (max-width: 720px) {
  .gal-tabs { gap: 6px; margin-bottom: 14px; }
  .gal-tabs button { padding: 6px 11px; font-size: 13px; }
}

/* ---- capability grid ---- */
.caps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 48px; }
@media (max-width: 900px) { .caps { grid-template-columns: 1fr; } }
.cap-card { padding: 26px; display: flex; flex-direction: column; gap: 12px; }
.cap-card .k { font-family: var(--font-mono); font-size: 11px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--t3); }
.cap-card h3 em { font-style: normal; color: var(--accent-text); }
.cap-card p { margin: 0; font-size: 15px; color: var(--t2); line-height: 1.55; }
.cap-card .fig { margin-top: auto; padding-top: 14px; border-top: 1px solid var(--border); display: flex; align-items: baseline; gap: 10px; }
.cap-card .fig .big-num { font-size: 30px; color: var(--t1); }
.cap-card .fig span { font-size: 12.5px; color: var(--t3); line-height: 1.35; }

/* ---- landed cost ---- */
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 56px; align-items: center; }
@media (max-width: 1000px) { .split { grid-template-columns: 1fr; gap: 36px; } }
.quote { margin: 22px 0 0; border-radius: var(--radius-card); background: var(--red-soft); color: var(--red-text); padding: 18px 20px; font-size: 16.5px; line-height: 1.5; }
.quote cite { display: block; margin-top: 8px; font-size: 12.5px; font-style: normal; opacity: .82; }
/* ⚠️ `overflow: hidden` here CLIPPED THE LANDED COLUMN — the product's entire
   pitch — on every phone, with no way to reach it. Measured on index.html:
   154px cut at 320, 114px at 360, 99px at 375 ("$41.11" sitting 98px past the
   card's right edge), 60px at 414, 44px at 430, clean from 480. The cells lost
   were "+ Freight", "Landed", "$3.11", "$41.11", "$0.77", "$10.17" — a merchant
   on a phone saw the unit cost and not the answer.

   ⚠️ It is `hidden`, not `auto`, so there was no scroll, no keyboard route and
   no screen-reader route. Strictly worse than the gallery scroller that
   prompted this branch, which at least scrolled.

   Neither site guard could see it: site-responsive exempts anything inside an
   overflow-x container (correct for a real scroller, wrong here), and
   site-reachable only looks at tablists. Found by an independent review.

   Same fix, same file, 115 lines up: `.mini` had the identical
   overflow-hidden-for-radius problem and `.mini-scroll` solved it. The radius
   still needs the clip, so the SCROLLER goes on an inner wrapper. */
.ledger { padding: 0; overflow: hidden; }
.ledger-scroll { overflow-x: auto; }
.ledger-h { display: flex; align-items: center; justify-content: space-between; padding: 14px 20px; border-bottom: 1px solid var(--border); font-family: var(--font-mono); font-size: 11.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--t2); }
.ledger table { width: 100%; border-collapse: collapse; font-size: 14px; }
.ledger th { padding: 10px 20px; font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--t2); text-align: right; border-bottom: 1px solid var(--border); }
.ledger th:first-child { text-align: left; }
.ledger td { padding: 13px 20px; border-bottom: 1px solid var(--border); text-align: right; }
.ledger td:first-child { text-align: left; }
.ledger tbody tr:last-child td { border-bottom: 0; }
.ledger .land { font-weight: 600; color: var(--accent-text); }
.ledger-f { padding: 14px 20px; border-top: 1px solid var(--border); background: var(--subtle); font-size: 13px; color: var(--t2); }

/* ---- dark band ---- */
.queue { border: 1px solid rgba(255, 255, 255, .1); border-radius: var(--radius-card); background: #151F29; padding: 20px; }
.queue-h { display: flex; justify-content: space-between; font-family: var(--font-mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: #75889B; margin-bottom: 14px; }
.q-item { border: 1px solid rgba(255, 255, 255, .09); border-radius: 10px; background: rgba(255, 255, 255, .04); padding: 14px 16px; margin-bottom: 10px; }
.q-item:last-child { margin-bottom: 0; }
.q-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 9px; }
.q-name { font-size: 14px; font-weight: 500; color: #fff; }
.q-src { font-family: var(--font-mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: #43D096; background: rgba(23, 184, 123, .13); padding: 3px 8px; border-radius: 999px; }
.q-prices { display: flex; align-items: center; gap: 10px; font-family: var(--font-mono); font-size: 14px; color: #9AABB8; }
.q-old { text-decoration: line-through; }
.q-new { color: #43D096; font-weight: 500; font-size: 16px; }
.q-floor { margin-left: auto; font-size: 11.5px; color: #75889B; }
.q-actions { display: flex; gap: 8px; margin-top: 12px; }
/* The dark band lost its right-hand column when the drawn approval queue came
   out, so it centres and the three points run across instead of down. */
.dark-centre { text-align: center; }
.dark-centre .lede { margin-left: auto; margin-right: auto; }
.points-row { display: grid !important; grid-template-columns: repeat(3, 1fr); gap: 28px; margin-top: 44px !important; text-align: left; }
@media (max-width: 900px) { .points-row { grid-template-columns: 1fr; gap: 18px; } }

.points { list-style: none; margin: 28px 0 0; padding: 0; display: flex; flex-direction: column; gap: 16px; }
.points li { display: flex; gap: 12px; align-items: flex-start; font-size: 16px; color: #C4D0DA; }
.points li b { color: #fff; font-weight: 600; }
.points li svg { flex-shrink: 0; margin-top: 3px; color: #43D096; }

/* ---- limits ---- */
.limits { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 44px; }
@media (max-width: 900px) { .limits { grid-template-columns: 1fr; } }
.limit { padding: 24px; }
.limit h3 { font-size: 18px; }
.limit p { margin: 10px 0 0; font-size: 14.5px; color: var(--t2); line-height: 1.55; }

/* ---- pricing ---- */
.tiers { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 44px; align-items: stretch; }
@media (max-width: 960px) { .tiers { grid-template-columns: 1fr; max-width: 460px; margin-left: auto; margin-right: auto; } }
.tier { display: flex; flex-direction: column; padding: 28px; }
.tier.feat { border: 1px solid var(--accent); }
.tier-top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.tier-name { font-size: 19px; font-weight: 600; letter-spacing: -.015em; }
.tier-flag { border-radius: 999px; background: var(--accent-soft); color: var(--accent-text); padding: 3px 11px; font-size: 12px; font-weight: 500; }
.tier-for { margin: 6px 0 20px; font-size: 13.5px; color: var(--t3); min-height: 40px; }
.tier-price { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.tier-price .big-num { font-size: 46px; }
.tier-price .was { font-family: var(--font-mono); font-size: 17px; color: var(--t3); text-decoration: line-through; }
.tier-price .per { font-size: 14px; color: var(--t3); }
.tier-lock { align-self: flex-start; margin-top: 12px; border-radius: 999px; background: var(--gold-soft); color: var(--gold-text); padding: 4px 12px; font-size: 12.5px; font-weight: 500; }
.tier ul { list-style: none; margin: 24px 0 26px; padding: 0; display: flex; flex-direction: column; gap: 11px; flex: 1; }
.tier li { display: flex; gap: 10px; font-size: 14.5px; color: var(--t2); line-height: 1.45; }
.tier li svg { flex-shrink: 0; margin-top: 3px; color: var(--accent-text); }
/* ⚠️ The li is a flex row, so EVERY element child becomes a flex item — an
   inline <strong> in the copy was being laid out as its own item with a 10px
   gap either side, dropping "every 8 hours" onto a line of its own mid-sentence.
   The text must live in exactly one child. This span is that child; keep it
   even on bullets with no emphasis, or the next <strong> reintroduces the bug. */
.tier li > span { min-width: 0; }
.tier li strong { color: var(--t1); font-weight: 600; }
.tier .b { width: 100%; }

/* ---- plan comparison table ----
   Mirrors the matrix in web/src/pages/Settings.tsx, which is the only tier
   differentiation that exists in the product. Ten rows of "every plan" before
   the six that differ is the honest shape of this product, and the reason the
   table is here rather than three cherry-picked bullet lists. */
.cmp-wrap { overflow-x: auto; margin-top: 44px; border: 1px solid var(--border); border-radius: var(--radius-card); background: var(--surface); box-shadow: var(--shadow-card); }
table.cmp { width: 100%; min-width: 720px; border-collapse: collapse; font-size: 14.5px; text-align: left; }
.cmp th, .cmp td { padding: 12px 18px; border-bottom: 1px solid var(--border); }
.cmp thead th { position: sticky; top: 0; background: var(--subtle); font-size: var(--text-label); font-weight: 600; letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--t2); white-space: nowrap; }
.cmp thead th:not(:first-child), .cmp td:not(:first-child) { text-align: center; }
.cmp tbody tr:last-child td { border-bottom: 0; }
.cmp td:first-child { color: var(--t2); }
.cmp .grp td { background: var(--subtle); font-size: var(--text-label); font-weight: 600; letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--t3); }
.cmp .yes { color: var(--accent-text); font-weight: 600; }
.cmp .no { color: var(--t3); }
.cmp .val { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 13px; color: var(--t1); white-space: nowrap; }
.cmp .col-hi { background: var(--accent-soft); }
.cmp thead .col-hi { background: var(--accent-soft); color: var(--accent-text); }

/* ---- faq ---- */
.faq { max-width: 760px; margin: 44px auto 0; }
.faq details { border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); margin-bottom: 10px; overflow: hidden; }
.faq summary { cursor: pointer; list-style: none; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 17px 20px; font-size: 16px; font-weight: 600; letter-spacing: -.01em; }
.faq summary::-webkit-details-marker { display: none; }
.faq summary svg { flex-shrink: 0; color: var(--t3); transition: transform var(--dur-1) var(--ease); }
.faq details[open] summary svg { transform: rotate(180deg); }
.faq .a { padding: 0 20px 18px; font-size: 15px; color: var(--t2); line-height: 1.6; }
.faq .a p { margin: 0 0 10px; }
.faq .a p:last-child { margin: 0; }

/* ---- founder note ----
   §11's strongest available trust substitute with zero customers: a real name,
   a real face and a signed note saying why the pricing and the limits are both
   published. It sits between "what it doesn't do" and the price on purpose —
   it is the sentence that explains both of them. */
/* ⚠️ TEXT ONLY, by the founder's choice — there is no photograph on this site.
   §11 ranks founder identity FOURTH of six trust substitutes, behind published
   pricing, the limits list and the worked examples, all of which are already on
   the page. And the 35% figure it cites is a stock-photo-versus-founder test,
   not a photo-versus-no-photo one, so the note carries this section on its own.
   Set as a reading column rather than full width: this is a signed letter, and
   a 1,100px measure is unreadable for one. */
.founder-solo { max-width: 760px; }
.founder-note p { margin: 0 0 16px; font-size: 16.5px; line-height: 1.6; color: var(--t2); }
.founder-note p:last-of-type { margin-bottom: 0; }
.founder-note strong { color: var(--t1); font-weight: 500; }
/* The three pain points are a list because they ARE a list — three separate
   jobs that were each hard for their own reason, not one flowing complaint. */
.founder-note ul { margin: 0 0 16px; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 12px; }
.founder-note li { position: relative; padding-left: 18px; font-size: 16.5px; line-height: 1.55; color: var(--t2); }
.founder-note li::before { content: ""; position: absolute; left: 0; top: .62em; width: 6px; height: 6px; border-radius: 50%; background: var(--accent-text); }
.sig { margin-top: 26px; padding-top: 20px; border-top: 1px solid var(--border); display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.sig .name { font-size: 16px; font-weight: 600; color: var(--t1); }
.sig .role { font-size: 13.5px; color: var(--t3); }

/* ---- founding list ----
   ⚠️ NO LIVE COUNTER. §11 reverses the earlier "show a live count"
   recommendation: realistic pre-launch volume is single digits, and a counter
   reading "4 of 50 claimed" tells a stranger nobody else bought. The cap is
   stated as POLICY. Turn a tally on only when the number flatters the product. */
.claim { max-width: 620px; margin: 32px auto 0; }
.claim-form { display: flex; gap: 10px; flex-wrap: wrap; }
.claim-form input[type="email"] { flex: 1; min-width: 240px; height: 54px; padding: 0 18px; border-radius: 8px; border: 1px solid var(--border-strong); background: var(--surface); color: var(--t1); font-family: inherit; font-size: 16px; }
.claim-form input[type="email"]::placeholder { color: var(--t3); }
.claim-form input[type="email"]:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-soft); }
.claim-form input.bad { border-color: var(--red); box-shadow: 0 0 0 2px var(--red-soft); }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.claim-msg { margin-top: 14px; font-size: 15px; min-height: 1.4em; }
.claim-msg.ok { color: var(--accent-text); }
.claim-msg.err { color: var(--red-text); }
.claim-note { margin-top: 18px; font-size: 13.5px; color: var(--t3); line-height: 1.6; }

/* ============================================================================
   CAMPAIGN — Stocky. DELETE THIS BLOCK ON 1 SEPTEMBER 2026, with the banner in
   index.html and the whole of stocky.html. Nothing else depends on it: the
   evergreen page must not leave a hole when the campaign comes out, which is
   the entire reason migration is a section and a page rather than the position
   (BRAND.md §1). migrating.html STAYS — it is evergreen and outlives this.
   ============================================================================ */
.strip { background: var(--t1); color: var(--canvas); font-size: 14.5px; }
.strip-in { display: flex; align-items: center; justify-content: center; gap: 14px; flex-wrap: wrap; padding: 10px 0; text-align: center; }
.strip a { color: #7FE3B4; font-weight: 500; }
.strip .when { font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: #E8C77A; }
.strip button { flex-shrink: 0; background: none; border: 0; color: var(--canvas); opacity: .6; cursor: pointer; font-size: 18px; line-height: 1; padding: 4px 8px; border-radius: var(--radius-field); }
.strip button:hover { opacity: 1; }

/* the decline, as dates rather than adjectives */
.timeline { list-style: none; margin: 40px 0 0; padding: 0; display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
@media (max-width: 820px) { .timeline { grid-template-columns: 1fr; } }
.timeline li { padding: 22px; border-radius: var(--radius-card); background: var(--surface); border: 1px solid var(--border); }
.timeline li.now { border-color: var(--red); }
.timeline .date { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 13px; font-weight: 600; letter-spacing: .04em; color: var(--t3); }
.timeline li.now .date { color: var(--red-text); }
.timeline .what { margin-top: 8px; font-size: 15.5px; font-weight: 500; color: var(--t1); line-height: 1.4; }
.timeline .note { margin-top: 6px; font-size: 13.5px; color: var(--t2); line-height: 1.5; }

/* the exact columns — a merchant is going to build a file from this, so it is
   set in mono and matches SUPPLIER_KEYS / OPENING_KEYS character for character */
.cols { margin-top: 16px; display: flex; flex-wrap: wrap; gap: 6px; }
.cols code { font-family: var(--font-mono); font-size: 12.5px; background: var(--subtle); border: 1px solid var(--border); border-radius: var(--radius-field); padding: 3px 8px; color: var(--t1); white-space: nowrap; }
.cols code.req { border-color: var(--accent); color: var(--accent-text); }
/* "sku OR barcode" — two green chips side by side read as "both required",
   which is the opposite of the rule. The conjunction has to be visible in the
   chips, not only in the caption underneath them. */
.cols .or { align-self: center; font-size: 12.5px; color: var(--t3); padding: 0 2px; }
.file-card { padding: 26px; }
.file-card h3 { font-size: 19px; }
.file-card p { margin: 10px 0 0; font-size: 14.5px; color: var(--t2); line-height: 1.55; }
.file-card .key { margin-top: 14px; font-size: 12.5px; color: var(--t3); }

/* ⚠️ THESE EXIST BECAUSE AN INLINE STYLE CANNOT BE COLLAPSED BY A MEDIA QUERY.
   Both grids on /stocky were written as `style="grid-template-columns:repeat(2,1fr)"`,
   which outranks `@media (max-width:900px) { .caps/.limits { …: 1fr } }` at every
   width — so the campaign page stayed two-up on a phone and hung 64px off the
   right edge at 375 while the pages using the SAME classes were fine.
   ⚠️ The two rules must live together and AFTER the base ones: `.caps-2` and
   `.caps` have equal specificity, so ordering is the whole mechanism. Keeping
   both here also keeps the campaign self-contained for the 1 Sept deletion. */
.caps-2, .limits-2 { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 900px) { .caps-2, .limits-2 { grid-template-columns: 1fr; } }

/* ---- cta + footer ---- */
.cta { text-align: center; }
.cta .lede { margin-left: auto; margin-right: auto; }
.cta-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; margin-top: 32px; }
.foot { padding: 44px 0 40px; font-size: 14px; color: var(--t3); }
.foot-in { display: flex; justify-content: space-between; gap: 28px; flex-wrap: wrap; align-items: center; }
.foot-links { display: flex; gap: 22px; flex-wrap: wrap; }
.foot-links a { color: var(--t3); }
.foot-links a:hover { color: var(--t1); }
.foot-legal { margin-top: 26px; padding-top: 22px; border-top: 1px solid var(--border); font-size: 12.5px; line-height: 1.65; color: var(--t3); }

/* ============================================================================
   LEGAL PAGES — privacy.html, terms.html
   A long-form reading column, not a marketing page. Scoped to `body.legal` so
   the marketing type scale (h1 up to 48px) does not turn a privacy policy into
   a billboard. These shipped with 41 lines of their own inline CSS, their own
   palette and Archivo 800 — a merchant who clicks "Privacy" from the footer
   should not arrive somewhere that looks like a different company.
   ============================================================================ */
body.legal { font-size: 17px; line-height: 1.65; }
.legal .wrap { max-width: 760px; }
.legal header { border-bottom: 1px solid var(--border); padding: 22px 0; }
.legal header .brand { font-size: 20px; }
.legal main { padding: 56px 0 80px; }
.legal h1 { font-size: clamp(30px, 3vw, 38px); font-weight: 600; letter-spacing: -.025em; line-height: 1.15; margin-bottom: 12px; }
.legal h2 { font-size: 22px; font-weight: 600; letter-spacing: -.015em; line-height: 1.3; margin: 44px 0 14px; }
.legal h3 { font-size: 17px; font-weight: 600; letter-spacing: -.01em; margin: 26px 0 8px; }
.legal p { margin: 0 0 15px; }
.legal .updated { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 13.5px; color: var(--t3); margin-bottom: 14px; }
.legal .lede { font-size: 19px; color: var(--t2); margin: 0 0 40px; max-width: none; }
.legal ul { margin: 0 0 15px 22px; }
.legal li { margin-bottom: 8px; }
.legal code { font-family: var(--font-mono); font-size: .88em; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-field); padding: 1px 5px; }
.legal .callout { background: var(--accent-soft); border: 1px solid var(--border); border-left: 4px solid var(--accent-solid); border-radius: var(--radius-md); padding: 18px 22px; margin: 24px 0; }
.legal .callout strong { color: var(--accent-text); }
.legal .note { background: var(--gold-soft); border: 1px solid var(--border); border-left: 4px solid var(--gold); border-radius: var(--radius-md); padding: 18px 22px; margin: 24px 0; }
.legal .callout p:last-child, .legal .note p:last-child { margin-bottom: 0; }
.legal table { width: 100%; border-collapse: collapse; margin: 18px 0; font-size: 15.5px; }
.legal th, .legal td { text-align: left; padding: 11px 12px; border-bottom: 1px solid var(--border); vertical-align: top; }
.legal th { font-weight: 600; background: var(--subtle); border-bottom: 1px solid var(--border-strong); }
.legal td:first-child { white-space: nowrap; font-weight: 500; }
.legal footer { border-top: 1px solid var(--border); padding: 32px 0 48px; font-size: 14.5px; color: var(--t3); }
.legal footer a { color: var(--t3); }
.legal footer a:hover { color: var(--t1); }
.legal .footer-links { display: flex; gap: 22px; flex-wrap: wrap; list-style: none; margin: 0 0 14px; padding: 0; }
@media (max-width: 620px) {
  body.legal { font-size: 16px; }
  .legal td:first-child { white-space: normal; }

  /* ⚠️ THE HEADER OVERFLOWED THE VIEWPORT ON EVERY NON-LEGAL PAGE UNTIL
     2026-08-04 — index, pricing, migrating by 65px and stocky by 76px, at 375.
     `.nav-in` IS `.wrap` (one element, both classes), so at --site-pad 32 a
     375px screen leaves 311px of content box, and brand 107 + gap 24 + the two
     buttons 253 needs 384. `.b` is `white-space: nowrap` and a flex item will
     not shrink below min-content, so the CTA simply hung off the right edge.
     ⚠️ The `.nav-links` rule at 900px hides the LINKS only, which is why this
     looked handled and was not. The two legal pages were clean throughout
     because they carry no nav CTA — checking one page of each kind would have
     found this, and checking only >=1024 found nothing at all. */
  :root { --site-pad: 20px; }
  .nav-cta .b-ghost { display: none; }
}

/* 320px — iPhone SE and the older Androids. Not in the original 375/1024/1440
   target set, but at 320 the brand plus the CTA alone still needed 341px, so
   the page scrolled sideways on the smallest screens people actually carry.
   Header only: the body rhythm at --site-pad 20 is already fine down here. */
@media (max-width: 380px) {
  .nav-in { gap: 12px; }
  .brand { font-size: 18px; }
  .nav-cta .b { height: 42px; padding: 0 14px; font-size: 14.5px; }
}

/* ---- reduced motion ---- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .rot span { transition: none; }
  * { transition-duration: .01ms !important; }
}

/* ⚠️ THE SHADOWS LIVE AT THE END OF THE FILE, AND THAT IS LOAD-BEARING.
   `.scroll-x` and `.cmp-wrap` have equal specificity, and `.cmp-wrap` sets its
   own `background: var(--surface)` ~200 lines further down — so declared
   earlier, the affordance silently lost the cascade on the one table that
   hides the most (387px at 375px on pricing). Measured: 4 gradient layers on
   `.mini-scroll` and `.ledger-scroll`, 0 on `.cmp-wrap`. Anything added below
   this that paints a background on a scroller must come BEFORE it. */
.scroll-x {
  background:
    linear-gradient(to right, var(--surface) 30%, transparent) left center / 32px 100% no-repeat local,
    linear-gradient(to left, var(--surface) 30%, transparent) right center / 32px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, .13), transparent) left center / 12px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .13), transparent) right center / 12px 100% no-repeat scroll;
}
