/* ============================================================
   CredX v2 — Design Tokens + Base + Components
   Source: design/tokens-from-brandguide-v2.md (Phase 3 locks)
   Surface: dark navy enterprise (primary). Light surface variant supported.
   ============================================================ */

/* ---- 1. Tokens ---- */

:root {
  /* Brand colors (Moodboard p8 — locked) */
  --navy: #323665;
  --magenta: #943f78;
  --purple: #6d5ea4;
  --soft-lavender: #a79ab4;
  --ink: #0b0c16;
  --cloud-white: #f8f9fc;

  /* Surface system (dark — primary v2 per §0) */
  --surface-0: var(--ink);           /* Primary page surface */
  --surface-1: #13141f;              /* Card surface, raised panel */
  --surface-2: #1a1c2c;              /* Hover / selected */
  --surface-3: #23253a;              /* Divider / outline */
  --surface-navy: var(--navy);       /* Strong navy accent surface */
  --surface-light: var(--cloud-white); /* Light variant for occasional cards */

  /* Type-on-dark accent — brighter than --magenta so it passes AA when
     used as text on --ink. --magenta stays for surfaces (button bg, slider
     thumb, gradient stop) where it carries text contrast on top of itself. */
  --magenta-bright: #d667ad;

  /* Text colors on dark surface */
  --text-primary: var(--cloud-white);
  --text-secondary: var(--soft-lavender);
  --text-muted: rgba(167, 154, 180, 0.85);
  --text-accent: var(--magenta-bright);

  /* Text colors on light surface (variant) */
  --text-primary-light: var(--ink);
  --text-secondary-light: var(--navy);

  /* Borders & dividers */
  --border-subtle: rgba(167, 154, 180, 0.15);
  --border-default: var(--surface-3);
  --border-strong: var(--navy);

  /* Gradient (cinematic retune per D5 — softer stops, atmospheric) */
  --gradient-1: linear-gradient(
    135deg,
    var(--navy) 0%,
    var(--navy) 25%,
    var(--purple) 55%,
    var(--magenta) 95%
  );

  /* Typography — locked to 2 families 2026-05-28 (drop Poppins). Inter
     covers display + body; Fraunces is editorial accent only at exactly
     4 selectors (S1 cover title · S3 movement H2 · S6 KPI values · S6
     quote text). See memories project_typography_v2_lock + project_fraunces_reopened. */
  --font-display: 'Inter', 'Inter Display', system-ui, -apple-system, sans-serif;
  --font-body: 'Inter', 'Inter Display', system-ui, -apple-system, sans-serif;
  --font-editorial: 'Fraunces', Georgia, 'Times New Roman', serif;

  /* Type scale (D2 — clamp() responsive) */
  --type-display: clamp(2.5rem, 5.5vw + 1rem, 4.5rem);   /* 40–72px */
  --type-h1: clamp(2rem, 3vw + 1rem, 3rem);              /* 32–48px */
  --type-h2: clamp(1.5rem, 2vw + 0.75rem, 2.25rem);      /* 24–36px */
  --type-h3: clamp(1.25rem, 0.75vw + 1rem, 1.5rem);      /* 20–24px */
  --type-body-lg: 1.125rem;                              /* 18px */
  --type-body: 1rem;                                     /* 16px */
  --type-caption: 0.875rem;                              /* 14px */
  --type-eyebrow: 0.75rem;                               /* 12px */
  --type-data-callout: clamp(3rem, 5vw + 1rem, 5rem);    /* 48–80px */

  /* Spacing (4px base — D6) */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;
  --s-8: 64px;
  --s-9: 96px;
  --s-10: 128px;
  /* ---- SECTION RHYTHM · rewritten 2026-08-19 (Marco: excessive and irregular) ----
     WAS: `clamp(64px, 10vh, 128px)` — keyed to viewport HEIGHT, which is why the page
     felt irregular. Measured on the Home at 1424×905 it resolved to 90.5px, so every
     boundary (padB + padT) was 181px, and 229px at `.movement`, 205px at `.join`.
     Resize the window vertically and the whole vertical rhythm moved. Worse, 90.5 is
     not a step on the 4px scale, so NO section padding on the site landed on the
     spacing system at all — 91, 139 and 115 were the real numbers.
     NOW: two token steps, chosen at a WIDTH breakpoint like `--container-pad` already
     is. Every section padding is now exactly `--s-7` or `--s-8`, boundaries are 96px
     on mobile and 128px from 960px up, and nothing changes when the window is
     resized vertically. Desktop boundary goes 181px → 128px, a 29% reduction. */
  --s-section: var(--s-7);

  /* Container */
  --container-max: 1280px;
  --container-pad: clamp(16px, 4vw, 48px);

  /* Border radius */
  --radius-pill: 999px;
  --radius-card: 24px;
  --radius-card-sm: 16px;
  --radius-small: 8px;

  /* Shadow (subtle — measured/structural per Moodboard p24) */
  --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.2);
  --shadow-2: 0 4px 12px rgba(0, 0, 0, 0.25);
  --shadow-3: 0 12px 32px rgba(0, 0, 0, 0.35);

  /* Motion */
  --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  /* Both curves above are heavily front-loaded: expo is ~85% resolved by 25% of the
     duration, quart ~78%. Great for entrances that should feel instant, wrong for a
     hover reveal the pointer is holding — the eye reads a snap plus a dead tail.
     This one keeps velocity through the middle so a long duration actually looks long.
     Added 2026-07-27 for the §2 voice-card reveal. */
  --ease-reveal: cubic-bezier(0.32, 0.72, 0.28, 1);
  --duration-fast: 200ms;
  --duration-base: 400ms;
  --duration-slow: 800ms;

  /* Z-index scale */
  --z-base: 0;
  --z-elevated: 10;
  --z-nav: 50;
  --z-modal: 100;

  /* Nav surface (theme-aware) */
  --nav-bg: rgba(11, 12, 22, 0.7);
  --nav-bg-scrolled: rgba(11, 12, 22, 0.92);
}

/* The one width step for the section rhythm. Above 960px sections breathe one token
   more; below it they do not, because a phone cannot afford 128px of nothing between
   every section. Deliberately a single breakpoint — a scale with more steps than this
   is unpredictable again, which was the original problem. */
@media (min-width: 960px) {
  :root { --s-section: var(--s-8); }
}

/* ---- 1b. Light theme overrides (dark is the default above) ----
   Toggle adds [data-theme="light"] on <html>. Surfaces + text flip; the
   two cinematic bands (.movement, .final-cta) stay dark in BOTH themes by
   design — they are the page's dark punctuation. Buttons use the
   surface-aware mapping below. */
[data-theme="light"] {
  --surface-0: #f4f5f9;              /* Primary page surface */
  --surface-1: #ffffff;              /* Card surface, raised panel */
  --surface-2: #eceef4;              /* Hover / selected */
  --surface-3: #dce0ea;              /* Divider / outline */

  --text-primary: var(--ink);
  --text-secondary: #4a4f73;         /* navy-slate, ~7:1 on white */
  --text-muted: rgba(40, 44, 80, 0.62);
  /* Accent on light must be the darker magenta (bright fails AA on white) */
  --text-accent: var(--magenta);

  --border-subtle: rgba(40, 44, 80, 0.12);
  --border-default: #dce0ea;
  --border-strong: var(--navy);

  --nav-bg: rgba(244, 245, 249, 0.72);
  --nav-bg-scrolled: rgba(244, 245, 249, 0.93);

  --shadow-1: 0 1px 2px rgba(20, 24, 50, 0.06);
  --shadow-2: 0 4px 12px rgba(20, 24, 50, 0.08);
  --shadow-3: 0 12px 32px rgba(20, 24, 50, 0.12);

  color-scheme: light;
}

/* ---- 2. Reset + base ---- */

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

html {
  font-size: 16px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

body {
  font-family: var(--font-body);
  font-size: var(--type-body);
  line-height: 1.5;
  color: var(--text-primary);
  background-color: var(--surface-0);
  overflow-x: hidden;
  text-wrap: pretty;
}

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

/* <picture> is just a container — make it transparent to layout so the
   inner <img> inherits the slot sizing rules. */
picture { display: contents; }

a {
  color: inherit;
  text-decoration: none;
}

button {
  font-family: inherit;
  cursor: pointer;
  background: none;
  border: none;
  color: inherit;
}

/* Headings — editorial serif (Fraunces) for all narrative titles. Data
   callouts (.stat__number, .pain__callout-number, .calc__*-number etc.)
   override back to Inter via their own font-family rule. Sentence/Title
   case enforced editorially per the copy proof. */
h1, h2, h3, h4 {
  font-family: var(--font-editorial);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.005em;
  font-optical-sizing: auto;
  text-wrap: balance;
  color: var(--text-primary);
}

h1 { font-size: var(--type-display); font-weight: 600; letter-spacing: -0.015em; }
h2 { font-size: var(--type-h1); font-weight: 500; }
h3 { font-size: var(--type-h2); font-weight: 500; }
h4 { font-size: var(--type-h3); font-weight: 500; }

p { line-height: 1.6; max-width: 65ch; }

/* ---- 3. Accent (color-only magenta per B1 v2 lock — no italic) ---- */

.accent {
  color: var(--text-accent);
  font-style: normal;
}

/* On gradient surfaces the accent magenta competes with the gradient's own
   magenta end-stop and disappears. Switch to cloud-white inside gradient
   bands; emphasis stays via the fact that the surrounding type is dimmer
   cloud-white at 92% opacity, while accent is full opacity. */
.movement .accent,
.final-cta .accent {
  color: var(--cloud-white);
  opacity: 1;
}

/* ---- 4. Layout primitives ---- */

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

.section {
  padding-block: var(--s-section);
  position: relative;
}

.eyebrow {
  display: inline-block;
  font-size: var(--type-eyebrow);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-secondary);
  margin-bottom: var(--s-4);
}

.eyebrow--accent { color: var(--text-accent); }

.sub-link {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  color: var(--text-accent);
  font-weight: 500;
  font-size: var(--type-body);
  margin-top: var(--s-5);
  transition: gap var(--duration-fast) var(--ease-out-quart);
}

.sub-link:hover { gap: var(--s-3); }
.sub-link::after { content: '→'; transition: transform var(--duration-fast) var(--ease-out-quart); }

/* ---- 5. Buttons (D3 surface-aware mapping) ---- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: var(--s-4) var(--s-6);
  font-family: var(--font-body);
  font-size: var(--type-body);
  font-weight: 600;
  line-height: 1;
  border-radius: var(--radius-pill);
  transition: transform var(--duration-fast) var(--ease-out-quart),
              background-color var(--duration-fast) var(--ease-out-quart),
              border-color var(--duration-fast) var(--ease-out-quart);
  white-space: nowrap;
}
/* ⚠ `nowrap` gives the long CTAs a 414-436px min-content, wider than a 390px
   viewport's 358px column. Measured 2026-08-18: 12px of real overflow on /about,
   and on /how-it-works it sized the whole hero copy block to 436px inside 358px —
   the H1 lost letters and EVERY line of the body was cut, invisible to a
   scrollWidth check because .hero--compact clips. Labels this long must wrap. */
@media (max-width: 560px) {
  .btn { white-space: normal; text-align: center; }
}

.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

/* On dark surface (default v2) */
.btn-primary {
  background-color: var(--magenta);
  color: var(--cloud-white);
}
.btn-primary:hover { background-color: var(--purple); }

.btn-secondary {
  background-color: transparent;
  color: var(--cloud-white);
  border: 1.5px solid var(--cloud-white);
}
.btn-secondary:hover { background-color: var(--cloud-white); color: var(--ink); }

.btn-text {
  padding: var(--s-2) var(--s-3);
  color: var(--text-accent);
  background: none;
}
.btn-text:hover { color: var(--purple); }

/* On light surface (theme toggle) */
[data-theme="light"] .btn-primary {
  background-color: var(--navy);
  color: var(--cloud-white);
}
[data-theme="light"] .btn-primary:hover { background-color: var(--purple); }

[data-theme="light"] .btn-secondary {
  background-color: transparent;
  color: var(--navy);
  border: 1.5px solid var(--navy);
}
[data-theme="light"] .btn-secondary:hover { background-color: var(--navy); color: var(--cloud-white); }

/* The .hero--full keeps its dark cinematic treatment in both themes (the hero
   image is dark-graded until a light hero render lands), so its overlaid
   secondary button stays light-on-dark regardless of theme. */
/* Hero primary keeps brand magenta in both themes for punch on the photo. */
[data-theme="light"] .hero--full .btn-primary { background-color: var(--magenta); }
[data-theme="light"] .hero--full .btn-primary:hover { background-color: var(--purple); }

/* Reusable centered CTA row (Movement §2, Gains §4 — more CTA touchpoints per Audrey) */
.section-ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  justify-content: center;
  margin-top: var(--s-8);
}


/* Keep the nav uncrowded on small screens — drop the secondary demo button there
   (Book a Demo still appears in the Movement, Gains, and footer touchpoints). */
@media (max-width: 640px) { .nav__cta--demo { display: none; } }

/* ---- 5b. Theme toggle ---- */

.nav__actions {
  display: flex;
  align-items: center;
  gap: var(--s-4);
}

.theme-toggle {
  display: inline-grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: var(--radius-pill);
  color: var(--text-secondary);
  border: 1px solid var(--border-subtle);
  background-color: transparent;
  transition: color var(--duration-fast) var(--ease-out-quart),
              border-color var(--duration-fast) var(--ease-out-quart),
              background-color var(--duration-fast) var(--ease-out-quart);
}
.theme-toggle:hover { color: var(--text-primary); border-color: var(--border-default); }
.theme-toggle__icon { display: none; }
/* Dark theme shows the sun (offer to brighten); light shows the moon. */
.theme-toggle__icon--sun { display: block; }
[data-theme="light"] .theme-toggle__icon--sun { display: none; }
[data-theme="light"] .theme-toggle__icon--moon { display: block; }

/* ---- 6. Navigation ---- */

.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--z-nav);
  padding-block: var(--s-4);
  background-color: var(--nav-bg);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border-subtle);
  transition: background-color var(--duration-base) var(--ease-out-quart);
}

.nav.is-scrolled { background-color: var(--nav-bg-scrolled); }

.nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
}

.nav__logo {
  display: block;
  height: 28px;
  width: auto;
}

/* The CredX wordmark SVG has only a viewBox (no width/height), so the global
   `img { height: auto }` reset above collapses it to zero — set an explicit
   size. It also ships a fixed dark-navy fill (#062641) invisible on dark
   surfaces, so recolor per theme: white on dark (default), navy on light.
   Covers nav + footer (both flip with the theme). */
.nav__logo img,
.footer img[src$="Credx-name.svg"] {
  width: auto;
  filter: brightness(0) invert(1);
}

.nav__logo img { height: 28px; }
.footer img[src$="Credx-name.svg"] { height: 32px; }

[data-theme="light"] .nav__logo img,
[data-theme="light"] .footer img[src$="Credx-name.svg"] {
  filter: none;
}

.nav__links {
  display: none;
  align-items: center;
  gap: var(--s-6);
}

.nav__link {
  font-size: var(--type-caption);
  font-weight: 500;
  color: var(--text-secondary);
  transition: color var(--duration-fast) var(--ease-out-quart);
}
.nav__link:hover { color: var(--text-primary); }
/* ⚠ This rule existed since the nav was built and was applied on ZERO pages
   (found 2026-08-18). A reader had no signal of which page they were on, which
   is most of what "the pages feel the same" actually was. It reuses the hover
   underline rather than introducing a second indicator — the active page simply
   holds the state hover is reaching for. */
.nav__link--active { color: var(--text-primary); }
/* ⚠ The matching `::after { width: 100% }` lived here and NEVER DREW —
   `.nav__link::after { width: 0 }` further down has identical specificity (0,1,1)
   and later wins. What shipped was colour alone: a WCAG 1.4.1 failure. Moved. */

.nav__cta {
  padding: var(--s-2) var(--s-5);
  font-size: var(--type-caption);
}

/* ⚠ THE NAV'S DESKTOP THRESHOLD MOVED 768 → 960 ON 2026-08-20, and every rule
   that keyed off 768 moved with it. Reason, measured by rendering at 820: the
   nav went from four flat items to FIVE plus an audience group on the same day,
   and "Embedded Value Platform" alone is 22 characters. At 820 the row overflowed
   — the CTA was cut off and the logo pushed out of frame. Tightening the gap was
   not enough and shrinking the type would have been papering over it. The honest
   fix is that a bigger nav collapses to the sheet sooner.
   🔴 THESE FOUR BREAKPOINTS ARE ONE DECISION. If any of them moves again, all of
   them move: .nav__links display · .nav__toggle display · the sheet block · the
   audience-group flatten (and its matchMedia twin in script.js). */
@media (min-width: 960px) {
  .nav__links { display: inline-flex; }
}

/* ---- 7. Hero (S1) ---- */

.hero {
  padding-block: calc(var(--s-section) + var(--s-7)) var(--s-section);
  background-color: var(--surface-0);
  position: relative;
  overflow: hidden;
}

.hero__inner {
  display: grid;
  gap: var(--s-7);
}

@media (min-width: 1024px) {
  .hero__inner {
    grid-template-columns: 1.1fr 1fr;
    align-items: center;
    gap: var(--s-9);
  }
}

/* Full-bleed hero variant (image carries the entire section, copy overlaid left).
   Follows the cover scrub prologue as the formal lead-form moment. */
.hero--full {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  padding-block: 0;
}

.hero--full .hero__bg {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: right center;
  z-index: 0;
}

.hero--full .hero__overlay {
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    var(--ink) 0%,
    rgba(11, 12, 22, 0.85) 30%,
    rgba(11, 12, 22, 0.35) 60%,
    transparent 100%);
  z-index: 1;
  pointer-events: none;
}

.hero--full .hero__inner {
  position: relative;
  z-index: 2;
  display: block;
  width: 100%;
}

.hero--full .hero__copy { max-width: 560px; }

/* Hero background swaps by theme: dark dusk image (default) / light daylight
   image. h1, eyebrow and accent follow theme tokens (cloud-white/bright-magenta
   on dark; ink/magenta on light). The sub gets an explicit tone per theme so
   it stays legible over the photo's negative-space side. */
.hero__bg--light { display: none; }
[data-theme="light"] .hero__bg--dark { display: none; }
[data-theme="light"] .hero__bg--light { display: block; }

.hero--full .hero__sub { color: rgba(248, 249, 252, 0.92); }

/* ---- hero--compact · the Pillar–Read opening (Phase 6 §6.2) ----
   Measured 2026-08-18: `.hero--full` opened the Home, all three pillar pages and
   all four verticals identically — nine pages, one opening. This is the Read
   type's own: type-led, no photograph behind the copy, no 100vh.
   Two things fall out of it for free. The copy column stops competing with an
   image so it widens from 560px to the prose measure. And on /how-it-works the
   49-word AEO paragraph — the page's most quotable asset, whose placement
   directly under the H1 is mandated and must not be moved — rises above the fold
   on a 900px laptop without touching the copy at all. */
.hero--compact {
  background-color: var(--surface-0);
  padding-block: calc(var(--s-section) + var(--s-8)) var(--s-section);
  display: block;
  min-height: 0;
}
/* ⚠ Measure is set per element, NOT on the wrapper. A single max-width on
   .hero__copy clamps the paragraph to the headline's measure and the AEO
   paragraph wrapped into eight short lines — worse reading than the hero it
   replaced. The headline wants a narrow, tall, ragged column; the paragraph
   wants a reading measure. They are different jobs. */
.hero--compact .hero__copy { max-width: none; }
.hero--compact .hero__h1 {
  font-size: clamp(2.75rem, 6vw, 4.5rem);
  max-width: 15ch;
}
.hero--compact .hero__sub {
  font-size: var(--type-body-lg);
  max-width: 58ch;
  margin-top: var(--s-6);
}
.hero--compact .hero__movement { max-width: 46ch; }

/* ---- .hero--split · the /how-it-works opening -------------------------------
   Copy and a framed photograph side by side, roughly even. This is the third
   distinct opening on the site and it exists because /about and /how-it-works
   were the one pair still sharing a composition — the 2026-08-18 audit's
   sharpest point: crop the nav and nothing sorted them.
   ⚠ The AEO paragraph's placement directly under the H1 is mandated and must not
   move. A 50/50 split narrows its column, so the measure is set explicitly here
   rather than inherited — check it still reads before changing the ratio. */
.hero--split { padding-block: calc(var(--s-section) + var(--s-7)) var(--s-section); }
.hero--split .hero__inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-8);
  align-items: center;
}
@media (min-width: 960px) {
  .hero--split .hero__inner {
    grid-template-columns: 1fr 1fr;
    gap: var(--s-9);
  }
}
.hero--split .hero__copy { max-width: none; }
/* ⚠ This was clamped to 3.75rem to suit the half-width column, which is exactly
   what `.prose-band__h2` also resolves to — both landed on 60px at 1440 and the
   section headings read as large as the page title. The H1 keeps the display
   face; the column is narrow, so it wraps, which is what a display face is for. */
.hero--split .hero__h1 { font-size: var(--type-display); max-width: 12ch; }
.hero--split .hero__sub { font-size: var(--type-body-lg); margin-top: var(--s-6); max-width: none; }
.hero--split .hero__movement { margin-top: var(--s-6); }
.hero--split .hero__ctas {
  /* ⚠ 48px, not 64. `.hero__movement` sets `margin-bottom: var(--s-7)` and a comment
     from 2026-07-27 records WHY — the line must read as its own beat with EQUAL air on
     both sides, never as a label for the buttons. At `--s-8` the collapsed gap below it
     was 64px against 48px above, so the documented intent was quietly broken on the
     pillar pages while the Home (`.hero--full`) still honoured it at 48/48. Measured on
     both. This restores the intent and takes 16px out. */
  margin-top: var(--s-7);
}

/* The frame. One elevation only — a border, no shadow under it. */
.hero__frame {
  border-radius: var(--radius-card);
  border: 1px solid var(--border-subtle);
  overflow: hidden;
  aspect-ratio: 4 / 5;
  background-color: var(--surface-1);
}
.hero__frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (max-width: 959px) { .hero__frame { aspect-ratio: 16 / 10; } }

/* ---- .hero__frame--mirrored ------------------------------------------------
   `/about`, 2026-08-19. `about-data-economy.png` has the operator turned to her
   left with her gaze leaving the frame to the RIGHT. In the left-copy split that
   aimed her away from the H1, at the outer edge of the page — a subject looking
   out of the composition pulls the reader out with her. Flipped, the gaze runs
   back into the headline.

   ⚠ Order of operations, because it is not obvious: `object-fit: cover` resolves
   the crop against the frame's aspect ratio FIRST, then this transform flips the
   already-cropped box. So mirroring does not change WHICH part of the photograph
   is visible, only its handedness — the 4:5 centre crop is identical either way.
   ⚠ Only ever use this on an image with no legible text, signage or lettering in
   it. This one has none; a mirrored sign would be an obvious defect. */
.hero__frame--mirrored .hero__frame-flip {
  display: block;
  width: 100%;
  height: 100%;
  transform: scaleX(-1);
}
/* ⚠ The flip is on a WRAPPER, never on the <img>, and that is not a style choice.
   `transform` on a `[data-reveal-media] img` has two owners that both outrank a
   stylesheet rule here:
     · line ~2909, `[data-reveal-media] img { transform: none }` under
       `prefers-reduced-motion` — equal specificity to `.class img`, declared later,
       so it wins;
     · `script.js:184/189`, GSAP `set(img, {scale: 1.25})` → `scale: 1`, written
       INLINE, which outranks the whole stylesheet.
   A flip declared on the img therefore renders for exactly one visitor: JS off AND
   no reduced-motion preference. It was written that way first and looked correct in
   a screenshot; the render was simply not mirrored. Verified the second time by
   cropping the rendered head and comparing it against the source file.
   The wrapper's own transform is contested by nothing. */

/* ---- .split · a section of prose beside a framed photograph ------------------
   The body counterpart to .hero--split. It exists so a reading section can use
   the full width instead of setting prose left and leaving the right half void,
   which was the shape every pillar section shared.
   `--reverse` puts the photograph on the LEFT. Alternating it against the hero
   above gives the page a rhythm instead of one repeated orientation. */
.split__inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-8);
  align-items: center;
}
@media (min-width: 960px) {
  .split__inner { grid-template-columns: 1fr 1fr; gap: var(--s-9); }
  .split--reverse .split__media { order: -1; }
}
.split__body { max-width: 58ch; }
.split__body .prose-band__h2 { text-align: left; margin-inline: 0; max-width: 18ch; }
.split__body > p + p { margin-top: var(--s-6); }
.split__media {
  border-radius: var(--radius-card);
  border: 1px solid var(--border-subtle);
  overflow: hidden;
  aspect-ratio: 4 / 5;
  background-color: var(--surface-1);
}
.split__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (max-width: 959px) { .split__media { aspect-ratio: 16 / 10; } }
/* `--wide` keeps a 16:9 source at 16:9. The default 4:5 exists for portrait-composed
   renders (§4's `about-mission` was delivered at 1122×1402 for it); forcing a 16:9
   asset into it crops width, which is the defect class that cost `hiw-hero` its
   operator on 2026-08-18. Use this whenever the asset is landscape. */
.split__media--wide { aspect-ratio: 16 / 9; }

/* ---- --fill · the media takes the row's full height ------------------------
   Added 2026-08-24 (Marcos, on /lenders §7: "acho que essa imagem pode ocupar a
   altura toda"). Every other `.split__media` is a FRAME of a fixed ratio, which
   is right when the photograph is the subject. Here the prose column is the
   taller of the two — headline, two paragraphs and the province grid — and a
   16:9 frame beside it left the image floating in the top third with the column
   empty beneath it.
   ⚠ `align-self: stretch` is what does the work, not `height: 100%`. The wrapper
   has no definite height of its own, so a percentage height would resolve
   against `auto` and collapse; stretch gives it the grid row, and the img's
   existing `height: 100%` + `object-fit: cover` then fill it.
   ⚠ `aspect-ratio: auto` is REQUIRED — the base sets 4/5 and `--wide` 16/9, and
   either one wins over a stretched height and re-collapses the box.
   ⚠ SCOPED to ≥960 on purpose. Below that the split stacks and the row height is
   the image's own, so stretch means nothing and the mobile 16/10 ratio from the
   base component is what should govern. The crop is `cover`, so a landscape
   master loses width, not height, as the column gets taller. */
@media (min-width: 960px) {
  .split__media--fill {
    aspect-ratio: auto;
    align-self: stretch;
  }
}

/* ---- --focus-top · mobile-crop insurance for both portrait frames ------------
   For a portrait asset whose faces sit high in the frame. `.split__media` AND
   `.hero__frame` are both 4:5 on desktop and **16/10 below 960px**, so a 1122×1402
   master keeps every pixel at desktop and loses 50% of its HEIGHT on mobile:
   `cover` shows only y 350→1051 of 1402, centred. One declaration serves both
   components — they have the same ratio switch and therefore the same trap.

   ⚠ Measured on `about-record.png`: the operator's head occupies y≈155→430, so the
   default centred crop cut him just below the eyes — beard and mouth visible, eyes
   gone. That is the same defect that decapitated a figure in `hiw-loop` on
   2026-08-18, arriving by a different route: not a bad master, a bad anchor.

   `center 20%` moves the visible band to y≈140→841, which holds both heads. The
   tray of shears at the bottom is sacrificed, which is the right trade — a
   signature prop is worth less than a face.
   ⚠ It costs nothing at desktop: there the frame ratio equals the asset ratio, so
   `cover` crops nothing and `object-position` has no effect at all. Safe to leave on.
   ⚠ Check this on any NEW portrait asset dropped into a `.split__media`. The default
   is only correct when the subject is vertically centred. */
.hero__frame--focus-top img,
.split__media--focus-top img { object-position: center 20%; }
/* Inside a split the column is ~half the page, so the elements that carry the
   prose measure must stop carrying it or they overflow their own column. */
.split__body .prose-band__body,
.split__body .consent-note,
.split__body .figure-note { max-width: none; }
.split__body .consent-note { margin-inline: 0; }

/* Centred variant of the Read opening. Used by the 404: one message, one way out.
   ⚠ `.hero__inner` is a GRID, so `margin-inline: auto` on the copy did not
   centre it — measured at L120 in a 1280 track, hard against the start edge.
   Grid items centre with justify-items, not with auto margins. */
.hero--centred { text-align: center; }
/* ⚠ Took three attempts and a measurement to get right, so it is written down.
   `.hero__inner` is a grid with no declared columns, so its single implicit
   track is auto-sized to the content (570px) and anchored at the start. Neither
   `justify-items: center` nor `margin-inline: auto` moves anything, because both
   position the item INSIDE its track and the track is already only as wide as
   the item. What centres it is `justify-content`, which positions the TRACK. */
.hero--centred .hero__inner { display: block; }
.hero--centred .hero__copy {
  width: fit-content;
  max-width: 100%;
  margin-inline: auto;
}
.hero--centred .hero__h1,
.hero--centred .hero__sub,
.hero--centred .hero__movement { margin-inline: auto; }
.hero--centred .hero__ctas { justify-content: center; }

/* ---- .hero--alert · the 404 opening ----------------------------------------
   A 404 that looks like every other page is a 404 the visitor reads as content.
   It has to announce itself as an interruption — but inside a closed token
   system, with no alert red to reach for and none coming.
   The system already owns the right surface: Phase 6 §1.5 reserves the gradient
   for PUNCTUATION, hero and CTA only. No page opens on it. So the 404 opening on
   the gradient band is instantly not-a-content-page, using nothing new, and it
   inherits the mark treatment from the closing band for the same reason. */
.hero--alert {
  --mark-size: 150%;
  --mark-x: 88%;
  --mark-opacity: 0.14;
  background: var(--gradient-1);
  position: relative;
  overflow: hidden;
}
.hero--alert::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 70% 30%, rgba(167, 154, 180, 0.15), transparent 55%),
    radial-gradient(ellipse at 30% 70%, rgba(148, 63, 120, 0.20), transparent 50%);
  pointer-events: none;
}
.hero--alert::after {
  content: '';
  position: absolute;
  inset: -12% -6%;
  background-image: url('assets/logo/credx-shape.svg');
  background-repeat: no-repeat;
  background-position: var(--mark-x) 50%;
  background-size: auto var(--mark-size);
  filter: brightness(0);
  opacity: var(--mark-opacity);
  pointer-events: none;
}
.hero--alert .hero__inner { position: relative; z-index: 1; }
.hero--alert .hero__h1, .hero--alert .hero__sub { color: var(--cloud-white); }

/* The status itself, stated rather than decorated. Not an eyebrow: it follows
   the headline, it carries information the headline does not, and it is the one
   string a person pastes into a message when they report the problem. */
.hero__status {
  display: inline-block;
  margin-top: var(--s-6);
  font-size: var(--type-caption);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: rgba(248, 249, 252, 0.72);
  border: 1px solid rgba(248, 249, 252, 0.28);
  border-radius: var(--radius-pill);
  padding: var(--s-2) var(--s-5);
}
.hero--compact .hero__movement { margin-top: var(--s-6); }
.hero--compact .hero__ctas { margin-top: var(--s-7); } /* see .hero--split, same reason */

/* The photograph is not deleted, it is demoted: the page opens on type and the
   image is the first thing you scroll INTO. Full-bleed, so it reads as a band
   rather than as another card in the column. */
.hero-media {
  margin-top: 0;
  aspect-ratio: 21 / 9;
  overflow: hidden;
  background-color: var(--surface-1);
}
.hero-media img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- Pillar–Sell keeps a photographic hero, capped ----
   /evp is the commercial page, so it keeps the image — but not 100vh. Letting
   the next section show under the fold is what says "the stack is below". */
/* ⚠ .hero--full centres its copy; capping the height without adding top room
   pushed the H1 hard against the nav. */
.hero--capped {
  min-height: 0;
  height: clamp(32rem, 78vh, 46rem);
  padding-block-start: var(--s-8);
}

/* ---- .stack · /evp signature (Phase 6 §6.3) ------------------------------
   The seven components were seven equal cards in a 3-column grid, which left
   Marketing orphaned on row three (EV-05) and said nothing about how the parts
   relate (EV-20). They are not peers, and the page itself proves it: MoaT and
   DaaP each get a whole section below; the others get a line.

   So the tiers ARE the content, which is the bento rule from §6.4 — a tile is
   larger because the thing is larger, never because the grid had a hole:
     tier 1  MoaT · DaaP                       load-bearing, own sections
     tier 2  Cost Reduction · BoosT            the other two of the four things
     tier 3  Marketplace · Orchestration · Marketing   the connective layer
   2 + 2 + 3 = 7. No orphan, and the silhouette reads as a hierarchy rather than
   as the Home's .proof__mosaic, which is a scattered wall of proof. */
.stack { display: grid; gap: var(--s-5); }
.stack__tier { display: grid; gap: var(--s-5); grid-template-columns: 1fr; }
@media (min-width: 768px) {
  .stack__tier--lead { grid-template-columns: repeat(2, 1fr); }
  .stack__tier--mid  { grid-template-columns: repeat(2, 1fr); }
  .stack__tier--leaf { grid-template-columns: repeat(3, 1fr); }
}
.stack__item {
  padding: var(--s-7);
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
}
/* Rank is carried by weight and air, not by a second border or a shadow —
   elevation is declared once on this component. */
.stack__item--lead { padding: var(--s-8); background-color: var(--surface-2); }
.stack__item--lead .gain__title { font-size: var(--type-h2); }
.stack__item--mid  .gain__title { font-size: var(--type-h3); }
.stack__item--leaf { padding: var(--s-6); }
.stack__item--leaf .gain__title { font-size: var(--type-h3); }
.stack__item--leaf .gain__body { font-size: var(--type-caption); }

.stack__tier-label {
  font-size: var(--type-caption);
  color: var(--text-muted);
  margin-bottom: var(--s-4);
  display: block;
}

/* ---- .stack__item--photo · /evp §2 lead tiles, added 2026-08-19 -------------
   The two --lead tiles (MoaT, DaaP) carry a photograph behind the same
   title + body markup instead of a flat surface — bento in the literal
   sense, not just in silhouette. A gradient does the contrast work so the
   source photo only has to be quiet in its lower third, not pre-darkened. */
.stack__item--photo {
  position: relative;
  overflow: hidden;
  min-height: 22rem;
  padding: var(--s-8) var(--s-6) var(--s-6);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  background-color: var(--surface-2);
}
.stack__item--photo .stack__item-media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}
.stack__item--photo::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(10, 12, 18, 0.88) 0%, rgba(10, 12, 18, 0.45) 55%, rgba(10, 12, 18, 0) 100%);
  z-index: 1;
}
.stack__item--photo .gain__title,
.stack__item--photo .gain__body {
  position: relative;
  z-index: 2;
  color: #f8f9fc;
}
.stack__item--photo .gain__body { color: rgba(248, 249, 252, 0.92); }

/* ---- .rail · /how-it-works signature (Phase 6 §6.3) ------------------------
   The page's whole subject is ORDER OF EVENTS, and it was rendering as four
   identical stacked cards — the same shape the Home uses for things that have
   no order at all. A card says "item". A rail says "then".

   ⚠ 2026-08-18, second pass: the rail was vertical, which left the right half of
   the section empty and drew every divider 1184px wide over a ~600px column.
   It is now HORIZONTAL from 960px: four columns on one line, the numerals
   sitting on it, the text hanging below. Same argument, full width, and still
   not cards — there is no container, only the line and what hangs off it.
   §6.4's caveat on section numbers is satisfied: on this page the sequence IS
   the information. Numerals are tabular so the row does not jitter. */
/* ⚠ It is an <ol>, so the UA draws its own "1." beside every numeral. Third time
   this trap has bitten in one day: `list-style: none` alone still leaves the UA's
   40px padding-inline-start, so both go. */
.rail {
  display: grid;
  gap: 0;
  position: relative;
  list-style: none;
  padding-inline-start: 0;
  margin: 0;
}

.rail__station { position: relative; }
.rail__marker { position: relative; display: flex; }
.rail__num {
  font-family: var(--font-display);
  font-variant-numeric: tabular-nums;
  font-size: var(--type-h2);
  font-weight: 700;
  line-height: 1;
  color: var(--text-accent);
}
.rail__h3 { font-size: var(--type-h3); margin-bottom: var(--s-3); }
.rail__text { color: var(--text-secondary); line-height: 1.6; }

/* ---- stacked, below 960: the numeral sits beside the text on a vertical spine */
@media (max-width: 959px) {
  .rail__station {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: var(--s-6);
    padding-block: var(--s-7);
  }
  .rail__station + .rail__station { border-top: 1px solid var(--border-subtle); }
  .rail__marker { width: 3.5rem; justify-content: center; }
  .rail__marker::after {
    content: '';
    position: absolute;
    top: calc(var(--type-h2) + var(--s-4));
    bottom: calc(var(--s-7) * -2);
    width: 1px;
    background-color: var(--border-subtle);
    transform: scaleY(var(--spine-scale, 1));
    transform-origin: top;
  }
  .rail__station:last-child .rail__marker::after { content: none; }
  .rail__body { max-width: 60ch; }
}
@media (max-width: 640px) {
  .rail__station { grid-template-columns: 1fr; gap: var(--s-3); }
  .rail__marker { width: auto; }
  .rail__marker::after { content: none; }
}

/* ---- horizontal, 960 and up: four columns on one line ---- */
@media (min-width: 960px) {
  .rail { grid-template-columns: repeat(4, 1fr); gap: var(--s-7); }
  .rail__station { padding-top: var(--s-7); }
  .rail__marker { margin-bottom: var(--s-6); }
  /* the line itself, drawn from each numeral to the next and never past the last */
  .rail__marker::after {
    content: '';
    position: absolute;
    left: calc(var(--type-h2) + var(--s-4));
    right: calc(var(--s-7) * -1);
    top: 50%;
    height: 1px;
    background-color: var(--border-subtle);
    transform: scaleX(var(--spine-scale, 1));
    transform-origin: left;
  }
  .rail__station:last-child .rail__marker::after { content: none; }
}

/* ---- .triad · /about signature (Phase 6 §6.3) -----------------------------
   MoaT is the claim that all three parties hold a stake in ONE thing. That was
   rendering as three sentences in a ticked list plus an empty image slot with no
   concept behind it (AB-09) — three separate items, which is the opposite of what
   the section says.
   So the three share a single rule that runs through all of them. The shared line
   is the shared stake; it is the only reason this is not three cards. Nothing is
   nested and elevation is never declared — there are no cards here at all. */
/* --rule-scale is driven by script.js: the two rules draw outward from the
   centre, because what the section claims is the CONNECTION between the three.
   Defaults to 1, so without JS the structure is simply there. */
.triad, .triad__stake { position: relative; }
.triad::before, .triad__stake::before {
  content: '';
  position: absolute;
  top: -1px; left: 0; right: 0;
  height: 1px;
  background-color: var(--border-subtle);
  transform: scaleX(var(--rule-scale, 1));
}
.triad {
  /* ⚠ THE ch TRAP, third instance. Plain `65ch` resolves at the inherited 16px
     = 656px against the 18px prose column's 738px — 41px inside the column this
     component exists to share. And the top rule was drawn TWICE, a real border
     plus the ::before, so the two rules that ARE the component rendered at two
     lengths and two weights. Measured: 656 vs 573, 41px off centre. */
  border-top: none;
  max-width: calc(65ch * 18 / 16);
  margin-inline: auto;
  margin-top: var(--s-8);
  display: grid;
  gap: var(--s-6);
  grid-template-columns: 1fr;
  border-top: 1px solid var(--border-subtle);
  padding-top: var(--s-6);
}
@media (min-width: 720px) {
  .triad { grid-template-columns: repeat(3, 1fr); gap: var(--s-7); }
}
.triad__party {
  font-family: var(--font-editorial);
  font-size: var(--type-h3);
  color: var(--text-primary);
  display: block;
  margin-bottom: var(--s-3);
}
.triad__holds { color: var(--text-secondary); font-size: var(--type-body); line-height: 1.5; }
.triad__stake {
  grid-column: 1 / -1;
  /* a <p>, so the global `p { max-width: 65ch }` at 14px capped it 83px shorter
     than the rule above it */
  max-width: none;
  border-top: none;
  padding-top: var(--s-5);
  color: var(--text-muted);
  font-size: var(--type-caption);
  text-align: center;
}
/* the paragraph that follows was landing on the closing rule */
.triad + .prose-band__body { margin-top: var(--s-7); }

/* ---- .triad--stack · the three parties running vertically -------------------
   `/about` §3, added 2026-08-19 (Marco). The three-across version used the full
   page width for three short phrases and left the section as five stacked centred
   bands. Stacked, the triad becomes the right-hand column of the page's own
   `.split` primitive and the argument gets the left — and the shared-rule idea
   from 08-18 reads *better* vertically: three rules in a column is a ledger of who
   holds what, where three side by side read as three unrelated items.

   ⚠ `.triad.triad--stack` and not `.triad--stack` alone: the base component turns
   itself into `repeat(3, 1fr)` inside `@media (min-width: 720px)` above, and a
   media query adds no specificity — a single-class override would depend purely on
   source order. Two classes settle it outright. */
.triad.triad--stack {
  grid-template-columns: 1fr;
  /* the base component is a centred 65ch column in the middle of the page; in a
     split column it is neither centred nor measured — the column IS the measure */
  max-width: none;
  margin-inline: 0;
  margin-top: 0;
  gap: 0;
  /* each party carries its own rule now, so the component-level one would draw a
     second line on top of the first party's */
  border-top: none;
  padding-top: 0;
}
.triad.triad--stack::before { content: none; }
.triad--stack > div {
  border-top: 1px solid var(--border-subtle);
  padding-block: var(--s-5);
}
/* Left, not centred: it closes a column, not a full-width section. */
.triad--stack .triad__stake { text-align: left; }

/* ---- .split__inner--top ----------------------------------------------------
   `.split__inner` centres its two columns against each other, which is right when
   the right column is a photograph of a fixed ratio. When it holds a component
   whose height comes from its own content, centring floats both columns and the
   H2 stops agreeing with the first row of the thing it introduces. */
.split__inner--top { align-items: start; }

/* The CTA is a third grid child so that mobile reads argument → ledger → action
   (see the note in about/index.html §3). On desktop that alone put it in row 2
   with the full `--s-9` column gap as a ROW gap, which left ~184px between the
   paragraph it belongs to and the button — measured, and it read as an orphan.
   Two declarations fix it: the right-hand component spans both rows, so row 1 is
   only as tall as the prose instead of as tall as the taller column, and the row
   gap drops to `--s-6`. The button lands ~32px under its own paragraph on desktop
   and stays dead last on mobile. */
@media (min-width: 960px) {
  .split__inner--top { row-gap: var(--s-6); }
  .split__inner--top > .triad--stack {
    /* ⚠ `grid-column: 2` is NOT redundant, and leaving it out swapped the columns.
       Grid places every item with a DEFINITE position before it auto-places the
       rest. Give this one a `grid-row` and nothing else and it stops being
       auto-placed: it claims row 1 / column 1 — the first free slot — and the
       prose, still auto-placed, lands in column 2. The section rendered mirrored,
       ledger left and argument right. Pinning the column is what keeps the
       row-span from reordering the section. */
    grid-column: 2;
    grid-row: 1 / span 2;
  }
  /* ⚠ Measured: the gap under the paragraph came out at 96px, not the 32 the
     row-gap asks for. `.section-ctas` carries its own `margin-top: var(--s-8)`
     for the case it was built for — a button closing a full-width section — and
     as a grid child that margin STACKS on the row gap. Same class of defect as
     AB-14, where a `.section-media` margin stacked on two section paddings.
     The grid owns the rhythm here, so the margin goes. */
  .split__inner--top > .section-ctas { margin-top: 0; }
}

/* ---- .section-ctas--start --------------------------------------------------
   The base row is centred, for a button closing a full-width section. Inside a
   split column, centred means centred on half the page — which reads as a
   mistake rather than a choice. */
.section-ctas--start { justify-content: flex-start; }

/* ---- 404 recovery list ----
   The one component the branded 404 needs that the system did not already have.
   Not cards: a 404 is a wayfinding moment, and four cards would ask the visitor
   to parse containers before they can read where to go. Rows on a rule, the
   destination first and the reason second. */
.notfound__list {
  max-width: calc(65ch * 18 / 16);
  margin-inline: auto;
  list-style: none;
  padding-inline-start: 0;
  display: flex;
  flex-direction: column;
}
.notfound__list li {
  display: grid;
  gap: var(--s-2);
  padding-block: var(--s-6);
  border-top: 1px solid var(--border-subtle);
}
.notfound__list li:last-child { border-bottom: 1px solid var(--border-subtle); }
.notfound__list a {
  font-family: var(--font-editorial);
  font-size: var(--type-h3);
  color: var(--text-primary);
  transition: color var(--duration-fast) var(--ease-out-quart);
}
.notfound__list a:hover { color: var(--text-accent); }
.notfound__list span {
  color: var(--text-secondary);
  font-size: var(--type-body);
  line-height: 1.5;
}
@media (min-width: 720px) {
  .notfound__list li { grid-template-columns: 18rem 1fr; gap: var(--s-6); align-items: baseline; }
}
[data-theme="light"] .hero--full .hero__sub { color: #2a2e4a; }

/* Light-theme hero overlay: pale wash on the left for ink copy. */
[data-theme="light"] .hero--full .hero__overlay {
  background: linear-gradient(90deg,
    var(--cloud-white) 0%,
    rgba(248, 249, 252, 0.85) 30%,
    rgba(248, 249, 252, 0.35) 60%,
    transparent 100%);
}

@media (max-width: 768px) {
  .hero--full .hero__overlay {
    background: linear-gradient(180deg,
      rgba(11, 12, 22, 0.45) 0%,
      rgba(11, 12, 22, 0.85) 55%,
      var(--ink) 100%);
  }
  [data-theme="light"] .hero--full .hero__overlay {
    background: linear-gradient(180deg,
      rgba(248, 249, 252, 0.45) 0%,
      rgba(248, 249, 252, 0.85) 55%,
      var(--cloud-white) 100%);
  }
  .hero--full .hero__bg { object-position: center center; }
  .hero--full .hero__inner { display: flex; align-items: flex-end; min-height: inherit; }
}

/* ---- 7b. Cover scrub (Trilha B) ----
   4-frame cross-fade hero, pinned via ScrollTrigger. Each beat overlays in
   a distinct quadrant per its prompt-carved negative space. Mobile collapses
   beat positions to bottom-aligned stack for portrait viewports. */

.cover-scrub {
  position: relative;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  background-color: var(--ink);
}

.cover-scrub__stage {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.cover-scrub__video,
.cover-scrub__poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center center;
  will-change: transform;
}

/* Poster sits beneath the video and is revealed only when the video is not
   yet ready (or reduced motion). Once metadata loads the video covers it. */
.cover-scrub__poster {
  z-index: 0;
  opacity: 1;
  transition: opacity 400ms var(--ease-out-quart);
}

.cover-scrub__video {
  z-index: 1;
  opacity: 0;
  transition: opacity 400ms var(--ease-out-quart);
}

.cover-scrub.is-video-ready .cover-scrub__video { opacity: 1; }
.cover-scrub.is-video-ready .cover-scrub__poster { opacity: 0; }

.cover-scrub__scrim {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: linear-gradient(180deg,
    rgba(11, 12, 22, 0.30) 0%,
    rgba(11, 12, 22, 0.30) 50%,
    rgba(11, 12, 22, 0.78) 100%);
  pointer-events: none;
}

.cover-scrub__copy {
  position: relative;
  z-index: 2;
  height: 100%;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-template-rows: repeat(12, 1fr);
  gap: var(--s-3) var(--s-5);
  padding-block: calc(var(--s-section) + var(--s-7)) var(--s-section);
  padding-inline: var(--container-pad);
}

.cover-scrub__beat {
  /* Opacity + transform are driven frame-by-frame from JS during the scroll
     scrub, so no CSS transition here (it would fight the per-frame writes).
     Reduced-motion path below still uses the static opacity rule. */
  opacity: 0;
  transform: translateY(8px);
  will-change: opacity, transform;
  pointer-events: none;
}

/* Quadrant placement per prompt-carved negative space. Beats span wider
   columns now to give bigger titles room to breathe without awkward wraps. */
.cover-scrub__beat[data-frame="1"] { /* bottom-left */
  grid-column: 1 / span 8;
  grid-row: 7 / span 6;
  align-self: end;
  text-align: left;
}
.cover-scrub__beat[data-frame="2"] { /* right, vertically centered */
  grid-column: 5 / span 8;
  grid-row: 4 / span 6;
  align-self: center;
  text-align: right;
}

/* F2 eyebrow stays cloud-white instead of magenta accent (per design call) */
.cover-scrub__beat[data-frame="2"] .eyebrow {
  color: var(--cloud-white);
}
.cover-scrub__beat[data-frame="3"] { /* bottom-right */
  grid-column: 5 / span 8;
  grid-row: 7 / span 6;
  align-self: end;
  text-align: right;
}
.cover-scrub__beat[data-frame="4"] { /* centered, lower-middle */
  grid-column: 2 / span 10;
  grid-row: 5 / span 6;
  align-self: center;
  text-align: center;
}

.cover-scrub__title {
  font-family: var(--font-editorial);
  font-size: clamp(2.75rem, 5.5vw + 1rem, 5.75rem);
  font-weight: 600;
  line-height: 1.0;
  letter-spacing: -0.025em;
  color: var(--cloud-white);
  font-variation-settings: 'opsz' 144;
  margin: 0;
  max-width: 26ch;
  text-wrap: pretty;
  text-shadow: 0 2px 24px rgba(11, 12, 22, 0.5);
}

.cover-scrub__beat[data-frame="3"] .cover-scrub__title,
.cover-scrub__beat[data-frame="2"] .cover-scrub__title {
  margin-left: auto;
}
.cover-scrub__beat[data-frame="4"] .cover-scrub__title {
  margin-inline: auto;
  max-width: 22ch;
}

.cover-scrub__sub {
  font-family: var(--font-body);
  font-size: clamp(1.0625rem, 0.6vw + 0.875rem, 1.375rem);
  color: rgba(248, 249, 252, 0.95);
  margin-top: var(--s-5);
  max-width: 44ch;
  line-height: 1.45;
  font-weight: 500;
  text-shadow: 0 1px 12px rgba(11, 12, 22, 0.5);
}
.cover-scrub__beat[data-frame="3"] .cover-scrub__sub,
.cover-scrub__beat[data-frame="2"] .cover-scrub__sub {
  margin-left: auto;
}
.cover-scrub__beat[data-frame="4"] .cover-scrub__sub {
  margin-inline: auto;
}

.cover-scrub__beat .eyebrow {
  margin-bottom: var(--s-3);
  color: var(--text-accent);
  font-size: var(--type-eyebrow);
  text-shadow: 0 1px 8px rgba(11, 12, 22, 0.4);
}

.cover-scrub__progress {
  position: absolute;
  bottom: var(--s-6);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: var(--s-3);
  z-index: 3;
  pointer-events: none;
}

.cover-scrub__progress-dot {
  width: 28px;
  height: 2px;
  border-radius: var(--radius-pill);
  background-color: rgba(248, 249, 252, 0.25);
  transition: background-color var(--duration-base) var(--ease-out-quart),
              width var(--duration-base) var(--ease-out-quart);
}

.cover-scrub__progress-dot.is-active {
  background-color: var(--text-accent);
  width: 44px;
}

/* Mobile portrait: collapse all beat positions to bottom-aligned stack.
   Image subject placement won't reliably survive narrow crops, so we drop
   the per-quadrant geometry and let the title always live in the bottom
   third with a stronger scrim above it. */
@media (max-width: 768px) {
  .cover-scrub__scrim {
    background: linear-gradient(180deg,
      rgba(11, 12, 22, 0.25) 0%,
      rgba(11, 12, 22, 0.55) 55%,
      rgba(11, 12, 22, 0.90) 100%);
  }
  .cover-scrub__beat[data-frame] {
    grid-column: 1 / -1;
    grid-row: 8 / span 5;
    align-self: end;
    text-align: left;
  }
  .cover-scrub__beat[data-frame] .cover-scrub__title,
  .cover-scrub__beat[data-frame] .cover-scrub__sub {
    margin-inline: 0;
    max-width: none;
  }
  .cover-scrub__progress { bottom: var(--s-4); }
}

/* Reduced motion: skip pin/scrub, show only F1 statically */
@media (prefers-reduced-motion: reduce) {
  .cover-scrub__video { display: none; }
  .cover-scrub__poster { opacity: 1; }
  .cover-scrub__beat { opacity: 0; }
  .cover-scrub__beat[data-frame="1"] { opacity: 1; transform: none; pointer-events: auto; }
  .cover-scrub__progress { display: none; }
}

.hero__copy { max-width: 640px; }

.hero__h1 {
  font-size: var(--type-display);
  line-height: 1.05;
  margin-bottom: var(--s-5);
}

.hero__sub {
  font-size: var(--type-body-lg);
  color: var(--text-secondary);
  margin-bottom: var(--s-7);
  max-width: 56ch;
}

.hero__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
}

.hero__media {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-card);
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  overflow: hidden;
}

.hero__media::after {
  content: 'Cover animation + explainer video — Trilha B';
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--text-muted);
  font-size: var(--type-caption);
  padding: var(--s-5);
  text-align: center;
}

.hero__media:has(img)::after { content: none; }

.hero__poster {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ---- 8. Pain (S2) ---- */

.pain {
  background-color: var(--surface-0);
}

.pain__inner {
  display: grid;
  gap: var(--s-7);
  align-items: center;
}

@media (min-width: 1024px) {
  .pain__inner {
    grid-template-columns: 1fr 1fr;
    gap: var(--s-9);
  }
  .pain__media { order: -1; }
}

.pain__media {
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-card);
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  position: relative;
  overflow: hidden;
}

.pain__media::after {
  content: 'F&I desk scene — editorial-cinematic photography (Phase 5)';
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--text-muted);
  font-size: var(--type-caption);
  padding: var(--s-5);
  text-align: center;
}

.pain__media:has(img)::after { content: none; }
.pain__media img { width: 100%; height: 100%; object-fit: cover; }

.pain__h2 { margin-bottom: var(--s-5); }
.pain__body {
  font-size: var(--type-body-lg);
  color: var(--text-secondary);
  margin-bottom: var(--s-6);
}

.pain__callout {
  margin-block: var(--s-6);
  padding-block: var(--s-5);
  border-block: 1px solid var(--border-subtle);
}

.pain__callout-number {
  font-family: var(--font-display);
  font-size: var(--type-data-callout);
  font-weight: 700;
  line-height: 1;
  color: var(--text-primary);
  letter-spacing: -0.03em;
  display: block;
}

.pain__callout-label {
  font-size: var(--type-caption);
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-top: var(--s-2);
  display: block;
}

/* ---- 9. Opportunity / Movement (S3) — gradient band ---- */

/* S3 Movement — editorial spread (gradient removed per /impeccable audit
   register-fix). Full-bleed photograph carries the surface; scrim guarantees
   text contrast; a single radial magenta atmospheric trace keeps brand color
   present without the gradient-band cliché. S7 is now the only gradient
   moment, paired with the conversion ask. */
/* Token-based dark band (photo retired per prompts §A3). Stays dark in BOTH
   themes by design — the page's manifesto punctuation. */
/* ============================================================
   THE MOVEMENT STATEMENT — manifesto beat between hero and §2
   Theme-aware surface; editorial type. Generic across verticals.
   ============================================================ */
.statement {
  text-align: center;
}

.statement__inner {
  max-width: 820px;
  margin-inline: auto;
}

.statement__h2 {
  font-family: var(--font-editorial);
  font-size: clamp(2.25rem, 4vw + 1rem, 3.75rem);
  font-weight: 500;
  line-height: 1.08;
  letter-spacing: -0.015em;
  font-variation-settings: 'opsz' 96;
  margin-bottom: var(--s-5);
}

.statement__lead {
  font-size: var(--type-h3);
  font-weight: 500;
  line-height: 1.35;
  color: var(--text-accent);
  max-width: 32ch;
  margin-inline: auto;
  margin-bottom: var(--s-6);
  text-wrap: balance;
}

.statement__body {
  font-size: var(--type-body-lg);
  color: var(--text-secondary);
  max-width: 56ch;
  margin-inline: auto;
  margin-bottom: var(--s-6);
  text-wrap: pretty;
}

.statement__closer {
  font-family: var(--font-editorial);
  font-size: var(--type-h3);
  font-weight: 500;
  color: var(--text-primary);
  font-variation-settings: 'opsz' 96;
}

.movement {
  background-color: var(--surface-0);
  position: relative;
  overflow: hidden;
  padding-block: calc(var(--s-section) + var(--s-7));
}

.movement::before {
  /* Subtle magenta + purple atmospheric traces — brand presence, no photo */
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 82% 75%, rgba(148, 63, 120, 0.20), transparent 55%),
    radial-gradient(ellipse at 12% 10%, rgba(109, 94, 164, 0.16), transparent 50%);
  pointer-events: none;
}

/* Movement h2 follows the page theme. */
.movement .movement__h2 { color: var(--text-primary); }

.movement__inner {
  position: relative;
  z-index: 1;
  max-width: var(--container-max); /* full container width — the cards span it;
                                      the narrative header keeps its own measure */
  text-align: center;
  margin-inline: auto;
}

.movement__h2 {
  font-family: var(--font-editorial);
  font-size: clamp(2.25rem, 4vw + 1rem, 3.75rem);
  font-weight: 500;
  margin-block: 0 var(--s-6);
  margin-inline: auto;
  max-width: 22ch; /* readable display measure inside the now-wide section */
  letter-spacing: -0.015em;
  line-height: 1.08;
  font-variation-settings: 'opsz' 96;
}

.movement__body {
  font-size: var(--type-body-lg);
  color: var(--text-secondary);
  margin-inline: auto;
  margin-bottom: var(--s-7);
  max-width: 54ch;
  text-wrap: balance;
}

.movement__quote {
  margin-top: var(--s-7);
  padding-top: var(--s-6);
  border-top: 1px solid var(--border-subtle);
}

.movement__quote-text {
  font-size: var(--type-h3);
  font-weight: 500;
  line-height: 1.4;
  color: var(--text-primary);
  max-width: 34ch;
  margin-inline: auto;
  text-wrap: balance;
}

.movement__quote-attribution {
  font-size: var(--type-caption);
  color: var(--text-secondary);
  margin-top: var(--s-4);
  margin-inline: auto;
  max-width: none;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* Three Voices, One Direction */
.voices {
  margin-top: var(--s-8);
  padding-top: var(--s-6);
  border-top: 1px solid var(--border-subtle);
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.voices__eyebrow {
  display: block;
  text-align: center;
  font-size: var(--type-eyebrow);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-accent);
  margin-bottom: var(--s-4);
}
/* Three Voices as full-image cards.
   The card is dark in BOTH themes (dark placeholder / photo behind a scrim),
   so type is cloud-white and the accent is the dark-theme magenta throughout —
   same rule as the .movement band and the hero placeholder. */
.voices__grid {
  list-style: none;
  margin: var(--s-4) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s-4);
  grid-template-columns: 1fr;
  align-items: start; /* each card sizes to its own content, so only the
                         hovered card grows on reveal — its siblings hold. */
}
.voices__grid > li { min-width: 0; } /* let the grid track shrink below content */
@media (min-width: 720px) {
  .voices__grid { grid-template-columns: repeat(3, 1fr); gap: var(--s-5); }
}

.voice-card {
  position: relative;
  width: 100%;
  min-height: clamp(420px, 66vw, 460px);
  display: flex;
  flex-direction: column;
  justify-content: flex-end; /* line + role rest at the bottom over the image */
  overflow: hidden;
  border-radius: var(--radius-card);
  background-color: var(--ink);
  isolation: isolate;
}
.voice-card:focus-visible {
  outline: 2px solid var(--magenta-bright);
  outline-offset: 3px;
}
/* Media layer — dark brand placeholder until Phase-8 renders land.
   When imagery arrives: swap the gradient for background-image: url(...) +
   background-size: cover; the scrim + type layers already guarantee contrast. */
.voice-card__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  background:
    radial-gradient(ellipse at 72% 22%, rgba(148, 63, 120, 0.32), transparent 55%),
    radial-gradient(ellipse at 24% 84%, rgba(109, 94, 164, 0.30), transparent 55%),
    linear-gradient(155deg, var(--ink), var(--navy));
  background-size: cover;
  background-position: center;
  transform: scale(1.001); /* paint on its own layer for a smooth zoom */
  /* Base = the EXIT transition (quicker, so pulling away feels responsive).
     The slow cinematic entrance is set on the :hover / :focus rule below. */
  transition: transform var(--duration-base) var(--ease-out-quart),
              filter var(--duration-base) var(--ease-out-quart);
}
/* Real render (when wired) fills the media layer; the gradient above is the
   fallback if the image is missing. The hover scale on __media scales this too. */
.voice-card__media picture,
.voice-card__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* Scrim carries WCAG contrast for the overlaid type — but in TWO STATES, reworked
   2026-07-27. The single gradient it used to have was 40% black at the very top and
   62% at 72% height, which is where the faces sit: it was protecting text that is
   not on screen yet and crushing the portraits to do it (Marco: "a parte de cima do
   degradê está muito escura, deixando os rostos sem visibilidade").

   REST — bottom-weighted, effectively clear at the top. It only has to protect the
   role label and the quote line, which live in the bottom third.
   HOVER — a second, fuller layer fades in, because that is when the supporting text
   rises into the upper half. Two layers rather than one animated gradient: CSS
   cannot interpolate between gradient stops, so transitioning `background` would
   snap. Opacity on a separate layer animates cleanly and costs nothing. */
.voice-card__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    to top,
    rgba(11, 12, 22, 0.92) 0%,
    rgba(11, 12, 22, 0.78) 24%,
    rgba(11, 12, 22, 0.34) 50%,
    rgba(11, 12, 22, 0.08) 76%,
    rgba(11, 12, 22, 0) 100%
  );
}
/* Defaults to VISIBLE so touch / no-hover devices — which show both text layers at
   once — keep full protection. The hover block below hides it at rest and brings it
   back on hover. */
.voice-card__scrim::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(11, 12, 22, 0.94) 0%,
    rgba(11, 12, 22, 0.88) 40%,
    rgba(11, 12, 22, 0.70) 72%,
    rgba(11, 12, 22, 0.56) 100%
  );
  opacity: 1;
}
.voice-card__content {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-6);
}
.voice-card__role {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--type-eyebrow);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--magenta-bright);
}
.voice-card__line {
  margin: 0;
  font-family: var(--font-editorial);
  font-size: var(--type-h3);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--cloud-white);
  text-wrap: pretty;
}
/* Align the role labels across the three cards (Marco, 2026-07-27).
   The content block is bottom-anchored, and the three quotes are very different
   lengths (the Merchant runs ~2× the Customer), so the labels landed at three
   different heights — a 62px spread at 1440. Fix: reserve the LONGEST quote's
   height on every card, so each label starts from the same line. The surplus falls
   below the shorter quotes, over the image, where it costs nothing.
   Line counts are measured, not assumed, and they change with column width:
     1440 / 1280 → 4 · 2 · 3 lines
     1084        → 6 · 3 · 4
     744         → 9 · 4 · 6
   Hence one reservation per band. Below 720 the cards stack — nothing to align.

   BOTH EDGES ALIGN (Marco, 2026-07-27 — "alinhar os textos de voice para baixo").
   The reservation alone left the short quotes floating: the surplus sat BELOW the text,
   leaving a gap to the card edge. Making the reserved box a flex column justified to
   flex-end drops the text to the bottom of its own box — so the box top keeps the role
   labels aligned AND the text bottoms land level. The surplus moves to the middle,
   between label and quote, where it reads as breathing room instead of a mistake. */
@media (min-width: 720px) {
  .voice-card__line { min-height: calc(9 * 1.3em); display: flex; flex-direction: column; justify-content: flex-end; }
}
@media (min-width: 1024px) { .voice-card__line { min-height: calc(6 * 1.3em); } }
@media (min-width: 1280px) { .voice-card__line { min-height: calc(4 * 1.3em); } }

.voice-card__support { color: rgba(248, 249, 252, 0.86); }
.voice-card__support p {
  margin: 0;
  max-width: 46ch;
  font-size: var(--type-body);
  line-height: 1.6;
  color: inherit;
  text-wrap: pretty;
}

/* Fine-pointer devices (real hover): the line rests at the bottom of the image
   and the supporting text reveals on hover / keyboard focus, growing the card
   downward (grid `align-items: start` keeps the siblings put). Touch + no-hover
   devices (the mobile-first rules above) keep both layers visible — no
   tap-to-reveal. */
@media (hover: hover) and (pointer: fine) {
  .voice-card { min-height: clamp(440px, 46vw, 500px); }

  /* REWORKED 2026-07-27 — the reveal read as mechanical. Two causes, both measured:

     1) Every property ran the same duration on the same front-loaded curve, so the
        whole card moved as one rigid block. Natural motion has properties resolving
        at DIFFERENT times; each now has its own duration.
     2) `--duration-slow` (800ms) on `--ease-out-expo` is ~85% done by 200ms. The
        declared duration was a lie about the perceived one — a snap plus 600ms of
        dead tail. Hence `--ease-reveal`, which holds velocity through the middle.

     Also dropped: the `blur(4px)` on the support text. It was the most expensive
     property here, it quantises on type, and opacity + translate already carry the
     arrival. One less thing moving is what made the rest legible.

     The sequence now reads as three overlapping beats rather than one block:
       darken (280ms) → card grows (400ms) → text rises and lands (440ms, +120ms in)
     The image scale is the only long move (640ms) because a slow depth drift is
     exactly where a tail belongs. */

  /* Resting + EXIT state. Exit stays quicker than entrance and uniform — retracting
     is an undo, not an authored moment. */
  .voice-card__support {
    display: grid;
    grid-template-rows: 0fr;
    opacity: 0;
    transform: translateY(10px);
    /* Required, and it was missing. `.voice-card` is a flex column, so this block is a
       flex item with the default `flex-shrink: 1`. Once the quote's height reservation
       (added 2026-07-27 to align the role labels) ate into the card, the revealed text
       no longer fit — so instead of growing the card, this block was being SQUEEZED and
       `overflow: hidden` on the paragraph silently truncated the copy mid-sentence.
       Caught by forcing the hover state in a headless render; a real pointer hover shows
       the same clipping. Do not remove. */
    flex-shrink: 0;
    transition:
      grid-template-rows 240ms var(--ease-out-quart),
      opacity 200ms var(--ease-out-quart),
      transform 240ms var(--ease-out-quart);
  }
  /* `overflow: hidden` sits on the CONTAINER, not the paragraph. On the paragraph it
     zeroes that item's automatic minimum size, so `grid-template-rows: 1fr` —
     which is `minmax(auto, 1fr)` — resolved its auto minimum to 0 inside the card's
     flex context and the row never opened: the text was clipped to nothing at hover.
     Measured `height: 0` with `scrollHeight: 282`. Clipping still happens, it just
     happens one level up where it does not poison the row sizing. */
  .voice-card__support { overflow: hidden; }

  .voice-card__media {
    transition:
      transform 420ms var(--ease-reveal),
      filter 260ms var(--ease-reveal);
  }

  /* On pointer devices the deep scrim is OFF at rest — the face reads — and fades in
     with the reveal, slightly ahead of the text it protects. */
  .voice-card__scrim::after {
    opacity: 0;
    transition: opacity 220ms var(--ease-out-quart);
  }
  .voice-card:hover .voice-card__scrim::after,
  .voice-card:focus-within .voice-card__scrim::after {
    opacity: 1;
    transition: opacity 300ms var(--ease-reveal);
  }

  /* ENTRANCE */
  .voice-card:hover .voice-card__media,
  .voice-card:focus-within .voice-card__media {
    transform: scale(1.045);          /* was 1.08 — a depth cue, not a lurch */
    filter: brightness(0.48);
    will-change: transform, filter;   /* scoped to the animating state only */
    transition:
      transform 640ms var(--ease-reveal),
      filter 280ms var(--ease-reveal);
  }
  .voice-card:hover .voice-card__support,
  .voice-card:focus-within .voice-card__support {
    grid-template-rows: 1fr;
    opacity: 1;
    transform: none;
    transition:
      grid-template-rows 400ms var(--ease-reveal),
      opacity 300ms linear 120ms,
      transform 440ms var(--ease-reveal) 120ms;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* Cap every voice-card transition to instant regardless of specificity, and
     drop the zoom. Keep the brightness shift (a fade, not motion) so the
     supporting text still reads over the image. */
  .voice-card__media,
  .voice-card__support,
  .voice-card__scrim::after,
  .voice-card:hover .voice-card__media,
  .voice-card:focus-within .voice-card__media,
  .voice-card:hover .voice-card__support,
  .voice-card:focus-within .voice-card__support,
  .voice-card:hover .voice-card__scrim::after,
  .voice-card:focus-within .voice-card__scrim::after {
    transition-duration: 0.01ms !important;
    transition-delay: 0ms !important;
  }
  .voice-card__support { filter: none; }
  .voice-card:hover .voice-card__media,
  .voice-card:focus-within .voice-card__media { transform: none; will-change: auto; }
}
.voices__closer {
  text-align: center;
  font-family: var(--font-editorial);
  font-size: var(--type-h3);
  font-weight: 500;
  color: var(--text-primary);
  margin-top: var(--s-5);
  /* Required, not optional: the global `p { max-width: 65ch }` (line ~222) caps
     this paragraph, and without an auto inline margin the capped box hugs the left
     edge — `text-align: center` then centres the text inside a box that is itself
     off-centre. It measured mid=626 against a container centre of 705. Any centred
     <p> in this file needs this. Fixed 2026-07-27. */
  margin-inline: auto;
}

/* ---- 10. Outcome (S4) — stat strip + calculator ---- */

.outcome {
  background-color: var(--surface-0);
}

.outcome__header {
  text-align: center;
  max-width: 720px;
  margin: 0 auto var(--s-9);
}

.outcome__h2 { margin-bottom: var(--s-4); }

.outcome__stats {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-7);
  margin-bottom: var(--s-10);
}

@media (min-width: 768px) {
  .outcome__stats {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--s-5);
  }
}

.stat {
  text-align: center;
  padding: var(--s-6) var(--s-4);
  position: relative;
}

@media (min-width: 768px) {
  .stat:not(:last-child)::after {
    content: '';
    position: absolute;
    right: 0;
    top: 20%;
    bottom: 20%;
    width: 1px;
    background-color: var(--border-subtle);
  }
}

.stat__number {
  font-family: var(--font-display);
  font-size: var(--type-data-callout);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--text-primary);
  display: block;
}

.stat__label {
  font-size: var(--type-body);
  color: var(--text-secondary);
  margin-top: var(--s-4);
  display: block;
  line-height: 1.4;
}

/* Calculator widget */
.calc {
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  padding: var(--s-7);
  display: grid;
  gap: var(--s-7);
}

@media (min-width: 1024px) {
  .calc {
    grid-template-columns: 1fr 1.2fr;
    padding: var(--s-9);
    gap: var(--s-10);
    /* ⚠ Added 2026-08-20. The two columns are different heights — the left is a
       title, a line of body and the slider; the right is the output card, which
       grew when the range and its hedge landed. Grid defaults to `stretch`, so the
       left column was pinned to the top and left a gap under the slider that read
       as a mistake. Centring the shorter column against the taller one is the fix;
       `align-items: center` on the ITEMS, not `justify` on the tracks. */
    align-items: center;
  }
}

.calc__h3 {
  font-family: var(--font-editorial);
  font-size: var(--type-h2);
  font-weight: 500;
  margin-bottom: var(--s-4);
  line-height: 1.2;
}

.calc__body {
  color: var(--text-secondary);
  margin-bottom: var(--s-6);
}

.calc__input {
  margin-top: var(--s-6);
}

.calc__label {
  font-size: var(--type-eyebrow);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-secondary);
  display: block;
  margin-bottom: var(--s-3);
}

.calc__slider {
  width: 100%;
  appearance: none;
  -webkit-appearance: none;
  height: 4px;
  border-radius: var(--radius-pill);
  background-color: var(--surface-3);
  outline: none;
}
.calc__slider::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 24px;
  height: 24px;
  border-radius: var(--radius-pill);
  background-color: var(--magenta);
  cursor: grab;
  box-shadow: 0 0 0 8px rgba(148, 63, 120, 0.15);
  transition: box-shadow var(--duration-fast) var(--ease-out-quart);
}
.calc__slider::-webkit-slider-thumb:hover {
  box-shadow: 0 0 0 12px rgba(148, 63, 120, 0.22);
}
.calc__slider::-moz-range-thumb {
  width: 24px;
  height: 24px;
  border-radius: var(--radius-pill);
  background-color: var(--magenta);
  cursor: grab;
  border: 0;
}

.calc__volume {
  display: flex;
  justify-content: flex-start;
  align-items: baseline;
  gap: var(--s-2);
  margin-top: var(--s-4);
}

.calc__volume-value {
  font-family: var(--font-display);
  font-size: var(--type-h2);
  font-weight: 700;
  color: var(--text-primary);
  letter-spacing: -0.02em;
}

.calc__volume-unit {
  font-size: var(--type-caption);
  color: var(--text-accent);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 600;
}

.calc__output {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-6);
  background-color: var(--surface-2);
  border-radius: var(--radius-card-sm);
}

.calc__output .calc__label { margin-bottom: 0; }

.calc__output-number {
  font-family: var(--font-display);
  font-size: clamp(2.75rem, 4.5vw + 1rem, 4.25rem);
  font-weight: 700;
  line-height: 1;
  color: var(--text-primary);
  letter-spacing: -0.03em;
  display: block;
}

.calc__output-yearly {
  font-family: var(--font-body);
  font-size: var(--type-body-lg);
  color: var(--text-secondary);
  font-weight: 500;
  display: block;
}

.calc__output-yearly .accent { color: var(--text-accent); }

.calc__hedge {
  font-size: var(--type-caption);
  color: var(--text-muted);
  line-height: 1.5;
  margin-top: var(--s-3);
  font-style: italic;
}

.calc__cta {
  margin-top: var(--s-5);
  align-self: start;
}

/* ---- 11. Process (S5) ---- */

.process {
  background-color: var(--surface-0);
}

.process__header {
  text-align: center;
  max-width: 720px;
  margin: 0 auto var(--s-8);
}

.process__image {
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-card);
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  position: relative;
  overflow: hidden;
  margin-bottom: var(--s-8);
}

.process__image::after {
  content: 'F&I floor + service drive — editorial-cinematic (Phase 5)';
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--text-muted);
  font-size: var(--type-caption);
  padding: var(--s-5);
  text-align: center;
}

.process__image:has(img)::after { content: none; }
.process__image img { width: 100%; height: 100%; object-fit: cover; }

/* New framed media slots (Control editorial + Built For scene trio). Placeholder
   boxes with a label until the aftermarket renders land (prompts in
   prompts/automotive-v2.md). Drop in an <img> and the label auto-hides. */
.section-media {
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-card);
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  position: relative;
  overflow: hidden;
  margin-bottom: var(--s-9);
}
/* ---- Proof mosaic (§6) ----
   Replaced the 3-up 4:5 portrait row (2026-07-27). Three equal frames of three
   people said the same thing three times; the section's claim is breadth, so the
   composition now carries two ideas instead of one: an ANCHOR tile for what kind
   of place this is, and three supporting tiles for how wide the range runs.
   Deliberately unequal. Businesses, not portraits. */
.proof__mosaic {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-4);
  margin: 0 auto var(--s-8); /* full container width, matching the §2 voice grid */
}

.proof__tile {
  border-radius: var(--radius-card); /* match the §2 voice cards */
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  position: relative;
  overflow: hidden;
}

/* Mobile: anchor full width, wide full width, the two small ones paired,
   then the three stat tiles full width at their natural height. */
.proof__tile--anchor { grid-column: 1 / -1; aspect-ratio: 4 / 3; }
.proof__tile--wide { grid-column: 1 / -1; aspect-ratio: 16 / 9; }
.proof__tile--small { aspect-ratio: 1 / 1; }
.proof__tile--stat { grid-column: 1 / -1; }

/* Stat tiles — same shell as the image tiles, that is the point of unifying the
   section. Content sits at the bottom so the tile has air above it, the way the
   image tiles carry their subject. */
.proof__tile--stat {
  display: flex;
  flex-direction: column;
  /* Centred, not bottom-aligned. Bottom-aligning read fine on the two narrow tiles
     but left the wide SOC 2 tile with a large empty rectangle above two lines of
     text — a hole in the corner of the mosaic, next to two tiles full of image. */
  justify-content: center;
  padding: var(--s-6);
  /* Outlined, not filled. The image tiles use --surface-1; letting the stat tiles
     sit on the page surface makes them read as deliberate text cells in the mosaic
     instead of photo frames that failed to load. Filled vs outlined is the only
     signal needed — no extra colour, no decoration. */
  background-color: var(--surface-0);
}

/* The bento starts at 1024, not 720. Measured at 760px the four columns are only
   ~159px wide, which forced the stat tiles to 287px tall to fit their own body copy
   and dragged the anchor to 0.72:1 — portrait, the opposite of the intent. Between
   720 and 1023 the 2-column layout above is simply better: taller section, but every
   tile keeps an honest proportion. */
@media (min-width: 1024px) {
  .proof__mosaic {
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: repeat(3, minmax(clamp(10rem, 14.5vw, 15rem), auto));
  }
  .proof__tile { aspect-ratio: auto; }
  .proof__tile--anchor { grid-column: 1 / span 2; grid-row: 1 / span 2; }
  .proof__tile--wide { grid-column: 3 / span 2; grid-row: 1; }
  .proof__tile--stat-a { grid-column: 3; grid-row: 2; }
  .proof__tile--stat-b { grid-column: 4; grid-row: 2; }
  .proof__tile--small-c { grid-column: 1; grid-row: 3; }
  .proof__tile--small-d { grid-column: 2; grid-row: 3; }
  .proof__tile--stat-c { grid-column: 3 / span 2; grid-row: 3; }
}

.section-media::after,
.proof__tile::after {
  content: attr(data-ph);
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--text-muted);
  font-size: var(--type-caption);
  padding: var(--s-5);
  text-align: center;
}
.section-media:has(img)::after,
.proof__tile:has(img)::after { content: none; }
.section-media img,
.proof__tile img { width: 100%; height: 100%; object-fit: cover; display: block; }

.process__steps {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-5);
}

@media (min-width: 768px) { .process__steps { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .process__steps { grid-template-columns: repeat(4, 1fr); } }

.step {
  padding: var(--s-6);
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  position: relative;
  transition: background-color var(--duration-fast) var(--ease-out-quart),
              border-color var(--duration-fast) var(--ease-out-quart);
}

.step:hover {
  background-color: var(--surface-2);
  border-color: var(--surface-3);
}

.step__eyebrow {
  font-size: var(--type-eyebrow);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-accent);
  display: block;
  margin-bottom: var(--s-4);
}

.step__h3 {
  font-family: var(--font-editorial);
  font-size: var(--type-h3);
  font-weight: 500;
  line-height: 1.25;
  margin-bottom: var(--s-3);
  letter-spacing: -0.01em;
}

.step__body {
  font-size: var(--type-caption);
  color: var(--text-secondary);
  line-height: 1.5;
}

/* ---- 12. Proof (S6) ---- */

.proof {
  background-color: var(--surface-0);
}

.proof__header {
  text-align: center;
  max-width: 720px;
  margin: 0 auto var(--s-8);
}

.proof__emblems {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-5);
  margin-bottom: var(--s-10);
}

@media (min-width: 768px) { .proof__emblems { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1024px) { .proof__emblems { grid-template-columns: repeat(6, 1fr); gap: var(--s-4); } }

.emblem {
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-card-sm);
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  display: grid;
  place-items: center;
  padding: var(--s-4);
  text-align: center;
}

.emblem__label {
  font-size: var(--type-caption);
  color: var(--text-secondary);
  font-weight: 500;
  line-height: 1.3;
}

/* Logo placeholder slots — dashed border + muted sub-label read clearly as
   "real logo coming here", not as a finished client tile. Swap on approval. */
.emblem--placeholder {
  border-style: dashed;
  border-color: var(--border-default);
}
.emblem__ph {
  display: block;
  margin-top: var(--s-2);
  font-size: var(--type-eyebrow);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
}

.proof__quote-block {
  display: grid;
  gap: var(--s-7);
  align-items: center;
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  padding: var(--s-7);
  margin-bottom: var(--s-9);
}

@media (min-width: 1024px) {
  .proof__quote-block {
    grid-template-columns: 240px 1fr;
    padding: var(--s-8) var(--s-9);
    gap: var(--s-9);
  }
}

.proof__quote-portrait {
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-card-sm);
  background-color: var(--surface-2);
  position: relative;
  overflow: hidden;
  max-width: 240px;
}

.proof__quote-portrait::after {
  content: 'Operator portrait — editorial-cinematic (Phase 5)';
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--text-muted);
  font-size: var(--type-caption);
  padding: var(--s-3);
  text-align: center;
}

.proof__quote-portrait:has(img)::after { content: none; }
.proof__quote-portrait img { width: 100%; height: 100%; object-fit: cover; }

.proof__quote-text {
  font-family: var(--font-editorial);
  font-size: clamp(1.375rem, 1vw + 1rem, 1.75rem);
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: -0.005em;
  margin-bottom: var(--s-5);
  font-variation-settings: 'opsz' 48;
}

.proof__quote-attribution {
  font-size: var(--type-caption);
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.proof__wide {
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-card);
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  position: relative;
  overflow: hidden;
  margin-bottom: var(--s-9);
}

.proof__wide::after {
  content: 'Dealer principal + service director at dashboard — editorial-cinematic (Phase 5)';
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--text-muted);
  font-size: var(--type-caption);
  padding: var(--s-5);
  text-align: center;
}

.proof__wide:has(img)::after { content: none; }
.proof__wide img { width: 100%; height: 100%; object-fit: cover; }

/* Built For — audience list (replaces the social-proof emblems + quote) */
.builtfor__list {
  list-style: none;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-3);
  max-width: 880px;
  margin: 0 auto var(--s-7);
}
@media (min-width: 640px) { .builtfor__list { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .builtfor__list { grid-template-columns: repeat(3, 1fr); } }
.builtfor__item {
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card-sm);
  padding: var(--s-5) var(--s-6);
  font-weight: 500;
  color: var(--text-primary);
}
.builtfor__qualifier {
  text-align: center;
  max-width: 60ch;
  margin: 0 auto var(--s-7);
  font-size: var(--type-body-lg);
  color: var(--text-secondary);
  text-wrap: balance;
}

.proof__kpis {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-5);
  padding-top: var(--s-7);
  border-top: 1px solid var(--border-subtle);
}

@media (min-width: 768px) { .proof__kpis { grid-template-columns: repeat(3, 1fr); } }

.kpi { text-align: center; }
.kpi__value {
  font-family: var(--font-editorial);
  font-size: clamp(2.5rem, 3.5vw + 1rem, 3.75rem);
  font-weight: 500;
  color: var(--text-primary);
  letter-spacing: -0.02em;
  line-height: 1.05;
  display: block;
  text-wrap: balance;
  font-variation-settings: 'opsz' 96;
}
/* The compliance KPI is a text badge, not a number — sized down so it sits on
   one line and the three KPI captions stay aligned (no dangling "+"). */
.kpi__value--compact {
  font-size: clamp(1.75rem, 1.6vw + 1rem, 2.4rem);
  letter-spacing: -0.01em;
}

.kpi__label {
  font-size: var(--type-caption);
  color: var(--text-secondary);
  margin-top: var(--s-4);
  display: block;
  max-width: 30ch;
  margin-inline: auto;
  text-wrap: balance;
}

/* Keep the KPI columns top-aligned so captions line up across all three. */
.proof__kpis { align-items: start; }

/* ---- 13. Final CTA (S7) — gradient band ---- */

.final-cta {
  background: var(--gradient-1);
  position: relative;
  overflow: hidden;
  padding-block: calc(var(--s-section) + var(--s-5));
}

.final-cta::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 70% 30%, rgba(167, 154, 180, 0.15), transparent 55%),
    radial-gradient(ellipse at 30% 70%, rgba(148, 63, 120, 0.20), transparent 50%);
  pointer-events: none;
}

/* ---- GL-04 · the mark on the gradient band -------------------------------
   Kyle, 2026-08-14: translucent, does NOT need to be centred, offset or angled
   is fine — "elongated creative freedom" — and try the DARK mark, because the
   dark sections above and below the band let it read.
   `brightness(0)` flattens the brand asset's own colours to black so it reads as
   a shadow in the gradient rather than a second coloured artwork fighting it.
   ⚠ The reference mockup ran the mark straight through the headline and
   "partner." lost contrast. The copy column is centred at 720px, so the mark is
   pushed past it and the overlap is kept to the outer third. Text contrast is
   not negotiable against a decorative layer.
   Tunable per band via the three custom properties. */
.final-cta {
  --mark-size: 116%;
  --mark-x: 74%;
  --mark-opacity: 0.16;
}
.final-cta::after {
  content: '';
  position: absolute;
  inset: -12% -6%;
  background-image: url('assets/logo/credx-shape.svg');
  background-repeat: no-repeat;
  background-position: var(--mark-x) 50%;
  background-size: auto var(--mark-size);
  filter: brightness(0);
  opacity: var(--mark-opacity);
  pointer-events: none;
  z-index: 0;
}
@media (prefers-reduced-motion: no-preference) {
  .final-cta::after { will-change: auto; }
}

.final-cta__inner {
  position: relative;
  z-index: 1;
  text-align: center;
  max-width: 720px;
  margin-inline: auto;
}

.final-cta__h2 {
  font-size: clamp(2rem, 3.5vw + 1rem, 3rem);
  margin-bottom: var(--s-5);
  letter-spacing: -0.02em;
}

.final-cta__body {
  font-size: var(--type-body-lg);
  color: var(--cloud-white);
  opacity: 0.9;
  margin-inline: auto;
  margin-bottom: var(--s-7);
  max-width: 56ch;
}

.final-cta__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-4);
  justify-content: center;
}

/* ---- 14. FAQ ---- */

.faq {
  background-color: var(--surface-0);
  padding-block: var(--s-section);
}

.faq__header { text-align: center; margin-bottom: var(--s-8); }

.faq__list {
  max-width: 880px;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.faq__item {
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card-sm);
  overflow: hidden;
  transition: border-color var(--duration-fast) var(--ease-out-quart);
}

.faq__item[open] { border-color: var(--text-accent); }

.faq__question {
  font-family: var(--font-editorial);
  font-size: var(--type-h3);
  font-weight: 500;
  line-height: 1.3;
  padding: var(--s-5) var(--s-6);
  cursor: pointer;
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-4);
  letter-spacing: -0.01em;
}

.faq__question::-webkit-details-marker { display: none; }

.faq__question::after {
  content: '+';
  font-size: var(--type-h2);
  font-weight: 400;
  color: var(--text-accent);
  transition: transform var(--duration-fast) var(--ease-out-quart);
}

.faq__item[open] .faq__question::after { transform: rotate(45deg); }

.faq__answer {
  padding: 0 var(--s-6) var(--s-5);
  color: var(--text-secondary);
  line-height: 1.6;
  /* HW-18: `.faq__answer` is the <p> itself, so the global `p { max-width: 65ch }`
     (line 228) was capping it at ~520px inside an 880px `.faq__list` card,
     leaving a large empty gutter. Neither the card nor this box needs to
     change shape — only the inherited ch cap, which was never intentional here. */
  max-width: none;
}

/* `.definition` (a non-interactive shell for a single Q&A) was added and then
   removed on 2026-07-27. It existed only while §3.5 held one question; the section
   now carries three, so it uses the plain `.faq` accordion with the first item
   `open` — the definition stays visible for the snippet, the objections collapse. */

/* ---- 15. Footer ---- */

.footer {
  background-color: var(--surface-0);
  border-top: 1px solid var(--border-subtle);
  padding-block: var(--s-8) var(--s-5);
}

.footer__inner {
  display: grid;
  gap: var(--s-7);
  grid-template-columns: 1fr;
}

@media (min-width: 768px) { .footer__inner { grid-template-columns: 2fr 1fr 1fr 1fr; } }

.footer__brand-line {
  font-family: var(--font-display);
  font-size: var(--type-h3);
  color: var(--text-primary);
  margin-bottom: var(--s-5);
  letter-spacing: -0.01em;
  max-width: 32ch;
}

.footer__cta-line {
  font-family: var(--font-display);
  font-size: var(--type-h3);
  color: var(--text-primary);
  margin-bottom: var(--s-5);
  letter-spacing: -0.01em;
  max-width: 24ch;
}
.footer__ctas { display: flex; flex-wrap: wrap; gap: var(--s-4); }

.footer__col-title {
  font-size: var(--type-eyebrow);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-bottom: var(--s-4);
}

.footer__links {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.footer__link {
  font-size: var(--type-caption);
  color: var(--text-secondary);
  transition: color var(--duration-fast) var(--ease-out-quart);
}

.footer__link:hover { color: var(--text-primary); }

.footer__bottom {
  border-top: 1px solid var(--border-subtle);
  margin-top: var(--s-8);
  padding-top: var(--s-5);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--s-4);
  font-size: var(--type-eyebrow);
  color: var(--text-muted);
}

/* ---- 16. Modal (Lead form + Video) ---- */

.modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--s-5);
  background-color: rgba(11, 12, 22, 0.85);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}

.modal[aria-hidden='false'] { display: flex; }

.modal__panel {
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  padding: var(--s-8);
  max-width: 560px;
  width: 100%;
  max-height: 85vh;
  overflow-y: auto;
  position: relative;
}

.modal__close {
  position: absolute;
  top: var(--s-4);
  right: var(--s-4);
  width: 36px;
  height: 36px;
  border-radius: var(--radius-pill);
  display: grid;
  place-items: center;
  color: var(--text-secondary);
  font-size: var(--type-h3);
  transition: background-color var(--duration-fast) var(--ease-out-quart);
}

.modal__close:hover { background-color: var(--surface-2); color: var(--text-primary); }

.modal__h3 {
  font-family: var(--font-editorial);
  font-size: var(--type-h2);
  font-weight: 500;
  margin-bottom: var(--s-3);
}

.modal__body { color: var(--text-secondary); margin-bottom: var(--s-6); }

.form {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

.form__field { display: flex; flex-direction: column; gap: var(--s-2); }
.form__label {
  font-size: var(--type-eyebrow);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-secondary);
}
.form__input,
.form__textarea,
.form__select {
  font-family: inherit;
  font-size: var(--type-body);
  padding: var(--s-3) var(--s-4);
  background-color: var(--surface-2);
  border: 1px solid var(--surface-3);
  border-radius: var(--radius-card-sm);
  color: var(--text-primary);
  transition: border-color var(--duration-fast) var(--ease-out-quart);
}
.form__input:focus,
.form__textarea:focus,
.form__select:focus {
  outline: none;
  border-color: var(--text-accent);
}

.form__textarea { resize: vertical; min-height: 96px; }

/* ---- 17. Reveal animation hook (used by script.js) ---- */

/* Baseline reveal — confident rise + subtle scale (bolder than the old 24px
   fade so entrances read with presence). */
[data-reveal] {
  opacity: 0;
  transform: translateY(48px) scale(0.96);
  transition: opacity var(--duration-slow) var(--ease-out-expo),
              transform var(--duration-slow) var(--ease-out-expo);
}

[data-reveal].is-revealed {
  opacity: 1;
  transform: translateY(0) scale(1);
}

[data-reveal-delay='1'] { transition-delay: 90ms; }
[data-reveal-delay='2'] { transition-delay: 180ms; }
[data-reveal-delay='3'] { transition-delay: 270ms; }

/* Editorial word-reveal — section titles rise word-by-word from behind a mask.
   Hidden state + animation are JS-driven (GSAP) so no-JS / reduced-motion keep
   the text fully visible. These rules only provide the clip structure. */
.reveal-word-mask {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  padding-bottom: 0.08em;   /* room for descenders inside the clip */
  margin-bottom: -0.08em;
}
.reveal-word { display: inline-block; will-change: transform; }

/* Cinematic media wipe — wide photos reveal left-to-right with a slow zoom-out.
   JS sets the hidden state; container already clips via overflow:hidden. */
[data-reveal-media] { will-change: clip-path; }
[data-reveal-media] img { will-change: transform; }

/* ---- 18. Reduced motion ---- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  [data-reveal] { opacity: 1; transform: none; }
  [data-reveal-words], [data-reveal-media] { opacity: 1; }
  .reveal-word { transform: none; }
  [data-reveal-media] { clip-path: none; }
  [data-reveal-media] img { transform: none; }
}

/* ---- 19. Accessibility helpers ---- */

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

:focus-visible {
  outline: 2px solid var(--text-accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px rgba(248, 249, 252, 0.35);
  border-radius: var(--radius-small);
}

[data-theme="light"] :focus-visible {
  box-shadow: 0 0 0 4px rgba(11, 12, 22, 0.18);
}

/* ============================================================
   20. V3.1 sections — Control · Gains · Calculator · Signup
   ============================================================ */

/* ---- Control (S3) ---- */
.control { background-color: var(--surface-0); }

/* ---- 2026-08-19 · HEADER → CONTENT UNIFIED AT --s-8 -------------------------
   Measured across all four pages: the gap between a section header and its first
   content block was **96px on `.control__header`, `.process__header`, `.faq__header`
   and `.proof__header`, but 64px on `.prose-band__h2`** — the same relationship at two
   values, on the same page in `/evp`'s case. 64px wins because it is the value the
   newer component already used and because the brief was to REDUCE. 12 headers across
   the site drop 32px each.
   ⚠ `.movement__h2` is deliberately EXEMPT at 32px. It is a manifesto heading whose
   "content" is a single closing line, not a block of content — tightening is the point
   there, and raising it to 64 would be standardising in the wrong direction. */
.control__header { text-align: center; max-width: 760px; margin: 0 auto var(--s-8); }
.control__h2 { margin-bottom: var(--s-4); }
.control__intro { color: var(--text-secondary); font-size: var(--type-body-lg); }

.control__three {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-5);
  margin-bottom: var(--s-8);
}
@media (min-width: 768px) { .control__three { grid-template-columns: repeat(3, 1fr); } }

.control-card {
  padding: var(--s-7);
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
}
.control-card__title {
  font-family: var(--font-editorial);
  font-size: var(--type-h3);
  font-weight: 500;
  color: var(--text-primary);
  display: block;
  margin-bottom: var(--s-3);
  letter-spacing: -0.01em;
}
.control-card__body { color: var(--text-secondary); font-size: var(--type-body); line-height: 1.55; }

.control__whitelabel {
  text-align: center;
  max-width: 720px;
  margin: 0 auto;
  padding-top: var(--s-7);
  border-top: 1px solid var(--border-subtle);
}
.control__whitelabel-head {
  font-family: var(--font-editorial);
  font-size: var(--type-h2);
  font-weight: 500;
  margin-bottom: var(--s-4);
}
.control__whitelabel-body {
  color: var(--text-secondary);
  font-size: var(--type-body-lg);
  max-width: 56ch;
  margin-inline: auto;
}

/* ---- Gains (S4) ---- */
.gains { background-color: var(--surface-0); }
.gains__header { text-align: center; max-width: 760px; margin: 0 auto var(--s-8); } /* unified 2026-08-19, see .control__header */
.gains__h2 { margin-bottom: var(--s-4); }

.gains__list {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-5);
  margin-bottom: var(--s-7);
}
@media (min-width: 1024px) { .gains__list { grid-template-columns: repeat(3, 1fr); } }

/* Benchmark proof line — relocated out of gain 2 so the cards stay at parity.
   A quiet centered statement of evidence; the % figures carry the accent. */
.gains__benchmark {
  max-width: 64ch;
  margin-inline: auto;
  text-align: center;
  font-size: var(--type-body-lg);
  line-height: 1.5;
  color: var(--text-secondary);
}
.gains__benchmark .accent { font-weight: 700; }
.gains__benchmark-src {
  color: var(--text-muted);
  font-size: var(--type-caption);
}

.gain {
  padding: var(--s-7);
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
}
.gain__num {
  font-family: var(--font-editorial);
  font-size: var(--type-h2);
  font-weight: 600;
  color: var(--text-accent);
  display: block;
  margin-bottom: var(--s-4);
  line-height: 1;
}
.gain__title {
  font-family: var(--font-editorial);
  font-size: var(--type-h3);
  font-weight: 500;
  margin-bottom: var(--s-3);
  line-height: 1.2;
}
.gain__body { color: var(--text-secondary); font-size: var(--type-body); line-height: 1.55; }

.gains__strip {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-7);
}
@media (min-width: 768px) {
  .gains__strip { grid-template-columns: repeat(3, 1fr); gap: var(--s-7); }
}

/* Editorial Fraunces on the big values (scoped to this strip — overrides the
   Inter data-callout default here only). Tidy, balanced captions with air. */
.gains__strip .stat__number {
  font-family: var(--font-editorial);
  font-weight: 500;
  letter-spacing: -0.01em;
  font-variation-settings: 'opsz' 96;
}
.gains__strip .stat__label {
  max-width: 22ch;
  margin-inline: auto;
  margin-top: var(--s-5);
  text-wrap: balance;
}

/* ---- Calculator section (S5) ---- */
.calc-section { background-color: var(--surface-0); }

/* ---- Signup (S8) — 2-step progressive form ---- */
.signup { background-color: var(--surface-1); }
.signup__inner { max-width: 760px; margin-inline: auto; }
.signup__intro { text-align: center; margin-bottom: var(--s-8); }
.signup__h2 { margin-bottom: var(--s-4); }
.signup__sub { color: var(--text-secondary); font-size: var(--type-body-lg); max-width: 48ch; margin-inline: auto; }

.signup__step {
  background-color: var(--surface-0);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  padding: var(--s-7);
}
.signup__step--2 { margin-top: var(--s-5); }
.signup__step .btn { width: 100%; }

.signup__step-label {
  font-family: var(--font-editorial);
  font-size: var(--type-h3);
  font-weight: 500;
  display: block;
  margin-bottom: var(--s-2);
}
.signup__optional { font-family: var(--font-body); font-size: var(--type-caption); color: var(--text-muted); font-weight: 500; }
.signup__step-note { color: var(--text-muted); font-size: var(--type-caption); margin-bottom: var(--s-6); }

.signup__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-5);
  margin-bottom: var(--s-6);
}
@media (min-width: 640px) { .signup__grid { grid-template-columns: repeat(2, 1fr); } }
.form__field--full { grid-column: 1 / -1; }

.signup__reward {
  margin-block: var(--s-5);
  padding: var(--s-5) var(--s-6);
  background-color: var(--surface-2);
  border-left: 3px solid var(--text-accent);
  border-radius: var(--radius-card-sm);
  color: var(--text-primary);
  font-size: var(--type-body);
  line-height: 1.5;
}
.signup__done {
  margin-top: var(--s-5);
  padding: var(--s-5) var(--s-6);
  background-color: var(--surface-2);
  border-radius: var(--radius-card-sm);
  color: var(--text-primary);
}

/* ============================================================
   21. Motion + micro-interactions (/impeccable enhance pass)
   One coherent feedback language: hover lift on cards, shadow on
   primary CTAs, icon feedback on the toggle, smooth disclosure on
   FAQ, a condensing nav, and a slow hero entrance. All bounded to
   pointer:fine where it's a hover, and disabled under reduced-motion
   by the global cap in §18.
   ============================================================ */

/* Hover lift — content cards. Steps keep their bg shift and gain a lift. */
.control-card,
.gain,
.emblem,
.step {
  transition: transform 250ms var(--ease-out-quart),
              border-color 250ms var(--ease-out-quart),
              background-color var(--duration-fast) var(--ease-out-quart),
              box-shadow 250ms var(--ease-out-quart);
}

@media (hover: hover) and (pointer: fine) {
  .control-card:hover,
  .gain:hover,
  .emblem:hover,
  .step:hover {
    transform: translateY(-8px) scale(1.015);
    border-color: var(--text-accent);
    box-shadow: var(--shadow-3), 0 10px 30px rgba(148, 63, 120, 0.18);
  }
  /* The gain numeral pops when its card lifts. */
  .gain:hover .gain__num { transform: scale(1.14); }
}

.gain__num { transition: transform 250ms var(--ease-out-quart); }

/* Primary CTA — confident lift + scale + a stronger magenta glow on hover. */
.btn-primary {
  transition: transform var(--duration-fast) var(--ease-out-quart),
              background-color var(--duration-fast) var(--ease-out-quart),
              box-shadow var(--duration-fast) var(--ease-out-quart);
}
@media (hover: hover) {
  .btn-primary:hover {
    transform: translateY(-2px) scale(1.03);
    box-shadow: 0 10px 30px rgba(148, 63, 120, 0.45);
  }
  .btn-primary:active { transform: translateY(0) scale(0.99); }
}

/* Nav links — magenta underline grows from the left on hover. */
.nav__link { position: relative; }
.nav__link::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -4px;
  height: 1.5px;
  width: 0;
  background-color: var(--text-accent);
  transition: width 250ms var(--ease-out-quart);
}
.nav__link:hover::after { width: 100%; }
/* AFTER .nav__link::after, so it actually wins. Measured to paint this time. */
.nav__link--active::after { width: 100%; }

/* FAQ — the + grows and the question brightens on hover (closed items). */
.faq__question { transition: color var(--duration-fast) var(--ease-out-quart); }
@media (hover: hover) {
  .faq__item:not([open]) .faq__question:hover { color: var(--text-primary); }
  /* Scope the + scale to closed items so it never fights the [open] × rotate. */
  .faq__item:not([open]) .faq__question:hover::after { transform: scale(1.25); }
}

/* Theme toggle — icon nudges on hover, spins on press. */
.theme-toggle__icon {
  transition: transform var(--duration-base) var(--ease-out-quart);
}
.theme-toggle:hover .theme-toggle__icon { transform: rotate(18deg); }
.theme-toggle:active .theme-toggle__icon { transform: rotate(180deg); }

/* Nav condenses slightly once scrolled. */
.nav {
  transition: background-color var(--duration-base) var(--ease-out-quart),
              padding-block var(--duration-base) var(--ease-out-quart);
}
.nav.is-scrolled { padding-block: var(--s-3); }

/* FAQ — fade + slide the answer in on open (native <details> snaps otherwise). */
.faq__item[open] .faq__answer {
  animation: faqReveal var(--duration-base) var(--ease-out-quart);
}
@keyframes faqReveal {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Hero bg scale-in is GSAP-driven (composes with parallax). CSS fallback for
   no-JS: a one-shot scale on load. GSAP overrides transform when present. */
@media (prefers-reduced-motion: no-preference) {
  .hero__bg { animation: heroIn 1.5s var(--ease-out-expo) both; }
}
@keyframes heroIn {
  from { transform: scale(1.07); }
  to   { transform: scale(1); }
}

/* Slightly more separation before the gains stat strip + control white-label,
   so the "summary" beats read as their own moment, not another card row. */
.gains__strip { margin-top: var(--s-9); }
.control__whitelabel { margin-top: var(--s-8); }

/* ============================================================
   HUB — root index.html. Industry chooser for the 5 verticals.
   Shares this design system. Introduces hub→vertical navigation:
   the hub is the single entry point; the verticals themselves still
   never cross-link to each other (supersedes the v1 "no `/` content"
   standalone rule for the root only).
   ============================================================ */

.hub-main { min-height: 100vh; display: flex; flex-direction: column; }

.hub-hero {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding-top: calc(var(--s-10) + var(--s-8));
  padding-bottom: var(--s-7);
}

.hub-hero__title {
  font-family: var(--font-editorial);
  font-size: var(--type-display);
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--text-primary);
  max-width: 18ch;
  margin-bottom: var(--s-5);
}

.hub-hero__sub {
  font-size: var(--type-body-lg);
  color: var(--text-secondary);
  max-width: 54ch;
}

.hub-chooser { padding-bottom: var(--s-10); flex: 1; }

.hub-chooser__label {
  font-size: var(--type-eyebrow);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 600;
  color: var(--text-accent);
  margin-bottom: var(--s-5);
}

.vertical-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(248px, 1fr));
  gap: var(--s-5);
}

.vertical-card {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-6);
  min-height: 184px;
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  transition: transform var(--duration-fast) var(--ease-out-quart),
              border-color var(--duration-fast) var(--ease-out-quart),
              background-color var(--duration-fast) var(--ease-out-quart);
}

a.vertical-card:hover {
  transform: translateY(-4px);
  border-color: var(--text-accent);
  background-color: var(--surface-2);
}

.vertical-card__name {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3);
  font-size: var(--type-h3);
  font-weight: 600;
  color: var(--text-primary);
}

.vertical-card__name::after {
  content: '\2192';
  color: var(--text-accent);
  transition: transform var(--duration-fast) var(--ease-out-quart);
}
a.vertical-card:hover .vertical-card__name::after { transform: translateX(4px); }

.vertical-card__desc {
  font-size: var(--type-caption);
  color: var(--text-muted);
  line-height: 1.5;
}

.vertical-card--soon { opacity: 0.55; cursor: default; }
.vertical-card--soon .vertical-card__name::after { content: none; }

.vertical-card__badge {
  margin-top: auto;
  align-self: flex-start;
  font-size: var(--type-eyebrow);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
  color: var(--text-secondary);
  background-color: var(--surface-3);
  padding: var(--s-1) var(--s-3);
  border-radius: var(--radius-pill);
}

.hub-footer {
  border-top: 1px solid var(--border-subtle);
  padding-block: var(--s-6);
  color: var(--text-muted);
  font-size: var(--type-caption);
}

/* ============================================================
   CredX Website — Home deltas (multi-page nav, placeholders,
   footer-sitemap, pull-quote, trust markers, partnership line).
   Seeded from the Automotive design system; only the additions live here.
   ============================================================ */

/* --- Multi-page nav: hamburger + mobile menu --- */
.nav__toggle {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  /* 40x40 failed the 44px tap minimum in BOTH dimensions, and below 768 this is
     the only control in the header. */
  width: 44px;
  height: 44px;
  padding: 10px;
  background: transparent;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-small);
  cursor: pointer;
}
.nav__toggle span {
  display: block;
  height: 2px;
  width: 100%;
  background: var(--text-primary);
  border-radius: 2px;
  transition: transform var(--duration-fast) var(--ease-out-quart), opacity var(--duration-fast);
}
.nav--open .nav__toggle span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav--open .nav__toggle span:nth-child(2) { opacity: 0; }
.nav--open .nav__toggle span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

@media (min-width: 960px) { .nav__toggle { display: none; } }

@media (max-width: 959px) {
  .nav--open .nav__links {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-4);
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    padding: var(--s-5) var(--container-pad);
    background: var(--nav-bg-scrolled);
    -webkit-backdrop-filter: blur(18px);
    backdrop-filter: blur(18px);
    border-top: 1px solid var(--border-subtle);
  }
  .nav--open .nav__link { font-size: var(--type-body); }

  /* 🔴 TOUCH TARGETS — measured 2026-08-20 with getBoundingClientRect in the open
     sheet: .nav__link 24px tall, .nav__group-toggle 24px, .nav__cta 30px, against
     a 44px minimum. Only .nav__toggle passed, because it was raised 40 → 44 in
     August for this exact reason — the standard was set and the LINKS were missed.
     ⚠ Not introduced by the nav rebuild; the drawer already shipped these. What
     the rebuild changed is exposure: the drawer now serves up to 959px instead of
     767px, so more visitors meet them. Fixing rather than inheriting. */
  .nav--open .nav__link,
  .nav--open .nav__sublink {
    display: flex;
    align-items: center;
    min-height: 44px;
  }
  /* The CTA sits OUTSIDE the drawer, in .nav__actions, so it needs its own rule.
     ⚠ It is also covered by the (pointer: coarse) block below — but that block
     cannot be verified with the available instrument (headless reports
     coarse=false), and an unverifiable fix is not a fix. Below 960 the hamburger
     is showing, which is the touch-oriented context by construction, so bind it
     to the breakpoint as well and measure it there. Belt and braces, on purpose. */
  .nav__cta {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

/* The CTA sits in .nav__actions, outside the drawer, so it needs its own rule —
   and the right signal for a touch target is the POINTER, not the viewport. A
   1200px tablet is coarse and a 900px window on a laptop is not. */
@media (pointer: coarse) {
  .nav__cta,
  .nav__join {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
}

/* --- Hero: movement line + placeholder background --- */
.hero__movement {
  font-family: var(--font-editorial);
  font-size: var(--type-body-lg);
  color: var(--text-accent);
  margin-top: var(--s-5);
  /* The line had NO margin-bottom, so the CTA row sat flush against it and the
     movement line read as a label for the buttons instead of the closing beat of
     the copy block. 48px matches the 48px above it (hero__sub's margin-bottom,
     which collapses over this element's 24px top), so the line now sits as its own
     beat with equal air on both sides. Fixed 2026-07-27. */
  margin-bottom: var(--s-7);
  max-width: 42ch;
}
.hero__bg--placeholder {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse at 75% 25%, rgba(148, 63, 120, 0.35), transparent 55%),
    radial-gradient(ellipse at 25% 80%, rgba(109, 94, 164, 0.30), transparent 55%),
    linear-gradient(150deg, var(--ink), var(--navy));
}
/* Placeholder hero is always dark — keep the overlaid copy light in both themes. */
.hero--full .hero__h1,
.hero--full .hero__sub { color: var(--cloud-white); }

/* --- Image placeholder box (until Phase 8 renders land) --- */
/* A reserved video slot, at the ratio the video will actually be. It stays a
   dashed placeholder rather than a mocked-up player: a fake play button would
   read as a broken video instead of as a slot that is honestly not built yet.
   Marco, 2026-08-18: one placeholder on this page, and it is the video. */
.media-ph {
  position: relative;
  min-height: 300px;
  border: 1px dashed var(--border-default);
  border-radius: var(--radius-card);
  background:
    repeating-linear-gradient(135deg, rgba(167,154,180,0.05) 0 12px, transparent 12px 24px),
    var(--surface-1);
  display: flex;
  align-items: center;
  justify-content: center;
}
.media-ph::after {
  content: 'Image slot — ' attr(data-img-ph);
  display: block;
  /* 🔴 WAS A FLAT `42ch`, AND IT BLEW THE MOBILE LAYOUT VIEWPORT OPEN. Measured
     2026-08-21 at 390x844 with real device emulation: the label's min-content is
     ~533px, `.media-ph` is a flex container, so the slot could not shrink and
     Chrome expanded `window.innerWidth` from 390 to 549 — the whole page laid out
     at the wrong width, nav included.
     ⚠ It was latent, not new: the site's only other `.media-ph` is the wide video
     slot on /how-it-works, which never gets narrow enough to trip it. The first
     narrow consumer found it. Fixed in the component, because the next one would
     find it again.
     ⚠ `min()` and not a media query — the constraint is the CONTAINER's width, and
     this component sits in containers of several widths at the same viewport. */
  max-width: min(42ch, 100%);
  padding: var(--s-5);
  text-align: center;
  font-size: var(--type-caption);
  color: var(--text-muted);
}
/* ⚠ Must sit AFTER `.media-ph::after` — same specificity (0,1,1), so position
   decides. Placed before it, this lost silently and the slot kept saying
   "Image slot". Second time this exact cascade trap bit today. */
.media-ph--video::after { content: 'Video slot — ' attr(data-img-ph); }

/* 🔴 MOVED BELOW `.media-ph` ON 2026-08-21 — THIS WAS A LIVE MOBILE DEFECT ON
   `/how-it-works`, NOT A TIDY-UP. `--video` declared `min-height: 0` to cancel
   the base's `min-height: 300px`, but it sat ABOVE the base at equal
   specificity (0,1,0), so it LOST and the 300px stood. Combined with
   `aspect-ratio: 16/9` that resolved the slot to 300 x 16/9 = 533px inside a
   358px column, and Chrome expanded the layout viewport: `/how-it-works`
   measured `document.scrollWidth` and `window.innerWidth` at 549 ON A 390px
   DEVICE — the whole page, nav included, laid out at the wrong width.
   ⚠ It was found by accident, while measuring the `.prose-band` two-column
   change across all six pages. It had been shipping since the video slot did.
   ⚠ THE ORDER WAS THE WHOLE BUG, so reordering is the fix rather than a risk —
   an earlier pass the same day left it alone out of caution and scoped
   `min-height: 0` onto `.media-ph--band` instead, which fixed one consumer and
   left this one broken. A modifier belongs after the base it modifies.
   ⚠ This is the FOURTH cascade trap in this one component, and every one of
   them was source order at equal specificity. If a fifth appears, the component
   wants restructuring, not another patch. */
.media-ph--video {
  aspect-ratio: 16 / 9;
  min-height: 0;
}

/* --- S2 partnership synthesis line --- */
.voices__partnership {
  margin-top: var(--s-6);
  padding-top: var(--s-6);
  border-top: 1px solid var(--border-subtle);
  color: var(--text-secondary);
  font-size: var(--type-body-lg);
  line-height: 1.6;
  max-width: 70ch;      /* readable measure inside the full-width section */
  margin-inline: auto;
  text-align: center;
}
.voices__partnership strong { color: var(--text-primary); font-weight: 600; }

/* --- S6 Proof: trust markers --- */
/* `.proof__markers` (the 3-up card row) and `.trust` (its card shell) were removed
   2026-07-27 — the markers moved INTO `.proof__mosaic` as `.proof__tile--stat`,
   which supplies the surface, border and radius. Only the type styles below are
   still live, reused verbatim inside the mosaic tiles. */
.trust__lead {
  display: block;
  font-family: var(--font-editorial);
  font-size: var(--type-h3);
  color: var(--text-accent);
  margin-bottom: var(--s-3);
}
.trust__body { color: var(--text-secondary); font-size: var(--type-body); }
.proof__pending {
  text-align: center;
  margin-top: var(--s-6);
  font-size: var(--type-caption);
  color: var(--text-muted);
  font-style: italic;
}

/* --- S7 Founder pull-quote --- */
.pullquote { background-color: var(--surface-0); }
.pullquote__inner { max-width: 900px; margin-inline: auto; text-align: center; }
.pullquote__text {
  font-family: var(--font-editorial);
  font-size: clamp(1.5rem, 2.5vw + 1rem, 2.4rem);
  line-height: 1.35;
  color: var(--text-primary);
  letter-spacing: -0.01em;
  margin: 0;
  border: 0;
  padding: 0;
  /* Kills the widow: the quote was breaking with `in it."` alone on line four.
     `balance` evens the line lengths across the whole block, which is what display
     text this short wants (`pretty` only protects the last line). Fixed 2026-07-27. */
  text-wrap: balance;
}
.pullquote__attribution {
  margin-top: var(--s-6);
  font-size: var(--type-caption);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  /* Same trap as .voices__closer — the global p max-width left this sitting at
     mid=590 against a container centre of 705. Fixed 2026-07-27. */
  margin-inline: auto;
}

/* --- Footer: brand column movement line --- */
.footer__movement {
  font-family: var(--font-editorial);
  font-size: var(--type-body-lg);
  color: var(--text-secondary);
  max-width: 30ch;
}

/* ============================================================
   23. Contact page (/contact) — added 2026-07-31
   Built almost entirely on existing classes (.section, .signup__*,
   .form__*, .eyebrow, .btn). Only the page-specific layout below is new.
   ⚠ Reminder for anyone editing centred text here: the global rule
   `p { max-width: 65ch }` (line 228) caps the BOX, so a centred <p>
   without `margin-inline: auto` centres its text inside a box that
   hugs the left edge. Two Home paragraphs shipped that bug on
   2026-07-27. Every centred paragraph below carries the auto margin.
   ============================================================ */

/* --- 1 · Intro --- */
.contact-intro__inner {
  max-width: 760px;
  margin-inline: auto;
  text-align: center;
}
.contact-intro__title { margin-bottom: var(--s-5); }
.contact-intro__body {
  font-size: var(--type-body-lg);
  color: var(--text-secondary);
  max-width: 52ch;
  margin-inline: auto;          /* required — see the note above */
}
.contact-intro__alt {
  margin-top: var(--s-6);
  font-size: var(--type-body);
  color: var(--text-muted);
  max-width: 52ch;
  margin-inline: auto;          /* required — see the note above */
}

/* --- 2 · Form: the note under the submit button --- */
.form__note {
  margin-top: var(--s-4);
  font-size: var(--type-caption);
  color: var(--text-muted);
  max-width: none;              /* overrides p{max-width:65ch}; the field grid sets the measure */
}

/* --- 3 + 4 · Secondary blocks --- */
.contact-block { text-align: center; }
.contact-block__title { margin-bottom: var(--s-4); }
.contact-block__body {
  color: var(--text-secondary);
  max-width: 46ch;
  margin-inline: auto;          /* required — see the note above */
  margin-bottom: var(--s-6);
}

/* --- 4 · Alternative-contact list --- */
.contact-list {
  margin-top: var(--s-6);
  text-align: left;
  display: grid;
  gap: var(--s-4);
}
.contact-list__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-1);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--border-subtle);
}
@media (min-width: 640px) {
  .contact-list__row {
    grid-template-columns: 200px 1fr;
    align-items: baseline;
    gap: var(--s-5);
  }
}
.contact-list__row:last-child {
  border-bottom: none;
  padding-bottom: 0;
}
.contact-list__label {
  font-size: var(--type-eyebrow);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-secondary);
}
.contact-list__value {
  margin: 0;
  color: var(--text-primary);
  font-size: var(--type-body);
}

/* --- Placeholder marker — DELIBERATELY LOUD --------------------
   Every unresolved value on this page renders through .pending. It is
   ugly on purpose: this repo auto-deploys `main` to GitHub Pages with
   no staging step, so a push IS a publish. A placeholder that looked
   like real content could reach the live site unnoticed; this one
   cannot. Gate before publishing:
       grep -rn "{{PENDING:" credx-website/
   Registered in copy/contact.md §Placeholder register.
   -------------------------------------------------------------- */
.pending {
  display: inline-block;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.78rem;
  line-height: 1.45;
  color: #ffd166;
  background-color: rgba(255, 209, 102, 0.08);
  border: 1px dashed #ffd166;
  border-radius: var(--radius-small);
  padding: 0.35em 0.6em;
  max-width: none;
}
.pending--banner {
  display: block;
  margin-bottom: var(--s-6);
  text-align: left;
}
.pending code {
  font-family: inherit;
  color: inherit;
  background-color: rgba(255, 209, 102, 0.14);
  padding: 0.05em 0.3em;
  border-radius: 3px;
}

/* Inert links while their destination is a placeholder. */
[aria-disabled="true"] { cursor: not-allowed; opacity: 0.55; }

/* ============================================================
   24. /JOIN — MERCHANT APPLICATION  (page-specific)
   ------------------------------------------------------------
   OPERATE-mode surface. Every other page in this project is PERSUADE
   (marketing, scroll-revealed, fluid clamp type). A form is a task, and
   the rules differ:

   · Type is a FIXED rem scale, not clamp(). A label that shrinks with the
     viewport helps nobody mid-application; the brand's fluid scale stays
     on the intro heading only, where it is still brand.
   · Motion is 140–220ms and conveys STATE only. No scroll reveals, no
     word reveal, no smooth-scroll library on this page.
   · Elevation is declared ONCE — border, never border + shadow.
   · Radius steps down to --radius-card-sm (16px) for step cards and
     --radius-small (8px) for controls. The 24px brand card radius is a
     marketing-surface value; at form density it reads soft.
   · No nested cards. The prototype nests a card per upload row inside the
     step card; rows with dividers carry that separation instead.

   Reuses from the existing system: .btn / .btn-primary / .btn-secondary /
   .btn-text, .form__input / .form__textarea / .form__select, .signup__grid,
   .eyebrow, .sr-only, and the whole token set. Nothing is recreated.
   ============================================================ */

.join-body {
  background-color: var(--surface-0);
  /* An application is a bounded task, not an endless page. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  /* The site nav is `position: fixed`, so it floats over content and the page
     has to reserve its height. ⚠ The nav shrinks to padding-block: var(--s-3)
     when script.js adds `.is-scrolled` — this page deliberately does NOT load
     script.js (its motion layer is Persuade-only), so the height is stable.
     If script.js is ever added here, this value and the rail offset need the
     scrolled height too. */
  padding-top: var(--join-nav-h);
}

/* ⚠ MEASURED, not derived — and my arithmetic was wrong first time.
   I computed 61px from the 28px logo + 2×16px padding + 1px border; the real
   height is 63px, because the `Book a meeting` button is the tallest child at
   30px, not the logo. The 2px error put the rail underneath the nav.
   Do not hand-edit this: the nav-geometry probe asserts
   `--join-nav-h === the measured nav height`, so if the nav ever changes the
   check fails loudly instead of drifting silently. */
:root { --join-nav-h: 63px; }

/* ---- 24.1 Nav ----
   This page uses the STANDARD site nav (Marco, 2026-08-03), not a reduced
   application chrome. The `.nav*` rules already exist and are reused verbatim;
   nothing here restyles them. ⚠ The nav is duplicated per page in this project
   rather than being a component, so a nav change is N edits and this page is
   now one of the N.
   ⚠ Two hrefs necessarily DIFFER from the Home and Contact copies because this
   page sits one level deep and must resolve in both mounts (Vercel serves the
   repo root; credx-tech.com will serve credx-website as root): the logo is
   `../` and both `Contact Us` and the `Book a meeting` fallback are
   `../contact/`. The three post-MVP links (/how-it-works, /lenders, /about)
   are kept byte-identical to the other pages so the nav block stays
   copy-pasteable — they 404 in every mount today regardless. */

/* ---- 24.2 Progress rail ----
   Sticky: an 8-step form must answer "how much is left?" without scrolling
   up. No overflow ancestor, so nothing clips it. */

.join-rail {
  position: sticky;
  /* Sticks BELOW the fixed nav, not under it. z-index stays under --z-nav (50)
     so the mobile menu, which drops from the nav at top:100%, overlays the rail
     instead of being clipped by it. */
  top: var(--join-nav-h);
  z-index: 20;
  background-color: rgba(11, 12, 22, 0.92);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border-subtle);
}
.join-rail__inner {
  max-width: 900px;
  margin-inline: auto;
  padding: var(--s-4) var(--container-pad) var(--s-3);
}
.join-rail__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-3);
}
.join-rail__count { font-size: 0.875rem; color: var(--text-secondary); }
.join-rail__count strong { color: var(--text-primary); font-weight: 600; }
.join-rail__name {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text-primary);
  text-align: right;
}

.join-rail__track {
  height: 3px;
  border-radius: var(--radius-pill);
  background-color: var(--surface-3);
  overflow: hidden;
}
.join-rail__fill {
  display: block;
  height: 100%;
  width: 0%;
  border-radius: var(--radius-pill);
  background-color: var(--magenta);
  /* State transition, not choreography. */
  transition: width 220ms var(--ease-out-quart);
}

.join-rail__steps {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  margin-top: var(--s-3);
}
.join-rail__step { display: flex; justify-content: center; }
.join-rail__step-num {
  font-size: 0.75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  opacity: 0.6;
  transition: color 140ms var(--ease-out-quart), opacity 140ms var(--ease-out-quart);
}
.join-rail__step.is-current .join-rail__step-num { color: var(--text-primary); opacity: 1; }
/* Completed steps are marked by a GLYPH as well as colour — state is never
   carried by colour alone (WCAG 1.4.1). */
.join-rail__step.is-done .join-rail__step-num { color: var(--text-accent); opacity: 1; font-size: 0; }
.join-rail__step.is-done .join-rail__step-num::after { content: "\2713"; font-size: 0.8125rem; }

/* ---- 24.3 Intro ---- */

.join-main {
  flex: 1 0 auto;
  width: 100%;
  max-width: 900px;
  margin-inline: auto;
  padding: var(--s-8) var(--container-pad) var(--s-9);
}
.join-intro { margin-bottom: var(--s-7); }
.join-intro__title {
  /* The one place the brand's fluid display scale still applies: this is the
     page's brand moment, above the task. */
  font-size: var(--type-h1);
  font-family: var(--font-display);
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin: var(--s-3) 0 var(--s-4);
}
.join-intro__body {
  color: var(--text-secondary);
  font-size: 1.0625rem;
  max-width: 52ch;
}

/* ---- 24.4 Step card ----
   Elevation declared ONCE (border). No shadow underneath it. */

.join-step {
  border: 1px solid var(--border-default);
  border-radius: var(--radius-card-sm);
  background-color: var(--surface-1);
  overflow: hidden;
}
.join-step__head {
  padding: var(--s-5) var(--s-6);
  /* The prototype uses a flat navy band. The brand gradient runs at a low
     angle and stops short of magenta, so the header reads as a surface and
     never competes with the primary button — the only magenta on the card. */
  background: linear-gradient(100deg, var(--navy) 0%, #3c3f74 100%);
  border-bottom: 1px solid var(--border-default);
}
.join-step__title {
  /* ⚠ REQUIRED, not cosmetic. The global rule is
     `h1, h2, h3, h4 { font-family: var(--font-editorial) }` — Fraunces. This page
     deliberately does not load Fraunces, so every heading was falling back to
     GEORGIA: an accidental serif in form UI, which is the one thing Operate mode
     bans outright. Every heading on this page pins the sans explicitly. */
  font-family: var(--font-display);
  font-size: 1.375rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--cloud-white);
  margin-bottom: var(--s-2);
}
.join-step__title:focus-visible {
  outline: 2px solid var(--magenta-bright);
  outline-offset: 4px;
  border-radius: var(--radius-small);
}
.join-step__sub {
  font-size: 0.9375rem;
  /* Secondary text on a coloured surface is tinted from that surface's own
     family, never grey. */
  color: #bdb6d8;
  margin: 0;
}

.join-step__body {
  padding: var(--s-6);
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
}
.join-step__foot {
  padding: var(--s-5) var(--s-6);
  border-top: 1px solid var(--border-default);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  background-color: var(--surface-1);
}

@media (max-width: 560px) {
  .join-step__head { padding: var(--s-5) var(--s-4); }
  .join-step__body,
  .join-step__foot { padding: var(--s-5) var(--s-4); }
  /* Buttons go full width and the primary sits first in visual order on narrow
     screens, where thumb reach beats convention. */
  .join-step__foot { flex-direction: column-reverse; align-items: stretch; }
  .join-step__foot .btn { width: 100%; justify-content: center; }
  .join-step__foot > span:empty { display: none; }
}

/* ---- 24.5 Fields ----
   .form__label is overridden inside .join-form ONLY. The shared definition is
   a 12px uppercase tracked micro-label, which reads as deliberate on the
   Automotive form's 4–9 fields. This page has ~40 fields across 8 screens,
   where the same treatment becomes a wall and costs the scanability that is an
   Operate surface's whole job. Colour, weight intent and spacing stay. */

/* ⚠ Do NOT give .join-form an explicit `display`. A class-level `display: block`
   outranks the UA stylesheet's `[hidden] { display: none }`, so `form.hidden = true`
   silently does nothing and the whole eight-step form stays on screen underneath
   the confirmation. That bug was live until a render caught it — the rail and the
   intro vanished correctly only because they declare no display of their own.
   This guard makes the attribute win for every element on the page. */
.join-body [hidden] { display: none !important; }

.join-field { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }
.join-form .form__label {
  font-size: 0.875rem;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0;
  color: var(--text-primary);
  line-height: 1.3;
}
.join-form .form__input,
.join-form .form__textarea,
.join-form .form__select {
  width: 100%;
  border-radius: var(--radius-small);
  background-color: var(--surface-0);
  border-color: var(--surface-3);
  font-size: 1rem;
  /* 44px minimum target, and 16px text so iOS does not zoom on focus. */
  min-height: 44px;
}
.join-form .form__input::placeholder,
.join-form .form__textarea::placeholder {
  /* Placeholder contrast is a real requirement, not a decorative choice. */
  color: rgba(167, 154, 180, 0.7);
}
.join-form .form__input:hover,
.join-form .form__textarea:hover,
.join-form .form__select:hover { border-color: var(--soft-lavender); }
.join-form .form__input:focus-visible,
.join-form .form__textarea:focus-visible,
.join-form .form__select:focus-visible {
  outline: 2px solid var(--magenta-bright);
  outline-offset: 1px;
  border-color: var(--magenta-bright);
}
.join-form .form__textarea { min-height: 88px; line-height: 1.5; }
.join-form .form__select {
  appearance: none;
  /* Native select, restyled — not a reinvented control. The chevron is the
     standard affordance; a custom dropdown would be flavour at the cost of
     keyboard and mobile behaviour that already works. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23a79ab4' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--s-4) center;
  padding-right: var(--s-8);
}

.join-req { color: var(--text-accent); font-weight: 600; }

.join-help {
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--text-muted);
  margin: 0;
  max-width: 62ch;
}
.join-group {
  font-family: var(--font-display); /* pins the sans — see .join-step__title */
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-secondary);
  margin-top: var(--s-2);
  padding-top: var(--s-5);
  border-top: 1px solid var(--border-subtle);
}

.join-rule { border: 0; border-top: 1px solid var(--border-subtle); margin: 0; }

.join-field--half { max-width: 100%; }
@media (min-width: 640px) { .join-field--half { max-width: calc(50% - var(--s-2)); } }

.join-field-row { display: grid; gap: var(--s-4); }
@media (min-width: 640px) { .join-field-row--3 { grid-template-columns: 2fr 1fr 1fr; } }

/* Error state — the border is not the only signal; the label keeps its
   contrast and the browser's own message names the problem and the fix. */
.join-field.has-error .form__input,
.join-field.has-error .form__textarea,
.join-field.has-error .form__select { border-color: #e2708f; }

/* ---- 24.6 Checkbox ---- */

.join-check { display: flex; align-items: flex-start; gap: var(--s-3); }
.join-check__box {
  appearance: none;
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  border: 1px solid var(--surface-3);
  border-radius: 5px;
  background-color: var(--surface-0);
  cursor: pointer;
  transition: background-color 140ms var(--ease-out-quart), border-color 140ms var(--ease-out-quart);
}
.join-check__box:hover { border-color: var(--soft-lavender); }
.join-check__box:checked {
  background-color: var(--magenta);
  border-color: var(--magenta);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
}
.join-check__box:focus-visible { outline: 2px solid var(--magenta-bright); outline-offset: 2px; }
.join-check__label { font-size: 0.9375rem; color: var(--text-primary); cursor: pointer; line-height: 1.45; }

/* A help line straight after a checkbox belongs TO that checkbox, so it closes
   the card's 24px stack gap down to 8px and indents to the label's text edge
   (20px box + 12px gap). At the full gap the consent sentence read as a separate
   item rather than a note governing the control above it — which for a consent
   statement is a meaning problem, not a spacing one. */
.join-check + .join-help {
  margin-top: calc(var(--s-2) - var(--s-5));
  padding-left: calc(20px + var(--s-3));
}

/* ---- 24.7 Document rows ----
   Rows with dividers, NOT nested cards. */

.join-formats { font-size: 0.8125rem; color: var(--text-muted); margin: 0; }

.join-docs { list-style: none; border-top: 1px solid var(--border-subtle); }
.join-doc {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-4) 0;
  border-bottom: 1px solid var(--border-subtle);
}
.join-doc__text { flex: 1 1 auto; min-width: 0; }
.join-doc__name { font-size: 0.9375rem; font-weight: 600; color: var(--text-primary); margin-bottom: 2px; }
.join-doc__desc { font-size: 0.8125rem; color: var(--text-muted); margin: 0; line-height: 1.45; }
/* The chosen filename, echoed back. Without it the merchant cannot tell a
   successful pick from a cancelled dialogue. */
.join-doc__file {
  font-size: 0.8125rem;
  color: var(--text-accent);
  margin: var(--s-2) 0 0;
  word-break: break-all;
}
/* Visually hidden but NOT display:none — the input stays focusable and its
   <label> is the real, keyboard-reachable control. */
.join-doc__input {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
.join-doc__btn { flex: 0 0 auto; cursor: pointer; }
.join-doc__input:focus-visible + .join-doc__btn,
.join-doc__btn:focus-visible { outline: 2px solid var(--magenta-bright); outline-offset: 2px; }
.join-doc.is-attached .join-doc__name::after {
  content: "\2713";
  color: var(--text-accent);
  margin-left: var(--s-2);
  font-weight: 700;
}

@media (max-width: 560px) {
  .join-doc { flex-direction: column; align-items: stretch; }
  .join-doc__btn { width: 100%; justify-content: center; }
}

/* Disabled primary — the gate on `Continue to review`. Muted, not a
   full-saturation accent sitting there inert. */
.join-step__foot .btn[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
  pointer-events: none;
}

/* ---- 24.8 Review ---- */

.join-review { display: flex; flex-direction: column; gap: var(--s-5); }
.join-review__group { border-top: 1px solid var(--border-subtle); padding-top: var(--s-4); }
.join-review__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-3);
}
.join-review__label {
  font-family: var(--font-display); /* pins the sans — see .join-step__title */
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--text-accent);
  margin: 0;
}
.join-review__edit { font-size: 0.8125rem; background: none; border: 0; cursor: pointer; padding: 0; }
.join-review__list {
  display: grid;
  gap: var(--s-2) var(--s-4);
  margin: 0;
  font-size: 0.875rem;
}
@media (min-width: 560px) {
  .join-review__list { grid-template-columns: minmax(0, 15rem) minmax(0, 1fr); }
}
.join-review__list dt { color: var(--text-secondary); line-height: 1.45; }
.join-review__list dd { color: var(--text-primary); margin: 0; line-height: 1.45; overflow-wrap: anywhere; }
.join-review__list dd.is-empty { color: var(--text-muted); font-style: italic; }
@media (max-width: 559px) { .join-review__list dd { margin-bottom: var(--s-2); } }

.join-notice {
  font-size: 0.875rem;
  line-height: 1.55;
  color: var(--text-primary);
  background-color: rgba(109, 94, 164, 0.14);
  border: 1px solid rgba(109, 94, 164, 0.4);
  border-radius: var(--radius-small);
  padding: var(--s-4);
  margin: 0;
}

/* ---- 24.9 Confirmation ---- */

.join-done { text-align: center; padding-block: var(--s-6) var(--s-8); }
.join-done:focus-visible { outline: none; }
.join-done__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 64px; height: 64px;
  border-radius: 50%;
  background-color: rgba(148, 63, 120, 0.18);
  color: var(--magenta-bright);
  margin-bottom: var(--s-5);
}
.join-done__title {
  font-size: var(--type-h2);
  font-family: var(--font-display);
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin-bottom: var(--s-3);
}
.join-done__sub {
  color: var(--text-secondary);
  font-size: 1.0625rem;
  max-width: 46ch;
  /* Same widow fix already used on the Home pull-quote — the measure cap was
     breaking this line before "happens next:". */
  text-wrap: balance;
  /* The global `p { max-width: 65ch }` caps the box; without an auto margin a
     centred paragraph centres its text inside an off-centre box. That bug was
     found and fixed twice on the Home — do not drop these margins. */
  margin-inline: auto;
  margin-bottom: var(--s-7);
}

.join-next {
  list-style: none;
  text-align: left;
  max-width: 34rem;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-card-sm);
  padding: var(--s-6);
  background-color: var(--surface-1);
}
.join-next__item { display: flex; gap: var(--s-4); align-items: flex-start; }
.join-next__num {
  flex: 0 0 auto;
  width: 28px; height: 28px;
  border-radius: 50%;
  background-color: var(--navy);
  color: var(--cloud-white);
  font-size: 0.8125rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.join-next__title { font-size: 0.9375rem; font-weight: 600; color: var(--text-primary); margin-bottom: 4px; }
.join-next__body { font-size: 0.875rem; line-height: 1.55; color: var(--text-secondary); margin: 0; }

.join-done__contact {
  font-size: 0.875rem;
  color: var(--text-muted);
  margin-top: var(--s-7);
  margin-inline: auto;
  max-width: 46ch;
}
/* Unresolved value, unmistakably marked. Nothing here may render as a
   plausible-looking address — that is exactly the guess this marker prevents. */
.join-pending {
  color: var(--text-accent);
  border-bottom: 1px dashed currentColor;
}

/* ---- 24.10 Footer ---- */

/* 🟠 UNREFERENCED SINCE 2026-08-25. `/join` was its only consumer and now renders the
   standard site footer like every other page (Marcos: "tornar padrão"). Kept, not deleted,
   because the funnel argument for a bare footer on an 8-step form is real and this is the
   rule to restore if it wins. ⚠ If that call goes the other way, delete this block — an
   orphaned rule with no consumer is the same maintenance liability as `.photo-band`. */
.join-foot {
  flex: 0 0 auto;
  border-top: 1px solid var(--border-subtle);
  padding: var(--s-5) var(--container-pad);
  text-align: center;
  font-size: 0.8125rem;
  color: var(--text-muted);
}

/* ---- 24.11 Reduced motion ---- */

@media (prefers-reduced-motion: reduce) {
  .join-rail__fill { transition: none; }
  .join-body * { scroll-behavior: auto !important; }
}

/* ==========================================================================
   25. BATCH 1 SECTION PRIMITIVES — /about · /evp · /how-it-works
   Added 2026-08-11 to repair execution defects in the three Batch-1 pages.

   ⚠⚠ EVERY SELECTOR IN THIS SECTION IS NEW. Nothing above line 3594 was
   modified, which is deliberate and load-bearing: this stylesheet is shared by
   index.html (the Home — client-approved and byte-identical to commit
   42d04d7), contact/index.html and join/index.html. Adjusting .movement*,
   .control* or .gains* in place would have changed the Home. If you need to
   change behaviour here, add or extend a selector in this section; do not
   reach up into the components above.

   WHY THESE EXIST. The three pages were first built by reusing the closest
   existing component rather than adding CSS. Two of those reuses were wrong
   for the content they carried:
     · .movement is specified for a CENTRED ONE-LINER (DESIGN.md: "centered
       one-liner H2 + body + founder pull-quote") and .movement__inner sets
       text-align:center. Eight sections across the three pages carry two or
       three stacked paragraphs, which silently inherited that centring.
       .movement__h2's 22ch measure also broke full-sentence H2s into three
       display-size lines.
     · .calc__hedge is an italic disclaimer class, and it was carrying the
       opt-in consent lines that the voice rules REQUIRE wherever data-sharing
       is mentioned. A required statement should not read as fine print.

   DIRECTION (Marco, 2026-08-11): the display H2 stays CENTRED — that is the
   gesture the Home established for these bands and it is part of the identity
   — and the prose alone goes left at a 65ch measure, with consent marked by a
   rule rather than by italics.

   Every colour here resolves through the existing theme-aware tokens, so the
   deferred light theme would pick these up correctly if it is ever restored.
   ========================================================================== */

.prose-band { background-color: var(--surface-0); }

.prose-band__inner {
  max-width: var(--container-max);
  margin-inline: auto;
}

/* Same clamp as .movement__h2, so the display scale is unchanged and these
   sections still read as part of one system. Only the measure widens — 22ch to
   34ch — because these H2s are full sentences rather than the manifesto
   one-liner the original measure was cut for. */
.prose-band__h2 {
  font-family: var(--font-editorial);
  /* ⚠ Was clamp(2.25rem, 4vw + 1rem, 3.75rem) = up to 60px, which is the same as
     the hero H1 and 12px LARGER than every other section heading on the same page
     (`.control__h2` inherits the base `h2`, which is --type-h1). Two heading sizes
     for the same rank, and one of them competing with the page title. This is the
     scale's own step for an h2. */
  font-size: var(--type-h1);
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.015em;
  text-align: center;
  text-wrap: balance;
  /* `34ch` resolved against the heading's OWN size, so it grew with it and the
     measure swung from ~600px to ~1160px between sections. Fixed in rem. */
  max-width: 30rem;
  margin-inline: auto;
  margin-block: 0 var(--s-8);
}

/* The prose. `p` already caps at 65ch globally; this restates the measure so
   the block is self-describing, and centres the COLUMN rather than the words.
   text-align:left is explicit because these blocks sit inside centred parents
   in three of the eight sections. */
.prose-band__body {
  font-size: var(--type-body-lg);
  color: var(--text-secondary);
  line-height: 1.65;
  max-width: 65ch;
  margin-inline: auto;
  text-align: left;
  text-wrap: pretty;
}

.prose-band__body + .prose-band__body { margin-top: var(--s-6); }

/* ---- .prose-band__h3 · the document sub-head -------------------------------
   Added 2026-08-24 for /lenders §3 ("What you get"), which is five beats under
   one H2 and had no class for its sub-heads.
   ⚠ THE REASON THIS RULE EXISTS AT ALL: the global `h3` is set to
   `--type-h2` at weight 500 (line 244). Five unclassed h3s inside a section
   would each have rendered at H2 scale, which is the "nothing on the page has
   rank" failure `.prose-band--note` was written to fix on the same page. A
   sub-head must be smaller than the head it sits under.
   🔴 IT CARRIES NO MEASURE AND NO CENTRING, AND THAT IS THE WHOLE POINT —
   THE FIRST VERSION OF THIS RULE, WRITTEN HOURS EARLIER THE SAME DAY, DID.
   It copied `.prose-band__body`'s `max-width: 65ch; margin-inline: auto`. But
   65ch resolves against the element's OWN font-size, so at 20–24px it is wider
   than the body's 65ch at 18px — and two auto-centred blocks of different
   widths do not share a left edge. It shipped ~4px off, in a section built to
   hang off ONE edge. **That is the `ch` trap, for the fourth time on this
   project, in the rule whose own comment warned about it.** The column comes
   from the parent — `.prose-band__inner`'s two-column grid sets
   `margin-inline: 0; max-width: none` on every child, and below 960px the
   container is narrower than either measure anyway. Do not "restore" a measure
   here.
   --------------------------------------------------------------------------- */
.prose-band__h3 {
  font-family: var(--font-body);
  font-size: var(--type-h3);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  margin-block: var(--s-7) var(--s-3);
}
.prose-band__h3 + .prose-band__body { margin-top: 0; }

/* ---- .benefits · /lenders §3, the six-up ----------------------------------
   Added 2026-08-24. Marcos: the section "ficou muito comprida". The six items
   are client-mandated, so the fix is the form: out of the 630px reading column
   and across the container as 3 × 2.

   🔴 WHY IT IS NOT CARDS. This page's register is hairlines and type — no
   fills, no shadow — and /evp already owns the card grid (`.stack`). A second
   one here would read as the same component twice across two pages. The grid is
   held by space and a type step, nothing else.
   🔴 WHY NO PER-ITEM RULE. The 2026-08-21 `.positions` rebuild exists to
   remove "uppercase labels over hairlines" repeated down a column. Six of them
   here would rebuild it in a different section of the same page. The page keeps
   ONE hairline geometry: the spine in §4.

   ⚠ `--benefits-measure` is in `rem`, not `ch`. Each track is ~360px at 1440 and
   the title face is larger than the body face, so a `ch` cap would resolve
   differently on the two and stagger the column edges — the trap this file has
   already recorded four times.
   --------------------------------------------------------------------------- */
/* 🔴 THE ONE SURFACE CHANGE ON THE PAGE, 2026-08-24. Marcos: "os fundos de
   todas as seções ficaram iguais". Everything ran on `--surface-0` and the rhythm
   came only from type and photographs.
   ⚠ IT IS NOT ALTERNATION. Striping odd/even sections ignores the content, and on
   a dark palette the step from #0b0c16 to #13141f is subtle enough to read as
   stripes that mean nothing. **The plate is assigned by meaning:** this is the only
   section that leaves the reading column and takes the full container, and the
   surface makes that structural change legible instead of accidental. It is also
   the section the client mandated as the heart of the page.
   ⚠ `--surface-1` is the approved raised-panel token, not a new colour. */
.benefits-band { background-color: var(--surface-1); }

.benefits__header {
  max-width: 46rem;
  margin-bottom: var(--s-8);
}
/* The base `.prose-band__h2` centres itself in a 30rem box. Here it opens a
   full-width section and hangs off the container's left edge, like `.section--doc`
   does for the same reason. */
.benefits__header .prose-band__h2 {
  text-align: left;
  margin-inline: 0;
  max-width: 34rem;
  margin-block-end: var(--s-5);
}
.benefits__lead {
  font-size: var(--type-body-lg);
  line-height: 1.65;
  color: var(--text-secondary);
  max-width: 44rem;
  margin: 0;
}

.benefits {
  list-style: none;
  /* `list-style: none` does NOT remove the UA `padding-inline-start: 40px`. That
     has shifted a list on this project before. */
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-7);
}

@media (min-width: 680px) {
  .benefits { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
  .benefits {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    /* More between rows than between columns: two rows of three need to read as
       two rows, not as one field of six. */
    column-gap: clamp(var(--s-6), 3vw, var(--s-8));
    row-gap: var(--s-9);
  }
}

.benefits__item {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  max-width: 26rem;
}

.benefits__title {
  font-family: var(--font-body);
  font-size: var(--type-h3);
  font-weight: 600;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  margin: 0;
  /* The title is the scan layer. `text-wrap: balance` stops a two-line title
     leaving one orphaned word over the body. */
  text-wrap: balance;
}

.benefits__body {
  font-size: var(--type-body);
  line-height: 1.6;
  color: var(--text-secondary);
  margin: 0;
  /* The global `p { max-width: 65ch }` would resolve to ~560px here and blow past
     a 360px track before the grid could constrain it in older engines. The track
     is the measure. */
  max-width: none;
  text-wrap: pretty;
}


/* A cross-link or trailing note under a prose block, held on the same column
   so it reads as part of that block rather than as a new one. */
.prose-band__aside {
  /* same ch trap: 16px here against the 18px prose column above it */
  max-width: calc(65ch * 18 / 16);
  margin-inline: auto;
  margin-top: var(--s-6);
  text-align: left;
}

/* ---- .defs · a run of term + definition ON the prose column ----------------
   /evp §6, added 2026-08-19 for EV-20. It was two rows of `.stack__item--mid`,
   which measured IDENTICAL to §2's `--mid` tier — left 120, width 580, padding
   48, font-size 16 on both — so the page's index and its detail were
   indistinguishable by anything except one sentence of intro copy.

   The detail run on that page (§3, §4, §5) is prose. This makes §6 the fourth
   of it instead of a second index, which also leaves §2's `.stack` as the only
   card grid on the page — the uniqueness `plan/06.Design-Direction.md` §6.3
   assigns it as the page's signature.

   ⚠ Elevation is declared NOWHERE here, deliberately. No surface, no border, no
   shadow: the whole point is that this is prose, and a card is what made it
   read as the index. Do not add a container to "group" the four — the shared
   column and the spacing rhythm already do that.
   ============================================================ */
.defs {
  /* The ch trap again, and this is the third component to need the conversion:
     the run inherits the 16px body face while the column above it is set at
     18px, so 65ch here is ~70px narrower than the prose it must line up with.
     `calc(65ch * 18 / 16)` lands on 630.703px, verified by render against
     .prose-band__body, .prose-band__aside, .consent-note and .figure-note —
     all five now compute the same left edge at 1440.
     ⚠ DESIGN.md states this column is 738.1px. That figure is WRONG; the real
     computed value is 630.703px. Do not "fix" an alignment against 738. */
  max-width: calc(65ch * 18 / 16);
  margin-inline: auto;
  /* ⚠ Found by render, not by reasoning: without this the first term collided
     with the framing paragraph above it. `.prose-band__body` carries no bottom
     margin of its own (its `+ .prose-band__body` rule only fires between two
     bodies) and the run had no top margin, so the gap was ZERO while the gap
     between items was 48px — the first heading read as part of the intro.
     64px is deliberately MORE than the 48px between items: this break separates
     two registers, the framing sentence from the run, and every heading in the
     run must keep more space above it than below.
     ⚠ 2026-08-19, spacing audit: was `--s-8` (64px), which made both the h2→intro and
     the intro→run gaps 64 and gave the section no internal hierarchy. `--s-7` (48px) is
     the value every other content-to-content gap on these three pages already uses
     (measured: 12 of 14 such gaps). Still more than the 8px inside each pair. */
  margin-top: var(--s-7);
}

/* Tight inside the pair, generous between pairs — the grouping is proximity,
   not a box. */
.defs__item + .defs__item { margin-top: var(--s-7); }

.defs__term {
  /* Body face at the prose size, not the editorial card title it used to be.
     §3 already names a component inside prose this way ("**Mutual Ownership
     Acquisition Technology** (MoaT) is…"), and `.prose-band__body strong`
     resolves to exactly these two declarations. Same idiom, one system.
     ⚠ Not accented: brandguide §7 "Controlled" permits the accent at one
     fragment per section, and four magenta terms would be four accents
     competing. */
  font-family: var(--font-body);
  font-size: var(--type-body-lg);
  font-weight: 600;
  line-height: 1.65;
  color: var(--text-primary);
  margin: 0 0 var(--s-2);
}

.defs__body {
  font-size: var(--type-body-lg);
  color: var(--text-secondary);
  line-height: 1.65;
  /* The run carries the measure; a second 65ch here would resolve against this
     element's own font-size and re-introduce the indent the wrapper just fixed. */
  max-width: none;
  margin: 0;
  text-wrap: pretty;
}

/* Required opt-in / consent lines. Replaces .calc__hedge's italic-disclaimer
   register. The rule is 1px and untinted — a heavier or coloured bar would be
   the callout cliché, and this is a statement of fact, not an alert. */
/* .figure-note is the same visual role as .consent-note — a required muted caption
   held on the prose column — for a different reason: CL-04's variance statement on
   the worked examples. Sharing the declarations keeps them from drifting apart. */
/* ⚠ .figure-note needs bottom separation of its own: a placeholder used to sit
   between it and the paragraph below, and when that went the two collided. */
.figure-note { margin-bottom: var(--s-6); }

.consent-note,
.figure-note {
  /* â  2026-08-18 â REWRITTEN AGAINST THIS BLOCK'S OWN PREVIOUS DECISION. It used to
     carry a 1px left rule and a 24px indent precisely SO THAT it would not read as an
     italic disclaimer. Andrea's review asks for the opposite on both /about Â§2 and
     /evp Â§4: italicise it, and align it with the paragraph above. Her instruction is
     later, and it is the reviewer's call, so the rule and the indent go.
     The misalignment also had a second, unreported cause: `ch` scales with font-size,
     so 65ch at the 14px caption face is ~65px narrower than 65ch at the 16px body
     face. Centred, that pushed the note's left edge inward before the padding even
     applied. The measure below is the caption-size equivalent of the prose column, so
     the two now start on the same x. */
  /* ⚠ MEASURED, do not eyeball this. The prose column is `--type-body-lg` (18px),
     so its 65ch is 738.1px. This face is 14px, so its own 65ch would be 656.1px —
     centred, that is 39.4px of silent indent. The ratio converts one to the other.
     A first pass used 16/14 and still missed by 39.4px, because the prose is not
     16px. Re-measured in the browser 2026-08-18. */
  max-width: calc(65ch * 18 / 14);
  margin-inline: auto;
  margin-top: var(--s-7);
  font-size: var(--type-caption);
  font-style: italic;
  color: var(--text-muted);
  line-height: 1.55;
  text-align: left;
}

/* .gains__strip is repeat(3, 1fr) from 768px up. /about §6 puts ONE .stat in
   it and /evp §5 puts TWO, so the row left one or two empty columns and read
   as broken rather than as a deliberate partial row. This fits the row to its
   actual contents and centres it. The .stat:not(:last-child)::after divider
   still lands correctly, because the last item is still the last child. */
.gains__strip--fit { justify-items: center; }

@media (min-width: 768px) {
  .gains__strip--fit {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--s-7);
  }
  .gains__strip--fit > .stat { flex: 0 1 clamp(14rem, 22vw, 20rem); }
}

/* Spacing roles, so the three pages stop carrying hardcoded pixel values in
   style attributes. .section-media already owns its own bottom margin. */
.section-media--spaced { margin-top: var(--s-7); }

/* ---- --panorama · /lenders, the band between §4 and §5 ---------------------
   Added 2026-08-24. `.section-media` is 16:9 because every master before this one
   was; this one was commissioned as a BAND and delivered at 2172 × 724, which is
   3:1. Forcing it into 16:9 would `cover`-crop the width, and the width is the
   photograph — a street receding down the frame. The component keeps its ratio
   from the asset instead of the asset losing its subject to the component.
   ⚠ Below 680px a 3:1 strip is ~166px tall and stops being a photograph. It opens
   to 2:1 there, which crops the sides on the axis that has the most redundancy. */
.section-media--panorama { aspect-ratio: 3 / 1; }
@media (max-width: 679.98px) {
  .section-media--panorama { aspect-ratio: 2 / 1; }
}

/* ---- .photo-band · UNUSED as of 2026-08-19, kept for the measurement it records --
   Built for `/about` §2 and dropped the same hour: Marco saw it rendered and called
   it — the overlaid version did not read. §2 is now `.split--reverse`. The component
   is left here because its contrast table is the only place on this project that
   records WHICH images can carry overlaid copy and which cannot, and because the
   `object-position: left` reasoning will be needed again. Delete it if a second
   sweep finds no consumer.
   ---- a section whose copy sits ON the photograph ---------------
   `/about` §2, 2026-08-19 (Marco: "o conteúdo da sessão não pode ir por cima da
   imagem?"). It can, on THIS asset and essentially no other one on the site.

   MEASURED before building, because the identical question failed on the hero image
   an hour earlier. `about-hero.png` against `--text-primary` (#f8f9fc):
     · left third   L= 20.1 → 17.34:1
     · left 45%     L= 19.9 → 17.36:1
     · whole image  L= 44.5 → 13.06:1
     · WORST CASE: the brightest 24px letter-sized patch anywhere inside the left
       45% is L=95.3 → 5.98:1, still over 4.5.
   So NO SCRIM IS APPLIED, deliberately. The asset was rendered on 2026-08-11 with
   its left third in deep shadow specifically to carry overlaid copy; darkening it
   further would be solving a problem the photograph already solved.
   ⚠ `about-data-economy.png` measures 2.24:1 in the same role. Do not reuse this
   component with another image without running the same measurement.

   WHY THE COPY DRIVES THE HEIGHT. At 16:9 in a 1184px container the band is 666px
   tall and this section's copy (H2 + a 51-word AEO paragraph + a second paragraph +
   the consent line) is taller than that. Fixing the ratio would have forced either a
   crop of the asset or a truncation of the copy. Instead the section is an ordinary
   padded block whose height comes from its text, and the photograph is absolutely
   positioned to cover it — so the composition holds at any copy length, in any
   language, at any font size.

   ⚠ `object-position: left` IS THE LOAD-BEARING LINE. As the box gets taller than
   16:9, `cover` has to discard width; left-anchored, it discards it from the LIT
   RIGHT side and the dark copy zone survives every time. Centre or right anchoring
   would eat the shadow and the text would start failing contrast as the viewport
   changed — silently, and only at some sizes.
   ⚠ Also why there is no `aspect-ratio` here: adding one would re-introduce the
   16:9-into-a-narrower-box crop that lost an operator from `hiw-hero` on 2026-08-18.
   ============================================================ */
.photo-band {
  position: relative;
  overflow: hidden;
  /* the photograph is the surface, so the section's own colour must not show */
  background-color: var(--ink);
}
.photo-band__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.photo-band__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: left center;
  display: block;
}
.photo-band__inner { position: relative; z-index: 1; }
/* 45% is the measured safe zone, not a taste call — see the contrast table above.
   Below 960px the photograph cannot hold a column AND stay readable, so the copy
   takes the full width and the image drops to a quiet backdrop. */
.photo-band__copy { max-width: 100%; }
@media (min-width: 960px) {
  .photo-band__copy { max-width: 45%; }
}
/* The prose components inside carry the centred 630.7px measure by default, which
   would push them out of a 45% column. The column IS the measure here. */
.photo-band__copy .prose-band__h2 { text-align: left; margin-inline: 0; max-width: 20ch; }
.photo-band__copy .prose-band__body,
.photo-band__copy .consent-note { max-width: none; margin-inline: 0; }

/* A .section-media that ENDS its section was stacking its own 96px bottom margin on
   top of the section's bottom padding AND the next section's top padding â up to
   ~350px of empty column between an image and the following heading. Andrea reported
   it five separate times across /about and /how-it-works as "there's a large gap
   here". The section padding already owns that separation.
   â  :last-child on purpose. The Home's .section-media is followed by .control__three,
   so the Home is untouched by this and keeps its margin. */
.section-media:last-child { margin-bottom: 0; }


/* ---- Icons ----
   Lucide (ISC), inline SVG, never a CDN script: these pages already pull three
   remote bundles, and an icon that needs JS to appear is wrong on a page whose
   job is being read and quoted.
   ⚠ Phase 6 §1.6 named Lucide from the start. Three hand-copied Lucide glyphs
   were already shipping (theme toggle, nav toggle) with no shared class, so
   stroke and size were maintained by hand. This is that missing primitive.
   Every icon: 24×24 viewBox, fill none, stroke currentColor, width 2, round cap
   and join. Colour is inherited from the text it sits in — never set per icon. */
.icon {
  width: 1.125rem;
  height: 1.125rem;
  flex: none;
  stroke: currentColor;
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.icon--accent { color: var(--text-accent); }
.icon--lg { width: 1.5rem; height: 1.5rem; }

/* MTG-05 (Kyle, 2026-08-14): the MoaT expansion is bolded wherever it appears in
   prose, not just the initials â readers skim "moat" and land on the Berkshire
   economic-moat association, the wrong one. `strong` carried no prose style at all;
   these are the values the two existing uses already set (.voices__partnership
   strong, .join-rail__count strong), so this introduces no new decision. */
.prose-band__body strong,
.gain__body strong { color: var(--text-primary); font-weight: 600; }

/* "40 to 85%" is a SENTENCE used as a display figure, and its wording is the locked
   A1 string â it may NOT be shortened to "40â85%" to make it fit, which is what the
   review proposed. At the 48â80px numeral face it wrapped to two lines inside the
   fitted column on /evp Â§5. This fits the type to the string instead of the other
   way round. */
.stat__number--phrase {
  /* ⚠ 48px against the 80px of the `$30,000` beside it meant the LESS important
     number dominated the client-locked claim — the 2026-08-18 audit caught it.
     60px now, with the column widened to carry it, so the two read as one rank. */
  font-size: clamp(2rem, 4.6vw, 3.75rem);
  white-space: nowrap;
}
@media (min-width: 768px) {
  .gains__strip--fit > .stat:has(.stat__number--phrase) {
    flex: 0 1 clamp(18rem, 28vw, 26rem);
  }
}
/* A second media block sitting tight under the first, so the pair reads as one
   group rather than as two separate beats. Used by /how-it-works §2, where the
   reserved video slot and the mechanics image belong together. */
.section-media--tight { margin-top: var(--s-5); }
.gains__strip--lead { margin-bottom: var(--s-7); }
.gains__strip--follow { margin-top: var(--s-7); }
.section-aside { margin-top: var(--s-7); }

/* A single trust marker standing on its own. /about §6 first used .gains__strip + .stat
   for this. The centring was right, but .stat__number is the 48–80px NUMERAL face, so
   "SOC 2 + PIPEDA" rendered as a masthead broken across two lines — caught only once the
   page was finally screenshotted (2026-08-11). The Home already solves this exact content
   with .trust__lead + .trust__body; both are standalone-safe, so this class supplies only
   the centred wrapper they normally inherit from .proof__tile. */
.trust-note {
  max-width: 46ch;
  margin-inline: auto;
  margin-top: var(--s-7);
  text-align: center;
}

/* ---- .feepath — /merchants signature component (§6.9), 2026-08-20 ----------
   Two routes through one transaction, the second one returning. STATIC: no
   input control, no computed output.

   🔴 It is deliberately NOT the Home's `.calc__`, which is an interactive
   slider producing a single figure, and it is NOT a bento — §6.4 caps bento at
   one per page and this page spends its budget here. Reproducing
   `.proof__mosaic`'s scattered composition is explicitly out.

   Built only from existing tokens: no new colour, type, radius, shadow or
   motion. The one thing encoding meaning is DIRECTION — the return route
   carries the accent border and an accent label, so the two paths are
   distinguishable without reading them.
   --------------------------------------------------------------------------- */
.feepath__routes {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-5);
  margin-top: var(--s-7);
}
@media (min-width: 960px) {
  .feepath__routes { grid-template-columns: 1fr 1fr; gap: var(--s-6); }
}

.feepath__route {
  border: 1px solid var(--border-default);
  border-radius: var(--radius-card);
  background-color: var(--surface-1);
  padding: var(--s-6);
}
.feepath__route--return { border-color: var(--text-accent); }

.feepath__label {
  display: inline-block;
  font-size: var(--type-eyebrow);   /* was 0.8125rem — off-scale */
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: var(--s-5);
}
.feepath__label--accent { color: var(--text-accent); }

.feepath__steps {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: feepath;
}
.feepath__step {
  counter-increment: feepath;
  position: relative;
  padding-left: var(--s-7);
  padding-bottom: var(--s-5);
  color: var(--text-secondary);
  /* ⚠ NOT `65ch`. The ch trap has bitten this project three times — 65ch
     resolves against THIS element's font-size, not the prose column's. These
     steps sit in a half-width card and need no measure cap at all. */
  max-width: none;
}
.feepath__step::before {
  content: counter(feepath);
  position: absolute;
  left: 0;
  top: 0;
  width: var(--s-6);
  height: var(--s-6);
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-default);
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--text-muted);
}
.feepath__route--return .feepath__step::before {
  border-color: var(--text-accent);
  color: var(--text-accent);
}
/* The connector between stations. Omitted on the last step so the route ends
   rather than trailing off. */
.feepath__step:not(.feepath__step--end)::after {
  content: '';
  position: absolute;
  left: calc(var(--s-6) / 2);
  top: var(--s-6);
  bottom: var(--s-2);
  width: 1px;
  background-color: var(--border-default);
}
.feepath__route--return .feepath__step:not(.feepath__step--end)::after {
  background-color: var(--text-accent);
  opacity: 0.4;
}
.feepath__step--end {
  padding-bottom: 0;
  color: var(--text-primary);
  font-weight: 500;
}

.feepath__closer {
  margin-top: var(--s-7);
  max-width: none;
  color: var(--text-primary);
  font-weight: 500;
}

/* ---- .ledger — /pos signature component (§6.9), 2026-08-20 ------------------
   The same mechanism read from both sides of the ledger at once: the platform
   on one side, its merchants on the other, paired row by row.

   🔴 MIRRORED, NOT SEQUENTIAL. `/how-it-works`'s mechanics rail is a NUMBERED
   progression through time. This has no order at all — the rows are pairs, not
   steps, and there is deliberately no counter. If a number ever appears here,
   the component has drifted into the other page's signature and the §6.9
   acceptance test fails.

   Only existing tokens: no new colour, type, radius, shadow or motion. The one
   thing carrying meaning is the SIDE — the merchant column takes the accent, so
   which side of the ledger you are reading is legible before the words are.
   --------------------------------------------------------------------------- */
.ledger__head { display: none; }
@media (min-width: 960px) {
  .ledger__head {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--s-6);
    margin-top: var(--s-7);
    padding-bottom: var(--s-4);
    border-bottom: 1px solid var(--border-default);
  }
}
.ledger__side-label {
  font-size: var(--type-eyebrow);   /* was 0.8125rem — off-scale */
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.ledger__side-label--accent { color: var(--text-accent); }

.ledger__rows { display: grid; gap: var(--s-5); margin-top: var(--s-5); }
@media (max-width: 959px) { .ledger__rows { margin-top: var(--s-7); } }

.ledger__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-3);
  padding-bottom: var(--s-5);
  border-bottom: 1px solid var(--border-subtle);
}
.ledger__row:last-child { border-bottom: 0; padding-bottom: 0; }
@media (min-width: 960px) {
  .ledger__row { grid-template-columns: 1fr 1fr; gap: var(--s-6); align-items: start; }
}

.ledger__cell {
  /* ⚠ NOT `65ch`. The ch trap has bitten this project three times and it resolves
     against THIS element's font-size, not the prose column's. These cells sit in
     a half-width track and need no measure cap. */
  max-width: none;
  margin: 0;
  color: var(--text-secondary);
}
/* On mobile the columns stack, so the side a cell belongs to stops being
   positional and has to be said. The accent rule does that without adding a
   label the desktop layout would then duplicate. */
.ledger__cell--merchant {
  color: var(--text-primary);
  border-left: 2px solid var(--text-accent);
  padding-left: var(--s-4);
}
@media (min-width: 960px) {
  .ledger__cell--merchant { border-left: 0; padding-left: 0; }
}

/* ---- .sheet — /lenders signature component (§6.9), 2026-08-20 -------------
   An asset-class sheet in a term-sheet register. Institutional readers evaluate
   an asset, not a promise, and nothing else on this site ships in a document
   register — that IS the differentiation for the Institutional page type.

   ⚠ Name checked against style.css before use. `.rail` was taken by
   /how-it-works and collided the same day; that is now a standing pre-flight.

   It is a <dl>, so the same UA-default trap as the .rail <ol> applies: the
   browser indents <dd> by 40px on its own. Both the indent and the margin go.
   --------------------------------------------------------------------------- */
.sheet {
  margin: var(--s-7) 0 0;
  padding: 0;
  border-top: 1px solid var(--border-default);
}
.sheet__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-2);
  padding: var(--s-5) 0;
  border-bottom: 1px solid var(--border-subtle);
}
@media (min-width: 720px) {
  .sheet__row { grid-template-columns: 14rem 1fr; gap: var(--s-6); align-items: baseline; }
}
.sheet__term {
  font-size: var(--type-eyebrow);   /* was 0.8125rem — off-scale */
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0;
}
.sheet__value {
  /* ⚠ NOT `65ch` — the ch trap resolves against THIS element, not the prose
     column. The value track is already constrained by the grid. */
  max-width: none;
  margin: 0;
  margin-inline-start: 0;   /* kills the UA's default <dd> indent */
  color: var(--text-secondary);
}

/* ---- .final-cta--plain ----------------------------------------------------
   🔴 The Institutional type (/lenders) has a gradient budget of ZERO (§6.9).
   This strips the band and leaves the section's structure intact. Do not
   "restore" the gradient to match the other pages — matching them is exactly
   what this page must not do.
   --------------------------------------------------------------------------- */
.final-cta--plain {
  background: var(--surface-1);
  border-top: 1px solid var(--border-default);
}

/* ---- Nav: the audience group (dropdown) + the /join secondary action --------
   Added 2026-08-20 with the nav rebuild. The site had NO dropdown component
   before this; the nav stayed at four flat items through Batch 1 because the
   group's destinations did not exist yet.

   🔴 CLICK, NOT HOVER, and that is deliberate. A hover-only menu is unreachable
   by touch and by keyboard, and mixing CSS :hover with a JS `aria-expanded`
   state produces a menu that is visually open while announcing itself closed.
   One state, one owner: the `data-open` attribute, written by script.js.
   --------------------------------------------------------------------------- */
.nav__group { position: relative; display: inline-flex; }

/* The toggle is a <button> wearing .nav__link, so it must be stripped back to
   the anchor's own box or it inherits the UA button chrome. */
.nav__group-toggle {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
}
.nav__group-toggle::before {
  /* The affordance. A caret drawn in text costs no icon and rotates with state. */
  content: '';
  order: 2;
  width: 0;
  height: 0;
  border-inline: 4px solid transparent;
  border-top: 4px solid currentColor;
  transition: transform var(--duration-fast) var(--ease-out-quart);
}
[data-nav-group][data-open="true"] .nav__group-toggle::before { transform: rotate(180deg); }

.nav__submenu {
  display: none;
  position: absolute;
  top: calc(100% + var(--s-4));
  left: 50%;
  transform: translateX(-50%);
  min-width: 15rem;
  padding: var(--s-3);
  background-color: var(--surface-2);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-card-sm);
  box-shadow: var(--shadow-2);
  z-index: 60;
}
[data-nav-group][data-open="true"] .nav__submenu { display: grid; }

.nav__sublink {
  display: block;
  padding: var(--s-3) var(--s-4);
  border-radius: var(--radius-small);
  color: var(--text-secondary);
  text-decoration: none;
  white-space: nowrap;
}
.nav__sublink:hover { color: var(--text-primary); background-color: var(--surface-3); }
/* ⚠ The active state is NOT colour alone. Colour-only "you are here" was a real
   WCAG 1.4.1 failure on this site's nav underline, found 2026-08-18. */
.nav__sublink--active {
  color: var(--text-primary);
  box-shadow: inset 2px 0 0 var(--text-accent);
}

/* /join is a quiet text action beside the CTA, never a button — Marco,
   2026-08-19: the meeting keeps the CTA, Join sits beside it. */
.nav__join {
  color: var(--text-secondary);
  text-decoration: none;
  font-size: var(--type-caption);   /* was var(--type-small, …) — that token does not exist and the fallback was off-scale */
  white-space: nowrap;
}
.nav__join:hover { color: var(--text-primary); }

/* ---- below 960: the group flattens ----------------------------------------
   Inside the open mobile sheet there is no room for a second level and no
   reason for one. The submenu is always visible and the toggle becomes a
   section label. script.js keeps aria-expanded truthful across the breakpoint
   rather than leaving it lying at "false" while the links are on screen. */
/* ⚠ 767, NOT 959. This was written as 959 and was WRONG: `.nav__links` becomes
   inline-flex at 768 and `.nav__toggle` hides at 768, so the horizontal nav
   starts there. At 959 the flattened, full-width group would have been dropped
   into a horizontal row for a 192px band. Caught by rendering at 1100. */
@media (max-width: 959px) {
  .nav__group { display: block; width: 100%; }
  .nav__group-toggle { cursor: default; color: var(--text-muted); }
  .nav__group-toggle::before { display: none; }
  .nav__submenu {
    display: grid;
    position: static;
    transform: none;
    min-width: 0;
    padding: 0 0 0 var(--s-4);
    background: none;
    border: 0;
    box-shadow: none;
  }
  .nav__sublink { padding-inline: 0; }
  .nav__join { display: none; }  /* the sheet already lists it; two entries is noise */
}

/* ---- Nav density between 768 and 1279 -------------------------------------
   Found by rendering at 1100 on 2026-08-20: five multi-word labels ("Embedded
   Value Platform" alone is 22 characters) at a 32px gap wrapped EVERY item onto
   two lines. The nav grew from four items to five plus a group on the same day,
   so this band had never been exercised.
   ⚠ `white-space: nowrap` here is safe in a way it was not on `.btn` — below 768
   the links stack full-width in the mobile sheet, so nowrap cannot force a
   min-content wider than the viewport. That was the 2026-08-18 mobile P0 and it
   is a different situation.
   --------------------------------------------------------------------------- */
.nav__link { white-space: nowrap; }
@media (min-width: 960px) and (max-width: 1279px) {
  .nav__links { gap: var(--s-4); }
  .nav__join { display: none; }
}

/* ---- Three Voices: card fixes + the mobile stack, 2026-08-20 ---------------
   Reported by Marco ("os cards tão com problema"), then reproduced. ⚠ Reproducing
   it needed a real pointer: headless reports `hover: none, pointer: coarse`, so it
   renders the TOUCH branch, where the supporting text stays in flow and the layout
   is a different one. Emulate with
   --blink-settings=primaryHoverType=2,availableHoverTypes=2,primaryPointerType=4,availablePointerTypes=4
   or you are looking at a layout no desktop visitor sees.

   THE CHAIN, measured at 1440 with a fine pointer:
   1 · `align-items: start` let each card size to its own content, so the three
       cards ended at three different heights (card 2 hung ~67px below its siblings).
   2 · The content block is BOTTOM-anchored, so three card heights put the three
       role labels at three heights — 261 / 330 / 305. The 2026-07-27 quote
       height-reservation was added to fix exactly this and could not: it equalises
       the QUOTE, while the ragged CARD is what moves the label.
   3 · Worst consequence: the text block floated up out of the bottom third — and
       the scrim is deliberately bottom-weighted (0.34 at mid-height). "THE MERCHANT"
       landed on the bright shop window and was effectively unreadable.
   One cause, three symptoms. Equalising the cards fixes all three, and the
   reservation stays as belt-and-braces.
   --------------------------------------------------------------------------- */
@media (min-width: 720px) {
  /* ⚠ This overrides the `align-items: start` above, whose stated reason was that a
     hovered card grows while its siblings hold. That trade was the wrong way round:
     it bought hover-stability at the cost of a permanently ragged row. Row growth on
     hover is a deliberate expansion; three mismatched cards is a defect on every
     view. */
  .voices__grid { align-items: stretch; }
  .voices__grid > li { display: flex; }
  .voice-card { flex: 1; }
}

/* ---- below 720: image on top, text below, separated (Marco, 2026-08-20) -----
   The overlay reads as one composed frame on desktop. On a phone the card is
   narrow enough that a 4-line quote over a face wins neither — the portrait is
   cropped to a letterbox and the type still fights the image. Stacked, each does
   its own job.
   ⚠ The scrim is REMOVED here, not faded: with no text over the image there is
   nothing for it to protect, and leaving it would just dim the portrait. */
@media (max-width: 719px) {
  .voice-card {
    min-height: 0;
    display: block;
    overflow: visible;
    background-color: transparent;
    border-radius: 0;
  }
  /* The media layer was absolutely positioned to sit behind the type. Returned to
     the flow as a real block; its <img> is still absolute, so it needs a
     positioned parent and an explicit ratio to have any height at all. */
  .voice-card__media {
    position: relative;
    inset: auto;
    display: block;
    aspect-ratio: 4 / 3;
    border-radius: var(--radius-card);
    overflow: hidden;
  }
  /* The masters are 4:5 portraits and the faces sit in the upper half, so a
     centred 4:3 crop cut the tops of the heads off. Bias the window upward
     rather than changing the ratio — a full 4:5 stack is ~537px per card on a
     430px phone, which is three screens of scroll for three quotes. */
  .voice-card__media img { object-position: 50% 18%; }
  .voice-card__scrim { display: none; }
  .voice-card__content {
    padding: var(--s-5) 0 0;   /* the separation between image and text */
    gap: var(--s-2);
  }
  /* The desktop reservation exists to align labels across a 3-up row. Stacked,
     there is no row to align to and it would only open a gap under each label. */
  .voice-card__line { min-height: 0; }
}

/* The content block must sit at the BOTTOM of the card — that is where the scrim
   protects, and Marco's 2026-07-27 ruling deliberately keeps the TOP of the
   gradient clear so the portraits stay visible. `.voice-card` sets
   `justify-content: flex-end` for this, and it was not surviving; `margin-top:
   auto` on the item itself is the robust form and cannot be defeated by a later
   rule on the container.
   ⚠ Do NOT "fix" a low text block by darkening the top of the scrim. That reverses
   a ruling made on the renders: "a parte de cima do degradê está muito escura,
   deixando os rostos sem visibilidade." */
.voice-card__content { margin-top: auto; }

/* ---- 🔴 THE ACTUAL DEFECT BEHIND THE RAGGED CARDS — found by measurement -----
   2026-08-20. Marco reported the cards looked wrong; equalising their heights was
   necessary and not sufficient. Measured with getBoundingClientRect at 1400 on an
   emulated fine pointer:

     card1 h=461  roleTop=386  supportH=236
     card2 h=461  roleTop=457  supportH=165
     card3 h=461  roleTop=433  supportH=189

   The cards ARE equal now. The ROLE LABELS are 71px apart — because
   `.voice-card__support` NEVER COLLAPSES. It is `opacity: 0`, so it is invisible,
   and it is still occupying 165–236px of the card. Three different support
   lengths give three different reserved heights, which push the bottom-anchored
   content up by three different amounts.

   WHY: `grid-template-rows: 0fr` does not collapse a grid item on its own. The
   row resolves as `minmax(auto, 0fr)`, and `auto` is the item's content height
   unless the item's automatic minimum size is zeroed. The 2026-07-27 fix moved
   `overflow: hidden` OFF the paragraph and onto the container — correctly, because
   on the paragraph it also broke the OPEN state — but nothing replaced the zeroing
   for the CLOSED state. So the reveal has had nothing to reveal since: the text
   was always there, just transparent.

   `min-height: 0` on the item is the missing half of the idiom. It zeroes the
   automatic minimum without clipping, so 0fr closes and 1fr still opens.
   --------------------------------------------------------------------------- */
@media (hover: hover) and (pointer: fine) {
  .voice-card__support > * { min-height: 0; }
}

/* ---- .calc__range-low — the floor of the recovery range, 2026-08-20 --------
   Marco's call after the first attempt: both ends at display size wrapped to two
   lines and read as two numbers instead of one range. The floor drops to a small
   line above, carrying the connecting word, and the top end keeps the display
   weight so the output still has a single focal figure.
   ⚠ It is a NUMBER, not a label — no uppercase, no letter-spacing. Digits get
   worse, not better, from eyebrow treatment.
   --------------------------------------------------------------------------- */
.calc__range-low {
  display: block;
  font-size: var(--type-caption);
  font-weight: 500;
  color: var(--text-muted);
  margin-bottom: var(--s-1);
}

/* ---- .positions — /lenders, added 2026-08-20 ------------------------------
   A map of POSITIONS, not of steps. An institution evaluating this asset does
   not ask what happens first; it asks who holds the receivable, who carries the
   risk, and who keeps the relationship. The page answered that in prose only.

   🔴 IT CARRIES NO NUMBERS, AND THAT IS THE WHOLE DIFFERENTIATION.
   /how-it-works owns the numbered mechanics rail (`.rail`). A numbered flow here
   would be that component again, on a page whose type exists to not resemble the
   others — and it would answer the wrong question besides.

   Institutional register: hairlines and type, no cards, no fills, no shadow.
   Tokens only. The accent appears exactly once, on the node that holds the
   asset, because that is the fact the reader came for.
   --------------------------------------------------------------------------- */
.positions { margin-top: var(--s-8); }

/* ↩ REBUILT 2026-08-21 · A SPINE, NOT A TABLE.
   The first version was a 4-column grid, each node carrying its OWN
   `border-left`, under a `border-top` on the row. Rendered, that is four
   uppercase labels over hairlines sitting directly above `.sheet` — six MORE
   uppercase labels over hairlines. Twelve rules in one column, and the map read
   as the sheet's header rather than as a separate idea. Marco named this
   component first when he called the page bad.
   🔴 The fix is that the two components now use DIFFERENT GEOMETRY, not
   different spacing. The sheet is a stack of rows. This is ONE continuous line
   with a marker per position. A reader cannot confuse a line with a table.
   ⚠ §6.9 gives this page a signature budget of 1 and `.sheet` holds it. This is
   deliberately subordinate — smaller type, no rules of its own, no surface. It
   is the section's diagram, not a second signature. */
.positions__row {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-6);
}

/* ---- The spine, wide: one rule across the top, markers sitting ON it -------- */
@media (min-width: 860px) {
  .positions__row {
    /* ↩ WAS `repeat(4, 1fr)` AND THERE ARE THREE NODES. The customer node was
       removed on 2026-08-21 and the track count was not, so the spine ran the
       full width while the last node stopped at 75% — a quarter of the
       component was empty rule. Found by render 2026-08-24. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s-7);
    /* The single continuous hairline. It is on the ROW, never on the node —
       that is the whole difference from the version this replaces. */
    border-top: 1px solid var(--border-default);
    padding-top: var(--s-6);
  }
  .positions__node { padding-left: 0; border-left: 0; }
  /* The marker straddles the spine. 9px on a 1px rule, so it reads as a station
     on the line rather than as a bullet beside it. */
  .positions__node::before {
    content: '';
    position: absolute;
    top: calc(-1 * var(--s-6) - 6px);
    left: 0;
    width: 11px;
    height: 11px;
    border-radius: 50%;
    border: 1px solid var(--border-default);
    background: var(--surface-0);
  }
  /* The accent appears ONCE on this page, on the node that holds the asset. It
     was already carrying it in type; the marker now carries it too, so the
     station reads before the label does. */
  .positions__node--lender::before {
    border-color: var(--text-accent);
    background: var(--text-accent);
  }
}

.positions__node {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

/* ---- The spine, narrow: the same line, turned vertical ---------------------
   Below 860 the four positions stack, so the spine runs down the left instead of
   across the top. ⚠ It is still ONE rule — drawn on the row, not per node — so
   the geometry argument above holds at every width. */
@media (max-width: 859.98px) {
  .positions__row {
    border-left: 1px solid var(--border-default);
    padding-left: var(--s-5);
  }
  .positions__node::before {
    content: '';
    position: absolute;
    left: calc(-1 * var(--s-5) - 5px);
    top: 0.45em;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    border: 1px solid var(--border-default);
    background: var(--surface-0);
  }
}

/* 🔴 THE ONE FILLED MARKER AND THE ONE ACCENT ON THE WHOLE COMPONENT. The
   §6.9 acceptance test is "crop the nav — which audience is this addressed to?"
   This is the page's answer: the only emphasis on the diagram is on the party
   holding the asset, which is the reader. */
/* ⚠ The unfilled markers were drawn at `--border-default` and rendered close to
   invisible against `--surface-0` — a spine with two of its four stations missing
   is worse than no spine. `--text-muted` is the readable floor here; the
   hierarchy between the three states now comes from FILL, not from opacity. */
.positions__node::before { border-color: var(--text-muted); }
.positions__node--lender::before {
  background: var(--text-accent);
  border-color: var(--text-accent);
}
.positions__node--credx::before {
  border-color: var(--text-secondary);
  background: var(--text-secondary);
}

/* ↩ PROMOTED 2026-08-24. Marcos: "podemos valorizar melhor cada item, dar um
   destaque maior para eles".
   🔴 THE SUBORDINATION THIS COMPONENT CARRIED WAS INHERITED FROM A COMPONENT
   THAT NO LONGER EXISTS. The rules above still say "deliberately subordinate —
   smaller type … it is the section's diagram, not a second signature", because
   `.sheet` held the page's single signature budget. **`.sheet` was retired from
   this page on 2026-08-21 and `.positions` BECAME the signature.** The type was
   never re-weighted to match, so the page's thesis — who holds what — was
   rendering one step below prose in caption grey. Type steps up; the geometry,
   the hairline register and the single accent do not change. */
.positions__who {
  font-size: var(--type-body);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
.positions__node--lender .positions__who { color: var(--text-accent); }

.positions__holds {
  /* ⚠ No `ch` cap. It resolves against THIS element's font-size, not the prose
     column's — the trap that has bitten this project three times. The grid track
     is the constraint here. */
  max-width: none;
  /* ↩ Was `--type-caption`, one step below prose, to sit under `.sheet`. The
     sheet is gone; this is the signature now and reads at body size. */
  font-size: var(--type-body);
  line-height: 1.6;
  color: var(--text-secondary);
}
.positions__node--lender .positions__holds { color: var(--text-primary); }

/* The return line. ⚠ NO top border any more — a rule here re-introduced exactly
   the horizontal-hairline stack this rebuild exists to remove. Space separates it. */
.positions__return {
  margin: var(--s-6) 0 0;
  max-width: 65ch;
  color: var(--text-muted);
  font-size: var(--type-caption);
  font-style: italic;
}


/* ===========================================================================
   /lenders · THE INSTITUTIONAL TYPE — the 2026-08-21 layout pass
   Marco judged the page bad on 2026-08-20 and named four things plus one defect
   on 2026-08-21: the positions map, the density, the missing photograph, the
   hierarchy, and a strange break in the H1. Everything below answers one of
   those, and every claim in these comments was measured in Chrome via Playwright
   at 1440 and at 390 before it was written.

   🔴 THE ROOT DIAGNOSIS, and it is worth recording because it will recur on the
   next restrained page: THE PAGE WAS NOT TOO DENSE, IT WAS TOO EVEN. Measured
   section heights were 844 · 1214 · 387 · 439 · 334 · 381. One section carried
   60% of the body and the next three were the same object three times — a
   centred display H2 over one paragraph. §6.9's "restrained" was built as
   "uniform", and those are not the same instruction. A document register has
   many austere forms: a header, a schedule, a diagram, a clause. It does not
   have one form repeated.
   ⚠ Nothing here touches a token, adds a colour, or introduces a card. §6.9
   closes §1's tokens and this pass stays inside them.
   =========================================================================== */

/* ---- .hero--ledger and .hero__plate: REMOVED 2026-08-21 -------------------
   Both were built earlier the same day for a composition that no longer exists:
   an asymmetric "document header" opening with the photograph as a small plate
   inside the summary column. Marcos then asked for the standard split hero
   ("deixar a imagem sozinha a esquerda, como as outras paginas"), so the whole
   block lost its only consumer within hours.
   ⚠ DELETED RATHER THAN LEFT INERT, which departs from how this file usually
   treats retired components (`.photo-band` is kept for the measurement it
   records). The difference: these rules carried long comments asserting they
   FIXED the H1 wrap. Dead CSS that claims to fix a live defect is worse than no
   CSS — the next reader finds the note, believes it was handled, and stops
   looking. That is exactly how the H1 broke a second time. The measurements
   worth keeping moved to `.hero--institutional` at the end of this file.
   --------------------------------------------------------------------------- */

/* ---- .media-ph--band · the 16:9 IMAGE slot the component was missing -------
   ⏸ NO CONSUMER as of 2026-08-21 — `/lenders` §4 now carries the real photograph
   and uses `.section-media`. KEPT, because the base component still ships only a
   `--video` ratio variant, and the next narrow image slot will need this one plus
   the `min-height` fix below. Delete it if a later sweep finds no consumer.

   `--video` was the only ratio variant, and it relabels the slot "Video slot".
   §4 needs the same 16:9 box with the image label, so this is the missing sibling
   rather than a page-local one-off.

   🔴 `min-height: 0` IS LOAD-BEARING AND IT IS THE THIRD CASCADE TRAP IN THIS
   COMPONENT. `.media-ph` sets `min-height: 300px`; `.media-ph--video` sets it to
   0 to cancel that — but `--video` is written EARLIER in the file at equal
   specificity (0,1,0), so it LOSES and the 300px stands. Combined with
   `aspect-ratio`, that resolved the slot to 300 x 16/9 = 533px wide inside a 358px
   column, and Chrome EXPANDED THE MOBILE LAYOUT VIEWPORT from 390 to 549 — the
   whole page, nav included, laid out at the wrong width. Measured, not inferred.
   ⚠ The file already documents this exact trap one property up, on
   `.media-ph::after`, with the note "second time this cascade trap bit today".
   This is the third, and the cause is that `--video` sits above `.media-ph`
   instead of below it. NOT reordered here: the /how-it-works video slot depends on
   the current order, and moving it to fix a page in Batch 2 is how the one live
   consumer breaks. `--band` is declared AFTER `.media-ph`, which is where a
   modifier belongs.
   --------------------------------------------------------------------------- */
.media-ph--band {
  aspect-ratio: 16 / 9;
  min-height: 0;
  margin-top: var(--s-8);
}

/* ---- .section--doc · one left edge, for the section that is a document -----
   🔴 §2 carried THREE left edges at 1440, measured: the H2 centred, the prose
   column centred at x=176 (65ch inside an 1184px container), and `.positions`
   plus `.sheet` spanning the container at x=128. A term sheet does not have
   three margins, and the 48px step between the prose and the sheet was visible
   as a silent indent.
   ⚠ Only §2 takes this. §3 and §4 stay centred on purpose — the alternation
   between a left-hung document section and centred reading sections IS the
   page's rhythm. Applying it everywhere would trade three alignments for one and
   land back on uniformity.
   --------------------------------------------------------------------------- */
.section--doc .prose-band__h2 {
  text-align: left;
  margin-inline: 0;
  /* Wider than the centred 30rem: a left-hung heading has the full column to
     run into and a 30rem cap would break it into a narrow stack. */
  max-width: 34rem;
  margin-block-end: var(--s-7);
}
.section--doc .prose-band__body {
  margin-inline: 0;
}
/* The generous interval on this page. Prose, then the diagram, then the
   document — the two gaps that matter are the ones that separate the three, and
   they are wider than any gap inside them. That contrast is what "air" is; the
   page had one repeated interval and no contrast at all. */
.section--doc .sheet {
  margin-top: var(--s-9);
  /* The sheet's opening rule is the heaviest on the page, because it is the one
     place the reader needs told "a document starts here". `.positions` no longer
     has a rule of its own to compete with it. */
  border-top-color: var(--border-default);
}

/* ---- .prose-band--note · the clause scale ---------------------------------
   §3, §4 and §5 were one object three times — a centred display H2 over a single
   paragraph, at 387 · 439 · 334px. Three sections of equal weight is why nothing
   on the page had rank. §5 now closes at clause scale: an institutional document
   ends on a compliance paragraph in a quieter register, not on a third headline
   the size of the first two.
   ⚠ THE HEADING STAYS AN `h2`. Only the type scale moves. The document order is
   h1 -> h2 x5 -> h3, no skips — the state the 2026-08-20 a11y pass put eight
   pages into. Do not "correct" this to an h3.
   --------------------------------------------------------------------------- */
.prose-band--note {
  padding-block: var(--s-9);
}
.prose-band--note .prose-band__h2 {
  font-size: var(--type-h3);
  font-family: var(--font-body);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  max-width: none;
  margin-block-end: var(--s-6);
}
/* ⚠ The accent `<em>` inside the heading is muted WITH the heading. At this size
   the magenta italic read as a link, and an uppercase label is not the place the
   page spends its accent — the lender marker in the spine is. */
.prose-band--note .prose-band__h2 .accent {
  color: inherit;
  font-style: normal;
}
.prose-band--note .prose-band__body {
  font-size: var(--type-body);
}


/* ===========================================================================
   .prose-band -> THE EDITORIAL TWO-COLUMN READING SECTION  ·  2026-08-21
   Marcos: "as prosas ficam com uma margem muito grande sem motivo aparente,
   inclusive levando a quebra de titulos h2 maior do que o necessario."

   MEASURED AT 1440 BEFORE CHANGING ANYTHING, because both halves of that are
   arithmetic, not taste:
   1. THE HEADING. `.prose-band__h2` capped at `max-width: 30rem` = 480px at a
      48px type size. `/merchants` #data is 68 characters, so it set in FOUR
      LINES inside a 480px box that was CENTRED IN AN 1184px CONTAINER -- 704px
      of the row empty while the headline stacked. Headings of only 33-35
      characters were going to two lines for the same reason.
      This was a fix that overshot: DESIGN.md records the measure moving from
      `34ch` to `30rem` because `34ch` resolved against the heading's own size
      and swung between ~600 and ~1160px. The rem fixed the swing and then sat
      too narrow to set a sentence.
   2. THE PROSE. `.prose-band__body` measured 738px wide at `left: 351` inside
      the same 1184px container. That is 351px of empty space to the LEFT of a
      LEFT-ALIGNED paragraph, with nothing in the layout accounting for it.
      A centred column of left-aligned text has no left edge to explain itself.

   THE FIX IS THE SECOND HALF OF HIS OWN SUGGESTION -- "algum elemento do lado
   esquerdo para sustentar o alinhamento do texto a esquerda". The heading moves
   into a column of its own on the left and the prose sits in the column beside
   it, so the prose's left edge is STRUCTURAL rather than residual.

   WHY NOT "tudo centralizado", the other option he offered: it fixes the
   heading and it does remove the unexplained margin, but centring the BODY text
   leaves a ragged left edge, and a ragged left edge breaks the return sweep at
   the end of every line. Several of these paragraphs run three and four lines.
   Recommended against and not built.

   SCOPE IS SMALLER THAN IT LOOKS -- 4 pages, 12 sections. `/about` and
   `/how-it-works` do NOT use `.prose-band__inner`: their headings live inside
   `.split__body`, already left-aligned against a photograph. Those two pages
   are the precedent for this direction, not casualties of it. Verified by
   querying every `.prose-band__inner` on all six pages.
   EVERY RULE HERE IS SCOPED `.prose-band__inner > ...` ON PURPOSE, so the
   `.split__body .prose-band__h2` overrides those two pages depend on are
   untouched.
   The Home does not use `.prose-band` at all, so the "home nao mexer" hold is
   not in the way. Checked, not assumed.
   =========================================================================== */

@media (min-width: 960px) {
  /* `:has()` guard: a section with no heading has no left column to justify, so
     it stays full width instead of being pushed into column two with a void
     beside it. The project already relies on `:has()` for `.section-media`. */
  .prose-band__inner:has(> .prose-band__h2, > .prose-band__rail) {
    display: grid;
    /* The right column is wider. `minmax(0, ...)` on both, because a bare `fr`
       floors at min-content and one long unbroken word in a heading would then
       widen the whole track. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.12fr);
    column-gap: clamp(var(--s-7), 4vw, var(--s-8));
    align-items: start;
    /* The reading column supplies its own inter-paragraph spacing through
       `.prose-band__body + .prose-band__body`, so the grid must not add a second
       one on top of it. */
    row-gap: 0;
    grid-auto-rows: min-content;
  }

  /* Everything that is not the heading goes to the reading column. This is a
     catch-all BY DESIGN: these twelve sections carry nine different child types
     between them (`__body`, `__aside`, `.figure-note`, `.gains__strip`,
     `.defs`, `.sub-link`, `.geo`, `.positions`, `.sheet`), and a whitelist
     would silently drop the next one into column one. */
  .prose-band__inner:has(> .prose-band__h2, > .prose-band__rail) > * {
    grid-column: 2;
    margin-inline: 0;
    max-width: none;
  }

  .prose-band__inner > .prose-band__h2 {
    grid-column: 1;
    /* 🔴 SPANS THE ROWS, and this is not cosmetic. With `grid-row: 1` the
       heading and the FIRST paragraph shared row one, so the row grew to the
       taller of the two — and on `/merchants` #data the heading is three lines
       against a five-line paragraph, which opened a visible dead gap between
       the first paragraph and the second. Spanning decouples the two columns'
       heights so the reading column stacks tightly regardless of how the
       heading breaks. The span is generous on purpose; surplus implicit rows
       have zero height. */
    grid-row: 1 / span 30;
    text-align: left;
    margin-inline: 0;
    /* The track is the measure now. The 30rem cap is what caused the four-line
       break, so it must not be reinstated in any form here. */
    max-width: none;
    /* `balance` is right for a heading that now has a real column: it evens the
       lines instead of leaving one word stranded. */
    text-wrap: balance;
  }

  /* ↩ A 1px accent rule sat above every heading here for one push. REMOVED
     2026-08-21 (Marcos: "esses tracos em cima dos titulos estao desnecessarios
     eu acho"). He is right on the argument that matters: THE RULE WAS REDUNDANT.
     What anchors the prose's left edge is the grid column, not an ornament — a
     heading in column one with the reading column beside it already accounts for
     the space, which is the whole fix. The rule was belt-and-braces, and it
     repeated four times down `/merchants`.
     ⚠ It also ran close to a ban this project's design skill states outright:
     no kicker or eyebrow above a heading, "not a default", no brief earns it
     back. A textless rule is not literally an eyebrow, but it is decoration in
     the same slot doing the same job.
     Do not reintroduce it. If the left edge ever stops reading as intentional,
     the column is what needs adjusting. */

  /* The prose keeps its own comfortable measure INSIDE the column. The column is
     ~560px at 1440, which is about 49ch at 18px -- inside the 45-75ch band, so
     the track governs and this is a ceiling for very wide viewports only. */
  .prose-band__inner:has(> .prose-band__h2, > .prose-band__rail) > .prose-band__body { max-width: 65ch; }
}

/* `.section--doc` OPTS OUT and stays a single left-hung column. `/lenders` #2
   carries the `.positions` spine and the `.sheet`, and a term sheet squeezed
   into 55% of the row is unreadable -- the value track would collapse. It was
   already left-aligned, so it loses nothing, and the contrast between a
   full-width document section and the two-column reading sections is now doing
   useful work on that page. */
@media (min-width: 960px) {
  .section--doc .prose-band__inner { display: block; }
  .section--doc .prose-band__inner > .prose-band__h2 { grid-column: auto; }

/* The rail inherits the bare heading's placement verbatim — column 1, spanning rows so
   the reading column stacks tightly regardless of how tall the rail gets. Added 2026-08-25. */
/* ⚠ A section with a rail places its heading ACROSS BOTH COLUMNS on row 1, then puts the rail
   and the reading column side by side on row 2 — so the two start on the same line. This is the
   opposite of the bare-heading case above, where the heading owns column 1 and spans the rows.
   Both are correct; they are different compositions. Added 2026-08-25. */
.prose-band__inner:has(> .prose-band__rail) > .prose-band__h2 {
  grid-column: 1 / -1;
  grid-row: 1;
  text-align: left;
  margin-inline: 0;
  /* Wide but not the full 1184px: a heading with no column to run into needs its own measure,
     and this is the same cap `:has(> .parts)` uses for the identical situation. */
  max-width: 40rem;
  margin-block-end: var(--s-8);
}

.prose-band__inner > .prose-band__rail {
  grid-column: 1;
  /* Starts on row 2, under the full-width heading, and spans so the reading column beside it
     stacks on its own rhythm however tall the frame gets. */
  grid-row: 2 / span 30;
  margin-inline: 0;
  max-width: none;
}
  .section--doc .prose-band__body { max-width: 65ch; }
}

/* Below 960 everything stacks in one column and the heading stays LEFT-aligned
   rather than reverting to centre -- a centred heading over left-aligned prose
   on a 358px phone column is the same unexplained-edge problem at small scale. */
@media (max-width: 959.98px) {
  .prose-band__inner > .prose-band__h2 {
    text-align: left;
    margin-inline: 0;
    max-width: none;
  }
}


/* ---- .geo · the province run on /lenders §4 --------------------------------
   The six regions were the tail of §4's paragraph. Pulled out so the section has
   a form of its own once the image left for the hero — §3 and §5 are already
   "heading plus one paragraph", and three of those in a row is the evenness the
   2026-08-21 layout pass exists to remove.
   ⚠ SHIPPED BRIEFLY WITH NO CSS AT ALL and rendered as a default UA bulleted
   list, complete with the 40px `padding-inline-start` that `list-style: none`
   does NOT remove — the exact trap DESIGN.md records for `.prose-list` and the
   `.rail` <ol>. Both the marker and the indent are killed explicitly below.
   Register: a schedule, not a bulleted list. Hairline-separated rows, the same
   language as `.sheet` one section up, at caption scale so it stays subordinate
   to the prose it sits under.
   --------------------------------------------------------------------------- */
.geo {
  list-style: none;
  margin: var(--s-6) 0 0;
  padding: 0;
  padding-inline-start: 0;   /* the UA indent; list-style:none does not touch it */
  border-top: 1px solid var(--border-subtle);
}
.geo__item {
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--border-subtle);
  font-size: var(--type-caption);
  color: var(--text-secondary);
}

/* Two columns as soon as the reading column can hold them — six single-word rows
   stacked vertically is a long thin ladder that out-measures the paragraph above
   it. ⚠ Not a grid on the <ul>: `columns` keeps the hairlines continuous per
   item, which a grid would break into a chequerboard. */
@media (min-width: 720px) {
  .geo {
    columns: 2;
    column-gap: var(--s-7);
  }
  /* Chrome breaks a bordered item across the column boundary without this. */
  .geo__item { break-inside: avoid; }
}







/* ===========================================================================
   TWO SECTIONS ON /evp WERE CONVERTED TO THE TWO-COLUMN READING LAYOUT AND
   SHOULD NOT HAVE BEEN  ·  2026-08-21
   Marcos: "evp ficou estranha, Why the saving holds. esse layout aqui nao
   funciona" / "para The parts that make the rest work. mesma coisa".

   THE CAUSE IS THE MIRROR OF THE ONE FIXED EARLIER THE SAME DAY. Three sections
   wore `.control` (component-led, centred) with NO component in them, and were
   reclassed to reading sections. These two are the opposite error: reading
   sections that DO carry a full-width component, converted anyway. A section
   with a real figure in it is not a reading section.

   MEASURED, #cost: `.gains__strip` landed in the 595px reading column and went
   476px TALL, because `--fit` sizes each `.stat` at `clamp(14rem, 22vw, 20rem)`
   = 317px at 1440, so two of them plus the gap could not sit side by side and
   wrapped into a tower. A one-line heading ("Why the saving holds.", 53px tall)
   then sat opposite a 756px stack. That is the "estranha".

   I FLAGGED THIS EXACT RISK WHILE BUILDING THE TWO-COLUMN CHANGE AND WROTE
   "let me measure it after and only special-case if it breaks" -- and then did
   not go back and measure it. The note is worth more than the fix: a deferred
   measurement is an unverified claim, and it shipped as one.
   =========================================================================== */

@media (min-width: 960px) {
  /* A full-width component in the section is the opt-out signal, not a page or a
     section id -- so the next section that grows a figure inherits the right
     behaviour instead of the next person rediscovering this. */
  .prose-band__inner:has(> .gains__strip),
  .prose-band__inner:has(> .parts) {
    display: block;
  }
  .prose-band__inner:has(> .gains__strip) > .prose-band__h2,
  .prose-band__inner:has(> .parts) > .prose-band__h2 {
    grid-column: auto;
    grid-row: auto;
    /* Still left-hung, still a wide measure. The two-column fix is not being
       reverted here -- only the column split. The unexplained centred margin
       does not come back. */
    text-align: left;
    margin-inline: 0;
    max-width: 40rem;
  }
  /* The prose keeps a reading measure now that it has the full row. */
  .prose-band__inner:has(> .gains__strip) > .prose-band__body,
  .prose-band__inner:has(> .parts) > .prose-band__body,
  .prose-band__inner:has(> .gains__strip) > .prose-band__aside,
  .prose-band__inner:has(> .gains__strip) > .figure-note {
    margin-inline: 0;
    max-width: 65ch;
  }
}

/* ---- .parts · /evp §6, the four components that need more than a line ------
   Marcos asked for cards here on 2026-08-21 and called it an exception. It is.
   `.defs` existed because this section WAS a second `.stack` until 2026-08-19
   and was removed on a measurement: as `.stack__item--mid` it came out at
   left 120 / width 580 / padding 48 / font-size 16, IDENTICAL to §2's mid tier,
   so the page's index and its detail were indistinguishable. That is EV-20.

   🔴 SO THESE CARDS ARE BUILT TO MEASURE DIFFERENTLY ON PURPOSE. `.stack__item`
   is a FILLED tile: `--surface-1`, 1px border all round, `--radius-card`, 48px
   padding, in UNEQUAL tiers of 2+2+3 with photographs on two of them. `.parts`
   is OPEN: no fill, no radius, no box, one hairline per cell, in a FLAT 2×2 of
   four equals. Squinted at, §2 reads as a bento and §6 as a quadrant.
   🔴 DO NOT ADD A FILL, A RADIUS, AN IMAGE, OR SIZE-RANK. Any one of them walks
   this straight back into EV-20, which took two attempts to close.
   ⚠ No icons either — a grid of icon-plus-heading-plus-text cards is the
   category default this project's design floor refuses outright, and these four
   are distinguished by what they DO, which is text.
   ⚠ Name pre-flighted against style.css before use: `.parts`, `.parts__item`,
   `.parts__term` and `.parts__body` were all unused. `.rail` collided this way
   once and it is now a standing check.
   --------------------------------------------------------------------------- */
.parts {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-7);
  margin-top: var(--s-8);
}

@media (min-width: 720px) {
  .parts {
    /* Flat 2×2. Not 2+2+3, which is §2's silhouette, and not 1×4, which would
       squeeze a 150-character body into a ~278px column ten lines deep. */
    grid-template-columns: repeat(2, 1fr);
    column-gap: var(--s-9);
    row-gap: var(--s-8);
  }
}

.parts__item {
  /* The hairline IS the card here. Elevation is declared once, and it is
     declared as a rule rather than as a box. */
  border-top: 1px solid var(--border-default);
  padding-top: var(--s-5);
}

.parts__term {
  font-family: var(--font-body);
  font-size: var(--type-h3);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  margin: 0 0 var(--s-3);
}

.parts__body {
  /* ⚠ No `ch` cap. It would resolve against THIS element's size, not the prose
     column's — the trap that has bitten this project four times. The grid track
     is the constraint. */
  max-width: none;
  margin: 0;
  font-size: var(--type-body);
  line-height: 1.65;
  color: var(--text-secondary);
  text-wrap: pretty;
}


/* ---- The stats figure is CENTRED, and the left-aligned version was wrong ----
   \u21a9 REVERTED 2026-08-21, same day it was introduced. I left-aligned this strip
   on the reasoning that the whole page now hangs off one left edge and a centred
   figure was the one thing breaking the column. Marcos, seeing it rendered:
   "ainda esta muito ruim, as coisas estao alinhadas para esquerda, muito
   estranho" \u2014 then, specifically, that the caption under the numeral and the
   `Results vary...` note both wanted centring.

   \U0001F534 HE IS RIGHT AND THE REASONING WAS THE MISTAKE. The unexplained-margin
   problem is a PROSE problem: it comes from a left-aligned paragraph floating in
   a centred column, where the reader's eye returns to an edge that nothing
   defines. A STATS FIGURE IS NOT READ THAT WAY \u2014 it is read as one composed
   object, so an axis of symmetry is what holds it together and flushing it left
   makes it look like a row that failed to fill.
   \u26a0 It was also visibly broken, not just debatable: `.stat` keeps
   `text-align: center` on its label, so left-aligning the row put the numeral at
   x=128 with its own caption indented to x=209. A figure misaligned against its
   own number.
   \u26a0 DO NOT "FIX" THIS BACK TO LEFT to match the reading sections. The rule that
   came out of it: a figure centres on its own axis; prose hangs off the column.
   --------------------------------------------------------------------------- */


/* ---- `.figure-note` under a figure centres with it ------------------------
   "Results vary by volume and card mix." belongs to the stats strip above it, not
   to the paragraph below it, and Marcos asked for it centred with the figure.
   \u26a0 SCOPED to a `.figure-note` that directly follows a `.gains__strip`. The same
   class is also used as a trailing note on prose blocks, where left is correct
   and centring it would be the mistake in the other direction.
   \u26a0 `margin-inline: auto` as well as `text-align`: the element carries the `ch`
   conversion measure, so without it the centred text would sit inside a column
   that is still flush left. Both are needed; one alone looks like a bug.
   --------------------------------------------------------------------------- */
/* ⚠ Specificity, measured not assumed: the section opt-out sets
   `margin-inline: 0` at `.prose-band__inner:has(> .gains__strip) > .figure-note`
   = (0,3,0), so a (0,2,0) selector lost and the note centred its TEXT inside a
   box that was still flush left — 574px wide starting at x=128. Half-applied
   centring reads as a bug, which is the failure mode the comment above predicted
   one round earlier. Matched at (0,3,0) and placed later in the file. */
.prose-band__inner .gains__strip + .figure-note {
  text-align: center;
  margin-inline: auto;
}

/* ---- A section that ENDS in a full-width component needs more air below ----
   Marcos: "o espaco entre a sessao e a cta ficou muito proxima" on /evp #platform.
   \u26a0 MEASURED FIRST, AND THE VALUE WAS NOT THE PROBLEM: every page carries the
   same 64px section padding plus the CTA band's own 88px, and /evp measured
   identically to /merchants, /pos, /about and /how-it-works. So this is not a
   drifted number.
   \U0001F534 IT IS A PERCEIVED-WEIGHT PROBLEM AND IT IS REAL. A section ending in prose
   ends on a ragged part-line, and 64px reads as air. A section ending in a
   full-width component \u2014 the `.parts` 2x2, or the stats strip \u2014 ends on a
   straight edge across the whole container, which butts into the band. Same
   measurement, different terminus.
   \u26a0 Precedent exists rather than being invented here: `/lenders` \u00a75 already
   overrides its own block padding to 96px.
   --------------------------------------------------------------------------- */
.section:has(> .prose-band__inner > .parts),
.section:has(> .prose-band__inner > .gains__strip) {
  padding-bottom: var(--s-9);
}

/* ---- .hero__frame--wide · a 16:9 master in a split hero --------------------
   `.hero__frame` is 4:5 on desktop and 16/10 below 960, sized for the 1122×1402
   portrait masters /merchants and /pos deliver. `/lenders` opens on a 16:9
   street elevation, and cropping that to 4:5 reduced a block of shops to two
   shopfronts — deleting the one thing its brief insists on, that the BLOCK is
   the subject rather than any single business.
   ⚠ The ratio follows the ASSET, not the page. If a 4:5 master is ever delivered
   for this slot, drop the modifier rather than re-cropping the frame.
   --------------------------------------------------------------------------- */
.hero__frame--wide { aspect-ratio: 16 / 9; }


/* ---- .hero--institutional · the /lenders headline, and the REAL cause -------
   Marcos, twice: "A Merchant-Linked Asset Class for Community Lenders quebrou de
   novo". It broke, was fixed, and broke again — because the first fix lived on a
   shell (`.hero--ledger`) that was replaced hours later, and because the actual
   cause was never the one being treated.

   🔴 THE CAUSE IS `max-width` IN `ch`, WHICH MAKES FONT-SIZE A DEAD LEVER.
   Measured at 72 / 64 / 60 / 56px: FIVE LINES EVERY TIME, breaking at the same
   words, with line widths shrinking proportionally. A `ch` measure resolves
   against the element's OWN font-size, so shrinking the type shrinks the measure
   by the same ratio and the text-per-line never changes. Two earlier rounds
   reached for font-size and `text-wrap`; both were inert for this reason.
   ⚠ THIS IS THE `ch` TRAP DESIGN.md ALREADY DOCUMENTS, in its third form — it
   has bitten `.prose-band__body`, `.consent-note`, `.prose-band__h2` (fixed by
   moving to rem) and now the hero H1. A `ch` measure on an element whose
   font-size is fluid is not a measure, it is a ratio.
   ✅ RULED OUT WITH EVIDENCE, not assumed: `data-reveal-words` is innocent. It
   never runs on this H1 — zero `.reveal-word-mask` nodes and the DOM is untouched
   after load — and the wrap is byte-identical with the attribute stripped before
   any script executes. That was the first hypothesis twice, and wrong twice.

   Measured with the cap released, in the real 544px column:
     72px -> 5 lines  ·  64px -> 4  ·  60px -> 4  ·  56px -> 3
   The clamp below reads 56px from 1280 up and sets THREE lines with a
   485 / 364 / 513 rag at 1280, 1440 and 1920.
   ⚠ Smaller than `--type-display` (72px) on purpose: this is the longest H1 on
   the site at 51 characters, and a quieter display size is the Institutional
   type's own argument now that it shares its opening shell with /merchants and
   /pos.
   ⚠ SCOPED to this page. The other two split heroes have shorter headlines that
   set correctly at the shared size; changing the measure globally would alter two
   pages nobody complained about.
   --------------------------------------------------------------------------- */
.hero--institutional .hero__h1 {
  /* The column is the measure. Nothing here may be expressed in `ch`. */
  max-width: none;
  font-size: clamp(2.5rem, 4.4vw, 3.5rem);
}

/* ============================================================
   .prose-band__rail — an OPTIONAL left rail for a prose-band section
   ------------------------------------------------------------
   Added 2026-08-25. `.prose-band__inner` puts the h2 in column 1 and everything
   else in column 2, and the h2 spans 30 rows so the two columns' heights stay
   decoupled. That works while the left column holds nothing but the heading.
   `.prose-band__rail` is the wrapper for when it holds more: it takes exactly the
   placement the bare h2 had, so anything inside it stacks under the heading in
   column 1 while the reading column keeps its own rhythm.
   ⚠ The h2 inside the rail is no longer a direct child of `__inner`, so the grid
   override that left-aligns it does not match. The rail re-applies it — do not
   remove those three declarations thinking they are duplicates.
   ============================================================ */
/* ⚠ A rule for a heading INSIDE the rail was removed 2026-08-25 when §5's heading moved out
   to span both columns. It is not "missing" — nothing consumes it, and an orphaned rule is the
   liability `.photo-band` and `.join-foot` already document. Re-add it if a rail ever carries
   its own heading again. */

/* ============================================================
   .tally — the framed pair of figures on /evp §5
   ------------------------------------------------------------
   🔴 WHY THIS EXISTS, because "it looked bad" is not a spec and the cause was
   structural. §5 carried a `.gains__strip`, and `.prose-band__inner:has(> .gains__strip)`
   sets `display: block` — a deliberate opt-out so a full-width figure is not crushed
   into one grid column. The side effect was that **§5 was the only detail section on
   the page with no two-column spine**: §3, §4 and §6 hang their heading left against a
   reading column, §5 fell back to a left-hung heading over a 65ch column centred in a
   1184px container, with a centred stat strip and a centred figure-note between them.
   **Four alignment systems in one section.** Marcos, 2026-08-25: "essa sessão está bem
   feia, podemos emoldurar ela e trabalhar de forma diferente aqui."
   THE FIX IS THE FRAME PLUS THE SPINE. The figures become a bordered panel narrow
   enough to live in the rail, so the section rejoins the grid instead of opting out.
   ⚠ WHY THE RAIL AND NOT FULL WIDTH: §3, §4 and §6 all leave the left rail empty below
   the heading. §5 is the only section on the page carrying numbers, so it is the only
   one with anything to put there — that is what differentiates it, and it is earned by
   content rather than applied as decoration.
   ⚠ BOTH FIGURES ARE THE SAME SIZE ON PURPOSE. The 2026-08-18 audit caught the previous
   version rendering the locked `40 to 85%` phrase at 48px beside `$30,000` at 80px, so
   the less important number dominated the client-locked claim. One rank, one size.
   🔒 `white-space: nowrap` on the figure is load-bearing: the string is client-locked
   and must not break across lines. Every size below is chosen so it fits the rail —
   re-measure if the copy changes.
   ============================================================ */
.tally {
  border: 1px solid var(--border-default);
  border-radius: var(--radius-card-sm);
  background-color: var(--surface-1);
  padding: var(--s-6);
  /* 🔴 NO TOP MARGIN, AND THAT IS THE WHOLE ALIGNMENT FIX. The frame's top edge has to meet
     the first line of prose beside it; any gap here reintroduces the step Marcos flagged. The
     space above the frame is the heading's `margin-block-end`, which the prose column shares. */
  margin-top: 0;
}

.tally__row + .tally__row {
  margin-top: var(--s-6);
  padding-top: var(--s-6);
  border-top: 1px solid var(--border-subtle);
}

.tally__figure {
  display: block;
  font-family: var(--font-editorial);
  /* Tuned to the rail, not to the container: the left track is ~531px at 1440 and the
     panel spends 64px of it on padding. The locked phrase is the constraint, not the
     dollar figure. */
  font-size: clamp(1.75rem, 3.1vw, 2.75rem);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--text-primary);
  font-variation-settings: 'opsz' 96;
  font-variant-numeric: lining-nums tabular-nums;
  white-space: nowrap;
}

.tally__label {
  display: block;
  margin-top: var(--s-4);
  font-size: var(--type-body);
  line-height: 1.4;
  color: var(--text-secondary);
  text-wrap: pretty;
}

/* The variance line belongs to the figures, not to the section. Outside the frame it
   floated centred in dead space attached to nothing. */
.tally__note {
  margin-top: var(--s-6);
  padding-top: var(--s-5);
  border-top: 1px solid var(--border-subtle);
  font-size: var(--type-caption);
  font-style: italic;
  line-height: 1.4;
  color: var(--text-muted);
}

@media (max-width: 959.98px) {
  /* One column below the grid breakpoint: the rail stops being a rail, so the frame
     should not keep a rail's width or a rail's top gap — and it needs its own gap
     underneath, because in the single-column stack the prose follows it directly
     instead of sitting beside it. */
  /* Single column: the frame follows the heading directly instead of sitting beside the prose,
     so the zero top margin that aligns the two columns at desktop is wrong here. */
  .tally { max-width: 32rem; }
  .prose-band__rail { margin-bottom: var(--s-7); }
}


/* ============================================================
   .datasheet — the case-study data table. NEW 2026-08-27.
   ⚠ Name pre-flighted against style.css and every page HTML before use:
   `.datasheet`, `.datasheet__*` were all unused. `.rail` collided this way
   once and it is now a standing check. ⚠ NOT `.sheet` — that is /lenders'
   two-column term/value list and cannot hold four columns.

   🔴 WHY A REAL <table> AND NOT A GRID OF CARDS. A case study's per-club
   breakdown is tabular data: the reader compares DOWN a column (which club
   costs most) and ACROSS a row (what one club produces per stream). A card
   grid supports neither comparison, and the source deck proves it — it
   repeats the same four-card row three times, once per stream, and the
   reader has to hold twelve cards in their head to make one comparison.
   One table says it once. Semantics come free: header association, row
   scope, and a caption a screen reader announces before the data.

   ⚠ THE WRAPPER SCROLLS, THE PAGE DOES NOT. Four numeric columns do not fit
   360px and must not be allowed to widen the document — that is a horizontal
   body scroll on every page of the site, not just this one. `min-width` on
   the table plus `overflow-x` on the wrapper keeps the overflow local.
   ============================================================ */
.datasheet {
  margin-top: var(--s-8);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-card-sm);
  background-color: var(--surface-1);
  overflow-x: auto;
  /* The scroll container is focusable so a keyboard user can reach the
     overflow. Without this the columns past the fold are unreachable without
     a pointer — the defect that makes most responsive tables inaccessible. */
  -webkit-overflow-scrolling: touch;
}

.datasheet table {
  width: 100%;
  /* Below this the numeric columns collide; the wrapper scrolls instead. */
  min-width: 40rem;
  border-collapse: collapse;
  font-variant-numeric: lining-nums tabular-nums;
}

/* The caption is the table's title and stays visible: a data table that
   arrives without saying what it counts is a wall of numbers. */
.datasheet caption {
  caption-side: top;
  text-align: left;
  padding: var(--s-6) var(--s-6) 0;
  font-family: var(--font-body);
  font-size: var(--type-h3);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--text-primary);
}

.datasheet th,
.datasheet td {
  padding: var(--s-5) var(--s-6);
  text-align: left;
  vertical-align: top;
}

.datasheet thead th {
  font-size: var(--type-caption);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-secondary);
  border-bottom: 1px solid var(--border-default);
  white-space: nowrap;
}

/* Numerals right-align so digits stack by place value; the row label does not. */
.datasheet thead th:not(:first-child),
.datasheet td:not(:first-child) { text-align: right; }

.datasheet tbody tr + tr th,
.datasheet tbody tr + tr td { border-top: 1px solid var(--border-subtle); }

.datasheet tbody th {
  font-family: var(--font-body);
  font-size: var(--type-body);
  font-weight: 600;
  line-height: 1.3;
  color: var(--text-primary);
}

.datasheet tbody td {
  font-size: var(--type-body);
  line-height: 1.4;
  color: var(--text-primary);
  white-space: nowrap;
}

/* The qualifier under a row label — the POS stack, the venue type. Caption
   scale so it stays subordinate to the name it describes. */
.datasheet__note {
  display: block;
  margin-top: var(--s-2);
  font-family: var(--font-body);
  font-size: var(--type-caption);
  font-weight: 400;
  font-style: italic;
  line-height: 1.35;
  color: var(--text-muted);
  white-space: normal;
  text-wrap: pretty;
}

/* The total row is the one line most readers came for, so it is the one line
   that gets a surface. The rule above it is the heaviest in the table. */
.datasheet tfoot th,
.datasheet tfoot td {
  border-top: 1px solid var(--border-default);
  background-color: var(--surface-2);
  font-size: var(--type-body-lg);
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
}
.datasheet tfoot th { font-family: var(--font-body); text-align: left; }
.datasheet tfoot tr:first-child th,
.datasheet tfoot tr:first-child td { border-top-width: 1px; }

/* ---- .datasheet--bars — the chart and the table are the same object -------
   🔴 THE SOURCE DECK DRAWS THIS AS A GROUPED BAR CHART WITH A Y-AXIS AND A
   LEGEND. Twelve bars, three colours, an axis to read heights against and a
   legend to decode the colours — four lookups to answer "what does this club
   save at 65%". Printing the value next to its own bar answers it in zero,
   and it retires the axis and the legend together.
   ⚠ THE BAR IS DECORATION, THE NUMBER IS THE DATA. Every value is written out
   in text, so the bar carries no information of its own and needs to meet no
   contrast threshold — which is what makes a three-tone ramp safe here.
   The ramp encodes adoption: lavender 40% → purple 65% → magenta 85%.
   --------------------------------------------------------------------------- */
.datasheet--bars td { white-space: nowrap; }

/* 🔴 A FIXED TRACK, NOT A PERCENTAGE OF THE CELL — and the first version got
   this wrong in a way only a render caught. With `width: var(--v)` as a
   percentage, `--v: 100%` filled the entire cell content box and butted the
   longest bar straight into the previous column's value: "$95K▬▬▬▬$125K", no
   gap, on the one row that mattered most. It also meant every column had its
   own silent scale, because cell widths differ. `--v` is now UNITLESS (0-1)
   against a 7rem track, so all twelve bars share one origin and one scale. */
.datasheet__bar {
  display: grid;
  grid-template-columns: 7rem auto;
  align-items: center;
  justify-content: end;
  gap: var(--s-4);
}

.datasheet__bar::before {
  content: '';
  display: block;
  width: calc(var(--v, 0) * 7rem);
  /* A zero-value bar would vanish and read as missing data rather than as a
     small number, so the track always shows at least a dot. */
  min-width: 3px;
  height: 8px;
  border-radius: var(--radius-pill);
  background-color: var(--bar-tone, var(--soft-lavender));
}

.datasheet--bars td:nth-of-type(1) { --bar-tone: var(--soft-lavender); }
.datasheet--bars td:nth-of-type(2) { --bar-tone: var(--purple); }
.datasheet--bars td:nth-of-type(3) { --bar-tone: var(--magenta-bright); }

@media (max-width: 719.98px) {
  .datasheet th,
  .datasheet td { padding: var(--s-4) var(--s-5); }
  .datasheet caption { padding: var(--s-5) var(--s-5) 0; }
  /* The wrapper is already scrolling by this width; a shorter track buys back
     enough of it that two columns stay on screen at once. */
  .datasheet__bar { grid-template-columns: 4.5rem auto; gap: var(--s-3); }
  .datasheet__bar::before { width: calc(var(--v, 0) * 4.5rem); }
}


/* ============================================================
   BLOG IMAGERY — `.vertical-card__media` + `.prose-figure`. NEW 2026-08-27.
   ⚠ Names pre-flighted against style.css and every page HTML: both unused,
   and `<figure>` did not appear anywhere on this site before now.

   🔴 THE STANDARD, AND THE ONE DECISION THAT MATTERS: THE IMAGE IS OPTIONAL.
   Marcos, 2026-08-27 — "pode ser que eles queiram adicionar alguma postagem
   com imagem". MAY want. Three of the four seed posts do not exist yet and
   their topics are owed, so the common production state is a post with NO
   featured image. A blog template whose layout only holds when an image is
   present is broken by the first post without one. Every slot below reads
   correctly filled AND empty, and the index proves both states side by side.

   THE THREE SLOTS A CMS BLOG NEEDS, AND NO MORE:
   1 · Featured image — ONE field, TWO renderings: the card in the index and
       the top of the post. That is the whole reason it is one field.
   2 · In-body figures — repeatable, captioned, inside the rich-text field.
   3 · The no-image default — not an absence, a designed state. Without it a
       CMS grid where one post in four has a picture reads as broken.

   ⚠ THE FEATURED IMAGE IS NOT A HERO. It sits BELOW the title, standfirst
   and meta row, so the reader scrolls INTO it rather than landing on it —
   `/about`'s `.hero-media` reasoning, same asset, lower rank. A full-bleed
   photo behind the title would make this the ninth page opening on the
   composition DESIGN.md's anti-references name outright: "nine pages with
   one opening". The post keeps `.hero--compact` and gains a picture under it.
   ⚠ The post's featured image needs NO new component — `.section-media` is
   already a 16:9 framed block and `.section-media img` already covers.
   ============================================================ */

/* ---- The card thumbnail ---------------------------------------------------
   Full-bleed to the card edge, not inset: a thumbnail with a margin inside a
   bordered card is two frames around one picture. The `:has()` guard scopes
   the clip to cards that actually carry media, so the root hub's five
   vertical cards are untouched. */
.vertical-card:has(> .vertical-card__media) {
  overflow: hidden;
  padding-top: 0;
}

.vertical-card__media {
  /* Cancels the card's own inline padding so the picture meets the border. */
  margin: 0 calc(var(--s-6) * -1) var(--s-3);
  aspect-ratio: 16 / 9;
  background-color: var(--surface-2);
  /* 🔴 THE NO-IMAGE DEFAULT LIVES HERE, ON THE CONTAINER, so it needs no
     markup of its own: a card with no img element inside still paints. The mark
     sits at 6% — present enough to read as a chosen surface, quiet enough
     that four of them in a grid do not become a wall of logos. NOT
     `--gradient-1`: that token is spent once per page on `.final-cta`, and a
     grid of gradient tiles would break the one rule that keeps it an event. */
  background-image: url(assets/logo/credx-shape.svg);
  background-repeat: no-repeat;
  background-position: center;
  background-size: 22%;
  position: relative;
  overflow: hidden;
}

/* The img element, when there is one, covers the default rather than replacing
   the rules — so a broken or slow image degrades to the designed state
   instead of to a white box. */
.vertical-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The lift on hover belongs to the card; the picture must not also scale, or
   two things move at two rates for one pointer gesture. */
a.vertical-card:hover .vertical-card__media { opacity: 0.92; }

@media (prefers-reduced-motion: reduce) {
  a.vertical-card:hover .vertical-card__media { opacity: 1; }
}

/* ---- The in-body figure ---------------------------------------------------
   A real `<figure>` + `<figcaption>`, which this site had no instance of
   before. ⚠ IT SITS ON THE PROSE COLUMN, NOT FULL WIDTH. A blog post is one
   reading column; a picture that breaks out of it interrupts the argument it
   is meant to support. `.section-media` inside supplies the frame.
   ⚠ THE CAPTION TAKES THE ch CONVERSION. It is 14px inside an 18px column, so
   a plain `65ch` would resolve ~107px narrow and indent silently against the
   prose above it — the trap that has bitten this project four times. This is
   the same declaration `.figure-note` and `.consent-note` carry. */
.prose-figure {
  margin: var(--s-8) 0;
  /* 🔴 THE ch CONVERSION, AND THE FIRST VERSION SHIPPED WITHOUT IT. The figure
     inherits the 16px body face while the column it belongs to is set at 18px,
     so it took the container's full 1184px and left a 630px caption sitting
     under a 1184px picture, left-aligned — which reads as a mistake, not as a
     full-bleed choice. Caught by render, not by reading; the comment above
     already CLAIMED the prose column while the CSS did not deliver it. Both
     this and `__caption` now resolve to 630.703px, same as `.prose-band__body`,
     `.figure-note`, `.consent-note`, `.defs` and `.triad`. */
  max-width: calc(65ch * 18 / 16);
}

.prose-figure .section-media {
  margin-bottom: var(--s-4);
}

.prose-figure__caption {
  max-width: calc(65ch * 18 / 14);
  font-size: var(--type-caption);
  font-style: italic;
  line-height: 1.5;
  color: var(--text-muted);
  text-wrap: pretty;
}


/* ============================================================
   READ MODE — one reading column, centred. NEW 2026-08-27.
   Marcos, having seen the five template screens live: "prosas de forma geral
   deveriam nesses casos se comportar como blogs mesmo, com o texto conteúdo
   todo alinhado ao centro… verificar o tamanho ideal da margem para esse tipo
   de aplicação, e padronizar tudo."

   🔴 THERE WAS A REAL BUG UNDERNEATH THE COMPLAINT, AND IT EXPLAINS TWO OF HIS
   FIVE ITEMS AT ONCE. `.prose-band__inner > .prose-band__h2` sets
   `max-width: none` inside the 960px media query (line ~5804). It has the same
   specificity as `.section--doc .prose-band__h2 { max-width: 34rem }` and comes
   LATER, so it wins — every heading on a document-mode section runs to the full
   container while its own prose stops at 630.7px. MEASURED off the render: the
   H2 "What changes if the credit is funded differently?" spans 1017px against
   paragraphs at 735px. That is his "passa da margem do texto" on the blog post
   and his "muitas quebras nos títulos" on the term page — one cause, four pages.

   🔴 `--measure-read` IS IN rem, AND THAT IS THE POINT. Every other measure on
   this site is `65ch`, which resolves against the ELEMENT's font-size — the trap
   that has now bitten this project five times (`.prose-list`, `.triad`,
   `.consent-note`, `.figure-note`, `.prose-figure`) and needs a
   `calc(65ch * 18 / 14)` conversion at every new consumer. A rem measure cannot
   drift: a 48px heading and a 14px caption in this column get the SAME edge,
   with no conversion and nothing to get wrong. The value is set below and now tracks
   `.faq__list`, so one page carries one width rather than two.

   ⚠ SCOPE: the five template screens only. `/evp` and `/lenders` keep
   `.section--doc` — they are shipped, client-reviewed pages, and "padronizar
   tudo" was said about the templates in front of him. Site-wide is a separate
   call.
   ⚠ THE COLUMN IS CENTRED; THE TEXT INSIDE IT IS NOT. That is what a blog does,
   and centre-aligning body prose would destroy the return sweep. Only the hero
   centres its text.
   ============================================================ */
:root {
  /* ↩ 42rem (672px) UNTIL 2026-08-27. Marcos, reading the blog post: "o FAQ
     tem tamanho diferente do resto do conteudo, acho ate a largura do faq e boa
     para usarmos em tudo, em vez de reduzir o faq, aumentar o resto."
     `.faq__list` is `max-width: 880px` (style.css:2751) and has been since the
     pillar pages, so the page now lands on ONE width instead of two.
     ⚠ THE TRADE-OFF IS REAL AND IS HIS CALL, NOT A SILENT ONE: 880px at the
     18px prose face is about 95 characters per line, above the 45-75 comfort
     band. If the lines ever read long, the fix is to raise the prose face to
     20px — which returns the same column to about 85 — NOT to narrow it back
     and re-introduce the two widths he asked to remove. */
  --measure-read: 55rem;
}

/* Beats `.prose-band__inner:has(> .prose-band__h2, > .prose-band__rail)` on
   source order at equal specificity — this block must stay after it. */
.section--read .prose-band__inner {
  display: block;
  /* 🔴 THE PADDING COMPENSATION IS NOT OPTIONAL. This element is `class="container
     prose-band__inner"` — one element, both roles — and `.container` spends
     `--container-pad` (48px at 1440) on each side. A bare `max-width:
     var(--measure-read)` therefore yields 576px of TEXT, not 672: the measure
     silently shrinks by 96px, and nothing in the render says so. Measured before
     this line existed: the column read at 432-1004px. */
  max-width: calc(var(--measure-read) + var(--container-pad) * 2);
  margin-inline: auto;
}

/* One edge for everything in the column. The catch-all is deliberate, for the
   same reason the grid's is: these sections carry headings, paragraphs, figures,
   captions, definition runs and notes, and a whitelist would silently drop the
   next component out of alignment. */
.section--read .prose-band__inner > *,
.section--read .defs,
.section--read .prose-figure,
.section--read .prose-figure__caption,
.section--read .figure-note,
.section--read .consent-note {
  max-width: none;
  margin-inline: 0;
}

/* 🔴 THE PROSE WAS 630px WHILE EVERYTHING AROUND IT WAS 880, AND THE CAUSE IS
   SPECIFICITY, NOT A MISSING RULE. `.prose-band__inner:has(> .prose-band__h2) >
   .prose-band__body { max-width: 65ch }` (in the 960px block above) counts THREE
   classes; the catch-all just above counts two, so the catch-all loses and the
   body stays at 65ch = 630.703px. Every other block on the page — hero, the
   related-terms run, the FAQ cards — sits at 880.
   ⚠ Marcos saw it before any measurement did, twice: "o conteúdo de texto está
   menor que o restante da página… é pra usar o mesmo tamanho do card de FAQ e
   Related Terms e Hero." I read it as a request to change the page width and
   then as a request to change the FAQ card. It was neither: the prose was the
   only thing out of line, and the fix is to stop it being capped.
   ⚠ Matching the winning selector's shape is what makes this hold. A
   `.section--read .prose-band__body` would be two classes and would lose again. */
.section--read .prose-band__inner > .prose-band__body,
.section--read .prose-band__inner > .prose-band__aside {
  max-width: none;
}

.section--read .prose-band__h2 {
  text-align: left;
  /* A heading with a real column to run into wants its lines evened, not a
     ragged stack. Same reasoning as the grid variant. */
  text-wrap: balance;
  margin-block-end: var(--s-6);
}

.section--read .prose-band__h3 {
  text-align: left;
  margin-inline: 0;
}

/* ---- .hero--read - the hero on the same axis as the column ----------------
   "alinhar hero com o conteúdo, pode ser tudo ao centro, assim quebrada está
   muito estranha." ⚠ THE DEFECT WAS NOT THE ALIGNMENT, IT WAS THE MEASURE.
   `.hero--compact` sets a narrow per-element measure so the headline gets a
   ragged column — correct on `/about`, whose H1 is four short words. On the term
   page it broke "Mutual Ownership Acquisition Technology (MoaT)" into FIVE lines
   in a rail while the content below sat wider. Same column, centred, three.
   --------------------------------------------------------------------------- */
/* 🔴 `display: block` IS THE LOAD-BEARING LINE, AND DESIGN.md ALREADY WARNED
   ABOUT IT. `.hero__inner` is a grid with no declared columns, so its implicit
   track is auto-sized to its CONTENT and anchored at the start — the note under
   `.hero--centred` says this outright, and it was measured three times there
   before it was right. The consequence here was worse than a centring bug: with
   the track sized to a short H1, the standfirst wrapped to the HEADLINE's width.
   "The Blog" collapsed the whole hero to ~230px and broke a two-line paragraph
   into five. Block layout is what centres this, and what lets each child take
   the measure. */
/* ↩ 2026-08-27, SECOND PASS: THE HERO IS LEFT-ALIGNED, NOT CENTRED.
   The first version centred it on Marcos's "pode ser tudo ao centro", and on the
   glossary index — H1 "The Glossary", two short symmetric lines — it read fine.
   On the term page it did not, and he called it: "o problema está em margens
   diferentes ao longo da página."
   🔴 MEASURED, and the count is the argument: every block below the hero sits at
   x=280 (279-282 is glyph variation). The hero, being centred, starts its five
   lines at 655, 387, 510, 284 and 604. FIVE left edges in the first block of the
   page, then one edge for the remaining forty.
   ⚠ AND THE STANDFIRST IS WHAT MAKES IT WORST: it measures 870px inside an 880px
   column, so centred it fills the line and only the last line is short — it reads
   as left-aligned with a mistake at the end rather than as a centred block.
   ✅ `.hero--feature` is deliberately NOT changed: on the blog post the copy sits
   on a photograph with no column beside it to align to, and centred is the
   convention there. That is one exception, on the one hero that has a picture
   behind it. */
.hero--read .hero__inner {
  display: block;
  max-width: calc(var(--measure-read) + var(--container-pad) * 2);
  margin-inline: auto;
  text-align: left;
}

.hero--read .hero__copy {
  max-width: var(--measure-read);
  margin-inline: 0;
  text-align: left;
}

.hero--read .hero__h1,
.hero--read .hero__sub,
.hero--read .eyebrow,
.hero--read .figure-note {
  max-width: none;
  margin-inline: 0;
  text-align: left;
}

.hero--read .hero__h1 { text-wrap: balance; }
.hero--read .hero__sub { text-wrap: pretty; }

/* ---- The hero-to-content interval - ONE gap, not two ----------------------
   Marcos, on the glossary: "o espaçamento entre o hero e os tópicos ficou muito
   grande não?" MEASURED, because the eye can be wrong about vertical space and
   this one was not: 241px between the hero and the first term, against 59-62px
   between the term groups on the same page. FOUR TIMES the page's own rhythm.
   ⚠ THE CAUSE IS A STACK, NOT A VALUE. `.hero` closes with `--s-section` and
   `.section` opens with `--s-section`, so the boundary gets two full intervals
   where the reader needs one. It is not new and it is not limited to the
   glossary — MoaT measured 188px and the blog post 201px — it simply became
   visible once the heroes got shorter and centred, and once the glossary's
   first element turned out to be a 12px eyebrow letter with nothing above it.
   ✅ 48 + 48 = 96px, about 1.6x the 59px internal rhythm: enough to say the hero
   has ended, not enough to read as a hole. `.hero--feature` keeps `--s-9` below
   the date because that padding is part of the PHOTOGRAPH's proportion, not
   just space — only the section under it gives ground.
   --------------------------------------------------------------------------- */
.hero--read { padding-block-end: var(--s-7); }

.hero--read + .section,
.hero--feature + .section {
  padding-block-start: var(--s-7);
}

/* 🔴 PADDING WAS ONLY 96 OF THE 241, AND TRIMMING IT ALONE MOVED THE GAP TO 209.
   The other 113px are two trailing/leading margins nobody sees in isolation:
   `.hero__sub` ships `margin-bottom: var(--s-7)` and the first `.defs` in a
   section ships `margin-top: var(--s-7)` — both correct in their own component,
   both dead space at a boundary that already has padding on each side. This is
   why the gap had to be measured twice: the obvious cause was real and was less
   than half of it. */
.hero--read .hero__copy > :last-child,
.hero--feature .hero__copy > :last-child {
  margin-bottom: 0;
}

.hero--read + .section > .container > :first-child,
.hero--read + .section .prose-band__inner > :first-child,
.hero--feature + .section .prose-band__inner > :first-child {
  margin-top: 0;
}

/* ---- .hero--feature - copy ON the photograph ------------------------------
   Marcos: "imagem ao fundo, overlay de cor, título e support text e data.. em
   cima da imagem."

   🔴 A SCRIM IS MANDATORY HERE AND THE PROJECT ALREADY LEARNED WHY. `.photo-band`
   carries the only contrast record on this project: overlaid copy was built for
   `/about` §2, rendered, and rejected the same hour, and it works on exactly one
   asset — one deliberately rendered with its left third in shadow. The comment
   there notes `about-data-economy.png` measures 2.24:1 in the same role. A blog
   featured image is arbitrary CMS content, so it can be trusted even less.

   ✅ THE SCRIM IS SIZED BY ARITHMETIC, NOT BY EYE. Worst case is white pixels
   under the title. At 0.80 navy (#323665) over #ffffff the composite is
   rgb(91, 94, 132), which measures 5.8:1 against `--text-primary` (#f8f9fc) —
   over the 4.5 threshold with margin, on the brightest image anyone could ever
   upload. Every real photograph does better. Do not lighten these stops without
   redoing that arithmetic.
   ⚠ It is a NAVY scrim, not black: Marcos asked for "overlay de cor", and navy
   is the brand's own dark. It is NOT `--gradient-1`, which is spent once per
   page on `.final-cta`.
   ⚠ The last stop is FULL ink, not 0.96. At 0.96 the band ended on a visible
   seam against the page below it; at 1 the photograph dissolves into the page
   and the hero has no bottom edge at all. It reaches full ink at 92%, not 100%:
   landing on it at the last pixel still left a visible transition.
   --------------------------------------------------------------------------- */
/* ---- .figure-note--start - a caption under a full-width figure ------------
   `.figure-note` centres itself on the prose measure, which is right under a
   figure inside a reading column and wrong under something that spans the
   container. On the case study the three `.datasheet` tables run full width at
   x=128 while their captions were centring at x=352.
   ↩ THIS SELECTOR WAS ADDED FOR THE BLOG INDEX, THEN DELETED THE SAME HOUR when
   its only consumer was removed. It is back because the case study gives it
   three, which is the difference between a utility and a leftover. */
.figure-note--start { margin-inline: 0; }

/* ---- .section--wide - the case study runs at the container, not the column -
   Marcos, on the case study: "as tabelas e a hero seguem uma margem, enquanto o
   corpo do texto/prosa segue outra, não pode ficar assim, pq quebra a leitura…
   essa página vamos fazer full width, 1184px."
   🔴 IT IS A PAGE-LEVEL DECISION AND IT ONLY MAKES SENSE ON THIS PAGE TYPE. A
   case study is evidence-led: three full-width tables and a bar chart, with
   prose between them. At 880 the reader's left edge moved between 280 and 128
   five times on the way down. Everything at 1184 gives one edge for the whole
   document — the nav, the footer, the hero, the tables and the prose.
   ⚠ THE COST IS REAL AND IS COMPENSATED, NOT IGNORED: 1184px at the 18px prose
   face is about 128 characters per line, well past the 45-75 comfort band. The
   line-height goes from 1.6 to 1.75 because on a long measure the return sweep
   is what fails first — the eye loses which line it came from — and leading is
   the lever that helps it, not size.
   ⚠ DO NOT take this to the term pages or the blog post. Those are argument, not
   evidence: they have no full-width figure to align to, so a wide measure there
   would buy nothing and cost the same 128 characters. */
.section--read.section--wide .prose-band__inner,
.section--read.section--wide > .container {
  max-width: var(--container-max);
}

.section--read.section--wide .prose-band__body,
.section--read.section--wide .defs__body,
.section--read.section--wide .gains__benchmark {
  line-height: 1.75;
}

/* `.gains__benchmark` carries `max-width: 64ch` and `margin-inline: auto` — the
   right shape for a proof line centred under a stat strip on the Home, and the
   wrong one here: released from its centring it sat left at 128 but only ~620px
   wide, a third measure on a page that now has one. Marcos: "deixa o texto The
   shift is a decision… full width tb." ⚠ Specificity has to beat the base rule,
   hence both modifiers in the selector. */
.section--read.section--wide .gains__benchmark {
  max-width: none;
}

/* 🔴 THE RAIL NEEDS ITS OWN GAP ONCE THE SECTION IS A BLOCK. `.prose-band__rail`
   carries `margin-bottom` only below 960px, because above it the rail sits in a
   COLUMN beside the prose and a bottom margin would do nothing. `.section--read`
   makes the section a block at every width, so the prose now follows the panel
   directly — measured, the first paragraph started on the panel's bottom edge
   with no gap at all. */
.section--read .prose-band__rail {
  margin-bottom: var(--s-7);
}

/* ---- .hero--index - a hero that belongs to a grid, not to a column --------
   Marcos, on the blog index: "pode alinhar a hero a esquerda, juntamente com os
   filtros."
   🔴 THE READING MEASURE IS THE WRONG REFERENCE ON THIS PAGE. `.hero--read` caps
   at 880px because every page using it is a reading column. The blog index is a
   card GRID at the full container — measured, the cards start at x=128 while the
   hero sat at 280 — so the hero read as indented against its own content. An
   index page's hero should hang off the same edge as the thing it indexes.
   ⚠ NOT applied to the glossary index: its content IS a reading column (the A-Z
   run sits at 880), so its hero is already on the right reference there. Same
   modifier, different page, would push it off. */
.hero--index .hero__inner {
  max-width: var(--container-max);
}

.hero--index .hero__copy {
  max-width: none;
}

.hero--feature {
  position: relative;
  overflow: hidden;
  /* The photograph is the surface, so the section's own colour must not show
     through at the edges. */
  background-color: var(--ink);
  padding-block: calc(var(--s-section) + var(--s-9)) var(--s-9);
}

/* 🔴 THE RESET IS THE WHOLE RULE, AND REUSING THIS CLASS NAME WAS THE TRAP.
   `.hero__media` (style.css:1494) is NOT a bare wrapper — it is the reserved
   VIDEO SLOT component, and it ships `aspect-ratio: 16/9`, a border, a card
   radius and a `::after` reading "Cover animation + explainer video". Setting
   `position: absolute; inset: 0` overrides none of that. Chrome then resolves
   the width from the insets and takes the HEIGHT FROM THE ASPECT RATIO, so the
   photograph became a 16:9 band instead of covering the section: at 500px it
   ended just under the title and the standfirst fell onto bare page. Caught at
   500px, INVISIBLE at 1440 — where the hero happens to be near 16:9 anyway,
   which is exactly how this kind of bug ships. */
.hero--feature .hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
  aspect-ratio: auto;
  border: 0;
  border-radius: 0;
  background-color: transparent;
  overflow: hidden;
}

.hero--feature .hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}

.hero--feature .hero__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    180deg,
    rgba(50, 54, 101, 0.80) 0%,
    rgba(11, 12, 22, 0.88) 60%,
    rgba(11, 12, 22, 1) 92%
  );
}

.hero--feature .hero__inner {
  position: relative;
  z-index: 1;
  /* The centring used to be inherited from `.hero--read`, which now left-aligns.
     It is declared here because on a photograph there is no column beside the
     copy to hang it off — see the note under `.hero--read`. */
  text-align: center;
}

.hero--feature .hero__copy { margin-inline: auto; }

/* ---- .final-cta--spaced - the closing band was arriving too soon ----------
   Marcos, on the Learn hub: "aumentar o espaçamento entre os cards e o CTA."
   MEASURED: 64px between the last card and the top of the gradient band, against
   190px between the hero and the cards on the same page.
   ⚠ THE REASON IT LOOKS TIGHTER THAN IT IS: `.final-cta` spends
   `calc(--s-section + --s-5)` on its own top padding, but that padding is INSIDE
   the coloured band — so it buys air under the heading, not before the band. The
   only gap the reader sees is the preceding section's 64px bottom padding.
   ✅ 112px total, which is the same interval the hero-to-content boundary now
   uses, so the page has one opening gap and one closing gap at the same weight.
   ⚠ Applied as a modifier on the six template screens rather than as
   `.section + .final-cta`, which would move the closing band on the Home and the
   three pillar pages — shipped, client-reviewed, and not in scope. */
.final-cta--spaced { margin-top: var(--s-7); }

/* ---- The blog card: the pill rides the picture, and the card asks ---------
   Marcos: "os cards de blogs deveriam ter um CTA e o pill poderia ir por cima da
   imagem… não ficaria melhor?" Yes on both, with one constraint each.

   🔴 THE CTA GOES ON REAL POSTS ONLY. Three of the four cards are slots with no
   topic and no destination — they are `<div>`, not `<a>`. "Read the post" on a
   card that goes nowhere is a lie the layout tells, and the whole point of those
   three is that they hold a slot honestly. The pill still rides their picture,
   because the no-image default IS a designed surface.
   ⚠ THE ARROW MOVES FROM THE TITLE TO THE CTA — it does not get added. Two
   arrows on one card is the `→ →` defect this project already shipped once, on
   `.sub-link`, and `.vertical-card__name::after` supplies one today. The removal
   is scoped to `--3` so the root hub's five vertical cards keep theirs.
   ⚠ The pill is opaque `--surface-3`, so its 5.67:1 is against the pill and not
   against whatever photograph is behind it. The hairline is what keeps it from
   dissolving into a busy frame.
   --------------------------------------------------------------------------- */
.vertical-card__media .vertical-card__badge {
  position: absolute;
  top: var(--s-4);
  left: var(--s-4);
  z-index: 1;
  margin-top: 0;
  border: 1px solid var(--border-subtle);
}

.vertical-grid--3 .vertical-card__name::after { content: none; }

.vertical-card__cta {
  /* Takes over the `margin-top: auto` the badge used to carry, so the card still
     pins one element to its bottom edge and the grid rows stay even. */
  margin-top: auto;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-size: var(--type-caption);
  font-weight: 600;
  color: var(--text-accent);
}

.vertical-card__cta::after {
  /* ⚠ AN ESCAPE, NOT A LITERAL ARROW — and this shipped broken for one
     render as a control character followed by "92": the sequence was written with a single
     backslash upstream, so the leading pair was read as an OCTAL escape and CSS
     received a control character followed by "92". ⚠ IT HAPPENED TWICE: the
     first FIX was written the same way and shipped the same broken value. Build
     the sequence without a literal backslash in the source, or copy the working
     form from `.vertical-card__name::after` — never retype it.
     the correct form; keep the two identical. */
  content: '\2192';
  transition: transform var(--duration-fast) var(--ease-out-quart);
}

a.vertical-card:hover .vertical-card__cta::after { transform: translateX(4px); }

@media (prefers-reduced-motion: reduce) {
  a.vertical-card:hover .vertical-card__cta::after { transform: none; }
}

/* ---- .vertical-grid--3 - three across, not four --------------------------
   "3 cards por linha ficaria melhor, assim eles estão espremidos demais, o que
   gera uma quebra muito grande nas linhas."
   ⚠ The base grid is `repeat(auto-fit, minmax(248px, 1fr))`, which fits FOUR in
   an 1184px container and is right for the root hub's five verticals. A blog
   card carries a picture, a title, a summary and a badge — four times the
   content in the same track. The modifier is scoped so the hub is untouched. */
.vertical-grid--3 { grid-template-columns: 1fr; }
@media (min-width: 680px) { .vertical-grid--3 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .vertical-grid--3 { grid-template-columns: repeat(3, 1fr); } }

/* ---- .filterbar - a filter that does not read as a call to action ---------
   Marcos: "vamos repensar o filtro? talvez um dropdown aqui faça mais sentido?
   não sei dizer…"
   🔴 THE ANSWER IS NOT A DROPDOWN, AND THE REASON IS ON THE PAGE. The hero says
   "pick the one you are" — the four audiences ARE the proposition, and a select
   hides them behind a click. The actual defect is that they were built as `.btn`
   (pill, one filled magenta) on the reasoning that "a filter row IS a row of
   small buttons, zero new CSS". Rendered, that puts a primary CTA in the reading
   flow, competing with `Book a meeting` in the nav. A filter should read as a
   state control: quiet, outlined, one selected.
   ⚠ Padding is `--s-3 --s-5`, not `--s-2 --s-4`: it puts the control at about
   44px tall, which is the touch-target floor. */
.filterbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
  /* ↩ Was `center`. Marcos: "pode alinhar a hero a esquerda, juntamente com os
     filtros." These controls belong to the grid below them, and that grid starts
     at the container edge — a centred row above a left-aligned grid is a third
     axis on a page that should have one. */
  justify-content: flex-start;
}

.filterbar__btn {
  appearance: none;
  padding: var(--s-3) var(--s-5);
  border: 1px solid var(--border-default);
  border-radius: var(--radius-pill);
  background-color: transparent;
  color: var(--text-secondary);
  font-family: var(--font-body);
  font-size: var(--type-caption);
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: color var(--duration-fast) var(--ease-out-quart),
              border-color var(--duration-fast) var(--ease-out-quart),
              background-color var(--duration-fast) var(--ease-out-quart);
}

.filterbar__btn:hover {
  color: var(--text-primary);
  border-color: var(--soft-lavender);
}

.filterbar__btn[aria-pressed="true"] {
  color: var(--text-primary);
  border-color: var(--text-accent);
  background-color: var(--surface-1);
}

/* A read-mode section that is NOT a `.prose-band` — the glossary A-Z run, the
   related-terms block — takes the measure on its container instead, so the two
   kinds of section land on the same axis. ⚠ On the term page "Related terms."
   was a centred `.control__h2` sitting over a `.defs` run at its own centred
   630.7px: two axes in one block, which is what read as broken. */
.section--read > .container {
  /* Same padding compensation as `.prose-band__inner` above — see the note there. */
  max-width: calc(var(--measure-read) + var(--container-pad) * 2);
}

.section--read .control__header,
.section--read .control__h2,
.section--read .faq__header,
.section--read .gains__header,
.section--read .gains__h2,
.section--read .gains__benchmark {
  text-align: left;
  margin-inline: 0;
}

.section--read .defs { margin-inline: 0; }

/* An H1 that is a SENTENCE rather than a statement — it carries figures and
   clauses, so `--type-display` at the 12ch cap that suits `/how-it-works`
   ("Your customers already borrow.") breaks it into a ragged tower in a
   half-width column. Specificity has to beat `.hero--split .hero__h1`, hence
   the descendant form.
   ⚠ ALSO THE ANSWER ON THE GLOSSARY TERM PAGE, and the arithmetic is why:
   "Mutual Ownership Acquisition Technology (MoaT)" at `--type-display` (72px)
   fits about 15 characters per line in a 672px column, and "Acquisition" alone
   is 11 — five lines, which is exactly the "muitas quebras nos títulos" Marcos
   flagged. At `--type-h1` (48px) the same column takes about 22, and it lands
   in three. A term page's title is a long technical phrase; the display face is
   sized for the Home's four-word statement. */
/* ⚠ ALSO THE HOOK FOR AN AUTHORED-BREAK HEADLINE. The Learn hub sets its own
   line breaks ("How / Embedded Value / Actually Works", Marcos) and at
   `--type-display` the longest of those lines is ~570px against a ~544px half
   column, so it would wrap anyway and the authored breaks would fight the
   organic ones. At `--type-h1` all three lines clear it. */
.hero .hero__h1--sentence,
.hero--split .hero__h1--sentence {
  font-size: var(--type-h1);
  max-width: none;
  text-wrap: balance;
}

/* ---- 25. Fee Analyzer (`/calculator`) — page-scoped ----
   ⚠ EVERY RULE HERE IS PREFIXED `.fa`. This page is a campaign surface reachable
   by direct link only, and nothing else on the site uses these classes. Scoping
   them is what keeps a tool built under an open decision (O1) from reaching the
   eleven indexed pages if it has to change.
   ⚠ It reuses `.signup__grid`, `.signup__step-label`, `.form__field`, `.form__input`,
   `.form__select` and `.btn` rather than restating them — the roles are identical
   and this stylesheet has four recorded source-order cascade traps. */

.fa__trust {
  color: var(--text-accent);
  font-size: var(--type-body);
  margin-top: var(--s-4);
}

.fa-section { background-color: var(--surface-0); padding-top: 0; }

.fa {
  max-width: 760px;
  margin-inline: auto;
}

.fa__step,
.fa__result {
  background-color: var(--surface-1);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  padding: var(--s-7);
}

/* The steps are swapped with the `hidden` attribute. Without this the panels'
   own `display` would win and every step would render at once. */
.fa__step[hidden],
.fa__result[hidden],
.fa [hidden] { display: none; }

.fa .signup__grid { margin-bottom: var(--s-5); }

/* ---- mode chooser ---- */
.fa__modes {
  border: 0;
  padding: 0;
  margin: 0 0 var(--s-6);
  display: grid;
  gap: var(--s-3);
}
.fa__legend {
  font-size: var(--type-body-lg);
  color: var(--text-primary);
  margin-bottom: var(--s-4);
  padding: 0;
}
.fa__mode {
  display: flex;
  gap: var(--s-4);
  align-items: flex-start;
  padding: var(--s-4);
  background-color: var(--surface-2);
  border: 1px solid var(--surface-3);
  border-radius: var(--radius-card-sm);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out-quart);
}
.fa__mode:hover { border-color: var(--border-strong); }
.fa__mode:has(input:checked) { border-color: var(--text-accent); }
.fa__mode input { margin-top: 3px; accent-color: var(--magenta); }
.fa__mode-body { display: flex; flex-direction: column; gap: var(--s-1); }
.fa__mode-title { color: var(--text-primary); font-weight: 600; }
.fa__mode-helper { color: var(--text-muted); font-size: var(--type-caption); }

/* ⚠ TWO FIELDS SIDE BY SIDE ONLY LINE UP IF THEIR LABELS ARE THE SAME HEIGHT.
   "What do most of your customers pay with?" wraps to two lines while "Card volume
   in a normal month" fits on one, so the two inputs sat at different heights — the
   same thing happens in the gate between the sales-count and the provider fields.
   Each `.form__field` is its own flex column, so nothing was aligning them.
   Reserving two lines of label above 640px fixes every pair on the page at once.
   ⚠ SCOPED TO `.fa` AND TO THE TWO-COLUMN BREAKPOINT. Below 640px the grid is a
   single column, where a reserved second line is just a gap, and `.form__label`
   is shared with /join and /contact, which must not move. */
@media (min-width: 640px) {
  .fa .form__label {
    display: flex;
    align-items: flex-end;
    line-height: 1.4;
    min-height: 2.8em;
  }
}

/* ---- field support text ---- */
.fa__helper { color: var(--text-muted); font-size: var(--type-caption); }
.fa__error { color: var(--magenta-bright); font-size: var(--type-caption); font-weight: 500; }
.fa__error--form { display: block; margin-bottom: var(--s-4); }
.fa__warn {
  display: block;
  color: var(--text-secondary);
  font-size: var(--type-caption);
  margin-bottom: var(--s-4);
}

.fa__submit { width: 100%; margin-top: var(--s-2); }
.fa__step-note {
  color: var(--text-muted);
  font-size: var(--type-caption);
  margin: var(--s-6) 0 var(--s-4);
}

/* ---- the result ---- */
.fa__readback {
  color: var(--text-secondary);
  font-size: var(--type-body-lg);
  margin-bottom: var(--s-3);
}

.fa__figure {
  display: flex;
  flex-direction: column;
  gap: var(--s-1);
  margin: var(--s-6) 0;
  padding: var(--s-6);
  background-color: var(--surface-2);
  border-radius: var(--radius-card-sm);
}
.fa__figure-label {
  font-size: var(--type-eyebrow);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-secondary);
}
/* ⚠ NOT `--type-data-callout`. A range is two figures and a word, and at 48–80px
   it wraps into a tower on a phone. `--type-h1` keeps it on one or two lines and
   still reads as the page's largest thing. */
.fa__figure-value {
  font-size: var(--type-h1);
  font-weight: 700;
  color: var(--text-primary);
  line-height: 1.1;
}
.fa__figure-unit { color: var(--text-accent); font-weight: 600; }
.fa__figure-annual { color: var(--text-secondary); margin-top: var(--s-2); }

.fa__hedge {
  color: var(--text-muted);
  font-size: var(--type-caption);
  line-height: 1.6;
  margin-bottom: var(--s-3);
}
.fa__note { color: var(--text-secondary); font-size: var(--type-caption); }

.fa__switch { margin-top: var(--s-4); font-size: var(--type-caption); color: var(--text-muted); }
.fa__link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: var(--text-accent);
  text-decoration: underline;
  cursor: pointer;
}
.fa__link--block {
  display: block;
  margin: var(--s-5) auto 0;
  font-size: var(--type-caption);
}

.fa__gate-intro { margin-top: var(--s-8); }
.fa__h2 { font-size: var(--type-h2); margin-bottom: var(--s-3); }
.fa__h3 { font-size: var(--type-h3); margin-bottom: var(--s-2); }
.fa__sub { color: var(--text-secondary); margin-bottom: var(--s-5); }

/* ---- the full breakdown ---- */
.fa__stats {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-4);
  margin: var(--s-6) 0;
}
.fa__stat {
  padding: var(--s-5);
  background-color: var(--surface-2);
  border-radius: var(--radius-card-sm);
}
.fa__stat-value {
  display: block;
  font-size: var(--type-h3);
  font-weight: 700;
  color: var(--text-primary);
  margin-bottom: var(--s-2);
}
.fa__stat-label { color: var(--text-muted); font-size: var(--type-caption); line-height: 1.5; }

.fa__close {
  margin-top: var(--s-7);
  padding-top: var(--s-6);
  border-top: 1px solid var(--border-subtle);
}

.fa__consent {
  margin-top: var(--s-5);
  color: var(--text-muted);
  font-size: var(--type-caption);
  line-height: 1.6;
}

.fa__compliance {
  max-width: 760px;
  margin: var(--s-6) auto 0;
  color: var(--text-muted);
  font-size: var(--type-caption);
}

@media (max-width: 640px) {
  .fa__step,
  .fa__result { padding: var(--s-5); }
  .fa__stats { grid-template-columns: 1fr; }
}
