/* Growth Bridge Partners. Tokens and rules from brief/handoff/handoff.md. */

/* Self-hosted Cormorant Garamond and Inter (SIL Open Font License 1.1, text in assets/fonts/OFL.txt).
   These replace the Google Fonts <link>, which transmitted every visitor's IP address to Google and
   was the site's only third-party request.
   Latin only: a scan of the built pages found no character outside ASCII and nothing in U+0100-024F,
   so the latin-ext subsets are deliberately not shipped. Copy that needs accented characters has to
   bring them back.
   One file per style, not one per weight. Google serves a single variable font for a family's
   upright weights, so the files it returned for 400, 500 and 600 were byte-identical (one md5 for
   the three Cormorant uprights, one for the three Inters). Shipping them under three names each
   would have the browser fetch the same bytes up to three times -- 244 KB of fonts on the Home
   first screen instead of 110 KB, which the 400 KB budget at 375px has no room for -- so each face
   below points at its one file and declares the weight range that file covers. */
@font-face{font-family:"Cormorant Garamond";font-style:normal;font-weight:400 600;font-display:swap;src:url(assets/fonts/CormorantGaramond-latin.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:"Cormorant Garamond";font-style:italic;font-weight:500;font-display:swap;src:url(assets/fonts/CormorantGaramond-italic-latin.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:"Inter";font-style:normal;font-weight:400 700;font-display:swap;src:url(assets/fonts/Inter-latin.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}

/* Metric-matched local fallbacks so text barely moves when the web fonts arrive (layout-shift budget < 0.05).
   Round 10. Georgia and Arial ship on macOS and Windows, and most Linux distributions have neither,
   so on Linux each src list resolved to nothing at all: the rule matched no local face, the
   metric-matched family was never constructed, and the layout-shift protection check_weight's CLS
   budget relies on was simply absent on the one platform that cannot be tested from here. Liberation
   and DejaVu are the two families a desktop Linux install almost always carries, and Liberation
   exists precisely to be metrically compatible with Times New Roman and Arial, which is why it is
   the better of the two. Georgia and Arial stay FIRST, so macOS and Windows resolve exactly as they
   did and nothing on those two platforms changes.
   The size-adjust and *-override values below were tuned against the FIRST face in each list and are
   therefore approximate for the others -- a close metric match on Linux rather than an exact one,
   which is still much better than no matched fallback at all.
   Safari did not support these four descriptors until 17.0 (Firefox 89). Below that the fallback
   still resolves to a real local face and the text is correct; only the metric adjustment is
   ignored, so the protection is reduced rather than inverted. */
@font-face{font-family:"Cormorant Fallback";src:local("Georgia"),local("Liberation Serif"),local("DejaVu Serif");size-adjust:90%;ascent-override:95%;descent-override:30%;line-gap-override:0%}
@font-face{font-family:"Inter Fallback";src:local("Arial"),local("Liberation Sans"),local("DejaVu Sans");size-adjust:107%;ascent-override:90%;descent-override:22%;line-gap-override:0%}

/* ---------- tokens ---------- */
:root{
  --obsidian:#1A1D20;--ink:#2B2D42;--navy:#102A43;--atlantic:#1E3A5F;--moss:#4A5D4E;--gold:#B58A3B;--line:#E5E5E5;--error-dark:#E8B4A6;
  /* --gold is used only on obsidian and navy. On the warm light grounds, gold is --gold-deep: on ivory
     --gold measures 2.78:1 and the old --gold-light 2.90:1, both under the 3:1 display minimum, while
     --gold-deep is 3.77:1. --gold-light was deleted on 2026-09-16 when its last use, the Stewardship
     ring's stroke, moved to gold-deep. Do not reintroduce it. */
  /* --error-dark: form error text and rules on obsidian (9.2:1); the handoff defines no error color */
  /* Warm grounds (2026-09-15 redesign): ivory is the page background AND the light tone on every dark
     ground — buttons, drawer, dark footer and the Contact fields included. The cool alabaster white the
     rebuild started from is gone: two whites on one band is the defect this redesign set out to fix. */
  --ivory:#F8F0E0;--ivory-hi:#FCF6EA;--sand:#EEDFC6;--card:#FBF4E6;--card-dark:#1E2226;
  /* Client review 2026-09-19: "Words in color code f8f0e0 all need to be changed to #FFFFFF".
     --ivory stays as a GROUND -- the page background, .btn--white's fill, the scrolled header bar --
     and only its TEXT uses moved here, so this is a second white on the site by deliberate client
     instruction; the 2026-09-16 rule against reintroducing one is overridden, not forgotten.
     Measured before building: it RAISES text contrast on dark grounds (ivory 14.94:1 -> white
     16.93:1 on obsidian, 12.92 -> 14.64 on navy), which runs against the same review's "blinding /
     gave them a headache" note. Flagged to the client, then built as directed. */
  --white:#FFFFFF;
  /* Accents measured on the warm grounds: gold-deep 3.77:1 on ivory, 3.26:1 on sand (display sizes only);
     umber 5.02:1 on sand for small labels; parchment 10.08:1 on obsidian for small labels on dark. */
  --gold-deep:#9A742E;--umber:#6E5A3C;--parchment:#D6C6A8;
  /* Round 2 item 5b: the Consulting pillar's surface. A desaturated step between --card #FBF4E6 and
     --sand #EEDFC6, biased warm rather than cool, so the card reads as a distinct light grey against
     the ivory band behind it instead of vanishing into it. Navy 11.93:1, ink at 78% 5.89:1 and umber
     5.36:1 on it -- the three pairs it introduces, all in PAIRS. */
  --greige:#EFE7DA;
  /* Round 2 item 9c: the About hero's deeper ground. That page opened ivory type on ivory beside a
     pale concrete photograph -- "the white is giving white out" -- so its hero runs sand to this step
     rather than ivory to sand. It is the darkest tone that hero paints, so it is the worst case for
     everything on it: navy 10.62:1, ink at 78% 5.47:1, and the 11px umber micro-line 4.77:1. */
  --dune:#E9DABD;
  /* Round 2 item 10: the Contact page's two grounds. It was one near-black field from header to
     footer; it now opens on --night and lifts to --slate behind the route cards. --slate is the
     lightest ground the page paints and so the worst case for the light text on it (ivory 13.15:1,
     ivory at 78% 8.56:1, parchment 8.87:1); every intermediate gradient stop sits between these two
     in luminance, so the registered pairs bound them all. */
  --night:#0D141C;--slate:#1F2833;
  --rule:rgba(181,138,59,.38);
  --lift:0 14px 26px -18px rgba(60,42,20,.30),0 1px 3px rgba(60,42,20,.08);
  --lift-hover:0 20px 34px -20px rgba(60,42,20,.36),0 2px 5px rgba(60,42,20,.10);
  --radius:3px;
  --hair-dark:rgba(248,240,224,.22);
  --font-display:"Cormorant Garamond","Cormorant Fallback",Georgia,serif;--font-ui:"Inter","Inter Fallback",Arial,sans-serif;
  --container:1440px;--gutter:64px;--header:80px;
  --section:clamp(40px,6vw,88px);
  --ease:cubic-bezier(.22,1,.36,1);--t:200ms;
  --bg:var(--ivory);--fg:var(--ink);--fg-head:var(--navy);--fg-soft:rgba(43,45,66,.78);--hair:var(--line);--focus:var(--atlantic);
}
@media (max-width:1023px){:root{--gutter:32px}}
@media (max-width:767px){:root{--gutter:20px;--header:72px}}
/* Taller header on desktop only (round 2, item 2): 88px from 1024px up, 80px on tablet, 72px on
   phones. Everything that offsets against the fixed header reads --header, so all of it follows:
   .site-header's own height, .drawer__top, the Home hero's top padding, .hero-full__stack's top,
   .split--hero's padding-top and its grid min-height, and .contact-hero's top padding. The sticky
   .steward__stage deliberately does NOT offset by --header (see its own note above that rule), and
   site.js's scroll handlers key off scrollY and innerHeight, not the header, so neither moves. */
@media (min-width:1024px){:root{--header:88px}}
.dark,body.theme-dark{--bg:var(--obsidian);--fg:var(--white);--fg-head:var(--white);--fg-soft:rgba(255,255,255,.78);--hair:var(--hair-dark);--focus:var(--ivory);background:var(--bg);color:var(--fg)}

/* ---------- base ---------- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
html.is-ready{scroll-behavior:smooth}   /* only after site.js has put the page at the top, so that jump is never animated */
body{margin:0;background:var(--linen) 0 0/120px 120px repeat,var(--grain) 0 0/180px 180px repeat,var(--bg);background-attachment:fixed,fixed,scroll;color:var(--fg);font-family:var(--font-ui);font-size:clamp(16px,1.1vw,18px);line-height:1.6;-webkit-font-smoothing:antialiased}
img,video,svg{display:block;max-width:100%}
picture{display:contents}   /* the <img> keeps its own class and box; the wrapper adds none */
a{color:inherit}
h1,h2,h3,p,ul,figure{margin:0}
ul{padding:0;list-style:none}
button{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer}
:focus-visible{outline:2px solid var(--focus);outline-offset:4px}
.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.container{max-width:var(--container);margin-inline:auto;padding-inline:var(--gutter)}
.section{padding-block:var(--section)}
/* 12-column grid shared by every section; items never force their track wider than the column */
:root{--col-gap:24px}
@media (max-width:767px){:root{--col-gap:16px}}
.grid-12{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));column-gap:var(--col-gap)}
.grid-12 > *{min-width:0}

/* ---------- bands, grain, cards, reveal (2026-09-15 redesign) ---------- */
/* Paper grain: 180px fractal noise at 5% as an SVG data URI (under 1 KB), layered over the light gradients. */
/* Linen weave, client review 2026-09-19: "The Beige background on the site i would like it to be a
   linen texture", with a reference photograph of linen-finish cardstock (cream stock, #FAE8D2, a fine
   vertical warp with a fainter horizontal weft). Built to that reference rather than to a stock noise:
   a 120px tile of anisotropic fractal noise -- high frequency across the warp and low along it,
   so the grain runs vertically like thread, with a fainter pass rotated for the weft. A hard 4px line
   grid was built first and rejected: at both 1x and 4x it read as graph paper, not cloth. It layers OVER --grain, which stays -- grain is paper fibre, this is
   the weave, and the reference shows both. No palette change: the reference cream sits between --ivory
   #F8F0E0 and --card #FBF4E6, so the client asked for texture, not a new colour.
   --linen-dark is the same weave in ivory thread for Contact, whose note was "there is no texture and
   it does not align with the rest of the site". This is also the one request in that review that
   actually answers "blinding": texture breaks a flat bright field and cuts perceived glare without
   moving a single contrast ratio. */
:root{--linen:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='w'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9 .02' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .43 0 0 0 0 .35 0 0 0 0 .24 0 0 0 1 0'/%3E%3C/filter%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.02 .9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .43 0 0 0 0 .35 0 0 0 0 .24 0 0 0 1 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23w)' opacity='.18'/%3E%3Crect width='100%25' height='100%25' filter='url(%23f)' opacity='.095'/%3E%3C/svg%3E");--linen-dark:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='w'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9 .02' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .97 0 0 0 0 .94 0 0 0 0 .88 0 0 0 1 0'/%3E%3C/filter%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.02 .9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .97 0 0 0 0 .94 0 0 0 0 .88 0 0 0 1 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23w)' opacity='.11'/%3E%3Crect width='100%25' height='100%25' filter='url(%23f)' opacity='.055'/%3E%3C/svg%3E");}
:root{--grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .24 0 0 0 0 .17 0 0 0 0 .08 0 0 0 .9 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.05'/%3E%3C/svg%3E")}
.band-ivory{background:var(--linen) 0 0/120px 120px repeat,var(--grain) 0 0/180px 180px repeat,linear-gradient(180deg,var(--ivory) 0%,var(--sand) 100%)}
.band-sand{background:var(--linen) 0 0/120px 120px repeat,var(--grain) 0 0/180px 180px repeat,linear-gradient(180deg,var(--sand) 0%,var(--ivory) 100%)}
/* Round 8. The darker warm ground, and the only band that descends instead of brightening: sand at
   the top, dune at the bottom. It exists because the Consulting focus areas were 900px -- 40% of
   that page -- of the brightest surface on the site, .band-sand running up into near-white ivory
   exactly where the section is emptiest. Deepening the same two-token warm range puts the heading on
   the lighter half and the card grid on the deeper one, which is the tonal change that section had
   none of, while the cards' own --card surface supplies the contrast. No new token: sand and dune
   are the About hero's pair, so the page's warm range is unchanged, only its direction. */
.band-dune{background:var(--linen) 0 0/120px 120px repeat,var(--grain) 0 0/180px 180px repeat,linear-gradient(180deg,var(--sand) 0%,var(--dune) 100%)}
.band-dark,.band-navy{--bg:var(--obsidian);--fg:var(--white);--fg-head:var(--white);--fg-soft:rgba(255,255,255,.78);--hair:var(--hair-dark);--focus:var(--ivory);color:var(--fg)}
.band-dark{background:linear-gradient(115deg,var(--navy) 0%,var(--obsidian) 70%)}
.band-navy{background:linear-gradient(180deg,#14304C 0%,var(--navy) 55%,#0D2033 100%)}
.band-dark .eyebrow,.band-navy .eyebrow{color:var(--gold)}

/* The client asked for the linen on "the Beige background on the site". The bands carry it, but every
   beige surface that sits ON a band overrides .card's background with its own, so the cards, the proof
   strip, the light pillar and the draft notice stayed flat against a textured ground. They carry it
   too now. .card--dark is unaffected: it replaces the background shorthand outright and is a dark
   surface, not a beige one. Buttons, their hover states and the back-to-top control are deliberately
   left flat -- a weave on a 48px control is noise, not texture. */
.card{background:var(--linen) 0 0/120px 120px repeat,var(--card);border-radius:var(--radius);box-shadow:var(--lift)}
/* A dark card is a dark context in its own right, so it sets the text variables its own surface
   implies: its headline and body read warm like the surface, and nothing depends on declaration
   order. (The Home pillar used to carry both `dark` and `card--dark`. Both are specificity (0,1,0),
   so the warm surface won only because `.card--dark` is written later, while every descendant
   reading --fg-head/--fg-soft still took the cool values from `.dark`: a warm card with cool text.
   --hair and --focus are set here too, so dropping `dark` from the pillar loses nothing.) */
/* --bg is part of that set, and leaving it out was a real hole. `.btn--outline:hover` resolves to
   `background:var(--fg); color:var(--bg)`, so both halves of that inversion are read from whatever
   context the button sits in. A .card--dark on a light band inherited --bg from :root, i.e. --ivory,
   while setting --fg to --ivory itself: measured on the Home pillar (a .card--dark on .band-ivory)
   both custom properties resolved to #F8F0E0, so a .btn--outline placed there would hover to ivory
   text on an ivory fill -- 1.0:1, invisible. Latent only because no outline button sits in a dark
   card on a light band today. A dark card's --bg is its own surface, so it is set here with the
   rest; check_states now measures both hover inversions as painted pixels on every page. */
.card--dark{--bg:var(--card-dark);--fg:var(--white);--fg-head:var(--white);--fg-soft:rgba(255,255,255,.78);--hair:var(--hair-dark);--focus:var(--ivory);background:var(--card-dark);color:var(--white);border:1px solid rgba(248,240,224,.14)}
.card--lift{transition:transform var(--t) ease,box-shadow var(--t) ease}
@media (hover:hover) and (prefers-reduced-motion:no-preference){
  .card--lift:hover,.card--lift:focus-within{transform:translateY(-3px);box-shadow:var(--lift-hover)}
}

/* Reveal only applies once html.js is set in <head>, so without JavaScript every block is visible. */
.js [data-reveal]{opacity:0;transform:translateY(12px);transition:opacity 500ms var(--ease),transform 500ms var(--ease)}
.js [data-reveal].is-revealed{opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){.js [data-reveal]{opacity:1;transform:none;transition:none}}
main{display:block}

/* ---------- type ---------- */
.display-xl,.h1,.h2,.h3{font-family:var(--font-display);font-weight:500;color:var(--fg-head);letter-spacing:-.01em}
.display-xl{font-size:clamp(52px,6.8vw,108px);line-height:.98}
.h1{font-size:clamp(48px,5.6vw,88px);line-height:1.02}
.h2{font-size:clamp(38px,4vw,64px);line-height:1.05}
.h3{font-size:clamp(28px,2.6vw,40px);line-height:1.1}
.lead{font-family:var(--font-display);font-weight:400;font-size:clamp(18px,1.5vw,24px);line-height:1.4;max-width:60ch}
.body{max-width:62ch;color:var(--fg-soft)}
.eyebrow{font-family:var(--font-ui);font-weight:600;font-size:clamp(10px,.8vw,12px);letter-spacing:.18em;text-transform:uppercase;line-height:1.4}
.eyebrow--rule::before{content:"";display:inline-block;width:28px;height:1px;background:currentColor;margin-right:14px;vertical-align:middle;opacity:.7}
.eyebrow--rule-both{display:flex;align-items:center;justify-content:center;gap:14px}
.eyebrow--rule-both::before,.eyebrow--rule-both::after{content:"";width:28px;height:1px;background:currentColor;opacity:.7}
.micro{font-family:var(--font-ui);font-weight:600;font-size:11px;letter-spacing:.2em;text-transform:uppercase}
.tagline{font-family:var(--font-display);font-style:italic;font-weight:500;font-size:clamp(22px,1.8vw,28px);color:var(--gold)}
.em{font-style:italic}
.word-stack{font-family:var(--font-ui);font-weight:500;font-size:11px;letter-spacing:.22em;text-transform:uppercase;line-height:2.1}
.word-stack::after{content:"";display:block;width:36px;height:1px;background:currentColor;margin-top:14px;opacity:.8}
.word-stack--gold::after{background:var(--gold);opacity:1}
/* The only .metallic elements on the site are the three proof numbers, so the gradient lives with
   them at `.proof .metallic` below; a base gradient here was never reachable. */

/* ---------- buttons ---------- */
.btn{display:inline-flex;align-items:center;gap:14px;min-height:48px;padding:0 28px;font-family:var(--font-ui);font-weight:600;font-size:11px;letter-spacing:.16em;text-transform:uppercase;text-decoration:none;border:1px solid transparent;border-radius:0;white-space:nowrap;transition:background-color var(--t) ease,color var(--t) ease,border-color var(--t) ease}
.btn__arrow{display:inline-block;transition:transform var(--t) ease}
.btn:hover .btn__arrow{transform:translateX(3px)}
/* Controls carry the warm light tone, not a second, cooler white: ivory on atlantic 10.15:1,
   ivory on obsidian 14.94:1, and both hover inversions measure the same (contrast is symmetric). */
.btn--atlantic{background:var(--atlantic);color:var(--white);border-color:var(--atlantic)}
.btn--atlantic:hover{background:var(--ivory);color:var(--atlantic)}
.btn--obsidian{background:var(--obsidian);color:var(--white);border-color:var(--obsidian)}
.btn--obsidian:hover{background:var(--ivory);color:var(--obsidian)}
.btn--white{background:var(--ivory);color:var(--obsidian);border-color:var(--ivory)}
.btn--white:hover{background:transparent;color:var(--white)}
.btn--outline{background:transparent;color:inherit;border-color:currentColor}
.btn--outline:hover{background:var(--fg);color:var(--bg)}
.btn:disabled{opacity:.6;cursor:default}
@media (max-width:767px){.btn{width:100%;justify-content:center;text-align:center;white-space:normal;padding:12px 20px}}
.btn-pair{display:inline-grid;grid-template-columns:repeat(2,minmax(256px,1fr));gap:16px}
.btn-pair .btn{justify-content:center}
@media (max-width:767px){.btn-pair{display:grid;grid-template-columns:1fr;width:100%}}

/* ---------- header ---------- */
.site-header{position:fixed;inset-inline:0;top:0;z-index:50;height:var(--header);color:var(--ink);transition:background-color var(--t) ease}
.site-header::before{content:"";position:absolute;left:0;right:0;top:0;height:160%;z-index:-1;pointer-events:none;background:linear-gradient(180deg,rgba(248,240,224,.92) 0%,rgba(248,240,224,.86) 55%,rgba(248,240,224,0) 100%);transition:opacity var(--t) ease}
.site-header.on-dark::before{background:linear-gradient(180deg,rgba(26,29,32,.9) 0%,rgba(26,29,32,.85) 55%,rgba(26,29,32,0) 100%)}
.site-header.is-scrolled::before{opacity:0}
.site-header.on-dark{color:var(--white)}
.site-header.is-scrolled{background:var(--linen) 0 0/120px 120px repeat,var(--ivory);box-shadow:0 1px 0 var(--rule)}
.site-header.on-dark.is-scrolled{background:var(--obsidian);box-shadow:0 1px 0 var(--hair-dark)}
.site-header__inner{height:100%;display:flex;align-items:center;justify-content:space-between}
.lockup{display:inline-flex;align-items:center;gap:12px;text-decoration:none;color:inherit}
.lockup__mark{font-family:var(--font-display);font-weight:500;font-size:34px;line-height:1;letter-spacing:-.02em}
.lockup__rule{width:1px;height:34px;background:currentColor;opacity:.6}
.lockup__name{font-family:var(--font-ui);font-weight:600;font-size:9px;letter-spacing:.18em;line-height:1.35;text-transform:uppercase}
/* The lockup grows with the taller desktop header (~12%), so mark, rule and wordmark keep the same
   proportions to each other and to the bar they sit in. Desktop only, like --header itself. */
@media (min-width:1024px){
  .lockup{gap:13px}
  .lockup__mark{font-size:38px}
  .lockup__rule{height:38px}
  .lockup__name{font-size:10px}
}
.site-nav ul{display:flex;gap:36px}
/* Client review 2026-09-19: header/menu font "slightly bigger" (11px -> 13px, .menu-btn follows),
   and the hover colour set to B58A3B. Measured: gold hovers at 5.37:1 on the dark headers (Home,
   Brokerage, Consulting, Contact) and at 2.78:1 on the three light-header pages (About, Privacy,
   Terms), under the 4.5 floor -- CLAUDE.md records that number as the reason gold-on-light is
   normally gold-deep. The client asked for B58A3B specifically, so B58A3B is what is painted. */
.site-nav a{position:relative;display:inline-block;padding:6px 0;font-family:var(--font-ui);font-weight:600;font-size:13px;letter-spacing:.16em;text-transform:uppercase;text-decoration:none;opacity:.8;transition:opacity var(--t) ease,color var(--t) ease}
.site-nav a:hover{opacity:1;color:var(--gold)}
/* The client asked for a B58A3B hover. It measures 5.37:1 on the five dark headers and 2.78:1 on the
   two light ones -- Privacy and Terms -- against the 4.5 floor for 13px nav text. --gold-deep does not
   rescue it either: 3.77:1, because gold-deep is a 3.0 display-size token, not a small-text one. The
   nearest colour that clears the floor without inventing one is --umber at 5.81:1, and umber is
   already this site's warm accent on light grounds -- the track, difference, focus and About eyebrows
   all take it. So the hover is B58A3B everywhere the nav is actually used, and umber on the two
   footer-only legal pages. check_tokens asserts the pair, so it cannot silently regress. */
.site-header:not(.on-dark) .site-nav a:hover{color:var(--umber)}
.site-nav a::after{content:"";position:absolute;left:50%;bottom:0;width:0;height:1px;background:currentColor;transform:translateX(-50%);transition:width var(--t) ease}
.site-nav a[aria-current="page"]{opacity:1}
.site-nav a[aria-current="page"]::after{width:100%}
.menu-btn{display:none;min-height:48px;padding:0 4px;font-family:var(--font-ui);font-weight:600;font-size:13px;letter-spacing:.16em;text-transform:uppercase}
@media (max-width:1023px){.site-nav{display:none}.menu-btn{display:inline-flex;align-items:center}}
.drawer .menu-btn{display:inline-flex}
.drawer{position:fixed;inset:0;z-index:60;background:var(--obsidian);color:var(--white);padding-inline:var(--gutter);overflow:auto}
.drawer[hidden]{display:none}
.drawer__top{display:flex;justify-content:space-between;align-items:center;height:var(--header)}
.drawer__nav{display:flex;flex-direction:column;gap:28px;padding-top:48px}
.drawer__nav a{font-family:var(--font-display);font-size:36px;font-weight:500;text-decoration:none;line-height:1}
.drawer__nav a[aria-current="page"]{color:var(--gold)}

/* ---------- footer ---------- */
.site-footer{border-top:1px solid var(--rule);padding-block:40px 32px}   /* gold hairline on the ivory footer */
.site-footer--dark{background:var(--obsidian);color:var(--white);border-top-color:var(--hair-dark);--hair:var(--hair-dark)}
.site-footer__row{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:24px 40px}
.site-footer__nav ul{display:flex;flex-wrap:wrap;gap:28px}
/* Client review 2026-09-19: "Bolder Text. Leave the copyright, privacy and terms text as is."
   So the footer nav and the tagline take more weight and the opacity comes off the nav links;
   .site-footer__legal below is untouched, which is the line carrying copyright, Privacy and Terms. */
.site-footer__nav a{display:inline-block;font-family:var(--font-ui);font-weight:700;font-size:12px;letter-spacing:.16em;text-transform:uppercase;text-decoration:none;opacity:1}
@media (max-width:767px){.site-footer__nav a,.site-footer__legal a{padding:16px 0}.lockup{min-height:48px}}
.site-footer__nav a:hover{opacity:1}
.site-footer__tag{font-family:var(--font-display);font-style:italic;font-weight:600;font-size:20px}
.site-footer__legal{display:flex;justify-content:space-between;flex-wrap:wrap;gap:16px;margin-top:40px;font-size:12px;opacity:.75}
.site-footer__legal p:last-child{display:flex;gap:20px}
.site-footer__legal a{display:inline-block;text-decoration:none}
/* Client review on a real iPhone, 2026-09-21: "the footer is not vertically aligned". The two-column
   grid below took five links and produced 2 + 2 + 1, orphaning CONTACT on its own row with a ragged
   right edge and uneven column heights. Five items do not divide by two. One column reads as a list,
   aligns on a single left edge, and needs no arithmetic to look deliberate. .site-footer__legal is
   untouched, as the same client asked in the written review. */
@media (max-width:767px){.site-footer__row{flex-direction:column;align-items:flex-start}.site-footer__nav ul{display:grid;grid-template-columns:1fr;column-gap:0;row-gap:0}}

/* ---------- home hero ---------- */
.hero-home{padding-block:calc(var(--header) + 56px) 108px}
.hero-home__grid{align-items:center;row-gap:48px}
.hero-home__copy{grid-column:1 / span 6}
.hero-home__copy > * + *{margin-top:26px}
.hero-home__copy .lead{color:var(--fg)}
.hero-home__media{grid-column:7 / -1;position:relative;margin:0;overflow:hidden;aspect-ratio:4/3;box-shadow:0 30px 50px -28px rgba(0,0,0,.6)}
.hero-home__media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* The dark band feathers into the photograph instead of meeting it at a cut line. Each gradient
   starts at the tone the band actually paints immediately outside that edge -- sampled from a
   screenshot at 1440 and 375, ~#16202A to the left and ~#171E26 above -- and falls to transparent
   across the first ~12% of the width and ~10% of the height, so the picture emerges from the ground.
   Left and top only: those are the edges that meet the band and the header. It tints nothing but the
   photo's own left and top margin, where no text sits, so contrast over the image is unchanged. */
.hero-home__media::after{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;
  background:linear-gradient(90deg,#16202A 0%,rgba(22,32,42,.72) 4%,rgba(22,32,42,0) 12%),
             linear-gradient(180deg,#171E26 0%,rgba(23,30,38,.70) 3%,rgba(23,30,38,0) 10%)}
.hero-home__stack{position:absolute;right:20px;bottom:20px;z-index:3;margin:0;background:rgba(26,29,32,.72);padding:14px 16px;color:var(--white)}
@media (max-width:1023px){.hero-home__copy,.hero-home__media{grid-column:1 / -1}.hero-home{padding-bottom:84px}}

/* ---------- proof strip: an ivory card overlapping the hero's bottom edge ---------- */
.proof{position:relative;z-index:2;margin-top:-44px}
.proof__card{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));padding:36px 24px;color:var(--navy);background:var(--linen) 0 0/120px 120px repeat,radial-gradient(120% 160% at 50% 0%,var(--ivory-hi) 0%,#F3E6CF 100%)}
.proof__cell{text-align:center;padding:8px 24px}
.proof__cell + .proof__cell{border-left:1px solid var(--rule)}
/* line-height 1 cut the old-style figures: background-clip:text paints only inside the box, and
   Cormorant's descending 3 and $ reach below a 1.0 line box. 1.2 plus a little bottom padding keeps
   every glyph inside the painted area; the numbers check screenshots it at 1440 and 375. */
.proof__num{font-family:var(--font-display);font-weight:500;font-size:clamp(56px,6vw,96px);line-height:1.2;padding-bottom:.1em}
/* On the ivory card the gradient runs through gold-deep (3.45:1 on the card's darkest tone), not the lighter alabaster-era stops. */
.proof .metallic{color:var(--gold-deep)}
@supports ((background-clip:text) or (-webkit-background-clip:text)){
  .proof .metallic{background:linear-gradient(105deg,#7E5C24 0%,#9A742E 45%,#8C672A 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
}
.proof__label{margin-top:10px;font-family:var(--font-ui);font-weight:600;font-size:11px;letter-spacing:.2em;text-transform:uppercase}
.proof__sub{margin-top:6px;font-family:var(--font-display);font-size:clamp(18px,1.4vw,22px)}
@media (max-width:767px){.proof{margin-top:-28px}.proof__card{grid-template-columns:1fr;padding:8px 20px}.proof__cell{padding:20px 8px}.proof__cell + .proof__cell{border-left:0;border-top:1px solid var(--rule)}}

/* ---------- pillars (home): two raised cards on the 12-column grid ---------- */
.pillars__grid{row-gap:var(--col-gap)}
.pillar{grid-column:span 6;position:relative;overflow:hidden;min-height:520px;display:flex}
.pillar__img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.pillar--dark::before{content:"";position:absolute;inset:0;z-index:1;background:linear-gradient(90deg,rgba(22,25,28,.96) 0%,rgba(22,25,28,.86) 48%,rgba(22,25,28,.35) 100%)}
/* The greige surface (item 5b), and the 1px border that fixes item 5a. The client reported the two
   cards' buttons and footers not lining up. Measured at 1440 and 1920, the outer boxes were already
   identical to the pixel, but the dark card carries `border:1px solid` from .card--dark and this one
   carried none, so its content box was inset 1px on every side and its bottom-aligned foot, button
   and tags all sat exactly 1px higher, with the copy starting 1px further in (45px against 44px).
   Matching the border makes both content boxes identical, which is the real fix; it also gives the
   greige card a warm hairline edge against the ivory band. */
.pillar--light{background:var(--linen) 0 0/120px 120px repeat,var(--greige);border:1px solid rgba(110,90,60,.16)}
.pillar--light .pillar__img{left:auto;width:46%}
.pillar--light::before{content:"";position:absolute;inset:0;z-index:1;background:linear-gradient(90deg,var(--greige) 0%,var(--greige) 56%,rgba(239,231,218,.9) 66%,rgba(239,231,218,0) 82%)}
.pillar__body{position:relative;z-index:1;display:flex;flex-direction:column;gap:18px;width:100%;max-width:560px;padding:48px 44px}
/* Round 6. `max-width:40ch` on its own is an absolute measure sitting inside a scrim specified as
   percentages of a card that shrinks, so the copy and the scrim behind it moved independently. At
   1024 the same 40ch filled 89.7% of the card, pushing the last words past the point where
   .pillar--light::before is fully transparent -- 1.20:1 against a 4.5 floor, body text very nearly
   invisible on the photograph. Above 1440 the container caps at --container so the card stops
   growing while the body font keeps climbing to 18px, so 40ch gets wider again and the copy pushes
   back out: 2.01:1 at 1700 and 1920. Both ends of the range failed for the same reason from opposite
   directions, and it was legible only in a narrow band around 1440 -- the one width the legibility
   check used to sample before round 5 widened it.
   The measure now scales with the card instead: cqw is a percentage of .pillar's own inline size, so
   the copy stops at the same fraction of the card at every width rather than after the same number
   of characters. The 44px is .pillar__body's left padding, so each cap reads as "stop at this
   fraction of the card" directly; the rule is scoped to 1024 and up, where that padding is 44px and
   where these two gradients are painted at all (below 1024 both scrims are flat fills and the copy
   already sat on solid ground). The two cards take different fractions because they have different
   scrims: the dark one grades from .96 to .35 across the whole card, the light one is solid to 56%
   and fully transparent by 82%. 40ch still wins wherever it is the smaller of the two, so this only
   ever shortens a line, never lengthens one.
   Each fraction is set above that pillar's own longest line at 1440 -- 353px dark and 394.4px light,
   against caps of 360.5px and 399.0px -- so the approved 1440 rendering keeps its exact line breaks:
   a smaller max-width can only push words down, never pull them up. Measured across 1024, 1280,
   1366, 1440, 1500, 1700 and 1920 after the change, the worst of the two bodies is 5.23:1. */
.pillar__body .body{max-width:40ch}
/* Round 10, the fallback. The cqw rules below are gated on @supports, and everything outside that
   gate exists for the engines that fail it: Safari before 16 and Firefox before 110 do not know cqw,
   so for them the two declarations above were INVALID and were discarded -- dropping the copy back to
   the bare `max-width:40ch` that measured 1.20:1 at 1024 and 2.01:1 at 1920, which is exactly the
   defect the round-6 note above describes. A Chrome-only test suite cannot see that, because Chrome
   has had container queries since 105.
   The fallback is not an approximation. .pillar is a span-6 item on the 12-column grid inside
   .container (max-width 1440px, 64px gutters, 24px column gap), so its border box is
   (min(100vw,1440px) - 128px)/2 - 12px, and because it carries a 1px border its CONTENT box -- which
   is what cqw is a percentage of -- is 2px less than that: 0.5vw - 78px up to 1440, and a flat 642px
   beyond it, where .container stops growing. Substituting that into the same "fraction of the card"
   caps reproduces the container-query values exactly (at 1024, 0.63 x 434 - 44 = 229.42px, which is
   the measured cqw result to the hundredth; at 1440, 0.63 x 642 - 44 = 360.46px).
   Two adjustments make it safe rather than merely equal. 6px is held back below 1440 because 100vw
   includes a classic scrollbar and the container's width does not, so on Windows and Linux Firefox
   the vw form would otherwise resolve about 6px wider than the true card; a slightly tighter cap can
   only pull a word down to the next line, while a looser one would push it back out onto the
   photograph, so the error is deliberately taken on the safe side. And above 1440 the card stops
   growing while vw does not, so the cap becomes the constant it actually is -- without that branch
   40ch would win again at 1700 and 1920, which is the upper half of the original defect. */
@media (min-width:1024px){
  .pillar--dark .pillar__body .body{max-width:min(40ch,calc(31.5vw - 99px))}
  .pillar--light .pillar__body .body{max-width:min(40ch,calc(34.5vw - 104px))}
}
@media (min-width:1440px){
  .pillar--dark .pillar__body .body{max-width:min(40ch,360px)}
  .pillar--light .pillar__body .body{max-width:min(40ch,398px)}
}
/* The container-query path, unchanged, for the engines that have it. It comes last so it wins on
   equal specificity, which is what keeps the approved 1440 rendering byte-identical in Chrome. The
   guard tests the UNIT as well as the property: a browser that knew container-type but not cqw would
   otherwise take this branch and discard the declarations, which is the failure being fixed. */
@supports (container-type:inline-size) and (width:1cqw){
  @media (min-width:1024px){
    .pillar{container-type:inline-size}
    .pillar--dark .pillar__body .body{max-width:min(40ch,calc(63cqw - 44px))}
    .pillar--light .pillar__body .body{max-width:min(40ch,calc(69cqw - 44px))}
  }
}
.pillar--light .eyebrow{color:var(--umber)}
/* The foot is pushed to the bottom of equal-height cards, so both buttons end on the same line. */
.pillar__foot{margin-top:auto;display:flex;flex-direction:column;align-items:flex-start;gap:22px;padding-top:12px}
.pillar .btn{min-width:256px;justify-content:center}
.tags{display:flex;flex-wrap:wrap;row-gap:8px;font-family:var(--font-ui);font-weight:600;font-size:10px;letter-spacing:.18em;text-transform:uppercase;opacity:.85}
.tags li{white-space:nowrap}
.tags li:not(:last-child)::after{content:"|";margin:0 16px;opacity:.5}
@media (max-width:1023px){.pillar{grid-column:1 / -1;min-height:460px}.pillar--dark::before{background:rgba(22,25,28,.9)}.pillar--light::before{background:rgba(239,231,218,.93)}}
@media (max-width:767px){.pillar__body{padding:36px 24px}.pillar .btn{min-width:0}}

/* ---------- reach ---------- */
/* Item 7a. The cards band and this one each contributed a full --section, leaving 173px of flat
   ivory between the last track card and the map at 1440. This band's top padding is trimmed the same
   way the Home difference band was -- clamped so the 40px phone floor is not crushed -- taking the
   gap to about 126px.
   The transition itself is the curved sweep below: a wide, shallow arc that starts up in the sand
   band, crosses the boundary and fades out before it reaches the map's artwork. Warm (sand over
   ivory) and low-contrast, so it reads as the two grounds resolving into one another rather than as
   a shape. isolation + z-index:-1 put it above this band's own background but beneath every piece of
   content, so the map's polygons and labels always paint over it; overflow:hidden keeps the
   deliberately over-wide arc from adding horizontal scroll. */
.reach{position:relative;isolation:isolate;padding-top:clamp(28px,2.8vw,44px)}
/* The arc has to be the visible edge, which means it must not be clipped and must still carry fill
   when it gets there. An earlier attempt put the shape at top:-96px under overflow:hidden, so the
   clip removed the dense first 40% of the gradient and left only its faded tail -- the curve landed
   in the fully transparent range and the whole thing read as a straight horizontal wash. So: no clip
   on this band, the shape is exactly 100% wide (no negative insets, nothing to overflow), it starts
   above the boundary and bleeds gently up into the sand band, and it keeps ~75% fill all the way
   down to the curved edge. blur() softens that edge so the arc is felt rather than drawn. */
.reach::before{content:"";position:absolute;z-index:-1;pointer-events:none;
  left:0;width:100%;top:-70px;height:185px;
  border-radius:0 0 50% 50% / 0 0 100% 100%;
  filter:blur(14px);
  background:linear-gradient(180deg,rgba(238,223,198,.30) 0%,rgba(238,223,198,.85) 34%,rgba(238,223,198,.75) 100%)}
.reach__grid{display:grid;grid-template-columns:5fr 7fr;gap:48px;align-items:center}
.reach__copy > * + *{margin-top:24px}
.reach__labels{display:flex;gap:40px;flex-wrap:wrap}
.reach__map svg{width:100%;height:auto}
.map-country{transform-box:fill-box;transform-origin:center;transition:transform 250ms var(--ease);cursor:pointer}
.map-shape{fill:#8C7A5B;opacity:.28;transition:fill 250ms ease,opacity 250ms ease}
.map-label{font-family:var(--font-ui);font-weight:600;font-size:13px;letter-spacing:.2em;fill:var(--ink);text-anchor:middle;pointer-events:none;transition:fill 250ms ease}
.map-country:hover,.map-country:focus-visible,.map-country.is-active{transform:scale(1.06)}
.map-country:hover .map-shape,.map-country:focus-visible .map-shape,.map-country.is-active .map-shape{fill:var(--navy);opacity:.9;filter:url(#map-glow)}
.map-country:hover .map-label,.map-country:focus-visible .map-label,.map-country.is-active .map-label{fill:var(--ivory)}
/* Mexico's widest interior run is 58 viewBox units and the word needs 79, so a lit label always
   overhangs its own coastline somewhere. The lit label carries a navy halo: ivory on navy is
   12.9:1 wherever the word overhangs the coast. (The spec's "gold halo" would drop that under
   3:1, so the halo is navy and gold stays out of small text.) */
.map-country:hover .map-label,.map-country:focus-visible .map-label,.map-country.is-active .map-label{paint-order:stroke;stroke:var(--navy);stroke-width:3;stroke-linejoin:round}
/* Dimming rests on the shape, never the group: a faded <g> takes the label down with it, and the
   label has to stay legible while its country is the dimmed one. The shapes rest at 28% of #8C7A5B
   and dim to 12%; check_map asserts the dimmed state is under .15 and the rest state at least .19. */
.map:hover .map-country:not(:hover):not(.is-active) .map-shape{opacity:.12}
.map:not(:hover):focus-within .map-country:not(:focus-visible):not(.is-active) .map-shape,.map.has-active .map-country:not(.is-active):not(:hover) .map-shape{opacity:.12}
.map-country:focus-visible .map-shape{stroke:var(--ivory);stroke-width:1.5}
@media (prefers-reduced-motion:reduce){.map-country:hover,.map-country:focus-visible,.map-country.is-active{transform:none}}
@media (max-width:1023px){.reach__grid{grid-template-columns:1fr}}

/* ---------- slideshow (home), on the navy band ---------- */
.slides__head > * + *{margin-top:20px}
.slides{margin-top:40px}
.slides__track{position:relative;aspect-ratio:16/9;overflow:hidden;background:var(--obsidian);border-radius:var(--radius);box-shadow:0 26px 44px -26px rgba(0,0,0,.5)}
.slide{position:absolute;inset:0;opacity:0;transition:opacity 600ms ease}
.slide.is-active{opacity:1}
.slide__img{width:100%;height:100%;object-fit:cover}
/* The eyebrow sits near the caption's own top edge, so the gradient's first stop can't be fully
   transparent: a bright photo (e.g. slide-channels.jpg's stainless kitchen) left the parchment
   eyebrow under 4.5:1 there. A light scrim from 0% keeps every slide's eyebrow legible.
   Round 12: that first stop was .28 and it was not enough. .slide__img is object-fit:cover in a 16/9
   box, so the crop under the caption tracks viewport width, and at 768 the channels eyebrow measured
   4.17:1 against a 4.5 floor while clearing it at 375, 1024, 1440 and 1920. Only this stop moved,
   .28 -> .45; the .85/35% and .92/100% stops and the whole phone branch below are untouched, because
   375 was never the failing width. The eyebrow's own rows sit around 22-34% of the caption, where the
   ramp now starts from .45 instead of .28, and channels at 768 goes 4.17 -> 4.91. Measured across all
   eight slides at all five widths: every eyebrow moved up by 0.00-0.84 and no lead moved at all (the
   leads already sit below the .85 stop), so this buys the failing case 0.74 without touching any
   value that was already comfortable. */
.slide__caption{position:absolute;left:0;right:0;bottom:0;padding:28px 36px;color:var(--white);background:linear-gradient(180deg,rgba(26,29,32,.45) 0%,rgba(26,29,32,.85) 35%,rgba(26,29,32,.92) 100%)}
.slide__caption .eyebrow{color:var(--gold)}
.slide__caption .lead{margin-top:10px;max-width:52ch;color:var(--white)}
/* One centred group: previous, dots (desktop) or counter (phone), pause, next. */
.slides__controls{display:flex;justify-content:center;align-items:center;gap:12px;margin-top:20px}
.slides__btn{min-width:48px;min-height:48px;display:inline-flex;align-items:center;justify-content:center;border:1px solid rgba(248,240,224,.35);color:var(--white);font-size:18px;transition:background-color var(--t) ease,color var(--t) ease}
.slides__btn:hover{background:var(--ivory);color:var(--navy)}
.slides__pause{font-size:13px}
.slides__dots{display:flex;padding:0;list-style:none}
.slides__dots button{width:40px;height:48px;display:inline-flex;align-items:center;justify-content:center}
/* Inactive dots are ivory at 55% (4.93:1 on navy); the active dot is gold (4.65:1 on navy). */
.slides__dots button::before{content:"";width:9px;height:9px;border-radius:50%;background:rgba(248,240,224,.55);transition:background-color var(--t) ease}
.slides__dots button[aria-current="true"]::before{background:var(--gold)}
.slides__count{display:none;min-width:64px;text-align:center;font-family:var(--font-ui);font-weight:600;font-size:12px;letter-spacing:.16em;color:var(--white)}
@media (max-width:767px){
  .slides__track{aspect-ratio:4/3}
  .slide__caption{padding:18px 20px;background:linear-gradient(180deg,rgba(26,29,32,.6) 0%,rgba(26,29,32,.88) 30%,rgba(26,29,32,.95) 100%)}
  .slide__caption .lead{font-size:16px}
  .slides__dots{display:none}
  .slides__count{display:block}
  .slides__controls{gap:8px}
}

/* ---------- our difference ---------- */
/* Item 6a: the slideshow band and this one each contributed a full --section of padding, so at 1440
   there were 173px of dead ground between the slideshow controls and the "Our difference" eyebrow.
   Only this band's top padding is trimmed -- the slideshow's own spacing was not the complaint. Not
   a flat multiplier, because --section is already at its 40px floor on a phone, where the same gap
   measured only 80px and had nothing to spare: this clamp trims desktop to ~40px (total ~127px) and
   phones to 28px (total ~68px), so the section still breathes at both ends. */
.difference-band{padding-top:clamp(28px,2.8vw,44px)}
.difference{text-align:center}
.difference > * + *{margin-top:22px}
.difference .body{margin-inline:auto;max-width:64ch}
.difference .eyebrow{color:var(--umber)}
/* The arrow belongs to the item before it, so a wrapped line never starts with an arrow. */
/* Item 6b: this chain is the clearest statement of the firm's range on the home page and was reading
   as fine print at 11px/600. Up a step to 13px and to 700, keeping the .2em tracking that gives it
   its character. Inter 700 is added to the font request for this (the family already loads
   400/500/600); check_weight guards the first-screen budget. */
.chain{display:flex;flex-wrap:wrap;justify-content:center;row-gap:12px;font-family:var(--font-ui);font-weight:700;font-size:13px;letter-spacing:.2em;text-transform:uppercase;color:var(--navy)}
.chain li{display:inline-flex;align-items:center;white-space:nowrap}
.chain li:not(:last-child)::after{content:"\2192";margin:0 24px;font-weight:400;color:var(--gold-deep)}
.difference .btn{margin-top:36px}
@media (max-width:767px){.chain{flex-direction:column;align-items:center}.chain li{flex-direction:column}.chain li:not(:last-child)::after{content:"\2193";margin:8px 0 0}}

/* ---------- closes ---------- */
.close__grid{display:grid;grid-template-columns:7fr 4fr;gap:48px;align-items:center}
.close__copy > * + *{margin-top:24px}
.close__copy .btn{margin-top:36px}
.close__aside{border-left:1px solid var(--hair);padding-left:40px}
.close--bare .close__grid{grid-template-columns:1fr auto}
/* Item 7b: the dark closing headline was oversized at desktop -- a full .h2 at 57.6px/1440 and 64px
   at 1920 against a single small button. One step down the scale, still a display headline and still
   well clear of lead and body sizes: 46px at 1440, 52px at 1920, 32px at 375. Applied to the
   Consulting closing band as well, so the two pages stay consistent. */
.close--bare .h2,.split--close .h2{font-size:clamp(32px,3.2vw,52px)}
.close--center{text-align:center}
.close--center .close__copy{margin-inline:auto;max-width:none}
.close--center .close__copy .h2{max-width:24ch;margin-inline:auto}
@media (max-width:1023px){
  .close__grid,.close--bare .close__grid{grid-template-columns:1fr}
  .close__aside{border-left:0;border-top:1px solid var(--hair);padding:24px 0 0}
}

/* ---------- full-bleed hero (brokerage) ---------- */
/* The vh line is a fallback, not a duplicate: Safari before 15.4 and Firefox before 101 do not know
   svh, so for them the whole svh declaration is invalid and is dropped -- and with it this hero's
   entire minimum height. The vh form is valid everywhere and is simply overridden by the next line
   wherever svh is understood. svh exists because mobile Safari's collapsing toolbar makes vh too
   tall; on the old iOS versions that take this fallback the hero can therefore run slightly past the
   first screen, which is the correct trade against having no minimum height at all. */
.hero-full{position:relative;min-height:min(100vh,820px);min-height:min(100svh,820px);display:flex;align-items:flex-end;color:var(--white)}
.hero-full__img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.hero-full::before{content:"";position:absolute;inset:0;z-index:1;background:linear-gradient(180deg,rgba(26,29,32,.45) 0%,rgba(26,29,32,.3) 30%,rgba(26,29,32,.72) 62%,rgba(26,29,32,.94) 100%)}
/* The scrim above was tuned for Brokerage's photograph and is left exactly as the client approved it.
   Home's banner is a bridge against open sky -- the brightest photograph on the site, and the copy
   lands on its palest corner, where white measured 2.82:1 against the 3.0 display floor at 1920.
   Deeper mid-stops only, and only here. NOTE for the photo re-selection: the durable fix is a scrim
   on .hero-full__copy itself, so hero legibility stops depending on which photograph is chosen --
   the slide captions already work that way. Not done now because it would alter Brokerage's
   approved hero, and every hero photograph is about to be re-picked anyway. */
.hero-full--deep::before{background:linear-gradient(180deg,rgba(26,29,32,.5) 0%,rgba(26,29,32,.42) 26%,rgba(26,29,32,.82) 58%,rgba(26,29,32,.95) 100%)}
.hero-full__inner{position:relative;z-index:1;width:100%}
.hero-full__copy{padding-block:72px;max-width:900px}
.hero-full__copy > * + *{margin-top:24px}
.hero-full__copy .h1{color:var(--white)}
/* The word-stack plate sits on the container's right content edge, not the viewport's (spec 4.1
   names 1920 as a width where everything shares the container edges). Its containing block is
   full-bleed, so `right` measures from the viewport edge; once the container stops growing past
   --container, the grid's right content edge is (100vw - --container)/2 + --gutter in from there.
   Below that width the container is full width, so max() falls back to a plain --gutter. */
.hero-full__stack{position:absolute;right:max(var(--gutter),calc((100vw - var(--container)) / 2 + var(--gutter)));top:calc(var(--header) + 56px);z-index:1;background:rgba(26,29,32,.7);padding:14px 16px}
/* PHONES: the banner is shown whole instead of cover-cropped. Client review on a real iPhone,
   2026-09-21 -- "the image is not visible within the viewing at all, it needs to be scaled".
   Measured: a 2880x1200 photograph cover-cropped into a 430x812 hero shows 22% of its width (19% at
   375), magnified, which is why it read as an unrecognisable close-up. No height fixes that; at 500px
   tall it is still only 36%. A portrait crop of the same original was built and rejected too -- the
   sources are 1.6-1.8:1 landscapes, so any 2:3 slice is a tight vertical strip of a wide room, and
   brk-hero's came out as a blurred hairnet. A wide photograph cannot become a portrait hero.
   So below 768px the image stops being a background: it sits in flow at its own 2.4:1 aspect, whole
   and undistorted, and the copy moves below it onto a solid dark ground. The scrim is switched off
   because its only job was protecting copy laid OVER the photograph, and nothing is over it now --
   which also means hero legibility on phones no longer depends on which photograph is chosen.
   Above 768px nothing changes: the full-bleed banner the client approved is untouched. */
@media (max-width:767px){
  /* padding-top clears the fixed header. Without it the header sits ON the banner, and since the
     banner is now a 2.4:1 strip rather than a full screen, losing its top 72px costs nearly half
     the picture -- the opposite of what this whole block is for. */
  .hero-full{min-height:0;display:block;padding-top:var(--header);
    background:linear-gradient(180deg,var(--navy) 0%,var(--obsidian) 100%)}
  .hero-full::before{display:none}
  .hero-full picture{display:block}
  .hero-full__img{position:static;width:100%;height:auto}
  .hero-full__copy{padding-block:32px 40px}
  .hero-full__stack{position:static;margin:0 var(--gutter);display:inline-block}
}

/* ---------- tracks (brokerage): two raised cards, photo on top ---------- */
/* Client review 2026-09-19, three changes to this section.
   (1) "Two Distinct Needs" is now a subheading above the h2 rather than its first line. On .band-sand
   it takes umber, the pairing this site already uses for small labels on sand (5.02:1, in PAIRS) --
   .band-dark/.band-navy's gold rule does not reach here.
   (2) "The size of the two image blocks is too long, taking up too much vertical space, the viewer has
   to scroll to get the complete message." The photographs were 3/2; at 1440 that is a 429px image in a
   644px-wide card. 16/9 takes it to 362px and the body padding tightens, which is ~75px off each card.
   (3) "There is an off-putting radius around it." The cards inherited .card's 3px radius -- itself over
   the 0-2px this project documents -- so the tracks are square now. Scoped to .track deliberately: the
   complaint was about this section, and --radius still governs every other card on the site. */
.tracks__eyebrow{color:var(--umber)}
.tracks__grid{margin-top:32px;row-gap:var(--col-gap)}
.track{grid-column:span 6;overflow:hidden;display:flex;flex-direction:column;border-radius:0}
/* height:auto is load-bearing, not tidiness. picture() emits width/height attributes on the <img> --
   here 1600x1066 -- and the height attribute maps to a height presentational hint, which beats
   aspect-ratio unless height is auto. Without it the 2026-09-19 change from 3/2 to 16/9 had NO rendered
   effect: the image drew at its intrinsic 644x1066 and the card stood 1256px tall, which is exactly the
   "too long, taking up too much vertical space" the client reported. Measured after the fix, not
   computed: 644x362 image, 574px card. This is the only aspect-ratio rule on an <img> itself; the
   others sit on containers, where the image is positioned inside an already-sized box. */
.track__img{aspect-ratio:16/9;width:100%;height:auto;object-fit:cover}
.track__body{padding:24px 32px 30px}
.track__body .eyebrow{color:var(--umber)}
.track__body .h3{margin-top:14px}
.track__body .body{margin-top:14px}
@media (max-width:767px){.track{grid-column:1 / -1}.track__body{padding:24px 20px 28px}}

/* ---------- split sections (consulting, about): copy on grid columns 1-6, photo filling the right half ---------- */
/* The copy lives inside .container, so at 1920px it still starts on the logo's edge; only the photo
   reaches the viewport edge. Under 1024px the photo drops below the copy at full width. */
.split{position:relative;overflow:hidden}
.split__grid{position:relative;z-index:2;align-items:center}
.split__copy{grid-column:1 / span 6;padding-block:72px}
.split__copy > * + *{margin-top:24px}
.split__copy .btn{margin-top:36px}
.split__media{position:absolute;top:0;right:0;bottom:0;width:50%;z-index:1;margin:0}
.split__media img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
/* The same inset as .hero-full__stack, even though this plate's parent is only half width:
   .split__media is anchored right:0, so the media's right edge IS the viewport's right edge and the
   distance in to the grid's right content edge is identical. (A percentage would not be: 50% of the
   media is not 50% of the viewport. The inset is in absolute units for exactly that reason.) */
.split__stack{position:absolute;right:max(var(--gutter),calc((100vw - var(--container)) / 2 + var(--gutter)));top:45%;z-index:2;margin:0;padding:14px 16px}
/* The two split heroes own their ground rather than borrowing .band-ivory, so the feather below can
   be derived from the very same two stops and the two can never drift apart. Item 9c retunes these
   variables for the About hero; round 8 retunes them for Consulting, and because everything on both
   heroes -- panel, feather, and the phone layout's flat fill -- is written in terms of --hero-top
   and --hero-bot, moving a hero's ground moves all of it together. */
.split--hero{--hero-top:var(--ivory);--hero-bot:var(--sand);padding-top:var(--header);
  background:var(--grain) 0 0/180px 180px repeat,linear-gradient(180deg,var(--hero-top) 0%,var(--hero-bot) 100%)}
/* Round 8, the panel. Item 8 opened this ground up from a flat ivory to ivory-hi -> sand, and the
   client still read the page as "too much white": it was the only light hero on the site (Home is
   navy, Brokerage a dark photograph, Contact near-black, About warm dune) and it opened 1565px of
   unbroken light. The panel is now dark, on the same two stops .band-dark paints, so the five heroes
   read as one family and the bright concrete-and-sky photograph reads as light AGAINST dark instead
   of adding to a light field. A dark panel is a dark context, so it sets the same variable block
   .band-dark does and the h1, lead and everything else on it follow from that; its eyebrow and
   micro-line take parchment, as they do on every other dark band, and its button takes the light
   .btn--white variant. (build.py also flips DARK_HEADER for this page, so the fixed header's scrim
   and lockup are the dark-ground pair rather than ivory-on-navy.) */
.con-hero{--hero-top:var(--navy);--hero-bot:var(--obsidian);
  --bg:var(--obsidian);--fg:var(--white);--fg-head:var(--white);--fg-soft:rgba(255,255,255,.78);
  --hair:var(--hair-dark);--focus:var(--ivory);color:var(--fg)}
.con-hero .eyebrow,.con-hero .micro{color:var(--gold)}
/* Item 9c. Because the feather is derived from these same two stops, moving the About hero's ground
   down a step moves its feather with it automatically. */
.abt-hero{--hero-top:var(--sand);--hero-bot:var(--dune)}
/* The wash that lifts the panel's emptiest corner, restated for the dark ground: item 8's warm
   radial would now be a pale stain on navy. Atlantic is the site's lighter dark, and laid into the
   bottom-left at 75% over navy/obsidian it grades the panel the way the warm wash graded the ivory
   one. Atlantic itself is the lightest tone this panel can paint -- every pixel of the wash is
   atlantic composited over something darker -- so it is the worst case for the text on it and the
   ground its pairs are registered against in check_tokens. */
.con-hero::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:radial-gradient(78% 86% at 2% 96%,rgba(30,58,95,.75) 0%,rgba(30,58,95,.34) 45%,rgba(30,58,95,0) 74%)}
/* 640, not 760: at 1440 the hero copy measures ~475px on its own, so a 760px box left ~175px of
   empty ivory below "CLARITY. EXPERIENCE. REAL-WORLD RESULTS." -- the white space the user
   complained about. The copy keeps its own 72px padding-block as breathing room, so what is left
   reads as deliberate spacing rather than a gap. min-height, not height: the box still grows when
   the copy is taller (narrow viewports, the Consulting hero's briefing button, larger text). */
/* vh first for the engines without svh, exactly as on .hero-full above. */
.split--hero .split__grid{min-height:calc(min(100vh,640px) - var(--header));min-height:calc(min(100svh,640px) - var(--header))}
/* Item 8, the feather. This used to fade from a flat --ivory while the panel behind it descended
   from ivory to sand, so the mismatch grew with height: measured at 1440, panel #F5EDDD against
   feather #F6EEDE at 15% down, but #EDDFC7 against #F7EFDF at 85% -- a pale stripe standing on a
   deeper ground, which is the hard edge the client saw. The fill is now the ground's own gradient,
   the identical two stops, and only its alpha is ramped away to the right by a mask. Because the
   media box spans the same height as the section, the colour matches the panel at every height by
   construction, so the picture emerges instead of butting against a line. The mask is written in
   rgba so no pure #000 enters the stylesheet. */
.split--hero .split__media::before{content:"";position:absolute;inset:0;z-index:1;
  background:linear-gradient(180deg,var(--hero-top) 0%,var(--hero-bot) 100%);
  -webkit-mask-image:linear-gradient(90deg,rgba(0,0,0,1) 0%,rgba(0,0,0,.55) 12%,rgba(0,0,0,0) 38%);
  mask-image:linear-gradient(90deg,rgba(0,0,0,1) 0%,rgba(0,0,0,.55) 12%,rgba(0,0,0,0) 38%)}
.split--hero .split__stack{color:var(--ink);background:rgba(248,240,224,.88)}
.split--close .split__grid{min-height:480px}
.split--close .split__stack{color:var(--white);background:rgba(26,29,32,.72);top:50%;transform:translateY(-50%)}
@media (max-width:1023px){
  .split--hero .split__grid,.split--close .split__grid{min-height:0}
  .split__copy{grid-column:1 / -1;padding-block:48px}
  .split__media{position:relative;width:100%;aspect-ratio:4/3}
  /* Under 1024px the photo sits below the copy at full width, so it feathers from its top edge
     instead of its left. The ground there is near the bottom of the panel's gradient, so the fill is
     --hero-bot rather than the old flat ivory, and the mask runs vertically. */
  .split--hero .split__media::before{background:var(--hero-bot);
    -webkit-mask-image:linear-gradient(180deg,rgba(0,0,0,1) 0%,rgba(0,0,0,.5) 10%,rgba(0,0,0,0) 28%);
    mask-image:linear-gradient(180deg,rgba(0,0,0,1) 0%,rgba(0,0,0,.5) 10%,rgba(0,0,0,0) 28%)}
}

/* The hero's small values line: umber reads as warm gold at 11px and clears 4.5:1 on ivory and sand
   (gold-deep is a display-size colour and fails at this size). */
.about__values{color:var(--umber)}
/* --umber was chosen when this line sat on About's light --dune hero. The client's 2026-09-19 "full
   banner photo on each page" made that hero dark, and the line measured 2.55-2.60:1 across all five
   widths against a 4.5 floor. On the banner it takes --gold, which is what every other small line on a
   dark ground carries since that same review, and which measures 5.37:1 on the scrim. Scoped to the
   hero so the selector's other homes keep umber. */
.hero-full .about__values{color:var(--gold)}
.close--center .eyebrow{color:var(--umber)}

/* ---------- focus areas ---------- */
.focus{text-align:center}
.focus .eyebrow{color:var(--umber)}
.focus .h2{margin-top:24px;margin-inline:auto;max-width:26ch}
/* Round 8. Four cards, not four sets of small text divided by hairline rules. The cells carry
   `card card--lift` in build.py -- the same surface, radius, shadow and hover lift as the Home
   pillars and the Brokerage tracks -- so the only thing this rule changes is that the grid gaps them
   apart where it used to rule between them: four objects with edges break the field, a hairline
   does not. The 34ch measure and the centred icon above each label are unchanged. */
.focus__grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--col-gap);margin-top:48px}
.focus__cell{padding:48px 40px}
.focus__icon{width:44px;height:44px;margin:0 auto 24px;stroke:var(--navy);fill:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
.focus__cell .lead{margin:16px auto 0;max-width:34ch}
@media (max-width:767px){.focus__grid{grid-template-columns:1fr}.focus__cell{padding:36px 24px}}

/* ---------- stewardship: sticky stage, ring, reveal, pictures ---------- */
.steward{position:relative;min-height:170vh;--p:0}
/* Round 9, and the whole of this page's brightness fix. The section is 170vh and its sticky stage is
   100vh, so the lower 70vh is ground with nothing standing on it: measured at 1440 that was 880px --
   26% of the page -- at 100% light, and .band-sand runs UP into near-white ivory, so it was brightest
   (mean 0.930) exactly where the page was emptiest. The brightest field on the site, on the brightest
   page on the site. The band now starts at --dune, where the hero's own gradient above it ends, holds
   that under the panel, and then descends to --obsidian so the section hands off INTO the dark
   approach band instead of butting against it. No new token, and no new idiom: this is .band-dune's
   "the one band that deepens as it descends" carried through to the band it descends into.
   Gated on .has-steward and on 1024px because the runway is. Without the script, under reduced
   motion, or below 1024 the section collapses to its content height and .band-dune is the whole of
   it -- a 60%-dark gradient compressed behind an opaque panel would read as a stray dark strip.
   This is also why the stage carries a card: the copy travels over every part of this ground, so its
   legibility must not depend on the ground's tone. Nothing else sits on the band, so the dark end
   carries no text and this rule deliberately does NOT set the .band-dark variable block -- if it did,
   the panel's own ink and navy would flip to ivory on a --card surface. */
@media (min-width:1024px){
  .steward.has-steward{background:var(--grain) 0 0/180px 180px repeat,
    linear-gradient(180deg,var(--dune) 0%,var(--dune) 50%,var(--obsidian) 100%)}
}
/* 24px top, not var(--header): the stage is a sticky 100vh pin, so its top padding adds to the
   centring slack and pushed the Stewardship heading ~256px below the hero at rest. Only the padding
   is trimmed -- the 170vh runway on .steward is the scroll effect's travel, which ruling R12 keeps
   because a scrolling visitor sees it filled, and centring is what composes the pinned state. */
.steward__stage{position:sticky;top:0;min-height:100vh;display:flex;align-items:center;padding-block:24px 48px}
/* The container is a flex item, so its width is shrink-to-fit unless it is told otherwise. It
   resolved to the full width anyway while it was itself the grid; now that its content is one block,
   say it, rather than leave the page's left content edge to a max-content calculation. */
.steward__stage > .container{width:100%}
/* Round 9. The copy and the four pictures stand on a real surface -- the site's own --card, the same
   one the Home pillars, the Brokerage tracks and the round-8 focus cells carry -- instead of directly
   on the band. Two things follow. The section reads as an object on a ground rather than as text
   adrift on an unbroken field, which is what the client saw; and the ground beneath is then free to
   darken across the runway without the copy following it down, which is what makes the rule above
   possible. Plain `card`, not `card--lift`: a 1300px panel that jumps on hover -- or on focus-within,
   which the four picture buttons would each trigger -- is not what that affordance is for. */
.steward__panel{padding:56px 64px}
@media (max-width:1023px){.steward__panel{padding:40px}}
@media (max-width:767px){.steward__panel{padding:32px 24px}}
/* Item 9a: the progress ring is gone -- markup, CSS and the JS that drove it, together. At rest it
   painted an empty gold circle in a wide empty column and read as an image that had failed to load;
   the client waited ten minutes for it to finish loading. A scroll indicator that looks broken is
   worse than none, and the sentence reveal carries the effect perfectly well on its own. Its 3fr
   column goes with it, so the copy and pictures spread into that space rather than leaving the hole
   the ring used to sit in. */
.steward__grid{display:grid;grid-template-columns:7fr 4fr;gap:64px;align-items:center}
.steward__copy > * + *{margin-top:24px}
.steward__copy .lead{max-width:none}
/* The sticky stage and its 170vh scroll runway only make sense while the script is driving them.
   Without JavaScript, or under reduced motion, .has-steward is never set and the section collapses
   to its own height with the copy fully readable. */
.steward:not(.has-steward){min-height:auto}
.steward:not(.has-steward) .steward__stage{position:static;min-height:0;display:block;padding-block:var(--section)}
/* A pending sentence sits at 55% and 6px low, so it reads as waiting rather than as faint body copy.
   `top`, not `translateY`: transforms do not apply to non-replaced inline elements, and making these
   spans inline-block would break each sentence onto its own line inside the paragraph. */
.steward__s{position:relative;top:0;transition:opacity 400ms ease,top 400ms var(--ease)}
.steward.has-steward .steward__s{opacity:.55;top:6px}
.steward.has-steward .steward__s.is-on{opacity:1;top:0}
.steward__pics{display:grid;grid-template-columns:1fr 1fr;gap:16px}
/* Item 9b: these rested at .35, which is the "faded pictures effect" the client disliked -- four
   photographs washed out to the point of looking like placeholders. They now rest at .9 and go to
   full strength on hover, keeping the gentle lift. */
.steward__pic{display:block;width:100%;aspect-ratio:4/3;overflow:hidden;opacity:.9;transition:opacity 250ms ease,transform 250ms var(--ease)}
.steward__pic img{width:100%;height:100%;object-fit:cover}
.steward__pic:hover,.steward__pic:focus-visible{opacity:1;transform:scale(1.04)}
.lightbox{border:0;padding:0;background:transparent;max-width:90vw;max-height:90vh;color:var(--white)}
.lightbox::backdrop{background:rgba(26,29,32,.92)}
.lightbox__fig{margin:0}
.lightbox__fig img{max-width:90vw;max-height:78vh;width:auto;height:auto;margin-inline:auto}
.lightbox__fig figcaption{margin-top:16px;text-align:center}
.lightbox__close{margin:20px auto 0;display:flex}
@media (max-width:1023px){
  .steward{min-height:auto}
  .steward__stage{position:static;min-height:0;display:block;padding-block:var(--section)}
  .steward__grid{grid-template-columns:1fr;gap:32px}
  .steward__pics{grid-template-columns:repeat(4,1fr);gap:8px}
  /* The phone used to fade these further, to .7; item 9b drops that override so it matches the
     desktop resting state and the photographs read as photographs on a phone too. */
}
@media (prefers-reduced-motion:reduce){
  .steward__pic:hover,.steward__pic:focus-visible{transform:none}
  .steward.has-steward .steward__s{opacity:1;top:0}
}

/* ---------- approach (about): dark stone photo under a navy-to-charcoal scrim ---------- */
.approach{position:relative;overflow:hidden;isolation:isolate}
.approach__texture{position:absolute;inset:0;z-index:-2;width:100%;height:100%;object-fit:cover}
/* Item 9d: this scrim was so heavy (.88/.90/.84) that it swallowed the photograph underneath it.
   Lifted so the stone reads through, with the text pairs re-measured against the pixels actually
   painted rather than guessed at. */
.approach::before{content:"";position:absolute;inset:0;z-index:-1;background:linear-gradient(115deg,rgba(16,42,67,.74) 0%,rgba(22,25,28,.78) 60%,rgba(22,25,28,.72) 100%)}
.approach__head{position:relative;display:grid;grid-template-columns:1fr auto;gap:48px;align-items:start}
.approach__head .h2{margin-top:24px;max-width:16ch}
.approach__cols{position:relative;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));column-gap:var(--col-gap);margin-top:56px}
.approach__col{padding:8px 0}
.approach__col + .approach__col{border-left:1px solid var(--hair);padding-left:var(--col-gap)}
.approach__col .lead{margin-top:16px;font-size:clamp(18px,1.3vw,21px)}
@media (max-width:1023px){.approach__cols{grid-template-columns:repeat(2,minmax(0,1fr));row-gap:32px}.approach__col:nth-child(3){border-left:0;padding-left:0}}
@media (max-width:767px){.approach__cols{grid-template-columns:1fr}.approach__col + .approach__col{border-left:0;padding-left:0;border-top:1px solid var(--hair);padding-top:24px}.approach__head{grid-template-columns:1fr}}

/* ---------- contact ---------- */
/* The whole page is a charcoal gradient; the route cards and intake are raised dark cards on it. */
body.theme-dark main{background:linear-gradient(180deg,#16191C 0%,var(--obsidian) 45%,#131C26 100%)}
/* Item 10. The page read as a single near-black field top to bottom, with a large empty black area
   under the two route cards. It now carries three grounds -- a deep night hero, a lifted slate band
   behind the cards so they sit on something instead of floating in the same void, and the obsidian
   footer -- which gives the page relief and rhythm while staying dark and formal.
   The dead space is closed at its source: the markup used to end with an empty aria-hidden spacer
   div, 173px tall at 1440 and 80px at 375. That div is gone and this band carries a real, clamped
   bottom padding instead, so the page closes rather than trailing off. */
/* The banner stays plain, by the client's instruction of 2026-09-20: the weave came off .contact-top
   and stays on .contact-main below it. Their earlier note on this page -- "the darkness is fine but
   there is no texture and it does not align with the rest of the site" -- is still answered, by the
   body of the page rather than by its opening panel. The two rules are deliberately no longer the
   same treatment; do not re-merge them. */
.contact-top{background:linear-gradient(180deg,var(--night) 0%,#17202A 100%)}
.contact-main{background:var(--linen-dark) 0 0/120px 120px repeat,linear-gradient(180deg,var(--slate) 0%,#1A222C 55%,#151C24 100%);
  padding-bottom:clamp(40px,4.5vw,72px)}
.contact-hero{text-align:center;padding-top:calc(var(--header) + 56px)}
.contact-hero > * + *{margin-top:24px}
.contact-hero .lead{margin-inline:auto;max-width:44ch}
.routes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:24px;margin-top:64px}
.route{padding:48px 40px;display:flex;flex-direction:column;gap:20px;transition:border-color var(--t) ease,box-shadow var(--t) ease,transform var(--t) ease}
.route .eyebrow{color:var(--gold)}
.route.is-active{border-color:var(--atlantic);box-shadow:0 0 0 1px var(--atlantic),var(--lift)}
.route .btn{align-self:flex-start;margin-top:auto}
@media (max-width:767px){.route .btn{align-self:stretch}}
.intake{margin-top:24px;padding:56px 40px;overflow:hidden}
.intake[hidden]{display:none}
.intake__head > * + *{margin-top:16px}
.intake__req{margin-top:12px;font-size:12px;opacity:.75}
.intake__grid{display:grid;grid-template-columns:1fr 1fr;gap:40px 56px;margin-top:40px}
.field--wide{grid-column:1 / -1}
.field label{display:block;font-family:var(--font-ui);font-weight:600;font-size:10px;letter-spacing:.18em;text-transform:uppercase;margin-bottom:10px}
/* The intake is a dark card, so the fields stay transparent with a warm ivory underline rather than
   taking an ivory fill: a light fill here would read as another card, not as a field. The underline
   is the field's edge and it stays visible (ivory at 40% over --card-dark, a non-text UI element). */
.field__input{width:100%;background:transparent;border:0;border-bottom:1px solid rgba(248,240,224,.4);color:var(--white);font-family:var(--font-display);font-size:20px;padding:10px 0 12px;border-radius:0;transition:border-color var(--t) ease,box-shadow var(--t) ease}
.field__input::placeholder{color:rgba(248,240,224,.55);opacity:1}
.field__input:focus{border-bottom-color:var(--ivory);box-shadow:0 8px 14px -10px rgba(248,240,224,.7)}
.field__input:focus:not(:focus-visible){outline:none}
select.field__input{appearance:none;-webkit-appearance:none;padding-right:28px;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='%23F8F0E0' stroke-width='1.5' 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 0 center}
select.field__input option{color:var(--obsidian);background:var(--ivory)}
select.field__input.is-placeholder{color:rgba(248,240,224,.55)}
textarea.field__input{min-height:84px;resize:vertical}
.field__error{display:none;margin-top:8px;font-size:12px;color:var(--error-dark)}
.field.is-invalid .field__error{display:block}
.field.is-invalid .field__input{border-bottom-color:var(--error-dark)}
.intake__foot{display:grid;grid-template-columns:auto 1px 1fr;gap:32px;align-items:center;margin-top:48px}
.intake__foot-rule{width:1px;height:48px;background:var(--hair)}
.intake__note{font-size:14px;line-height:1.6;opacity:.8;max-width:52ch}
.intake__status{margin-top:20px;font-size:14px;min-height:1.6em}
.intake__status.is-error{color:var(--error-dark)}
.intake__done > * + *{margin-top:20px}
@media (max-width:1023px){.routes{grid-template-columns:1fr}.route,.intake{padding:40px 24px}.intake__grid{grid-template-columns:1fr;gap:32px}.intake__foot{grid-template-columns:1fr}.intake__foot-rule{display:none}}

/* ---------- legal pages (privacy, terms) ---------- */
/* Text pages: one column on the same grid, bands and type scale as the rest of the site, with no
   hero, no photographs and no cards, so they read as part of this site rather than as something
   pasted in. The measure is about 70ch, where this type stops being comfortable over long runs.
   Body copy is full --ink rather than the --fg-soft the marketing pages use: these are documents
   somebody has to read closely, and the pairs are ink on ivory 11.90 and ink on sand 10.87. */
/* The header is fixed, so the first section of every page has to clear it -- .contact-hero pads by
   calc(var(--header) + 56px), the split heroes by --header. This section had only --section, which is
   40px at 375px against a 72px header, so the "Legal" eyebrow was rendering underneath it: at 375
   check_legibility found nothing of it left to sample once the header strip was excluded. */
.legal.section{padding-top:calc(var(--header) + var(--section))}
.legal__head{max-width:70ch}
.legal__head .h2{margin-top:16px}
.legal__date{margin-top:20px;font-family:var(--font-ui);font-weight:600;font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--fg-head)}
.legal__body{max-width:70ch;margin-top:var(--section);color:var(--fg)}
.legal__body > * + *{margin-top:20px}
.legal__body h2{margin-top:56px}
.legal__body ul{display:grid;gap:12px;padding-left:24px;list-style:disc}
.legal__body li::marker{color:var(--gold-deep)}
.legal__lead{font-family:var(--font-display);font-size:clamp(18px,1.5vw,24px);line-height:1.4}
.legal__address{line-height:1.9}
/* The draft notice. Visually separate from the document it sits above -- its own surface, its own
   rule down the left edge -- so nobody mistakes it for a clause. Ink on card 12.32, its label navy
   on card 13.38, the gold-deep rule 3.91 against the same surface. */
.legal-draft{margin-top:40px;max-width:70ch;padding:28px 32px;background:var(--linen) 0 0/120px 120px repeat,var(--card);border-left:3px solid var(--gold-deep);border-radius:var(--radius);color:var(--ink)}
.legal-draft__label{font-family:var(--font-ui);font-weight:600;font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--navy);margin-bottom:12px}
@media (max-width:767px){.legal-draft{padding:24px 20px}.legal__body h2{margin-top:44px}}

/* ---------- back to top ---------- */
/* One control, one place in the DOM: the end of <main>, above the footer. On phones it stays in flow
   there as a full-width control, because a floating button covers copy on a small screen. From 768px
   up the same element is lifted out of flow into the bottom-right gutter and only appears once the
   reader is roughly a viewport down. Its surface is --card on every page, the dark Contact page
   included, so it never sinks into whatever band it happens to float over. */
.to-top{display:inline-flex;align-items:center;justify-content:center;gap:10px;min-height:48px;padding:0 22px;
  font-family:var(--font-ui);font-weight:600;font-size:11px;letter-spacing:.16em;text-transform:uppercase;
  background:var(--card);color:var(--ink);border:1px solid var(--rule);border-radius:var(--radius);box-shadow:var(--lift)}
.to-top__arrow{display:inline-block;transition:transform var(--t) ease}
.to-top:hover .to-top__arrow{transform:translateY(-3px)}
/* Client review 2026-09-19: "Option does not stay visible when scrolling and it disappears
   completely when i reach the bottom." Two separate causes, both by prior design, both now changed.
   (1) Below 768px it was never a floating control -- it sat in flow at the end of <main> -- and the
   client reviewed on an iPhone 15 Pro Max at 430px, so they only ever saw that version. It now floats
   at every width. (2) site.js hid it as soon as the footer appeared, because the bottom-right gutter
   is exactly where the footer's Privacy and Terms links land. It now STAYS VISIBLE as asked, and
   instead of vanishing it rides above the footer: site.js clamps --totop-bottom so the button stops
   at the footer's top edge. That keeps the client's requirement without covering the only route to
   the legal pages, which is a defect the suite has asserted against since round 5. */
.to-top-wrap{padding:0}
.to-top{position:fixed;right:var(--gutter);bottom:var(--totop-bottom,32px);z-index:40;
  opacity:0;transform:translateY(8px);pointer-events:none;
  transition:opacity 200ms var(--ease),transform 200ms var(--ease)}
.to-top.is-visible{opacity:1;transform:none;pointer-events:auto}
@media (max-width:767px){.to-top{right:20px;bottom:var(--totop-bottom,20px)}}
@media (prefers-reduced-motion:reduce){
  .to-top{transition:none}
  .to-top,.to-top.is-visible{transform:none}
  .to-top:hover .to-top__arrow{transform:none}
}

/* ---------- motion ---------- */
@media (prefers-reduced-motion:reduce){
  html.is-ready{scroll-behavior:auto}
  *,*::before,*::after{transition-duration:.01ms!important;animation-duration:.01ms!important}
}
