/* ============================================================================
   The Stakeholder's Family Office — styles
   Dark site on Deep Green. Cormorant display + Raleway body. Restrained gold.
   ========================================================================== */

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; color-scheme: dark; }
/* Taps flash the brand, not the UA's gray. */
html { -webkit-tap-highlight-color: rgba(184, 150, 62, 0.10); }
/* 2.4.11 focus not obscured: keyboard-driven scrolls keep the focused element clear of
   the sticky header (top) and the fixed mobile CTA bar (bottom, 52px where it exists). */
html { scroll-padding-top: calc(var(--header-h) + var(--sp-3)); scroll-padding-bottom: calc(var(--cta-bar-h) + var(--sp-3)); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--text);
  background: var(--green-900);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img, svg { display: block; max-width: 100%; }
img { height: auto; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; cursor: pointer; }
a, button, summary, input, select, textarea, .mobile-cta { touch-action: manipulation; }
ul, ol { list-style: none; padding: 0; }
/* Outlines follow each element's own border-radius natively; forcing
   border-radius: inherit here reshaped focused elements to their parent's radius. */
:focus-visible {
  outline: 2px solid var(--champagne);
  outline-offset: 3px;
}

/* skip link */
.skip {
  position: absolute; left: 50%; top: -60px; transform: translateX(-50%);
  background: var(--gold); color: var(--ink); font-weight: 700;
  padding: 0.7rem 1.2rem; border-radius: 0 0 var(--radius) var(--radius);
  z-index: 200; transition: top var(--dur-fast) var(--ease);
}
.skip:focus { top: 0; }
.visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* ---------- layout primitives ----------
   Spacing values live on the --sp-* scale. The exceptions are deliberate optical
   values, not drift: nav link padding (0.4/0.7rem, width-pressure-verified at the
   1101px collapse), button padding (0.9/1.7rem sets the 44px tap height with the
   label's tracking), and the mobile bar (0.85rem). Snap nothing here without
   re-measuring the nav row and the fold. */
.wrap { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }
.section { padding-block: var(--section-y); position: relative; }
/* DESIGN.md: "Vary spacing for rhythm; do not apply uniformly." One clamp across all
   15 sections put every boundary inside a 10% spread, so a gap meant nothing. Two
   tiers, applied ONLY to pure-air transitions: where a sunk band meets the ground the
   colour change already separates, so those keep the default and stay symmetric.
   Measured at 1440: continuations 175/173, default median 260, act break 389 = 1.50x. */
.section--continues { padding-top: calc(var(--section-y) * 0.52); }  /* one argument running on */
.section--act       { padding-top: calc(var(--section-y) * 2.05); }  /* a real act break */
/* Only #engagement takes --act: #contact already has the hardest boundary on the page,
   the only full-bleed photographic ground. */
.section--sunk { background: var(--green-950); box-shadow: inset 0 1px 0 var(--line), inset 0 -1px 0 var(--line); }

.eyebrow {
  font-family: var(--font-sans);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--champagne);
  display: inline-flex; align-items: center; gap: var(--sp-3);
}
.eyebrow::before {
  content: ""; width: 1.75rem; height: 1px; background: var(--gold);
  display: inline-block;
}
.section--center .eyebrow { justify-content: center; }

/* balance evens the display rag: the h1 was setting 24/26/9 chars, a widow at 84px. */
h1, h2, h3, .display { font-family: var(--font-display); font-weight: 600; text-wrap: balance; }
p, li { text-wrap: pretty; }
.s-guarantee { text-wrap: balance; }
/* Break control. pretty/balance cannot see sentence boundaries or keep units
   together, so unbreakable units (names, figures, citations) and forced
   thought-boundary breaks are marked in the markup. .nb glues a unit at 375px
   and up and must be shorter than the narrowest column it sits in; below 375
   the browser's default wrapping wins, so the 320px WCAG reflow pass is never
   at risk from a glue. .nb-w glues only above 560px, for re-breaks whose
   glued unit would overflow or triple-wrap a phone-width heading. Spans only:
   no nbsp, the font subset does not cover U+00A0. */
@media (min-width: 375px) { .nb { white-space: nowrap; } }
@media (min-width: 560.02px) { .nb-w { white-space: nowrap; } }
/* The phase rail captions run 2 lines at 1440 and up to 5 in the 768 rail;
   balance picks the width-appropriate even break instead of stranding the
   preposition at one width to fill the line at another. */
.phase p { text-wrap: balance; }
.display { font-size: var(--fs-display); font-weight: 500; line-height: var(--lh-tight); letter-spacing: -0.015em; }
h2.title { font-size: var(--fs-h2); line-height: var(--lh-snug); letter-spacing: -0.008em; font-weight: 500; }
.subtitle {
  font-family: var(--font-sans); font-weight: 400; color: var(--text-dim);
  font-size: var(--fs-lead); line-height: var(--lh-prose); max-width: var(--maxw-read);
}
.section-head { margin-bottom: clamp(var(--sp-7), 1.75rem + 3vw, 4rem); }
.section-head .title { margin-top: var(--sp-3); }
.section-head .title:first-child { margin-top: 0; }
.section-head .subtitle { margin-top: var(--sp-4); }
.section--center .section-head { text-align: center; margin-inline: auto; }
.section--center .subtitle { margin-inline: auto; }

em { font-style: italic; color: var(--champagne); }

/* lining + tabular numerals: ledger-grade figures, not Cormorant's bobbing old-style digits */
.display, h1, h2, h3, .col-foot .price,
.savings .s-amt, .hero-proof .pf-figure, .price-sub, .modules .m-price {
  font-variant-numeric: lining-nums tabular-nums;
}

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.55rem;
  min-height: var(--tap); padding: 0.9rem 1.7rem;
  font-family: var(--font-sans); font-weight: 700; font-size: var(--fs-ui);
  letter-spacing: var(--tracking-cta); text-transform: uppercase; white-space: nowrap;
  border-radius: 3px; border: 1px solid transparent;
  transition: transform var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.btn:active { transform: translateY(1px); }
/* the one CTA color: gold fill, deep-ink text (5.7:1 contrast) */
.btn-primary { background: var(--gold); color: var(--ink); box-shadow: 0 10px 30px -14px var(--glow-gold); }
.btn-primary:hover { background: var(--gilt); transform: translateY(-1px); box-shadow: 0 14px 34px -16px var(--glow-gold); }
.btn-primary:focus-visible { outline-color: var(--champagne); box-shadow: 0 0 0 4px var(--green-900), 0 0 0 5px var(--line-gold); }
.btn-ghost { background: transparent; color: var(--text); border-color: rgba(232, 212, 166, 0.5); }
/* One filled gold control on screen, everywhere. body.cta-near means a Book control
   is already visible (hero, price card, serve close, the form), so the header's copy
   of it steps down to ghost until the page's own CTA scrolls away. The .btn
   transition already animates the swap. */
body.cta-near .site-header .btn-primary {
  background: transparent; color: var(--text);
  border-color: rgba(232, 212, 166, 0.5); box-shadow: none;
}
body.cta-near .site-header .btn-primary:hover { background: var(--card-raised); border-color: var(--champagne); transform: none; }
.btn-ghost:hover { border-color: var(--champagne); background: var(--card-raised); }
.btn .arrow { transition: transform var(--dur-fast) var(--ease); }
.btn:hover .arrow { transform: translateX(3px); }
/* 1.4.10 reflow: at narrow widths the nowrap CTA label sets the min-content width of the
   whole pricing grid (measured 371px page width at a 320px viewport). Let labels wrap. */
@media (max-width: 420px) {
  .btn { white-space: normal; }
}

/* ---------- header ---------- */
/* The header row carries brand + six links + CTA. A wider wrap than the content
   grid gives the space-between real air at 1200-1500px viewports. */
.site-header .wrap { max-width: 1360px; }
.site-header {
  position: sticky; top: 0; z-index: 100;
  background: color-mix(in srgb, var(--green-900) 82%, transparent);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  border-bottom: 1px solid transparent;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.site-header.is-stuck {
  background: color-mix(in srgb, var(--green-950) 90%, transparent);
  border-bottom-color: var(--line);
}
/* flex-wrap is the 1.4.12 escape valve: under user-injected text spacing the inflated
   link row wraps under the brand instead of pushing the CTA off-screen. In the normal
   state everything fits on one row, so wrap never engages. */
.nav { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; min-height: var(--header-h); gap: 0 var(--sp-5); }
.brand { display: inline-flex; align-items: center; min-height: var(--tap); }

/* ---------- brand lockup ----------
   The firm's wordmark, set rather than placed. It was supplied as flat artwork;
   rebuilt in the brand's own committed face it scales to any size, stays
   selectable and searchable, recolours for the green ground (the artwork is
   Deep Green, which is this page's background), and costs no bytes.

   The grid is what makes it a lockup: one column sized by the widest line, so
   the rule row measures itself against the name above it and the two lines stay
   optically locked at every size. Nothing here is nowrap-fragile, because both
   lines are their own row.

   Two faces on purpose. The name is Cormorant, which the type rules hold at 18px
   and above. "Family Office" sits far below that floor at lockup scale, so it is
   Raleway in the site's own tracked-caps label treatment, in champagne, the one
   gold tone these rules permit at small sizes on green. */
.brand-lockup { display: inline-grid; justify-items: center; gap: 0.24rem; }
.brand-name {
  font-family: var(--font-display); font-weight: 600; font-size: 1.16rem;
  line-height: 1; color: var(--text);
  text-transform: uppercase; letter-spacing: 0.13em;
  /* Tracked caps add a trailing space after the last letter; the indent gives
     it back so the name sits centred over the rule instead of drifting left. */
  text-indent: 0.13em;
}
.brand-name .b { color: var(--champagne); }
/* The rule row: two hairlines out to the lockup's full width with the diamond
   held between them. `currentColor` is set once here so the SVG and both rules
   can never drift apart. */
.brand-rule {
  display: flex; align-items: center; gap: 0.34rem;
  /* Inline axis, not block: the row measures itself against the name above it,
     which is what locks the two lines into one mark. `align-self` here is the
     block axis and leaves both hairlines at zero width. */
  justify-self: stretch; color: var(--gold);
}
.brand-rule::before, .brand-rule::after {
  content: ""; flex: 1 1 0; height: 1px; background: currentColor; opacity: 0.55;
}
.brand-rule svg { display: block; flex: none; }
.brand-sub {
  font-family: var(--font-sans); font-weight: 700; font-size: 0.7rem;
  line-height: 1; color: var(--champagne);
  text-transform: uppercase; letter-spacing: 0.28em; text-indent: 0.28em;
}
.nav-links { display: flex; align-items: center; gap: 0.4rem; }
.nav-links a:not(.btn) {
  display: inline-flex; align-items: center; min-height: var(--tap);
  font-size: var(--fs-ui); font-weight: 600; color: var(--sage);
  padding: 0.4rem 0.7rem; border-radius: var(--radius-sm);
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease);
}
.nav-links a:not(.btn):hover { color: var(--text); }
/* Scrollspy state: typographic, not a pill. Set by main.js with aria-current. */
.nav-links a:not(.btn).is-current {
  color: var(--text);
  text-decoration: underline; text-underline-offset: 6px;
  text-decoration-color: var(--gold); text-decoration-thickness: 1px;
}
.nav-cta { margin-left: var(--sp-2); }
.nav-toggle { display: none; }
/* Just above the 1100px collapse point the full row's PREFERRED width (brand + 7 links
   + CTA, ~1062px) exceeded the viewport: flex shrink squeezed the brand box under its
   rigid glyphs, colliding the wordmark with the first link (pre-existing, worse once FAQ
   landed). The band steps the wordmark down (same mechanism as the <=600px steps, still
   >=18px) and trims link air so the preferred row genuinely fits from 1101 up; full
   spacing returns at 1360. */
@media (min-width: 1100.02px) and (max-width: 1359.98px) {
  .nav { gap: 0 var(--sp-3); }
  .brand-name { font-size: 1.05rem; }
  .brand-sub { letter-spacing: 0.24em; text-indent: 0.24em; }
  .nav-links { gap: 0.05rem; }
  .nav-links a:not(.btn) { padding: 0.4rem 0.25rem; }
  .nav-cta { margin-left: 0.2rem; }
  .nav-cta .btn { padding-inline: 0.9rem; }
}

/* scroll progress */
.scroll-progress {
  position: fixed; top: 0; left: 0; height: 2px; width: 100%;
  transform: scaleX(0); transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--bullion), var(--gold), var(--champagne));
  z-index: 101; will-change: transform;
}

/* ---------- hero ---------- */
.hero { position: relative; isolation: isolate; padding-top: clamp(6rem, 4.5rem + 9vw, 11rem);
  /* Bottom air trimmed independently: the top clamp frames the headline against the
     photograph, but the same 176px below the proof chip made hero->problem measure
     322px against a 224-260px default band — the page's second-largest gap with no
     act-break meaning. This lands it back inside the band. */
  padding-bottom: clamp(4rem, 3rem + 4.5vw, 6.5rem); overflow: hidden; }
.hero-media { position: absolute; inset: 0; z-index: -2; }
/* The hero source is the full sofa trio with wall above. 58% from the top lands the visible
   band on all three faces plus the sofa at desktop aspect ratios; below 900px the window
   turns portrait and rides a touch higher so the faces stay inside it. */
.hero-media img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 58%; }
@media (max-width: 900px) { .hero-media img { object-position: 50% 55%; } }
.hero-media::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(180deg, rgba(12,33,23,0.72) 0%, rgba(12,33,23,0.80) 42%, var(--green-900) 100%),
    linear-gradient(90deg, rgba(12,33,23,0.92) 0%, rgba(12,33,23,0.45) 60%, rgba(12,33,23,0.25) 100%);
}
.hero-grain { position: absolute; inset: 0; z-index: -1; opacity: 0.5;
  background: radial-gradient(120% 90% at 12% 0%, rgba(184,150,62,0.10), transparent 55%); }
.hero-inner { max-width: 52rem; }
/* 20ch sets the headline in three lines, keeping the lead and CTAs above the desktop fold. */
.hero h1 { margin-top: var(--sp-5); max-width: 20ch; }
.hero .hero-lead { margin-top: var(--sp-5); font-size: var(--fs-lead); color: var(--text-dim); max-width: 44ch; line-height: var(--lh-prose); }
/* Situation line. Sage and one step below body size so it reads as a quiet aside under the
   lead, not a second label: the hero already has an eyebrow. The CTA row gives back the air
   this costs (sp-7 -> sp-5) so the primary CTA stays above the 900px fold. */
/* 44ch: at 70ch this ran 98 characters on a single 651px line, run 7's one genuine
   line-length finding. Two short lines read; one long one gets skipped. */
.hero-trigger { max-width: 44ch; margin-top: var(--sp-4); font-size: var(--fs-body-sm); color: var(--sage); line-height: var(--lh-prose); }
.hero-actions { margin-top: var(--sp-5); display: flex; flex-wrap: wrap; gap: var(--sp-4); }
/* Below ~540px the two hero buttons stop sharing a row and stack, which is what pushed
   "See How It Works" off a 812px-tall phone once the trigger line landed. Measured: at
   375x812 the ghost button ran 804-857. One row is not recoverable at this width (the
   two labels measure 380px of content against a 333px container, so no padding or gap
   change fits them side by side), so the space comes back from the vertical rhythm
   instead: the trigger line's own top margin, the gap down to the CTA row, the gap
   between the stacked buttons, and the buttons' block padding, which falls back on the
   44px --tap floor rather than the 53px the 0.9rem padding was setting. The h1, the
   lead, and the hero's own padding are untouched. */
@media (max-width: 560px) {
  .hero-trigger { max-width: 44ch; margin-top: var(--sp-2); }
  .hero-actions { margin-top: var(--sp-3); gap: var(--sp-3); }
  .hero-actions .btn { padding-block: var(--sp-2); }
}
.hero-proof {
  margin-top: var(--sp-8); padding-top: var(--sp-5); border-top: 1px solid var(--line);
  display: flex; align-items: baseline; gap: var(--sp-4); flex-wrap: wrap; max-width: 60ch;
}
.hero-proof .pf-figure { font-family: var(--font-display); font-weight: 600; color: var(--champagne); font-size: var(--fs-figure-sm); line-height: 1; }
.hero-proof .pf-text { color: var(--sage); font-size: var(--fs-body-sm); line-height: var(--lh-prose); }

/* ---------- problem / fragmentation ---------- */
.compare { display: grid; grid-template-columns: 1fr; gap: var(--sp-5); }
@media (min-width: 880px) { .compare { grid-template-columns: 1fr auto 1fr; align-items: stretch; gap: 0; } }
.compare-col {
  border: 1px solid var(--card-border); border-radius: var(--radius-lg);
  background: var(--card); padding: clamp(var(--sp-5), var(--sp-4) + 2vw, var(--sp-7));
  display: flex; flex-direction: column;
}
.compare-col h3 {
  /* Card title, same rank as .modules h3 / .savings h3. The gold rule is what marks a
     section kicker, not the tracking, so card titles keep --tracking-label. */
  font-family: var(--font-sans); text-transform: uppercase; letter-spacing: var(--tracking-label);
  font-size: var(--fs-label); font-weight: 700; margin-bottom: var(--sp-5);
}
.compare-before { border-color: var(--card-border); }
.compare-before h3 { color: var(--sage-dim); }
.compare-after { background: var(--card-raised); border-color: var(--line-gold); box-shadow: var(--shadow-card); }
.compare-after h3 { color: var(--champagne); }
/* The gold connector is the section's argument glyph ('this stack becomes this'), so it
   renders on every breakpoint: same 46px circle, arrow rotated to point down the stack
   below 880px, horizontal between the panels above it. */
.compare-mid { display: flex; align-items: center; justify-content: center; position: relative; z-index: 2; }
.compare-mid span {
  width: 46px; height: 46px; border-radius: 50%; background: var(--green-900);
  border: 1px solid var(--line-gold); color: var(--gold);
  display: grid; place-items: center; font-size: 1.1rem;
  box-shadow: var(--shadow-pop);
}
@media (max-width: 879.98px) { .compare-mid svg { transform: rotate(90deg); } }
@media (min-width: 880px) {
  /* Both panels keep all four borders closed: with the 64px .compare-mid track between
     them, stripped facing borders read as hairlines terminating in mid-air. */
  .compare-before { border-radius: var(--radius-lg); }
  .compare-after  { border-radius: var(--radius-lg); }
  .compare-mid { width: 64px; }
}
.stack-list li, .gain-list li {
  display: grid; grid-template-columns: 1.4rem 1fr; gap: var(--sp-3);
  padding: var(--sp-3) 0; border-top: 1px solid var(--line); align-items: baseline;
}
.stack-list li:first-child, .gain-list li:first-child { border-top: none; }
.stack-list .k { font-weight: 600; color: var(--text); }
.stack-list .v, .gain-list .v { color: var(--sage); font-size: var(--fs-body-sm); }
.stack-list .mark { color: var(--sage-dim); }
.gain-list .mark { color: var(--gilt); }
.gain-list .k { color: var(--text); font-weight: 600; }
.col-foot { margin-top: auto; padding-top: var(--sp-5); }
.col-foot .price { font-family: var(--font-display); font-size: var(--fs-figure-sm); font-weight: 600; }
.compare-before .col-foot .price { color: var(--text-dim); }
.compare-after .col-foot .price { color: var(--champagne); }
.col-foot .meta { display: block; color: var(--sage-dim); font-size: var(--fs-ui); margin-top: var(--sp-1); }


/* ---------- pillars, de-carded ----------
   The four disciplines as editorial rows on hairlines: the row grammar that proved
   itself in the cut beliefs section, now carrying content that earns it. This retires
   the page's one icon-card grid, which PRODUCT.md's anti-references name. */
.pillar-list { margin-top: var(--sp-2); }
.pillar-row { display: grid; grid-template-columns: auto 1fr; gap: var(--sp-3) var(--sp-5);
  padding: var(--sp-5) 0; border-top: 1px solid var(--line); }
.pillar-row:last-child { border-bottom: 1px solid var(--line); }
.pillar-row .b-mark { grid-row: span 2; align-self: start; width: 9px; height: 9px; margin-top: 0.55em;
  background: var(--gold); transform: rotate(45deg); }
.pillar-row h3 { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-h3); line-height: var(--lh-heading); }
.pillar-row p { color: var(--sage); font-size: var(--fs-body); line-height: var(--lh-prose); max-width: 50ch; }
@media (min-width: 820px) {
  .pillar-row { grid-template-columns: 1.25rem minmax(0, 1fr) minmax(0, 1.15fr); align-items: baseline; gap: var(--sp-6); }
  .pillar-row .b-mark { grid-row: auto; }
}

/* The framework block inside the merged section: scope above, sequence below. */
.flow-block { margin-top: var(--sp-10); }
.flow-head { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-h2-sm); line-height: var(--lh-heading); }
.flow-block .subtitle { margin-top: var(--sp-3); }

/* ---------- framework flow ---------- */
.flow { display: grid; grid-template-columns: 1fr; gap: var(--sp-4); margin-top: var(--sp-4); }
/* minmax(0,1fr) keeps the four tracks equal at tablet widths (plain 1fr respects
   min-content, so 'Diversification' inflated its track to 193px against 159px
   neighbours); the h3 wrap permission below absorbs the long words instead. */
@media (min-width: 720px) { .flow { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; } }
.phase { position: relative; padding: var(--sp-5); }
@media (min-width: 720px) {
  .phase { padding: var(--sp-6) var(--sp-5); border-top: 1px solid var(--line-gold); }
  .phase::before {
    content: ""; position: absolute; top: -5px; left: 0; width: 9px; height: 9px; border-radius: 50%;
    background: var(--gold); box-shadow: 0 0 0 4px var(--green-900), 0 0 14px var(--glow-gold);
  }
  .phase:not(:last-child) { border-right: 1px solid var(--line); }
}
/* Below 720px every desktop sequence device (arrows, dots, top rails) is gated off, so the
   stacked column keeps its own spine: a gold hairline rail with counter numerals 01-04 sitting
   on it. Numerals live on ::after because html.gsap .phase::before belongs to the desktop
   dot tween (scale var --dot-scale) and reusing it would inherit that hidden-at-0 state. */
@media (max-width: 719.98px) {
  .flow { border-left: 1px solid var(--line-gold); counter-reset: phase; }
  .phase { counter-increment: phase; padding: var(--sp-4) 0 var(--sp-4) var(--sp-6); }
  .phase::after {
    content: counter(phase, decimal-leading-zero);
    position: absolute; left: 0; top: 1.45rem;
    transform: translateX(-50%);
    font-family: var(--font-sans); font-size: var(--fs-label); font-weight: 700;
    letter-spacing: var(--tracking-tight); color: var(--champagne);
    background: var(--green-900); padding-block: 0.3rem;
    font-variant-numeric: lining-nums tabular-nums;
  }
}
.phase h4 { font-family: var(--font-display); font-size: var(--fs-h3-sm); font-weight: 600; margin: 0.4rem 0 0.6rem; overflow-wrap: break-word; hyphens: auto; }
/* In the 720-900px band the equal tracks leave ~116-158px of content width and
   'Diversification' at 1.55rem measures wider, which forced an unhyphenated mid-word
   break. A fluid step-down (20px at 720 scaling back toward full size, never under the
   18px Cormorant floor) keeps every phase name on one line; full size returns at 900px. */
.phase p { color: var(--sage); font-size: var(--fs-body-sm); line-height: var(--lh-prose); }
.phase .p-arrow { display: none; }
@media (min-width: 720px) {
  .phase .p-arrow { display: block; position: absolute; top: -14px; right: -7px; color: var(--gilt); }
  .phase:last-child .p-arrow { display: none; }
}

/* Generic dated-source line, usable under any figure on the page. */
/* Label ranks. One treatment was doing nine jobs at the same 12.48px/champagne/0.18em,
   so rank could not be read from form (PRODUCT.md names this an anti-reference).
   Rank 1: champagne, --tracking-label, gold rule, for section kickers. Rank 2: sage,
   --tracking-sub, no rule, for labels naming a block rather than opening a section. */
.s-src { display: block; font-size: var(--fs-label); letter-spacing: var(--tracking-sub); text-transform: uppercase; color: var(--sage); font-weight: 700; }
.col-foot .s-src { margin-top: var(--sp-1); }

/* ---------- engagement / pricing ---------- */
/* Source order is savings -> price-card -> modules so the single-column stack reads
   value-before-ask with DOM, visual, and focus order aligned (no `order` property).
   The named areas rebuild the desktop layout: card spans both rows on the left. */
.pricing { display: grid; grid-template-columns: 1fr; gap: var(--sp-5); align-items: start; }
/* The fractional layer is a grid child now: the grid gap spaces it, its own
   top margin would double that at every width. */
.pricing .fx { margin-top: 0; }
@media (min-width: 940px) {
  .pricing {
    grid-template-columns: 1.15fr 0.85fr;
    grid-template-rows: auto auto auto;
    /* Three full-width bands, not a 2x2 mosaic. Four equal cards in quadrants
       gave the offer the same weight as the add-on list and left whatever
       column ran short as a hole. Now the terms pair off, the offer spans and
       anchors the section, and the extras close it. */
    grid-template-areas: "savings modules" "card card" "fx quote";
    gap: var(--sp-5);
  }
  .price-card { grid-area: card; }
  .savings { grid-area: savings; }
  .modules { grid-area: modules; }
  .pricing .fx { grid-area: fx; align-self: stretch; }
  /* Row 1 is a pair: the terms card and the add-on list share a bottom edge
     rather than ending 28px apart. */
  .savings { align-self: stretch; }
  .pricing .tq--sm { grid-area: quote; margin-top: 0; max-width: none; align-self: center; }
  /* At full width the eleven included lines run three up rather than two, so
     the anchor card stays a band and never becomes a column of its own. */
  .price-card .includes { grid-template-columns: repeat(3, 1fr); }
}
.price-card {
  border: 1px solid var(--line-gold); border-radius: var(--radius-lg);
  background: linear-gradient(180deg, var(--card-raised), var(--card));
  padding: clamp(var(--sp-6), 1.25rem + 2.4vw, var(--sp-8)); box-shadow: var(--shadow-card);
}
.price-title { font-family: var(--font-display); font-weight: 600; color: var(--text); font-size: clamp(1.7rem, 1.35rem + 1.5vw, 2.3rem); line-height: 1.08; letter-spacing: -0.008em; }
.price-sub { color: var(--champagne); font-weight: 600; font-size: var(--fs-body-sm); letter-spacing: 0.02em; margin-top: var(--sp-2); }
.includes { margin-top: var(--sp-5); display: grid; grid-template-columns: 1fr; gap: var(--sp-2) var(--sp-5); }
@media (min-width: 560px) { .includes { grid-template-columns: 1fr 1fr; } }
.includes li { display: grid; grid-template-columns: 1.1rem 1fr; gap: 0.6rem; align-items: baseline; color: var(--text-dim); font-size: var(--fs-body-sm); line-height: var(--lh-prose); }
.includes .tick { color: var(--gilt); }
.price-cta { margin-top: var(--sp-6); display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center; }

.modules { border: 1px solid var(--card-border); border-radius: var(--radius-lg); background: var(--card); padding: var(--sp-6) var(--sp-5); }
.modules h3, .savings h3 {
  font-family: var(--font-sans); text-transform: uppercase; letter-spacing: var(--tracking-label);
  font-size: var(--fs-label); font-weight: 700; color: var(--champagne); margin-bottom: var(--sp-4);
}
.modules li { display: flex; justify-content: space-between; gap: var(--sp-4); padding: var(--sp-3) 0; border-top: 1px solid var(--line); font-size: var(--fs-body-sm); }
.modules li:first-child { border-top: none; }
.modules .m-name { color: var(--text); }
.modules .m-price { color: var(--sage); text-align: right; white-space: nowrap; }

/* sanctioned savings figure + equal-weight non-guarantee */
.savings { border: 1px solid var(--line-gold); border-radius: var(--radius-lg); background: var(--card-raised); padding: var(--sp-6) var(--sp-5); }
.savings .s-amt { font-family: var(--font-display); font-weight: 600; color: var(--gold); font-size: clamp(1.55rem, 1.2rem + 1.6vw, 2.1rem); line-height: var(--lh-heading); }
.savings .s-cap { color: var(--text-dim); font-size: var(--fs-body-sm); margin-top: var(--sp-2); line-height: var(--lh-prose); }
.savings .s-guarantee {
  margin-top: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--line-gold);
  font-family: var(--font-display); font-weight: 600; color: var(--champagne);
  font-size: clamp(1.45rem, 1.15rem + 1.4vw, 1.95rem); line-height: var(--lh-heading);
}
.savings .s-guarantee small { display: block; font-family: var(--font-sans); font-weight: 400; font-size: var(--fs-body-sm); color: var(--text-dim); margin-top: var(--sp-2); line-height: var(--lh-prose); }

.coi { margin-top: var(--sp-5); display: grid; grid-template-columns: 1.1rem 1fr; gap: var(--sp-3);
  font-size: var(--fs-ui); color: var(--sage); line-height: var(--lh-prose); }
.coi .coi-mark { color: var(--gilt); }
.coi a { color: var(--champagne); text-decoration: underline; text-underline-offset: 3px; }


/* ---------- who we serve ---------- */
.serve { position: relative; }
.profile { display: grid; grid-template-columns: 1fr; gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden; }
@media (min-width: 680px) { .profile { grid-template-columns: 1fr 1fr; } }
.profile .row { background: var(--green-900); padding: var(--sp-5); }
.profile .row .lab { font-size: var(--fs-label); letter-spacing: var(--tracking-sub); text-transform: uppercase; color: var(--sage); font-weight: 700; }
.profile .row .val { margin-top: var(--sp-2); color: var(--text); font-size: var(--fs-body); line-height: var(--lh-prose); }

/* ---------- team ---------- */
/* stretch, not start: with Tinja's strengthened bio the height stagger fell 140 -> 69px,
   where it reads as misalignment instead of intent. Creds are NOT bottom-pinned (the
   L1.4 mistake), so the slack lands at the card bottom and reads as padding. */
.team { display: grid; grid-template-columns: 1fr; gap: var(--sp-5); }
@media (min-width: 600px) { .team { grid-template-columns: 1fr 1fr; row-gap: 0; } }
/* Two principals: hold the card width the three-up grid had (≈384px) and centre
   the pair, so the row reads as composed rather than as a grid missing a card. */
@media (min-width: 920px) {
  .team { grid-template-columns: repeat(2, 1fr); max-width: 800px; margin-inline: auto; }
  /* The clamp is settled, but it left the heading at x=168 while its cards started at
     x=320: the only place a head and its content did not share a left edge. */
  #team .section-head { max-width: 800px; margin-inline: auto; }
}
@media (max-width: 599px) { .member-photo { max-width: 320px; margin-inline: auto; } }
/* min-width: 0 so a missing portrait degrades to a broken icon instead of blowing
   the grid track out past the container and clipping a card. */
/* Column so credentials can be pushed to the card foot. Equal-height tracks left 181px
   of void under one founder's last credential against 33px under the other's. The slack
   now falls between bio and credentials and both cards close on the same hairline. */
.member { border: 1px solid var(--card-border); border-radius: var(--radius-lg); background: var(--card); padding: var(--sp-6) var(--sp-5); min-width: 0; display: flex; flex-direction: column; }
/* Side-by-side symmetry (operator-directed): the two cards share row tracks
   (photo / name / role / bio / creds), so the creds hairline opens on the same
   line in both cards even when the bios differ by a line. Internal spacing is
   carried by child margins (.team row-gap is 0 at 2-up); the subgrid spans the
   member's 5 children today — a 6th child (the gate-0.8 date stamp) needs the
   span bumped. Must sit AFTER the base .member rule or its flex wins the
   cascade. Pre-subgrid browsers keep the flex-column card unchanged. */
@media (min-width: 600px) {
  @supports (grid-template-rows: subgrid) {
    .member { display: grid; grid-template-rows: subgrid; grid-row: span 5; }
  }
}
.member-photo {
  width: 100%; aspect-ratio: 4 / 5; height: auto;
  border-radius: var(--radius); object-fit: cover; object-position: center top;
  display: block; margin-bottom: var(--sp-5);
  box-shadow: 0 0 0 1px var(--line-gold), 0 14px 30px -16px rgba(0,0,0,0.8);
}
.member h3 { font-size: var(--fs-h3); font-weight: 600; }
.member .role { color: var(--champagne); font-size: var(--fs-label); letter-spacing: var(--tracking-sub); text-transform: uppercase; font-weight: 700; margin-top: var(--sp-1); font-family: var(--font-sans); }
.member p { color: var(--sage); font-size: var(--fs-body-sm); line-height: var(--lh-prose); margin-top: var(--sp-4); }
/* Credentials as scannable labeled rows, not chips (Callan-grade credential density). */
.member .creds { margin-top: var(--sp-6); padding-top: var(--sp-4); }
/* These rows run 33-54 characters, stretching a micro-label scale across sentence-length
   content (flagged independently by both critique assessments). Same champagne, same
   hairline rows: set as credential lines rather than labels so they can be read. */
.member .creds li { font-size: var(--fs-ui); letter-spacing: 0.01em; color: var(--champagne); border-top: 1px solid var(--line); padding: var(--sp-3) 0; }
.member .s-src { margin-top: var(--sp-2); }

/* ---------- CTA + form ---------- */
.cta { position: relative; isolation: isolate; overflow: hidden; }
.cta-media { position: absolute; inset: 0; z-index: -2; }
.cta-media img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 60%; }
.cta-media::after { content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, var(--green-900) 0%, rgba(12,33,23,0.9) 50%, var(--green-950) 100%); }
.cta-grid { display: grid; grid-template-columns: 1fr; gap: clamp(2rem, 1.4rem + 3vw, 4rem); align-items: start; }
@media (min-width: 900px) { .cta-grid { grid-template-columns: 1fr 1fr; } }
/* Below 900px the copy column pushed the first field 750px below the anchor, so the tap
   that means "I want to talk" delivered 620px of prose before an input. Putting the form
   first fixed that and broke something else: the field arrived with its framing headline
   819px BELOW it, so the highest-trust moment had no context above it.
   display:contents lets the copy column's own children be ordered individually, so the
   headline leads, the form follows immediately, and the supporting prose sits under it.
   That overshot: it left the reassurance about 950px BELOW the form, so the ask came
   before the reason. "After you book" is the reassurance that belongs beside the ask (it
   is three short rows, not a paragraph), so it moves up between the headline and the
   form. The longer explanatory prose and the email fallback stay below. */
@media (max-width: 899.98px) {
  .cta-copy { display: contents; }
  .cta-copy > h2 { order: -3; margin-bottom: var(--sp-5); }
  /* The lead paragraph (what to bring) computed order:0 and landed below the submit
     button, so the phone met the form with no explanation of the call. It ties the h2
     at -3 and DOM order resolves the tie: h2, lead, steps, form. */
  .cta-copy > p:first-of-type { order: -3; margin-bottom: var(--sp-5); }
  /* margin-top zeroed: the grid gap plus the h2's own margin already separate them. */
  .cta-grid .form { order: -1; }
}
.cta h2 { font-size: var(--fs-h1); line-height: 1.1; }
.cta-copy p { margin-top: var(--sp-4); color: var(--text-dim); font-size: var(--fs-lead); line-height: var(--lh-prose); max-width: 44ch; }
.cta-alt { margin-top: var(--sp-6); display: flex; flex-direction: column; gap: var(--sp-2); color: var(--sage); font-size: var(--fs-body-sm); }
.cta-alt a { color: var(--champagne); text-decoration: underline; text-underline-offset: 3px; }
/* Compact post-book sequence beside the form (C16): triggers-lead label idiom + hairline rows. */

.form { border: 1px solid var(--card-border); border-radius: var(--radius-lg); background: color-mix(in srgb, var(--green-850) 86%, transparent); padding: clamp(var(--sp-5), var(--sp-4) + 2vw, 2.2rem); }
.field { margin-bottom: var(--sp-4); }
.field label { display: block; font-size: var(--fs-ui); letter-spacing: var(--tracking-tight); text-transform: uppercase; color: var(--text-dim); font-weight: 700; margin-bottom: var(--sp-2); }
.field label .req { color: var(--gilt); }
.field input, .field select, .field textarea {
  width: 100%; min-height: var(--tap); padding: 0.7rem 0.9rem; font: inherit; font-size: 1rem;
  color: var(--text); background: rgba(12,33,23,0.55);
  border: 1px solid rgba(232, 212, 166, 0.58); border-radius: var(--radius-sm);
  transition: border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
.field textarea { min-height: 110px; resize: vertical; }
.field select { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%23B8963E' d='M1 1l5 5 5-5'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 1rem center; padding-right: 2.4rem; }
/* Fields substituted a 1px border plus a halo for the page's 2px champagne outline:
   1.4.11-clean but visibly the weakest indicator on the page. One treatment now. */
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible {
  outline: 2px solid var(--champagne); outline-offset: 3px; border-color: var(--gilt);
}
.field input::placeholder, .field textarea::placeholder { color: var(--sage-dim); }

/* Invalid state. aria-invalid had been set since launch with no rule consuming it. */
.field input[aria-invalid="true"],
.field select[aria-invalid="true"],
.field textarea[aria-invalid="true"] { border-color: var(--error); }
.field input[aria-invalid="true"]:focus-visible,
.field select[aria-invalid="true"]:focus-visible,
.field textarea[aria-invalid="true"]:focus-visible { border-color: var(--error); outline-color: var(--error); }
/* The message carries the fix, not just the fact. Never colour alone. */
.field-error { margin-top: var(--sp-2); font-size: var(--fs-ui); line-height: var(--lh-prose); color: var(--error); display: none; }
.field-error.is-shown { display: block; }
.field-hint { margin-top: var(--sp-2); font-size: var(--fs-label); line-height: 1.4; color: var(--sage-dim); }

.form .btn-primary { width: 100%; }
.form .btn-primary[disabled] { opacity: 0.62; cursor: progress; box-shadow: none; }
.form-note { margin-top: var(--sp-4); font-size: var(--fs-ui); color: var(--sage-dim); line-height: var(--lh-prose); }
.form-note a { color: var(--champagne); text-decoration: underline; text-underline-offset: 2px; }
/* The rule gives the status presence in any state, so meaning never rides on hue. */
.form-status { font-size: var(--fs-ui); line-height: var(--lh-prose); color: var(--text-dim); }
.form-status:not(:empty) { margin-bottom: var(--sp-4); padding-left: var(--sp-4); border-left: 2px solid currentColor; }
.form-status.is-error { color: var(--error); }
.form-status.is-ok { color: var(--champagne); }
.form-status.is-busy { color: var(--sage); }
/* Inline escape hatch, only rendered when a draft was actually restored. Text, not a
   button shape: the page has exactly one filled CTA and this must not read as a second. */
.status-action { background: none; border: 0; padding: 0; font: inherit; color: var(--champagne);
  text-decoration: underline; text-underline-offset: 3px; }
.status-action:hover { color: var(--text); }

/* Spam trap: off-screen, untabbable, aria-hidden. */
.hp { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* ---------- footer ---------- */
.site-footer { background: var(--green-950); border-top: 1px solid var(--line); padding-block: clamp(3rem, 2rem + 3vw, 4rem); }
.foot-top { display: grid; grid-template-columns: 1fr; gap: var(--sp-6); padding-bottom: var(--sp-6); border-bottom: 1px solid var(--line); }
@media (min-width: 760px) { .foot-top { grid-template-columns: 1.3fr 1fr 1fr; } }
.foot-brand .brand { margin-bottom: var(--sp-4); }
.foot-brand .tagline { font-family: var(--font-display); font-style: italic; color: var(--champagne); font-size: var(--fs-h3-sm); }
.foot-brand .foot-contact { margin-top: var(--sp-4); color: var(--sage); font-size: var(--fs-body-sm); }
.foot-brand .foot-contact a { color: var(--text-dim); }
/* font-family was missing, making this the only serif micro-label on the page:
   Cormorant at 12.48px, against DESIGN.md's "never below 18px". */
.foot-col h3 { font-family: var(--font-sans); font-size: var(--fs-label); letter-spacing: var(--tracking-sub); text-transform: uppercase; color: var(--sage-dim); font-weight: 700; margin-bottom: var(--sp-4); }
.foot-col li { margin-bottom: var(--sp-1); }
.foot-col a { color: var(--text-dim); font-size: var(--fs-body-sm); display: inline-block; padding-block: 0.8rem; }
.foot-col a:hover { color: var(--text); }
/* A 397px text column left-aligned in a 1104px row left 707x473px dead, the largest
   empty field on the page, and abandoned the three-column grid the footer had just
   established one row above. Same words, same measure per column, the row now used. */
.disclosure { padding-top: var(--sp-6); }
@media (min-width: 920px) {
  .disclosure { display: grid; grid-template-columns: 1fr 1.15fr 1.25fr; gap: var(--sp-6); }
  .disclosure p { max-width: none; margin-bottom: 0; }
}
/* 84ch rendered 86/106/104 actual chars per line (ch is the width of "0", which Raleway
   draws wide). 52ch renders 69/72/68. Locked legal copy untouched; only the container. */
.disclosure p { color: var(--sage-dim); font-size: var(--fs-label); line-height: 1.65; max-width: 52ch; margin-bottom: 0.9rem; }
.disclosure a { color: var(--sage); text-decoration: underline; text-underline-offset: 2px; }
.disclosure .legal-name { color: var(--sage); }
.foot-base { margin-top: var(--sp-5); display: flex; flex-wrap: wrap; justify-content: space-between; gap: var(--sp-4); color: var(--sage-dim); font-size: var(--fs-label); }

/* ---------- reveal animation ---------- */
/* content is visible by default (works with JS off / before JS runs, and keeps the LCP heading instant);
   the hidden-then-reveal animation only applies once the inline head script adds .js to <html> */
.reveal { opacity: 1; transform: none; }
/* No will-change here: promoting all 53 reveal elements at first paint costs more than
   the transition saves, and the GSAP path manages its own compositing hints. */
html.js .reveal { opacity: 0; transform: translateY(20px); transition: opacity 0.6s var(--ease-out), transform 0.6s var(--ease-out); }
html.js .reveal.in { opacity: 1; transform: none; }
/* The hero is exempt from the pre-JS hidden state: its lead, CTAs, and proof chip must
   paint before any script arrives (the LCP window). The GSAP tier animates them FROM
   visible (gsap.from), so a failed or slow script can never strand the primary CTA. */
html.js .hero .reveal { opacity: 1; transform: none; transition: none; }
.reveal[data-d="1"] { transition-delay: 0.08s; }
.reveal[data-d="2"] { transition-delay: 0.16s; }
.reveal[data-d="3"] { transition-delay: 0.24s; }
.reveal[data-d="4"] { transition-delay: 0.32s; }

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none; transition: none; }
  .scroll-progress { display: none; }
  .btn, .btn .arrow, .skip, .site-header { transition: none; }
}

/* ---------- GSAP enhancement layer ---------- */
/* main.js adds .gsap to <html> only after GSAP and ScrollTrigger both load and motion
   is allowed. Everything below is scoped to that class, so the CSS-only reveal above
   remains the behaviour whenever GSAP is absent. */

/* GSAP writes opacity and transform inline every frame; the CSS transition would try to
   interpolate each of those writes and fight it. Hand the property over cleanly. */
html.gsap .reveal { transition: none; }

/* The gold rule draws itself in. Defaulting the scale to 1 means the rule is full width
   if the tween never runs, so a failed script leaves the mark correct rather than absent. */
html.gsap .eyebrow::before { transform: scaleX(var(--rule-scale, 1)); transform-origin: left center; }

/* Framework phase dots pop in sequence under GSAP. Same default-to-1 failsafe. */
html.gsap .phase::before { transform: scale(var(--dot-scale, 1)); }

/* ---------- mobile nav ---------- */
@media (max-width: 1100px) {
  .nav-toggle .ico-close { display: none; }
  .nav-toggle[aria-expanded="true"] .ico-bars { display: none; }
  .nav-toggle[aria-expanded="true"] .ico-close { display: block; }
  .nav-toggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: var(--tap); height: var(--tap); border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm); background: transparent; color: var(--text);
  }
  /* with JS present: links collapse into a toggled dropdown */
  html.js .nav-links {
    position: fixed; inset: var(--header-h) 0 auto 0; flex-direction: column; align-items: stretch; gap: 0;
    /* Fully opaque: at 97% body copy read straight through the panel. backdrop-filter
       goes with it, since nothing translucent is left to blur. */
    background: var(--green-950);
    border-bottom: 1px solid var(--line); padding: var(--sp-2) var(--gutter) var(--sp-5);
    max-height: calc(100dvh - var(--header-h)); overflow-y: auto; overscroll-behavior: contain;
    transform: translateY(-8px); opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease), visibility var(--dur-fast) var(--ease);
  }
  html.js .nav-links.open { opacity: 1; visibility: visible; transform: none; pointer-events: auto; }
  /* without JS the toggle cannot work, so show the links as a static stacked list */
  html:not(.js) .nav { flex-wrap: wrap; }
  html:not(.js) .nav-toggle { display: none; }
  html:not(.js) .nav-links { flex-basis: 100%; flex-direction: column; align-items: stretch; }
  .nav-links a:not(.btn) { padding: 0.95rem 0.4rem; font-size: 1rem; border-bottom: 1px solid var(--line); }
  .nav-links .nav-cta { margin: var(--sp-4) 0 0; }
  .nav-links .nav-cta .btn { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .nav-links { transition: none; }
}

/* keep the wordmark on one line on small phones, never below the 18px Cormorant floor */
@media (max-width: 600px) {
  .brand-name { font-size: 1.02rem; letter-spacing: 0.11em; text-indent: 0.11em; }
  .brand-sub { letter-spacing: 0.2em; text-indent: 0.2em; }
}
@media (max-width: 360px) {
  .brand-name { font-size: 0.94rem; letter-spacing: 0.09em; text-indent: 0.09em; }
  .brand-sub { letter-spacing: 0.16em; text-indent: 0.16em; }
  .brand-lockup { gap: 0.2rem; }
}

/* ============================ ULTIMATE-PASS ADDITIONS ============================ */

/* html carries scroll-padding-top of --header-h + --sp-3. This element used to repeat
   the SAME value as scroll-margin-top; the two add, so every anchor landed 176px past
   its section against a 77px header and the heading arrived 340-360px down. html owns
   the header compensation; this only cancels enough of the section's own top padding
   to bring the heading up under the header. After: h2 at 131-196px across viewports. */
main > section { scroll-margin-top: calc(var(--sp-3) - var(--section-y) * 0.62); }
/* The offset has to know which padding tier it is cancelling. One formula against three
   tiers landed #pillars 21px under the header and #engagement 192px: a 171px spread. */
main > section.section--continues { scroll-margin-top: calc(var(--sp-3) - var(--section-y) * 0.14); }
main > section.section--act { scroll-margin-top: calc(var(--sp-3) - var(--section-y) * 1.62); }

/* ---------- fractional executive layer (inside engagement) ---------- */
/* The operator called the naked band out of place, and the diagnosis holds: everything
   else in #engagement is a card, so the fx layer becomes the section's closing card —
   full-width like the fee-model strip that opens it, neutral-bordered like the pair it
   sits under. Same words, same internal split. */
.fx { margin-top: var(--sp-5); border: 1px solid var(--card-border); border-radius: var(--radius-lg);
  background: var(--card); padding: var(--sp-6); }
/* The editorial two-track split belonged to the full-width band. Inside the pricing
   grid the card holds one column of the section, so eyebrow, head and roles stack:
   a 0.8fr headline track at this width would set the head four lines deep. */
.fx-eyebrow { font-size: var(--fs-label); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--champagne); font-weight: 700; }
.fx-roles { margin-top: var(--sp-4); color: var(--text-dim); font-size: var(--fs-body-sm); line-height: var(--lh-prose); max-width: 52ch; }
.fx-head { font-family: var(--font-display); font-weight: 500; font-size: var(--fs-h2-sm); margin-top: var(--sp-2); line-height: var(--lh-heading); }

/* ---------- decision triggers (inside who we serve) ---------- */
.triggers { margin-top: var(--sp-7); }
.triggers-lead { font-size: var(--fs-label); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--champagne); font-weight: 700; margin-bottom: var(--sp-4); }
.trigger-list { display: grid; grid-template-columns: 1fr; gap: 0 var(--sp-6); }
@media (min-width: 720px) { .trigger-list { grid-template-columns: 1fr 1fr; } }
.trigger-list li { display: grid; grid-template-columns: 1.2rem 1fr; gap: var(--sp-3); align-items: baseline; color: var(--text-dim); font-size: var(--fs-body); line-height: var(--lh-prose); padding: var(--sp-3) 0; border-top: 1px solid var(--line); }
.trigger-list .tg-mark { color: var(--gilt); }
.triggers-close { margin-top: var(--sp-5); font-family: var(--font-display); font-style: italic; font-size: var(--fs-h2-sm); color: var(--champagne); }
/* The close stopped at x=756 against a 1,320px edge: 564px of empty right field under
   a full-width two-column list. It now sits on the same two tracks. */
.triggers-notfit { margin-top: var(--sp-5); color: var(--sage); font-size: var(--fs-body-sm); line-height: var(--lh-prose); max-width: 52ch; }
@media (min-width: 720px) {
  .triggers-foot { display: grid; grid-template-columns: 1fr 1fr; gap: 0 var(--sp-6); align-items: start; }
  /* Explicit placement: auto-flow put the link under the disqualifier, separating the
     ask from the sentence that earns it. */
  /* Explicit placement: the disqualifier holds the left column; the conviction line
     keeps its own CTA directly beneath it. */
  .triggers-foot .triggers-notfit { grid-column: 1; grid-row: 1 / span 2; margin-top: var(--sp-5); max-width: none; }
  .triggers-foot .triggers-close { grid-column: 2; grid-row: 1; margin-top: var(--sp-5); }
  .triggers-foot .triggers-cta { grid-column: 2; grid-row: 2; }
}
/* The conviction-peak exit ramp: a quiet text link on the champagne underline idiom.
   Padding reaches the 44px tap floor. */
.triggers-cta { margin-top: var(--sp-2); }
.triggers-cta a { display: inline-block; padding-block: 0.8rem; color: var(--champagne); font-weight: 600; font-size: var(--fs-body-sm); text-decoration: underline; text-underline-offset: 3px; }
.triggers-cta a:hover { color: var(--text); }

/* ---------- fee model strip ---------- */
.fee-model { border: 1px solid var(--line-gold); border-radius: var(--radius-lg); background: var(--card); padding: var(--sp-6); margin-bottom: var(--sp-7); max-width: var(--maxw-text); }
/* Same device at component scale: the fee statement reads as a band, not a narrow
   card with 415px of empty field beside it. */
@media (min-width: 1000px) {
  .fee-model { max-width: none; display: grid; grid-template-columns: 0.8fr 1.2fr;
    gap: var(--gap-editorial); align-items: start; padding: var(--sp-7) var(--sp-6); }
  .fee-model p { margin-top: 0; }
}
.fee-model h3 { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-h2-sm); line-height: var(--lh-heading); }
.fee-model p { margin-top: var(--sp-3); color: var(--text-dim); line-height: 1.65; }

/* ---------- first-year roadmap ---------- */
.roadmap { display: grid; grid-template-columns: 1fr; gap: var(--sp-5); margin-top: var(--sp-7); }
/* Four bands since the 2026-07-30 copy review added "the first 30 days": two up at the
   tablet step, four across once the measure can carry it. */
@media (min-width: 880px) { .roadmap { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1120px) { .roadmap { grid-template-columns: repeat(4, 1fr); } }
.rm-band { border-top: 1px solid var(--line-gold); padding-top: var(--sp-4); }
.rm-band h3 { font-family: var(--font-sans); font-size: var(--fs-label); letter-spacing: var(--tracking-sub); text-transform: uppercase; color: var(--sage); font-weight: 700; }
.rm-band p { margin-top: var(--sp-3); color: var(--sage); font-size: var(--fs-body-sm); line-height: var(--lh-prose); }

/* head-aside: the one pattern break. Measured dead right field at 1440 was #faq 458px
   and .fee-model 415px; the others already filled their measure and are
   left alone. The head steps out of the stack and stands beside its content, reusing
   .story-grid's settled proportions so it is the page's own idiom. Below 1000px
   everything stacks as before. (#definition also used this pattern until it was folded
   into the FAQ as its first answer; #faq is now the only user.) */
@media (min-width: 1000px) {
  .head-aside > .wrap {
    display: grid; grid-template-columns: 0.8fr 1.2fr;
    gap: var(--gap-editorial); align-items: start;
  }
  .head-aside .section-head { margin-bottom: 0; }
  /* The answer column is far taller than its head, so the head holds its place. */
  #faq .section-head { position: sticky; top: calc(var(--header-h) + var(--sp-6)); }
}

/* ---------- founder story ---------- */
.story-grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-6); }
@media (min-width: 880px) { .story-grid { grid-template-columns: 0.8fr 1.2fr; gap: var(--gap-editorial); } }
.story-grid .section-head { margin-bottom: 0; }
/* The story's title column ran ~377px empty beside the letter. The photograph fills it on
   the same 4:5 crop and hairline treatment as the team portraits, so the page gains a
   human moment without a new component idiom. */
.story-photo { display: block; margin-top: var(--sp-6); position: relative; }
.story-photo img {
  width: 100%; aspect-ratio: 4 / 5; height: auto; object-fit: cover; object-position: center top;
  border-radius: var(--radius);
  box-shadow: 0 0 0 1px var(--line-gold), 0 14px 30px -16px rgba(0,0,0,0.8);
}
/* Joins the green world: --overlay-green at .78 pulled back to .34, because the hero
   and CTA grounds are backdrops for text and this one is the subject. Environments take
   the wash; faces are the exception, so team portraits stay unwashed. */
.story-photo::after {
  content: ""; position: absolute; inset: 0; border-radius: var(--radius);
  background: rgba(12, 33, 23, 0.34);
  pointer-events: none;
}
@media (max-width: 879.98px) { .story-photo { max-width: 360px; } }
.story-body p { color: var(--text-dim); font-size: var(--fs-lead); line-height: var(--lh-prose); max-width: 62ch; }
.story-body p + p { margin-top: var(--sp-4); }
.story-body p.story-pull { font-family: var(--font-display); font-style: italic; color: var(--champagne); font-size: var(--fs-h2-sm); line-height: 1.3; margin-top: var(--sp-5); }
.story-by { margin-top: var(--sp-6); display: flex; align-items: center; gap: var(--sp-3); color: var(--sage); font-size: var(--fs-body-sm); }
.story-by .mono { display: inline-flex; gap: 0.4rem; }
.story-by .mono span { width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; background: var(--green-950); border: 1px solid var(--line-gold); color: var(--champagne); font-family: var(--font-display); font-weight: 500; font-size: var(--fs-h3-sm); }

/* ---------- how it works steps ---------- */
/* Intake triad: .roadmap's breakpoint and track count, so #how reads as one grid. */
.steps { display: grid; grid-template-columns: 1fr; gap: var(--sp-5); margin-top: var(--sp-2); }
@media (min-width: 880px) { .steps { grid-template-columns: repeat(3, 1fr); } }
.step { border: 1px solid var(--card-border); border-radius: var(--radius-lg); background: var(--card); padding: var(--sp-6) var(--sp-5); }
.step .st-num { font-family: var(--font-display); font-weight: 500; font-size: var(--fs-h3); color: var(--gilt); font-variant-numeric: lining-nums tabular-nums; line-height: 1; }
.step h3 { font-family: var(--font-display); font-weight: 600; font-size: var(--fs-h3); margin: var(--sp-3) 0 var(--sp-2); }
.step p { color: var(--sage); font-size: var(--fs-body-sm); line-height: var(--lh-prose); }

/* ---------- client quotes (.tq), distributed ---------- */
/* One idiom, used four times across the page, each beside the argument it answers: the
   fragmentation diagram, the retainer, the founders, the ask. Deliberately NOT a section
   and NOT a card. Cards already carry pillars, steps, modules and the compare panels, so a
   bordered box around a client's sentence reads like a fifth product feature, and a
   dedicated testimonials block is the one place on a page a skeptical buyer skips.
   Construction is .hero-proof's (hairline, then content), so it lands as native page
   furniture rather than a bolted-on widget.
   Voice separation: .story-pull, the founders' own line, is Cormorant ITALIC in champagne.
   Client quotes take Cormorant ROMAN in ivory, so the two never read as one speaker. */
/* No rule, and nothing spanning the container. Two earlier cuts both failed on the same
   axis. A 60ch-wide gold rule under 1100px cards left a stub with dead green beside it;
   widening the rule to the full container fixed the alignment but made it read as a
   SECTION DIVIDER, so each quote announced itself as the start of a new section instead of
   commenting on the one above it, and the meta column it created turned a person's
   sentence into a table footer. Space separates these now. That also removes the alignment
   problem entirely: with no rule there is no edge that has to line up with anything.
   The measure is the signal. Body prose on this page runs to --maxw-text (760px); a quote
   set to ~50ch sits visibly inside that column, which is what marks it as an aside. */
/* --sp-10, not --sp-9: this scale runs 1-8 then 10 and 12, and the missing step silently
   resolved to 0, which parked the first cut about 20px under the compare cards. */
.tq { margin-top: var(--sp-10); max-width: 50ch; }
.tq blockquote { margin: 0; }
.tq blockquote p {
  font-family: var(--font-display); font-weight: 500; color: var(--text);
  font-size: var(--fs-h3); line-height: 1.4; letter-spacing: -0.004em;
  text-wrap: pretty;
  quotes: "\201C" "\201D";
  hanging-punctuation: first last;
}
/* The marks come from CSS, not the markup. Two reasons: they stay out of the text a reader
   copies, because they are punctuation ABOUT the quote rather than part of what was said;
   and open-quote/close-quote is a semantic pair the browser matches, not two loose glyphs.
   U+201C and U+201D were already in the subset's COVERED set (ops/subset-fonts.py) and had
   simply never been rendered, so this costs nothing in font bytes. The original reason to
   go without marks was that straight quotes would have been the only typewriter marks on
   the page; the proper curly pair was available the whole time.
   Gilt rather than ivory, and slightly smaller: at full size and full contrast Cormorant's
   quotes are heavy enough to read as glyphs in their own right and the eye lands on the
   mark before the sentence. Dropped back, they punctuate instead of announce.
   hanging-punctuation stops the opening mark pushing the first line optically right; it is
   Safari-only today, which is why the size and colour do the work and it is the bonus. */
.tq blockquote p::before { content: open-quote; }
.tq blockquote p::after  { content: close-quote; }
.tq blockquote p::before,
.tq blockquote p::after { color: var(--gilt); font-size: 0.88em; }
/* Attribution sits directly under the quote, the way an attribution has read since print:
   who said it, immediately after what they said. */
.tq figcaption { margin-top: var(--sp-4); display: flex; flex-direction: column; gap: var(--sp-1); }
.tq .q-who {
  font-family: var(--font-sans); font-size: var(--fs-label); letter-spacing: var(--tracking-sub);
  text-transform: uppercase; color: var(--champagne); font-weight: 700;
}
.tq .q-what { color: var(--sage); font-size: var(--fs-body-sm); line-height: var(--lh-prose); }
/* The Marketing Rule disclosure: current-client status, compensation, conflicts. It lives
   inside the <figcaption> so it is part of the quote's accessible caption rather than a
   loose sibling that only happens to follow it. "Clear and prominent" is a layout
   constraint, not a footnote: moving this to the footer, a tooltip or a modal breaks the
   rule even though the words are unchanged.
   --sage-dim on --green-900 measures 5.6:1, on --green-950 6.3:1, both past AA at this
   size. --sp-2 not --sp-3: it is a flex child now, so the column gap adds the other 4px. */
.tq .q-disc { margin-top: var(--sp-2); color: var(--sage-dim); font-size: var(--fs-ui); line-height: 1.5; }

/* Centred where the content above it is symmetrical, so the quote resolves against that
   axis instead of hanging off the left of a centred composition: #problem sits under the
   two compare panels, #team under the two portraits. Left everywhere else. */
.tq--center { margin-inline: auto; text-align: center; }
.tq--center figcaption { align-items: center; }
/* Quieter where the quote supports dense material rather than closing a section: beside
   the fee model, and beside the form where the primary CTA has to stay the loudest thing. */
.tq--sm blockquote p { font-size: var(--fs-h3-sm); line-height: 1.45; }
.tq--sm { margin-top: var(--sp-8); max-width: 46ch; }

/* The CTA is the one placement whose ground is a photograph under a gradient scrim, not
   flat green, so the composited background varies with the image. Measured off rendered
   pixels beside the text: median 6.04:1 but the photo's lightest regions drop --sage-dim
   to 3.62:1, under the 4.5:1 this size needs. axe-core cannot catch it (text over a
   background image reports as "incomplete", not a violation), so the gate stayed green
   while the one element that legally has to be readable was failing.
   --sage measures 5.24:1 against that same worst-case pixel. The quote itself was never at
   risk: 8.52:1 worst case against a 3:1 requirement. */
.cta-copy .tq .q-disc { color: var(--sage); }

/* ---------- FAQ (native disclosure, no JS) ---------- */
/* Left-aligned to the gutter so the questions sit under the section heading. */
.faq { max-width: 820px; border-top: 1px solid var(--line); }
/* Inert above 1000px (the head-aside column is narrower); governs the stacked layout. */
/* Sits between the list's top rule and the first question, right-aligned so it lands on
   the same axis as the summaries' plus marks. Inserted by main.js, so it costs nothing
   JS-off. Kept on the .status-action text idiom: no second button shape on the page. */
.faq-bar { display: flex; justify-content: flex-end; padding-top: var(--sp-3); }
.faq-bar .status-action { min-height: var(--tap); display: inline-flex; align-items: center; font-size: var(--fs-body-sm); }
.faq-item { border-bottom: 1px solid var(--line); }
.faq-item summary { list-style: none; cursor: pointer; display: flex; justify-content: space-between; align-items: center; gap: var(--sp-5); padding: var(--sp-5) 0; font-family: var(--font-display); font-weight: 600; font-size: var(--fs-h3-sm); color: var(--text); }
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after { content: ""; flex: none; width: 14px; height: 14px; background: no-repeat center / contain; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 14 14' fill='none' stroke='%23CCAA55' stroke-width='1.6' stroke-linecap='round'%3E%3Cpath d='M7 1.5v11M1.5 7h11'/%3E%3C/svg%3E"); transition: transform var(--dur-fast) var(--ease); }
.faq-item[open] summary::after { transform: rotate(45deg); }
.faq-item p { color: var(--sage); font-size: var(--fs-body); line-height: var(--lh-prose); padding: 0 0 var(--sp-5); max-width: 52ch; }
.faq-item summary:focus-visible { outline: 2px solid var(--champagne); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { .faq-item summary::after { transition: none; } }

/* ---------- closing promise band ---------- */
.promise { padding-block: clamp(3.5rem, var(--sp-7) + 4vw, 6rem); background: var(--green-950); border-top: 1px solid var(--line); text-align: center; }
/* The seal. Height only, width from the file, so the mark cannot be stretched
   by a rule written for another aspect. 120px is the ART's ceiling as much as
   a taste: the supplied monogram is raster at 168x232 and softens above ~120px,
   and below ~56px the interlocked S/F/O closes into a smudge. That floor is
   why the mark lives here and not in the 30px header slot.
   Gold alone on the deepest ground: the artwork's Deep Green strokes are the
   page's own background colour and would read as holes cut in the mark. This is
   the "large gold figure" the colour rules already allow, never small gold on
   green. */
.promise-mark { display: block; height: 120px; width: auto; margin: 0 auto var(--sp-5); }
@media (max-width: 600px) { .promise-mark { height: 92px; } }

.promise-lines { font-family: var(--font-display); font-weight: 500; font-size: var(--fs-h2); line-height: var(--lh-heading); color: var(--text); }
.promise-tag { margin-top: var(--sp-4); font-size: var(--fs-label); letter-spacing: var(--tracking-label); text-transform: uppercase; color: var(--champagne); font-weight: 700; }

/* ---------- footer legal links ---------- */
.foot-legal { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5); }
.foot-legal a { color: var(--sage-dim); font-size: var(--fs-label); display: inline-block; padding-block: 0.8rem; }
.foot-legal a:hover { color: var(--text-dim); }
address.foot-contact { font-style: normal; }

/* ---------- legal documents (privacy / terms) ---------- */
.legal-doc { padding-block: clamp(3rem, 2rem + 5vw, 6rem); }
.legal-doc .wrap { max-width: 760px; }
.legal-doc .display { font-size: clamp(2.2rem, 1.6rem + 2.5vw, 3.2rem); font-weight: 500; }
.legal-back { display: inline-block; margin-bottom: var(--sp-6); font-size: var(--fs-ui); letter-spacing: var(--tracking-tight); text-transform: uppercase; color: var(--champagne); }
.legal-back:hover { color: var(--text); }
.legal-meta { color: var(--sage-dim); font-size: var(--fs-ui); margin-top: var(--sp-2); }
.legal-doc h2.title { font-size: clamp(1.3rem, 1.1rem + 1vw, 1.7rem); margin-top: var(--sp-8); margin-bottom: var(--sp-3); }
.legal-doc p { color: var(--text-dim); max-width: 55ch; line-height: 1.7; margin-top: var(--sp-3); }
.legal-doc a { color: var(--champagne); text-decoration: underline; text-underline-offset: 3px; }
.legal-foot { margin-top: var(--sp-10); padding-top: var(--sp-5); border-top: 1px solid var(--line); color: var(--sage-dim); font-size: var(--fs-ui); }

/* ---------- mobile sticky CTA (persistent conversion on phones) ---------- */
.mobile-cta { display: none; }
@media (max-width: 1100px) {
  /* gated under html.js: with the JS that hides it contextually absent, the bar
     never shows, so no-JS users never see a CTA duplicated over the hero/contact ones */
  html.js .mobile-cta {
    display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 95; min-height: var(--cta-bar-h);
    padding: 0.85rem var(--sp-4); background: var(--gold); color: var(--ink);
    font-family: var(--font-sans); font-weight: 700; font-size: var(--fs-ui);
    letter-spacing: var(--tracking-cta); text-transform: uppercase; border-top: 1px solid var(--bullion);
    box-shadow: 0 -12px 30px -16px rgba(0,0,0,0.85);
    transition: transform var(--dur-fast) var(--ease), visibility 0s linear 0s;
   padding-bottom: calc(0.85rem + env(safe-area-inset-bottom, 0px)); }
  html.js body { padding-bottom: calc(var(--cta-bar-h) + env(safe-area-inset-bottom, 0px)); }
  /* Hidden means hidden: visibility + pointer-events off so a keyboard user cannot
     tab to a fixed bar that is translated off screen. The visibility flip waits for
     the slide-out; showing again is instant. */
  body.cta-near .mobile-cta,
  body.nav-open .mobile-cta {
    transform: translateY(100%);
    visibility: hidden; pointer-events: none;
    transition: transform var(--dur-fast) var(--ease), visibility 0s linear var(--dur-fast);
  }
}
@media (max-width: 380px) {
  .modules li { flex-direction: column; align-items: flex-start; gap: var(--sp-1); }
  .modules .m-price { text-align: left; white-space: normal; }
}
@media (prefers-reduced-motion: reduce) { .mobile-cta { transition: none; } }

/* ---------- back to top (every width) ---------- */
/* The page runs to roughly 22,000px at 375px wide and 13,982px at 1440, and nothing else
   is fixed, so a reader deep in the page has no way back. main.js reveals this one only on
   an upward scroll past two viewports, which is what keeps it from competing with the
   sticky bar on phones: a reader travelling down toward the CTA never sees a second
   control arrive beside it. The page has exactly one filled gold control, so this is a
   hairline-bordered dark disc with a champagne glyph, not a second button shape. The glyph
   is an inline SVG, not a font character: the fonts are subset and an arrow would ship as
   tofu. Same display: none base as .mobile-cta, so JS-off never shows it. */
.to-top { display: none; }
/* Ungated as of the run-6 fixes. It was bounded at 1100px to pair with the sticky bar, but
   the desktop page is 13,982px tall with no bar and no other way up, which is the same
   problem the control exists to solve. */
html.js .to-top {
  display: grid; place-items: center;
  /* Clear of both viewport edges by one gutter. Where the sticky bar exists the bottom
     offset is re-derived below; on desktop there is no bar to sit above. */
  position: fixed; right: var(--gutter); bottom: var(--gutter); z-index: 94;
  width: var(--tap); height: var(--tap); border-radius: 50%;
  background: var(--green-950); color: var(--champagne);
  border: 1px solid var(--line-strong); box-shadow: var(--shadow-pop);
  /* Hidden means hidden: visibility keeps it out of the tab order too, so the inert
     attribute main.js manages is the belt and this is the braces. */
  opacity: 0; visibility: hidden; pointer-events: none; transform: translateY(6px);
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease), visibility 0s linear var(--dur-fast);
}
/* Wherever .mobile-cta is on screen, sit 12px above its 52px bar instead. */
@media (max-width: 1100px) {
  html.js .to-top { bottom: calc(var(--cta-bar-h) + var(--sp-3) + env(safe-area-inset-bottom, 0px)); }
}
html.js body.show-top:not(.cta-near):not(.nav-open) .to-top {
  opacity: 1; visibility: visible; pointer-events: auto; transform: none;
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease), visibility 0s linear 0s;
}
html.js .to-top:hover { border-color: var(--line-gold); }
@media (prefers-reduced-motion: reduce) { .to-top { transition: none; } }
