/* ==========================================================================
   OffersOver - design system
   Implements _design/DESIGN-SPEC.md v1.0 (LOCKED, 8 August 2026).

   Section map
     1  Tokens                    spec 2
     2  Reset and base            spec 3.1, 10
     3  Type scale                spec 3.2, 3.3
     4  Grid, container, measure  spec 2, 9
     5  Components                spec 4
     6  Vertical rhythm           spec 5
     7  Site chrome               spec 6 row 0, 12.7
     8  Artefact skeleton         spec 7, 12.10
     9  Motion / reduced motion   spec 8.4
    10  TEMPORARY compatibility   remove when guide pages are rebuilt

   Hard rules enforced throughout:
     - Border OR shadow, never both. Only the Artefact is shadowed.
     - Zero box-shadow on any button. No 999px radius on any button.
     - Every neutral satisfies R >= G >= B (warm). No cool greys.
     - font-style: italic is banned globally (spec 3.1).

   This file is ASCII-only on purpose: Windows PowerShell 5.1 mangles UTF-8
   punctuation. Glyphs are written as CSS escapes (\201C, \2192).
   ========================================================================== */


/* ==========================================================================
   1. TOKENS - spec 2, verbatim
   Deleted in this revision: --ink-55, --ink-40, --ease-in-out-quint,
   --max-w, --max-w-text.
   ========================================================================== */

:root {
  /* ---------- Surfaces: warm ramp, never a cool grey ---------- */
  --bg:            #ffffff;   /* page floor */
  --surface:       #f2f1ed;   /* recessed band, inner panels, chips */
  --surface-2:     #e8e7e3;   /* pressed / hovered surface, chart troughs */
  --rule:          #dfdeda;   /* hairlines, card borders, table row rules */
  --rule-strong:   #dbd7cd;   /* input borders, redaction blocks, table head rule */
  --surface-ink:   #1a1815;   /* THE dark band. Warm charcoal. Used ONCE per page. */

  /* ---------- Ink: honest names, alpha-derived ---------- */
  --ink:           #131211;                    /* headings AND body copy */
  --ink-72:        rgba(19, 18, 17, 0.72);     /* secondary body, descriptions  - 7.5:1 on #fff */
  --ink-62:        rgba(19, 18, 17, 0.62);     /* meta, source lines, captions  - 5.2:1 on #fff */
  --ink-hover:     #2a2420;                    /* ink button hover */
  --on-dark:       rgba(255, 255, 255, 0.94);  /* text on --surface-ink; never pure #fff */
  --on-dark-62:    rgba(255, 255, 255, 0.68);  /* meta on --surface-ink */

  /* ---------- Accent: ONE hue. Glasgow red sandstone. ---------- */
  --accent:        #a63e26;   /* links, ticks, focus ring, the money CTA fill */
  --accent-hover:  #8d3320;
  --accent-press:  #782b1a;
  --accent-soft:   #f6e9e3;   /* accent chip fill, accent badge fill */
  --accent-bright: #c96f4a;   /* CHART FILLS AND GRAPHICS ONLY - 3.6:1, never text */

  /* ---------- Type families ---------- */
  --font-serif: "Brawler", Georgia, "Times New Roman", serif;
  --font-sans:  "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* ---------- Radii: 4-step ladder + pill. Nesting always reduces. ---------- */
  --r-xs:   4px;    /* redaction blocks, chart bars, badges, the paper edge */
  --r-sm:   8px;    /* ALL buttons, inputs, select, chips, inner panels */
  --r-md:  12px;    /* cards, the artefact frame, callouts */
  --r-lg:  20px;    /* outer panels only: the area-check shell, the price band inner */
  --r-pill: 999px;  /* EVERY button (spec 13.9), plus skip-link and step numerals. */

  /* ---------- Shadows: warm-tinted (58,44,32), never black, never neutral ---------- */
  --shadow-1: 0 1px 2px rgba(58, 44, 32, .06), 0 2px 6px rgba(58, 44, 32, .04);
  --shadow-2: 0 2px 5px rgba(58, 44, 32, .08), 0 8px 20px -6px rgba(58, 44, 32, .10);
  --shadow-3: 0 2px 6px rgba(58, 44, 32, .06), 0 24px 48px -20px rgba(58, 44, 32, .28);

  /* ---------- Spacing scale (4-based). No value outside this list. ---------- */
  --s-1:   4px;  --s-2:   8px;  --s-3:  12px;  --s-4:  16px;
  --s-5:  20px;  --s-6:  24px;  --s-7:  32px;  --s-8:  40px;
  --s-9:  48px;  --s-10: 64px;  --s-11: 80px;  --s-12: 88px;
  --s-13: 96px;  --s-14:128px;  --s-15:160px;

  /* ---------- Grid & containers ---------- */
  --container:  1176px;  /* 1128 content + 24 padding each side */
  --content:    1128px;  /* 12 x 72 + 11 x 24 = 1128, exactly */
  --col:          72px;
  --gutter:       24px;
  --measure-lede: 648px; /* 7 cols - leads, section intros */
  --measure-body: 552px; /* 6 cols - running body copy, ~65ch at 17px */
  --header-h:     64px;

  /* ---------- z-index: five stops, no others ---------- */
  --z-base:      0;
  --z-raised:   10;   /* artefact page 1 over page 2, sticky panel */
  --z-stickybar:60;   /* mobile buy bar */
  --z-header:  100;
  --z-overlay: 200;   /* skip link, mobile nav panel */

  /* ---------- Motion: TWO curves. There is no third. ---------- */
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);  /* user-triggered: hover, press, open */
  --ease-out-expo:  cubic-bezier(0.16, 1, 0.3, 1);  /* entrances only */
  --t-press:   80ms;
  --t-hover:  120ms;
  --t-state:  240ms;
  --t-enter:  600ms;
  --t-hero:   900ms;
}


/* ==========================================================================
   2. RESET AND BASE - spec 3.1, 10
   ========================================================================== */

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

html {
  font-family: var(--font-sans);   /* so no node ever computes the UA serif */
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 17px;
  font-weight: 400;
  line-height: 1.65;
  letter-spacing: -0.011em;
  color: var(--ink);
  background: var(--bg);
  font-synthesis: none;          /* spec 3.1.3 - no browser may fake an italic or a bold */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* --- The italic ban (spec 3.1.3). Brawler ships no italic; the fallback to
       Georgia is the single most expensive-looking defect on the site. --- */
em, i, cite, dfn, var, address, q, blockquote, figcaption, u, s,
.h1-serif em, .display em {
  font-style: normal;
}

img, svg, video, canvas { max-width: 100%; height: auto; }
svg { display: block; }

p { margin: 0 0 1em; text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

h1, h2, h3, h4, h5, h6 {
  margin: 0 0 0.5em;
  font-family: var(--font-sans);
  font-weight: 600;
  text-wrap: balance;
}

ul, ol { margin: 0 0 1em; padding-left: 22px; }
li { margin-bottom: 0.35em; }

hr, .divider { border: 0; border-top: 1px solid var(--rule); margin: 0; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }

strong, b { font-weight: 600; }   /* Inter 700 is banned - spec 3.1.5 */
th { font-weight: 500; text-align: left; }   /* UA default is 700, also banned */

button, input, select, textarea { font-family: inherit; }

::selection { background: var(--accent-soft); color: var(--ink); }

/* --- Focus. spec 10: 2px ring, offset 3px, on every interactive element. --- */
a:focus-visible,
button:focus-visible,
select:focus-visible,
input:focus-visible,
textarea:focus-visible,
summary:focus-visible,
details:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
/* Fallback for browsers with no :focus-visible support. Scoped with @supports
   so no rule anywhere in this file writes `outline: none` (checklist 22). */
@supports not selector(:focus-visible) {
  a:focus, button:focus, select:focus, input:focus,
  textarea:focus, summary:focus, [tabindex]:focus {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
  }
}
/* On accent fills the ring is ink; on the dark band it is --on-dark. */
.on-accent:focus-visible { outline-color: var(--ink); }
.section--ink :focus-visible { outline-color: var(--on-dark); }

/* Purely a clipping utility. `font: inherit` matters: without it the UA's
   h2 default (1.5em / bolder) survives, so a visually-hidden <h2> computes
   25.5px with body tracking - an off-ladder size that fails checklist 8 on
   any computed-style dump even though no sighted user can see it. */
.visually-hidden {
  position: absolute !important;
  font: inherit;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

.skip-link {
  position: absolute; left: 16px; top: -48px;
  background: var(--ink); color: #fff;
  padding: 10px 16px;
  border-radius: var(--r-pill);   /* one of the two sanctioned pills */
  z-index: var(--z-overlay);
  transition: top var(--t-state) var(--ease-out-quart);
}
.skip-link:focus { top: 12px; text-decoration: none; }


/* ==========================================================================
   3. TYPE SCALE - spec 3.2 / 3.3

   Tracking is a pure function of rendered size (spec 3.3, rule 1):
     size >= 48px          -> -0.025em
     28-47px               -> -0.02em
     17-27px               -> -0.011em
     13-16px               ->  0
     <= 12px, uppercase    -> +0.02em

   Clamped tokens cross a band as the viewport changes, so each clamped class
   carries its small-viewport band value here and is corrected upward in the
   "tracking band corrections" block below. The correction widths are the exact
   viewport widths at which the clamp's vw term crosses the band boundary; they
   are derived, not invented.
   ========================================================================== */

/* --- Display / H1. Brawler 400, once per page, never below 22px. --- */
.display,
.h1-serif {
  font-family: var(--font-serif);
  font-optical-sizing: auto;
  font-weight: 400;
  font-size: clamp(40px, 5.4vw, 64px);
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-wrap: balance;
  margin: 0;
}
/* The accent word in the H1: Brawler roman in --accent. Never italic,
   never weight 200 (spec 3.1.3 / 3.1.4 / migration item 3). */
.display em,
.h1-serif em {
  font-style: normal;
  font-weight: 400;
  color: var(--accent);
}

.h2 {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: clamp(30px, 4.2vw, 48px);
  line-height: 1.08;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-wrap: balance;
  margin: 0;
}
/* Two-tone headline (spec 3.3, rule 3): sentence 2 drops to --ink-72 at the
   same size and weight. Cap the whole heading at 12 words. */
.h2 .dim, .h3 .dim, .display .dim { color: var(--ink-72); }

.h3 {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: clamp(22px, 2.4vw, 28px);
  line-height: 1.20;
  letter-spacing: -0.011em;
  color: var(--ink);
  text-wrap: balance;
  margin: 0;
}

.h4 {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 20px;
  line-height: 1.30;
  letter-spacing: -0.011em;
  color: var(--ink);
  text-wrap: balance;
  margin: 0;
}

.lede {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: clamp(17px, 1.6vw, 19px);
  line-height: 1.55;
  letter-spacing: -0.011em;
  color: var(--ink);
  max-width: var(--measure-lede);
  text-wrap: pretty;
  margin: 0;
}

/* Body is the document default set on <body>: 17px / 1.65 / -0.011em / --ink.
   .body exists so a builder can restate it on an element that inherited
   something else. Body copy is NEVER grey (spec 3.3). */
.body {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 17px;
  line-height: 1.65;
  letter-spacing: -0.011em;
  color: var(--ink);
  max-width: var(--measure-body);
  text-wrap: pretty;
}
/* The one sanctioned subordinate body colour: a description under a bold
   label, or the second half of a two-tone heading. */
.body--sub, .desc { color: var(--ink-72); }

.ui {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 15px;
  line-height: 1.45;
  letter-spacing: 0;
  color: var(--ink);
}

.meta {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 13.5px;
  line-height: 1.50;
  letter-spacing: 0;
  color: var(--ink-62);
  margin: 0;
}

.micro {
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 12px;
  line-height: 1.45;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink-62);
}

/* Eyebrow is .micro as a block. Spec 6 keeps it on three sections only. */
.eyebrow {
  display: inline-block;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 12px;
  line-height: 1.45;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink-62);
  margin: 0 0 14px;
}

.num {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(40px, 4.6vw, 56px);
  line-height: 1.00;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  display: block;
}

.price {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(56px, 6vw, 72px);
  line-height: 1.00;
  letter-spacing: -0.025em;   /* min clamp value is 56px, so always the >=48 band */
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  display: block;
}

.quote {
  font-family: var(--font-serif);
  font-weight: 400;
  font-style: normal;          /* roman, not italic */
  font-size: clamp(24px, 3vw, 32px);
  line-height: 1.30;
  letter-spacing: -0.011em;
  color: inherit;
  text-wrap: balance;
  margin: 0;
}

/* --- Tracking band corrections, on the FOUR SANCTIONED BREAKPOINTS ONLY.
       This replaced five bespoke queries at 889 / 934 / 1044 / 1143 / 1167px -
       the exact widths at which each clamp's vw term crossed a band boundary.
       They were derived correctly and they were still wrong: spec 9 permits
       four breakpoints and no others, and a 0.005em tracking step inside a
       135px-wide band is not perceptible at any size. Five extra breakpoints
       is a cost paid forever for a difference nobody can see.
       These two blocks carry the same corrections at 1024 and 1280, so every
       clamped step is on-band at the widths the design is actually specified
       at. The residual error is confined to the sliver between each clamp's
       true crossing point and the nearest sanctioned breakpoint. --- */
@media (min-width: 1024px) {
  .display, .h1-serif { letter-spacing: -0.025em; }   /* 55px+ -> the >=48 band */
  .quote              { letter-spacing: -0.02em;  }   /* 31px+ -> the 28-47 band */
}
@media (min-width: 1280px) {
  .h2  { letter-spacing: -0.025em; }                  /* clamped at 48px */
  .num { letter-spacing: -0.025em; }                  /* clamped at 56px */
  .h3  { letter-spacing: -0.02em;  }                  /* clamped at 28px */
}

/* Every figure on the page is tabular. */
.tabular { font-variant-numeric: tabular-nums; }


/* ==========================================================================
   4. GRID, CONTAINER, MEASURE - spec 2, 9
   Container 1176 (content 1128) = 12 x 72 + 11 x 24.
   Gutters: 24 at >=1280, 32 at 1024-1279, 24 at 768-1023, 20 below 768.
   ========================================================================== */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: 24px;
}
.content { width: 100%; max-width: var(--content); margin-inline: auto; }

@media (max-width: 1279px) { .container { max-width: none; padding-inline: 32px; } }
@media (max-width: 1023px) { .container { padding-inline: 24px; } }
@media (max-width:  767px) { .container { padding-inline: 20px; } }

/* 12-column helper. Spec 5.2: grid gap 24 desktop / 16 mobile. */
.grid-12 {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--gutter);
}
@media (max-width: 767px) { .grid-12 { gap: 16px; } }

/* Spec 13.3b: the pair is mandatory. `grid-column: span N` leaves
   grid-column-END at auto, so adding .start-M overrode only the start and
   collapsed the item to a single 72px track. `span N / span N` composes
   correctly with .start-M and is identical for single-class usage. */
.col-1  { grid-column: span 1  / span 1;  }
.col-2  { grid-column: span 2  / span 2;  }
.col-3  { grid-column: span 3  / span 3;  }
.col-4  { grid-column: span 4  / span 4;  }
.col-5  { grid-column: span 5  / span 5;  }
.col-6  { grid-column: span 6  / span 6;  }
.col-7  { grid-column: span 7  / span 7;  }
.col-8  { grid-column: span 8  / span 8;  }
.col-9  { grid-column: span 9  / span 9;  }
.col-10 { grid-column: span 10 / span 10; }
.col-11 { grid-column: span 11 / span 11; }
.col-12 { grid-column: span 12 / span 12; }

.start-2  { grid-column-start: 2;  }
.start-3  { grid-column-start: 3;  }
.start-4  { grid-column-start: 4;  }
.start-5  { grid-column-start: 5;  }
.start-6  { grid-column-start: 6;  }
.start-7  { grid-column-start: 7;  }
.start-8  { grid-column-start: 8;  }
.start-9  { grid-column-start: 9;  }
.start-10 { grid-column-start: 10; }

/* Below 768 everything is one column, left-aligned (spec 9). */
@media (max-width: 767px) {
  .grid-12 > [class*="col-"]   { grid-column: span 12 / span 12; }
  .grid-12 > [class*="start-"] { grid-column-start: 1; }
}

/* The two measures. Nothing else is a legal text width. */
.measure-lede { max-width: var(--measure-lede); }
.measure-body { max-width: var(--measure-body); }

/* Horizontal scroll is never permitted on the body (spec 9). */
.no-x { overflow-x: clip; }


/* ==========================================================================
   5. COMPONENTS - spec 4
   ========================================================================== */

/* --------------------------------------------------------------------------
   5.1 Buttons - spec 4.1
   Three heights (40 / 48 / 56). One radius (8px). One weight (500).
   Zero box-shadow, ever. No 999px radius, ever.
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 48px;
  padding: 0 24px;
  border: 0;
  /* Michael's call, 8 Aug 2026 (spec 13.9): buttons are PILLS. The 8px
     experiment is reverted. Pills were his original, approved decision. */
  border-radius: var(--r-pill);
  background: none;
  box-shadow: none;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  touch-action: manipulation;
  transition:
    background-color var(--t-hover) linear,
    border-color     var(--t-hover) linear,
    color            var(--t-hover) linear,
    transform        var(--t-press) var(--ease-out-quart);
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn svg { flex: 0 0 auto; width: 16px; height: 16px; }

.btn--lg { height: 56px; padding: 0 28px; font-size: 16px; }
.btn--lg svg { width: 18px; height: 18px; }

.btn--sm { height: 40px; padding: 0 20px; font-size: 15px; }
.btn--sm svg { width: 16px; height: 16px; }

.btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-pill);   /* must follow the button's own shape */
}

.btn:disabled,
.btn[aria-disabled="true"],
.btn.is-disabled { opacity: .45; pointer-events: none; }

/* The money CTA. Exactly three per page. Its label always carries the price.
   Michael's call, 8 Aug 2026 (spec 13.9): the accent NEVER fills a button. The
   buy path is now black like every other primary action, and earns its primacy
   from size (.btn--lg), position, and carrying the price in its own label.
   The class is retained — it marks the buy path for the audit and for analytics
   — it simply no longer changes the fill. */
/* THE BUY COLOUR — Michael's call, 8 Aug 2026 (spec 13.10).
   Stripe's #635BFF, applied to the buy path ONLY: .btn--money, the header CTA
   and the mobile buy bar. Everything else stays ink, exactly as Stripe keeps
   one purple CTA against white and outline buttons.

   This is a SECOND hue alongside --accent sandstone, which is a deliberate
   departure from the one-accent rule in spec 2. It is tokenised here so the
   whole decision is one line to tune or revert. Note it is a COOL hue on a
   deliberately warm palette — see spec 13.10 for what that costs and what to
   watch. */
:root {
  --buy:        #131211;
  --buy-hover:  #2a2420;
  --buy-press:  #2a2420;
}

.btn--money { background: var(--buy); color: #fff; }
.btn--money:hover { background: var(--buy-hover); }
.btn--money:active { background: var(--buy-press); transform: translateY(1px); }
/* Sandstone focus ring on a purple fill is muddy and under 3:1; ink reads
   cleanly against both. */
.btn--money:focus-visible { outline-color: var(--ink); }

.btn--primary { background: var(--ink); color: #fff; }
.btn--primary:hover { background: var(--ink-hover); }

/* The header CTA is the buy path, so it takes the buy colour at EVERY width —
   Stripe's "Contact sales" is purple at desktop too, sitting beside a plain
   outline "Sign in". Declared after .btn--primary because both are (0,1,0) and
   source order decides. */
.site-header__cta { background-color: var(--buy); color: #fff; }
.site-header__cta:hover { background-color: var(--buy-hover); }
.site-header__cta:active { background-color: var(--buy-press); }
.site-header__cta:focus-visible { outline-color: var(--ink); }

/* Replaces the deleted .btn--secondary: a --surface fill equals the --surface
   band background and reads as disabled. Outline survives on every surface. */
.btn--outline {
  background: transparent;
  border: 1px solid rgba(19, 18, 17, .28);
  color: var(--ink);
}
.btn--outline:hover { border-color: var(--ink); background: rgba(19, 18, 17, .04); }

/* Tertiary path. ~20x less visual weight than the primary. Trailing arrow is
   part of the component: do not also type one into the markup. */
.btn--quiet {
  height: auto;
  min-height: 44px;
  padding: 12px 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--ink);
  font-size: 15px;
  font-weight: 500;
  gap: 6px;
}
.btn--quiet::after { content: "\2192"; font-size: 1.05em; line-height: 1; }
.btn--quiet:hover { text-decoration: underline; text-underline-offset: 3px; }
.btn--quiet:active { transform: none; }

/* On the one dark band. */
.section--ink .btn--outline { border-color: rgba(255, 255, 255, .32); color: var(--on-dark); }
.section--ink .btn--outline:hover { border-color: var(--on-dark); background: rgba(255, 255, 255, .06); }
.section--ink .btn--quiet { color: var(--on-dark); }

/* Full-width stacked CTAs below 768 (spec 9). */
@media (max-width: 767px) {
  .btn-row { display: flex; flex-direction: column; gap: 12px; align-items: stretch; }
  .btn-row > .btn { width: 100%; }
}
.btn-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }

/* --------------------------------------------------------------------------
   5.2 Inputs and select - spec 4.2
   No icon inside a field, ever. No search magnifier: we have no address search
   and must not imply one.
   -------------------------------------------------------------------------- */
.field { display: block; margin: 0 0 var(--s-4); }

.label,
.field > label {
  display: block;
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--ink);
  margin-bottom: 6px;
}

.input,
.select,
input[type="text"],
input[type="email"],
input[type="tel"],
input[type="url"],
input[type="number"],
select {
  width: 100%;
  height: 48px;
  padding: 0 14px;
  border-radius: var(--r-sm);
  background: #fff;
  border: 1px solid var(--rule-strong);
  box-shadow: none;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: 0;
  color: var(--ink);
  transition: border-color var(--t-hover) linear;
}

::placeholder { color: var(--ink-62); opacity: 1; }

.input:focus-visible,
.select:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  border-color: var(--ink);
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* 12 x 8 chevron in --ink-62, 14px from the right edge, not hit-testable.
   The full 48px height remains the hit area. */
select,
.select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 40px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'><path d='M1 1.6L6 6.4L11 1.6' stroke='rgba(19,18,17,0.62)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 12px 8px;
  pointer-events: auto;
}

.input--error,
[aria-invalid="true"] { border-color: var(--accent); }

.field-error {
  display: block;
  margin-top: 6px;
  font-size: 13.5px;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--accent);
}

/* --------------------------------------------------------------------------
   5.3 Cards and panels - spec 4.3
   Border OR shadow, never both. Nothing here is ever shadowed.
   -------------------------------------------------------------------------- */
.card {
  background: var(--surface);
  border: 0;
  border-radius: var(--r-md);
  box-shadow: none;
  padding: var(--s-6);
  color: var(--ink);
}
.card--on-surface { background: #fff; border: 1px solid var(--rule); }
.card--on-dark {
  background: rgba(255, 255, 255, .06);
  border: 1px solid rgba(255, 255, 255, .12);
  color: var(--on-dark);
}

.panel {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: none;
  padding: var(--s-7);
}

@media (max-width: 767px) {
  .card  { padding: var(--s-5); }
  .panel { padding: var(--s-5); }
}

/* Hover exists only where the whole card is a link. Never a lift, never a
   shadow, never a scale. */
a.card,
.card--link {
  display: block;
  color: var(--ink);
  text-decoration: none;
  transition: border-color 160ms var(--ease-out-quart);
}
a.card:hover,
.card--link:hover { border-color: var(--ink); text-decoration: none; }

/* --------------------------------------------------------------------------
   5.4 Chips - spec 4.4
   The rest border is the same colour as its own fill so the active state
   cannot shift the box model by 2px.
   -------------------------------------------------------------------------- */
.chip-group { display: flex; flex-wrap: wrap; gap: var(--s-2); }

/* 32px visual + 6px top/bottom wrapper padding = 44px hit area. */
.chip-wrap { display: inline-flex; align-items: center; padding: 6px 0; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  padding: 0 12px;
  border-radius: var(--r-sm);
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0;
  background: var(--surface);
  color: var(--ink-72);
  border: 1px solid var(--surface);
  box-shadow: none;
  cursor: pointer;
  white-space: nowrap;
  touch-action: manipulation;
  transition:
    background-color var(--t-state) var(--ease-out-quart),
    color            var(--t-state) var(--ease-out-quart);
}
.chip[aria-pressed="true"],
.chip[aria-selected="true"],
.chip.is-active {
  background: var(--accent-soft);
  color: var(--accent);
  border-color: rgba(166, 62, 38, .25);
}
/* Colour is never the sole signal: the active chip also carries a tick. */
.chip__tick { width: 12px; height: 12px; flex: 0 0 auto; }

/* --------------------------------------------------------------------------
   5.5 Tabs - spec 4.5
   Underline only; no pills, no boxes. Guide pages only - the homepage ships
   zero tab groups.
   -------------------------------------------------------------------------- */
.tabs {
  display: flex;
  gap: var(--s-6);
  border-bottom: 1px solid var(--rule);
}
.tab {
  display: inline-flex;
  align-items: center;
  height: 44px;
  padding: 0 4px;
  border: 0;
  border-radius: 0;
  background: none;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0;
  color: var(--ink-72);
  cursor: pointer;
  touch-action: manipulation;
  box-shadow: inset 0 -2px 0 0 transparent;
  transition:
    color      var(--t-state) var(--ease-out-quart),
    box-shadow var(--t-state) var(--ease-out-quart);
}
.tab[aria-selected="true"] {
  color: var(--ink);
  box-shadow: inset 0 -2px 0 0 var(--accent);
}

/* --------------------------------------------------------------------------
   5.6 Tables - spec 4.6
   -------------------------------------------------------------------------- */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.table { width: 100%; border-collapse: collapse; }
.table th {
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.45;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: var(--ink-62);
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--rule-strong);
}
.table td {
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--ink);
  padding: 12px;
  border-bottom: 1px solid var(--rule);
}
/* Every numeric cell and its header. */
.table th.is-num,
.table td.is-num,
.table th[data-num],
.table td[data-num] { font-variant-numeric: tabular-nums; text-align: right; }

/* Row hover only where rows are links. Static data tables do not respond. */
.table--links tbody tr:hover { background: rgba(19, 18, 17, .025); }

/* A source line sits 10px below every table. */
.table-source {
  margin-top: 10px;
  font-size: 13.5px;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--ink-62);
}

/* --------------------------------------------------------------------------
   5.7 Badges - spec 4.7
   Never a number that is not sourced. Never "NEW" / "POPULAR" / "BEST VALUE".
   -------------------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--r-xs);
  background: var(--surface-2);
  color: var(--ink-72);
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.badge--accent { background: var(--accent-soft); color: var(--accent); }
.badge--source { background: transparent; border: 1px solid var(--rule); color: var(--ink-62); }

/* --------------------------------------------------------------------------
   5.8 Tooltips - spec 4.8
   ONLY to expand a source abbreviation (ESPC, RoS, OGL v3.0). Never for
   anything a user needs in order to act. If the keyboard behaviour in spec 4.8
   cannot be built, print the expansion inline as 13.5px --ink-62 meta instead.
   -------------------------------------------------------------------------- */
.tooltip { position: relative; display: inline-flex; }

.tooltip__trigger {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 12px 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  cursor: help;
  text-decoration: underline dotted;
  text-underline-offset: 3px;
  touch-action: manipulation;
}

.tooltip__bubble {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 8px);
  transform: translateX(-50%);
  z-index: var(--z-overlay);
  width: max-content;
  max-width: 260px;
  padding: 8px 10px;
  border-radius: 6px;
  background: var(--surface-ink);
  color: var(--on-dark);
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: 0;
  box-shadow: var(--shadow-2);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition:
    opacity    var(--t-state) var(--ease-out-quart),
    visibility 0s linear var(--t-state);
}
.tooltip__trigger:hover + .tooltip__bubble,
.tooltip__trigger:focus-visible + .tooltip__bubble,
.tooltip__bubble.is-open {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

/* --------------------------------------------------------------------------
   5.9 Accordion - spec 4.9
   Native <details> / <summary>. No JS.
   -------------------------------------------------------------------------- */
.accordion > details { border-bottom: 1px solid var(--rule); }
.accordion > details:first-of-type { border-top: 1px solid var(--rule); }

.accordion summary {
  position: relative;
  display: block;
  list-style: none;
  padding: 20px 44px 20px 0;
  font-family: var(--font-sans);
  font-size: 20px;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.011em;
  color: var(--ink);
  cursor: pointer;
}
.accordion summary::-webkit-details-marker { display: none; }
.accordion summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

.accordion summary::after {
  content: "+";
  position: absolute;
  right: 0;
  top: 50%;
  margin-top: -10px;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  font-weight: 400;
  line-height: 1;
  color: var(--ink-62);
  transform-origin: center;
  transition: transform var(--t-state) var(--ease-out-quart);
}
.accordion details[open] summary::after { transform: rotate(45deg); }

.accordion__body {
  padding: 0 44px 22px 0;
  max-width: var(--measure-lede);
  font-family: var(--font-sans);
  font-size: 17px;
  font-weight: 400;
  line-height: 1.65;
  letter-spacing: -0.011em;
  color: var(--ink-72);
}
.accordion__body p:last-child { margin-bottom: 0; }

/* --------------------------------------------------------------------------
   5.10 Ruled list - the de-boxed alternative to cards (spec 6, sections 3/5/10)
   -------------------------------------------------------------------------- */
.ruled-list { list-style: none; margin: 0; padding: 0; }
.ruled-list > li {
  margin: 0;
  padding: 18px 0;
  border-bottom: 1px solid var(--rule);
}
.ruled-list > li:first-child { border-top: 1px solid var(--rule); }
@media (max-width: 767px) { .ruled-list > li { padding: 16px 0; } }

.tick { flex: 0 0 auto; color: var(--accent); }


/* ==========================================================================
   6. VERTICAL RHYTHM - spec 5
   Asymmetric 1 : 1.45. Content sits optically high in its band.
   No section may end in padding.
   ========================================================================== */

/* The bare element carries the standard rhythm so no page can ship unpadded. */
section,
.section { padding: 88px 0 128px; }

.section--compact { padding: 40px 0; }      /* source row, steps strip */
.section--closing { padding: 128px 0 160px; }

@media (max-width: 1023px) {
  section, .section   { padding: 64px 0 96px; }
  .section--compact   { padding: 32px 0; }
  .section--closing   { padding: 96px 0 128px; }
}
@media (max-width: 767px) {
  section, .section   { padding: 48px 0 72px; }
  .section--compact   { padding: 24px 0; }
  .section--closing   { padding: 72px 0 96px; }
}

/* Surfaces a section can sit on. */
.section-surface { background: var(--surface); }
.section--ink { background: var(--surface-ink); color: var(--on-dark); }
.section--ink .h2, .section--ink .h3, .section--ink .h4,
.section--ink .display, .section--ink .lede { color: var(--on-dark); }
.section--ink .meta, .section--ink .micro, .section--ink .eyebrow { color: var(--on-dark-62); }

/* Two consecutive sections on the SAME surface are separated by a hairline,
   not by doubled padding. */
.section--ruled { border-top: 1px solid var(--rule); }

/* --- Standard gaps inside a section (spec 5.2). Desktop / mobile. --- */
.gap-eyebrow-h2   { margin-bottom: 14px; }
.gap-h2-lede      { margin-bottom: 20px; }
.gap-h2-content   { margin-bottom: 40px; }
.gap-lede-content { margin-bottom: 48px; }
.gap-h3-body      { margin-bottom: 12px; }
.gap-h4-desc      { margin-bottom: 6px;  }
.gap-body-cta     { margin-bottom: 28px; }
.gap-cta-note     { margin-top: 16px; }    /* reassurance ALWAYS below the CTA */
.gap-figure-claim { margin-top: 10px; }
.gap-claim-source { margin-top: 10px; }

@media (max-width: 767px) {
  .gap-eyebrow-h2   { margin-bottom: 12px; }
  .gap-h2-lede      { margin-bottom: 16px; }
  .gap-h2-content   { margin-bottom: 28px; }
  .gap-lede-content { margin-bottom: 32px; }
  .gap-body-cta     { margin-bottom: 24px; }
  .gap-cta-note     { margin-top: 14px; }
}


/* ==========================================================================
   7. SITE CHROME - spec 6 row 0, migration item 7
   Header is OPAQUE #fff. No backdrop-filter: content was ghosting through it.
   ========================================================================== */

.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  height: var(--header-h);
  background: #fff;
  border-bottom: 1px solid var(--rule);
}
/* Logo and nav are ONE left-hand group; the CTA is pushed to the far right by
   margin-left:auto. A centred nav is what all three reference headers reject:
   with three links it reads as decoration floating in 1128px. */
/* The 64 / 32 pair is a 2:1 ratio, and 2:1 is what makes the eye read "brand"
   and "navigation" as two zones rather than one 360px blob. At the previous
   40 / 28 the ratio was 1.46:1 and the whole left side read as one object. */
.site-header .container {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--s-10);
}

/* 19px is an existing size step (--t-lede) and keeps the -0.011em tracking
   band, so nothing moves off the ladder. At 17px/600 the logotype was only
   1.13x the nav links and one weight step above them - a bolder nav item,
   not a mark. 19/600 makes it 1.27x. Hit box: 8 + 31.35 + 8 = 47px. */
.logo {
  display: inline-flex;
  align-items: baseline;
  padding: 8px 0;
  font-family: var(--font-sans);
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.011em;
  color: var(--ink);
}
.logo:hover { text-decoration: none; }
.logo .dot { color: var(--accent); }

.main-nav { display: flex; align-items: center; gap: var(--s-7); }

.nav-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding: 12px 0;            /* 44px hit box (spec 10 prescribes this value) */
  min-width: 44px;            /* ...and 44 wide, which "FAQ" alone would miss */
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--ink);
}
.nav-link:hover { text-decoration: none; }
.nav-link .line {
  position: absolute; left: 0; bottom: 8px;
  width: 100%; height: 1px;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-state) var(--ease-out-quart);
}
.nav-link:hover .line,
.nav-link:focus-visible .line { transform: scaleX(1); }

.header-cta { display: flex; align-items: center; gap: 18px; margin-left: auto; }
.header-cta .text-link {
  display: inline-flex; align-items: center;
  padding: 12px 0;
  font-size: 15px; font-weight: 500; letter-spacing: 0;
  color: var(--ink);
}

.nav-toggle {
  display: none;
  width: 44px; height: 44px;
  align-items: center; justify-content: center;
  background: none; border: 0; padding: 0;
  cursor: pointer; touch-action: manipulation;
}

/* Header CTA label. Above 768 it reads "Get the report" in ink. Below 768 it
   takes the accent fill and grows its price; below 480 it also drops to the
   short noun, because "Get the report - £15" plus the logo plus the burger
   does not fit a 390px header without the logo and the button colliding. */
.site-header__cta-price { display: none; }
.site-header__cta-short { display: none; }
/* .btn's 8px gap is the ICON gap (spec 4.1); it has no business sitting
   between three label spans. It gave the em dash 8px on its left and a
   collapsed 4px word space on its right, so the button's only piece of copy
   read as a double space. gap:0 alone is not enough - a flex item is
   blockified, so the leading space inside the price span collapses too.
   The markup therefore carries a non-breaking space and the spacing is back
   in the text, 4px on both sides of the dash. */
.site-header__cta { gap: 0; }

/* Spec 4.1 fixes .btn--sm at 40px; spec 10 fixes the minimum hit box at 44x44.
   They disagree by 4px, and spec 10 states the resolution itself: "achieved
   with PADDING rather than height where the visual object is smaller".

   So the element is 44px and the button PAINTS at 40px inside it, using a
   TRANSPARENT VERTICAL BORDER rather than padding:
     height 44 - 2px border top and bottom = a 40px padding box;
     background-clip: padding-box paints the fill in that 40px box;
     border-inline stays 0, so the fill keeps its full width — which is why
     this is not done with padding: .btn--sm's `padding: 0 20px` would have
     been clipped away too, stranding the label outside its own black box.
   Radius is now --r-pill (spec 13.9), which stays a pill on both the border
   box and the inset padding box, so the 2px inset no longer has to be
   compensated for.
   The hit box is therefore a real 44px of pointer and layout area, not an
   overlay, and the header's 64px rhythm is unchanged. */
.site-header__cta,
.site-header__sample {
  height: 44px;
  border-block: 2px solid transparent;
  background-clip: padding-box;
  border-radius: var(--r-pill);
}
/* The outline variant cannot use the transparent-border trick: a border whose
   top and bottom are transparent and whose sides are visible draws a pair of
   brackets, not a button — which is exactly what shipped and what the first
   screenshot caught. The hit box and the visible edge have to be different
   objects here, so the ELEMENT stays a transparent 44px target and a
   pseudo-element draws the 40px hairline rectangle inside it. */
.site-header__sample {
  border: 0;
  background: none;
  position: relative;
}
.site-header__sample::before {
  content: "";
  position: absolute;
  inset: 2px 0;                     /* the 40px painted box inside the 44px target */
  border: 1px solid rgba(19, 18, 17, .28);
  border-radius: var(--r-pill);     /* pill, on the box that is seen (spec 13.9) */
  transition: border-color var(--t-hover) linear, background-color var(--t-hover) linear;
}
.site-header__sample:hover::before { border-color: var(--ink); background: rgba(19, 18, 17, .04); }
.site-header__sample > * { position: relative; }   /* label above the outline */

/* Below 900 the sticky buy bar takes over the persistent buy job, and at 390
   the logo, two buttons and a burger cannot share a 64px header without
   colliding. The sceptic's route survives as the hero's own "See a sample"
   button, which is 56px and unmissable. */
@media (max-width: 899px) { .site-header__sample { display: none; } }

/* Spec 9, <768: logo left / buy CTA right / burger far right. Never a centred
   floating pill.

   Michael's call, 8 Aug 2026 (spec 13.9): the accent never fills a button, so
   this CTA is simply .btn--primary black at every width. Its label still grows
   the price below 768 — that was always the more useful half of the device,
   and it is what distinguishes the buy path now that fill no longer does. */
@media (max-width: 767px) {
  .site-header .container { gap: var(--s-4); }

  .main-nav {
    /* No-JS default: a visible panel. Spec 10 forbids display:none without JS. */
    position: absolute; top: var(--header-h); left: 0; right: 0;
    z-index: var(--z-overlay);
    flex-direction: column; align-items: flex-start; gap: 0;
    background: #fff;
    border-bottom: 1px solid var(--rule);
    padding: 8px 20px 16px;
  }
  .js .main-nav { display: none; }
  .js .main-nav.open { display: flex; }
  .main-nav .nav-link { font-size: 20px; font-weight: 600; letter-spacing: -0.011em; padding: 12px 0; }

  .header-cta { gap: 8px; }
  .header-cta .text-link { display: none; }

  .site-header__cta-price { display: inline; }
  /* background-COLOR, not the `background` shorthand. The shorthand resets
     every background longhand to its initial value, which silently threw away
     the `background-clip: padding-box` that builds this button's 44px hit box
     around a 40px painted box — so below 768 the fill covered the full 44px
     and the painted radius jumped. Caught by the audit at 390px, not at 1440.
     Do not turn these back into the shorthand. */
  .site-header__cta          { background-color: var(--buy); color: #fff; }
  .site-header__cta:hover    { background-color: var(--buy-hover); }
  .site-header__cta:active   { background-color: var(--buy-press); }

  /* The burger only exists where it works. */
  .nav-toggle { display: none; }
  .js .nav-toggle { display: inline-flex; }
}

@media (max-width: 479px) {
  .site-header__cta-long  { display: none; }
  .site-header__cta-short { display: inline; }
}

/* Mobile sticky buy bar (spec 6, final row). Hidden until JS reveals it; with
   no JS the page still carries a buy CTA in the header. */
.buy-bar {
  position: fixed; left: 0; right: 0; bottom: 0;
  z-index: var(--z-stickybar);
  height: 60px;
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 0 20px;
  background: #fff;
  border-top: 1px solid var(--rule);
  transform: translateY(100%);
  transition: transform var(--t-state) var(--ease-out-quart);
}
.buy-bar.is-visible { transform: translateY(0); }
@media (max-width: 899px) { .buy-bar { display: flex; } }

/* --- Footer. Legal copy is load-bearing; do not edit its words. --- */
.site-footer {
  border-top: 1px solid var(--rule);
  padding: 48px 0 40px;
  background: var(--surface);
}
.site-footer .cols {
  display: flex; justify-content: space-between; gap: 32px; flex-wrap: wrap;
  margin-bottom: 32px;
}
.site-footer nav { display: flex; gap: 22px; flex-wrap: wrap; }
.site-footer a {
  display: inline-flex; align-items: center;
  padding: 12px 0;
  color: var(--ink-72); font-size: 15px; font-weight: 500; letter-spacing: 0;
}
.site-footer a:hover { color: var(--ink); }
.site-footer .legal {
  font-size: 13.5px; line-height: 1.7; letter-spacing: 0;
  color: var(--ink-62); max-width: 820px;
}
.site-footer .legal p { margin-bottom: 0.6em; }


/* ==========================================================================
   8. THE ARTEFACT - shared skeleton only (spec 7, migration item 10)

   Promoted here out of the sample-report page so all three appearances
   (hero, section 5, price) share one object. Detailed internal styling -
   the em ladder from spec 7.2, the KPI row, the comparables table, the spread
   chart - is the next agent's work. What is fixed here and must not change:
     - the paper edge: #fff, 1px --rule, 4px radius, --shadow-3
     - the ONLY shadowed thing on the site
     - page 2 offset translate(28px,-20px) rotate(1.5deg), max rotation 2deg
     - font-size:1px scaling container so the whole ladder tunes with one number
   ========================================================================== */

.artefact {
  position: relative;
  width: 640px;
  max-width: 100%;
  font-size: 1px;                 /* the ladder in spec 7.2 is written in em */
  font-family: var(--font-sans);
  color: var(--ink);
}

.artefact__page {
  position: relative;
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: var(--r-xs);     /* paper corners are sharp */
  box-shadow: var(--shadow-3);
}
.artefact__page--front  { z-index: var(--z-raised); }
.artefact__page--behind {
  position: absolute;
  inset: 0;
  transform: translate(28px, -20px) rotate(1.5deg);
  box-shadow: var(--shadow-2);
  z-index: var(--z-base);
}

/* Compliance device and crop in one: a visible absence, sized in px to the
   digit count it replaces. Container gets aria-label, never one per block. */
.redaction {
  display: inline-block;
  height: 0.95em;
  vertical-align: middle;
  background: var(--rule-strong);
  border-radius: var(--r-xs);
}

.artefact__kpis { display: grid; grid-template-columns: repeat(3, 1fr); }
.artefact__kpi  { background: var(--surface); border-radius: var(--r-sm); }
.artefact__bar  { display: grid; align-items: center; }
.artefact__bar .bar {
  position: relative; overflow: hidden;
  background: var(--surface-2);
  border-radius: var(--r-xs);
}
.artefact__bar .bar i {
  position: absolute; inset: 0;
  width: var(--w);
  background: var(--accent-bright);   /* graphics only - never a text colour */
  border-radius: var(--r-xs);
}

@media (max-width: 1279px) and (min-width: 1024px) { .artefact { width: 576px; font-size: 0.9px; } }
@media (max-width: 767px) {
  .artefact { width: 320px; }
  .artefact__page { box-shadow: var(--shadow-2); }
  .artefact__page--behind { display: none; }
}
@media (max-width: 479px) { .artefact { width: 300px; } }


/* ==========================================================================
   9. MOTION - reduced motion and no-JS (spec 8.4, verbatim)
   Content-visibility is the default in CSS and is only removed by the .js
   class, so a JS failure can never hide content.
   ========================================================================== */

[data-reveal], [data-rise] { opacity: 1; transform: none; }        /* default: visible */
.js [data-reveal], .js [data-rise] { opacity: 0; transform: translateY(16px); }
.js.reduced-motion [data-reveal], .js.reduced-motion [data-rise] { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
  /* Exception: colour transitions survive at 120ms so hover and focus stay perceivable. */
  a, button, summary, .chip { transition: background-color 120ms linear, color 120ms linear, border-color 120ms linear !important; }
}


/* ==========================================================================
   10. TEMPORARY - remove when guide pages are rebuilt
   ==========================================================================

   This block exists ONLY so the 14 pages that have not yet been rebuilt keep
   rendering while the homepage is redesigned. Nothing here is part of the
   design system. Every selector below is scheduled for deletion.

   Pages still depending on this block:
     /report, /sample-report, /faq, /terms, /privacy,
     /glasgow, /edinburgh, /closing-dates, /fixed-price,
     /home-report-valuation-gap, /how-much-over-home-report,
     /note-of-interest, /offers-in-excess-of, /what-does-offers-over-mean,
     and / (index) until its rebuild lands.

   Removal order: rebuild a page, delete the selectors it was the last user of.
   -------------------------------------------------------------------------- */

/* --- 10.1 Deleted tokens still referenced by inline styles.
       10 pages carry style="color:var(--ink-55)" on a <p class="detail">.
       Deleting --ink-55 outright makes those declarations invalid at computed-
       value time. Written as :where(html) - zero specificity, and a selector
       that appears nowhere else - so this file keeps exactly one :root block
       and no duplicate selectors. Delete once the inline styles are gone. --- */
:where(html) {
  --ink-55: var(--ink-72);
  --ink-40: var(--ink-62);
}

/* --- 10.2 .btn--secondary -> .btn--outline (migration item 4).
       Used by /report and by index.html until its rebuild. --- */
.btn--secondary {
  background: transparent;
  border: 1px solid rgba(19, 18, 17, .28);
  color: var(--ink);
}
.btn--secondary:hover { border-color: var(--ink); background: rgba(19, 18, 17, .04); }

/* --- 10.3 Legacy layout utilities --- */
.container--text { max-width: 720px; }
.center { text-align: center; }
section.tight { padding: 40px 0; }
@media (max-width: 1023px) { section.tight { padding: 32px 0; } }
@media (max-width:  767px) { section.tight { padding: 24px 0; } }

/* --- 10.4 Legacy type aliases --- */
.small, small {
  font-size: 13.5px;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--ink-62);
}
.headline {
  font-family: var(--font-serif);
  font-weight: 400;
  font-style: normal;
  font-size: clamp(22px, 2.4vw, 28px);
  line-height: 1.2;
  letter-spacing: -0.011em;
  margin-bottom: 8px;
}
.headline strong { font-weight: 400; font-style: normal; color: var(--accent); }
.detail { color: var(--ink-72); }
.attr, .src, .src-note, .article-meta { font-size: 13.5px; letter-spacing: 0; color: var(--ink-62); }

/* --- 10.5 Long-form guide/article pages.
       Bare h2/h3 are demoted one step against spec 3.2 so a 48px heading does
       not land on a 720px measure. These pages get real .h2 / .h3 classes at
       rebuild; the demotion goes away with this block. --- */
.article-header { padding: clamp(48px, 7vw, 88px) 0 clamp(24px, 4vw, 40px); text-align: center; }
.article { padding-bottom: clamp(56px, 9vw, 104px); }
.article h2 {
  font-size: clamp(22px, 2.4vw, 28px); font-weight: 600;
  line-height: 1.2; letter-spacing: -0.011em; margin-top: 1.8em;
}
.article h3 {
  font-size: 20px; font-weight: 600;
  line-height: 1.3; letter-spacing: -0.011em; margin-top: 1.5em;
}
/* same band correction as .h3, on a sanctioned breakpoint (spec 9: four
   breakpoints, no others). The last of the five bespoke tracking queries. */
@media (min-width: 1280px) { .article h2 { letter-spacing: -0.02em; } }
.article p, .article li { font-size: 17px; line-height: 1.65; letter-spacing: -0.011em; }
.article ul, .article ol { margin: 0 0 1.2em; padding-left: 22px; }
/* Was `border-left: 2px solid var(--accent)`. Removed for the same two reasons
   as .guide-note: spec 2 allows the accent on links, ticks, focus rings, THE
   QUOTE MARK and chart fills — a border bar is on none of those lists — and a
   coloured bar down one edge of a block is the most recognisable tell of a
   generated interface. The quote mark itself IS sanctioned, so the accent moves
   there: it now marks the quote as a quote, which is the job the bar was badly
   standing in for. Lives in the TEMPORARY compatibility block and disappears
   with it once the remaining guides are rebuilt, but it is on eight live pages
   until then. */
.article blockquote {
  position: relative;
  margin: 1.6em 0; padding: 4px 0 4px 26px;
  font-style: normal;
  color: var(--ink-72);
}
.article blockquote::before {
  content: "\201C";
  position: absolute;
  left: 0; top: -2px;
  font-family: var(--font-serif);
  font-size: 30px;
  line-height: 1;
  color: var(--accent);
}
.article .callout {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-md);
  padding: 22px 24px; margin: 1.8em 0; font-size: 15px; line-height: 1.6;
}
.article .callout p:last-child { margin: 0; }
.article .inline-cta { margin: 2.4em 0; }
.article table { width: 100%; border-collapse: collapse; margin: 1.6em 0; }
.article th {
  font-size: 12px; font-weight: 500; line-height: 1.45;
  text-transform: uppercase; letter-spacing: 0.02em; color: var(--ink-62);
  text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--rule-strong);
  font-variant-numeric: tabular-nums;
}
.article td {
  font-size: 15px; font-weight: 400; line-height: 1.5; letter-spacing: 0;
  color: var(--ink); padding: 12px; border-bottom: 1px solid var(--rule);
  font-variant-numeric: tabular-nums;
}

/* --- 10.6 Legacy FAQ list (superseded by .accordion) --- */
.faq-list { margin-top: 36px; text-align: left; }
.faq-list details { border-bottom: 1px solid var(--rule); }
.faq-list details:first-of-type { border-top: 1px solid var(--rule); }
.faq-list summary {
  position: relative; display: block; list-style: none; cursor: pointer;
  padding: 20px 44px 20px 0;
  font-size: 20px; font-weight: 600; line-height: 1.3; letter-spacing: -0.011em;
  color: var(--ink);
}
.faq-list summary::-webkit-details-marker { display: none; }
.faq-list summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }
.faq-list summary::after {
  content: "+"; position: absolute; right: 0; top: 50%; margin-top: -10px;
  width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 400; line-height: 1; color: var(--ink-62);
  transform-origin: center; transition: transform var(--t-state) var(--ease-out-quart);
}
.faq-list details[open] summary::after { transform: rotate(45deg); }
.faq-list .faq-body {
  padding: 0 44px 22px 0; max-width: var(--measure-lede);
  font-size: 17px; line-height: 1.65; letter-spacing: -0.011em; color: var(--ink-72);
}
.faq-list .faq-body p:last-child { margin-bottom: 0; }

/* --- 10.7 Legacy free area lookup (rebuilt as spec 6 section 4) --- */
.lookup {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-lg);
  padding: clamp(20px, 4vw, 32px); max-width: 720px; margin: 0 auto; text-align: left;
}
.lookup .small { display: block; margin-bottom: 18px; }
.lookup-controls { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.lookup select { flex: 1 1 260px; width: auto; }
.lookup-result { margin-top: 20px; }
.lookup-result .meta { margin-bottom: 18px; }
.lookup-upsell {
  border-top: 1px solid var(--rule-strong); padding-top: 18px; margin-top: 18px;
  display: flex; gap: 16px; align-items: center; justify-content: space-between; flex-wrap: wrap;
}
.lookup-upsell p { margin: 0; font-size: 15px; }

/* --- 10.8 Legacy homepage blocks (index.html only; deleted at rebuild)
       DELETED in this revision, with their markup: .hero / .hero-ctas /
       .hero-note (rebuilt as section 12), .source-bar + .ticker +
       @keyframes tickerSlide + .hero-map-wrap (spec 6 rows 1-2, migration
       item 5). The hero map SVG and the marquee are gone at every width, not
       hidden by a media query. --- */

.stat-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-top: 40px; }
.stat { border-top: 1px solid var(--rule); padding: 24px 0 0; text-align: left; }
.stat .num { margin-bottom: 10px; }
.stat .num sup { font-size: 0.4em; }
.stat p { margin: 0; font-size: 15px; }
.stat .src { display: block; margin-top: 10px; }
@media (max-width: 767px) { .stat-row { grid-template-columns: 1fr; gap: 16px; } }

.steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-top: 40px; }
.step { text-align: left; }
.step .n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: var(--r-pill);   /* sanctioned pill */
  background: var(--ink); color: #fff; font-size: 14px; font-weight: 600;
  margin-bottom: 14px;
}
.step h3 { font-size: 20px; line-height: 1.3; letter-spacing: -0.011em; margin-bottom: 6px; }
.step p { margin: 0; font-size: 15px; color: var(--ink-72); }
@media (max-width: 767px) { .steps { grid-template-columns: 1fr; gap: 16px; } }

.report-list { list-style: none; margin: 32px 0 0; padding: 0; text-align: left; }
.report-list li {
  display: flex; gap: 14px; align-items: flex-start; margin: 0;
  padding: 18px 0; border-bottom: 1px solid var(--rule);
}
.report-list li:first-child { border-top: 1px solid var(--rule); }
.report-list .tick { margin-top: 3px; }
.report-list strong { display: block; font-size: 17px; font-weight: 600; }
.report-list span.desc { font-size: 15px; color: var(--ink-72); }
@media (max-width: 767px) { .report-list li { padding: 16px 0; } }

.stories { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; margin-top: 40px; }
.story {
  border-top: 1px solid var(--rule); padding: 24px 0 0; text-align: left;
  display: flex; flex-direction: column; gap: 12px;
}
.story p { margin: 0; font-size: 17px; line-height: 1.6; }
.story-disclaimer { margin-top: 22px; font-size: 12.5px; letter-spacing: 0; color: var(--ink-62); }
@media (max-width: 899px) { .stories { grid-template-columns: 1fr; gap: 16px; } }

.pull-quote { max-width: 760px; margin: 0 auto; position: relative; padding-top: 34px; }
.pull-quote::before {
  content: "\201C"; position: absolute; top: -14px; left: 0;
  font-family: var(--font-serif); font-size: 96px; line-height: 1; color: var(--accent);
}
.pull-quote blockquote {
  margin: 0 0 14px;
  font-family: var(--font-serif); font-weight: 400; font-style: normal;
  font-size: clamp(24px, 3vw, 32px); line-height: 1.3; letter-spacing: -0.011em;
}
.pull-quote figcaption { font-size: 13.5px; letter-spacing: 0; color: var(--ink-62); }

.price-card {
  max-width: 480px; margin: 40px auto 0;
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-lg);
  padding: 32px; text-align: left;
}
.price-card .price { margin: 6px 0 2px; }
.price-card .price-note { font-size: 13.5px; letter-spacing: 0; color: var(--ink-72); margin-bottom: 22px; }
.reassure {
  margin: 16px 0 0; padding: 0; list-style: none;
  font-size: 15px; color: var(--ink-72);
  display: flex; flex-direction: column; gap: 6px;
}
.reassure li { margin: 0; }

.guide-cards { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; margin-top: 40px; }
.guide-card {
  display: block; background: #fff; border: 1px solid var(--rule);
  border-radius: var(--r-md); padding: 24px; color: var(--ink);
  transition: border-color 160ms var(--ease-out-quart);
}
.guide-card:hover { text-decoration: none; border-color: var(--ink); }
.guide-card h3 { font-size: 20px; line-height: 1.3; letter-spacing: -0.011em; margin-bottom: 6px; }
.guide-card p { margin: 0; font-size: 15px; color: var(--ink-72); }
.guide-card .go { display: inline-block; margin-top: 14px; font-size: 15px; font-weight: 500; letter-spacing: 0; color: var(--accent); }
@media (max-width: 767px) { .guide-cards { grid-template-columns: 1fr; gap: 16px; } }

/* --- 10.9 Legacy sample-report markup mapped onto the .artefact skeleton.
       Same paper edge, same shadow, same redaction block - only the internal
       type ladder is still px-based here. --- */
.sample-frame {
  background: var(--surface); border: 1px solid var(--rule); border-radius: var(--r-lg);
  padding: clamp(20px, 4vw, 32px); margin-top: 36px;
}
.sample-page {
  background: #fff; border: 1px solid var(--rule); border-radius: var(--r-xs);
  box-shadow: var(--shadow-3);
  padding: clamp(24px, 4vw, 48px); max-width: 640px; margin: 0 auto; text-align: left;
}
.sample-page + .sample-page { margin-top: 28px; }
.redacted {
  display: inline-block; height: 0.95em; vertical-align: middle;
  background: var(--rule-strong); border-radius: var(--r-xs);
}
.sample-kpis { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin: 20px 0; }
.sample-kpi { background: var(--surface); border-radius: var(--r-sm); padding: 14px; }
.sample-kpi .k {
  display: block; margin-bottom: 6px;
  font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-62);
}
.sample-kpi .v { font-family: var(--font-serif); font-size: 26px; font-weight: 400; line-height: 1; }
.sample-bars { margin: 18px 0 6px; display: grid; gap: 10px; }
.sample-bar {
  display: grid; grid-template-columns: 110px 1fr 52px; gap: 10px; align-items: center;
  font-size: 12.5px; color: var(--ink-72); font-variant-numeric: tabular-nums;
}
.sample-bar .bar { height: 10px; border-radius: var(--r-xs); background: var(--surface-2); position: relative; overflow: hidden; }
.sample-bar .bar i { position: absolute; inset: 0; width: var(--w); background: var(--accent-bright); border-radius: var(--r-xs); }
.sample-watermark {
  margin-top: 18px; font-size: 12px; font-weight: 500;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-62);
}
@media (max-width: 560px) { .sample-kpis { grid-template-columns: 1fr; } }

/* --- 10.10 Netlify email fallback form --- */
.buy-fallback { display: none; margin-top: 22px; text-align: left; }
.buy-fallback.show { display: block; }
.buy-fallback label { display: block; margin-bottom: 6px; font-size: 13.5px; font-weight: 600; letter-spacing: 0; }
.buy-fallback input { margin-bottom: 12px; }
.form-msg { margin-top: 10px; font-size: 15px; }

/* --- 10.11 Typed-heading shim. DELETED (spec 8.3, migration item 6) together
       with the SplitText script tag, the typed-heading code in js/main.js and
       the .typed-container markup. The shim hid .typed-content until GSAP ran;
       leaving it behind once SplitText is gone would hide those headings
       permanently. No selector in this file matches .typed-* any more. --- */


/* ==========================================================================
   11. THE ARTEFACT - the full component (spec 7)

   This section COMPLETES and SUPERSEDES the skeleton in section 8. Section 8
   is left in place because other agents are reading it; every declaration it
   makes that matters is restated here, later in the cascade, at equal or
   higher specificity. If the two ever disagree, section 11 is the one that
   renders. Delete section 8 once no branch depends on it.

   WHAT THIS IS
   A real HTML/CSS page of the Local Offers Report at true document scale.
   Not a screenshot, not a device mock. It is the only shadowed object on the
   site (spec 4.3), and its only motion, ever, is its entrance (spec 7.2/9).
   It requires zero JavaScript.

   ------------------------------------------------------------------------
   MECHANISM 1 - the single scaling number (spec 7.2 mechanism 1)
   ------------------------------------------------------------------------
   .artefact declares TWO custom properties and nothing else needs touching:

       --artefact-scale : the one number. 1 = true scale (1em == 1px inside).
       --artefact-w     : the paper width. Page height follows at A4 ratio.

   From those, --u is derived (`calc(var(--artefact-scale) * 1px)`) and set as
   the container's own font-size. Every internal font-size is therefore written
   in `em` and reads straight off the spec 7.2 table: `font-size: 13em` IS the
   13px report title. Change --artefact-scale and the whole document - type,
   padding, gaps, bar heights, redaction widths, the offset of page 2 - moves
   together, in proportion. The mobile and thumbnail variants are one line each.

   THE ONE DISCIPLINE THAT KEEPS IT HONEST
   `em` compounds. So:
     - an element that sets `font-size` NEVER also sets em-based spacing, and
       never contains another element that sets `font-size` in em;
     - all internal spacing is flex/grid `gap` or `padding` on containers that
       do NOT set a font-size (so their em resolves against --u);
     - where spacing must live on a font-size-bearing element (th, td, li),
       it is written `calc(n * var(--u))`, which is the same single number.
   Break that rule and the ladder silently multiplies. Do not break it.

   ------------------------------------------------------------------------
   THE TYPE LADDER - spec 7.2 mechanism 1, verbatim
   ------------------------------------------------------------------------
     Report title        13em    600  Inter
     Property line        9.5em  400  Inter
     KPI label            8.5em  500  Inter, uppercase, +0.06em
     KPI value           22em    400  Brawler          <- the only exception
     Section head        10em    600  Inter
     Table header         8.5em  500  Inter, uppercase, +0.02em
     Table cell          10.5em  400  Inter, tabular-nums
     Chart axis label     9em    400  Inter
     Source footnote      8em    400  Inter
     Watermark            8.5em  500  Inter, uppercase, +0.14em

   Ceiling inside the Artefact is 13px; the page-chrome floor is 12px (spec
   3.2 --t-micro). The two ranges touch and must not overlap. Do not add a
   14em anything.

   ------------------------------------------------------------------------
   COMPLIANCE - not styling. Read before editing the markup.
   ------------------------------------------------------------------------
   Every number inside the Artefact is EITHER a real published figure whose
   publisher and period are printed inside the Artefact, OR a .redaction
   block. There is no third category. The redaction block is both the legal
   device and the crop: a visible absence that implies the full thing exists.
   Never invent an address, a sale price, a comparable or a percentage.

   ------------------------------------------------------------------------
   FIXED, DO NOT CHANGE
   ------------------------------------------------------------------------
     - paper edge: #fff, 1px solid var(--rule), var(--r-xs), var(--shadow-3)
     - no chrome of any kind: no traffic lights, URL bar, bezel or laptop
     - page 2 offset translate(28em, -20em) rotate(1.5deg) - 1.5deg is the max
     - no transition and no animation is declared anywhere in this section
   ========================================================================== */

/* --------------------------------------------------------------------------
   11.1 The scaling container
   -------------------------------------------------------------------------- */
.artefact {
  --artefact-scale: 1;
  --artefact-w: 640px;
  --u: calc(var(--artefact-scale) * 1px);

  position: relative;
  width: var(--artefact-w);
  max-width: none;                 /* see the warning below */
  margin: 0;                       /* it ships as a <figure> */
  font-family: var(--font-sans);
  font-size: var(--u);             /* the ladder above is written in em */
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: 0;
  color: var(--ink);
}

/* WARNING - max-width is deliberately `none`, not 100%.
   The Artefact is CROPPED, never shrunk (spec 7.2 mechanism 2). A max-width
   of 100% would let a narrow parent column quietly scale the paper down, and
   a scaled-down document stops reading as "a real document" and starts
   reading as "a diagram of one" - which is the exact defect this component
   exists to fix. Every width it can take is declared explicitly in 11.8/11.9,
   so it needs no fluid fallback.
   The consequence: the hero instance WILL overflow its grid column on
   purpose. It must therefore sit inside .artefact-crop, or the body will
   scroll sideways and break spec 9. That is not optional.

   .artefact-crop supplies the clip and the positioning context so the crop is
   not reinvented at three call sites. The consumer sets the height it wants
   to reveal (hero: enough for >=420px of paper above y=900, spec 7.3). */
.artefact-crop { position: relative; overflow: hidden; }

/* --------------------------------------------------------------------------
   11.2 The paper edge - spec 7.2 mechanism 2, spec 4.3 final row
   The one place on the site where a border and a shadow coexist, because
   spec 4.3 specifies both for this object explicitly.
   Page proportion is A4 (1 : 1.414) derived from --artefact-w, so the same
   formula gives 640x905, 576x815, 320x452 and 180x255 with no extra numbers.
   Content taller than that grows the page; it is never clipped.
   -------------------------------------------------------------------------- */
.artefact__page {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 18em;
  min-height: calc(var(--artefact-w) * 1.414);
  padding: 24em;
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: var(--r-xs);      /* paper corners are sharp */
  box-shadow: var(--shadow-3);
  overflow: hidden;
}
.artefact__page--front { z-index: var(--z-raised); }

/* Page 2, behind and up-right. It is a PDF, so more than one page shows.
   Marked aria-hidden in the markup: it carries no readable information. */
.artefact__page--behind {
  position: absolute;
  inset: 0;
  z-index: var(--z-base);
  transform: translate(28em, -20em) rotate(1.5deg);
  box-shadow: var(--shadow-2);
}

/* --------------------------------------------------------------------------
   11.3 Redaction block - spec 7.2 mechanism 4
   Width is sized to the character count it replaces: set --d on the element.
   0.62em is the Inter tabular digit advance, so --d:6 is exactly six digits
   wide at whatever size the surrounding line is running at.
   Accessibility: the CONTAINER carries one aria-label. Never label a block.
   -------------------------------------------------------------------------- */
.redaction {
  display: inline-block;
  width: calc(var(--d, 4) * 0.62em);
  height: 0.95em;
  margin-inline: 0.12em;
  vertical-align: -0.1em;
  background: var(--rule-strong);
  border-radius: calc(var(--r-xs) * var(--artefact-scale, 1));
}

/* --------------------------------------------------------------------------
   11.4 Document furniture
   -------------------------------------------------------------------------- */
.artefact__head {
  display: flex;
  flex-direction: column;
  gap: 5em;
  padding-bottom: 14em;
  border-bottom: 1px solid var(--rule);
}

/* The classification stamp, above the title, at every width.
   Two jobs, both spec-mandated and both previously rendered off-canvas:
     - it labels the redaction blocks. Left unlabelled, a pale rounded pill on
       the two most-read lines of the document is the universal skeleton-loader
       convention, and a visitor who reads "this page hasn't finished loading"
       has been shown a broken artefact. The words "figures redacted" now land
       ~30px above the first block.
     - it carries spec 7.2 mechanism 5, the dated timestamp, which is our only
       permitted substitute for a live counter.
   It sits ABOVE the title rather than beside it because at 320px there is no
   room for a right-hand column, and one layout at every width beats two. */
.artefact__stamp-row {
  display: flex;
  flex-wrap: wrap;          /* at 320px the two stamps cannot share a line */
  align-items: baseline;
  justify-content: space-between;
  gap: 4em 16em;
  margin: 0 0 5em;
  padding-bottom: 9em;
  border-bottom: 1px solid var(--rule);
}
.artefact__title {
  margin: 0;
  font-size: 13em;                 /* the Artefact ceiling */
  font-weight: 600;
  line-height: 1.30;
  letter-spacing: 0;
  color: var(--ink);
}
.artefact__property {
  margin: 0;
  font-size: 9.5em;
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: 0;
  color: var(--ink-62);
}

.artefact__block { display: flex; flex-direction: column; gap: 10em; }

/* Keeps a date or a document name whole. The header line is dot-separated,
   so without this it breaks as "2 August / 2026" or "Local / Offers Report"
   at 300px - a mid-phrase break, which spec 3.3 treats as a pass/fail item. */
.artefact__nb { white-space: nowrap; }

.artefact__section-head {
  margin: 0;
  font-size: 10em;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: 0;
  color: var(--ink);
}
.artefact__caption {                /* chart axis label */
  margin: 0;
  font-size: 9em;
  font-weight: 400;
  line-height: 1.40;
  letter-spacing: 0;
  color: var(--ink-62);
}
.artefact__source {                 /* the publisher + period, printed */
  margin: 0;
  font-size: 8em;
  font-weight: 400;
  line-height: 1.50;
  letter-spacing: 0;
  color: var(--ink-62);
}
.artefact__watermark {
  font-size: 8.5em;
  font-weight: 500;
  line-height: 1.40;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;      /* a stamp that breaks mid-phrase is not a stamp */
  color: var(--ink-62);
}
.artefact__stamp {                  /* "Sources checked ..." - see js/main.js */
  font-size: 8em;
  font-weight: 400;
  line-height: 1.50;
  letter-spacing: 0;
  white-space: nowrap;
  color: var(--ink-62);
  font-variant-numeric: tabular-nums;
}
.artefact__foot {
  margin-top: auto;                 /* pins the stamp to the foot of the page */
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16em;
  padding-top: 14em;
  border-top: 1px solid var(--rule);
}

/* --------------------------------------------------------------------------
   11.5 KPI row - the only Brawler inside the Artefact
   -------------------------------------------------------------------------- */
.artefact__kpis {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10em;
}
.artefact__kpi {
  display: flex;
  flex-direction: column;
  gap: 7em;
  padding: 12em;
  background: var(--surface);
  border-radius: calc(var(--r-sm) * var(--artefact-scale));
}
.artefact__kpi-label {
  font-size: 8.5em;
  font-weight: 500;
  line-height: 1.30;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-62);
}
.artefact__kpi-val {
  display: flex;
  align-items: baseline;
  gap: calc(4 * var(--u));          /* calc, not em: this element sets font-size */
  font-family: var(--font-serif);   /* Brawler 400. Weight 700 is banned. */
  font-weight: 400;
  font-size: 22em;
  line-height: 1;
  letter-spacing: -0.011em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.artefact__kpi-unit {
  font-family: var(--font-sans);
  font-size: calc(10 * var(--u));   /* calc: it lives inside a 22em parent */
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink-62);
}

/* --------------------------------------------------------------------------
   11.6 Spread chart - CSS bars, no SVG, no JS
   Every bar carries a text value beside it, so colour is never the sole
   signal (spec 10). --accent-bright is a graphics fill only, never text.
   -------------------------------------------------------------------------- */
.artefact__chart { display: flex; flex-direction: column; gap: 8em; }

.artefact__bar,
.artefact__axis {
  display: grid;
  grid-template-columns: 170em minmax(0, 1fr) 44em;
  gap: 10em;
}
.artefact__bar { align-items: center; }

.artefact__bar-label {
  font-size: 9em;
  font-weight: 400;
  line-height: 1.35;
  letter-spacing: 0;
  color: var(--ink-72);
}
.artefact__bar-val {
  font-size: 9em;
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
/* THE BARS ARE ANCHORED AT 100%, NOT AT THE LEFT EDGE.

   A blind adversarial critic caught this and was right: the axis runs 95–105,
   so a left-anchored filled bar encodes a 10-point window as its full length.
   East Fife at 98.5% drew a bar roughly a third the length of West Fife at
   102.9% — a 4.4-point difference rendered as a 3x difference. Filled bars
   from a non-zero baseline are the textbook misleading chart, and on a page
   whose entire argument is "we handle the data honestly" it is self-refuting.
   Spec 1 puts "Researched" first and says an unsourced figure forfeits it; a
   figure drawn to distort forfeits it harder.

   Truncating the axis is still correct — nobody needs to see 0% of a Home
   Report valuation. What was wrong was the ENCODING. So the bar now grows from
   the 100% gridline, which is the only meaningful zero this data has: it
   measures the gap against Home Report valuation, which is exactly what the
   product is about. Areas above valuation grow right, areas below grow LEFT,
   and East Fife finally reads as what it is — a market selling under
   valuation, an insight the old chart actively hid.

   --w stays the position on the 95–105 scale, unchanged in the markup, so the
   numbers stay tied to the published ESPC figures. The bar's box is derived:
   it spans between --w and the 50% centre, whichever way round they fall.
   min()/max() pick the correct edge without a second variable per row. */
.artefact__bar .bar {
  position: relative;
  height: 8em;
  overflow: hidden;
  background: var(--surface-2);
  border-radius: calc(var(--r-xs) * var(--artefact-scale));
}
/* The 100%-of-valuation reference line, at the centre of the track and behind
   the bar, so every row is read against the same datum. */
.artefact__bar .bar::before {
  content: "";
  position: absolute;
  inset: 0 auto;
  left: 50%;
  width: 1px;
  /* --rule-strong on --surface-2 is about 1.2:1 — invisible, which would leave
     the bars floating with no stated datum. Spec 10 wants chart elements at
     >=3:1 against their adjacent surface. */
  background: rgba(19, 18, 17, .32);
}
.artefact__bar .bar i {
  position: absolute;
  inset: 0 auto 0 auto;
  left: min(var(--w), 50%);
  width: max(calc(var(--w) - 50%), calc(50% - var(--w)));
  min-width: 1px;                 /* a value sitting exactly on 100% still marks */
  background: var(--accent-bright);
  border-radius: inherit;
}
/* The axis row: three labels, space-between, so the centre label sits at the
   100%-of-Home-Report gridline. */
.artefact__axis-scale {
  display: flex;
  justify-content: space-between;
  gap: 8em;
}
.artefact__axis-scale span {
  font-size: 9em;
  font-weight: 400;
  line-height: 1.40;
  letter-spacing: 0;
  color: var(--ink-62);
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   11.7 Comparables table + signals list (page 2)
   Spec 4.6 / checklist 23: every table sits in an overflow-x wrapper and
   every numeric cell is tabular.
   -------------------------------------------------------------------------- */
.artefact__scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.artefact__table { width: 100%; border-collapse: collapse; }
.artefact__table th {
  font-size: 8.5em;
  font-weight: 500;
  line-height: 1.40;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  text-align: left;
  white-space: nowrap;
  color: var(--ink-62);
  padding: calc(6 * var(--u)) calc(10 * var(--u)) calc(6 * var(--u)) 0;
  border-bottom: 1px solid var(--rule-strong);
}
.artefact__table td {
  font-size: 10.5em;
  font-weight: 400;
  line-height: 1.50;
  letter-spacing: 0;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  padding: calc(8 * var(--u)) calc(10 * var(--u)) calc(8 * var(--u)) 0;
  border-bottom: 1px solid var(--rule);
}
.artefact__table th:last-child,
.artefact__table td:last-child { padding-right: 0; }
.artefact__table .is-num { text-align: right; font-variant-numeric: tabular-nums; }
/* Static data table: no row hover (spec 4.6). Nothing declared, deliberately. */

.artefact__signals { list-style: none; margin: 0; padding: 0; }
.artefact__signals li {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12em;
  margin: 0;
  padding: 7em 0;
  border-bottom: 1px solid var(--rule);
}
.artefact__signals li:first-child { border-top: 1px solid var(--rule); }
.artefact__signal-label {
  font-size: 10em;
  font-weight: 400;
  line-height: 1.40;
  letter-spacing: 0;
  color: var(--ink-72);
}
.artefact__signal-val {
  font-size: 10.5em;
  font-weight: 500;
  line-height: 1.40;
  letter-spacing: 0;
  white-space: nowrap;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* Contents rows on page 1. font-size lives on the <li>, so its padding is
   written in calc rather than em. */
.artefact__list { list-style: none; margin: 0; padding: 0; }
.artefact__list li {
  margin: 0;
  padding: calc(7 * var(--u)) 0;
  border-bottom: 1px solid var(--rule);
  font-size: 10.5em;
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: 0;
  color: var(--ink-72);
}
.artefact__list li:first-child { border-top: 1px solid var(--rule); }

/* --------------------------------------------------------------------------
   11.8 The three variants - spec 7.2 mechanism 7
   Same object, three times, escalating specificity. Never a new object.
   Selectors are [data-artefact="..."] on .artefact, which is 0,2,0 - higher
   than section 8's bare .artefact rules, so those can never win.
   -------------------------------------------------------------------------- */

/* hero      - spec 7.3: page 1, two pages offset, cropped right AND bottom.
   794px is A4 at 96dpi, so the paper is LITERALLY life-size and
   --artefact-scale stays at 1: not one type or spacing value inside the
   document changes, and the A4 min-height formula follows to 1122px on its own.
   Why it is not 640: at 640 the right edge = left + 640 = 50vw + 616, which
   only passes the viewport below 1232px. At 1440 the paper stopped 104px short
   and above 1440 the max() second term pinned it 104px short forever - so the
   largest dead area on the page was the white band beside the product, and the
   paper's outer margin (104px) was LARGER than the channel between the copy
   column and the paper (84px). A complete four-edges-visible rectangle reads
   as "here is a picture of the thing"; a cut edge reads as "the thing is
   bigger than this window". That is spec 7.2 mechanism 2 and checklist 27, and
   it is the mechanism the whole product-demonstration strategy rests on.
   At 794 the right edge lands at 696+794 = 1490: 50px past a 1440 viewport,
   130px past at 1280, and a constant 50px past at every width above 1440.
   The left edge stays at x=696 - that is a locked contract value (spec 7.3). */
.artefact[data-artefact="hero"]      { --artefact-w: 794px; --artefact-scale: 1; }

/* THE BLEED MARGIN — what the crop is allowed to cut through.
   Two independent blind critics, given different briefs, reported the same
   defect unprompted: the viewport was severing the bar-chart values to an
   amputated "10" and the header stamp to "Sources checked 4 Augus". Both read
   it as a rendering bug rather than as a crop, and one put it as the reason
   this page lost its round: "a deliberate crop cuts through whitespace or
   imagery; cutting through live numerals reads as an accident."

   They are right, and the distinction is the whole mechanism. Spec 7.2
   mechanism 2 wants the paper to continue past the window — it does NOT want a
   half-eaten digit. Stripe's cropped mock is cut through panel edges and table
   rows, never through a numeral.

   So the last 50px of the paper — exactly the strip the viewport removes — is
   reserved as margin. The right padding grows from 24em to 72em, which puts
   the rightmost glyph at x=1418, 22px clear of the 1440 fold, while the paper
   edge, the hairline rules and the bar tracks still run past it and get cut.
   The crop signal survives intact; the amputated numbers do not.

   This costs nothing visually: the widened margin lives entirely inside the
   clipped strip, so the asymmetry against the 24px left margin is literally
   never on screen. Hero variant only — the evidence and thumb instances are
   not cropped and keep the symmetric document margin. */
.artefact[data-artefact="hero"] .artefact__page { padding-right: 72em; }

/* evidence  - spec 6 section 5: page 2 at full readable size in a 5-col
   sticky panel. Type does NOT scale down: the em ladder is width-independent,
   so a 10.5px table cell stays 10.5px in a 432px panel. Paper is content-tall
   here, not A4-tall, because it is not cropped. */
.artefact[data-artefact="evidence"]  { --artefact-w: 100%; --artefact-scale: 1; width: 100%; }
.artefact[data-artefact="evidence"] .artefact__page { min-height: 0; }

/* thumb     - spec 6 section 9: page 1 at 180px beside the price.
   180 / 640 = 0.28125. One number, and the entire document miniaturises. */
.artefact[data-artefact="thumb"]     { --artefact-w: 180px; --artefact-scale: 0.28125; }

/* --------------------------------------------------------------------------
   11.9 Responsive - spec 9
   These restate (and override) the media queries in section 8, which sit
   earlier in the cascade and would otherwise lose to 11.1/11.2.
   The Artefact never disappears: it is the product.
   -------------------------------------------------------------------------- */

/* 1024-1279 desktop-narrow, and 768-1023 tablet: scale 0.9.
   700px for the same reason as 794 above: at 576 the right edge landed inside
   the viewport at the top of the band. At 700 it lands 164px past a 1024
   viewport and 36px past a 1279 one - clipped across the whole range. */
@media (max-width: 1279px) and (min-width: 768px) {
  .artefact[data-artefact="hero"] { --artefact-w: 700px; --artefact-scale: 0.9; }
}

/* <768 mobile: single page, 320px, shadow drops to --shadow-2. Scale stays
   at 1 so the ladder stays legible; the internal grids reflow instead. */
@media (max-width: 767px) {
  .artefact[data-artefact="hero"] { --artefact-w: 320px; --artefact-scale: 1; }
  .artefact[data-artefact="hero"] .artefact__page--behind { display: none; }
  .artefact .artefact__page { box-shadow: var(--shadow-2); }

  /* Stays 3-up below 768. The 2-up-plus-full-width-third variant collapsed the
     KPI block to 150px tall and pushed BOTH the third figure and the "ESPC
     published House Price Report, Feb-Apr 2026" source line below the fold, so
     the mobile visitor's first impression of the product was two redaction
     blocks and a truncated number with no publisher attached. 3-up keeps a
     real number AND its publisher inside the visible 260px. */
  .artefact__kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6em; }
  .artefact__kpi  { padding: 9em; }

  /* Document furniture tightens on a 300px page. Every em reclaimed here is an
     em of the KPI row that clears the 844px fold, and the KPI row is the only
     place on the mobile first screen where a real published number appears. */
  .artefact__page       { padding: 16em; gap: 14em; }
  .artefact__stamp-row  { padding-bottom: 6em; margin-bottom: 4em; }
  .artefact__head       { padding-bottom: 10em; }

  .artefact__bar { grid-template-columns: 118em minmax(0, 1fr) 40em; }

  /* The axis goes full width below the bars. At a 100px bar track the centre
     label ("100% = Home Report valuation") wrapped onto four lines; the
     reference point is the most useful thing on the chart, so it gets the
     whole width rather than being dropped. */
  .artefact__axis { grid-template-columns: minmax(0, 1fr); }
  .artefact__axis > span:empty { display: none; }
}

/* <480 small: 300px, page 2 removed entirely, price-band thumbnail hidden
   (spec 9 - the recap list survives without it). */
@media (max-width: 479px) {
  .artefact[data-artefact="hero"] { --artefact-w: 300px; }
  .artefact[data-artefact="thumb"] { display: none; }
}


/* ==========================================================================
   12. HOMEPAGE HERO + SOURCE ROW - spec 6 rows 1 and 2, spec 7.3

   THE ABOVE-THE-FOLD CONTRACT (spec 7.3, at 1440x900 with a 64px header)
   Every number below is derived from it. None of them is a preference.

     header                                     0 -> 64
     .hero padding-top                    132   (= 196 - 64, so the paper's
                                                 top edge lands on y = 196)
     artefact left edge                   696   (see .hero__stage)
     artefact visible height              558   (hero ends at y = 754; the
                                                 contract needs >= 420)
     hero cluster ends at                ~626   complete above the fold, with
                                                 the .btn--money fully visible
     source row                        754 -> 854, still above y = 900

   THE CLUSTER RULE (spec 5.2)
   H1 -> lede -> CTA row -> reassurance is ONE object. Internal gaps are
   20 / 28 / 16 and nothing inside it may exceed 32. It is surrounded by
   132px above and 128px below - both >= 4x the largest internal gap. Five
   evenly spaced elements is the failure mode; one tight object in air is the
   target.

   THE CROP (spec 7.2 mechanism 2)
   .hero owns the clip. The paper is NEVER shrunk to fit - it is cropped by
   the hero's bottom boundary and, below 1404px, by the viewport's right edge.
   .artefact carries max-width:none on purpose, so without this overflow the
   body would scroll sideways and break spec 9.
   ========================================================================== */

/* In-page anchors (e.g. /report/#buy) must clear the sticky header. */
[id] { scroll-margin-top: calc(var(--header-h) + var(--s-6)); }

/* ==========================================================================
   BACKGROUND IMAGERY — Michael's gradients, 8 Aug 2026 (spec 13.11)
   Three images, used with intent and repeated, rather than fifteen used once.
   Each is emitted by _design/images.mjs at four widths in AVIF / WebP / JPEG;
   image-set() negotiates format, the media queries negotiate size. A phone
   fetches ~4KB of AVIF for all three, which matters because these are SEO
   landing pages and hero weight is a ranking input.

   The rule that governs all of them: the image lives on the side AWAY from the
   copy, and never darkens the column the words sit in. Contrast is verified by
   sampling rendered pixels under the text (_design/contrast.mjs), because the
   automated audit reads background-COLOR and is blind to an image.
   ========================================================================== */
:root {
  --bg-hero:
    image-set(url("/img/bg/hero-1280.avif") type("image/avif"),
              url("/img/bg/hero-1280.webp") type("image/webp"),
              url("/img/bg/hero-1280.jpg")  type("image/jpeg"));
}

.hero {
  position: relative;
  overflow: hidden;              /* the crop. Not optional - see the note above */
  padding: 132px 0 128px;
  min-height: 640px;             /* spec 6 row 1: self-sized, about 640px */
  background-color: var(--bg);
  /* TWO layers, and the order matters. On top, a left-to-right white scrim;
     underneath, the gradient itself.

     Full-bleed alone put the most saturated part of 942 directly under the
     headline, the lede and the reassurance lines — the copy column is the one
     place the image must not be strong. The scrim keeps the drama on the
     product side, where the Artefact sits, and hands the words back a near-white
     ground. It is a gradient rather than a flat panel so there is no visible
     edge where it ends.

     Verified by sampling rendered pixels under every text block
     (_design/contrast.mjs), not by eye — the automated audit reads
     background-COLOR and cannot see an image at all. */
  background-image:
    linear-gradient(100deg,
      rgba(255,255,255,.94) 0%,
      rgba(255,255,255,.86) 26%,
      rgba(255,255,255,.42) 46%,
      rgba(255,255,255,.06) 62%,
      rgba(255,255,255,0)   74%),
    var(--bg-hero);
  background-size: cover, cover;
  /* `top`, not `center`: at hero proportions `cover` crops a 16:9 image
     vertically, and 942's palest region is its upper band. Centring showed the
     saturated middle. */
  background-position: 50% 0%, 50% 0%;
  background-repeat: no-repeat, no-repeat;
}

@media (max-width: 640px) {
  :root { --bg-hero:
    image-set(url("/img/bg/hero-640.avif") type("image/avif"),
              url("/img/bg/hero-640.webp") type("image/webp"),
              url("/img/bg/hero-640.jpg")  type("image/jpeg")); }
}
@media (min-width: 1281px) {
  :root { --bg-hero:
    image-set(url("/img/bg/hero-1920.avif") type("image/avif"),
              url("/img/bg/hero-1920.webp") type("image/webp"),
              url("/img/bg/hero-1920.jpg")  type("image/jpeg")); }
}
@media (min-width: 1921px) {
  :root { --bg-hero:
    image-set(url("/img/bg/hero-2560.avif") type("image/avif"),
              url("/img/bg/hero-2560.webp") type("image/webp"),
              url("/img/bg/hero-2560.jpg")  type("image/jpeg")); }
}

.hero__grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--gutter);
}
.hero__copy { grid-column: 1 / span 5; }

/* --- the cluster. Gaps 20 / 28 / 16 (spec 5.2), reassurance ALWAYS last. --- */

/* A 2px optical stagger ran down the left edge: "O" and "B" at 64px Brawler
   carry a 2px left sidebearing, "h" carries none, so the accent line - the one
   the eye goes to - hung 2px left of the two above it and both of those sat
   2px right of the 149px column edge shared by the lede, the CTA row and the
   note. 0.031em is 2px at 64px and scales correctly to 1.24px at the 40px
   mobile size, so all three ink edges land on the column. */
.hero__title    { margin: 0 0 0 -0.031em; }
.hero__title em { margin-left: 0.031em; }

/* 12, not spec 5.2's declared 20. The declared numbers rendered as OPTICAL
   gaps of 37 / 33 / 20.5 - the first two within 4px of each other, so the H1
   read as detached from its own lede and the intended tightening was
   invisible. Optically it is the gap that counts. At 12 the run becomes
   29 / 33 / 21: tight, then the widest beat immediately before the action,
   then tightest under the button. The declared gaps are still all <=32 so the
   cluster rule holds (4 x 28 = 112, against 139.5 above and 128 below), and
   the 8px the hero loses is what puts the source row's closing hairline back
   above the 900px fold. */
.hero__lede  { margin: var(--s-3) 0 0; }
/* No column-gap override any more. The second route is now a real .btn--lg
   .btn--outline rather than a chrome-less .btn--quiet, so the shared .btn-row
   12px measures edge-to-edge and the pair reads as ONE object - which is what
   Stripe (filled + outlined, 8px apart) and Canva both do. */
.hero__ctas  { margin-top: 28px; }

/* Spec 3.2 assigns --t-ui (15px/500) to reassurance microcopy, and this line
   does more work here than on any reference page: our ask is £15 to a stranger
   for a document that does not exist yet. At 13.5px --ink-62 it was 5.2:1 and
   read as legal small print; at 15px --ink-72 it is 7.5:1 and reads as
   permission. It is a stacked block, not a dot-separated run: at 456px (and at
   350px) the three claims cannot share a line, and every separator scheme -
   ::before, ::after or a standalone glyph - leaves a dot dangling at a line
   edge, which is the most conspicuous small error a page can make. */
.hero__note  {
  display: flex;
  flex-direction: column;
  gap: 0;                  /* 1.45 leading already sets it as one paragraph */
  margin: var(--s-4) 0 0;
  font-size: 15px;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: 0;
  color: var(--ink-72);
}
.hero__note span { white-space: nowrap; }

/* --- the stage --------------------------------------------------------------
   Absolutely positioned against .hero, not laid into a grid column, because a
   grid track would quietly constrain the paper and a scaled-down document
   stops reading as a real document.

   left: max(50% - 24px, 100% - 744px)
     at 1440 both terms are 696px, which is the locked contract value;
     below 1440 the first term wins, so the paper tracks the split and its
     right edge is clipped by the viewport;
     above 1440 the second term wins, so the paper holds a constant 744px
     stage and never drifts into the middle of a wide screen.
   -------------------------------------------------------------------------- */
.hero__stage {
  position: absolute;
  top: 132px;
  left: max(calc(50% - var(--gutter)), calc(100% - 744px));
}

/* 768-1023 tablet (spec 9): the hero stacks. Copy full width, paper below the
   reassurance line, right edge running 104px past the container so it is still
   cropped rather than politely contained. */
@media (max-width: 1023px) {
  .hero { padding: 64px 0 96px; min-height: 0; }
  .hero__copy  { grid-column: 1 / -1; }
  /* Stacked, .hero__grid's 24px row gap silently added itself to the stage's
     own margin, so the declared "40px" between the reassurance and the paper
     was really 64. Zeroing it here keeps the number in one place, where it can
     be reasoned about against the above-the-fold contract. */
  .hero__grid  { row-gap: 0; }
  /* Stacked, .lede's own --measure-lede (648px) applies while the font-size
     has already clamped down to 17px - that is ~96 characters per line,
     far past the 75-CPL ceiling, and it made the lede block 2.27x the width
     of the H1 above it, so the display type was optically subordinate to its
     own supporting copy. 480px at 17px is <=75 CPL and brings the ratio to
     1.68x. At >=1024 the 5-column track already holds it at 456px, so nothing
     above this breakpoint changes. */
  .hero__lede  { max-width: 480px; }
  .hero__stage {
    position: static;
    grid-column: 1 / -1;
    display: flex;
    justify-content: flex-end;
    margin: var(--s-11) -104px 0 0;
    /* Stacked, the hero's own bottom boundary no longer crops the paper, so
       the stage does it. Without this a 990px page turns the tablet hero into
       a screen and a half of one object. 600 is ~0.86 x the 700px paper, close
       to the reveal-to-width ratio the 1440 contract produces (595 / 794). */
    height: 600px;
    overflow: hidden;
  }
}

@media (max-width: 767px) {
  /* THE MOBILE CLUSTER. What shipped was four near-identical optical gaps -
     28.5 / 27.5 / 28.5 / 31 - which is spec 5.2's named failure mode verbatim
     ("five evenly spaced elements is the failure mode"). Nothing read as one
     object; it read as a list.
     Two moves break the tie. (1) The second CTA is now a real 56px button, so
     the pair sits at a flat 12px and reads as one control, not as a fifth
     peer. (2) The lede is pulled up hard against the H1, which is where it
     belongs - they are one statement. Optical gaps become roughly
       H1 -> lede 20.5 | lede -> CTA row 27.5 | [CTA pair 12] | CTA -> note 18.5
     i.e. tight, then the widest beat immediately before the action, then
     tightest under it. A crescendo, not a ladder. */
  .hero { padding: var(--s-9) 0 72px; }
  .hero__lede { margin-top: var(--s-1); }  /* 4 declared = ~17.75 optical */
  .hero__ctas { margin-top: var(--s-6); }  /* 24 declared = ~26.9 optical */
  .hero__note { margin-top: 14px; }        /* spec 5.2: CTA -> reassurance, mobile */

  /* KNOWN, DELIBERATE SHORTFALL - measured, not guessed.
     Spec 5.2's cluster rule wants >=4x the largest internal gap (4 x 24 = 96px)
     of air on both sides. Spec 7.3 wants >=260px of paper above the 844px fold.
     At 390x844 those two are arithmetically incompatible: with a 3-line H1
     (126px), a 3-line lede (79px), two 56px buttons (124px) and a 3-line
     reassurance (65px), honouring the 4x rule puts the paper's top edge at
     y~678 and leaves 166px of the product visible.
     The 260px contract wins. It is a hard number about the thing being sold;
     the 4x is a ratio; and the failure the ratio exists to prevent - a stack of
     near-identical gaps reading as a list - has been fixed above by other
     means. 48 top / 32 stage puts the paper at y=580 with 264px visible.
     -48 right keeps the right edge 28px past the viewport: still a crop.
     -20 would land it exactly ON the viewport edge, i.e. no crop at all. */
  .hero__stage { margin: var(--s-7) -48px 0 0; height: 400px; }
}

/* --------------------------------------------------------------------------
   12.1 Source row - spec 6 row 2
   Static. Ruled top and bottom. Five dot-separated labels at 13.5px --ink-62.
   No animation, no logos, no marquee: the old ticker clipped mid-word at both
   viewport edges and read as a bug. On a small UK product the data source IS
   the logo wall - it is free, and it is true.
   -------------------------------------------------------------------------- */
.source-row {
  background: var(--bg);
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.source-row__list {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  /* 12px, not 20. Each separator cost 20 (flex gap) + the dot + 20 (::before
     margin) = 45px, four times over, which pushed the fifth label onto a
     second line as a lone orphan - and because the dot is an li + li::before,
     that line OPENED with a "·". A separator can never start a line. The wrap
     also inflated the band from 100px to 126.5px and put its closing hairline
     1px BELOW the 900px fold, so the first screen ended mid-band with no rule
     and the page read as cut off rather than as continuing.
     At 12/12 the separator costs 26.5px and all five labels set on one line
     with ~35px of headroom in the 1128px content width. */
  gap: 6px 12px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 13.5px;
  line-height: 1.50;
  letter-spacing: 0;
  color: var(--ink-62);
}
.source-row__list li { margin: 0; }
.source-row__list li + li::before {
  content: "\00B7";
  margin-right: 12px;
  /* --rule-strong is 1.44:1 on white. It is a hairline/border token and has no
     business carrying a glyph: at that contrast the five cited sources read as
     one 1000px run of words rather than as five sources. --ink-62 is 5.2:1 and
     matches the identical device 40px above it in the hero. */
  color: var(--ink-62);
}

/* Below 1024 the five labels need ~1091px against 911px (tablet) and 350px
   (mobile) of content, so no gap value can hold them on one line - and a
   wrapped `li + li::before` puts a "·" at the start of every continuation
   line, which is the same orphan defect the desktop fix removes. The dot is
   therefore dropped below 1024 and the labels become a real grid: 2-up at
   tablet, 1-up on mobile. A separator that cannot exist cannot dangle. */
@media (max-width: 1023px) {
  .source-row__list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 6px var(--s-7);
  }
  .source-row__list li + li::before { content: none; }
}
@media (max-width: 767px) {
  .source-row__list { grid-template-columns: minmax(0, 1fr); }
}


/* @@ SECTION-CSS START — assembled by _design/assemble.mjs, do not edit inside @@ */

/* Per-section CSS from _design/sections/*.css, concatenated in spine order.
   It loads after the design system so a section can specialise a system
   class without either file needing !important. Re-run assemble.mjs after
   editing any fragment; edits made between the markers are overwritten. */

/* ======================= s3-s4.css ======================= */
/* ==========================================================================
   SECTIONS 3 + 4 - "The problem" and "Free area check"
   OffersOver - implements DESIGN-SPEC.md section 6 rows 3 and 4 (LOCKED v1.1).
   Load AFTER css/style.css, or paste this block at the end of it.

   SCOPE DISCIPLINE
   Everything below is either (a) layout that only these two sections need, or
   (b) a targeted neutralisation of the LEGACY .lookup / .headline / .detail
   rules in css/style.css section 10, which are marked for deletion but are
   still in the cascade and would otherwise centre the panel in 720px and set
   the result headline in a serif.

   This file defines NO token, NO button, NO card, NO chip, NO table and NO
   type step. Section padding, the panel, the ruled list, the select, the
   gap classes and the type ladder all come from the design system unchanged.

   Every neutralisation below is written as a two-class selector (0,2,0) so it
   wins on specificity rather than on file order - it is therefore safe whether
   this file is linked separately or pasted into style.css.

   ASCII-only on purpose, matching css/style.css: Windows PowerShell 5.1
   mangles UTF-8 punctuation. Glyphs go in the markup, never here.
   ========================================================================== */


/* ==========================================================================
   1. SECTION 3 - THE PROBLEM
   Asymmetric split. Copy on columns 1-5, the ruled figure stack on 7-12.

   The columns are written as explicit grid-column values rather than with the
   .col-6 / .start-7 utilities, because those two do not compose: .col-6
   resolves to `grid-column-start: span 6; grid-column-end: auto`, and .start-7
   then overwrites the start, leaving an auto end - a one-column-wide track.
   ========================================================================== */

.problem__grid { row-gap: var(--s-9); }          /* lede -> content block, 48 */

.problem__intro   { grid-column: 1 / span 5; }
.problem__figures { grid-column: 7 / span 6; }

/* Below 1024 the split collapses. A 48px H2 and a 56px Brawler figure cannot
   share a 12-column row inside a 960px container without both becoming
   unreadable, so they stack and keep their full measures. */
@media (max-width: 1023px) {
  .problem__intro,
  .problem__figures { grid-column: 1 / -1; }
}
@media (max-width: 767px) {
  .problem__grid { row-gap: var(--s-7); }        /* lede -> content block, 32 */
}

/* Both tertiary links in these two sections carry a full sentence rather than
   a two-word label, and .btn inherits `white-space: nowrap`. At 390px "See
   what the published data says about your area" measures 377px against 350px
   of content and pushed the document 7px sideways, which spec section 9 makes
   a hard fail at 320, 390, 768, 1024, 1280 and 1440. No label short enough to
   survive 320px exists without changing what the copy claims, so the label
   wraps instead. Left-aligned, because a centred two-line link inside a
   left-aligned column reads as a mistake. Everything else about .btn--quiet -
   its 44px hit box, its trailing arrow, its underline on hover - is untouched.
   .btn--quiet is the only button this applies to; no chromed button in these
   sections is allowed to wrap.

   display goes to inline-block for the same reason. .btn is an inline-flex
   row, so once the label wraps, the text becomes one flex item that fills the
   line and strands the trailing arrow against the right edge, vertically
   centred between two lines and disconnected from the sentence it belongs to.
   As inline-block the ::after arrow is ordinary trailing text and follows the
   last word, which is what a text link should do at every width - so this is
   not behind a media query. Padding and min-height still build the 44px hit
   box on an inline-block, and line-height moves from .btn's 1 (correct for a
   single-line control) to the 1.45 of --t-ui (correct for wrapped copy).
   Nothing else about .btn--quiet changes. */
.problem .btn--quiet,
.area-check .btn--quiet {
  display: inline-block;
  max-width: 100%;
  white-space: normal;
  text-align: left;
  line-height: 1.45;
}
.problem .btn--quiet::after,
.area-check .btn--quiet::after { margin-left: 6px; }

/* Rows come from .ruled-list: hairline top and bottom, 18px / 16px padding.
   Only the internal spacing is section work. Both paragraphs inside a row
   carry a .gap-* class for their top margin (10px figure -> claim, 10px
   claim -> source), so the base <p> bottom margin has to go or the source
   line drifts past the 24px in which checklist item 34 requires it. */
.problem__figure p { margin-bottom: 0; }

/* THE ACCENT BECOMES DATA-INK.

   A blind reviewer's note, and it is the right diagnosis: "the accent
   decorates; it should MEAN 'the number that matters'." The three published
   figures here are the most load-bearing content on the page — they are what
   turns the section's argument from an adjective into an instrument reading —
   and they were set in plain ink, while the accent was spent on buttons and a
   quote mark. The same reviewer separately measured a two-screen stretch with
   no accent at all, which starts here.

   So: the figure takes --accent, its claim and its source line stay ink and
   ink-62. Contrast is 6.3:1 on #fff and 5.6:1 on --surface, both comfortably
   past the 3:1 that type this size needs (spec §10). This costs no new colour
   and adds no element; it just spends the one accent on the one thing the
   section exists to show. */
.problem__figure .num { color: var(--accent); }


/* ==========================================================================
   2. SECTION 4 - FREE AREA CHECK
   ========================================================================== */

/* --------------------------------------------------------------------------
   2.1 The panel
   .panel already supplies the outer-panel spec (section 4.3): --surface fill,
   1px --rule, --r-lg, 32px / 20px padding, no shadow. The four declarations
   here exist only to undo css/style.css section 10.7, which still styles
   .lookup as a centred 720px card. The padding is restated because 10.7's
   clamp() would otherwise win over .panel's on file order.
   -------------------------------------------------------------------------- */
.area-check.lookup {
  max-width: none;
  margin: 0;
  padding: var(--s-7);
  text-align: left;
}
@media (max-width: 767px) {
  .area-check.lookup { padding: var(--s-5); }
}

/* Two columns inside the one panel. They stack below 1024 for the same reason
   section 3 does: at 1024 each half is roughly 430px, which is the floor at
   which a 28px result headline and a four-bar chart both still read. */
.area-check__cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--gutter);
}
@media (max-width: 1023px) {
  .area-check__cols { grid-template-columns: minmax(0, 1fr); row-gap: var(--s-8); }
}
@media (max-width: 767px) {
  .area-check__cols { row-gap: var(--s-7); }
}

/* --------------------------------------------------------------------------
   2.2 The control
   The select keeps every value the design system gives it (48px, 8px radius,
   1px --rule-strong, the 12x8 chevron, the full-height hit area). Legacy 10.7
   sets `.lookup select { width: auto; flex: 1 1 260px }` for a flex row this
   panel does not use, which would shrink it to its content; the two-class
   selector puts it back to the system's own width.
   -------------------------------------------------------------------------- */
.area-check.lookup select { width: 100%; }

/* .field already carries the 16px below itself (spec 5.2, CTA -> microcopy). */
.area-check .area-check__update { margin: 0; }
.area-check .area-check__noscript { margin: var(--s-3) 0 0; }

/* --------------------------------------------------------------------------
   2.3 The result
   Legacy 10.7 gives .lookup-result a 20px top margin and its .meta an 18px
   bottom margin; both are replaced here so the block reads as one object
   separated from the control by a single 32px step.
   -------------------------------------------------------------------------- */
.area-check .lookup-result { margin-top: var(--s-7); }
.area-check .lookup-result .meta { margin-bottom: 0; }

/* THE RESULT HEADLINE - a resolved contradiction, recorded.
   Spec section 6 row 4 describes a "serif result headline". Spec section 3.2
   assigns "area-check result title" to --t-h3, which is Inter 600 / 28px, and
   section 3.1.2 plus checklist item 6 cap Brawler at six rendered elements
   none smaller than 22px - a budget the H1, the three section 3 figures, the
   price and the pull-quote consume exactly. A serif headline here would be
   the seventh and would fail item 6. Section 3.2 names this element by name,
   so section 3.2 wins and the headline is .h3.

   .headline is the write target for js/main.js, and css/style.css section
   10.4 gives that class the legacy serif treatment at the same size, so .h3
   has to be restored explicitly. Every value below is one .h3 already owns,
   including the 1167px tracking-band correction from section 3.3 rule 1
   (28px sits in the -0.02em band once the clamp reaches its ceiling).
   Nothing here is new. */
.area-check .headline {
  font-family: var(--font-sans);
  font-weight: 600;
  letter-spacing: -0.011em;
  margin: 0 0 12px;                              /* H3 -> body, spec 5.2 */
}
@media (min-width: 1167px) {
  .area-check .headline { letter-spacing: -0.02em; }
}
/* The published figure inside the headline, in the one accent hue - the same
   device as the accent word in the H1. 6.3:1 on #fff and 5.6:1 on --surface,
   and it is heading-sized, so it clears every threshold. Weight matches the
   heading: legacy 10.4 drops it to 400, which reads as a typo beside 600. */
.area-check .headline strong { font-weight: 600; color: var(--accent); }

/* Body copy is full-strength --ink (spec 3.3). Legacy .detail is --ink-72,
   which is reserved for a description subordinate to a bold label; this
   paragraph carries three published figures and is not subordinate to one. */
.area-check .detail { color: var(--ink); margin: 0 0 10px; }

/* --------------------------------------------------------------------------
   2.4 The spread chart
   Page chrome, not the Artefact: no em ladder, nothing shadowed, and every
   size sits at or above the 12px page floor. Label and value ride above the
   track rather than beside it, so a two-line area name can never squeeze the
   bar - and so the layout survives the panel halving at 1024.
   -------------------------------------------------------------------------- */
.area-check__chart .spread { margin-top: var(--s-5); }

.spread {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}
.spread__row {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.spread__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
}
.spread__label { color: var(--ink-72); }
.spread__val   { color: var(--ink); white-space: nowrap; }

.spread__track {
  position: relative;
  height: var(--s-3);
  overflow: hidden;
  background: var(--surface-2);                  /* the sanctioned trough */
  border-radius: var(--r-xs);
}
/* A LOLLIPOP PLOT, NOT A BAR CHART — and the distinction is doing two jobs.

   1. Honesty. The axis is truncated at 95–105, because nobody needs to see 0%
      of a Home Report valuation. A filled bar on a truncated axis encodes the
      window as its whole length and lies about ratios (DESIGN-SPEC §13.8). A
      dot has no length to misread: it states a position, which is all this
      data is.
   2. Differentiation. Two blind critics, reviewing independently, both said
      the page runs one graphic idea over and over — and this chart plotted the
      SAME four ESPC areas with the SAME four values as the hero Artefact's
      chart, three screens apart. Same figures, same axis, same bars. Changing
      the FORM here keeps every published figure identical while giving the
      scroll a second visual language.

   The stem runs from the 100% datum to the value, so it still reads as
   deviation; the dot marks the value itself. */
.spread__track {
  background: none;                              /* no trough: a dot needs no rail */
  height: 18px;
  overflow: visible;
}
/* The axis rail, thin and full width, plus the 100% datum standing taller. */
.spread__track::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 1px;
  transform: translateY(-50%);
  background: var(--rule);
}
.spread__track::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  width: 1px;
  background: rgba(19, 18, 17, .32);             /* >=3:1 on the surface */
  z-index: 1;
}
/* The stem: datum -> value. */
.spread__track i {
  position: absolute;
  top: 50%;
  left: min(var(--w), 50%);
  width: max(calc(var(--w) - 50%), calc(50% - var(--w)));
  height: 3px;
  transform: translateY(-50%);
  background: var(--accent-bright);              /* graphics only, never text */
  border-radius: 2px;
  z-index: 2;
}
/* The dot, on whichever end of the stem the value sits. */
.spread__track i::after {
  content: "";
  position: absolute;
  top: 50%;
  left: var(--dot-left, auto);
  right: var(--dot-right, auto);
  width: 9px; height: 9px;
  transform: translate(0, -50%);
  border-radius: 50%;
  background: var(--accent);                     /* the dot is the reading */
}
.spread__row[data-side="above"] .spread__track i::after { right: -4px; }
.spread__row[data-side="below"] .spread__track i::after { left: -4px; }

/* Three stops, space-between, so the middle label sits on the 100%-of-Home-
   Report gridline without a line being drawn: a rule at 3:1 against both the
   trough and the bar fill does not exist in this palette, and a rule that
   vanishes where it crosses a bar is worse than no rule. */
.spread__axis {
  display: flex;
  justify-content: space-between;
  gap: var(--s-3);
  margin-top: var(--s-3);
}

/* --------------------------------------------------------------------------
   2.5 The escalation
   Full panel width, closing the panel on a hairline. --rule-strong rather
   than --rule because --rule is within two steps of --surface and disappears
   on this fill.
   -------------------------------------------------------------------------- */
.area-check__escalation {
  margin-top: var(--s-7);
  padding-top: var(--s-7);
  border-top: 1px solid var(--rule-strong);
}

/* ======================= s5.css ======================= */
/* ==========================================================================
   SECTION 5 - "What's in the report" - layout only
   OffersOver - implements DESIGN-SPEC.md section 6 row 5, section 8.2 row 11,
   section 9. Pairs with _design/sections/s5.html.

   SCOPE OF THIS FILE
   Layout, and nothing else. Every colour, size, weight, radius, rule, gap
   token and component in this section comes from css/style.css. Nothing here
   redefines a token, a button, a card, a chip, a table or a type step, and
   nothing here declares a box-shadow, a border-radius or a transition.
   Nine declarations do the whole job, and every length in them is a token
   from spec section 2: --header-h, --gutter, --s-4, --s-7, --s-9,
   --measure-body. No number in this file was invented.

   This file is ASCII-only on purpose, matching css/style.css: Windows
   PowerShell 5.1 mangles UTF-8 punctuation in .css files.
   ========================================================================== */


/* --------------------------------------------------------------------------
   5.1 The sticky evidence panel - spec 8.2 row 11 (CSS ONLY: no JS, no GSAP
   pin, no ScrollTrigger).

   HOW THE STICKY GETS ITS TRAVEL, since this is the part that silently
   breaks if someone "tidies" it:

     - .s5__panel is a GRID ITEM. A grid item's containing block is its grid
       AREA, and the sticky constraint rectangle is that containing block. The
       area is as tall as the tallest item in the row - the checklist column -
       so the panel has roughly 400px of room to slide through.
     - align-self:start is therefore load-bearing, not cosmetic. Without it
       the item stretches to fill its own grid area, box and area become the
       same height, the constraint rectangle offers zero travel, and the
       panel simply never sticks. It looks like "position:sticky is broken".
     - The row must stay a single grid row. Do not wrap either column in an
       extra element, and do not give the panel a grid-row of its own.

   top = header (64) + one gutter (24) = 88px, so the paper clears the sticky
   header by the same 24px that separates every other column on the page.

   The panel is ~600px tall including its caption; 88 + 600 = 688, so it is
   fully visible at every viewport height this breakpoint serves.
   -------------------------------------------------------------------------- */
.s5__panel {
  /* Columns 8-12 (spec 6 row 5). Written out rather than composed from
     .col-5 + .start-8, because those two utilities cannot be combined:
     `.col-N { grid-column: span N }` sets grid-column-END to auto, and
     `.start-M` then overrides only the START, so the pair resolves to a
     one-track-wide item. This is a latent bug in css/style.css section 4 that
     will bite every builder who needs an offset column - it is reported, not
     patched here, because this section must not edit the shared stylesheet. */
  grid-column: 8 / span 5;

  position: sticky;
  top: calc(var(--header-h) + var(--gutter));   /* 64 + 24 = 88 */
  align-self: start;                             /* see the note above */
}

/* The locked caption line, spec 5.2's "claim -> source line" gap. */
.s5__caption { margin-top: var(--s-4); }         /* 16px */

/* The five rows already carry .ruled-list's 18px/16px padding and hairlines.
   The list is the last painted element in its column, so the section ends on
   content and not on air (checklist 36) - nothing to declare, deliberately. */


/* --------------------------------------------------------------------------
   5.2 Tablet and below - spec 9: "the section 5 sticky panel unsticks and
   moves below the checklist" at 768-1023, and stays there below 768.

   position:static also releases align-self, so both columns are full width
   and stack in source order: claim first, evidence beneath it. The eyeline
   pairing is a two-column effect; once stacked, the honest arrangement is
   sequential, not a shrunken side-by-side.

   The paper is CAPPED, never SCALED (spec 7.2 mechanism 2). The evidence
   variant sets --artefact-scale:1 and --artefact-w:100%, so the em ladder is
   width-independent: a 10.5px table cell stays 10.5px whether the paper is
   456px or 552px wide. Capping at --measure-body keeps the table's measure
   the same as it is in the desktop 5-column panel instead of letting a
   975px-wide "page" appear, which no document ever looks like. Below 768 the
   cap is above the available width, so the paper simply fills the container
   and .artefact__scroll takes the table's overflow (spec 9, checklist 23).
   -------------------------------------------------------------------------- */
@media (max-width: 1023px) {
  .s5__claim { grid-column: 1 / -1; }
  .s5__panel {
    grid-column: 1 / -1;
    position: static;
    margin-top: var(--s-9);                      /* 48px */
  }
  .s5__panel .artefact[data-artefact="evidence"] { max-width: var(--measure-body); }
}

@media (max-width: 767px) {
  .s5__panel { margin-top: var(--s-7); }         /* 32px */
}

/* ======================= s6-s7.css ======================= */
/* ==========================================================================
   SECTIONS 6 + 7 — "How it works" and THE ONE DARK BAND
   OffersOver · implements DESIGN-SPEC.md §6 rows 6 and 7 and §13.1 (LOCKED)
   Markup: _design/sections/s6-s7.html

   SCOPE RULE FOR THIS FILE
   Nothing here redefines a token, a type step, a button, a card, a chip or a
   gap. Everything the design system already provides is used by its own
   class: .h4, .meta, .lede, .quote, .btn, .btn--lg, .btn-row, .grid-12,
   .col-3, .col-9, .container, .section--compact, .section-surface,
   .section--ink, .visually-hidden, .dim, .gap-h4-desc, .gap-body-cta,
   .gap-cta-note. What is below is ONLY the layout these two rows need, plus
   the one button variant spec §13.1 introduces and css/style.css has not
   shipped yet.

   CASCADE REQUIREMENT
   Block 1 (.btn--on-dark) MUST land AFTER css/style.css §5.1. `.btn:focus-
   visible` sets `outline: 2px solid var(--accent)` at specificity 0,2,0 and
   would otherwise beat the on-dark ring on source order. If the assembler
   moves .btn--on-dark into style.css, put it directly after .btn--quiet and
   delete block 1 from here — do not leave two copies.

   HARD RULES HONOURED THROUGHOUT
     - zero box-shadow on any element in these two sections (checklist 18/19)
     - no element carries both a border and a shadow (checklist 20)
     - every button radius is 8px, inherited from .btn (checklist 17)
     - every neutral satisfies R >= G >= B; the only non-token colours used
       are white at alpha, which composites warm over --surface-ink
     - no text is centre-aligned (checklist 10)
     - letter-spacing follows the §3.3 size formula at every declared size
   ========================================================================== */


/* ==========================================================================
   1. .btn--on-dark — spec §13.1, the fourth button variant

   EXACTLY ONE INSTANCE PER PAGE, and only on --surface-ink.
   Why it exists: --accent on --surface-ink measures about 1.6:1, so an
   accent-filled button cannot legally sit on the dark band. White fill /
   ink label measures 17.4:1 for the label and 18.6:1 for the fill against
   the band. Geometry, weight, radius and transitions all come from .btn —
   only the three colours and the focus ring are stated here.
   ========================================================================== */

.btn--on-dark { background: #fff; color: var(--ink); }
.btn--on-dark:hover { background: rgba(255, 255, 255, .88); }
.btn--on-dark:active { transform: translateY(1px); }
/* On a white fill the accent ring drops to 1.5:1. The ring is --on-dark,
   which reads against the band the button sits on, per spec §13.1. */
.btn--on-dark:focus-visible { outline-color: var(--on-dark); }


/* ==========================================================================
   2. SECTION 6 — the steps strip

   Heading left (cols 1-3), three steps inline right (cols 4-12). The strip is
   deliberately short: at 1440 it computes to about 180px including its 40/40
   compact padding. Anything that makes it taller — a lede, an eyebrow, a
   card, an icon, a fourth step — defeats the section's whole purpose, which
   is to make 48 hours read as a process rather than a delay.
   ========================================================================== */

/* Heading column and step column both hang from the top of the band. */
.steps-strip { align-items: start; }

/* The two-tone heading rule (spec §3.3 rule 3) is declared in style.css for
   .h2, .h3 and .display only. This heading is an <h2> set at .h4 size, so the
   rule is restated HERE, scoped to this section — .dim itself is untouched. */
.steps-strip__head .dim { color: var(--ink-72); }

/* The standing note under the heading. .meta already supplies 13.5px / 1.5 /
   letter-spacing 0 / --ink-62; only the gap is section business. */
.steps-strip__note { margin-top: 12px; }

.steps-strip__list {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gutter);
  list-style: none;
  margin: 0;
  padding: 0;
}
.steps-strip__list > li { margin: 0; }

/* Numeral and title share one line, so the numeral costs no vertical space. */
.steps-strip__title { display: flex; align-items: center; gap: 10px; }

/* One of the two sanctioned uses of --r-pill (spec §2: "skip-link and the
   step numerals ONLY"). Ink fill, not accent: the accent fill is reserved
   for the money CTA, and a quiet numeral is the point of this strip.
   No border, no shadow. 12px is the page floor. Tracking is 0, not +0.02em:
   the §3.3 formula only opens tracking at 12px for UPPERCASE runs, and a
   single lining figure inside a 24px disc is neither uppercase nor a run. */
.steps-strip__n {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: var(--r-pill);
  background: var(--ink);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}

/* Type comes entirely from the system: .body is 17px / 1.65 / -0.011em and
   .desc is the one sanctioned subordinate colour, --ink-72 (spec §3.3). That
   is the same pair the §5 checklist rows and the §8 objection tiles use, so
   an h4 label with a description reads identically in all three places.
   A smaller step description would buy about 25px of band height and cost
   that consistency; it is not worth it. Only the margin is section business. */
.steps-strip__desc { margin: 0; }

/* 768-1023 (spec §9 tablet): a 3-column heading gutter is too narrow for a
   20px heading, so the heading takes the full width above the steps. The gap
   is the spec §5.2 "H2 (no lede) -> content" value, 40 desktop / 28 mobile. */
@media (max-width: 1023px) {
  .steps-strip__head { grid-column: 1 / -1; margin-bottom: 40px; }
  .steps-strip__list { grid-column: 1 / -1; }
}

/* <768 (spec §9 mobile): everything stacks, everything stays left-aligned. */
@media (max-width: 767px) {
  .steps-strip__head { margin-bottom: 28px; }
  .steps-strip__list { grid-template-columns: minmax(0, 1fr); gap: var(--s-6); }
}


/* ==========================================================================
   3. SECTION 7 — the dark band

   Full-bleed --surface-ink. .section--ink (style.css §6) already supplies the
   background, the --on-dark text colour, the --on-dark-62 .meta colour and
   the --on-dark focus ring. Everything below is layout and the two type
   values the spec states for this row specifically.
   ========================================================================== */

/* --- 3.1 The pull-quote ---------------------------------------------------
   .quote supplies 32px Brawler roman / 1.30 / the tracking band correction.
   The measure is --measure-lede (648px = exactly 7 columns), which puts the
   quote on 2 lines at 1440 and keeps the band a statement rather than a
   paragraph. Left-aligned: checklist item 10 permits centring only in the
   closing CTA band. -------------------------------------------------------- */
.buyers__quote {
  margin: 0;
  max-width: var(--measure-lede);
}

/* Decorative graphic, aria-hidden in the markup. --accent-bright is a
   GRAPHICS-ONLY token (spec §2 / checklist 12: it may never colour a text
   node) and this is a graphic: 4.9:1 against --surface-ink, past the 3:1
   floor for graphical objects. Height follows the 36x26 viewBox. */
.buyers__mark {
  width: 40px;
  height: auto;
  margin-bottom: 20px;
  fill: var(--accent-bright);
}

/* spec §5.2 "claim -> source line" is 10px; an attribution under a 32px
   quote needs a little more air to stop reading as a third line of quote. */
.buyers__attrib { margin-top: 14px; }

/* --- 3.2 The three accounts ----------------------------------------------
   De-boxed on hairlines: one 1px top rule each, no fill, no border on the
   other three sides, no radius, no shadow. rgba(255,255,255,.12) is the same
   hairline value the system already uses for .card--on-dark, so the dark band
   has one hairline weight and not two. ------------------------------------- */
.buyers__evidence { margin-top: var(--s-9); }

.buyers__accounts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gutter);
  list-style: none;
  margin: 0;
  padding: 0;
}
.buyers__accounts > li {
  display: flex;
  margin: 0;
  padding-top: var(--s-6);
  border-top: 1px solid rgba(255, 255, 255, .12);
}

/* The account text itself is unstyled on purpose: it inherits 17px / 1.65 /
   -0.011em from <body> and --on-dark from .section--ink, which is the
   full-strength body treatment the spec requires. Only the resets are here.

   The three accounts run to different lengths, so the attributions are pushed
   to a shared bottom line rather than each floating under its own last line.
   Two things follow from that: the three columns read as one ruled object
   instead of three ragged ones, and the disclaimer below then sits exactly
   12px from EVERY attribution rather than 12px from the longest — which is
   what checklist item 31 actually asks for. */
.buyers__account {
  display: flex;
  flex-direction: column;
  width: 100%;
  margin: 0;
}
.buyers__account blockquote { margin: 0; }
.buyers__account blockquote p { margin: 0; }
.buyers__account figcaption { margin-top: auto; padding-top: 12px; }

/* --- 3.3 The disclaimer ---------------------------------------------------
   COMPLIANCE, not styling. 12.5px per spec §6 row 7, sitting 12px below the
   accounts — inside the 24px checklist item 31 requires. 12.5px clears the
   12px page floor (checklist 26) and 12.5px is not uppercase, so the size
   formula gives it letter-spacing 0. --on-dark-62 measures 8.5:1 on the band.
   Do not enlarge this gap and do not move this element.

   It ships as <small>, which is the element HTML defines for legal small
   print and disclaimers — not as a styled <p>. That is also why it does not
   read as a body paragraph to anything walking the DOM. ---------------------- */
.buyers__disclaimer {
  display: block;
  max-width: var(--measure-lede);
  margin: 12px 0 0;
  font-size: 12.5px;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--on-dark-62);
}

/* --- 3.4 The exit ---------------------------------------------------------
   The cluster rule (spec §5.2): the closing line -> CTA -> reassurance object
   has internal gaps of 28 and 16, so the gap that surrounds it must be at
   least 4 x 28 = 112px. 128px is the nearest value on the spacing scale, and
   it also stops the reassurance line from being read as a fourth account.
   Below 768 the internal gaps drop to 24 and 14, so 96px satisfies the same
   rule. The reassurance line is the last painted element in the section, so
   the band does not end in padding (checklist item 36). ---------------------- */
.buyers__close { margin-top: var(--s-14); }

/* 768-1023 and below: three columns of 17px copy get too narrow, so the
   accounts stack. Each one is then capped at --measure-lede so no body
   paragraph exceeds 648px (checklist item 7). */
@media (max-width: 1023px) {
  .buyers__evidence { margin-top: var(--s-7); }
  .buyers__accounts { grid-template-columns: minmax(0, 1fr); gap: var(--s-7); }
  .buyers__account { max-width: var(--measure-lede); }
}

@media (max-width: 767px) {
  .buyers__mark { width: 34px; margin-bottom: 16px; }
  .buyers__accounts { gap: var(--s-6); }
  .buyers__close { margin-top: var(--s-13); }
}

/* ======================= s8-s9.css ======================= */
/* ==========================================================================
   OffersOver - section CSS for spec 6 rows 8 and 9.
   Pairs with _design/sections/s8-s9.html. LOAD AFTER css/style.css.

   SCOPE DISCIPLINE. Nothing here defines a token, a button, a card, a chip,
   a badge, a table or a type step. All of those come from the system. What is
   here is only what a two-section layout genuinely needs:
      8.1  the objection grid (a three-column track and two reflows)
      9.1  the price panel's two-column split and its one fill override
      9.2  the buy cluster's five gap values, all from the spec 5.2 table
      9.3  the recap's list-beside-paper arrangement

   ASCII-only, like css/style.css and the other section files: glyphs would be
   mangled by PowerShell on this machine.

   AAA (spec 11) invariants for this file, all checkable:
     - No box-shadow is declared anywhere in it. The Artefact is the only
       shadowed object on the page and it brings its own (item 19).
     - No border-radius is declared anywhere in it. .card supplies 12px,
       .panel supplies 20px, .btn supplies 8px (items 17, 20).
     - Exactly two 1px hairlines are declared, both in var(--rule), and
       neither sits on an element that carries a shadow (item 20).
     - No colour value is introduced. Every colour used is a system token and
       every neutral satisfies R >= G >= B (item 13).
     - No :focus rule and no `outline:` declaration appears here, so nothing
       in this file can weaken the system's focus ring (item 22).
     - Every spacing value is either a spec 2 token or a literal that appears
       in the spec 5.2 gap table. Each literal is labelled where it is used.
   ========================================================================== */


/* ==========================================================================
   8.1  OBJECTION TILES - spec 6 row 8

   Six .card tiles on a #fff band. The card itself - --surface fill, no
   border, 12px radius, no shadow, 24px padding falling to 20px below 768 -
   is entirely the system's (css/style.css 5.3). This block owns the track
   count and nothing else.

   TRACK COUNT, per spec 9:
     >=1024      3-up   (two rows of three; 1128 - 2*24 gutters / 3 = 360px)
     768-1023    2-up
     <768        1-up

   Grid rows stretch by default, so the three tiles in a row share the height
   of the tallest. That is deliberate: six cards of six different heights
   would read as six unrelated statements, and the section's whole effect is
   that these are a bounded, finite, symmetrical set - six things, and none
   of them is bad news.
   -------------------------------------------------------------------------- */

.objections__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gutter);              /* 24 - spec 5.2 grid gap */
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The base stylesheet gives every <li> a 0.35em bottom margin. Inside a grid
   that would add a phantom row gap on top of the real one. */
.objections__grid > li { margin: 0; }

/* margin:0 removes the paragraph's trailing 1em so the tile's own 24px
   padding is the only thing between the text and the card edge - otherwise
   every card reads as bottom-heavy by 17px. .body's 552px measure cap is
   left in place: the widest tile is 360px, so it can never bind, and keeping
   it means no paragraph in this file can drift past the measure. */
.objection__body { margin: 0; }

@media (max-width: 1023px) {
  .objections__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 767px) {
  .objections__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;                     /* spec 5.2 grid gap, mobile */
  }
}


/* ==========================================================================
   9.1  THE PRICE PANEL - spec 6 row 9

   One panel at full --content width on the --surface band, holding four
   things across it, left to right:

       PRICE + dated rise + the ask  |  What you get  |  the document

   The money at one end and the object it buys at the other. That is the
   section's argument made by position, before a word is read.

   THE ONE OVERRIDE IN THIS FILE, and why it is legitimate.
   css/style.css .panel fills with --surface because it was authored for the
   area-check shell, which sits on a #fff band. This panel sits ON --surface,
   where a --surface fill is invisible. Spec 4.3's row for a box on a
   --surface band is "#fff, 1px solid var(--rule)", so .price-panel restates
   the FILL and nothing else: radius stays --r-lg (spec 2 names "the price
   band inner" as one of the two sanctioned --r-lg objects), the border stays
   the system's 1px --rule, padding stays 32px and 20px below 768, and the
   shadow stays none. No new value is introduced.

   COLUMN RATIO 1 : 1.1.
   The left column has to hold a 72px numeral and a full-width button; the
   right has to hold a 17px four-item list beside a 180px sheet of paper.
   At 1440 that resolves to 488 / 496 usable, which is the widest the recap
   can be while the list still keeps a 292px measure - wide enough that no
   recap item runs past two lines.
   -------------------------------------------------------------------------- */

.price-panel {
  background: #fff;                /* spec 4.3: a box on a --surface band */
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: var(--s-8);                 /* 40 */
}

/* The divider. A hairline, not a gap: two columns separated by air read as
   two sections, and this must read as one object with the price inside it.
   Stretch is the default, so the rule runs the full height of the panel's
   content box and the composition stays a single frame. */
.price-recap {
  border-left: 1px solid var(--rule);
  padding-left: var(--s-8);        /* 40 */
}


/* --------------------------------------------------------------------------
   9.2  THE BUY CLUSTER - spec 5.2

   Five elements, four gaps, every one of them off the spec 5.2 table and
   none of them larger than 32px, which is the cluster ceiling:

     price   -> rise note      12   (spec 2 --s-3)
     rise    -> CTA            28   (spec 5.2 "body -> CTA", 24 below 768)
     CTA     -> reassurance    16   (spec 5.2 "CTA -> reassurance microcopy",
                                     14 below 768 - ALWAYS below the button)
     reassurance -> legal      32   (16 above the hairline + 16 below it)

   The reassurance line is below the CTA and never above it. Above the button
   it is a feature; below the button it is permission.
   -------------------------------------------------------------------------- */

/* .price is display:block and sets no margin, so the paragraph's inherited
   1em bottom margin would compute to 72px under a 72px figure. */
.price-block__figure { margin: 0; }

/* .body's 552px measure cap is deliberately NOT removed. When the panel
   stacks below 1024 the left column becomes the full panel width (656px at
   768), and an uncapped paragraph there would run to a 656px measure -
   past the 648px ceiling in checklist item 7. */
.price-block__rise { margin: var(--s-3) 0 0; }

/* Full column width, per spec 6 row 9. .btn is inline-flex and centres its
   own label, so this is the only declaration needed. Height (56), radius (8)
   and the zero box-shadow all stay the system's. */
.price-block__cta {
  width: 100%;
  margin-top: 28px;                /* spec 5.2 "body -> CTA" */
}

/* The measure cap applies to the reassurances and the legal pair for the
   same reason as the rise note: when the panel stacks, the column is wider
   than any legal text measure ought to be. */
.price-reassure {
  margin: var(--s-4) 0 0;          /* 16 - spec 5.2 "CTA -> reassurance" */
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s-2);                 /* 8 */
  max-width: var(--measure-body);
}

.price-reassure li {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);                 /* 12 */
  margin: 0;
}

/* Optical alignment, not spacing: the .ui line box is 15 x 1.45 = 21.75px
   and the tick glyph is 18px, so (21.75 - 18) / 2 = 1.9 -> 2. The tick is
   aria-hidden in the markup and the sentence carries the meaning, so colour
   is never the sole signal here. */
.price-reassure .tick {
  flex: 0 0 18px;
  height: 18px;
  margin-top: 2px;
}

/* Both legal lines live INSIDE the block: there, they read as candour;
   under the section, they would read as boilerplate. 16 above the hairline
   and 16 below it is 32 in total, exactly the cluster ceiling. */
.price-legal {
  margin-top: var(--s-4);          /* 16 */
  padding-top: var(--s-4);         /* 16 */
  border-top: 1px solid var(--rule);
  display: grid;
  gap: var(--s-3);                 /* 12 */
  max-width: var(--measure-body);
}


/* --------------------------------------------------------------------------
   9.3  "WHAT YOU GET", ANCHORED TO THE PAPER

   The recap head is .h3 (28px - spec 3.2 names this exact element, "price
   recap head"). 12px below it is the spec 5.2 "H3 -> body" gap; the ruled
   list's own top hairline and 18px row padding open that to ~30px optically,
   which is why the table value is right here and does not need a larger one.

   The list and the thumbnail sit side by side and wrap as one unit. The
   190px flex-basis is the decision point: 190 + 24 + 180 = 394, so the pair
   stays side by side wherever the recap has 394px, which is every width from
   1024 up and every stacked width from about 470 up. It only wraps where the
   alternative would be a list crushed below a 20-character measure. Below
   480px css/style.css 11.9 removes the thumbnail entirely and the recap list
   survives without it (spec 9) - hiding the wrapper here would duplicate
   that rule, so the wrapper is left alone and only its caption is handled.

   THE ROW'S OWN MAX-WIDTH is derived, not chosen: measure-body (552, the
   widest legal measure for 17px body copy) + one gutter + the thumbnail.
   Above 1280 the recap column is ~496px so it never binds; below 1024 the
   panel stacks and the recap gets the full 911px panel width, where without
   it the list would run to a 707px measure - past the 648px ceiling in
   checklist item 7 - and the thumbnail would drift 180px away from the list
   it is supposed to anchor.
   -------------------------------------------------------------------------- */

.price-recap__head { margin-bottom: var(--s-3); }   /* 12 - spec 5.2 H3 -> body */

.price-recap__body {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--s-6);                 /* 24 */
  max-width: calc(var(--measure-body) + var(--s-6) + 180px);
}

.price-recap__list {
  flex: 1 1 190px;
  min-width: 0;
}

.price-recap__thumb { flex: 0 0 180px; }

.price-recap__caption { margin-top: var(--s-3); }   /* 12 */


/* --------------------------------------------------------------------------
   9.4  Responsive - spec 9
   -------------------------------------------------------------------------- */

/* 1024-1279 desktop-narrow. The split holds (spec 9 keeps this band on the
   desktop structure), but 1024px of viewport leaves the panel only 896px of
   content, and at the 1:1.1 ratio with a 40px gap the recap column falls to
   408px - one pixel class short of holding the list and the paper side by
   side. Tightening the gap to 32 and the ratio to 1:1.2 gives the recap 448
   and the list a 244px measure, which keeps every recap item inside three
   lines. The left column still has 384px, which a 72px numeral and a
   full-width button carry comfortably. */
@media (max-width: 1279px) and (min-width: 1024px) {
  .price-panel {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
    gap: var(--s-7);               /* 32 */
  }
  .price-recap { padding-left: var(--s-7); }   /* 32 */
}

/* 768-1023 tablet, and anything narrower: the panel stacks, and the recap's
   left hairline becomes a top hairline so the buy block is always first.
   32 above the rule and 32 below it keeps the division symmetrical and keeps
   both numbers on the spec 2 scale. */
@media (max-width: 1023px) {
  .price-panel { grid-template-columns: minmax(0, 1fr); gap: var(--s-7); }  /* 32 */
  .price-recap {
    border-left: 0;
    border-top: 1px solid var(--rule);
    padding-left: 0;
    padding-top: var(--s-7);       /* 32 */
  }
}

@media (max-width: 767px) {
  .price-block__cta   { margin-top: var(--s-6); }   /* 24 - spec 5.2 mobile */
  .price-reassure     { margin-top: 14px; }         /* spec 5.2 mobile */
}

/* Below 480 the thumbnail is display:none (css/style.css 11.9). Its caption
   names an object that is no longer on screen, so it goes with it. */
@media (max-width: 479px) {
  .price-recap__caption { display: none; }
}


/* --------------------------------------------------------------------------
   9.5  Shared utility used by both sections

   Keeps a date, or the four-word answer to "is this a subscription?", from
   breaking across a line. Applied only to strings whose rendered width is
   provably inside the narrowest line the page ever draws (280px at a 320px
   viewport), so it can never push the body sideways - spec 9 permits zero
   horizontal page scroll at 320, 390, 768, 1024, 1280 and 1440.
   -------------------------------------------------------------------------- */
.price-nb { white-space: nowrap; }

/* ======================= s10-s11.css ======================= */
/* ==========================================================================
   SECTIONS 10 + 11 - GUIDES INDEX and FAQ
   OffersOver - implements DESIGN-SPEC.md section 6 rows 10 and 11 (LOCKED).
   Companion markup: _design/sections/s10-s11.html

   SCOPE OF THIS FILE - read before adding anything to it
   Layout only. Every token, button, card, chip, table, badge, accordion and
   ruled list these two sections use already exists in css/style.css and is
   used unchanged. Nothing below redefines a token, a colour, a type step, a
   radius, a shadow or a component. What is here is the small amount of
   two-column and two-panel geometry that the design system has no opinion
   about, plus the compensation described in 1.2, which exists only because a
   44px-tall component is being nested inside an 18px-padded ruled row.

   ALREADY PROVIDED BY THE SYSTEM - do not restate it here:
     section padding 88/128, 64/96, 48/72          style.css section 6
     .ruled-list rules and 18px/16px row padding    style.css 5.10
     .section--ruled hairline between same-surface  style.css section 6
     .accordion, summary, the "+" marker, the body  style.css 5.9
     .btn--quiet geometry, 44px min hit box, arrow  style.css 5.1
     .h2 / .h3 / .meta / .dim / .measure-lede       style.css section 3
     .gap-h2-content 40/28                          style.css section 6

   This file is ASCII-only on purpose: PowerShell 5.1 mangles UTF-8
   punctuation, and CSS never needs a glyph the markup cannot carry.
   ========================================================================== */


/* ==========================================================================
   1. SECTION 10 - GUIDES INDEX
   ========================================================================== */

/* --------------------------------------------------------------------------
   1.1 The two-column ruled list

   Row flow, not column flow, so the DOM order and the reading order are the
   same object: items 1,2 sit on row 1, 3,4 on row 2, 5,6 on row 3. Because
   grid rows equalise height, the hairline under item 1 lands at exactly the
   same y as the hairline under item 2 whatever their descriptions weigh -
   which is the only reason a two-column ruled list reads as ruled rather than
   as two lists that happen to be adjacent.

   Six items over two columns is deliberate: both columns end level, so the
   list has no ragged bottom edge to explain.

   Gap is the grid gutter, 24 desktop / 16 mobile (spec 5.2). Row gap stays 0
   so the rules of one column form one continuous stack.
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   1.0 The demoted header's two-tone break

   Spec 3.3 rule 3 sets the second clause in --ink-72 at the same size and
   weight; .dim already does the colour and is left alone. This makes the
   clause start its own line, and it is the only way to get that break here:

     - a hard <br> makes Chrome abandon text-wrap:balance for the whole
       heading, which drops "email." onto a line by itself at 320px
       (checklist 9);
     - left purely inline, balance splits the heading mid-sentence at every
       width from 768 up - "Understand the system before you / bid. Six
       guides. ..." - which reads as a clumsy break and hides the two-tone
       structure the rule exists to produce.

   As a block, each clause balances independently: statement on one line,
   qualification on the next, and balance still protects both from orphans at
   320. Scoped to this header, so the global .dim is untouched.
   -------------------------------------------------------------------------- */
.guides__head .dim { display: block; }

.guides__list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--gutter);
}

/* .ruled-list gives item 1 its top rule. In two columns the first item of the
   SECOND column needs one too, or column 2 starts with an open edge.
   Selector is doubled (.ruled-list.guides__list) so it outranks
   .ruled-list > li:first-child regardless of which order the two stylesheets
   are concatenated in. */
.ruled-list.guides__list > li:nth-child(2) {
  border-top: 1px solid var(--rule);
}

/* 1.2 Row padding compensation - the one non-obvious rule in this file.

   The row's title is a .btn--quiet, and .btn--quiet is min-height:44px with
   align-items:center, so its 15px label floats roughly 14.5px below the top
   of its own box. Left at the system's 18px row padding, the optical gap
   above every title would be 32.5px and the vertical rhythm of the ruled list
   would be wrong by half a step.

   4 + 14.5 = 18.5, so the OPTICAL top gap is the spec's 18px (16px on
   mobile: 2 + 14.5). The bottom padding is untouched, because the description
   is real text and needs the full 18. The component is not modified - its
   44px hit box survives intact, which is the point of compensating here
   rather than shaving the button. */
.ruled-list.guides__list > li { padding: 4px 0 18px; }

/* --------------------------------------------------------------------------
   1.3 Quiet links in both sections

   .btn centres its label and forbids wrapping, which is right for a 2-word
   CTA in a button-shaped box and wrong for a 39-character guide title sitting
   in a left-aligned column. These three declarations let the label wrap and
   sit on the text edge. Nothing else about the component changes: no colour,
   no size, no weight, no radius, no arrow, no hit box.

   It also keeps checklist 10 honest - no text in these two sections computes
   text-align:center.
   -------------------------------------------------------------------------- */
.guides .btn--quiet,
.faq .btn--quiet {
  justify-content: flex-start;
  text-align: left;
  white-space: normal;
}

/* --------------------------------------------------------------------------
   1.4 The inline term links

   Not a fourth and fifth and sixth ruled row: these three pages are term
   explainers, and giving them rules would make the section look like nine
   equal guides and cost row 10 its quietness.

   column-gap is 20 rather than the .btn-row 12 because .btn--quiet carries no
   horizontal padding of its own, so 12px would measure label-edge to arrow
   and read as a collision - the same reasoning as .hero__ctas in style.css.
   row-gap 0: the buttons' own 44px boxes already separate wrapped lines.
   -------------------------------------------------------------------------- */
.guides__also {
  display: flex;
  flex-wrap: wrap;
  /* Baseline, not centre: the 13.5px label and the 15px links are different
     sizes on one line, so they sit on a shared baseline the way .source-row
     and .artefact__foot do. Centring them leaves the label riding ~3px high. */
  align-items: baseline;
  column-gap: var(--s-5);
  margin: var(--s-5) 0 0;
}

/* Below 768 the list is one column, so item 2 must lose the top rule it was
   given for the second column or it doubles up with item 1's bottom rule. */
@media (max-width: 767px) {
  .guides__list { grid-template-columns: minmax(0, 1fr); column-gap: 0; }
  .ruled-list.guides__list > li:nth-child(2) { border-top: 0; }
  .ruled-list.guides__list > li { padding: 2px 0 16px; }
  .guides__also { margin-top: var(--s-4); }

  /* One column means the three links stack, and an inline "Also" then labels
     only the first of them. Give it the whole first line so it reads as a
     label for the group. */
  .guides__also > .meta { flex: 0 0 100%; }
}


/* ==========================================================================
   2. SECTION 11 - FAQ

   Columns 1-5 carry the heading, columns 7-12 the accordion; column 6 is the
   empty gutter that makes the split asymmetric rather than halved. Six
   columns is 552px, which holds a 20px summary at a readable measure - the
   full 1128px container would run a summary to 90 characters and the
   accordion would stop reading as a list of questions.

   Everything the accordion does is style.css 5.9. Nothing here touches it.
   ========================================================================== */

/* 2.1 Column placement

   These two panels carry NO .col-* / .start-* class, and the placement is
   written here instead. That is not a preference - it is a defect avoidance.
   style.css collapses the helper classes below 768 with:

     .grid-12 > [class*="col-"]   { grid-column: span 12; }
     .grid-12 > [class*="start-"] { grid-column-start: 1; }

   `grid-column: span 12` puts "span 12" in grid-column-START and leaves END
   auto. The second rule then overwrites that start with 1, END is still auto,
   and any element carrying both classes resolves to a SINGLE track. Measured
   before this rewrite: the accordion panel rendered 9px wide at 320px, with
   every summary broken to one character per line.

   Placing the two panels from this file also makes them immune to the order
   the two stylesheets are concatenated in: nothing in style.css matches these
   selectors, so there is no specificity race to lose. */
.faq__head { grid-column: 1 / span 5; }
.faq__body { grid-column: 7 / span 6; }

/* Below 1024 the split stops earning its keep: five columns is under 300px,
   which drops a 48px heading to three lines. Stack instead, and cap the
   accordion at the lede measure so the summaries keep roughly the line length
   they had in the two-column layout.

   The margin values are the spec 5.2 "H2 (no lede) -> content" step, 40/28,
   the same numbers .gap-h2-content carries. */
@media (max-width: 1023px) {
  .faq__head { grid-column: 1 / -1; margin-bottom: 40px; }
  .faq__body { grid-column: 1 / -1; max-width: var(--measure-lede); }
}
@media (max-width: 767px) {
  .faq__head { margin-bottom: 28px; }
}

/* 2.2 Orphan control on the questions

   Spec 3.3 puts text-wrap:balance on every H1/H2/H3 and pretty on every <p>,
   which leaves <summary> as the one piece of heading-weight type on the page
   with no orphan control at all. It shows: at 390px the questions wrapped to
   "Where does the data come / from?" and "Can I not just look this up /
   myself?" - a single-word last line on 20px/600 type, which is the exact
   defect checklist 9 exists to prevent on headings.

   Balance rather than pretty, so a two-line question splits at its natural
   phrase rather than leaving a runt line. */
.faq .accordion summary { text-wrap: balance; }

/* The tertiary path out of the section. 16px clears the last details' bottom
   rule; the button's own 44px box supplies the rest of the optical gap.
   It is the section's last painted element, so section 11 ends in content
   rather than in empty padding (checklist 36). */
.faq__tail { margin-top: var(--s-4); }

/* ======================= s12-s13.css ======================= */
/* ==========================================================================
   OffersOver - section CSS for spec 6 rows 12 and 13 + the sticky buy bar.
   Pairs with _design/sections/s12-s13.html. LOAD AFTER css/style.css.

   SCOPE DISCIPLINE. Nothing here defines a token, a button, a card, a chip or
   a table. Every one of those comes from the system. What is here is:
     12.1  the closing band's cluster geometry (three gap values, derived)
     13.1  the footer's arrangement (a hairline and a two-column legal block)
      C.1  the buy bar's contents (the system already owns the bar itself)

   ASCII-only, like css/style.css: glyphs would be mangled by PowerShell.
   No box-shadow is declared anywhere in this file. No radius is declared
   anywhere in this file. No new colour value is introduced anywhere in this
   file. Every neutral used is a system token and every one satisfies R>=G>=B.
   ========================================================================== */


/* ==========================================================================
   12.1  CLOSING CTA BAND - spec 6 row 12

   The band's 128 / 160 padding comes from .section--closing and its --surface
   fill from .section-surface. Neither is restated here.

   THE THREE GAP VALUES ARE DERIVED, NOT CHOSEN. The cluster rule (spec 5.2)
   is: no gap inside a conversion cluster may exceed the largest internal gap,
   and the air around the cluster must be at least 4x that gap. So at each
   breakpoint the largest internal gap is capped at a quarter of the band's
   own TOP padding, which is the smaller of its two paddings:

     >=1024   padding 128 / 160   ->  128 / 4 = 32   ->  headline->CTA  32
      768-1023 padding  96 / 128  ->   96 / 4 = 24   ->  headline->CTA  24
      <768    padding  72 /  96   ->   72 / 4 = 18   ->  headline->CTA  16
                                                          (16, not 18: 18 is
                                                          not on the spec 2
                                                          spacing scale)

   CTA -> reassurance is the spec's own 16 / 14 and is never larger than the
   gap above it, so it can never become the binding constraint.
   -------------------------------------------------------------------------- */

/* The cluster is one object. It carries no fill, no border and no shadow -
   isolation here is done with air, which is the whole point of the section. */
/* The closing band's gradient is generated by _design/bands.mjs as
   .band--closing, applied in the markup. Do not re-add it here. */

.closing__cluster { max-width: var(--content); margin-inline: auto; }

/* Centred only at >=1024. Spec checklist 10 permits centring in this band and
   nowhere else, but it also requires every paragraph of two or more lines to
   be left-aligned. Below 1024 the reassurance line can wrap, so below 1024
   the band is left-aligned - which is also what spec 9 requires under 768.
   At >=1024 the line measures ~672px against >=960px of available width and
   is therefore always a single line. */
@media (min-width: 1024px) {
  .closing__cluster { text-align: center; }
}

.closing__title { margin: 0; }

.closing__cta { margin-top: 32px; }

.closing__note {
  margin: 16px 0 0;
  font-size: 15px;              /* spec 3.2 --t-ui: reassurance microcopy */
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: 0;            /* 13-16px band, spec 3.3 rule 1 */
  color: var(--ink-72);         /* 7.5:1 - subordinate to the CTA above it,
                                   which is the one sanctioned use of -72.
                                   This is UI microcopy, not body copy, so it
                                   is not bound by the full-ink body rule. */
}
/* Each claim is one unbreakable unit, so the line can only ever break at a
   separator - never "delivered within / 48 hours". */
.closing__note span { white-space: nowrap; }

@media (max-width: 1023px) {
  .closing__cta { margin-top: 24px; }
}

@media (max-width: 767px) {
  /* Spec 9: below 768 CTAs are full width. There is only one here, so there
     is nothing to stack and nothing to space. */
  .closing__cta  { display: flex; width: 100%; margin-top: 16px; }
  .closing__note { margin-top: 14px; }
}


/* ==========================================================================
   13.1  FOOTER - spec 6 row 13, RESTYLE ONLY

   .site-footer itself is untouched: its --surface fill, its 1px --rule top
   border (which is also the hairline separating it from the closing band,
   both being on --surface per spec 5.1) and its 48 / 40 padding all come
   from css/style.css section 7, as does the shared `.site-footer a` link
   treatment. What is added here is arrangement only.

   NOTE FOR WHOEVER MERGES THIS: `.site-footer .cols` and `.site-footer .legal`
   in css/style.css become dead selectors once index.html adopts this markup.
   They are not overridden here - they simply stop matching. Delete them when
   the last page stops using them.
   -------------------------------------------------------------------------- */

.footer-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-6);
  padding-bottom: var(--s-6);
  margin-bottom: var(--s-7);
  border-bottom: 1px solid var(--rule);
}

/* `.site-footer a` (0,2,0) would otherwise repaint the wordmark as a 15px/500
   --ink-72 nav link. This restores .logo's own values at equal specificity,
   later in the cascade. It is not a new component - it is the existing .logo. */
.site-footer .footer-brand {
  padding: 8px 0;               /* 28px glyph box + 16 = the 44px floor */
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.011em;
  color: var(--ink);
}

/* Hit box (checklist 21). `.site-footer a` already supplies 12px 0 of vertical
   padding, which clears 44px in height; "FAQ" is the only label narrow enough
   to miss 44px in width. */
.site-footer .footer-nav a { min-width: 44px; }

/* Two columns of exactly 6 grid columns: (1128 - 24) / 2 = 552 = --measure-body.
   The single 820px measure this replaces ran to ~108 characters a line. */
.footer-legal {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gutter);
  font-size: 13.5px;
  line-height: 1.7;
  letter-spacing: 0;
  color: var(--ink-62);         /* metadata / legal only - never an action */
}
.footer-legal p {
  margin: 0;
  max-width: var(--measure-body);
  text-wrap: pretty;
}

.footer-copy {
  margin: var(--s-7) 0 0;
  font-size: 13.5px;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--ink-62);
  font-variant-numeric: tabular-nums;   /* the year is a figure */
}

@media (max-width: 767px) {
  .footer-top {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-2);
    padding-bottom: var(--s-4);
    margin-bottom: var(--s-6);
  }
  .footer-legal { grid-template-columns: minmax(0, 1fr); gap: var(--s-4); }
  .footer-copy  { margin-top: var(--s-6); }
}


/* ==========================================================================
   C.1  STICKY MOBILE BUY BAR - spec 6 final row, spec 8.2 item 9

   The bar itself is already fully specified in css/style.css:1161-1176:
   fixed, 60px, top hairline, display:none above 899px, translateY(100% -> 0)
   on .is-visible at --t-state / --ease-out-quart. None of that is restated
   here and none of it may be changed here. What follows is its contents.
   -------------------------------------------------------------------------- */

.buy-bar__price {
  flex: 0 1 auto;
  min-width: 0;
  font-size: 17px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.011em;     /* 17-27px band, spec 3.3 rule 1 */
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* The button never shrinks: a shrunk .btn would spill its nowrap label past
   its own 8px box. At 320px the row measures 40 + 34 + 16 + ~218 = ~308. */
.buy-bar .btn { flex: 0 0 auto; }

/* Michael's call, 8 Aug 2026 (spec 13.9): THE ACCENT NEVER FILLS A BUTTON, so
   the bar's CTA is black like every other buy target. It needs no override at
   all now beyond inheriting .btn--primary - the rule is kept only so the
   intent stays documented and the hover matches. What distinguishes the buy
   path is the price in its own label, which this button carries. The bar
   exists only below 900px, where the hero's buy CTA is far off-screen, so
   nothing on screen competes with it. Specificity 0,2,0 so it lands after
   .btn--primary regardless of source order.

   THIS FRAGMENT WAS STALE. css/style.css had already been hand-corrected to
   13.9 while this file still carried the v1.0 accent fill, so re-running the
   assembler silently reverted the homepage and broke audit item 42 at 390px.
   If you correct a value in css/style.css between the SECTION-CSS markers,
   correct it in the matching _design/sections/*.css fragment in the same
   commit - the fragment is the source and the stylesheet is the build. */
@media (max-width: 899px) {
  /* The buy colour (spec 13.10), same as .btn--money and the header CTA. */
  .buy-bar .buy-bar__cta                { background: var(--buy); color: #fff; }
  .buy-bar .buy-bar__cta:hover          { background: var(--buy-hover); }
  .buy-bar .buy-bar__cta:active         { background: var(--buy-press); }

  /* Clearance so the 60px bar can never sit on top of the copyright line.
     40 (the footer's own bottom padding) + 60 (the bar). Scoped to .js
     because without JavaScript the bar never appears and the space would be
     dead. */
  .js .site-footer { padding-bottom: 100px; }
}

/* ======================= guide.css ======================= */
/* =============================================================================
   GUIDE PAGES - THE ARTICLE DESIGN SYSTEM
   OffersOver - implements DESIGN-SPEC.md (LOCKED) for the nine guide pages.

   Reference implementation: /what-does-offers-over-mean/index.html, the pillar
   guide. The other eight guides are rebuilt against it and against this file.
   Nine of the thirteen indexable pages use this, and they are the pages
   organic search actually lands on, so this file matters commercially more
   than the homepage does.

   Brief, in one line: a guide must read as a well-set journal that happens to
   have one shop window - NOT as a narrower copy of the homepage.

   ---------------------------------------------------------------------------
   SCOPE OF THIS FILE - read before adding anything to it
   ---------------------------------------------------------------------------
   Article furniture and layout only. Every token, button, table, accordion,
   panel, field, ruled list and type step used by a guide already exists in
   css/style.css and is used unchanged. Nothing below redefines a token, a
   colour, a radius, a shadow, a curve or a type step.

   ALREADY PROVIDED BY THE SYSTEM - do not restate it here:
     section padding 88/128, 64/96, 48/72          style.css section 6
     .h2 / .h3 / .h4 / .lede / .body / .ui / .meta style.css section 3
     .eyebrow, .dim, .tabular                      style.css section 3
     .btn and every variant, incl. the pill radius style.css 5.1
     .field / label / select and the chevron       style.css 5.2
     .panel, .card                                 style.css 5.3
     .table-scroll                                 style.css 5.6
     .accordion, summary, the "+" marker           style.css 5.9
     .ruled-list rules and 18px/16px row padding   style.css 5.10
     .gap-* rhythm helpers                         style.css section 6
     .artefact and its three variants              style.css section 11

   ---------------------------------------------------------------------------
   THE SIX RULES A GUIDE BUILDER MUST FOLLOW
   ---------------------------------------------------------------------------
   1  HEADINGS. Section headings are <h2 class="h3">, sub-heads <h3 class="h4">.
      The LEVEL is semantic, the SIZE comes from the class (spec section 10:
      "a level is never chosen for its size"). The rules below supply only
      STRUCTURE - the rule, the counter, the margins, the measure - and
      deliberately do not set font-size on a heading that carries a class.
      Bare <h2> / <h3> keep a :not([class]) fallback purely so a guide that has
      not been rebuilt yet still renders; do not write new markup against it.

   2  TRACKING. Every element that renders below 17px must set its own
      letter-spacing. <body> ships -0.011em and everything inherits it, so a
      13.5px or 15px element that does not restate `letter-spacing: 0` is off
      the spec 3.3 formula. That single omission accounted for all 27 tracking
      failures the audit found on the pillar guide before this rewrite. The
      cheapest way to comply is to use the system's own .meta / .ui / .micro
      classes, which already carry the right value for their size.

   3  RHYTHM. A <section> that is a direct child of <main> takes the rhythm
      table's padding from the bare `section` rule in css/style.css section 6:
      88/128 at >=1024, 64/96 at 768-1023, 48/72 below 768. NEVER give a guide
      <section> its own padding. The masthead is a <header>, and the article is
      an <article>, so neither is measured against that table and both keep the
      tighter editorial padding set here.

   4  BRAWLER IS CAPPED AT SIX rendered elements per page (checklist 6), none
      below 22px. A guide spends them on: the H1 (1), up to two
      .guide-stat__val figures (2), and the area-check result headline and its
      <strong> (2). That is five, and it is the budget. Note in particular that
      the table's figure column is INTER, not Brawler: eight serif cells at
      17px would break the cap and the 22px floor in one move.

   5  PERCENTAGES. Folklore is set in WORDS ("ten to twenty per cent over");
      published data is set in DIGITS with its publisher and period beside it.
      Digits signal a measurement, words signal hearsay. Never invent a figure:
      the real ones live in js/areas.js.

   6  COMPLIANCE. A guide may never state or imply a recommended bid, an offer
      figure or a valuation. The refusal to give one is load-bearing trust copy
      and is repeated in the conversion block. No testimonials, no third-party
      logos, no strikethrough prices; the price rise is always framed as a real
      dated future rise.

   This file is ASCII-only on purpose: PowerShell 5.1 mangles UTF-8
   punctuation, and CSS never needs a glyph the markup cannot carry.
   ========================================================================= */


/* --------------------------------------------------------------------------
   G1. The measure, and the breakout
   --------------------------------------------------------------------------
   Body copy sits on a 648px column - --measure-lede, seven grid columns, and
   the hard ceiling checklist 7 measures ("no body paragraph's measure exceeds
   648px"). At 17px that is about 67 characters, inside the 66-72 the interior
   review asked for and far tighter than the ~85 the old pages ran.

   The column is LEFT-anchored inside the content grid rather than centred in
   the viewport, so the page has a spine. Centring a strip in a void was the
   single biggest reason the old guides read as a docs template.

   Tables, panels and figures BREAK OUT wider than the prose. That is the
   cheapest rhythm available: it costs no new content and it gives the eye a
   horizon line every few screens.
   -------------------------------------------------------------------------- */
.guide { --measure: 648px; --rail: 264px; }

/* --------------------------------------------------------------------------
   G1a. The two-track layout: prose + a sticky rail
   --------------------------------------------------------------------------
   At >=1024 the article is a two-column grid - an evidence track on the left
   and a 264px navigation rail on the right. Before this, the article set at
   648px inside an 1128px container and roughly 480px of every screen was
   empty for the whole 11,700px of the page, which read as under-designed
   against the homepage.

   THE PROSE MEASURE DOES NOT MOVE. The left column is ~840px at 1440 and the
   running copy is still capped at 648 (checklist 7). What the extra width buys
   is the BREAKOUT: tables, stat pulls, the worked example and the area check
   fill the whole 840px track, so the page reads as narrow prose punctuated by
   wide evidence rather than as one column at one width.

   THREE GRID CHILDREN, and the order is the reason it is three rather than
   two: intro (lede + the short version), rail, body. Below 1024 the grid
   collapses to one column and they stack in DOM order - lede, then the jump
   list, then the article - which is exactly where the jump list belongs on a
   phone. With only two children the rail would have had to sit either above
   the lede or below the entire article.

   The rail is `position: sticky`, CSS only. Spec 8.2 row 11 does exactly this
   for the homepage's evidence panel: no ScrollTrigger, no GSAP pin, no scroll
   handler. `align-self: start` is what stops the item stretching to fill its
   grid area and killing the effect.
   -------------------------------------------------------------------------- */
/* minmax(0, 1fr) is NOT decoration. An implicit grid column is `auto`, which
   is floored at min-content, so the wide comparables table inside
   .table-scroll pushed the single-column layout out to 446px at a 320px
   viewport and the whole page scrolled sideways - a hard fail under spec 9.
   The explicit minmax(0, 1fr) lets the track shrink and hands the overflow
   back to .table-scroll, where it belongs. */
.guide__layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}
.guide__layout > * { min-width: 0; }

/* THE SECOND TRACK ONLY EXISTS IF SOMETHING IS IN IT.
   Gated on :has(> .guide-rail) so a short guide that omits the rail gets one
   full-width track instead of 264px of dead column - and its breakouts then
   fill the whole 1128px content width, which is what keeps a three-screen
   guide from reading as a 648px strip. See the TOC rule in G4.
   The degradation is benign in a browser without :has(): the whole block is
   dropped, the layout stays single-column and the rail renders where it does
   below 1024, between the lede and the article. */
@media (min-width: 1024px) {
  .guide__layout:has(> .guide-rail) {
    grid-template-columns: minmax(0, 1fr) var(--rail);
    column-gap: var(--gutter);
  }
  .guide__body--intro { grid-column: 1; grid-row: 1; }
  .guide__body--main  { grid-column: 1; grid-row: 2; }
  .guide-rail {
    grid-column: 2;
    grid-row: 1 / span 2;
    align-self: start;
    position: sticky;
    /* Clears the 64px sticky header plus one 24px step. */
    top: calc(var(--header-h) + var(--s-6));
    /* On a short window the rail scrolls inside itself rather than running
       its last items off the bottom of the viewport where they can never be
       reached while it is stuck. */
    max-height: calc(100vh - var(--header-h) - var(--s-10));
    overflow-y: auto;
  }
}
@media (max-width: 1023px) {
  /* The rail is content again: it flows between the intro and the article. */
  .guide-rail { margin-bottom: 40px; }
}

/* .guide__body is the TRACK, not the measure: the 648px cap lives on its
   children, so a breakout child can use the whole track without any
   viewport-width arithmetic. The old rule capped the block itself at 648 and
   let breakouts escape with `width: calc(100vw - 2 * var(--gutter))`, which is
   what ran the area-check panel 100px past the right edge of a 1440 viewport
   and which would now collide with the rail. */
.guide__body { max-width: none; }
.guide__body > * { max-width: var(--measure); }
.guide__body > .breakout { max-width: 100%; }


/* --------------------------------------------------------------------------
   G2. The masthead band
   --------------------------------------------------------------------------
   A closed ivory band, so the article visibly starts. The old pages ran the
   header straight into the body on unbroken white and the page never began.
   It is a <header>, not a <section>: see rule 3 above.
   -------------------------------------------------------------------------- */
/* The third gradient (spec 13.11), on every guide masthead. 924 is the softest
   of the set, so the standfirst and meta line stay comfortably readable over
   it. Anchored right so the H1's left edge sits on the palest part. */
.guide-masthead {
  background-color: var(--surface);
  background-image:
    image-set(url("/img/bg/masthead-1280.avif") type("image/avif"),
              url("/img/bg/masthead-1280.webp") type("image/webp"),
              url("/img/bg/masthead-1280.jpg")  type("image/jpeg"));
  background-size: cover;
  background-position: 100% 50%;
  background-repeat: no-repeat;
  border-bottom: 1px solid var(--rule);
  padding: 72px 0 56px;
}
@media (min-width: 1281px) {
  .guide-masthead {
    background-image:
      image-set(url("/img/bg/masthead-1920.avif") type("image/avif"),
                url("/img/bg/masthead-1920.webp") type("image/webp"),
                url("/img/bg/masthead-1920.jpg")  type("image/jpeg"));
  }
}
@media (max-width: 640px) {
  .guide-masthead {
    background-image:
      image-set(url("/img/bg/masthead-640.avif") type("image/avif"),
                url("/img/bg/masthead-640.webp") type("image/webp"),
                url("/img/bg/masthead-640.jpg")  type("image/jpeg"));
  }
}
@media (max-width: 1023px) { .guide-masthead { padding: 56px 0 40px; } }
@media (max-width:  767px) { .guide-masthead { padding: 40px 0 32px; } }

/* Wider than the prose measure, because display type needs it. */
.guide-masthead__inner { max-width: 780px; }
.guide-masthead .eyebrow { display: block; margin-bottom: 14px; }
.guide-masthead h1 { margin: 0; }

/* The standfirst is the system .lede (19px/1.55, --ink, --measure-lede). This
   class adds the gap and nothing else - full-strength ink, because spec 3.3
   reserves --ink-72 for a description subordinate to a bold label, and a
   standfirst is not one. */
.guide-masthead__standfirst { margin: 20px 0 0; }

/* The meta row: the "last updated" line the brief requires, plus reading time
   and the article's place in the cluster. All three are facts, so they are
   metadata colour and metadata size (13.5px, letter-spacing 0, --ink-62).
   THE SEPARATOR TRAILS ITS OWN ITEM. Written as `li + li::before` the dot
   belongs to the item that FOLLOWS it, so when the row wraps at 390px the new
   line opens with an orphaned "." A separator may never start a line. As an
   ::after inside the preceding <li> it travels with the item it follows and
   the wrap point is always clean. */
.guide-masthead__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 8px;
  margin: 24px 0 0;
  padding: 0;
  list-style: none;
  font-size: 13.5px;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--ink-62);
}
.guide-masthead__meta li { margin: 0; }
.guide-masthead__meta li:not(:last-child)::after {
  content: "\00B7";
  margin-left: 8px;
  color: var(--rule-strong);
}


/* --------------------------------------------------------------------------
   G3. Article body rhythm
   -------------------------------------------------------------------------- */
/* The bottom padding is a tail, not a rhythm value: the article's last painted
   element is usually a ruled component (the FAQ accordion, a table, a stat
   block) and the conversion band that follows opens with a hairline of its
   own. At 0 the two hairlines sat 8px apart and read as a rendering fault.
   56 + the band's own 88 gives a clean 144px break at desktop. */
.guide-body { padding: 64px 0 56px; }
@media (max-width: 1023px) { .guide-body { padding: 56px 0 48px; } }
@media (max-width:  767px) { .guide-body { padding: 40px 0 40px; } }

/* Running copy: --t-body verbatim. 17px / 1.65 / -0.011em, full-strength
   --ink. Body copy is NEVER grey (spec 3.3).

   :where() IS LOAD-BEARING, NOT A FLOURISH. Written as `.guide__body p` this
   rule is (0,1,1) and it silently outranks every single-class helper in the
   design system: .lede rendered at 17px instead of 19, .meta at 17px instead
   of 13.5, and every .gap-* margin was thrown away inside an article.
   :where() drops the scope to zero, so the selector is (0,0,1) - a bare
   element - and any class on the tag wins, which is what a builder expects.
   Do not "tidy" the :where() away.

   Note this also gives FAQ answers full-strength --ink rather than spec 4.9's
   --ink-72: inside an article the accordion body IS running body copy, and
   spec 3.3's "body copy is never grey" governs. */
:where(.guide__body) p,
:where(.guide__body) li {
  font-size: 17px;
  line-height: 1.65;
  letter-spacing: -0.011em;
  color: var(--ink);
  text-wrap: pretty;
}
:where(.guide__body) p { margin: 0 0 20px; }

/* The opening lede: one step up, and the only paragraph allowed to be. It
   carries the system .lede class; this adds the gap only. */
.guide__body > .lede { margin: 0 0 32px; }

/* H2 STRUCTURE ONLY - see rule 1. A hairline, a section numeral and the
   margins. The numeral is the navigation aid the old pages lacked entirely: a
   scanner flicking the scrollbar could not tell one section from another, and
   could not tell how far through they were. It also earns SERP sitelinks
   alongside the jump list in G4.
   No font-size and no letter-spacing here: those come from the .h3 class on
   the tag, so the heading stays on the spec 3.3 formula at all four widths. */
.guide__body h2 {
  counter-increment: guide-section;
  position: relative;
  max-width: var(--measure);
  margin: 64px 0 20px;
  padding-top: 24px;
  border-top: 1px solid var(--rule);
  text-wrap: balance;
}
.guide__body h2::before {
  content: counter(guide-section, decimal-leading-zero);
  display: block;
  margin-bottom: 10px;
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.guide { counter-reset: guide-section; }

.guide__body h3 {
  max-width: var(--measure);
  margin: 36px 0 12px;
  text-wrap: balance;
}

/* LEGACY FALLBACK ONLY (rule 1). A guide that has not been rebuilt yet still
   writes bare <h2> / <h3>; without this they would fall back to the UA's
   1.5em / 1.17em and land off the type ladder. New markup must carry .h3 and
   .h4 instead, and these two selectors go away when the last guide is done. */
.guide__body h2:not([class]) {
  font-family: var(--font-sans);
  font-size: clamp(22px, 2.4vw, 28px);
  font-weight: 600;
  line-height: 1.20;
  letter-spacing: -0.011em;
  color: var(--ink);
}
.guide__body h3:not([class]) {
  font-family: var(--font-sans);
  font-size: 20px;
  font-weight: 600;
  line-height: 1.30;
  letter-spacing: -0.011em;
  color: var(--ink);
}
@media (min-width: 1280px) {
  .guide__body h2:not([class]) { letter-spacing: -0.02em; }
}

/* Lists. The bold lead-in habit in the copy is good and is kept. The markers
   become accent rules and accent numerals rather than browser discs - the one
   place the accent is spent inside running prose.

   DIRECT CHILDREN ONLY (`>`), and that is not a style choice either. As a
   descendant selector these rules reached into every nested list on the page:
   the jump list's <ol> was renumbered off the article's own counter and lost
   its leading zeros, and the reassurance tick list grew an accent dash beside
   every tick. A prose list is always a direct child of .guide__body; a
   component's list never is. */
.guide__body > ul,
.guide__body > ol { margin: 0 0 24px; padding: 0; list-style: none; }
.guide__body > ul > li,
.guide__body > ol > li { position: relative; padding-left: 26px; margin-bottom: 12px; }
.guide__body > ul > li::before {
  content: "";
  position: absolute;
  left: 0; top: 11px;
  width: 10px; height: 1px;
  background: var(--accent);
}
.guide__body > ol { counter-reset: guide-item; }
.guide__body > ol > li { counter-increment: guide-item; }
.guide__body > ol > li::before {
  content: counter(guide-item);
  position: absolute;
  left: 0; top: 2px;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

/* Links inside running prose stay accent-coloured and underline on hover; the
   base rule in style.css section 2 already does that. They are exempt from the
   44px target floor by WCAG 2.5.8's inline exception, which is why they may
   sit in a 17px line without wrecking its leading. */


/* --------------------------------------------------------------------------
   G4. "In this guide" - the jump list
   --------------------------------------------------------------------------
   THE RULE FOR THE OTHER EIGHT GUIDES - count, do not judge:

     6 or more <h2> sections  ->  build the rail. Wrap the article in
                                  .guide__layout with .guide__body--intro,
                                  <aside class="guide-rail"> and
                                  .guide__body--main, exactly as the pillar
                                  guide does.
     5 or fewer               ->  NO rail and NO .guide-toc. Omit the <aside>
                                  entirely; the layout collapses to one track
                                  on its own (see G1a) and .breakout then fills
                                  the whole 1128px content width, which is what
                                  carries the rhythm on a short page.

   Six is where a jump list stops being navigation and starts being furniture:
   below it the whole article is three or four screens, the reader can see its
   shape by scrolling, and a sticky list of four links is a permanent 264px
   advertisement for how little there is to read. The cluster guides run
   600-860 words and most of them will land on the second branch. Count the
   <h2>s in the finished page, not the words.

   Two jobs where it IS built: it tells a scanner the shape of the page before
   they commit, and it is what earns sitelinks in the SERP. Pure markup; the
   only JavaScript is the aria-current pass in js/main.js.

   TWO MODES, ONE MARKUP.
     >=1024  the sticky rail (G1a). One column, no fill, a continuous 2px
             --rule track down the left edge with the current section's segment
             in --accent.
     <1024   a two-column ivory card sitting between the lede and the article,
             which is where a jump list belongs on a phone. One column below
             640px.

   THE CURRENT-SECTION MARKER IS A BORDER, NOT A BOX-SHADOW. Spec 4.5 marks the
   active tab with `inset 0 -2px 0 var(--accent)`, and this is that device
   rotated - a selection-state indicator on a navigation item, not a decorative
   bar on a card. It has to be a border because checklist 19 allows a
   box-shadow on nothing except the Artefact. Every item carries the same 2px
   border at all times, so marking one shifts no geometry.

   Each link is a real 44px target (checklist 21): a jump list is standalone
   navigation, not prose, so WCAG 2.5.8's inline exception does not apply.
   -------------------------------------------------------------------------- */
.guide-toc {
  margin: 0;
  padding: 20px 24px 22px;
  background: var(--surface);
  border-radius: var(--r-md);
}
.guide-toc__label {
  display: block;
  margin-bottom: 8px;
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink-62);
}
.guide-toc__list {
  counter-reset: toc-item;
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--gutter);
}
@media (max-width: 639px) { .guide-toc__list { grid-template-columns: minmax(0, 1fr); } }
.guide-toc__list li { counter-increment: toc-item; margin: 0; }

/* The numeral is the link's own ::before rather than the row's, so it can
   change with the link's state without needing :has(). */
.guide-toc a {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  min-width: 44px;
  padding: 11px 0 11px 12px;
  border-left: 2px solid transparent;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: 0;
  color: var(--ink);
  transition: color var(--t-hover) linear, border-color var(--t-hover) linear;
}
.guide-toc a::before {
  content: counter(toc-item, decimal-leading-zero);
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: 0.02em;
  color: var(--ink-62);
  font-variant-numeric: tabular-nums;
}
.guide-toc a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
.guide-toc a[aria-current="location"] { border-left-color: var(--accent); }
.guide-toc a[aria-current="location"]::before { color: var(--accent); }

@media (min-width: 1024px) {
  /* Rail mode: the card furniture comes off and the list becomes the track. */
  .guide-toc { padding: 0; background: none; border-radius: 0; }
  .guide-toc__label { margin-bottom: 12px; padding-left: 14px; }
  .guide-toc__list { grid-template-columns: minmax(0, 1fr); column-gap: 0; }
  /* Consecutive links touch, so the inactive borders form one continuous
     hairline down the rail and the accent segment reads as a position. */
  .guide-toc a { border-left-color: var(--rule); }
  .guide-toc a[aria-current="location"] { border-left-color: var(--accent); }
}


/* --------------------------------------------------------------------------
   G5. Two callout devices, deliberately unalike
   --------------------------------------------------------------------------
   The old pages styled the editorial TL;DR and the sales widget as the SAME
   grey slab, so the reader could not tell teaching from selling. They are now
   different objects, and that difference is the point: this site's entire
   argument is that it is not quietly selling you something.
   -------------------------------------------------------------------------- */

/* G5a. Editorial note. Ivory fill, caps accent label. Used for "the short
   version", for a worked example, for anything that is teaching. May appear
   more than once; THE LABEL is what distinguishes the instances, and the label
   is also the whole accent budget for this component.

   NO ACCENT BAR DOWN THE LEFT EDGE, and no asymmetric radius. This component
   first shipped with a 2px sandstone rule on its leading edge and a radius
   flattened on that side. Both were wrong twice over:
     - Spec 2 restricts the accent to links, ticks, focus rings, the quote mark
       and chart fills. A decorative edge rule is on none of those lists.
       Spec 4.3's row for a card on a white band is a --surface fill with NO
       border at all.
     - The side-tab - a thick coloured bar on one edge of a filled card - is
       the single most recognisable tell of a generated interface, which is
       precisely the signal this page exists not to send.
   The --surface fill already separates the callout from the prose and the
   accent caps label already names it, so the bar bought nothing.

   (Described in prose deliberately. Quoting the offending declaration verbatim
   made the design linter flag this comment as the very pattern it documents,
   on every edit to this file. The reasoning matters more than the snippet.) */
.guide-note {
  margin: 0 0 32px;
  padding: 20px 24px 22px;
  background: var(--surface);
  border-radius: var(--r-md);
}
.guide-note__label {
  display: block;
  margin-bottom: 8px;
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--accent);
}
.guide-note p:last-child { margin-bottom: 0; }
/* A callout's copy is still copy: 648px is the ceiling checklist 7 measures,
   and it applies inside a box exactly as it does in the prose column. Without
   this a .breakout note ran its lines to 679px. */
.guide-note p,
.guide-aside p { max-width: var(--measure-lede); }

/* A callout that BREAKS OUT has to earn the width rather than just running
   wider lines. The label moves into a column of its own and the copy keeps its
   measure, so the extra track becomes structure instead of slack - the same
   label-beside-content move the homepage's ruled rows make. */
@media (min-width: 768px) {
  .guide-note.breakout {
    display: grid;
    grid-template-columns: 144px minmax(0, 1fr);
    column-gap: var(--gutter);
  }
  .guide-note.breakout .guide-note__label { margin-bottom: 0; padding-top: 5px; }
}

/* G5b. Product aside. A bordered card on white, never a filled slab, so it
   reads as an aside rather than as the page talking to itself. Max ONE per
   article, and it never carries the only buy path. */
.guide-aside {
  margin: 32px 0;
  padding: 20px 24px 22px;
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
}
.guide-aside__label {
  display: block;
  margin-bottom: 8px;
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink-62);
}
.guide-aside p { margin-bottom: 14px; }
.guide-aside p:last-child { margin-bottom: 0; }


/* --------------------------------------------------------------------------
   G6. Stat pull
   --------------------------------------------------------------------------
   Lifts an ALREADY-PUBLISHED figure out of prose into the homepage's serif
   numeral, with its publisher and period beneath it. Same data, same citation,
   same device as the homepage's ruled figure stack - this is reuse, not new
   content. It is the main source of visual relief across a long scroll.

   MAXIMUM TWO PER GUIDE (rule 4). The figure is one of the six Brawler
   elements a page is allowed, and it may only ever carry a figure that appears
   in js/areas.js or in a published report named in the source line.

   THE MARKUP IS THE HOMEPAGE'S, VERBATIM - three system classes and three
   system gap helpers, so this block introduces no new type step at all:

     <div class="guide-stat">
       <span class="num guide-stat__val">101.5%</span>
       <p class="body gap-figure-claim">the claim</p>
       <p class="meta gap-claim-source">publisher, period</p>
     </div>

   Use those exact classes. .body and .meta are what keep the claim inside the
   body-copy population and the source line outside it; a bespoke 17px/1.55
   paragraph here failed checklist 7 on leading, and a bespoke 13.5px one
   failed it on size.
   -------------------------------------------------------------------------- */
.guide-stat {
  margin: 36px 0;
  padding: 24px 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
/* The figure is the system .num (Brawler 400, clamp 40-56, tabular). All this
   adds is the colour: the published figure is the most load-bearing content in
   the block, so it takes the accent and its claim and source stay ink and
   ink-62. 6.3:1 on #fff. */
.guide-stat__val { color: var(--accent); }
/* 10px figure -> claim and 10px claim -> source come from the .gap-* helpers
   in the markup, so the base <p> bottom margin has to go or the source line
   drifts past the 24px checklist 34 allows. Same move as .problem__figure p
   on the homepage. */
.guide-stat p { margin-bottom: 0; }


/* --------------------------------------------------------------------------
   G7. Tables
   --------------------------------------------------------------------------
   Spec 4.6 verbatim, plus one column of its own: the figure the table exists
   to show. It was buried mid-sentence in the old markup.

   THE FIGURE COLUMN IS INTER, NOT BRAWLER. It was serif until this rewrite,
   which put eight 17px Brawler cells on the page and broke both halves of
   checklist 6 (six elements, none under 22px) on every guide at once. Weight
   500 and tabular numerals do the same job at no cost.

   Every table must sit inside <div class="table-scroll"> (checklist 23): the
   page body may never scroll sideways.
   -------------------------------------------------------------------------- */
.guide-table { margin: 32px 0 24px; }
.guide-table table { width: 100%; border-collapse: collapse; }
.guide-table th {
  font-family: var(--font-sans);
  font-size: 12px; font-weight: 500; line-height: 1.45;
  text-transform: uppercase; letter-spacing: 0.02em;
  color: var(--ink-62); text-align: left;
  padding: 10px 12px; border-bottom: 1px solid var(--rule-strong);
  white-space: nowrap;
}
.guide-table td {
  font-family: var(--font-sans);
  font-size: 15px; font-weight: 400; line-height: 1.5; letter-spacing: 0;
  color: var(--ink); padding: 12px;
  border-bottom: 1px solid var(--rule);
}
.guide-table th.figure,
.guide-table td.figure {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.guide-table td.figure { font-weight: 500; }
/* The source column is metadata, and it is the spec's own table-source
   treatment (4.6: 13.5px --ink-62) moved inline so every publisher and period
   sits in the same row as the figure it belongs to - within the 24px
   checklist 34 asks for, horizontally rather than vertically. nowrap only
   where there is room for it: squeezed, "ESPC, Feb-Apr 2026" broke after the
   month range and read as two different citations. */
.guide-table td.source {
  font-size: 13.5px;
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--ink-62);
}
@media (min-width: 1280px) {
  .guide-table td.source { white-space: nowrap; }
}
/* "no series" is not a value, and it must not be typeset as one. Half of
   Scotland has no published matched sold-vs-valuation series at all, so this
   cell appears on several guides: it drops to metadata colour and regular
   weight so a reader scanning the column cannot mistake an absence for a
   measurement. 5.2:1 on #fff. */
.guide-table td.figure.is-none { color: var(--ink-62); font-weight: 400; }
/* The rows where the published answer is "below valuation". Colour is never
   the sole signal - the figure itself says 98.5%, and the note column says so
   in words - but the accent is the one thing on the page that means "this is
   the number that matters". 6.3:1 on #fff. */
.guide-table tr[data-standout] td.figure { color: var(--accent); }
/* On a phone the table still scrolls inside .table-scroll rather than
   reflowing - a source line has to stay beside its figure (checklist 34), so
   the columns cannot be dropped. What can go is the nowrap on the header row
   and half the cell padding, which is worth roughly a third of the table's
   width and turns a four-screen scroll into most of one. */
@media (max-width: 767px) {
  .guide-table th { white-space: normal; }
  .guide-table th,
  .guide-table td { padding: 10px 8px; }
}
.guide-table caption {
  caption-side: bottom;
  text-align: left;
  padding-top: 10px;
  font-size: 13.5px; line-height: 1.5; letter-spacing: 0;
  color: var(--ink-62);
}


/* --------------------------------------------------------------------------
   G8. The in-article area check
   --------------------------------------------------------------------------
   The free, sourced, no-email instrument, dropped at the exact point in the
   article where every competing page-one result punts to "ask a solicitor".
   It is the article's one shop window and it is placed on editorial grounds:
   the reader has just been shown that no single rule of thumb survives contact
   with the published data, so the next thing they meet is the published data
   for their own area.

   THE .lookup CLASS IS A JAVASCRIPT HOOK, NOT A STYLE. js/main.js binds to
   .lookup and writes into .lookup-result's .headline / .detail / .meta. The
   legacy rules for it in css/style.css section 10.7 still style it as a
   centred 720px card, so the four declarations below undo exactly those and
   nothing else - the same move s3-s4.css makes for the homepage panel.

   THE RESULT BOX IS PRE-RENDERED IN THE MARKUP and is never hidden. Checklist
   38/39: with JavaScript disabled the site's best demo must still be visible.
   -------------------------------------------------------------------------- */
/* The panel is a .breakout: it fills the evidence track (about 840px beside
   the rail at 1440) rather than the 648px prose measure. The max-width here
   only undoes legacy 10.7's centred 720px card - the real cap is the track.
   ALWAYS give this panel the .breakout class; without it, 100% resolves
   against a 648px child slot and the two columns inside it collapse. */
.guide-lookup.lookup {
  max-width: 100%;
  margin: 40px 0;
  padding: var(--s-7);
  text-align: left;
}
@media (max-width: 767px) { .guide-lookup.lookup { padding: var(--s-5); } }

.guide-lookup__cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 300px);
  gap: var(--s-8);
  align-items: start;
}
@media (max-width: 767px) {
  .guide-lookup__cols { grid-template-columns: minmax(0, 1fr); gap: var(--s-7); }
}

/* Legacy 10.7 sets `.lookup select { width:auto; flex: 1 1 260px }` for a flex
   row this panel does not use. Put the system's own full width back. */
.guide-lookup.lookup select { width: 100%; }

/* .field already carries its own 16px below itself (spec 5.2). */
.guide-lookup .guide-lookup__update { margin: 0; }
.guide-lookup noscript p { margin: var(--s-3) 0 0; }

/* Legacy 10.7 puts 18px under .lookup-result .meta; the source line is the
   last thing in the box and needs nothing under it. */
.guide-lookup .lookup-result { margin-top: var(--s-5); }
.guide-lookup .lookup-result .meta { margin-bottom: 0; }
.guide-lookup .lookup-result .detail { margin: 10px 0; }
.guide-lookup .lookup-result .headline { margin: 0; }

/* THE RESULT HEADLINE IS ONE OF THE PAGE'S SIX BRAWLER ELEMENTS, so it has to
   clear the 22px floor checklist 6 sets. Restated here at (0,3,0) because
   `.guide__body p` is (0,1,1) and outranks both the .h3 and the legacy
   .headline class the markup carries - which pinned it at the 17px body step
   and put two sub-22px serif nodes on the page. Same clamp and the same band
   correction as .h3, so it stays on the spec 3.3 formula at all four widths. */
.guide-lookup .lookup-result .headline {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(22px, 2.4vw, 28px);
  line-height: 1.20;
  letter-spacing: -0.011em;
  color: var(--ink);
  text-wrap: balance;
}
@media (min-width: 1280px) {
  .guide-lookup .lookup-result .headline { letter-spacing: -0.02em; }
}
.guide-lookup .lookup-result .headline strong { font-weight: 400; color: var(--accent); }

/* The escalation. A hairline rather than a second box: the panel already is a
   box, and boxing the ask inside it would make the free thing look like bait.
   Above 768 it is a right-hand column; below, it drops beneath the result with
   a top rule, so the free answer always comes first. */
.guide-lookup__ask {
  border-left: 1px solid var(--rule-strong);
  padding-left: var(--s-7);
}
@media (max-width: 767px) {
  .guide-lookup__ask {
    border-left: 0;
    border-top: 1px solid var(--rule-strong);
    padding-left: 0;
    padding-top: var(--s-6);
  }
  .guide-lookup__ask .btn { width: 100%; }
}


/* --------------------------------------------------------------------------
   G9. End-of-article conversion block
   --------------------------------------------------------------------------
   Full-bleed ivory, so the ask is a designed moment rather than a paragraph
   that happens to contain a button. Deliberately NOT the homepage's dark band:
   an article should not change key that hard mid-read, and checklist 14 holds
   a guide to zero dark bands.

   NO PADDING DECLARATION HERE (rule 3). This is a <section> under <main>, so
   the rhythm table's 88/128 - 64/96 - 48/72 comes from css/style.css section 6
   and must not be overridden. It was 64/72 before this rewrite, which is the
   one thing on the page the audit could see from a computed-style dump.

   THE ARTEFACT LIVES HERE, and only here, on a guide. A guide's job is to
   inform first, so the deliverable is not put above the fold; but showing the
   deliverable is the entire conversion strategy, so it has to appear. The
   EVIDENCE variant is the right one: page 2, the comparables table at full
   readable size. The thumb variant is display:none below 480px (spec 9), which
   would leave a phone with no product visible at all.
   -------------------------------------------------------------------------- */
.guide-cta {
  background: var(--surface);
  border-top: 1px solid var(--rule);
}

.guide-cta__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 432px);
  gap: var(--s-10);
  align-items: start;
}
@media (max-width: 1023px) {
  .guide-cta__grid { grid-template-columns: minmax(0, 1fr); gap: var(--s-9); }
}

/* The cluster (spec 5.2): eyebrow -> H2 -> lede -> CTA row -> reassurance ->
   legal. Internal gaps 14 / 20 / 28 / 16 / 32, none over 32, and the band's
   own 88/128 is more than 4x the largest of them. Gaps come from the .gap-*
   helpers in the markup; only the measure is set here. */
.guide-cta__ask > * { max-width: var(--measure-body); }
.guide-cta__ask .btn-row { max-width: none; }

.guide-cta__legal { margin-top: var(--s-7); }
.guide-cta__legal p + p { margin-top: 10px; }

/* LEGACY FALLBACK ONLY (rule 1) - bare <h2> on a guide that has not been
   rebuilt. New markup carries .h2 and this selector never matches it. */
.guide-cta h2:not([class]) {
  margin: 0 0 16px;
  font-family: var(--font-sans);
  font-size: clamp(30px, 4.2vw, 48px);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.02em;
  color: var(--ink);
  text-wrap: balance;
}
@media (min-width: 1280px) { .guide-cta h2:not([class]) { letter-spacing: -0.025em; } }
.guide-cta__inner { max-width: 720px; }
.guide-cta__inner p { margin: 0 0 28px; max-width: var(--measure-body); }
.guide-cta__note {
  margin: 16px 0 0;
  font-size: 15px; font-weight: 500; line-height: 1.45; letter-spacing: 0;
  color: var(--ink-72);
}

/* The evidence column. The caption is what stops the redaction blocks reading
   as a loading state: it names them as the compliance device they are. */
.guide-cta__caption { margin-top: var(--s-3); }

/* At 320px the document's footer stamp - watermark on the left, "Sources
   checked ... Page 2" on the right - is wider than the paper, and
   .artefact__foot is a nowrap flex row, so the date was being severed
   mid-glyph. Spec 13.7 is explicit that a crop may cut paper, a hairline or a
   bar track, and may NEVER cut a numeral. Letting the row wrap costs one line
   of paper and nothing else. Scoped to the guide's artefact slot so the
   homepage's own instances are untouched; the same fix belongs in the shared
   component (css/style.css 11.x) whenever that is next opened. */
@media (max-width: 479px) {
  .guide-cta__evidence .artefact__foot { flex-wrap: wrap; gap: 6em 16em; }
}


/* --------------------------------------------------------------------------
   G9b. .tick-list - the reassurance cluster under a CTA
   --------------------------------------------------------------------------
   The homepage's .price-reassure, generalised. Same geometry, same optical
   alignment, same rules:
     - it is ALWAYS below the button, never above (spec 5.2). Above the button
       a reassurance is a feature; below it, it is permission.
     - the tick is decorative and aria-hidden. The sentence carries the
       meaning, so colour is never the sole signal.
     - it is a <ul> of <li class="ui">, not a <p>. --t-ui (15px/1.45/500) is
       the step spec 3.2 names for "reassurance microcopy", and setting it on a
       <p> would put a non-body paragraph into the body-copy population.
   Optical alignment: the .ui line box is 15 x 1.45 = 21.75px and the tick
   glyph is 18px, so (21.75 - 18) / 2 = 1.9 -> 2.
   -------------------------------------------------------------------------- */
.tick-list {
  margin: var(--s-4) 0 0;          /* 16 - spec 5.2 "CTA -> reassurance" */
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s-2);                 /* 8 */
  max-width: var(--measure-body);
}
.tick-list li { display: flex; align-items: flex-start; gap: var(--s-3); margin: 0; }
.tick-list .tick { flex: 0 0 18px; height: 18px; margin-top: 2px; }
@media (max-width: 767px) { .tick-list { margin-top: 14px; } }


/* --------------------------------------------------------------------------
   G10. .ruled-index - a two-column ruled list of links
   --------------------------------------------------------------------------
   The homepage's guides-index pattern, generalised so any page can use it.
   NOT cards: an index of further reading must never out-weigh the ask above
   it, and the ruled list is the site's de-boxed device (sections 3, 5, 9, 10
   of the homepage all use it).

   Row flow, not column flow, so DOM order and reading order are the same
   object: items 1,2 on row 1, 3,4 on row 2. Grid rows equalise height, so the
   hairline under item 1 lands at exactly the same y as the one under item 2 -
   which is the only reason a two-column ruled list reads as ruled rather than
   as two lists that happen to be adjacent. Use an EVEN number of items so both
   columns end level.

   Row padding is compensated: the row title is a .btn--quiet, which is
   min-height:44px with align-items:center, so its 15px label floats ~14.5px
   below the top of its own box. 4 + 14.5 = 18.5, so the OPTICAL top gap is the
   spec's 18px (16 on mobile: 2 + 14.5). The component itself is untouched -
   its 44px hit box survives intact.
   -------------------------------------------------------------------------- */
.ruled-index {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--gutter);
}
.ruled-list.ruled-index > li { padding: 4px 0 18px; }
/* .ruled-list gives item 1 its top rule; in two columns the first item of the
   SECOND column needs one too, or column 2 starts with an open edge. Doubled
   selector so it outranks .ruled-list > li:first-child whatever the
   concatenation order. */
.ruled-list.ruled-index > li:nth-child(2) { border-top: 1px solid var(--rule); }

/* .btn centres its label and forbids wrapping, which is right for a two-word
   CTA in a button-shaped box and wrong for a 39-character guide title in a
   left-aligned column. Nothing else about the component changes: no colour, no
   size, no weight, no radius, no arrow, no hit box. It also keeps checklist 10
   honest - no text here computes text-align:center. */
.ruled-index .btn--quiet {
  justify-content: flex-start;
  text-align: left;
  white-space: normal;
}
.ruled-index .meta { max-width: var(--measure-body); }

@media (max-width: 767px) {
  .ruled-index { grid-template-columns: minmax(0, 1fr); column-gap: 0; }
  .ruled-list.ruled-index > li:nth-child(2) { border-top: 0; }
  .ruled-list.ruled-index > li { padding: 2px 0 16px; }
}


/* --------------------------------------------------------------------------
   G11. Related reading
   --------------------------------------------------------------------------
   A <section> under <main>, so it takes the rhythm table's padding from the
   system (rule 3) - it had a bespoke 56/72 before this rewrite.
   The list itself is .ruled-list.ruled-index; nothing extra is needed.
   -------------------------------------------------------------------------- */
.guide-related__label {
  display: block; margin-bottom: 20px;
  font-family: var(--font-sans);
  font-size: 12px; font-weight: 500; line-height: 1.45; letter-spacing: 0.02em;
  text-transform: uppercase; color: var(--ink-62);
}
.guide-related__head .dim { display: block; }

/* LEGACY three-up card grid, kept only until the last guide is rebuilt onto
   .ruled-index. Do not write new markup against it. */
.guide-related__grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gutter); }
@media (max-width: 767px) { .guide-related__grid { grid-template-columns: 1fr; gap: 0; } }
.guide-related__item {
  display: block;
  padding: 20px 0;
  border-top: 1px solid var(--rule);
  text-decoration: none;
}
.guide-related__item:hover .guide-related__title { color: var(--accent); }
.guide-related__title {
  display: block; margin-bottom: 6px;
  font-size: 17px; font-weight: 600; line-height: 1.3; letter-spacing: -0.011em;
  color: var(--ink);
  transition: color var(--t-hover) linear;
}
.guide-related__desc { font-size: 13.5px; line-height: 1.5; letter-spacing: 0; color: var(--ink-62); }


/* --------------------------------------------------------------------------
   G12. The FAQ on a guide page
   --------------------------------------------------------------------------
   Native <details> on the system .accordion (spec 4.9). No JavaScript of any
   kind - no toggle handler, no height animation, no ARIA bolted on top. The
   only motion is the "+" rotating to "x", which is CSS and is spec 8.2 row 7.

   THE VISIBLE TEXT AND THE FAQPage JSON-LD MUST MATCH VERBATIM. If a word
   changes in one it changes in the other, in the same commit, or Google drops
   the rich result. That is why an FAQ answer never gets a source line appended
   underneath it inside the answer element.

   The accordion sits at the prose measure, so .accordion__body's own
   --measure-lede cap does nothing harmful.
   -------------------------------------------------------------------------- */
.guide__body .accordion { margin: 0 0 8px; }
.guide__body .accordion summary { text-wrap: balance; }
.guide__body .accordion__body p { margin-bottom: 20px; }

/* ======================= bands.css ======================= */
/* ==========================================================================
   GRADIENT BANDS — generated by _design/bands.mjs. DO NOT EDIT BY HAND.
   Change the list in bands.mjs and re-run it, then merge-css.mjs.

   The rhythm is deliberate: gradient, white break, gradient. No two gradient
   bands ever touch. The single dark band (spine row 7) is untouched — it is
   the page's one change of gear and an image would blunt it.

   Every band pairs its image with a scrim shaped to that section's layout, and
   every one is verified with _design/contrast.mjs, which samples the real
   rendered pixels behind each text block. The main audit reads
   background-COLOR and is blind to all of this.
   ========================================================================== */

/* Spine row 3. H2 and lede left, ruled stat stack right. */
@media (max-width: 640px) {
  .band--problem {
    background-color: var(--bg);
    background-image:
      linear-gradient(100deg,
        rgba(255,255,255,.94) 0%,
        rgba(255,255,255,.86) 26%,
        rgba(255,255,255,.42) 46%,
        rgba(255,255,255,.06) 62%,
        rgba(255,255,255,0)   74%),
      image-set(url("/img/bg/problem-640.avif") type("image/avif"),
                url("/img/bg/problem-640.webp") type("image/webp"),
                url("/img/bg/problem-640.jpg")  type("image/jpeg"));
    background-size: cover, cover;
    background-position: 50% 0%, 50% 0%;
    background-repeat: no-repeat, no-repeat;
  }
}
@media (min-width: 641px) and (max-width: 1280px) {
  .band--problem {
    background-color: var(--bg);
    background-image:
      linear-gradient(100deg,
        rgba(255,255,255,.94) 0%,
        rgba(255,255,255,.86) 26%,
        rgba(255,255,255,.42) 46%,
        rgba(255,255,255,.06) 62%,
        rgba(255,255,255,0)   74%),
      image-set(url("/img/bg/problem-1280.avif") type("image/avif"),
                url("/img/bg/problem-1280.webp") type("image/webp"),
                url("/img/bg/problem-1280.jpg")  type("image/jpeg"));
    background-size: cover, cover;
    background-position: 50% 0%, 50% 0%;
    background-repeat: no-repeat, no-repeat;
  }
}
@media (min-width: 1281px) {
  .band--problem {
    background-color: var(--bg);
    background-image:
      linear-gradient(100deg,
        rgba(255,255,255,.94) 0%,
        rgba(255,255,255,.86) 26%,
        rgba(255,255,255,.42) 46%,
        rgba(255,255,255,.06) 62%,
        rgba(255,255,255,0)   74%),
      image-set(url("/img/bg/problem-1920.avif") type("image/avif"),
                url("/img/bg/problem-1920.webp") type("image/webp"),
                url("/img/bg/problem-1920.jpg")  type("image/jpeg"));
    background-size: cover, cover;
    background-position: 50% 0%, 50% 0%;
    background-repeat: no-repeat, no-repeat;
  }
}
.band--problem.section-surface { background-color: var(--bg); }
@media (max-width: 1023px) {
  .band--problem {
    background-image:
      linear-gradient(180deg,
      rgba(255,255,255,.88) 0%,
      rgba(255,255,255,.80) 45%,
      rgba(255,255,255,.86) 100%),
      image-set(url("/img/bg/problem-640.avif") type("image/avif"),
              url("/img/bg/problem-640.webp") type("image/webp"),
              url("/img/bg/problem-640.jpg")  type("image/jpeg"));
  }
}

/* Spine row 6. ~160px strip, three short labels, no body copy. */
@media (max-width: 640px) {
  .band--steps {
    background-color: var(--bg);
    background-image:
      linear-gradient(180deg,
        rgba(255,255,255,.70) 0%,
        rgba(255,255,255,.52) 100%),
      image-set(url("/img/bg/steps-640.avif") type("image/avif"),
                url("/img/bg/steps-640.webp") type("image/webp"),
                url("/img/bg/steps-640.jpg")  type("image/jpeg"));
    background-size: cover, cover;
    background-position: 50% 50%, 50% 50%;
    background-repeat: no-repeat, no-repeat;
  }
}
@media (min-width: 641px) and (max-width: 1280px) {
  .band--steps {
    background-color: var(--bg);
    background-image:
      linear-gradient(180deg,
        rgba(255,255,255,.70) 0%,
        rgba(255,255,255,.52) 100%),
      image-set(url("/img/bg/steps-1280.avif") type("image/avif"),
                url("/img/bg/steps-1280.webp") type("image/webp"),
                url("/img/bg/steps-1280.jpg")  type("image/jpeg"));
    background-size: cover, cover;
    background-position: 50% 50%, 50% 50%;
    background-repeat: no-repeat, no-repeat;
  }
}
@media (min-width: 1281px) {
  .band--steps {
    background-color: var(--bg);
    background-image:
      linear-gradient(180deg,
        rgba(255,255,255,.70) 0%,
        rgba(255,255,255,.52) 100%),
      image-set(url("/img/bg/steps-1920.avif") type("image/avif"),
                url("/img/bg/steps-1920.webp") type("image/webp"),
                url("/img/bg/steps-1920.jpg")  type("image/jpeg"));
    background-size: cover, cover;
    background-position: 50% 50%, 50% 50%;
    background-repeat: no-repeat, no-repeat;
  }
}
.band--steps.section-surface { background-color: var(--bg); }

/* Spine row 9. Price and CTA left, "what you get" recap right. */
@media (max-width: 640px) {
  .band--price {
    background-color: var(--bg);
    background-image:
      linear-gradient(100deg,
        rgba(255,255,255,.94) 0%,
        rgba(255,255,255,.86) 26%,
        rgba(255,255,255,.42) 46%,
        rgba(255,255,255,.06) 62%,
        rgba(255,255,255,0)   74%),
      image-set(url("/img/bg/price-640.avif") type("image/avif"),
                url("/img/bg/price-640.webp") type("image/webp"),
                url("/img/bg/price-640.jpg")  type("image/jpeg"));
    background-size: cover, cover;
    background-position: 50% 100%, 50% 100%;
    background-repeat: no-repeat, no-repeat;
  }
}
@media (min-width: 641px) and (max-width: 1280px) {
  .band--price {
    background-color: var(--bg);
    background-image:
      linear-gradient(100deg,
        rgba(255,255,255,.94) 0%,
        rgba(255,255,255,.86) 26%,
        rgba(255,255,255,.42) 46%,
        rgba(255,255,255,.06) 62%,
        rgba(255,255,255,0)   74%),
      image-set(url("/img/bg/price-1280.avif") type("image/avif"),
                url("/img/bg/price-1280.webp") type("image/webp"),
                url("/img/bg/price-1280.jpg")  type("image/jpeg"));
    background-size: cover, cover;
    background-position: 50% 100%, 50% 100%;
    background-repeat: no-repeat, no-repeat;
  }
}
@media (min-width: 1281px) {
  .band--price {
    background-color: var(--bg);
    background-image:
      linear-gradient(100deg,
        rgba(255,255,255,.94) 0%,
        rgba(255,255,255,.86) 26%,
        rgba(255,255,255,.42) 46%,
        rgba(255,255,255,.06) 62%,
        rgba(255,255,255,0)   74%),
      image-set(url("/img/bg/price-1920.avif") type("image/avif"),
                url("/img/bg/price-1920.webp") type("image/webp"),
                url("/img/bg/price-1920.jpg")  type("image/jpeg"));
    background-size: cover, cover;
    background-position: 50% 100%, 50% 100%;
    background-repeat: no-repeat, no-repeat;
  }
}
.band--price.section-surface { background-color: var(--bg); }
@media (max-width: 1023px) {
  .band--price {
    background-image:
      linear-gradient(180deg,
      rgba(255,255,255,.88) 0%,
      rgba(255,255,255,.80) 45%,
      rgba(255,255,255,.86) 100%),
      image-set(url("/img/bg/price-640.avif") type("image/avif"),
              url("/img/bg/price-640.webp") type("image/webp"),
              url("/img/bg/price-640.jpg")  type("image/jpeg"));
  }
}

/* Spine row 12. Centred cluster, nothing else in the viewport. */
@media (max-width: 640px) {
  .band--closing {
    background-color: var(--bg);
    background-image:
      linear-gradient(180deg,
        rgba(255,255,255,.82) 0%,
        rgba(255,255,255,.58) 40%,
        rgba(255,255,255,.30) 72%,
        rgba(255,255,255,.10) 100%),
      image-set(url("/img/bg/closing-640.avif") type("image/avif"),
                url("/img/bg/closing-640.webp") type("image/webp"),
                url("/img/bg/closing-640.jpg")  type("image/jpeg"));
    background-size: cover, cover;
    background-position: 50% 100%, 50% 100%;
    background-repeat: no-repeat, no-repeat;
  }
}
@media (min-width: 641px) and (max-width: 1280px) {
  .band--closing {
    background-color: var(--bg);
    background-image:
      linear-gradient(180deg,
        rgba(255,255,255,.82) 0%,
        rgba(255,255,255,.58) 40%,
        rgba(255,255,255,.30) 72%,
        rgba(255,255,255,.10) 100%),
      image-set(url("/img/bg/closing-1280.avif") type("image/avif"),
                url("/img/bg/closing-1280.webp") type("image/webp"),
                url("/img/bg/closing-1280.jpg")  type("image/jpeg"));
    background-size: cover, cover;
    background-position: 50% 100%, 50% 100%;
    background-repeat: no-repeat, no-repeat;
  }
}
@media (min-width: 1281px) {
  .band--closing {
    background-color: var(--bg);
    background-image:
      linear-gradient(180deg,
        rgba(255,255,255,.82) 0%,
        rgba(255,255,255,.58) 40%,
        rgba(255,255,255,.30) 72%,
        rgba(255,255,255,.10) 100%),
      image-set(url("/img/bg/closing-1920.avif") type("image/avif"),
                url("/img/bg/closing-1920.webp") type("image/webp"),
                url("/img/bg/closing-1920.jpg")  type("image/jpeg"));
    background-size: cover, cover;
    background-position: 50% 100%, 50% 100%;
    background-repeat: no-repeat, no-repeat;
  }
}
.band--closing.section-surface { background-color: var(--bg); }
@media (max-width: 1023px) {
  .band--closing {
    background-image:
      linear-gradient(180deg,
      rgba(255,255,255,.88) 0%,
      rgba(255,255,255,.80) 45%,
      rgba(255,255,255,.86) 100%),
      image-set(url("/img/bg/closing-640.avif") type("image/avif"),
              url("/img/bg/closing-640.webp") type("image/webp"),
              url("/img/bg/closing-640.jpg")  type("image/jpeg"));
  }
}

/* The white breaks. Named so the rhythm is legible in the markup rather than
   being an accident of which sections happen to have no image. */
.band--break { background-color: var(--bg); }

/* @@ SECTION-CSS END @@ */
