@layer reset { [hidden] { display: none !important; } }
/* The hidden attribute is UA-origin; any author display rule beats it. Without this,
   app.js setting el.hidden on the pending pills has no visible effect. */
/* =====================================================================
   BREEDHANDBOOK — shared design system
   -------------------------------------------------------------------
   Single stylesheet for breedhandbook.com. Drop-in compatible with the
   live repo: asset paths, font-family names ("Breed Serif" / "Breed
   Sans") and the assets/ tree are unchanged.

   Cascade order is declared once, at the top, in @layer. Nothing in
   this file uses !important except the reduced-motion kill switch and
   the forced-colors block, both of which must win.

   Feature support was verified against api.webstatus.dev on 2026-07-26.
   Every non-Baseline feature is inside @supports and the page is
   complete, legible and sellable with all of them switched off.
   ===================================================================== */

/* ---------------------------------------------------------------------
   0. FONTS — self-hosted, unchanged from the live repo
   --------------------------------------------------------------------- */

@font-face {
  font-family: "Breed Serif";
  src: url("assets/fonts/serif-regular.woff2") format("woff2");
  font-style: normal;
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Breed Serif";
  src: url("assets/fonts/serif-italic.woff2") format("woff2");
  font-style: italic;
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Breed Serif";
  src: url("assets/fonts/serif-semibold.woff2") format("woff2");
  font-style: normal;
  font-weight: 600;
  font-display: swap;
}
@font-face {
  font-family: "Breed Serif";
  src: url("assets/fonts/serif-bold.woff2") format("woff2");
  font-style: normal;
  font-weight: 700;
  font-display: swap;
}
@font-face {
  font-family: "Breed Sans";
  src: url("assets/fonts/sans-medium.woff2") format("woff2");
  font-style: normal;
  font-weight: 500;
  font-display: swap;
}
@font-face {
  font-family: "Breed Sans";
  src: url("assets/fonts/sans-semibold.woff2") format("woff2");
  font-style: normal;
  font-weight: 600;
  font-display: swap;
}
@font-face {
  font-family: "Breed Sans";
  src: url("assets/fonts/sans-bold.woff2") format("woff2");
  font-style: normal;
  font-weight: 700;
  font-display: swap;
}

/* ---------------------------------------------------------------------
   1. @property — registered custom properties
   Baseline "newly" since 2024-07-09 (Chrome 85 / Safari 16.4 / FF 128).
   Unsupported engines ignore the at-rule; var() still resolves, the
   value simply snaps instead of interpolating. Nothing breaks.
   --------------------------------------------------------------------- */

@property --sheen {
  syntax: "<angle>";
  inherits: false;
  initial-value: 104deg;
}
@property --sheen-pos {
  syntax: "<percentage>";
  inherits: false;
  initial-value: -20%;
}
@property --rule-scale {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}
@property --lift {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}
@property --tilt {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

/* ---------------------------------------------------------------------
   2. Cross-document view transitions
   Baseline "limited": Chrome/Edge 126+, Safari 18.2+. Firefox does not
   ship the cross-document form. Unsupported browsers ignore this rule
   and navigate normally — there is nothing to degrade, so it ships bare.
   Both documents must carry it; it lives here so both do.
   MUST stay outside @layer (at-rule, not a style rule).
   --------------------------------------------------------------------- */

@view-transition {
  navigation: auto;
}

/* ---------------------------------------------------------------------
   3. Cascade order. Declared once. Later layers win regardless of
      selector specificity, which is what keeps `.btn` overridable by a
      one-class utility without an arms race.
   --------------------------------------------------------------------- */

@layer reset, tokens, base, layout, components, utilities, motion, a11y;

/* =====================================================================
   LAYER: reset
   ===================================================================== */

@layer reset {
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  * {
    margin: 0;
  }

  html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    tab-size: 4;
  }

  body {
    min-block-size: 100svb;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
  }

  img,
  picture,
  svg,
  video,
  canvas {
    display: block;
    max-inline-size: 100%;
    block-size: auto;
  }

  input,
  button,
  textarea,
  select {
    font: inherit;
    color: inherit;
  }

  button {
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
  }

  a {
    color: inherit;
    text-decoration-thickness: 1px;
    text-underline-offset: 0.18em;
  }

  a,
  button,
  summary {
    -webkit-tap-highlight-color: transparent;
  }

  ul[class],
  ol[class] {
    list-style: none;
    padding: 0;
  }

  summary {
    cursor: pointer;
    list-style: none;
  }
  summary::-webkit-details-marker {
    display: none;
  }

  dialog {
    border: 0;
    padding: 0;
    color: inherit;
    max-inline-size: 100%;
    max-block-size: 100%;
    /* Restores the UA centring that `* { margin: 0 }` above destroys.
       Without this a top-layer <dialog> pins to the top-left corner. */
    margin: auto;
  }

  /* :target scroll-margin removed - html{scroll-padding-block-start} already clears the
   sticky header; applying both double-counted it and left a 144px void. */

}

/* =====================================================================
   LAYER: tokens
   Built from D:/dog_guides/theme/tokens.json. The five brand values
   (ink / green / gold / muted / rule) are verbatim from that file; every
   other value is derived from them with color-mix() in oklab so the
   whole palette moves together if a brand value ever changes.
   color-mix(): Baseline widely available since 2025-11-09.
   ===================================================================== */

@layer tokens {
  :root {
    /* --- Brand primitives (verbatim from theme/tokens.json) --- */
    --ink: #2b2622;
    --green: #2e6f5e;
    --gold: #b67b2e;
    --muted: #6e655c;
    --rule: #d8d2c9;
    --warn: #b3402e;

    /* --- Paper family: warm, never pure white --- */
    --paper: #fbf8f1;
    --paper-2: #f7f0e3;
    --paper-3: #f1e7d6;
    --paper-4: #e9dcc6;
    --card: #fffdf8;

    /* --- Ink family --- */
    --ink-2: color-mix(in oklab, var(--ink) 78%, var(--paper));
    --ink-3: var(--muted);
    --ink-4: color-mix(in oklab, var(--muted) 62%, var(--paper));

    /* --- Green family --- */
    --green-deep: #1d4a3e;
    --green-deeper: #143329;
    --green-bright: #3a8570;
    --green-pale: color-mix(in oklab, var(--green) 14%, var(--paper));
    --green-mist: color-mix(in oklab, var(--green) 7%, var(--paper));

    /* --- Gold family ---
       --gold is 3.38:1 on --paper. That clears WCAG AA for large text
       (>=24px, or >=18.66px bold) and for non-text UI, and fails it for
       body copy. --gold-ink is 5.62:1 and is the ONLY gold allowed on
       small text. Measured, not guessed. */
    --gold-ink: #8c5817;
    --gold-lit: #dda64e;
    --gold-pale: color-mix(in oklab, var(--gold) 16%, var(--paper));

    /* --- Lines --- */
    --line: color-mix(in oklab, var(--ink) 16%, transparent);
    --line-soft: color-mix(in oklab, var(--ink) 9%, transparent);
    --line-strong: color-mix(in oklab, var(--ink) 30%, transparent);
    --line-on-dark: color-mix(in oklab, #ffffff 20%, transparent);

    /* --- Semantic surface roles. Sections re-declare these; every
       component reads them, so a component is correct on any surface
       without a --on-dark variant. This is the mechanism that keeps the
       stylesheet small. --- */
    --surface: var(--paper);
    --on-surface: var(--ink);
    --on-surface-2: var(--ink-2);
    --on-surface-3: var(--ink-3);
    --on-surface-accent: var(--gold-ink);
    --on-surface-line: var(--line);

    /* --- Type --- */
    --serif: "Breed Serif", "Source Serif 4", Georgia, "Times New Roman", serif;
    --sans: "Breed Sans", Montserrat, "Segoe UI", system-ui, sans-serif;

    /* Fluid scale. Each step interpolates linearly between a hand-set
       375px value and a hand-set 1600px value, then clamps at both
       ends. The small end is set from what actually reads on a phone —
       a display line has to fit more than two words per line — not from
       a ratio walked down from the desktop size. */
    --step--2: clamp(0.78rem, 0.77rem + 0.04vi, 0.79rem);
    --step--1: clamp(0.875rem, 0.852rem + 0.1vi, 0.95rem);
    --step-0: clamp(1.031rem, 0.983rem + 0.2vi, 1.19rem);
    --step-1: clamp(1.125rem, 1.01rem + 0.49vi, 1.5rem);
    --step-2: clamp(1.281rem, 1.081rem + 0.86vi, 1.94rem);
    --step-3: clamp(1.438rem, 1.103rem + 1.43vi, 2.53rem);
    --step-4: clamp(1.656rem, 1.148rem + 2.17vi, 3.32rem);
    --step-5: clamp(1.938rem, 1.18rem + 3.23vi, 4.41rem);
    --step-6: clamp(2.313rem, 1.214rem + 4.69vi, 5.9rem);
    --step-7: clamp(2.625rem, 1rem + 6.94vi, 7.94rem);

    --leading-tight: 1.04;
    --leading-snug: 1.14;
    --leading-normal: 1.5;
    --leading-loose: 1.62;

    --tracking-display: -0.022em;
    --tracking-tight: -0.012em;
    --tracking-wide: 0.07em;
    --tracking-caps: 0.13em;

    /* Measure caps. These are NOT page-width caps — the page itself is
       uncapped. These bound the reading measure of a text block in ch so
       a 3440px monitor does not produce 40-word lines. */
    --measure: 62ch;
    --measure-narrow: 48ch;
    --measure-lede: 42ch;
    --measure-display: 17ch;
    --measure-h1: 19ch;
    --measure-h2: 26ch;   /* 20ch forced short clauses onto three lines on desktop */

    /* --- Space: fluid scale --- */
    --space-3xs: clamp(0.25rem, 0.24rem + 0.06vi, 0.3rem);
    --space-2xs: clamp(0.5rem, 0.47rem + 0.12vi, 0.59rem);
    --space-xs: clamp(0.75rem, 0.71rem + 0.19vi, 0.89rem);
    --space-s: clamp(1rem, 0.94rem + 0.25vi, 1.19rem);
    --space-m: clamp(1.5rem, 1.41rem + 0.37vi, 1.78rem);
    --space-l: clamp(2rem, 1.88rem + 0.5vi, 2.38rem);
    --space-xl: clamp(3rem, 2.82rem + 0.75vi, 3.56rem);
    --space-2xl: clamp(4rem, 3.76rem + 1vi, 4.75rem);
    --space-3xl: clamp(6rem, 5.65rem + 1.5vi, 7.13rem);

    /* Vertical rhythm of a full-width band.
       The ceiling is set to the value the curve reaches at 1440px, NOT to a
       larger number reached later. Every content block in the page is capped
       in ch or rem, so past 1440 extra viewport width buys nothing
       horizontally — if the band kept growing to 10.5rem the page would get
       TALLER on a bigger monitor (measured: landing 10806px @1440 ->
       11157px @1920, +49.28px per band and nothing else moving). Freezing at
       the 1440 value leaves every width up to and including 1440 untouched
       and makes 1920 render at the same height. */
    --band: clamp(4.5rem, 3.2rem + 6.4vi, 8.96rem);
    --band-tight: clamp(3rem, 2.2rem + 4vi, 5.8rem);

    /* --- Page frame. NO max-width on the page. The gutter grows with
       the viewport instead, so an ultrawide monitor gets more air, not
       a letterboxed 1180px column. --- */
    --gutter: clamp(1.15rem, 0.6rem + 4.4vi, 6.5rem);
    /* Content measure. `.shell` is already `inline-size: 100%`, so below this it is
       purely fluid and there is NO margin at all — the page behaves exactly as it does
       today. The cap only starts to bite above a ~2000px window, and it caps at the
       content width a 2000px window produces (1790px measured). That matters because
       every --step-* is saturated by ~1600px of viewport: hold the content at 1790 and
       a 2560 screen renders the hero identically to a 2000px window, headline at 94.4px
       on two lines and the fan at 350. The surplus becomes symmetric page margin.
       Resize down and the margin shrinks to nothing, then the responsive rules take
       over — no snap, because there is nothing to snap. */
    --shell-max: 112rem;
    --header-h: clamp(4rem, 3.6rem + 1.6vi, 5.25rem);
    --announce-h: 2.25rem;

    /* --- Radii --- */
    --r-xs: 0.25rem;
    --r-s: 0.5rem;
    --r-m: 0.875rem;
    --r-l: 1.25rem;
    --r-xl: 2rem;
    --r-pill: 999rem;
    /* The arch: a book-plate shape used for hero art. */
    --r-arch: 50% 50% var(--r-m) var(--r-m) / 34% 34% var(--r-m) var(--r-m);

    /* --- Elevation. Two-part shadows: a tight contact shadow plus a
       wide ambient one. Warm-tinted, never neutral grey. --- */
    --shadow-xs: 0 1px 2px color-mix(in oklab, var(--ink) 8%, transparent);
    --shadow-s: 0 1px 2px color-mix(in oklab, var(--ink) 7%, transparent),
      0 6px 16px -6px color-mix(in oklab, var(--ink) 12%, transparent);
    --shadow-m: 0 2px 4px color-mix(in oklab, var(--ink) 6%, transparent),
      0 14px 34px -12px color-mix(in oklab, var(--ink) 18%, transparent);
    --shadow-l: 0 3px 6px color-mix(in oklab, var(--ink) 6%, transparent),
      0 30px 64px -20px color-mix(in oklab, var(--ink) 24%, transparent);
    --shadow-book: 1px 2px 3px color-mix(in oklab, var(--ink) 14%, transparent),
      18px 34px 60px -18px color-mix(in oklab, var(--ink) 34%, transparent);

    /* --- Motion --- */
    --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
    --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
    --ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);
    --dur-fast: 140ms;
    --dur: 260ms;
    --dur-slow: 520ms;

    /* --- Focus --- */
    --focus-ring: var(--gold-ink);
    --focus-ring-on-dark: var(--gold-lit);

    /* --- Icons. Inline SVG as data URIs: no requests, no icon font.
       The check marks are drawn with their stroke baked in (they are
       background-images); the plus and shield are solid fills used
       through mask, so they take currentColor. --- */
    --icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%232e6f5e' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 8.6 6.2 12.3 13.5 4.4'/%3E%3C/svg%3E");
    --icon-check-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23dda64e' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 8.6 6.2 12.3 13.5 4.4'/%3E%3C/svg%3E");
    --icon-plus: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M7 1h2v14H7z'/%3E%3Cpath d='M1 7h14v2H1z'/%3E%3C/svg%3E");
    --icon-shield: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 1 3 3.6v5.9c0 4 3 7.6 7 9.5 4-1.9 7-5.5 7-9.5V3.6L10 1Zm3.6 6.6-4.2 5a.9.9 0 0 1-1.4.05L5.9 10.6a.9.9 0 1 1 1.3-1.2l1.4 1.5 3.6-4.3a.9.9 0 0 1 1.4 1.1Z'/%3E%3C/svg%3E");

    /* Only a light treatment exists. Declared so the UA does not
       auto-darken form controls, scrollbars or the canvas. Rationale is
       in README.md §"Why there is no dark mode". */
    color-scheme: light;

    accent-color: var(--green);
    scrollbar-color: color-mix(in oklab, var(--ink) 28%, transparent) transparent;
  }

  /* Surface roles for dark bands. Any component dropped inside inherits
     correct colours with no modifier class. */
  .on-dark {
    --surface: var(--green-deep);
    --on-surface: #f6f1e6;
    --on-surface-2: color-mix(in oklab, #f6f1e6 82%, var(--green-deep));
    --on-surface-3: color-mix(in oklab, #f6f1e6 68%, var(--green-deep));
    --on-surface-accent: var(--gold-lit);
    --on-surface-line: var(--line-on-dark);
    --focus-ring: var(--focus-ring-on-dark);
    --card: color-mix(in oklab, #ffffff 7%, var(--green-deep));
  }

  .on-cream {
    --surface: var(--paper-2);
  }
  .on-tan {
    --surface: var(--paper-3);
  }
  .on-card {
    --surface: var(--card);
  }
}

/* =====================================================================
   LAYER: base
   ===================================================================== */

@layer base {
  html {
    scroll-padding-block-start: calc(var(--header-h) + var(--space-m));
  }

  body {
    background-color: var(--paper);
    color: var(--ink);
    font-family: var(--serif);
    font-size: var(--step-0);
    line-height: var(--leading-normal);
    font-variant-numeric: oldstyle-nums proportional-nums;
    overflow-x: clip;
  }

  /* ---- Layered background -------------------------------------------
     Three cheap fixed layers, zero images, zero requests:
       1. a wide warm gradient wash (paper is never one flat colour)
       2. two low-chroma colour fields, oklch, kept far from the
          watercolour palette so they sit under the art, not against it
       3. a real fractal-noise grain tile, multiplied
     All three are position:fixed single composited layers that never
     repaint on scroll. Nothing animates here by default.
     ------------------------------------------------------------------ */

  body::before,
  body::after {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
  }

  body::before {
    background:
      radial-gradient(
        62rem 48rem at 8% -8%,
        oklch(88% 0.045 92 / 0.55),
        transparent 68%
      ),
      radial-gradient(
        54rem 44rem at 98% 12%,
        oklch(90% 0.032 168 / 0.5),
        transparent 66%
      ),
      radial-gradient(
        70rem 52rem at 46% 106%,
        oklch(89% 0.038 64 / 0.42),
        transparent 70%
      ),
      linear-gradient(178deg, #fdfbf6 0%, var(--paper) 42%, #f6efe3 100%);
  }

  /* Grain. feTurbulence tile at 220px, desaturated, multiplied at low
     opacity. This is what reads as paper; a dot-pattern reads as dots. */
  body::after {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.86' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23g)'/%3E%3C/svg%3E");
    background-size: 220px 220px;
    mix-blend-mode: multiply;
    opacity: 0.055;
  }

  /* Respect the reduced-transparency preference: drop the grain and
     flatten the wash rather than leaving a busy field. */
  @media (prefers-reduced-transparency: reduce) {
    body::after {
      display: none;
    }
    body::before {
      background: var(--paper);
    }
  }

  /* ---- Typography ---------------------------------------------------
     text-wrap: balance on every heading. Baseline "newly" since
     2024-05-13 (Chrome 114 / FF 121 / Safari 17.5). This is the direct
     fix for the orphaned word the operator called out — combined with
     the ch measure caps below, a display line physically cannot leave
     one word alone on the last line.
     Headings must NOT contain <br>. That is what caused the fault.
     ------------------------------------------------------------------ */

  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    font-family: var(--serif);
    font-weight: 600;
    line-height: var(--leading-tight);
    letter-spacing: var(--tracking-display);
    text-wrap: balance;
    color: var(--on-surface);
  }

  h1 {
    font-size: var(--step-6);
    max-inline-size: var(--measure-h1);
  }
  h2 {
    font-size: var(--step-4);
    line-height: var(--leading-snug);
    max-inline-size: var(--measure-h2);
  }
  h3 {
    font-size: var(--step-2);
    line-height: var(--leading-snug);
    letter-spacing: var(--tracking-tight);
    max-inline-size: 26ch;
  }
  h4 {
    font-size: var(--step-1);
    line-height: var(--leading-snug);
    letter-spacing: var(--tracking-tight);
  }
  h5,
  h6 {
    font-size: var(--step-0);
  }

  /* The editorial accent: an italic clause inside a heading. Carries the
     line break instead of a <br>, so the browser still balances. */
  h1 em,
  h2 em,
  h3 em,
  .display em {
    display: block;
    font-style: italic;
    font-weight: 400;
    color: color-mix(in oklab, var(--on-surface) 88%, var(--gold));
  }

  p {
    text-wrap: pretty; /* limited baseline; a no-op where unsupported */
    max-inline-size: var(--measure);
  }

  p + p {
    margin-block-start: 0.85em;
  }

  strong,
  b {
    font-weight: 600;
  }

  em {
    font-style: italic;
  }

  small {
    font-size: var(--step--1);
  }

  ::selection {
    background: color-mix(in oklab, var(--gold) 30%, transparent);
    color: var(--ink);
  }

  hr {
    border: 0;
    block-size: 1px;
    background: var(--on-surface-line);
    margin-block: var(--space-l);
  }

  code,
  kbd {
    font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
    font-size: 0.86em;
    padding: 0.1em 0.35em;
    border-radius: var(--r-xs);
    background: color-mix(in oklab, var(--on-surface) 7%, transparent);
    white-space: nowrap;
  }
}

/* =====================================================================
   LAYER: layout
   Primitives, not page-specific rules. Everything here is fluid; there
   is no fixed breakpoint outside the two navigation media queries,
   because components size themselves against their CONTAINER.
   Container queries: Baseline widely available since 2025-08-14.
   ===================================================================== */

@layer layout {
  /* The page frame. Full-bleed by construction: width is 100%, the
     gutter grows with the viewport. No --shell max-width anywhere.

     GOTCHA: container-type makes .shell a containing block for
     position:fixed descendants. Keep .mobile-menu, .buy-bar,
     .progress-rail, .skip-link and <dialog> OUTSIDE any .shell. */
  .shell {
    inline-size: 100%;
    /* border-box, so this caps the OUTER box: content lands on --shell-max exactly. */
    max-inline-size: calc(var(--shell-max) + var(--gutter) * 2);
    padding-inline: var(--gutter);
    margin-inline: auto;
    container-type: inline-size;
    container-name: shell;
  }

  /* Escape the gutter for edge-to-edge art inside a shell. */
  .bleed {
    margin-inline: calc(var(--gutter) * -1);
  }

  /* A full-width colour band. Surface role comes from .on-* classes. */
  .band {
    position: relative;
    isolation: isolate;
    padding-block: var(--band);
    background-color: var(--surface);
    color: var(--on-surface);
  }
  .band--tight {
    padding-block: var(--band-tight);
  }
  .band--flush-start {
    padding-block-start: 0;
  }
  .band--flush-end {
    padding-block-end: 0;
  }

  /* A band whose background is transparent so the body wash shows
     through. Use for the paper-coloured sections. */
  .band--open {
    background-color: transparent;
  }

  /* Vertical flow. One rule replaces dozens of margin declarations.
     --flow is DECLARED here rather than left to the var() fallback: it is an
     inheriting custom property, so a fallback can never apply inside another
     stack — a bare `.stack` nested in a `.stack--xl` silently picked up the
     XL flow and set its paragraphs 55.9px apart instead of 27.6px. Declaring
     the default resets it at every level; the --xs/--s/--l/--xl modifiers are
     in this same layer, later in source order, so they still win. */
  .stack {
    display: flex;
    flex-direction: column;
    --flow: var(--space-m);
    gap: var(--flow);
  }
  .stack > * {
    margin-block: 0;
  }
  .stack--xs {
    --flow: var(--space-2xs);
  }
  .stack--s {
    --flow: var(--space-xs);
  }
  .stack--l {
    --flow: var(--space-l);
  }
  .stack--xl {
    --flow: var(--space-xl);
  }

  /* Horizontal grouping that wraps instead of overflowing. */
  .cluster {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--gap, var(--space-s));
  }
  .cluster--between {
    justify-content: space-between;
  }
  .cluster--center {
    justify-content: center;
  }
  .cluster--tight {
    --gap: var(--space-2xs);
  }

  /* Auto grid. On an ultrawide monitor this ADDS columns rather than
     stretching three of them to 900px each — the correct answer to
     "no artificial max-width". --col sets the ideal column width. */
  .grid {
    display: grid;
    gap: var(--gap, var(--space-l));
    grid-template-columns: repeat(
      auto-fit,
      minmax(min(100%, var(--col, 20rem)), 1fr)
    );
  }
  /* .grid--s carries sets of FOUR sibling cards that have to read as four
     identical things. auto-fit sizes its repetition count from an ideal track
     width, and that count is not required to divide the item count: from
     ~840px to ~1128px of shell it resolved to three tracks and stranded the
     fourth card alone beside two empty ones (measured at 1024: cards at
     x 54.6/371.3/687.9 then one at 54.6 on row two, 633px of empty row).
     Counts here are divisors of four only — 1, 2, 4. The four-across state is
     gated at 66rem rather than at the width where four tracks merely fit,
     because at the bare minimum the cards are ~192px of text and the serif
     headings ladder to four lines. */
  .grid--s {
    --col: 14rem;
    grid-template-columns: minmax(0, 1fr);
  }
  @container shell (min-width: 30rem) {
    .grid--s {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
  }
  @container shell (min-width: 66rem) {
    .grid--s {
      grid-template-columns: repeat(4, minmax(0, 1fr));
    }
  }
  .grid--l {
    --col: 27rem;
  }
  .grid--fixed-2 {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 24rem), 1fr));
  }

  /* NOTE: the .grid--aligned subgrid rule lives in @layer components, beside
     .card. Declared here it lost to `.card { display: flex }` in a later
     layer and the subgrid was inert — see "Aligned card rows" below. */

  /* Two-part editorial split. Asymmetry is set by container width, not
     viewport, so the same component works in a sidebar or full bleed. */
  .split {
    display: grid;
    gap: var(--space-xl);
    align-items: var(--split-align, center);
    container-type: inline-size;
    container-name: split;
  }

  @container shell (min-width: 56rem) {
    .split {
      grid-template-columns: var(--split, minmax(0, 1.02fr) minmax(0, 0.98fr));
      gap: clamp(var(--space-xl), 5cqi, var(--space-3xl));
    }
    /* The 0.68fr track carries running prose (or the one picture a band
       exists for) and is floored at 22rem. Without the floor the moment the
       split turns on the intro copy dropped from 438px/4 lines to 287px/6
       lines while the heading beside it sat in a 646px track it can never
       fill — h2 is capped at --measure-h2 (26ch, ~480px there). The floor
       hands back only width the wide column cannot use: at 1024 the wide
       track still measures 517px against a 480px cap, and from ~1330px up
       the floor never binds at all. */
    .split--wide-start {
      --split: minmax(0, 1.32fr) minmax(min(100%, 22rem), 0.68fr);
    }
    .split--wide-end {
      --split: minmax(min(100%, 22rem), 0.68fr) minmax(0, 1.32fr);
    }
    .split--reverse > :first-child {
      order: 2;
    }
  }

  /* Beyond ~150rem the split stops widening the text column and gives
     the extra space to art plus gutter. Prevents ultrawide gigantism. */
  @container shell (min-width: 150rem) {
    .split {
      --split: minmax(0, 0.82fr) minmax(0, 1.18fr);
      column-gap: var(--space-3xl);
    }
    .split--wide-start {
      --split: minmax(0, 1fr) minmax(0, 1fr);
    }
  }

  /* Editorial rail: sticky column beside scrolling content. Short
     sequences only — no pinning, no scroll capture. */
  .rail {
    display: grid;
    gap: var(--space-xl);
    align-items: start;
  }
  /* One hinge for the whole page. This was 62rem while .split above is 56rem,
     and .shell's content box is 0.912*viewport - 19.2 — so at a 1024 viewport
     the shell measures 914.7px (57.2rem) and the rail missed its hinge by
     77px while every split beside it had already gone two-column. The page's
     tallest band ran all ten chapter rows down one ragged column with no
     spine and no sticky intro. 56rem makes rail and split switch together.
     max-block-size is part of the primitive, not an afterthought: a sticky
     column taller than the viewport captures the scroll. */
  @container shell (min-width: 56rem) {
    .rail {
      grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
      column-gap: clamp(var(--space-xl), 6cqi, var(--space-3xl));
    }
    .rail__sticky {
      position: sticky;
      inset-block-start: calc(var(--header-h) + var(--space-l));
      max-block-size: calc(100svb - var(--header-h) - var(--space-2xl));
      overflow: clip auto;
      scrollbar-width: none;
    }
  }

  /* Bound reading measure without bounding the page. */
  .measure {
    max-inline-size: var(--measure);
  }
  .measure--narrow {
    max-inline-size: var(--measure-narrow);
  }
  .measure--wide {
    max-inline-size: 78ch;
  }
  .measure--none {
    max-inline-size: none;
  }

  .center {
    margin-inline: auto;
    text-align: center;
  }
  .center :is(h1, h2, h3, p, .lede) {
    margin-inline: auto;
  }
}

/* =====================================================================
   LAYER: components
   ===================================================================== */

@layer components {
  /* ---------------------------------------------------------------
     Announcement strip
     --------------------------------------------------------------- */
  .announce {
    --surface: var(--green-deeper);
    display: grid;
    place-items: center;
    min-block-size: var(--announce-h);
    background: var(--surface);
    color: #f3ecdf;
    font-family: var(--sans);
    font-size: var(--step--2);
    font-weight: 600;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
  }
  /* The dot, the copy and the arrow are ONE centred inline run, not three
     flex items. As flex siblings, the moment the copy wrapped to two lines
     (390px) the anonymous text item stretched to the full remaining width and
     centred its own lines inside that box, flinging the dot to the far left
     edge of the bar and the arrow to the far right — measured ~34px from the
     dot to the first glyph against an authored 7.5px gap. */
  .announce a {
    display: block;
    align-content: center;
    inline-size: 100%;
    min-block-size: var(--announce-h);
    padding: 0.45rem var(--gutter);
    text-align: center;
    text-decoration: none;
  }
  /* Only the two decorations become inline-blocks. The copy span must stay a
     plain inline: as an atomic inline-block it shrink-wraps to the whole
     remaining width and drops to a line of its own, which turns a two-line
     strip into a four-line one. */
  .announce a > [aria-hidden] {
    display: inline-block;
    vertical-align: middle;
    margin-inline: 0.3em;
  }
  .announce a:hover {
    color: var(--gold-lit);
  }
  .announce__dot {
    inline-size: 0.34em;
    block-size: 0.34em;
    border-radius: 50%;
    background: var(--gold-lit);
    box-shadow: 0 0 0 0.24em color-mix(in oklab, var(--gold-lit) 22%, transparent);
  }

  /* ---------------------------------------------------------------
     Header
     --------------------------------------------------------------- */
  .site-header {
    position: sticky;
    inset-block-start: 0;
    z-index: 90;
    block-size: var(--header-h);
    background: color-mix(in oklab, var(--paper) 82%, transparent);
    border-block-end: 1px solid transparent;
    transition: background var(--dur) var(--ease-out),
      border-color var(--dur) var(--ease-out),
      box-shadow var(--dur) var(--ease-out);
  }

  @supports (backdrop-filter: blur(1px)) {
    .site-header {
      backdrop-filter: blur(18px) saturate(140%);
      -webkit-backdrop-filter: blur(18px) saturate(140%);
    }
  }

  .site-header.is-scrolled {
    background: color-mix(in oklab, var(--paper) 94%, transparent);
    border-block-end-color: var(--line-soft);
    box-shadow: 0 10px 30px -18px color-mix(in oklab, var(--ink) 40%, transparent);
  }

  .header-inner {
    block-size: 100%;
    display: flex;
    align-items: center;
    gap: var(--space-m);
  }

  /* .brand is /disclaimer/'s spelling of .brandmark. See "Legacy class names"
     at the end of this layer. */
  .brandmark,
  .brand {
    display: inline-flex;
    align-items: center;
    flex: 0 0 auto;
    margin-inline-end: auto;
    text-decoration: none;
    border-radius: var(--r-s);
  }
  .brandmark img,
  .brand img {
    inline-size: clamp(9.5rem, 7rem + 8vi, 15rem);
    block-size: auto;
    background: none;
  }

  .nav {
    display: none;
    align-items: center;
    gap: clamp(1rem, 2.2vi, 2.4rem);
    font-family: var(--sans);
    font-size: var(--step--1);
    font-weight: 600;
    white-space: nowrap;
  }
  .nav a {
    position: relative;
    color: var(--ink-2);
    text-decoration: none;
    padding-block: 0.4rem;
  }
  /* --rule-scale is @property-registered, so this interpolates. */
  .nav a::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    inset-block-end: 0;
    block-size: 1.5px;
    background: var(--gold);
    transform: scaleX(var(--rule-scale));
    transform-origin: right;
    transition: --rule-scale var(--dur) var(--ease-out);
  }
  .nav a:hover,
  .nav a:focus-visible {
    color: var(--ink);
  }
  .nav a:hover::after,
  .nav a:focus-visible::after {
    --rule-scale: 1;
    transform-origin: left;
  }
  .nav a[aria-current="page"] {
    color: var(--ink);
  }
  .nav a[aria-current="page"]::after {
    --rule-scale: 1;
  }

  /* Hamburger. Three bars, animated to an X, 48px hit area. */
  .menu-toggle {
    display: grid;
    place-content: center;
    gap: 5px;
    inline-size: 3rem;
    block-size: 3rem;
    margin-inline-end: calc(var(--space-2xs) * -1);
    border-radius: var(--r-s);
  }
  .menu-toggle span[aria-hidden] {
    display: block;
    inline-size: 1.35rem;
    block-size: 1.5px;
    background: var(--ink);
    transition: transform var(--dur) var(--ease-out),
      opacity var(--dur-fast) linear;
  }
  .menu-toggle[aria-expanded="true"] span[aria-hidden]:nth-child(1) {
    transform: translateY(6.5px) rotate(45deg);
  }
  .menu-toggle[aria-expanded="true"] span[aria-hidden]:nth-child(2) {
    opacity: 0;
  }
  .menu-toggle[aria-expanded="true"] span[aria-hidden]:nth-child(3) {
    transform: translateY(-6.5px) rotate(-45deg);
  }

  /* Must come AFTER both base rules: same layer, same specificity, so
     source order decides. Declared earlier this loses to `.menu-toggle
     { display: grid }` and the hamburger shows on desktop. */
  @media (width >= 62rem) {
    .nav {
      display: flex;
    }
    .menu-toggle {
      display: none;
    }
  }

  .header-cta {
    flex: 0 0 auto;
  }

  /* Positioned against the sticky header, NOT the viewport.
     `position: fixed` cannot be used here: .site-header carries a
     backdrop-filter, and a filtered element becomes the containing
     block for its fixed descendants — the panel would collapse to a
     zero-height box and paint no background at all. Anchoring to the
     header is also what removes the need for any JS offset. */
  .mobile-menu {
    position: absolute;
    inset-block-start: 100%;
    inset-inline: 0;
    block-size: calc(100svb - var(--header-h));
    overflow-y: auto;
    overscroll-behavior: contain;
    z-index: 89;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    padding: var(--space-xl) var(--gutter) var(--space-2xl);
    /* Opaque. At 0.97 alpha the hero headline was still legible through
       the panel even with a 22px backdrop blur — large dark type has
       enough contrast to read through 3% transparency. Menu legibility
       beats the frosted effect; the header itself still carries it. */
    background: var(--paper);
    font-family: var(--sans);
    font-size: var(--step-1);
    font-weight: 600;
    opacity: 0;
    translate: 0 -1.5rem;
    visibility: hidden;
    transition: opacity var(--dur) var(--ease-out),
      translate var(--dur) var(--ease-out), visibility 0s linear var(--dur);
  }
  .mobile-menu.is-open {
    opacity: 1;
    translate: 0;
    visibility: visible;
    transition-delay: 0s;
  }
  .mobile-menu a {
    padding-block: var(--space-xs);
    border-block-end: 1px solid var(--line-soft);
    text-decoration: none;
  }
  .mobile-menu a:last-child {
    border-block-end: 0;
    color: var(--gold-ink);
  }

  /* Body scroll lock, no JS class needed. :has() Baseline widely
     available since 2026-06-19. */
  body:has(.mobile-menu.is-open),
  body:has(dialog[open]) {
    overflow: hidden;
  }

  /* ---------------------------------------------------------------
     Buttons
     --------------------------------------------------------------- */
  .btn {
    --btn-bg: var(--ink);
    --btn-fg: var(--paper);
    --btn-line: transparent;
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.55em;
    min-block-size: 3.25rem;
    padding: 0.9rem 1.65rem;
    border: 1px solid var(--btn-line);
    border-radius: var(--r-pill);
    background: var(--btn-bg);
    color: var(--btn-fg);
    font-family: var(--sans);
    font-size: var(--step--1);
    font-weight: 700;
    letter-spacing: 0.015em;
    line-height: 1.2;
    text-align: center;
    text-decoration: none;
    text-wrap: balance;
    isolation: isolate;
    overflow: hidden;
    /* Same reason as .pending-pill: a .stack would otherwise stretch a
       button to full width. .btn--block opts back in deliberately. */
    inline-size: fit-content;
    max-inline-size: 100%;
    translate: 0 var(--lift);
    transition: translate var(--dur) var(--ease-out),
      box-shadow var(--dur) var(--ease-out),
      background-color var(--dur) var(--ease-out),
      border-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
  }

  /* Sheen sweep. --sheen-pos is registered, so the gradient position
     interpolates instead of snapping. Composited-cheap: one element,
     one paint, only on hover/focus. */
  .btn::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(
      var(--sheen),
      transparent 0%,
      color-mix(in oklab, #ffffff 26%, transparent) 46%,
      transparent 62%
    );
    background-size: 260% 100%;
    background-position-x: var(--sheen-pos);
    background-repeat: no-repeat;
    transition: --sheen-pos 620ms var(--ease-out);
  }
  .btn:hover,
  .btn:focus-visible {
    --lift: -2px;
    --sheen-pos: 120%;
  }
  .btn:active {
    --lift: 0px;
    transition-duration: 60ms;
  }

  .btn--primary {
    --btn-bg: var(--gold);
    --btn-fg: #1f1710;
    box-shadow: 0 1px 0 color-mix(in oklab, #ffffff 34%, transparent) inset,
      var(--shadow-m);
  }
  .btn--primary:hover,
  .btn--primary:focus-visible {
    --btn-bg: color-mix(in oklab, var(--gold) 88%, #ffffff);
    box-shadow: 0 1px 0 color-mix(in oklab, #ffffff 40%, transparent) inset,
      var(--shadow-l);
  }

  .btn--green {
    --btn-bg: var(--green);
    --btn-fg: #fbf8f1;
    box-shadow: var(--shadow-m);
  }
  .btn--green:hover,
  .btn--green:focus-visible {
    --btn-bg: var(--green-bright);
  }

  .btn--ink {
    --btn-bg: var(--ink);
    --btn-fg: var(--paper);
  }

  .btn--ghost {
    --btn-bg: transparent;
    --btn-fg: var(--on-surface);
    --btn-line: var(--line-strong);
  }
  .btn--ghost:hover,
  .btn--ghost:focus-visible {
    --btn-bg: color-mix(in oklab, var(--on-surface) 6%, transparent);
    --btn-line: var(--on-surface);
  }

  .btn--sm {
    min-block-size: 2.6rem;
    padding: 0.5rem 1.1rem;
    font-size: var(--step--2);
  }
  .btn--lg {
    min-block-size: 3.75rem;
    padding: 1.05rem 2.2rem;
    font-size: var(--step-0);
  }
  .btn--block {
    inline-size: 100%;
  }

  .btn[aria-disabled="true"],
  .btn:disabled {
    cursor: not-allowed;
    opacity: 0.72;
    --lift: 0px;
  }

  /* Text link with a moving arrow. */
  .link-arrow {
    display: inline-flex;
    align-items: baseline;
    gap: 0.45em;
    font-family: var(--sans);
    font-size: var(--step--1);
    font-weight: 700;
    color: var(--on-surface-accent);
    text-decoration: none;
    padding-block: 0.15em;
    border-block-end: 1.5px solid
      color-mix(in oklab, currentColor 32%, transparent);
    inline-size: fit-content;
    transition: border-color var(--dur) var(--ease-out);
  }
  .link-arrow > span[aria-hidden] {
    transition: translate var(--dur) var(--ease-spring);
  }
  .link-arrow:hover,
  .link-arrow:focus-visible {
    border-block-end-color: currentColor;
  }
  .link-arrow:hover > span[aria-hidden],
  .link-arrow:focus-visible > span[aria-hidden] {
    translate: 0.3em 0;
  }

  /* ---------------------------------------------------------------
     Buy block — CTA + verbatim guarantee, always together
     The guarantee line is a sibling of the CTA inside .buy. Keeping
     them in one component is how the line cannot be forgotten.
     --------------------------------------------------------------- */
  .buy {
    display: flex;
    flex-direction: column;
    align-items: var(--buy-align, flex-start);
    gap: var(--space-xs);
    inline-size: 100%;
  }
  .buy--center {
    --buy-align: center;
    text-align: center;
  }

  /* Block, with the shield riding the first line as an inline mark — NOT an
     inline-flex row with the icon as a sibling. As a sibling, once the line
     wrapped to two lines inside a centred .buy the text item stretched to
     fill and centred itself, leaving the shield stranded ~57px away at the
     far edge of the section, beside the money-back promise. As an inline
     ::before the whole run centres and wraps as one thing at every width and
     in both alignments. */
  .guarantee {
    display: block;
    margin: 0;
    max-inline-size: none;
    font-family: var(--sans);
    font-size: var(--step--1);
    font-weight: 600;
    line-height: 1.35;
    color: var(--on-surface-2);
    text-wrap: balance;
  }
  .guarantee::before {
    content: "";
    display: inline-block;
    vertical-align: -0.16em;
    margin-inline-end: 0.5em;
    inline-size: 1.05em;
    block-size: 1.05em;
    background: currentColor;
    /* Shield + check, inlined as a mask so it takes currentColor. */
    -webkit-mask: var(--icon-shield) center / contain no-repeat;
    mask: var(--icon-shield) center / contain no-repeat;
    opacity: 0.85;
  }

  .buy-note {
    font-family: var(--sans);
    font-size: var(--step--2);
    color: var(--on-surface-3);
    letter-spacing: 0.01em;
  }

  /* Pre-launch state. Driven entirely by CHECKOUT.productKey in app.js;
     no page ever ships a dead buy link. */
  .pending-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.5em;
    padding: 0.32em 0.85em;
    border: 1px solid color-mix(in oklab, var(--on-surface-accent) 40%, transparent);
    border-radius: var(--r-pill);
    background: color-mix(in oklab, var(--on-surface-accent) 9%, transparent);
    color: var(--on-surface-accent);
    font-family: var(--sans);
    font-size: var(--step--2);
    font-weight: 700;
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    /* Without this the pill is stretched to full width by the
       align-items:stretch default of any .stack it is dropped into. */
    inline-size: fit-content;
  }

  /* Price display. Tabular figures so digits do not jitter. */
  .price {
    display: flex;
    align-items: baseline;
    gap: 0.4em;
    font-family: var(--serif);
    font-variant-numeric: lining-nums tabular-nums;
  }
  .price__amount {
    font-size: var(--step-5);
    font-weight: 600;
    line-height: 1;
    letter-spacing: var(--tracking-display);
  }
  .price__unit {
    font-family: var(--sans);
    font-size: var(--step--1);
    font-weight: 600;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    color: var(--on-surface-3);
  }

  /* Sticky mobile buy bar. Carries a scroll link to #buy, never a buy
     button, so it needs no guarantee line beside it. */
  .buy-bar {
    position: fixed;
    inset-inline: 0;
    inset-block-end: 0;
    z-index: 80;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-s);
    padding: 0.7rem var(--gutter)
      calc(0.7rem + env(safe-area-inset-bottom, 0px));
    background: color-mix(in oklab, var(--paper) 92%, transparent);
    border-block-start: 1px solid var(--line-soft);
    box-shadow: 0 -12px 30px -22px color-mix(in oklab, var(--ink) 60%, transparent);
    translate: 0 110%;
    visibility: hidden;
    transition: translate var(--dur-slow) var(--ease-out),
      visibility 0s linear var(--dur-slow);
  }
  @supports (backdrop-filter: blur(1px)) {
    .buy-bar {
      backdrop-filter: blur(16px);
      -webkit-backdrop-filter: blur(16px);
    }
  }
  .buy-bar.is-visible {
    translate: 0;
    visibility: visible;
    transition-delay: 0s;
  }
  .buy-bar__price {
    font-family: var(--sans);
    font-size: var(--step--1);
    font-weight: 700;
    line-height: 1.2;
  }
  .buy-bar__price span {
    display: block;
    font-weight: 500;
    color: var(--ink-3);
    font-size: var(--step--2);
  }
  /* Hidden on a wide screen because the page's own CTA is right there — but that
     assumed a wide screen is also a tall one. A 1366x768 laptop gives 623px of page,
     where the Labrador hero cannot fit a headline, a price AND a button no matter how
     it is tightened. Short viewports keep the bar at any width, so the CTA is always
     one tap away instead of 181px under the fold. */
  @media (width >= 52rem) and (height >= 700px) {
    .buy-bar {
      display: none;
    }
  }

  /* ---------------------------------------------------------------
     Editorial type components
     --------------------------------------------------------------- */
  .eyebrow {
    display: flex;
    align-items: center;
    gap: 0.7em;
    max-inline-size: none;
    font-family: var(--sans);
    font-size: var(--step--2);
    font-weight: 700;
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    color: var(--on-surface-accent);
  }
  .eyebrow::before {
    content: "";
    inline-size: clamp(1.2rem, 2.4vi, 2.4rem);
    block-size: 1.5px;
    background: currentColor;
    flex: 0 0 auto;
    opacity: 0.7;
  }
  .center .eyebrow,
  .eyebrow--center {
    justify-content: center;
  }
  .eyebrow--bare::before {
    display: none;
  }

  .display {
    font-family: var(--serif);
    font-size: var(--step-7);
    font-weight: 600;
    line-height: 0.99;
    letter-spacing: var(--tracking-display);
    text-wrap: balance;
    max-inline-size: var(--measure-display);
  }

  .lede {
    font-size: var(--step-1);
    line-height: var(--leading-loose);
    color: var(--on-surface-2);
    max-inline-size: var(--measure-lede);
    text-wrap: pretty;
  }

  /* 48ch, not the inherited 62ch of `p`. At --step-1 a 62ch box is physically
     wider than the 62ch of the --step-0 paragraph directly beneath it, so two
     consecutive paragraphs in one stack had right edges 143px apart and read
     as a step in the rag. 48ch here lands within ~20px of the body measure at
     both 1440 and 1920. */
  .large-copy {
    font-size: var(--step-1);
    line-height: 1.55;
    color: var(--on-surface-2);
    max-inline-size: var(--measure-narrow);
  }

  .caption {
    font-family: var(--sans);
    font-size: var(--step--2);
    font-weight: 600;
    letter-spacing: 0.04em;
    color: var(--on-surface-3);
    max-inline-size: 46ch;
  }

  .fineprint {
    font-family: var(--sans);
    font-size: var(--step--2);
    line-height: 1.5;
    color: var(--on-surface-3);
  }

  /* ---------------------------------------------------------------
     Surfaces
     --------------------------------------------------------------- */
  /* Padding is a PERCENTAGE, not cqi. An element's own container-type does
     not apply to its own properties, so `3.2cqi` here resolved against the
     nearest ANCESTOR container — .shell — and the inset became a function of
     the page width rather than the component width: identical 37-41px insets
     on boxes from 296px to 811px wide, i.e. 12.6% of one card and 6.9% of
     another. A percentage resolves against the element's own containing block
     (its grid track / column), which is what "3% of this component" was
     always meant to say. */
  .panel {
    position: relative;
    padding: clamp(var(--space-m), 6%, var(--space-xl));
    border: 1px solid var(--on-surface-line);
    border-radius: var(--r-l);
    background: var(--card);
    color: var(--on-surface);
    container-type: inline-size;
  }
  .panel--flat {
    box-shadow: none;
  }
  .panel--raised {
    box-shadow: var(--shadow-m);
    border-color: transparent;
  }
  .panel--quiet {
    background: color-mix(in oklab, var(--on-surface) 3.5%, transparent);
    border-color: var(--on-surface-line);
  }

  .card {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    /* Percentage, for the reason documented on .panel above. */
    padding: clamp(var(--space-m), 9%, var(--space-l));
    border: 1px solid var(--on-surface-line);
    border-radius: var(--r-l);
    background: var(--card);
    color: var(--on-surface);
    /* NO container-type here. It was carried for the cqi padding above, which
       is now a percentage — and it is actively harmful: an element with size
       containment cannot be a subgrid, so `container-type: inline-size` made
       `grid-template-rows: subgrid` compute to a plain grid and the aligned
       card rows silently stopped aligning. Verified in Chrome: with
       container-type on, a card resolved its own 256.7/106.8/133.4 tracks and
       the three siblings' bodies started 8.9px apart; with it off, all three
       compute `subgrid` and their bodies start on the same line to 0.0px.
       If a descendant of a card ever needs container units, give that
       descendant its own container — do not put one back here. */
    overflow: clip;
    translate: 0 var(--lift);
    transition: translate var(--dur) var(--ease-out),
      box-shadow var(--dur) var(--ease-out),
      border-color var(--dur) var(--ease-out);
  }
  .card:hover {
    --lift: -3px;
    box-shadow: var(--shadow-m);
    border-color: color-mix(in oklab, var(--gold) 40%, var(--on-surface-line));
  }
  /* A card that contains a link becomes fully clickable, keyboard
     access still coming from the real anchor. :has() again. */
  .card:has(a.card__link) {
    cursor: pointer;
  }
  .card a.card__link::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
  }
  .card__index {
    font-family: var(--sans);
    font-size: var(--step--2);
    font-weight: 700;
    letter-spacing: var(--tracking-caps);
    color: var(--on-surface-accent);
    font-variant-numeric: lining-nums tabular-nums;
  }
  /* Bleeds the media to the card edge, so it must cancel .card's padding
     EXACTLY. A percentage margin resolves against this element's containing
     block — the card's CONTENT box — while the card's percentage padding
     resolves against the card's own containing block. In the linear zone
     padding = 9%W, so the content box is 0.82W and the cancelling margin is
     9/0.82 = 10.9756% of it. Both expressions clamp at the same card width
     (11.111 x --space-m and 11.111 x --space-l), so the two stay identical at
     every width, not only inside the ramp. */
  .card__media {
    margin: calc(clamp(var(--space-m), 10.9756%, var(--space-l)) * -1);
    margin-block-end: 0;
  }
  .card__media img {
    inline-size: 100%;
    aspect-ratio: var(--ratio, 4 / 3);
    object-fit: cover;
  }
  .card--feature {
    --card: color-mix(in oklab, var(--gold) 7%, var(--paper));
    border-color: color-mix(in oklab, var(--gold) 34%, transparent);
  }

  /* ---------------------------------------------------------------
     Aligned card rows
     Cards in a .grid--aligned share the parent's rows, so every card's
     heading and every card's paragraph start on the same line however many
     lines the headings above them take. That is what makes a set of cards
     read as identical siblings rather than as four things that happen to be
     the same width.

     This block MUST live in @layer components, after .card. Declared in
     @layer layout it lost to `.card { display: flex }` here and the subgrid
     never applied at all: all four landing cards computed display:flex, and a
     three-line heading pushed its own paragraph 35.4px below its neighbours'.

     The row count is detected from the card rather than taken on trust from
     an authored --rows: a card given one row too many spans an empty parent
     row and pays a full grid gap for it. --rows is still honoured as the
     fallback for any shape not covered here.
     subgrid: Baseline widely available since 2026-03-15. Without it the cards
     simply size themselves and nothing breaks.
     --------------------------------------------------------------- */
  @supports (grid-template-rows: subgrid) {
    .grid--aligned > * {
      display: grid;
      grid-row: span var(--rows, 3);
      grid-template-rows: subgrid;
      row-gap: var(--space-xs);
    }
    .grid--aligned > :has(> :nth-child(2):last-child) {
      --rows: 2;
    }
    .grid--aligned > :has(> :nth-child(3):last-child) {
      --rows: 3;
    }
    .grid--aligned > :has(> :nth-child(4):last-child) {
      --rows: 4;
    }
  }

  /* Hairline rule with a small ornament. */
  .hairline {
    display: flex;
    align-items: center;
    gap: var(--space-s);
    color: var(--on-surface-line);
  }
  .hairline::before,
  .hairline::after {
    content: "";
    block-size: 1px;
    flex: 1;
    background: currentColor;
  }

  /* ---------------------------------------------------------------
     Stats / proof ribbon
     --------------------------------------------------------------- */
  .stat {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
  }
  .stat__num {
    font-family: var(--serif);
    font-size: var(--step-4);
    font-weight: 600;
    line-height: 1;
    letter-spacing: var(--tracking-display);
    font-variant-numeric: lining-nums tabular-nums;
    color: var(--on-surface);
  }
  .stat__label {
    font-family: var(--sans);
    font-size: var(--step--2);
    font-weight: 600;
    letter-spacing: 0.05em;
    color: var(--on-surface-3);
    text-wrap: balance;
  }
  .ribbon {
    display: grid;
    /* auto-fit picks whatever number of columns fits, which for a FOUR-item ribbon
       resolved to three at tablet width and orphaned the last stat on a row of its own.
       Four items want 1, 2 or 4 columns — never 3. The container query below pins the
       middle case to 2x2; auto-fit still handles the phone (one column) and the desktop
       (all four across), so nothing else has to be hard-coded. */
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
    gap: var(--space-m) var(--space-l);
    padding-block: var(--space-l);
    border-block: 1px solid var(--on-surface-line);
    /* The ribbon measures ITSELF. Keyed to the shell, the divider rule fired
       whenever the PAGE was wide, including where the ribbon sits in a ~40%
       hero column and its own items had already wrapped — so the item that
       dropped to row two drew a vertical hairline with nothing to its left
       and an indent no sibling had. It read as a stray rule, not a divider. */
    container-type: inline-size;
    container-name: ribbon;
  }

  @container shell (30rem <= width < 62rem) {
    .ribbon {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
  }
  /* Vertical dividers only when every item really is on one row. The tracks
     are equal 1fr with an 11rem floor, so n items share a row exactly when the
     ribbon can hold n floors plus their gaps; the thresholds below are those
     widths, and the :has() guard picks the one matching the item count. Any
     other count simply gets no dividers, which is the safe direction. */
  @container ribbon (min-width: 24.5rem) {
    .ribbon:has(> :nth-child(2):last-child) > * + * {
      padding-inline-start: var(--space-l);
      border-inline-start: 1px solid var(--on-surface-line);
    }
  }
  @container ribbon (min-width: 38rem) {
    .ribbon:has(> :nth-child(3):last-child) > * + * {
      padding-inline-start: var(--space-l);
      border-inline-start: 1px solid var(--on-surface-line);
    }
  }
  @container ribbon (min-width: 51.5rem) {
    .ribbon:has(> :nth-child(4):last-child) > * + * {
      padding-inline-start: var(--space-l);
      border-inline-start: 1px solid var(--on-surface-line);
    }
  }

  /* ---------------------------------------------------------------
     Chips
     --------------------------------------------------------------- */
  .chip {
    display: inline-flex;
    align-items: center;
    gap: 0.45em;
    padding: 0.42em 0.95em;
    border: 1px solid var(--on-surface-line);
    border-radius: var(--r-pill);
    background: color-mix(in oklab, var(--on-surface) 3%, transparent);
    font-family: var(--sans);
    font-size: var(--step--2);
    font-weight: 600;
    color: var(--on-surface-2);
    white-space: nowrap;
    inline-size: fit-content;
  }
  .chip--accent {
    border-color: color-mix(in oklab, var(--green) 38%, transparent);
    background: color-mix(in oklab, var(--green) 8%, transparent);
    color: var(--green);
  }
  .on-dark .chip--accent {
    color: var(--on-surface);
  }

  /* ---------------------------------------------------------------
     Checklist
     --------------------------------------------------------------- */
  /* The one text block in the system with no measure of its own: dropped into
     a wide column it ran to ~98 characters a line at 1920 — 1.6x the system
     measure and wider than .measure--wide — directly above a paragraph set at
     62ch. The cap is --measure plus the item's own marker gutter (1.35em
     column + 0.7em gap), so the TEXT lands on exactly 62ch and the list rags
     with everything stacked under it. No string changes; they wrap earlier.
     Invisible on the Labrador page, where the lists sit in 499-627px panels. */
  .checklist {
    display: grid;
    gap: var(--space-xs);
    padding: 0;
    list-style: none;
    max-inline-size: calc(var(--measure) + 2.05em);
  }
  .checklist li {
    display: grid;
    grid-template-columns: 1.35em 1fr;
    gap: 0.7em;
    align-items: start;
    font-size: var(--step-0);
    color: var(--on-surface-2);
  }
  .checklist li::before {
    content: "";
    inline-size: 1.35em;
    block-size: 1.35em;
    margin-block-start: 0.12em;
    border-radius: 50%;
    background: color-mix(in oklab, var(--green) 14%, transparent);
    -webkit-mask: none;
    mask: none;
    box-shadow: inset 0 0 0 1px
      color-mix(in oklab, var(--green) 30%, transparent);
    background-image: var(--icon-check);
    background-repeat: no-repeat;
    background-position: center;
    background-size: 0.72em;
  }
  .on-dark .checklist li::before {
    background-color: color-mix(in oklab, var(--gold-lit) 20%, transparent);
    background-image: var(--icon-check-light);
    box-shadow: inset 0 0 0 1px
      color-mix(in oklab, var(--gold-lit) 40%, transparent);
  }

  /* ---------------------------------------------------------------
     Chapter list — the spine of the product page
     --------------------------------------------------------------- */
  .chapters {
    display: grid;
    gap: 0;
    counter-reset: chapter;
    padding: 0;
    list-style: none;
    border-block-start: 1px solid var(--on-surface-line);
    /* Measures itself, not the page. The two-column rule below was keyed to
       the shell, but on the product page this list sits inside a rail column
       roughly half the shell wide — so at a 1320px viewport the shell passed
       74rem while the list itself was 584px, and the page had to defeat the
       rule (and put back the border it strips) by hand. */
    container-type: inline-size;
    container-name: chapters;
  }
  .chapters > li {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--space-xs) var(--space-m);
    align-items: baseline;
    /* Bound the ROW, not just the note inside it. The divider under each row
       spanned the whole track while the note is capped at 58ch, so on a wide
       screen 258px of bare rule ran past the last possible glyph, ten times
       over. This is the note measure plus the number gutter, so rule and text
       end together; it is wider than any rail column below ~1500px and so
       has no effect there. */
    max-inline-size: calc(58ch + 2.4ch + var(--space-m));
    padding-block: clamp(var(--space-s), 1.6cqi, var(--space-m));
    border-block-end: 1px solid var(--on-surface-line);
    transition: background-color var(--dur) var(--ease-out);
  }
  .chapters > li:hover {
    background-color: color-mix(in oklab, var(--on-surface) 4%, transparent);
  }
  .chapters__num {
    font-family: var(--sans);
    font-size: var(--step--1);
    font-weight: 700;
    font-variant-numeric: lining-nums tabular-nums;
    color: var(--on-surface-accent);
    min-inline-size: 2.4ch;
  }
  .chapters__title {
    font-family: var(--serif);
    font-size: var(--step-2);
    font-weight: 600;
    line-height: var(--leading-snug);
    letter-spacing: var(--tracking-tight);
    /* 30ch left exactly one title of ten wrapping a single word onto a second
       line, making that row 35px taller than its nine neighbours with ~326px
       of unused width beside it. 34ch pulls it back onto one line and is
       still far inside the row bound above; no other title comes near it. */
    max-inline-size: 34ch;
  }
  .chapters__age {
    grid-column: 2;
    font-family: var(--sans);
    font-size: var(--step--2);
    font-weight: 600;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
    color: var(--on-surface-3);
  }
  .chapters__note {
    grid-column: 2;
    color: var(--on-surface-2);
    max-inline-size: 58ch;
  }
  /* Two columns once the LIST can genuinely hold two. */
  @container chapters (min-width: 74rem) {
    .chapters {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      column-gap: clamp(var(--space-xl), 5cqi, var(--space-3xl));
    }
    .chapters > li:nth-last-child(2):nth-child(odd) {
      border-block-end: 0;
    }
  }
  .chapters > li:last-child {
    border-block-end: 0;
  }

  /* ---------------------------------------------------------------
     Figures / art frames
     --------------------------------------------------------------- */
  .figure {
    position: relative;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
  }
  .figure img {
    inline-size: 100%;
    border-radius: var(--r-l);
  }
  .figure figcaption {
    font-family: var(--sans);
    font-size: var(--step--2);
    font-weight: 600;
    letter-spacing: 0.04em;
    color: var(--on-surface-3);
  }
  /* Book-plate arch — the shape that says "printed reference". */
  .figure--arch img {
    border-radius: var(--r-arch);
    box-shadow: var(--shadow-l);
  }
  .figure--framed img {
    padding: clamp(0.5rem, 1.4cqi, 1rem);
    background: var(--card);
    border: 1px solid var(--on-surface-line);
    box-shadow: var(--shadow-m);
  }

  /* The cover, presented as an object with thickness. */
  .book {
    position: relative;
    inline-size: 100%;
    perspective: 1400px;
  }
  .book img {
    inline-size: 100%;
    border-radius: 3px var(--r-s) var(--r-s) 3px;
    box-shadow: var(--shadow-book);
    rotate: y var(--tilt);
    /* Centre origin, so the perspective swing grows the near edge and
       shrinks the far edge by the same amount and the rotated box stays
       inside its column instead of bleeding into the gutter. */
    transform-origin: 50% 50%;
    transition: rotate var(--dur-slow) var(--ease-out),
      box-shadow var(--dur-slow) var(--ease-out);
  }
  .book--tilt img {
    --tilt: -11deg;
  }
  .book--tilt:hover img {
    --tilt: -5deg;
  }
  /* Page-block edge suggested with a gradient, not an image. */
  .book::before {
    content: "";
    position: absolute;
    inset-block: 2.5%;
    inset-inline-start: 0;
    inline-size: 0.55rem;
    border-radius: 2px;
    background: linear-gradient(
      90deg,
      color-mix(in oklab, var(--ink) 26%, transparent),
      color-mix(in oklab, var(--paper) 92%, transparent) 42%,
      color-mix(in oklab, var(--ink) 14%, transparent)
    );
    opacity: 0.55;
  }

  /* Halo behind the cover: a soft glow disc plus two rings, pure CSS,
     no image. The padding is what makes the halo read — it insets the
     cover so the glow is visible around its edges. */
  .halo {
    position: relative;
    display: grid;
    place-items: center;
    isolation: isolate;
    padding: clamp(1.25rem, 3.4vi, 4rem);
  }
  .halo::before {
    content: "";
    position: absolute;
    z-index: -1;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    translate: -50% -50%;
    inline-size: min(92%, 38rem);
    aspect-ratio: 1;
    border-radius: 50%;
    background: radial-gradient(
      circle,
      color-mix(in oklab, var(--gold) 30%, transparent) 0 46%,
      transparent 62%
    );
    filter: blur(var(--halo-blur, 22px));
  }
  /* Concentric hairlines, unblurred so they stay crisp. */
  .halo::after {
    content: "";
    position: absolute;
    z-index: -1;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    translate: -50% -50%;
    inline-size: min(86%, 34rem);
    aspect-ratio: 1;
    border-radius: 50%;
    border: 1px solid color-mix(in oklab, var(--gold) 30%, transparent);
    box-shadow: 0 0 0 clamp(1.25rem, 3vi, 3rem)
        color-mix(in oklab, var(--green) 7%, transparent),
      0 0 0 calc(clamp(1.25rem, 3vi, 3rem) + 1px)
        color-mix(in oklab, var(--green) 16%, transparent);
  }

  /* ---------------------------------------------------------------
     Preview cards + lightbox
     --------------------------------------------------------------- */
  /* A contact sheet is a SHEET: it begins on the page's own text axis and
     ends on it. The old rule capped every track at a fixed 15rem and then
     centred the row, so the thumbnails were 240px wide at 390px and at 1920px
     alike and floated as an island 48px / 88px / 120px / 324px inside the
     shell edge their own heading uses — the only content block on either page
     that did not start on the page axis. auto-fit also counts repetitions
     against the MAX track, so at 1024 it produced three columns for four
     pages and dropped the fourth onto a row of its own.

     Column counts are now divisors of four and the tracks fill the axis. The
     "not posters" intent survives on the numbers: 4 across gives ~300px at
     1440 and ~402px at 1920 against a lightbox that opens at roughly 528px
     and 633px, so opening a page is still a real magnification. */
  .previews {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: clamp(var(--space-m), 2.4cqi, var(--space-xl));
    align-items: start;
  }
  @container shell (min-width: 56rem) {
    .previews {
      grid-template-columns: repeat(4, minmax(0, 1fr));
    }
  }
  .preview {
    display: flex;
    flex-direction: column;
    gap: var(--space-xs);
    text-align: start;
    inline-size: 100%;
    border-radius: var(--r-m);
    translate: 0 var(--lift);
    transition: translate var(--dur) var(--ease-out);
  }
  .preview img {
    inline-size: 100%;
    border-radius: var(--r-s);
    border: 1px solid var(--on-surface-line);
    background: var(--card);
    box-shadow: var(--shadow-s);
    transition: box-shadow var(--dur) var(--ease-out);
  }
  .preview:hover,
  .preview:focus-visible {
    --lift: -4px;
  }
  .preview:hover img,
  .preview:focus-visible img {
    box-shadow: var(--shadow-l);
  }
  .preview__label {
    font-family: var(--sans);
    font-size: var(--step--1);
    font-weight: 700;
    color: var(--on-surface);
  }
  .preview__meta {
    font-family: var(--sans);
    font-size: var(--step--2);
    color: var(--on-surface-3);
  }
  /* Alternating vertical offset — editorial, and only in the four-across
     state, so it is gated on the same threshold the four-column rule uses.
     At 62rem it switched on while the grid was still three columns, where
     "even" meant item 2 on row one and item 4 on row two and the alternation
     read as random. Halved as well: the offset is carried by the caption too,
     so at 52-69px the four page titles sat on two visibly different
     baselines; at ~21-28px the scatter still reads and the labels read as one
     line. */
  @container shell (min-width: 56rem) {
    .previews--offset > :nth-child(even) {
      margin-block-start: clamp(1rem, 1.6cqi, 1.75rem);
    }
  }

  .lightbox {
    /* Hug the page image instead of reserving a fixed 60rem panel: a portrait page in a
       960px-wide box left large empty margins that read as an oversized white window. */
    inline-size: fit-content;
    max-inline-size: 96vw;
    max-block-size: 96svb;
    padding: clamp(var(--space-s), 2vi, var(--space-m));
    border-radius: var(--r-l);
    background: var(--card);
    box-shadow: var(--shadow-l);
    opacity: 1;
    scale: 1;
    transition: opacity var(--dur) var(--ease-out), scale var(--dur) var(--ease-out),
      display var(--dur) allow-discrete, overlay var(--dur) allow-discrete;
  }
  .lightbox:not([open]) {
    opacity: 0;
    scale: 0.97;
  }
  /* @starting-style: Baseline newly since 2024-08-06. Unsupported
     engines simply skip the entry animation. */
  @starting-style {
    .lightbox[open] {
      opacity: 0;
      scale: 0.97;
    }
  }
  .lightbox::backdrop {
    background: color-mix(in oklab, var(--ink) 62%, transparent);
    opacity: 1;
    transition: opacity var(--dur) var(--ease-out), display var(--dur) allow-discrete;
  }
  @supports (backdrop-filter: blur(1px)) {
    .lightbox::backdrop {
      backdrop-filter: blur(6px);
    }
  }
  .lightbox:not([open])::backdrop {
    opacity: 0;
  }
  @starting-style {
    .lightbox[open]::backdrop {
      opacity: 0;
    }
  }
  .lightbox img {
    /* Fill the available height so opening a page is a real magnification, not a
       same-size copy. Portrait pages are height-bound; width follows the aspect ratio. */
    display: block;
    block-size: min(88svb, calc(94vw * 1.5));
    inline-size: auto;
    max-inline-size: 100%;
    object-fit: contain;
    border-radius: var(--r-m);
    background: var(--paper);
  }
  .lightbox__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-s);
    margin-block-start: var(--space-xs);
  }
  .lightbox__close {
    display: grid;
    place-items: center;
    inline-size: 2.75rem;
    block-size: 2.75rem;
    border-radius: 50%;
    border: 1px solid var(--on-surface-line);
    font-size: var(--step-1);
    line-height: 1;
    color: var(--on-surface-2);
    transition: background-color var(--dur) var(--ease-out);
  }
  .lightbox__close:hover {
    background: color-mix(in oklab, var(--ink) 7%, transparent);
  }

  /* ---------------------------------------------------------------
     FAQ
     --------------------------------------------------------------- */
  .faq {
    display: grid;
    border-block-start: 1px solid var(--on-surface-line);
  }
  .faq > details {
    border-block-end: 1px solid var(--on-surface-line);
  }
  .faq summary {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-m);
    padding-block: var(--space-m);
    font-family: var(--serif);
    font-size: var(--step-1);
    font-weight: 600;
    letter-spacing: var(--tracking-tight);
    color: var(--on-surface);
    text-wrap: balance;
  }
  .faq summary::after {
    content: "";
    flex: 0 0 auto;
    inline-size: 0.85em;
    block-size: 0.85em;
    align-self: center;
    background: currentColor;
    -webkit-mask: var(--icon-plus) center / contain no-repeat;
    mask: var(--icon-plus) center / contain no-repeat;
    color: var(--on-surface-accent);
    transition: rotate var(--dur) var(--ease-out);
  }
  .faq > details[open] summary::after {
    rotate: 135deg;
  }
  .faq__body {
    padding-block-end: var(--space-m);
    color: var(--on-surface-2);
    max-inline-size: 66ch;
  }
  /* Dim the siblings of an open question. :has() on the container. */
  .faq:has(details[open]) > details:not([open]) summary {
    color: var(--on-surface-2);
  }

  /* ---------------------------------------------------------------
     Question / stage cards
     --------------------------------------------------------------- */
  .qa {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    padding-block-start: var(--space-s);
    border-block-start: 2px solid var(--on-surface-accent);
  }
  .qa__stage {
    font-family: var(--sans);
    font-size: var(--step--2);
    font-weight: 700;
    letter-spacing: var(--tracking-caps);
    text-transform: uppercase;
    color: var(--on-surface-accent);
  }
  .qa__q {
    font-family: var(--serif);
    font-size: var(--step-2);
    font-weight: 400;
    font-style: italic;
    line-height: var(--leading-snug);
    letter-spacing: var(--tracking-tight);
    text-wrap: balance;
    max-inline-size: 24ch;
  }

  /* ---------------------------------------------------------------
     Scroll rail — horizontal snap strip for small screens
     --------------------------------------------------------------- */
  .rail-x {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: min(78%, 22rem);
    gap: var(--space-m);
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--gutter);
    padding-inline: var(--gutter);
    margin-inline: calc(var(--gutter) * -1);
    padding-block-end: var(--space-s);
    scrollbar-width: thin;
  }
  .rail-x > * {
    scroll-snap-align: start;
  }
  @container shell (min-width: 56rem) {
    .rail-x {
      grid-auto-flow: row;
      grid-auto-columns: auto;
      grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
      overflow: visible;
      padding-inline: 0;
      margin-inline: 0;
    }
  }

  /* ---------------------------------------------------------------
     Footer
     --------------------------------------------------------------- */
  .site-footer {
    --surface: #262119;
    --on-surface: #f2ece0;
    --on-surface-2: color-mix(in oklab, #f2ece0 76%, var(--surface));
    --on-surface-3: color-mix(in oklab, #f2ece0 58%, var(--surface));
    --on-surface-accent: var(--gold-lit);
    --on-surface-line: color-mix(in oklab, #ffffff 14%, transparent);
    --focus-ring: var(--focus-ring-on-dark);
    background: var(--surface);
    color: var(--on-surface);
    padding-block: var(--space-2xl) var(--space-xl);
  }
  .site-footer a {
    color: var(--on-surface-2);
    text-decoration: none;
  }
  .site-footer a:hover,
  .site-footer a:focus-visible {
    color: var(--on-surface);
    text-decoration: underline;
  }
  /* Three columns, so the only clean counts are 1 and 3. auto-fit gave it two
     through the tablet band and left the Help column alone on a row with a
     completely empty cell beside it as the last thing on the page — and cost
     ~230-280px of footer height doing it. 42rem of shell is where three
     tracks are comfortably wider than the longest link. */
  .footer-grid {
    display: grid;
    gap: var(--space-xl);
    grid-template-columns: minmax(0, 1fr);
  }
  @container shell (min-width: 42rem) {
    .footer-grid {
      /* The published site's proportions: the brand takes the room, the two link
         columns take what three short links actually need. Equal thirds left the
         links floating in space and the brand cramped against them. */
      grid-template-columns: minmax(0, 1fr) minmax(0, 0.28fr) minmax(0, 0.28fr);
      column-gap: var(--space-3xl);
    }
  }
  /* Type matched to the published footer, which reads better at this size: the tagline
     was setting at 12.6px against its 17px, small enough to look like fine print rather
     than a brand line. Flat values, as the published site has them — this footer is a
     self-contained component and 17/15/12 are already comfortable on a phone, so there
     is nothing for the fluid scale to protect here. */
  .footer-brand p {
    max-inline-size: 46ch;
    font-size: 1.0625rem;   /* 17px */
    line-height: 1.58;
    color: var(--on-surface-3);
  }
  .footer-brand img {
    inline-size: clamp(12rem, 9rem + 6vi, 17rem);
    background: none;
  }
  .footer-col h3 {
    font-family: var(--sans);
    font-size: 0.75rem;     /* 12px */
    font-weight: 700;
    line-height: 1.35;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--on-surface-accent);
    margin-block-end: var(--space-xs);
  }
  /* No gap: the a11y layer already gives every footer link a 2.75rem minimum
     block size, and the two were adding up to a 53.2px link-to-link pitch for
     type that needs about 25px. The touch target is untouched. */
  .footer-col ul {
    display: grid;
    gap: var(--space-2xs);
    font-family: var(--sans);
    font-size: 0.9375rem;   /* 15px */
    font-weight: 500;
    line-height: 1.6;
  }
  .footer-base {
    display: flex;
    flex-wrap: wrap;
    /* Two items, one at each edge — the published site's balance. Left-packed with a
       gap they read as a list rather than as a footer rule. */
    justify-content: space-between;
    gap: var(--space-2xs) var(--space-m);
    margin-block-start: var(--space-xl);
    padding-block-start: var(--space-m);
    border-block-start: 1px solid var(--on-surface-line);
    font-family: var(--sans);
    font-size: 0.75rem;     /* 12px */
    font-weight: 500;
    line-height: 1.5;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--on-surface-3);
  }
  .footer-note {
    max-inline-size: 62ch;
    font-family: var(--sans);
    font-size: var(--step--2);
    line-height: 1.55;
    color: var(--on-surface-3);
  }

  /* ---------------------------------------------------------------
     Legacy class names — /disclaimer/ only
     That page is written against a markup contract this stylesheet no
     longer has: .site-header__inner, .brand, .site-footer__inner,
     .footer__nav and .site-footer__legal match no rule anywhere, so every
     one of them computed display:block. The header stacked its logo above
     its button and overflowed its own fixed height by 37px at 1440 and 54px
     at 390; the footer nav collapsed to one run of unspaced text with
     sub-44px targets; the legal line got no rule, no gap and no typography;
     and both logos rendered at their raw 292px width attribute, 22-36%
     larger than the same brand on the two pages that link to it.

     These names appear nowhere else in the site, so aliasing them here is
     inert for every other surface. If that page's markup is ever migrated to
     the system class names, delete this whole block AND the page-scope hooks
     below, which key off .site-footer__legal.
     --------------------------------------------------------------- */
  .site-header__inner {
    block-size: 100%;
    display: flex;
    align-items: center;
    gap: var(--space-m);
  }

  /* Two children, so auto-fit can only produce 1 or 2 tracks and both divide
     the item count. It cannot be a @container shell rule: this element IS the
     .shell, and a container query resolves against the nearest ANCESTOR
     container, so the rule would never match itself. */
  .site-footer__inner {
    display: grid;
    gap: var(--space-xl);
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  }
  .site-footer__inner img {
    inline-size: clamp(10rem, 8rem + 6vi, 14rem);
    background: none;
  }
  /* The brand line under the logo is a bare <p> here and .footer-note on the
     other two pages, so it was setting at ~19px bold serif against their
     ~12.6px sans — the same element, two sizes, on one site. */
  .site-footer__inner p {
    max-inline-size: 62ch;
    font-family: var(--sans);
    font-size: var(--step--2);
    line-height: 1.55;
    color: var(--on-surface-3);
  }

  .footer__nav {
    display: grid;
    align-content: start;
    font-family: var(--sans);
    font-size: var(--step--1);
  }

  .site-footer__legal {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2xs) var(--space-m);
    margin-block-start: var(--space-xl);
    padding-block-start: var(--space-m);
    font-family: var(--sans);
    font-size: var(--step--2);
    color: var(--on-surface-3);
  }
  /* The rule is a pseudo-element, not border-block-start: this element is its
     own .shell, so a border would be drawn on the padding box and run the
     full bleed while the text it separates starts one gutter in. */
  .site-footer__legal::before {
    content: "";
    position: absolute;
    inset-block-start: 0;
    inset-inline: var(--gutter);
    block-size: 1px;
    background: var(--on-surface-line);
  }

  /* Page scope for /disclaimer/, taken from a class only that page carries.
     It is a single prose column with nothing beside it: left-flush in an
     uncapped shell it left 55-66% of the content area bare down the full
     height of the document, which reads as a page whose right half failed to
     load rather than as a quiet legal notice. Bounding and centring the
     column turns that into symmetric page margin. The measure, the type
     sizes and every word are unchanged.

     EVERY shell on the page, not just main's: capping the prose alone moved
     it 160px inboard at 1440 and 400px at 1920 while the header logo and the
     footer stayed out at the gutter, so the site's spine jumped sideways on
     the way into the body and jumped back at the foot. One column, one axis,
     top to bottom. The band backgrounds stay full-bleed either way. */
  body:has(.site-footer__legal) .shell {
    max-inline-size: 70rem;
  }
  /* ...and the lede is the page's own intro, stacked directly on the body it
     introduces, so it must rag with it rather than 86-106px inside it. On the
     sales pages the lede always sits in a narrow side column, which is why
     --measure-lede is right everywhere else and wrong only here. */
  body:has(.site-footer__legal) .lede {
    /* 50ch, not `var(--measure)`: ch is font-relative, so 62ch at the lede's
       --step-1 is ~150px WIDER than 62ch at the body's --step-0 and would
       simply swap which of the two rags sticks out. The ratio of the two
       steps is near-constant across the fluid range, and 50ch lands the lede
       within ~4px of the body measure at 1024, 1440 and 1920 alike. */
    max-inline-size: 50ch;
  }

  /* ---------------------------------------------------------------
     Scroll progress rail (see motion layer for the timeline)
     --------------------------------------------------------------- */
  .progress-rail {
    position: fixed;
    inset-block-start: 0;
    inset-inline: 0;
    z-index: 95;
    block-size: 2px;
    background: linear-gradient(90deg, var(--green), var(--gold));
    transform: scaleX(0);
    transform-origin: 0 50%;
    pointer-events: none;
    opacity: 0;
  }
}

/* =====================================================================
   LAYER: utilities
   ===================================================================== */

@layer utilities {
  .sr-only {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  .skip-link {
    position: fixed;
    z-index: 999;
    inset-block-start: 0.75rem;
    inset-inline-start: 0.75rem;
    padding: 0.7rem 1rem;
    border-radius: var(--r-s);
    background: var(--ink);
    color: var(--paper);
    font-family: var(--sans);
    font-size: var(--step--1);
    font-weight: 700;
    text-decoration: none;
    translate: 0 -180%;
    transition: translate var(--dur-fast) var(--ease-out);
  }
  .skip-link:focus-visible {
    translate: 0;
  }

  .text-accent {
    color: var(--on-surface-accent);
  }
  .text-muted {
    color: var(--on-surface-3);
  }
  .sans {
    font-family: var(--sans);
  }
  .serif {
    font-family: var(--serif);
  }
  .nowrap {
    white-space: nowrap;
  }
  .full {
    inline-size: 100%;
  }
  .hide {
    display: none;
  }

  /* On a phone, logo + CTA + hamburger is one item too many and the CTA
     label wraps to two lines. The sticky .buy-bar covers the CTA job at
     this width. This lives in `utilities` on purpose: in `components` it
     would lose to `.btn { display: inline-flex }` on source order, since
     both selectors have the same specificity. Layers settle it. */
  @media (width < 30rem) {
    .header-cta {
      display: none;
    }
  }
  .flow-xl {
    --flow: var(--space-xl);
  }

  /* Defer offscreen paint on long sections. content-visibility:
     Baseline newly since 2025-09-15. contain-intrinsic-size stops the
     scrollbar from jumping. Never put this on an above-fold section. */
  .defer-paint {
    content-visibility: auto;
    contain-intrinsic-size: auto 60rem;
  }

  /* Named elements for the cross-document transition. A name must be
     UNIQUE within a document — using the same name twice on one page
     silently cancels the whole transition. */
  .vt-brand {
    view-transition-name: brand;
  }
  .vt-cover {
    view-transition-name: cover;
  }
  .vt-title {
    view-transition-name: page-title;
  }
}

/* =====================================================================
   LAYER: motion
   Everything here is additive. The page is complete with this layer
   deleted. Ordering matters: the visible/final state is ALWAYS the
   default, and the hidden start state exists only inside the feature
   query, so an engine without scroll timelines can never leave content
   invisible.
   ===================================================================== */

@layer motion {
  @media (prefers-reduced-motion: no-preference) {
    html {
      scroll-behavior: smooth;
    }

    /* ---- Scroll-driven reveals (CSS-native) --------------------------
       Baseline "limited": Chrome/Edge 115+, Safari 26+. Firefox has NOT
       shipped these outside Nightly (pref
       layout.css.scroll-driven-animations.enabled), so release Firefox
       renders the static page — which is correct and complete.
       The @supports guard is load-bearing: without it an engine that
       ignores animation-timeline would still run `animation` on the
       document timeline and could strand content at opacity 0.
       ---------------------------------------------------------------- */
    @supports (animation-timeline: view()) {
      .reveal {
        animation: reveal-rise linear both;
        animation-timeline: view();
        animation-range: entry 8% cover 26%;
      }
      .reveal--fade {
        animation-name: reveal-fade;
      }
      .reveal--start {
        animation-name: reveal-start;
      }
      .reveal--end {
        animation-name: reveal-end;
      }
      .reveal--scale {
        animation-name: reveal-scale;
      }
      /* Longer runway for big art. */
      .reveal--slow {
        animation-range: entry 0% cover 44%;
      }

      /* Children reveal in sequence for free: each child owns its own
         view() timeline, so the stagger is a consequence of geometry
         rather than a delay chain. */
      .reveal-group > * {
        animation: reveal-rise linear both;
        animation-timeline: view();
        animation-range: entry 12% cover 34%;
      }

      /* Art drifts a little against the scroll. Capped at 5% of the
         element box — enough to feel alive, far below the parallax
         threshold that triggers motion sickness. */
      .drift {
        animation: drift linear both;
        animation-timeline: view();
        animation-range: cover;
      }

      /* Sticky section headings that release their grip near the end. */
      .fade-out-late {
        animation: fade-late linear both;
        animation-timeline: view();
        animation-range: exit 20% exit 90%;
      }

      /* Reading-progress rail, driven by the root scroller. */
      .progress-rail {
        opacity: 1;
        animation: progress linear both;
        animation-timeline: scroll(root block);
      }

      /* Slow ambient drift on the background field blobs. Transform
         only, so it composites and never repaints. */
      .ambient span {
        animation: ambient-drift 42s var(--ease-in-out) infinite alternate;
        animation-delay: calc(var(--i, 0) * -14s);
      }
    }

    /* IntersectionObserver fallback (app.js) for engines without scroll
       timelines — currently release Firefox. app.js adds .is-armed only
       to elements it has actually begun observing, and only to elements
       below the initial viewport, so a JS failure can never hide
       above-fold content. A 3s fail-open timer reveals everything
       regardless. */
    .reveal.is-armed,
    .reveal-group.is-armed > * {
      opacity: 0;
      translate: 0 1.4rem;
      transition: opacity 620ms var(--ease-out), translate 620ms var(--ease-out);
    }
    .reveal-group.is-armed > * {
      transition-delay: calc(var(--i, 0) * 70ms);
    }
    .reveal.is-armed.is-in,
    .reveal-group.is-armed.is-in > * {
      opacity: 1;
      translate: 0;
    }
  }

  @keyframes reveal-rise {
    from {
      opacity: 0;
      translate: 0 2.2rem;
    }
    to {
      opacity: 1;
      translate: 0;
    }
  }
  @keyframes reveal-fade {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
  @keyframes reveal-start {
    from {
      opacity: 0;
      translate: -2.4rem 0;
    }
    to {
      opacity: 1;
      translate: 0;
    }
  }
  @keyframes reveal-end {
    from {
      opacity: 0;
      translate: 2.4rem 0;
    }
    to {
      opacity: 1;
      translate: 0;
    }
  }
  @keyframes reveal-scale {
    from {
      opacity: 0;
      scale: 0.965;
    }
    to {
      opacity: 1;
      scale: 1;
    }
  }
  @keyframes drift {
    from {
      translate: 0 3%;
    }
    to {
      translate: 0 -3%;
    }
  }
  @keyframes fade-late {
    from {
      opacity: 1;
    }
    to {
      opacity: 0.12;
    }
  }
  @keyframes progress {
    from {
      transform: scaleX(0);
    }
    to {
      transform: scaleX(1);
    }
  }
  @keyframes ambient-drift {
    from {
      translate: -3% 2%;
      scale: 1;
    }
    to {
      translate: 4% -3%;
      scale: 1.12;
    }
  }

  /* ---- Ambient colour field -----------------------------------------
     Optional per-section component. Three oversized blurred blobs that
     move only by transform. Markup:
       <div class="ambient" aria-hidden="true">
         <span style="--i:0"></span><span style="--i:1"></span>
         <span style="--i:2"></span>
       </div>
     Static and perfectly pleasant with motion disabled.
     -------------------------------------------------------------------- */
  .ambient {
    position: absolute;
    inset: 0;
    z-index: -1;
    overflow: clip;
    pointer-events: none;
    /* layout + paint, NOT strict — `contain: strict` adds size
       containment, which is a foot-gun on a decorative overlay. */
    contain: layout paint;
    /* Without this the clipped blobs leave a hard rectangular seam
       exactly on the band boundary, which reads as a broken background
       rather than a colour field. Fades the field out at all edges. */
    -webkit-mask-image: linear-gradient(
        to bottom,
        transparent 0%,
        #000 15%,
        #000 85%,
        transparent 100%
      ),
      linear-gradient(to right, transparent 0%, #000 8%, #000 92%, transparent 100%);
    -webkit-mask-composite: source-in;
    mask-image: linear-gradient(
        to bottom,
        transparent 0%,
        #000 15%,
        #000 85%,
        transparent 100%
      ),
      linear-gradient(to right, transparent 0%, #000 8%, #000 92%, transparent 100%);
    mask-composite: intersect;
  }
  .ambient span {
    position: absolute;
    inline-size: clamp(22rem, 46vi, 60rem);
    aspect-ratio: 1;
    border-radius: 50%;
    filter: blur(clamp(48px, 7vi, 110px));
    opacity: 0.5;
    /* will-change removed: promoting ~21 large blurred layers permanently cost more
     than it saved. */
  }
  .ambient span:nth-child(1) {
    inset-block-start: -18%;
    inset-inline-start: -12%;
    background: oklch(84% 0.075 84 / 0.85);
  }
  .ambient span:nth-child(2) {
    inset-block-start: 22%;
    inset-inline-end: -16%;
    background: oklch(86% 0.05 172 / 0.8);
  }
  .ambient span:nth-child(3) {
    inset-block-end: -22%;
    inset-inline-start: 32%;
    background: oklch(88% 0.055 58 / 0.7);
  }
  .on-dark .ambient span:nth-child(1) {
    background: oklch(48% 0.09 168 / 0.85);
  }
  .on-dark .ambient span:nth-child(2) {
    background: oklch(52% 0.075 88 / 0.55);
  }
  .on-dark .ambient span:nth-child(3) {
    background: oklch(42% 0.06 172 / 0.8);
  }
  @media (prefers-reduced-transparency: reduce) {
    .ambient {
      display: none;
    }
  }

  /* ---- View transitions ---------------------------------------------
     Same-document VT is Baseline newly (2025-10-14, FF 144).
     Cross-document is Chromium 126+ / Safari 18.2+ only.
     -------------------------------------------------------------------- */
  ::view-transition-group(brand) {
    animation-duration: 320ms;
  }
  ::view-transition-group(cover) {
    animation-duration: 460ms;
    animation-timing-function: var(--ease-out);
  }
  ::view-transition-old(root) {
    animation: 240ms var(--ease-out) both vt-out;
  }
  ::view-transition-new(root) {
    animation: 380ms var(--ease-out) 60ms both vt-in;
  }
  @keyframes vt-out {
    to {
      opacity: 0;
    }
  }
  @keyframes vt-in {
    from {
      opacity: 0;
      translate: 0 1.2rem;
    }
  }

  /* ---- Reduced motion: the kill switch ------------------------------
     Chrome's own guidance is that "reduced" does not mean "none", so
     the root cross-fade is left alone and everything with displacement
     is stopped. Scroll-linked movement is a canonical vestibular
     trigger; it is defaulted OFF above rather than merely damped.
     -------------------------------------------------------------------- */
  @media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
    ::view-transition-group(*),
    ::view-transition-old(*),
    ::view-transition-new(*) {
      animation: none !important;
    }
    .progress-rail {
      display: none;
    }
  }
}

/* =====================================================================
   LAYER: a11y — last, so it wins
   ===================================================================== */

@layer a11y {
  /* One focus treatment, visible on paper, on gold, on deep green.
     Double ring: a dark inner and a light outer, so at least one edge
     always contrasts with whatever is behind it. */
  /* No border-radius here: the outline already follows the element's own
     radius, and setting it would square off the pill buttons on focus. */
  :focus-visible {
    outline: 3px solid var(--focus-ring);
    outline-offset: 3px;
  }
  .btn:focus-visible,
  .chip:focus-visible,
  .preview:focus-visible,
  .brandmark:focus-visible,
  .brand:focus-visible {
    outline-offset: 4px;
    box-shadow: 0 0 0 7px color-mix(in oklab, var(--focus-ring) 22%, transparent);
  }
  /* Never remove the ring for mouse users on interactive text. */
  :focus:not(:focus-visible) {
    outline: none;
  }

  /* Every control clears the 44x44 CSS-px target minimum. */
  .nav a,
  .footer-col a,
  .footer__nav a,
  .link-arrow,
  .faq summary {
    min-block-size: 2.75rem;
    display: flex;
    align-items: center;
  }
  .faq summary {
    align-items: baseline;
  }
  /* The link to the veterinary disclaimer lives inside a sentence in the
     footer legal line, so it was the one control on the site the rule above
     missed — a 15px-tall target at every width. It is grown with block
     padding rather than by joining the rule above: vertical padding on a
     non-replaced INLINE box enlarges the hit area without entering the line
     box, so the target clears 44px and the paragraph's leading, its line
     breaks and its copy are all untouched. (min-block-size via inline-flex
     does work, but it inflates the line that carries the link by 12px and
     leaves a visible hole in a two-line paragraph.) */
  .footer-legal a,
  .site-footer__legal a {
    padding-block: 0.9rem;
  }

  /* Windows High Contrast / forced colours. Decorative layers are
     removed and every border becomes a system colour so structure
     survives. */
  @media (forced-colors: active) {
    body::before,
    body::after,
    .ambient,
    .halo::before,
    .book::before,
    .progress-rail {
      display: none;
    }
    .btn,
    .card,
    .panel,
    .chip,
    .preview img,
    .lightbox {
      border: 1px solid CanvasText;
      forced-color-adjust: none;
      background: Canvas;
      color: CanvasText;
    }
    .btn {
      background: ButtonFace;
      color: ButtonText;
      border-color: ButtonText;
    }
    :focus-visible {
      outline: 3px solid Highlight;
    }
    .guarantee::before,
    .faq summary::after {
      background: CanvasText;
    }
  }

  /* Printing a sales page is rare but should not produce 12 blank
     colour blocks. */
  @media print {
    body::before,
    body::after,
    .site-header,
    .announce,
    .buy-bar,
    .progress-rail,
    .ambient {
      display: none !important;
    }
    .band {
      padding-block: 1.5rem;
      background: none !important;
      color: #000 !important;
    }
    a[href^="http"]::after {
      content: " (" attr(href) ")";
      font-size: 0.8em;
      word-break: break-all;
    }
  }
}

/* ===================================================================
   BREED PICKER — "Choose a breed" disclosure
   The CTA used to push one product ("See the Labrador handbook").
   Choosing a breed is the real action, so the button opens the library.
   Only breeds with a page are links; the rest are inert and labelled,
   so the menu shows the full range without offering a dead link.
   =================================================================== */
@layer components {
  /* align-self: stretch is what makes the authored `.btn--block` on the CTA
     inside actually mean anything. As a plain flex item of `.buy` (a column
     flex with align-items: flex-start) this wrapper shrink-wrapped to the
     button's max-content width, so `inline-size: 100%` resolved against the
     button's own label instead of the panel and the only action in the price
     panel sat at 26-34% of the card's width with the rest blank beside it —
     while the same component on the product page, which has no wrapper,
     rendered full width. The dropdown (inset-inline: 0) is corrected for
     free. */
  .breedpick { position: relative; align-self: stretch; }

  .breedpick__chevron {
    display: inline-block;
    transition: rotate var(--dur) var(--ease-out);
  }
  [data-breedpick-toggle][aria-expanded="true"] .breedpick__chevron { rotate: 180deg; }

  .breedpick__menu {
    position: absolute;
    inset-inline: 0;
    inset-block-start: calc(100% + var(--space-2xs));
    z-index: 30;
    margin: 0;
    padding: var(--space-2xs);
    list-style: none;
    background: var(--card);
    border: 1px solid var(--rule);
    border-radius: var(--r-m);
    box-shadow: var(--shadow-l);
    /* Open state. The closed state is the [hidden] attribute, which the
       reset layer forces to display:none — so with JS blocked the menu is
       simply absent rather than stuck open over the page. */
    opacity: 1;
    translate: 0 0;
    transition: opacity var(--dur) var(--ease-out), translate var(--dur) var(--ease-out);
  }
  /* Entry animation. @starting-style is skipped by engines that lack it,
     which costs the animation and nothing else. */
  @starting-style {
    .breedpick__menu { opacity: 0; translate: 0 -0.5rem; }
  }

  .breedpick__item {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-s);
    padding: var(--space-2xs) var(--space-xs);
    border-radius: var(--r-s);
    text-decoration: none;
    font-family: var(--sans);
    font-size: var(--step--1);
  }
  .breedpick__name { font-weight: 600; color: var(--ink); }
  .breedpick__state { font-size: var(--step--2); color: var(--muted); white-space: nowrap; }

  .breedpick__item--ready:hover,
  .breedpick__item--ready:focus-visible { background: var(--surface-2); }
  .breedpick__item--ready .breedpick__state { color: var(--green-deep); font-weight: 600; }

  .breedpick__item--soon { cursor: default; }
  .breedpick__item--soon .breedpick__name { color: var(--muted); }

  @media (prefers-reduced-motion: reduce) {
    .breedpick__menu, .breedpick__chevron { transition: none; }
  }

  /* ---------------------------------------------------------------
     SHORT VIEWPORTS
     On a short screen the fold, not the width, is the binding constraint, and
     the hero was running past it and taking the buy CTA with it. Measured
     against the real viewports (screen minus browser chrome and taskbar):

       1920x911  — the most common desktop in the world. Labrador CTA 51px
                   below the fold, the guarantee line 85px below.
       1366x623  — the most cramped common laptop. Landing CTA 194px below;
                   on the Labrador page the H1 itself did not clear the fold.
       390x745   — iPhone 14. Labrador CTA 48px below, guarantee 98px below.

     Both heroes carry data-hero, so this is one rule for both pages. It touches
     vertical rhythm only — no column, measure or alignment changes — so nothing
     from the relayout moves. Tall viewports (1017, 1271, 954) never see it.
     --------------------------------------------------------------- */
  @media (height < 940px) {
    [data-hero] {
      padding-block: var(--space-xl) var(--band-tight);
    }
    [data-hero] h1 {
      font-size: var(--step-5);
    }
    [data-hero] .stack--l {
      --flow: var(--space-m);
    }
    [data-hero] .stack {
      --flow: var(--space-s);
    }
  }

  /* A phone is ~745px of page and the Labrador hero has more in it than the landing
     hero does — breadcrumb, eyebrow, headline, lede, price, CTA and the guarantee. */
  @media (height < 800px) {
    [data-hero] {
      padding-block: var(--space-l) var(--band-tight);
    }
    [data-hero] h1 {
      font-size: var(--step-4);
    }
    [data-hero] .lede {
      font-size: var(--step-0);
    }
    [data-hero] .stack--l,
    [data-hero] .stack {
      --flow: var(--space-xs);
    }
  }

  /* Under ~700px there is no room to be gentle: a 1366x768 laptop gives 623px of page,
     and on the Labrador page the headline alone was filling it. */
  @media (height < 700px) {
    [data-hero] {
      padding-block: var(--space-m) var(--space-2xl);
    }
    [data-hero] h1 {
      font-size: var(--step-3);
    }
    [data-hero] .lede {
      font-size: var(--step--1);
    }
    [data-hero] .stack--l,
    [data-hero] .stack {
      --flow: var(--space-2xs);
    }
  }
}
