/* ==========================================================================
   Coffeenomics — Shared design system
   Editorial, documentary, anti-template. See project handoff for the rules
   this file encodes: no gradients, no glassmorphism, no floating cards,
   square-ish corners, thin rules instead of shadows, yellow as a signal only.
   ========================================================================== */

/* ---- Reset ---- */
/* The [hidden] attribute only hides an element via the browser's default
   stylesheet ([hidden] { display: none }) — any later, more specific rule
   that sets `display` on the same element (a lot of our components do,
   e.g. .field { display: flex }) silently overrides it, leaving the
   element invisible-looking-but-actually-still-laid-out or, worse, fully
   visible depending on the rest of the box model. Found via the Playwright
   test suite catching a conditional form field that stayed visible. This
   rule guarantees [hidden] always wins, everywhere, permanently. */
[hidden] { display: none !important; }

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, picture, svg { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; color: inherit; }
button { cursor: pointer; }
a { color: inherit; text-decoration: none; }

/* ---- Design tokens ---- */
:root {
  /* Brand colour tokens (locked — see handoff §2) */
  --ink: #101820;
  --cream: #F5F1E8;
  --yellow: #F4C542;
  --slate: #53606A;
  --white: #FFFFFF;

  /* Derived */
  --ink-70: rgba(16, 24, 32, 0.7);
  --ink-15: rgba(16, 24, 32, 0.15);
  --ink-08: rgba(16, 24, 32, 0.08);
  --cream-on-ink: rgba(245, 241, 232, 0.85);
  --rule: rgba(16, 24, 32, 0.18);
  --rule-on-ink: rgba(245, 241, 232, 0.25);

  /* Type. Avenir Next Condensed is licensed (Apple/Linotype), not a
     freely embeddable webfont — no substitute file is provided in the
     project yet, so this falls back to the handoff's own documented
     fallback chain. Swap --font-display's first entry when a licensed
     webfont file is supplied. */
  --font-display: "Avenir Next Condensed", "Arial Narrow", "Helvetica Neue Condensed", Arial, sans-serif;
  --font-body: "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Courier New", monospace;

  /* Spacing base: 8px, deliberately not applied with mechanical evenness */
  --sp-1: 8px;
  --sp-2: 16px;
  --sp-3: 24px;
  --sp-4: 32px;
  --sp-5: 48px;
  --sp-6: 64px;
  --sp-7: 96px;
  --sp-8: 128px;

  --radius-control: 6px;
  --radius-form: 10px;

  --content-max: 1440px;
  --measure: 62ch; /* ~58-68 characters */

  --ease: cubic-bezier(0.2, 0.6, 0.2, 1);
}

/* ---- Base ---- */
html, body { background: var(--cream); }
html { overflow-x: hidden; }
body {
  font-family: var(--font-body);
  color: var(--ink);
  line-height: 1.5;
  font-size: 17px;
  background: var(--cream);
}
/* overflow-x: hidden is only declared on <html> above, not repeated
   here. Setting it on both broke every position:sticky element site-
   wide (including .site-header): overflow-x: hidden forces overflow-y
   to compute as auto (a documented CSS interdependency), which turned
   body into an extra intermediate scroll container that never
   actually scrolls internally — so every sticky descendant computed
   its offset against a scrollport that never moves, instead of the
   real page scroll on <html>. html's own overflow-x: hidden already
   fully blocks horizontal scroll for the whole document. */

/* Web Interface Guidelines audit (vercel-labs/web-interface-guidelines):
   anchor-linked sections (buying-request-form, upcoming-events, etc.) sit
   under a sticky header — without this, jumping to #id or focusing a field
   inside it lands partly hidden behind the header. */
[id] { scroll-margin-top: 96px; }

/* Same audit: touch targets shouldn't get the double-tap-zoom delay, and
   the tap highlight should be an intentional colour, not the browser
   default grey flash. */
a, button, input, select, textarea, label {
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(244, 197, 66, 0.35);
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.02;
  text-wrap: balance;
}

h1 { font-size: clamp(2.6rem, 5.2vw, 5.4rem); text-transform: uppercase; }
/* Baseline heading-to-body gap. Section intros were relying on an
   inline margin-bottom per instance, which drifted between 0 and
   var(--sp-4) for the same "heading directly above a paragraph"
   relationship. This default covers every heading with no explicit
   override; the larger, deliberate inline overrides (numbered-
   sequence intros, form headings) and the deliberate 0 on solo
   aside-media headings still win, since an inline style always beats
   this rule. */
h2 { font-size: clamp(1.9rem, 3.4vw, 3.4rem); text-transform: uppercase; margin-bottom: var(--sp-2); }
h3 { font-size: clamp(1.3rem, 1.8vw, 1.7rem); text-transform: uppercase; }

p { max-width: var(--measure); }
p + p { margin-top: var(--sp-2); }

/* Centers a plain .wrap text section (eyebrow + h2 + body copy) as a single
   column on the page instead of letting it hug the left edge with empty
   space to the right. Every line (heading, paragraphs) is centered, and
   the eyebrow's leading dash is dropped since it reads as a left-alignment
   marker once the label itself is centered. */
.wrap.centered-copy { max-width: 640px; margin-inline: auto; text-align: center; }
.centered-copy h2, .centered-copy p { margin-inline: auto; }
.centered-copy .eyebrow::before { display: none; }
/* Opt back into the eyebrow's dash on a centered section where it should stay. */
.centered-copy.centered-copy--keep-dash .eyebrow::before { display: inline-block; }

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--slate);
  margin-bottom: var(--sp-2);
}
.eyebrow::before { content: ""; display: inline-block; width: 18px; height: 3px; background: var(--yellow); }

.on-ink .eyebrow { color: var(--cream-on-ink); }

/* ---- Accessibility ---- */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--cream);
  padding: var(--sp-2) var(--sp-3);
  z-index: 1000;
}
.skip-link:focus {
  left: var(--sp-2);
  top: var(--sp-2);
}

:focus-visible {
  outline: 3px solid var(--yellow);
  outline-offset: 2px;
}

.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---- Layout / grid ---- */
.wrap {
  max-width: var(--content-max);
  margin-inline: auto;
  /* content-max (1440px) sits close to a lot of real desktop
     viewports, so the old var(--sp-5) (48px) read as nearly
     edge-to-edge. A little more room on wide screens; tablet and
     mobile keep their existing, already-appropriate padding. */
  padding-inline: var(--sp-6);
}
@media (max-width: 1080px) { .wrap { padding-inline: var(--sp-3); } }
@media (max-width: 640px) { .wrap { padding-inline: 20px; } }

.grid-12 {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--sp-3);
}
@media (max-width: 1080px) {
  .grid-12 { grid-template-columns: repeat(8, 1fr); }
}
@media (max-width: 640px) {
  .grid-12 { grid-template-columns: repeat(4, 1fr); }
}

section { padding-block: var(--sp-6); }
section.tight { padding-block: var(--sp-4); }
section.roomy { padding-block: var(--sp-8); }
section.on-ink { background: var(--ink); color: var(--cream); }
section.on-white { background: var(--white); }

.rule-top { border-top: 1px solid var(--rule); }
.rule-bottom { border-bottom: 1px solid var(--rule); }
.on-ink .rule-top { border-top-color: var(--rule-on-ink); }
.on-ink .rule-bottom { border-bottom-color: var(--rule-on-ink); }

/* ---- Image slots (real photography per handoff §2; placeholder until supplied) ---- */
.image-slot {
  position: relative;
  width: 100%;
  aspect-ratio: var(--slot-ratio, 4 / 5);
  background:
    repeating-linear-gradient(135deg, var(--ink-08) 0 1px, transparent 1px 14px),
    var(--white);
  border: 1px solid var(--ink-15);
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}
.image-slot img { width: 100%; height: 100%; object-fit: cover; }
.image-slot__label {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--slate);
  background: var(--cream);
  border: 1px solid var(--ink-15);
  padding: 6px 10px;
  margin: var(--sp-2);
}
.image-slot--wide { --slot-ratio: 16 / 9; }
.image-slot--square { --slot-ratio: 1 / 1; }
.image-slot--tall { --slot-ratio: 3 / 4; }

.evidence-label {
  position: absolute;
  left: var(--sp-2);
  bottom: var(--sp-2);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.03em;
  background: var(--ink);
  color: var(--cream);
  padding: 5px 9px;
}

/* ---- Buttons ---- */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 0.98rem;
  padding: 14px 22px;
  border-radius: var(--radius-control);
  border: 2px solid var(--ink);
  transition: background-color 160ms var(--ease), color 160ms var(--ease), border-color 160ms var(--ease);
  white-space: nowrap;
}
.btn-primary {
  background: var(--yellow);
  border-color: var(--yellow);
  color: var(--ink);
}
.btn-primary:hover { background: #e0b334; border-color: #e0b334; }
.btn-primary:active { background: #cfa02e; border-color: #cfa02e; }
.btn-secondary {
  background: transparent;
  border-color: var(--ink);
  color: var(--ink);
}
.btn-secondary:hover { background: var(--ink); color: var(--cream); }
.on-ink .btn-secondary { border-color: var(--cream); color: var(--cream); }
.on-ink .btn-secondary:hover { background: var(--cream); color: var(--ink); }

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  border-bottom: 2px solid var(--ink);
  padding-bottom: 2px;
}
.on-ink .link-arrow { border-bottom-color: var(--cream); }
.link-arrow::after { content: "→"; transition: transform 160ms var(--ease); }
.link-arrow:hover::after { transform: translateX(3px); }

.actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }

/* ---- Cookie consent banner ---- */
.consent-banner {
  position: fixed;
  left: var(--sp-2);
  right: var(--sp-2);
  bottom: var(--sp-2);
  z-index: 550;
  max-width: 760px;
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  background: var(--ink);
  color: var(--cream);
  border-radius: var(--radius-control);
  padding: var(--sp-3);
  box-shadow: 0 8px 24px rgba(16, 24, 32, 0.25);
}
.consent-banner__text { flex: 1 1 280px; font-size: 0.92rem; }
.consent-banner__actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-left: auto; }
.consent-banner .btn-secondary { border-color: var(--cream); color: var(--cream); }
.consent-banner .btn-secondary:hover { background: var(--cream); color: var(--ink); }
@media (max-width: 480px) {
  .consent-banner { flex-direction: column; align-items: stretch; }
  /* flex: 1 1 280px on .consent-banner__text sets a 280px *width* basis
     for the row layout above - but flex-basis applies to the main axis,
     which switches to height once the banner goes to flex-direction:
     column here, forcing the text block to a 280px-tall minimum (plus
     flex-grow stretching it further) for what's actually 2-3 lines of
     text. Reset to its natural content height. */
  .consent-banner__text { flex: 0 1 auto; }
  .consent-banner__actions { margin-left: 0; justify-content: center; }
}

/* ---- Crosslink ---- */
/* The "looking to buy?" / "already have a quotation?" bands that hand a
   visitor off to another page's form (buy-coffee, partner-with-us,
   review-my-deal). Centered to read as a single closing prompt, matching
   .final-cta's treatment rather than the left-pinned body copy above it. */
.crosslink { text-align: center; }
.crosslink h2, .crosslink p, .crosslink .actions { margin-inline: auto; }
.crosslink .actions { justify-content: center; }
/* Baseline text-to-CTA gap. Every page hero's button row sat flush
   against its last paragraph (no margin at all) while in-body
   section CTAs mostly carried an inline margin-top: var(--sp-3) —
   the same relationship, two different results. var(--sp-4) reads
   right against the large display headings sitting above this copy;
   scoped to "actions right after content" so it never touches
   .final-cta__grid, where the actions row sits beside its heading in
   a flex row rather than below body copy. */
p + .actions, .ledger + .actions, .checklist + .actions, figure + .actions {
  margin-top: var(--sp-4);
}

/* ---- SiteHeader ---- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 500;
  background: var(--cream);
  border-bottom: 1px solid var(--rule);
}
.site-header__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--sp-3);
  transition: padding-block 160ms var(--ease);
}
.site-header.is-compact .site-header__bar { padding-block: var(--sp-1); }

.wordmark { display: flex; align-items: center; }
.wordmark img { height: 32px; width: auto; display: block; }

.site-nav { display: flex; align-items: center; gap: var(--sp-4); }
.site-nav__links { display: flex; gap: var(--sp-3); }
.site-nav__links a {
  font-weight: 700;
  font-size: 0.92rem;
  padding-bottom: 2px;
  border-bottom: 2px solid transparent;
}
.site-nav__links a:hover, .site-nav__links a[aria-current="page"] {
  border-bottom-color: var(--yellow);
}

.menu-toggle {
  display: none;
  background: none;
  border: 2px solid var(--ink);
  border-radius: var(--radius-control);
  width: 44px; height: 44px;
  align-items: center;
  justify-content: center;
}

@media (max-width: 900px) {
  .site-nav__links { display: none; }
  .site-header__cta.desktop-only { display: none; }
  .site-nav .lang-switch { display: none; }
  .menu-toggle { display: flex; }
}

.mobile-menu {
  position: fixed;
  inset: 0;
  background: var(--ink);
  color: var(--cream);
  z-index: 600;
  display: flex;
  flex-direction: column;
  padding: max(var(--sp-3), env(safe-area-inset-top)) max(var(--sp-3), env(safe-area-inset-right)) max(var(--sp-5), env(safe-area-inset-bottom)) max(var(--sp-3), env(safe-area-inset-left));
  transform: translateY(-100%);
  transition: transform 220ms var(--ease);
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* (Real hidden-when-[hidden] behaviour for this element is now guaranteed
   by the global [hidden] rule near the top of this file.) */
.mobile-menu.is-open { transform: translateY(0); }
.mobile-menu__top { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--sp-6); }
.mobile-menu__close {
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 2px solid var(--cream);
  border-radius: var(--radius-control);
  width: 44px; height: 44px;
  color: var(--cream);
}
.mobile-menu__links { display: flex; flex-direction: column; gap: var(--sp-3); margin-bottom: auto; }
.mobile-menu__links a { font-family: var(--font-display); font-size: 2rem; text-transform: uppercase; font-weight: 800; }
.mobile-menu__links a:active { color: var(--yellow); }
.mobile-menu .btn-primary { align-self: flex-start; margin-top: var(--sp-5); }

/* ---- Language switcher ---- */
.lang-switch {
  font-weight: 700;
  font-size: 0.85rem;
  border: 2px solid var(--ink);
  border-radius: var(--radius-control);
  padding: 5px 10px;
  line-height: 1;
}
.lang-switch:hover { border-color: var(--yellow); }
.mobile-menu .lang-switch {
  align-self: flex-start;
  font-family: inherit;
  text-transform: none;
  font-size: 0.95rem;
  font-weight: 700;
  border: 2px solid var(--cream);
  border-radius: var(--radius-control);
  color: var(--cream);
  padding: 6px 14px;
  margin-top: var(--sp-3);
}

/* ---- PageHero ---- */
.page-hero { padding-block: var(--sp-6) var(--sp-6); }
.page-hero__grid {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: var(--sp-5);
  align-items: end;
}
.page-hero__copy h1 { margin-block: var(--sp-2) var(--sp-3); }
.page-hero__copy p { color: var(--slate); font-size: 1.15rem; }
@media (max-width: 900px) {
  .page-hero__grid { grid-template-columns: 1fr; }
}

/* Every other page-hero uses a compact 3D object image, roughly as
   tall as the text column, so align-items:end barely shows. Meet Us
   is the only hero using a tall photo (media-frame--tall, 3:4), much
   taller than its text — once top-aligned instead of bottom-aligned,
   the photo still runs well past where the text column ends, with
   real vertical room to spare. Rather than leave that as dead space,
   .page-hero__copy sticks in place while the photo scrolls past it:
   .page-hero__copy-slot stretches to the row's full height (the
   photo's height) so the sticky element nested inside has somewhere
   to travel — same reason as the homepage's .sticky-visual__slot. */
.page-hero__grid.page-hero__grid--sticky { align-items: stretch; }
.page-hero__copy--sticky {
  position: sticky;
  top: 96px;
  align-self: start;
}
@media (max-width: 900px) {
  .page-hero__copy--sticky { position: static; }
}

/* ---- ProofRail ---- */
.proof-rail {
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
}
.proof-rail__list {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}
.proof-rail__item {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-3) var(--sp-2);
  border-left: 1px solid var(--rule);
  font-family: var(--font-display);
  font-weight: 800;
  text-transform: uppercase;
  font-size: 1rem;
  letter-spacing: 0.01em;
  text-align: center;
}
.proof-rail__item:first-child { border-left: none; }
.on-ink .proof-rail__item { border-left-color: var(--rule-on-ink); }
@media (max-width: 640px) {
  .proof-rail__list { grid-template-columns: repeat(2, 1fr); }
  .proof-rail__item:nth-child(3) { border-left: none; }
}

/* ---- Copy paired with a vertical stat list ----
   A paragraph/CTA block with a short list of trust points positioned
   beside it rather than below it (HomeDealReviewFeature: the "free
   comparison / private / response time" points belong to this specific
   CTA, not the whole merged network+deal-review section they used to
   sit underneath as a single unrelated-looking strip). */
.copy-with-stats { display: grid; grid-template-columns: 2fr 1fr; gap: var(--sp-4); align-items: start; }
.stat-list { display: flex; flex-direction: column; gap: var(--sp-4); }
.stat-list__item {
  font-family: var(--font-display);
  font-weight: 800;
  text-transform: uppercase;
  font-size: clamp(1.3rem, 1.8vw, 1.7rem);
  letter-spacing: -0.01em;
  line-height: 1.02;
}
@media (max-width: 900px) {
  .copy-with-stats { grid-template-columns: 1fr; }
  /* At full column width, .stat-list's heading-scale items (up to 1.7rem,
     32px gaps) stacked directly under the CTA button read as more giant
     headlines rather than supporting trust points. Rework at proof-rail's
     type scale instead, one item per line, with a divider so it reads as
     a distinct, secondary group under the button. */
  .stat-list {
    gap: var(--sp-2);
    margin-top: var(--sp-4);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--rule);
  }
  .stat-list__item { font-size: 0.85rem; letter-spacing: 0.01em; }
}

/* ---- EditorialIntro ---- */
.editorial-intro__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-5);
  align-items: center;
}
@media (max-width: 900px) { .editorial-intro__grid { grid-template-columns: 1fr; } }

/* Same sticky technique as .page-hero__grid--sticky / .sticky-visual:
   the shorter text column stays pinned in view while the taller
   image+ledger column scrolls past beside it. .editorial-intro__copy-slot
   stretches to the row's full height so the sticky element nested
   inside has room to travel — align-items:start on a plain grid item
   would otherwise shrink it to its own content size, leaving no room
   to stick at all. */
.editorial-intro__grid.editorial-intro__grid--sticky { align-items: stretch; }
.editorial-intro__copy--sticky {
  position: sticky;
  top: 96px;
  align-self: start;
}
@media (max-width: 900px) {
  .editorial-intro__copy--sticky { position: static; }
}

/* ---- StickyVisual ----
   One figure tracks scroll across two stacked text blocks: it starts
   level with the first block and, via position:sticky, stays pinned
   at `top` while the text column scrolls past underneath it, then
   settles at the bottom of the row once the second block's end
   catches up — which is what makes it look like it "arrives" there.
   Sticky is inherently bidirectional, so scrolling back up reverses
   it with no extra code.

   The figure sits in a wrapper (.sticky-visual__slot), not directly
   in the grid, on purpose: a sticky element needs a containing block
   taller than itself to have room to travel in. With align-items:start
   a grid item shrinks to fit its own content, leaving zero such room —
   sticky computes fine but never visibly sticks. Default grid stretch
   makes the slot match the row's full height (the taller text column);
   the shorter figure inside it then has real space to move within. */
.sticky-visual {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-4) var(--sp-5);
}
.sticky-visual__text {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
}
/* .media-frame.sticky-visual__figure (not just .sticky-visual__figure):
   .media-frame itself sets position:relative, and as an equal-specificity
   single-class selector defined later in this file it would otherwise win
   and silently cancel the sticky behaviour. The compound selector's higher
   specificity guarantees this wins regardless of source order. */
.media-frame.sticky-visual__figure {
  position: sticky;
  top: 96px;
}
@media (max-width: 900px) {
  .sticky-visual { grid-template-columns: 1fr; }
  .media-frame.sticky-visual__figure { position: static; }
}

/* ---- NumberedSequence ---- */
.numbered-sequence { border-top: 1px solid var(--rule); }
.numbered-sequence__row {
  display: grid;
  grid-template-columns: 100px 1fr;
  gap: var(--sp-3);
  padding-block: var(--sp-4);
  border-bottom: 1px solid var(--rule);
}
.on-ink .numbered-sequence__row { border-bottom-color: var(--rule-on-ink); }
/* Some rows wrap their heading + paragraph in one div (one grid item,
   auto-placed into the 1fr column); others — every step inside
   --horizontal, since that variant collapses each row to a single
   column above 901px and doesn't need the wrapper — list the heading
   and paragraph as separate flat children. Below 901px, without this,
   those flat children fall through the base [number][text] grid one
   at a time: heading lands in column 2 as expected, but the paragraph
   then auto-places into a new implicit row starting back at column 1
   (100px), wrapping to one word per line. Pinning both to column 2
   makes the flat markup behave like the wrapped markup at every width. */
.numbered-sequence__row > h3, .numbered-sequence__row > p { grid-column: 2; }
.numbered-sequence__num {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 2.6rem;
  color: var(--yellow);
  line-height: 1;
}
.numbered-sequence__row h3 { margin-bottom: var(--sp-1); }
@media (min-width: 901px) {
  .numbered-sequence--horizontal { display: grid; grid-template-columns: repeat(3, 1fr); border-top: none; }
  /* A four-step sequence (Review My Deal's "second opinion" steps) used to
     split into a 3-across row plus a lone 4th item dropped below it, and
     that orphaned item's copy was mis-nested into the row's default
     [number][text] grid, wrapping to a ~100px sliver. Four items read
     better as one row than as an awkward 3-plus-1. */
  .numbered-sequence--horizontal.numbered-sequence--four { grid-template-columns: repeat(4, 1fr); }
  .numbered-sequence--horizontal .numbered-sequence__row {
    grid-template-columns: 1fr;
    border-bottom: none;
    border-left: 1px solid var(--rule);
    padding-inline: var(--sp-3);
  }
  /* This variant's row only has one column, so the column-2 pin above
     (needed for the flat-markup fix below 901px) would otherwise create
     a phantom second column here. */
  .numbered-sequence--horizontal .numbered-sequence__row > h3,
  .numbered-sequence--horizontal .numbered-sequence__row > p {
    grid-column: auto;
  }
  .numbered-sequence--horizontal .numbered-sequence__row:first-child { border-left: none; padding-left: 0; }
}
@media (min-width: 901px) {
  .numbered-sequence--horizontal.on-ink .numbered-sequence__row { border-left-color: var(--rule-on-ink); }
}

/* ---- DisclosureBlock ---- */
.disclosure-block {
  border: 1px solid var(--rule);
  padding: var(--sp-4);
  background: var(--white);
}
.disclosure-block p { color: var(--slate); font-size: 0.98rem; }
.disclosure-block h2, .disclosure-block h3 { color: var(--ink); }

/* ---- FormShell ---- */
.form-shell {
  background: var(--white);
  border: 1px solid var(--rule);
  padding: var(--sp-5);
}
@media (max-width: 640px) { .form-shell { padding: var(--sp-3); } }

.form-stages {
  display: flex;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.form-stages__step { padding-bottom: var(--sp-1); border-bottom: 3px solid var(--rule); color: var(--slate); flex: 1; }
.form-stages__step.is-active { border-bottom-color: var(--yellow); color: var(--ink); font-weight: 700; }
.form-stages__step.is-done { border-bottom-color: var(--ink); color: var(--ink); }

.form-stage { display: none; }
.form-stage.is-active { display: block; }

.field-group { display: grid; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.field-group--split { grid-template-columns: 1fr 1fr; }
@media (max-width: 640px) { .field-group--split { grid-template-columns: 1fr; } }

.field { display: flex; flex-direction: column; gap: 6px; }
.field label {
  font-size: 0.9rem;
  font-weight: 700;
}
.field .hint { font-size: 0.82rem; color: var(--slate); }
.field .optional-tag {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--slate);
  text-transform: uppercase;
  font-weight: 400;
}
.field input[type="text"],
.field input[type="email"],
.field input[type="tel"],
.field input[type="number"],
.field select,
.field textarea {
  border: 1.5px solid var(--ink);
  border-radius: 4px;
  padding: 12px 14px;
  background: var(--white);
  font-size: 1rem;
}
.field textarea { resize: vertical; min-height: 96px; }
.field input:focus, .field select:focus, .field textarea:focus {
  outline: 3px solid var(--yellow);
  outline-offset: 1px;
}
.field.has-error input, .field.has-error select, .field.has-error textarea {
  border-color: #B3261E;
}
.field-error {
  font-size: 0.85rem;
  color: #B3261E;
  display: none;
}
.field.has-error .field-error { display: block; }

.radio-group, .checkbox-group { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.radio-option, .checkbox-option {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1.5px solid var(--ink-15);
  border-radius: var(--radius-control);
  padding: 10px 14px;
  font-size: 0.92rem;
}
.radio-option:has(input:checked), .checkbox-option:has(input:checked) {
  border-color: var(--ink);
  background: var(--cream);
}

/* flex-wrap:wrap — without it, two full-length labels like "Back to
   Requirement" / "Continue to Contact" don't fit side by side on a
   narrow phone and neither button shrinks (same flex min-width:auto
   issue as .acknowledgment label), so the second button silently
   overflows past the edge of the screen, partly or fully untappable.
   Wrapping lets it drop to its own full-width row instead. */
.form-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2);
  margin-top: var(--sp-4);
}
@media (max-width: 480px) {
  .form-nav .btn { flex: 1 1 100%; }
}

.acknowledgment {
  font-size: 0.88rem;
  color: var(--slate);
  border-top: 1px solid var(--rule);
  padding-top: var(--sp-3);
  margin-top: var(--sp-3);
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: flex-start;
}
.acknowledgment input { margin-top: 4px; }
/* flex-basis defaults to the label's full unwrapped text width, which
   is far wider than the row (checkbox + label + long sentence), so
   the label was pushed to its own flex line entirely instead of
   wrapping its own text within the remaining space next to the
   checkbox. flex:1 1 0% + min-width:0 is the standard fix: it ignores
   content size for sizing and lets the label wrap normally instead. */
.acknowledgment label { flex: 1 1 0%; min-width: 0; }
input[type="checkbox"], input[type="radio"] { accent-color: var(--yellow); }
.acknowledgment .required-tag {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--ink);
  text-transform: uppercase;
  font-weight: 700;
  margin-left: 4px;
}
/* Checkboxes fail native validation the same as text fields, but had no
   equivalent .has-error treatment — someone could fail to submit with no
   visible sign of which checkbox was the problem. Mirrors .field.has-error
   (red border, only-on-error message) but as a full box, since a checkbox
   row has no input border of its own to redden. */
.acknowledgment.has-error {
  border: 1.5px solid #B3261E;
  border-radius: 4px;
  padding: var(--sp-2) var(--sp-3);
  background: rgba(179, 38, 30, 0.05);
}
.acknowledgment.has-error label { color: #B3261E; }
.acknowledgment .field-error {
  font-size: 0.85rem;
  color: #B3261E;
  display: none;
  flex-basis: 100%;
}
.acknowledgment.has-error .field-error { display: block; }

.form-status {
  font-size: 0.9rem;
  padding: var(--sp-2);
  border: 1px solid var(--rule);
  margin-bottom: var(--sp-3);
  display: none;
}
.form-status.is-visible { display: block; }
.form-status.is-error { border-color: #B3261E; color: #B3261E; }

.form-success { text-align: left; padding-block: var(--sp-4); }
.form-success h2 { margin-bottom: var(--sp-2); }

/* ---- FinalCTA ---- */
.final-cta {
  background: var(--ink);
  color: var(--cream);
  text-align: center;
}
/* .final-cta.roomy (not just .final-cta) for the specificity to beat
   section.roomy's own padding-block: this content is just a heading
   and a button (~166px), and .roomy's normal 128px top/bottom (meant
   for sections with substantially more content) left it looking like
   mostly empty space rather than a deliberately spacious close. */
.final-cta.roomy { padding-block: var(--sp-6); }
.final-cta__grid {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
}
.final-cta h2 { margin-bottom: var(--sp-2); max-width: 16ch; margin-inline: auto; }
.final-cta p { color: var(--cream-on-ink); }
.final-cta .actions { justify-content: center; }

/* Modifier for a final-cta heading written as several short lines (explicit
   <br> per line) rather than one wrapped sentence. Needs its own width (no
   16ch cap) and, on narrow phones, a smaller size so each line still fits
   without an unwanted extra wrap. */
.final-cta h2.final-cta__heading--lines { max-width: none; }
@media (max-width: 640px) {
  .final-cta h2.final-cta__heading--lines { font-size: clamp(22px, 7vw, 30px); }
}

/* ---- SiteFooter ---- */
.site-footer {
  background: var(--ink);
  color: var(--cream);
  padding-block: var(--sp-6) var(--sp-4);
}
.site-footer__grid {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: var(--sp-5);
}
@media (max-width: 900px) { .site-footer__grid { grid-template-columns: 1fr; gap: var(--sp-4); } }
.site-footer p { color: var(--cream-on-ink); }
.site-footer__links { display: flex; flex-direction: column; gap: 10px; }
.site-footer__links a:hover { color: var(--yellow); }
.site-footer__bottom {
  border-top: 1px solid var(--rule-on-ink);
  margin-top: var(--sp-5);
  padding-top: var(--sp-3);
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--sp-2);
  font-size: 0.82rem;
  color: var(--slate);
}
.site-footer__wordmark { color: var(--cream); }

/* ---- Ledger / split / editorial utilities ---- */
.ledger { border-top: 1px solid var(--rule); }
.ledger__row {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: var(--sp-3);
  padding-block: var(--sp-3);
  border-bottom: 1px solid var(--rule);
}
.on-ink .ledger__row { border-bottom-color: var(--rule-on-ink); }
.ledger__row dt { font-weight: 700; }
.ledger__row dd { color: var(--slate); margin: 0; }
.on-ink .ledger__row dd { color: var(--cream-on-ink); }
/* A link inside a ledger row (e.g. UpcomingEvents' "Event website")
   otherwise inherits the site's plain a { color: inherit; text-decoration:
   none; } and reads as identical, non-clickable body text against the
   already-muted dd color. */
.ledger__row dd a { text-decoration: underline; text-underline-offset: 3px; font-weight: 700; }
.ledger__row dd a:hover { color: var(--yellow); }
@media (max-width: 640px) {
  .ledger__row { grid-template-columns: 1fr; gap: 6px; }
}

/* ---- Ledger: stacked variant ----
   The default side-by-side term/description row reads fine at full
   width, but crowds a bold heading when the whole ledger already sits
   in a half-width column next to an image (aside-media). Stacks the
   term above the description instead, styled like an h3, matching the
   card heading treatment used everywhere a list has the full width to
   itself (.two-col-list). */
.ledger--stacked .ledger__row { grid-template-columns: 1fr; gap: var(--sp-1); padding-block: var(--sp-4); }
.ledger--stacked .ledger__row dt {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.3rem, 1.8vw, 1.7rem);
  text-transform: uppercase;
  letter-spacing: -0.01em;
  line-height: 1.02;
}

.split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-5); }
@media (max-width: 900px) { .split { grid-template-columns: 1fr; } }

.two-col-list {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-4) var(--sp-5);
}
@media (max-width: 900px) {
  .two-col-list { grid-template-columns: 1fr; }
}

.stamp {
  border: 2px solid var(--ink);
  padding: var(--sp-3);
}
.stamp.is-active { border-color: var(--yellow); background: var(--ink); color: var(--cream); }
.stamp__label {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.3rem;
  letter-spacing: 0.02em;
}

.checklist { display: grid; gap: 10px; }
.checklist li { display: flex; gap: 10px; align-items: baseline; }
.checklist li::before { content: ""; display: inline-block; width: 10px; height: 10px; border: 2px solid var(--yellow); flex: none; transform: translateY(1px); }

.location-line {
  font-family: var(--font-mono);
  letter-spacing: 0.03em;
  color: var(--slate);
}
.on-ink .location-line { color: var(--cream-on-ink); }

.container-note {
  font-size: 0.85rem;
  color: var(--slate);
}

/* ---- Full-bleed statement (per handoff §3: "full-width statements" as
   one of the preferred alternatives to a repeated split layout) ---- */
.full-bleed {
  position: relative;
  min-height: 520px;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}
.full-bleed .image-slot {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: none;
  aspect-ratio: auto;
}
.full-bleed__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(0deg, rgba(16,24,32,0.82) 0%, rgba(16,24,32,0.35) 45%, rgba(16,24,32,0.05) 75%);
}
.full-bleed__panel {
  position: relative;
  z-index: 1;
  color: var(--cream);
  padding: var(--sp-5);
  max-width: 720px;
}
.full-bleed__panel .eyebrow { color: var(--cream-on-ink); }
.full-bleed__panel p { color: var(--cream-on-ink); }
@media (max-width: 640px) {
  .full-bleed { min-height: 440px; }
  .full-bleed__panel { padding: var(--sp-3); }
}
.full-bleed__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---- Media system (real photography + approved 3D objects) ----
   .media-frame is the shared wrapper. Two variants:
   --photo crops to a fixed ratio with object-fit: cover, for real
   documentary photography. --object never crops (object-fit: contain,
   transparent background) for the approved 3D renders, so the full
   silhouette always survives regardless of the surrounding column
   width. Ratio modifiers (--wide/--tall/--square) only apply to
   --photo; --object ignores them and sizes from its own intrinsic
   ratio via height:auto, per the "never crop a 3D object" rule. */
.media-frame {
  position: relative;
  width: 100%;
  display: block;
}
.media-frame figcaption {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.03em;
  color: var(--slate);
  padding-top: var(--sp-1);
}
.on-ink .media-frame figcaption { color: var(--cream-on-ink); }

.media-frame--photo {
  aspect-ratio: var(--frame-ratio, 4 / 5);
  overflow: hidden;
  border: 1px solid var(--ink-15);
  background: var(--white);
}
.media-frame--photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: var(--frame-position, center);
  display: block;
}
.media-frame--wide { --frame-ratio: 16 / 9; }
.media-frame--tall { --frame-ratio: 3 / 4; }
.media-frame--square { --frame-ratio: 1 / 1; }

.media-frame--object {
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
}
.media-frame--object img {
  width: 100%;
  height: auto;
  object-fit: contain;
  display: block;
  filter: drop-shadow(0 20px 16px rgba(16, 24, 32, 0.16));
}
/* A small number of object placements sit on a light card so a
   transparent-background PNG reads clearly against a busy section
   (e.g. next to a ledger). Used sparingly, never as the default. */
.media-frame--object.media-frame--grounded {
  background: var(--white);
  border: 1px solid var(--ink-15);
  padding: var(--sp-4);
}
.on-ink .media-frame--object.media-frame--grounded {
  background: rgba(245, 241, 232, 0.06);
  border-color: var(--rule-on-ink);
}

/* A plain --object figure placed straight into a 50/50 (or near-50/50)
   two-column grid — HomeBuyingIntro, HomeDealReviewFeature, the three
   HomeNetwork/BuyNetwork/PartnerNetwork placements, BuySharedContainer,
   BuyPromptBoard — has no size cap, so width:100% fills the whole
   column: at content-max widths that let a 3D render outgrow a short
   ledger or paragraph sitting next to it (the "coffee sack" report).
   The aside-media system already solved this for its own placements
   with a max-width; this brings the remaining plain placements to the
   same scale. --hero stays excluded: it is deliberately allowed to
   run larger. */
.editorial-intro__grid .media-frame--object:not(.media-frame--hero),
.wrap.split .media-frame--object:not(.media-frame--hero),
.buying-brief__grid .media-frame--object:not(.media-frame--hero) {
  max-width: 360px;
  margin-inline: auto;
}

/* HomeFounders / AboutHero: two aligned portraits standing in for one
   "Kevin and Dougie together" image, per the handoff's own fallback
   (§ founder imagery: "two aligned documentary portraits"). */
.founder-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-2);
}
.founder-pair .media-frame--photo { --frame-ratio: 4 / 5; }

/* AboutOriginStory: a small real sequence (archival + current) rather
   than one image standing in for the whole story. */
.archive-sequence {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-2);
}
.archive-sequence .media-frame--photo { --frame-ratio: 3 / 4; }

/* DealOutcomes: the three verdict plates sit above their matching
   .stamp panels. object-fit: contain keeps each plate's baked-in
   lettering legible at every width; this is the one place more than
   one 3D asset shares a viewport, which the asset manifest allows
   for a deliberate side-by-side comparison. */
.verdict-visuals {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.verdict-visuals .media-frame--object { max-width: 100%; }
.verdict-visuals .media-frame--object img { max-height: 220px; margin-inline: auto; }
@media (max-width: 640px) {
  .verdict-visuals { grid-template-columns: 1fr; gap: var(--sp-2); }
}

/* Form success screens: a small confirmation object, never load-bearing
   for the copy above it. */
.form-success__visual {
  max-width: 200px;
  margin-bottom: var(--sp-3);
}

/* Hero-scale 3D placements (HomePartnerFeature-style tall columns,
   HomeHero) are allowed to run a little larger than a supporting
   in-page object. */
.media-frame--hero img { max-height: 560px; margin-inline: auto; }
@media (max-width: 640px) {
  .media-frame--hero img { max-height: 360px; }
}

/* ---- Aside-media: copy + one 3D object, sharing one mobile rule ----
   Every new placement below needs the same thing: an intro (eyebrow +
   heading, sometimes a lead paragraph), the object, then the rest of
   the section's real content (a ledger, a checklist, a list). Rather
   than one-off markup per section, grid-template-areas puts the
   object on the left or right on desktop and always between the
   intro and the rest on mobile, matching "place after the
   introduction, before the detailed content" for every one of these
   sections without extra markup or JS reordering. */
.aside-media {
  display: grid;
  gap: var(--sp-4) var(--sp-5);
  align-items: center;
}
.aside-media--right {
  grid-template-columns: 1fr 1fr;
  grid-template-areas: "intro visual" "body visual" "extra visual";
}
.aside-media--left {
  grid-template-columns: 1fr 1fr;
  grid-template-areas: "visual intro" "visual body" "visual extra";
}
.aside-media__intro { grid-area: intro; }
.aside-media__body { grid-area: body; }
.aside-media__extra { grid-area: extra; }
.aside-media__visual {
  grid-area: visual;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Recommended sizing: ~440-560px standard, up to 580px for a wider
   composition (three trays side by side needs the room), smaller when
   an asset should stay visually subordinate to the section's main
   point rather than compete with it. */
/* These target the figure itself: every actual usage puts .aside-media__visual
   and .media-frame on the same element (never nested), so the selector must
   be compound (no space) to actually match. */
.aside-media__visual.media-frame { width: 100%; max-width: 520px; }
.aside-media__visual--wide.media-frame { max-width: 580px; }
.aside-media__visual--subordinate.media-frame { max-width: 400px; }

/* ---- AsideMedia sticky visual ----
   These sections usually carry a multi-row ledger, making the text
   column (intro+body+extra) taller than the image, which just sits
   centered in its spanned grid area and scrolls away as soon as the
   shorter visual ends. Same nested-slot technique as .sticky-visual:
   .aside-media__visual-slot takes over the "visual" grid-area and
   stretches to the row's full spanned height (align-items:stretch),
   giving the sticky figure nested inside real room to travel. */
.aside-media.aside-media--sticky { align-items: stretch; }
.aside-media__visual-slot { grid-area: visual; }
.aside-media__visual--sticky {
  position: sticky;
  top: 96px;
  align-self: start;
}
@media (max-width: 900px) {
  .aside-media__visual--sticky { position: static; }
}

@media (max-width: 1024px) and (min-width: 901px) {
  .aside-media__visual.media-frame { max-width: 440px; }
  .aside-media__visual--wide.media-frame { max-width: 460px; }
}
@media (max-width: 900px) {
  .aside-media--right, .aside-media--left {
    grid-template-columns: 1fr;
    grid-template-areas: "intro" "visual" "body" "extra";
    gap: var(--sp-3);
  }
  .aside-media__visual.media-frame,
  .aside-media__visual--wide.media-frame,
  .aside-media__visual--subordinate.media-frame {
    max-width: 360px;
  }
  .aside-media__visual.media-frame img { width: 84vw; max-width: 360px; margin-inline: auto; }
}

/* ---- BuyPromptBoard: ledger + buying-brief object ---- */
.buying-brief__grid {
  display: grid;
  grid-template-columns: 1fr 0.8fr;
  gap: var(--sp-5);
  align-items: center;
}
@media (max-width: 900px) { .buying-brief__grid { grid-template-columns: 1fr; } }

/* The ledger here runs longer than the figure, so the image can stick
   in view while the ledger scrolls past — same nested-slot technique
   as .sticky-visual/.aside-media--sticky. */
.buying-brief__grid.buying-brief__grid--sticky { align-items: stretch; }
.buying-brief__grid__visual--sticky {
  position: sticky;
  top: 96px;
  align-self: start;
}
@media (max-width: 900px) {
  .buying-brief__grid__visual--sticky { position: static; }
}

/* JS-only motion reveal (see js/main.js). Never a static default: an
   element only gets this class added by script, and the class itself
   only sets the *starting* state — a no-JS visitor, or a page where
   the observer never fires for some reason, keeps full opacity via
   the [data-reveal] default below. prefers-reduced-motion is already
   handled globally (transition-duration clamped near the top of this
   file), so no separate override is needed here. */
[data-reveal] {
  opacity: 1;
  transform: none;
}
[data-reveal].is-pending {
  opacity: 0;
  transform: translateY(8px);
}
[data-reveal].is-visible {
  opacity: 1;
  transform: none;
  transition: opacity 420ms var(--ease), transform 420ms var(--ease);
}
