/* ============================================================================
   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; }
/* ⚠️ HEADING LEVEL IS STRUCTURE, SIZE IS STYLE. migrating.html went h1 -> h3
   with no h2, which is a real navigation break for anyone moving by heading. The
   three cards are h2 now; this keeps them looking exactly as they did. */
.as-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; }

/* ⚠️ A LINK INSIDE RUNNING TEXT MUST NOT BE COLOUR-ALONE (WCAG 1.4.1).
   The rule above is right for chrome — nav, buttons, footer, the brand — and
   wrong for prose. Measured against the surrounding text it sits in: 1.20:1 in
   the dark section, 1.27:1 on stocky.html, 2.97:1 on privacy and terms. The
   threshold is 3:1, so all four fail, and privacy/terms fail by 0.03 — close
   enough to look fine and still be unusable to anyone who does not separate
   those two greens. Underlining prose is the fix that does not touch the palette.

   ⚠️ NO `li a` HERE, AND THAT IS DELIBERATE. It was in the first version of this
   rule and it was a defect: EVERY <li><a> on this site is chrome, not prose. It
   underlined all four nav links on four pages plus the legal footers (24 anchors),
   and because `.nav-links a:hover` sets `text-decoration: none` at higher
   specificity, the nav underline INVERTED ON HOVER — present at rest, gone when
   you pointed at it. Measured across all six pages: prose links inside a <li> = 0.
   `p a` covers every one of them. If a prose list ever needs this, scope it to
   that list rather than reopening `li a`; e2e/site-link-affordance.spec.ts guards it. */
p a, dd a, figcaption a, cite a, blockquote a {
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
/* Prose links are underlined at rest, so hover needs its own signal — without
   this, pointing at a link changes nothing at all. */
p a:hover, dd a:hover, figcaption a:hover, cite a:hover, blockquote a:hover {
  text-decoration-thickness: 2px;
}

/* ---- 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); }
/* inline-block is load-bearing for how the clause wraps beside "with Stocked,";
   the transition and the .out state left with the rotator. */
.rot span { display: inline-block; }
.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; }
/* The panel is a <div role="tabpanel"> wrapping a <figure> — ARIA does not allow
   tabpanel on a figure that has a figcaption. Kill the figure's UA margin so the
   extra nesting is invisible. */
.gal-panel figure { margin: 0; }
.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; }
/* ⚠️ NO `opacity` HERE. At .82 the --red-text #A33636 composites to #B25656
   against --red-soft: 4.09:1, under the 4.5:1 that 12.5px text needs. Opacity on
   text is a contrast change that never shows up in the palette. Full strength is 5.69:1. */
.quote cite { display: block; margin-top: 8px; font-size: 12.5px; font-style: normal; }
/* ⚠️ `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 th` above is a COLUMN header — 11px, uppercase, tracked out. The
   reorder table's first column is a ROW header (th[scope=row]) so a screen reader
   announces "Supplier MOQ: 150", and it must keep the body-cell look or the table
   reads as seven stacked headings. */
.ledger tbody th[scope="row"] {
  padding: 13px 20px; border-bottom: 1px solid var(--border);
  text-align: left; font-size: 14px; font-weight: 400;
  letter-spacing: 0; text-transform: none; color: inherit;
}
.ledger tbody tr:last-child th[scope="row"] { border-bottom: 0; }
.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); }
/* ⚠️ NO `position: sticky` HERE, AND IT IS NOT AN OVERSIGHT. It used to say
   `sticky; top: 0`, which never once engaged: sticky resolves against the nearest
   SCROLLPORT, that is `.cmp-wrap`, and `.cmp-wrap` has no height constraint anywhere
   in this file — measured scrollHeight 1284 === clientHeight 1284, so it never
   scrolls vertically and the offset never applies. Dead code that read as a
   solved problem. The column labels genuinely do leave the screen while you read
   row 20; fixing that needs a real height on the scrollport or page-level sticky,
   and is a design change rather than a property. */
.cmp thead th { 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; }
/* ⚠️ `tbody:last-of-type`, NOT `tbody`. The three group headings each open their own
   <tbody> so `scope="rowgroup"` means what it says, which turns "last row of a
   tbody" into four rows rather than one. Without this the last line of every
   section loses its rule and the table grows three invisible gaps. */
.cmp tbody:last-of-type tr:last-child td { border-bottom: 0; }
/* ⚠️ THE FEATURE COLUMN IS A ROW HEADER, AND THE PLAN COLUMNS ARE ANSWERS TO IT.
   As <td> a screen reader in table mode announced "Growth. Yes." for 21 of 23
   rows — the answer without the question, on the page where a merchant chooses
   between $39, $89 and $179. As th[scope=row] it announces "Automatic repricing,
   Growth, Not included."
   These rules exist ONLY to keep the cell looking exactly as it did: <th> is bold
   and centred by UA default, and `.cmp td:first-child` (which set the colour)
   stops matching the moment the tag changes. */
.cmp tbody th[scope="row"] { font-weight: 400; text-align: left; color: var(--t2); }
.cmp tbody:last-of-type tr:last-child th { border-bottom: 0; }
/* A group heading labels the rows beneath it, so it is a header, not a data cell. */
/* ⚠️ The section headings are th[colspan=4]; pinning the CELL would cover the
   whole scrollport, so the pin goes on the text inside it — sticky clamps within
   its containing block, which is exactly the wanted behaviour. Without this, a
   phone reader scrolled right keeps every feature name and loses every section
   boundary: pinned labels interrupted by empty grey bands. */
.cmp .grp th > span { display: inline-block; position: sticky; left: 18px; }
.cmp .grp th { background: var(--subtle); font-size: var(--text-label); font-weight: 600; letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--t3); text-align: left; }
/* ⚠️ THE ROW HEADER HAS TO SURVIVE THE HORIZONTAL SCROLL, OR IT IS ONLY A HEADER
   TO A SCREEN READER. table.cmp is min-width 720px inside a ~311px scrollport at
   375px, so a merchant who scrolls right to read Pro loses the feature name
   entirely — the same "answer without the question" this table was just fixed for
   non-visually. It needs a background or the cells slide under it.
   The group headings are scope="rowgroup" and span all four columns, so this
   selector deliberately does not reach them. */
.cmp tbody th[scope="row"], .cmp thead th:first-child {
  position: sticky; left: 0; z-index: 1; background: var(--surface);
  /* ⚠️ THE EDGE IS A box-shadow, NOT A border-right. This table is
     `border-collapse: collapse`, where borders belong to the table rather than
     the cell, and a sticky cell that has translated away from its static
     position does not reliably carry them — the three engines have each shipped
     a different wrong answer to that over the years. A shadow is painted by the
     cell and travels with it. */
  box-shadow: 1px 0 0 var(--border);
}
/* ⚠️ PINNING A 300px COLUMN INSIDE A 278px SCROLLPORT HIDES THE TABLE.
   The first column sizes to its longest feature name ("Stockout risk, low stock,
   oversold, selling below cost") and auto layout gave it 300px. Measured
   remainder — scrollport width minus the pinned column — before this rule:

     320px  -22px   375px  33px   414px  72px   620px  278px   768px  402px

   Negative at 320: the pin was WIDER than the scrollport, so scrolling right
   revealed nothing and the table was strictly worse than before it was pinned.
   A plan column needs about 90px. Capping and wrapping the label below 620px
   gives 93 / 148 / 187px, which is one plan column at the narrowest and two by
   375px. ⚠️ The cost is real and is not hidden: the labels wrap, so rows go from
   48px to 71px and the table from 1377px to 1725px on a phone. The alternative
   was to switch the pin off below 620px, which removes the regression without
   fixing anything on the width where people actually scroll. */
@media (max-width: 619px) {
  .cmp tbody th[scope="row"] { max-width: 150px; white-space: normal; }
}
.cmp thead th:first-child { background: var(--subtle); z-index: 2; }
.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); }
/* ⚠️ WAS `outline: none` WITH AN --accent-soft RING. That ring is 1.14:1 against
   the white card — WCAG 2.2 (1.4.11) wants 3:1 — so it threw away the outline the
   :focus-visible rule near the top of this file already gives every other control.
   ⚠️ THAT OUTLINE IS 4.54:1 HERE, NOT THE 5.50:1 IT MEASURES ON WHITE. It sits at
   `outline-offset: 2px`, so its backdrop is whatever is behind the control, and
   `.cta` sets no background — the ring lands on --canvas #EBE9E6, not --surface.
   4.54:1 against canvas, 4.84:1 against the --accent-soft ring it abuts. Both clear
   the 3:1 floor. Keep the tinted ring as decoration and let the outline come back. */
.claim-form input[type="email"]:focus { 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. ⚠️ THE DATE IN THIS COMMENT WAS 1 SEPTEMBER 2026 AND IS NO LONGER THE DATE.
    The campaign was extended on 2026-09-01, deliberately, because the strip was
    switching itself off on the morning its audience peaked. Four comments across
    four files each asserted that same past date, which does not read as
    "superseded, go and ask" — it reads as "overdue, someone forgot", and it is
    confident, four-way corroborated, and describes the removal procedure well
    enough to be carried out correctly. The failure mode is a tidy, complete
    deletion of a campaign that was extended on purpose.
    THE CAMPAIGN ENDS WHEN THE STRIP'S `GONE` DATE PASSES — index.html, in the
    Stocky strip script, which carries the date and the reasoning together. Delete
    this when that date has passed, not before.
   Goes 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 */
/* ⚠️ FOUR COLUMNS THEN TWO, NEVER THREE. This was repeat(3) built for three
   cards; the fourth — "FROM 1 SEPTEMBER 2026", the only one marked `.now`, the
   only one addressing today's reader, the only one carrying a link — wrapped
   onto a second row alone in the left third, its red `.now` border reading as
   an error state rather than emphasis.
   ⚠️ auto-fit was the obvious fix and it is the WRONG ONE for a 4-item list:
   measured with minmax(215px), 1280 and 1024 gave four across but 900px gave
   THREE, orphaning the same card again one breakpoint lower. An even column
   count can never orphan an even list, so the steps are explicit. */
.timeline { list-style: none; margin: 40px 0 0; padding: 0; display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
@media (max-width: 1080px) { .timeline { grid-template-columns: repeat(2, 1fr); } }
@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; }
  * { 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;
}
