/* =========================================================================
   BURKARD GROUP — single stylesheet
   -------------------------------------------------------------------------
     1  Reset & tokens          8  Animation primitives  14  Impact, flow
     2  Typography              9  Hero                  15  Family office
     3  Layout primitives      10  About                 16  Footer, forms
     4  Canvas, grain, plates  11  Services              17  Cursor
     5  Preloader              12  Collection            18  Responsive
     6  Header                 13  Global reach          19  Reduced motion
     7  Chapters rail
   Colour is not static: backgrounds.js interpolates the whole palette from
   each section's data-hue and writes it onto :root every frame.
   ========================================================================= */

/* -------------------------------------------------------- 1. Reset & tokens */

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

html,
body,
h1, h2, h3, h4, p, figure, blockquote, ul, ol, dl, dd {
  margin: 0;
  padding: 0;
}

ul, ol { list-style: none; }
img, canvas, svg { display: block; max-width: 100%; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }

/* Outfit is a VARIABLE font — one file carrying a continuous 100–900 weight
   axis, not five static cuts. It was previously declared as five separate
   faces pointing at five byte-identical copies of this same file, each
   claiming a single fixed weight; the browser then rendered close to the
   axis default (100, Thin) throughout, so the page got a fraction of the
   weight contrast the design asks for, and paid for five downloads to get it.

   Declaring the RANGE is what lets the browser interpolate the axis, so
   font-weight: 200 and font-weight: 600 render as genuinely different weights.
   Licence: SIL Open Font License 1.1 — self-hosting is expressly permitted. */
@font-face {
  font-family: Outfit;
  src: url(../fonts/outfit-variable.woff2) format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* One family, two roles. Outfit stands in for Degular (Typekit, licensed). */
  --font-display: Outfit, "Century Gothic", "Helvetica Neue", sans-serif;
  --font-ui: Outfit, "Helvetica Neue", sans-serif;

  /* Fluid gutter — 18px at 400px wide, 150px at 1600px */
  --gutter: clamp(1.125rem, 0.1rem + 9vw, 9.375rem);
  --rule: 1px;

  /* Viewport units that survive mobile browser chrome (set from JS) */
  --vh: 1vh;
  --vw: 1vw;

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-io: cubic-bezier(0.65, 0, 0.35, 1);
  --dur: 0.9s;

  --z-canvas: 0;
  --z-content: 1;
  --z-rail: 40;
  --z-header: 50;
  --z-cursor: 90;
  --z-loader: 100;
}

/* Brand palette: #0c1933 navy, #4a698a slate, #c5e3f1 pale blue,
   #f3f5f9 off-white.

   These are the static baseline only — what renders before JS runs, and what
   renders if it never does. Once backgrounds.js is up it overwrites every one
   of them on :root each frame, interpolated from the surrounding sections'
   hues. Scoped to :root deliberately — an unscoped [data-theme] selector
   would redefine these for any subtree that happened to carry the attribute
   and beat the root value. */
:root[data-theme="dark"] {
  /* Declare the page natively dark (with the meta tag in each <head>), so
     mobile forced-dark features (Chrome auto-dark, Samsung dark mode) don't
     invert it — inversion is what turns the white wordmark black. */
  color-scheme: dark;
  --c-bg: #0c1933;
  --c-fg: #f3f5f9;
  --c-muted: rgba(243, 245, 249, 0.56);
  --c-faint: rgba(243, 245, 249, 0.32);
  --c-line: rgba(243, 245, 249, 0.16);
  --c-accent: #c5e3f1;
  --canvas-opacity: 0.9;
  --plate-opacity: 0.30;
}


/* ---------------------------------------------------------- 2. Typography */

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: auto; /* the smooth-scroll module owns this */
  /* The coverflow's off-screen covers sit thousands of px to the sides.
     Clip that overflow at the root or mobile browsers derive their minimum
     pinch-zoom scale from it and let the user zoom out into a broken layout.
     `clip` rather than `hidden`: it does not create a scroll container, so
     position: sticky and anchor scrolling keep working. */
  overflow-x: clip;
}

body {
  min-height: 100vh;
  font-family: var(--font-ui);
  font-weight: 300;
  font-size: clamp(0.875rem, 0.8rem + 0.3vw, 1rem);
  line-height: 1.75;
  color: var(--c-fg);
  background-color: var(--c-bg);
  transition: background-color 0.8s var(--ease-io), color 0.8s var(--ease-io);
  overflow-x: hidden;
  overflow-x: clip;   /* fallback above for browsers without `clip` support */
}

body.is-locked { overflow: hidden; }

.display,
h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 200;
  line-height: 1.02;
  letter-spacing: -0.025em;   /* geometric sans wants tighter tracking than a serif */
  text-wrap: balance;
}

.d-1 { font-size: clamp(2.5rem, 1rem + 6.6vw, 7.25rem); }
.d-2 { font-size: clamp(2rem, 1rem + 3.9vw, 4.5rem); }
.d-3 { font-size: clamp(1.5rem, 1rem + 2vw, 2.5rem); }

/* Emphasis is a colour and weight shift, not an italic: the vendored Outfit
   subset has no true italic and synthesised oblique on a geometric sans is
   unmistakably wrong. */
.display em,
.accent-em {
  font-style: normal;
  font-weight: 300;
  color: var(--c-accent);
}

/* The deliberate exception to the rule directly above: this word is not set
   in Outfit at all, so there is no synthesised oblique to avoid — it is handed
   to whatever script face the reader's OS provides, as a flourish on a single
   opening word. Nothing cursive is vendored and no font is fetched (the site
   ships zero third-party runtime code), so the face varies by platform and the
   `cursive` generic is the guaranteed floor. Script designs run small against
   a geometric sans at the same size, hence the step up in em — relative, so it
   tracks the fluid size of whatever it sits in. It survives the word splitter
   because collectWords now carries authored classes through the flatten. */
.script-em {
  font-family: "Snell Roundhand", "Apple Chancery", "Segoe Script",
               "Brush Script MT", cursive;
  font-size: 1.5em;
  /* Tight, but not so tight that the loops and descenders spill out of the
     box — a split word sits inside `.split__word`, which is overflow: hidden
     for the mask reveal and would shear anything that overflows. */
  line-height: 1.15;
  letter-spacing: normal;  /* script joins want their natural fit, not display tracking */
  padding-right: 0.06em;   /* the swash overhangs its advance width and would clip */
}

/* Sit the word back on the line. The splitter's masks are `vertical-align:
   top` (see .split__word in section 8) because an overflow:hidden inline-block
   baselines off its bottom margin edge, which is useless here. Aligning tops
   is invisible while every word is the same size — but this one is 1.5em, so
   its taller box hangs its baseline below the rest of the line. Nudge the
   whole mask up rather than the text inside it: the mask clips, so shifting
   its contents would shear the ascenders instead of moving them.
   `position: relative` moves the clip box along with the word, and leaves the
   reveal transform on the inner span untouched.
   Only applies to the split (JS) rendering — with scripting off there is no
   mask, the word is a plain inline span, and baseline alignment is already
   correct. Adjust this one value if the word sits high or low. */
.split__word:has(> .script-em) {
  position: relative;
  top: -0.26em;
}

/* Section headers — the numbered "01 About" markers and the label paired
   with each. These are the page's division markers, sized to be read at a
   glance rather than to sit quietly, with an uppercase, wide-tracked
   treatment. */
.label {
  display: inline-flex;
  align-items: baseline;
  gap: 0.9em;
  font-size: clamp(0.875rem, 0.78rem + 0.28vw, 1.0625rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--c-muted);
}

.label i {
  font-style: normal;
  color: var(--c-faint);
  font-variant-numeric: tabular-nums;
}

.lede {
  font-size: clamp(1.0625rem, 0.9rem + 0.7vw, 1.5rem);
  line-height: 1.55;
  font-weight: 200;
  text-wrap: pretty;
}

.body-copy p + p { margin-top: 1.25em; }
.body-copy { color: var(--c-muted); max-width: 46ch; }

.num { font-variant-numeric: tabular-nums; }

::selection { background: var(--c-accent); color: var(--c-bg); }

:focus-visible {
  outline: 1px solid var(--c-accent);
  outline-offset: 4px;
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------- 3. Layout primitives */

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

.grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: clamp(0.75rem, 1.5vw, 1.75rem);
}

.section {
  position: relative;
  padding-block: clamp(5rem, 12vh, 11rem);
}

/* Interior pages open below the fixed header rather than under it. */
.page-head {
  padding-top: clamp(8rem, 22vh, 14rem);
  padding-bottom: clamp(2rem, 6vh, 4rem);
}

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  /* Both slots hold a `.label`, and two long ones ("04 Our Approach"
     opposite "Institutional Discipline · …") don't fit side by side on
     a narrow screen. Wrapping drops the right-hand one onto its own line
     rather than letting it overflow. */
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
  padding-bottom: 1.25rem;
  margin-bottom: clamp(2.5rem, 6vw, 5rem);
  border-bottom: var(--rule) solid var(--c-line);
}

.rule {
  height: var(--rule);
  background: var(--c-line);
  border: 0;
}

/* ------------------------------------------------- 4. Canvas, grain, plates */

#canvas-wrapper {
  position: fixed;
  inset: 0;
  z-index: var(--z-canvas);
  pointer-events: none;
  opacity: var(--canvas-opacity);
  transition: opacity 0.9s var(--ease-io);
}

#canvas-wrapper canvas {
  width: 100%;
  height: 100%;
}

/* Fallback when WebGL is unavailable — a static approximation. */
.no-webgl #canvas-wrapper {
  background:
    radial-gradient(120% 80% at 70% 0%, rgba(197, 227, 241, 0.16), transparent 60%),
    radial-gradient(100% 100% at 20% 100%, rgba(74, 105, 138, 0.28), transparent 70%);
}

/* Film grain over everything, cheap and always on. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 20;
  pointer-events: none;
  background-image: url(../img/grain.png);
  background-size: 180px 180px;
  opacity: 0.045;
  mix-blend-mode: overlay;
}

.page { position: relative; z-index: var(--z-content); }

/* Fixed stack the section plates are hoisted into on boot, so two of them can
   overlap and cross-dissolve. Sits above the shader canvas, below content. */
.bg-stack {
  position: fixed;
  inset: 0;
  z-index: var(--z-canvas);
  pointer-events: none;
  overflow: hidden;
}

.plate--fixed {
  position: absolute;
  inset: 0;
  z-index: auto;
  opacity: 0;
  will-change: opacity, transform;
}

/* backgrounds.js writes these every frame — a CSS transition on top would
   fight it and lag the whole backdrop a second behind the scroll. */
.bg-driven #canvas-wrapper { transition: none; }

/* Text colour switches instantly, not on a transition. Easing it looks
   smoother in isolation but is measurably worse: a fading foreground and a
   fading background pass through grey at the same moment and the copy drops
   to ~1.7:1 contrast for a beat. Cutting it holds ~3.9:1 at the worst point,
   and the cut is masked because the shader and plate are mid-crossfade. */
.bg-driven body { transition: none; }

/* Full-bleed procedural plate used as a section backdrop. */
.plate {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
}

.plate img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: var(--plate-opacity);
  will-change: transform;
}

.plate::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to bottom,
    var(--c-bg) 0%,
    color-mix(in srgb, var(--c-bg) 45%, transparent) 28%,
    color-mix(in srgb, var(--c-bg) 45%, transparent) 72%,
    var(--c-bg) 100%
  );
}

/* ------------------------------------------------------------ 5. Preloader */

.loader {
  position: fixed;
  inset: 0;
  z-index: var(--z-loader);
  display: grid;
  place-items: center;
  background: #0c1933;
  color: #f3f5f9;
  transition: opacity 0.8s var(--ease-io), visibility 0.8s;
}

.loader__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.75rem;
}

.loader__mark {
  font-family: var(--font-display);
  font-size: clamp(2rem, 6vw, 3.5rem);
  letter-spacing: 0.3em;
  text-indent: 0.3em;
}

.loader__bar {
  width: min(240px, 40vw);
  height: 1px;
  background: rgba(243, 245, 249, 0.2);
  overflow: hidden;
}

.loader__bar span {
  display: block;
  height: 100%;
  width: 100%;
  background: #f3f5f9;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.4s linear;
}

.loader__pct {
  font-size: 0.6875rem;
  letter-spacing: 0.22em;
  color: rgba(243, 245, 249, 0.5);
  font-variant-numeric: tabular-nums;
}

body.loaded .loader {
  opacity: 0;
  visibility: hidden;
}

/* --------------------------------------------------------------- 6. Header */

.header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: var(--z-header);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  padding: clamp(1.1rem, 2.4vw, 2rem) var(--gutter);
  transition: transform 0.6s var(--ease-out), color 0.8s var(--ease-io);
  color: var(--c-fg);
}

.header.is-hidden {
  transform: translateY(-101%);
}

/* The logo is inlined so the wordmark can inherit currentColor and follow the
   running palette; only the monogram keeps a fixed brand colour. */
.wordmark { display: flex; align-items: center; }

.wordmark .logo {
  height: clamp(22px, 2vw, 28px);
  width: auto;
}

.logo__mark { fill: var(--c-accent); transition: fill 0.8s var(--ease-io); }

.nav {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.2vw, 2.5rem);
}

.nav a {
  position: relative;
  font-size: 0.6875rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  padding-block: 0.4em;
}

.nav a::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right;
  transition: transform 0.5s var(--ease-out);
}

.nav a:hover::after,
.nav a[aria-current]::after {
  transform: scaleX(1);
  transform-origin: left;
}

/* Mobile hamburger. Desktop never sees it; below 720px it replaces the
   inline nav (see the responsive section). Sits above the fixed nav panel —
   as does the wordmark — so both stay crisp over the open menu. */
.menu-toggle {
  display: none;
  position: relative;
  z-index: 2;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 2.5rem;
  height: 2.5rem;
}

.menu-toggle span {
  display: block;
  width: 22px;
  height: 1px;
  background: currentColor;
  transition: transform 0.35s var(--ease-out), opacity 0.2s linear;
}

.menu-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.menu-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.menu-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* --------------------------------------------------------- 7. Chapters rail */

.rail {
  position: fixed;
  right: calc(var(--gutter) * 0.42);
  top: 50%;
  transform: translateY(-50%);
  z-index: var(--z-rail);
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  opacity: 0;
  transition: opacity 0.6s var(--ease-io);
}

body.loaded .rail { opacity: 1; }

.rail__item {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.75rem;
  color: var(--c-faint);
  transition: color 0.4s var(--ease-io);
}

.rail__item span {
  font-size: 0.5625rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  opacity: 0;
  transform: translateX(6px);
  transition: opacity 0.4s var(--ease-out), transform 0.4s var(--ease-out);
}

.rail__item::after {
  content: "";
  width: 22px;
  height: 1px;
  background: currentColor;
  transition: width 0.5s var(--ease-out);
}

.rail__item:hover,
.rail__item.is-active { color: var(--c-fg); }

.rail__item:hover span,
.rail__item.is-active span { opacity: 1; transform: none; }

.rail__item.is-active::after { width: 40px; }

/* ------------------------------------------------ 8. Animation primitives */

/* Every reveal is one of these four, selected by [data-animation].
   JS only adds `.is-in`; the motion itself lives here. */

[data-animation] { --i: 0; --stagger: 70ms; }

[data-animation="FadeIn"] {
  opacity: 0;
  transform: translateY(1.75rem);
  transition:
    opacity var(--dur) var(--ease-out) calc(var(--i) * var(--stagger)),
    transform var(--dur) var(--ease-out) calc(var(--i) * var(--stagger));
}

[data-animation="Line"] {
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 1.2s var(--ease-io) calc(var(--i) * var(--stagger));
}

[data-animation].is-in { opacity: 1; transform: none; }

/* Split output — a mask per line, an inner element that slides up. */
.split__line { display: block; overflow: hidden; padding-bottom: 0.22em; margin-bottom: -0.22em; }
.split__inner {
  display: block;
  transform: translateY(105%);
  transition: transform 1.1s var(--ease-out) calc(var(--i) * 90ms);
  will-change: transform;
}
.is-in .split__inner { transform: none; }

/* Word atoms produced by the splitter. `.em` survives the flattening pass
   that discards the original inline <em> wrapper. */
.w { display: inline; }
.w.em,
.split__word > span.em {
  font-weight: 300;
  color: var(--c-accent);
}

.split__word { display: inline-block; overflow: hidden; vertical-align: top; }
.split__word > span {
  display: inline-block;
  transform: translateY(105%);
  opacity: 0;
  transition:
    transform 0.9s var(--ease-out) calc(var(--i) * 26ms),
    opacity 0.7s linear calc(var(--i) * 26ms);
}
.is-in .split__word > span { transform: none; opacity: 1; }

/* Images reveal behind a wipe rather than fading — reads more expensive. */
.reveal-media {
  position: relative;
  overflow: hidden;
}

.reveal-media img {
  transform: scale(1.18);
  transition: transform 1.6s var(--ease-out);
  will-change: transform;
}

.reveal-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--c-bg);
  transform-origin: bottom;
  transition: transform 1.2s var(--ease-io);
}

.reveal-media.is-in img { transform: scale(1); }
.reveal-media.is-in::after { transform: scaleY(0); }

/* ------------------------------------------------------------------ 9. Hero */

/* Centred, not bottom-anchored. Pinning to flex-end kept the block glued to
   the bottom of the viewport, so the taller the screen the further down the
   eyebrow sat — 10% from the top at 1366x768 but 61% at 2560x1440, which on a
   large monitor reads as the hero having fallen off the bottom of the page.
   Centring makes the position a function of the content rather than of the
   screen, and the top padding keeps it clear of the fixed header. */
.hero {
  position: relative;
  min-height: calc(var(--vh) * 100);
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Clears the fixed header (0..92px). Compresses on short viewports, which
     have barely 76px of slack once the content and the scroll cue are placed
     — the same squeeze the bottom-anchored version lived with. */
  padding-top: clamp(4rem, 9vh, 7rem);
  padding-bottom: clamp(2rem, 6vh, 4.5rem);
}

.hero__inner { width: 100%; }

/* Nudge below dead centre so the eyebrow lands a little above the midpoint
   instead of a fifth of the way down. Gated on height rather than clamped on
   vh: a vh clamp still resolves to something on a 768px laptop, where the
   block already fills the screen and any offset pushes the scroll cue below
   the fold. Below this breakpoint, plain centring is what fits. */
@media (min-height: 1000px) {
  .hero__inner { margin-top: clamp(0rem, 15vh, 14rem); }
}

.hero__eyebrow {
  margin-bottom: clamp(1.5rem, 4vh, 3rem);
}

.hero__title {
  margin-bottom: clamp(1.75rem, 4vh, 3rem);
}

.hero__foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: clamp(1.5rem, 5vw, 6rem);
  flex-wrap: wrap;
}

.hero__foot .body-copy { max-width: 38ch; }

.scroll-cue {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.625rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--c-muted);
  white-space: nowrap;
}

.scroll-cue::after {
  content: "";
  width: 48px;
  height: 1px;
  background: currentColor;
  transform-origin: left;
  animation: cue 2.4s var(--ease-io) infinite;
}

@keyframes cue {
  0%, 100% { transform: scaleX(0.25); opacity: 0.4; }
  50%      { transform: scaleX(1);    opacity: 1; }
}

/* ---------------------------------------------------------- 10. About */

.about__quote {
  grid-column: 1 / -1;
  margin-bottom: clamp(3rem, 8vw, 6rem);
}

.about__cols {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: clamp(2rem, 4vw, 4rem);
}

.stats {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(150px, 45%), 1fr));
  gap: clamp(1.5rem, 3vw, 3rem);
  margin-top: clamp(3rem, 8vw, 6rem);
  padding-top: clamp(2rem, 4vw, 3rem);
  border-top: var(--rule) solid var(--c-line);
}

.stat__value {
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 4.5vw, 4.25rem);
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.stat__value sup {
  font-family: var(--font-ui);
  font-size: 0.28em;
  letter-spacing: 0.1em;
  vertical-align: super;
  color: var(--c-accent);
}

/* Sized up from a caption to a proper label: under numerals that reach
   4.25rem an 11px cap-height line read as a footnote. Fluid like the rest of
   the scale, and capped short of body copy so the 0.16em tracking still says
   "label" rather than "sentence". */
.stat__label {
  margin-top: 0.75rem;
  font-size: clamp(0.8125rem, 0.74rem + 0.23vw, 0.9375rem);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c-muted);
  line-height: 1.5;
}

/* ---- about.html: founder profile -------------------------------------

   The source photograph is a cut-out on transparency, so there is no plate
   to place — it reads as a figure standing in the page rather than as an
   image in a box. That drives the whole layout: the portrait is its own
   column and sticks while the biography scrolls past it, which is also
   what stops a 360px-tall asset from being stretched to match a column of
   nine paragraphs. */

.profile {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: minmax(0, 0.55fr) minmax(0, 1fr);
  gap: clamp(2rem, 4.5vw, 4.5rem);
  align-items: start;
  /* Narrower than the container and centred in it. At full container width
     the portrait sits hard against the left margin and the pair reads as
     two separate columns rather than one figure-and-text composition.
     Widening this is what moves the portrait left: the block is centred by
     its own auto margins, so extra width is taken off both sides equally
     while the column ratio hands most of it to the biography. */
  max-width: 74rem;
  margin-inline: auto;
}

.profile__portrait {
  position: sticky;
  top: clamp(6rem, 14vh, 9rem);
  margin: 0;
}

.profile__portrait img {
  display: block;
  width: 100%;
  max-width: 26rem;
  height: auto;
  /* A cut-out on a dark page has no edge to sit on. A soft downward shadow
     in the accent gives it one without drawing a frame. */
  filter: drop-shadow(0 24px 40px rgba(0, 0, 0, 0.45));
  /* The source asset is cropped dead straight at mid-thigh. Left alone that
     razor edge reads as a mistake; fading the last eighth lets the figure
     dissolve into the page instead. */
  -webkit-mask-image: linear-gradient(to bottom, #000 82%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 82%, transparent 100%);
}

.profile__portrait figcaption {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: var(--rule) solid var(--c-line);
  max-width: 26rem;
}

.profile__name {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 2.4vw, 2rem);
  line-height: 1.1;
  letter-spacing: -0.01em;
}

.profile__role {
  margin-top: 0.5rem;
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--c-accent);
}

/* `.body-copy` caps every measure at 46ch, which here left the biography
   ending well short of its own column and a wide band of dead space down
   the right of the section. The cap is raised to just past the column's
   own width, so the text now fills it and the line length is set by the
   grid rather than by a second constraint fighting it. 74ch is at the top
   of the comfortable range — going wider means either a narrower portrait
   or a measure that is genuinely hard to track back from line to line. */
.profile__bio { max-width: 74ch; }

.profile__bio > p + p { margin-top: 1.25em; }

/* ---- about.html: the three operating principles ----------------------

   Each card is image over rule over copy over link. The source artwork is
   only 278×138, so the frame is held to a wide letterbox at a modest
   column width — enlarging it past its native size is what would make it
   look cheap, not the ratio. */

.pillars {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: clamp(2rem, 4vw, 3.5rem);
}

.pillar { display: flex; flex-direction: column; }

.pillar .reveal-media {
  aspect-ratio: 278 / 138;
  margin-bottom: 1.5rem;
}

.pillar .reveal-media img { width: 100%; height: 100%; object-fit: cover; }

.pillar__title {
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 1.9vw, 1.6rem);
  line-height: 1.15;
  padding-top: 1.25rem;
  border-top: var(--rule) solid var(--c-line);
}

.pillar__body {
  margin-top: 0.9rem;
  color: var(--c-muted);
  /* Pushes the link to the bottom of every card, so the three links line
     up across the row even though the paragraphs differ in length. */
  flex: 1;
}

.pillar__link { margin-top: 1.75rem; }

/* ---- about.html: global reach ----------------------------------------

   The homepage renders these same seven markets as a canvas globe beside
   the list. This page follows the source instead: Burkard's flat dotted
   map, centred, with the list stacked under it. The map ships with the
   markers already drawn on it, so nothing here has to place them — but it
   was drawn for a light page (landmass in #0c1933), and is repainted to
   the foreground tone at build time rather than filtered in CSS, which
   would drag the accent markers off-hue with it. */

.reach-map {
  grid-column: 1 / -1;
  display: grid;
  justify-items: center;
}

.reach-map__img {
  display: block;
  width: 100%;
  max-width: 72rem;
  height: auto;
  opacity: 0.9;
}

@media (max-width: 900px) {
  .profile { grid-template-columns: minmax(0, 1fr); }
  .profile__portrait { position: static; justify-self: center; text-align: center; }
  .profile__portrait figcaption { margin-inline: auto; }
}

/* -------------------------------------------------------- 11. Services */

.services { grid-column: 1 / -1; }

.service {
  position: relative;
  display: grid;
  grid-template-columns: 4rem 1fr;
  gap: clamp(1rem, 3vw, 3rem);
  align-items: start;
  padding-block: clamp(1.75rem, 3.5vw, 3rem);
  border-bottom: var(--rule) solid var(--c-line);
  transition: padding-left 0.6s var(--ease-out);
}

.service:first-child { border-top: var(--rule) solid var(--c-line); }
.service:hover { padding-left: clamp(0.5rem, 1.5vw, 1.5rem); }

.service__index {
  font-size: 0.6875rem;
  letter-spacing: 0.18em;
  color: var(--c-faint);
  padding-top: 0.65em;
  font-variant-numeric: tabular-nums;
}

.service__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: clamp(1rem, 3vw, 3rem);
  align-items: start;
}

.service__name {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 3.4vw, 3rem);
  line-height: 1.05;
  transition: color 0.5s var(--ease-io);
}

.service:hover .service__name { color: var(--c-accent); }

.service__desc { color: var(--c-muted); max-width: 46ch; }

.service__meta {
  margin-top: 1rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.25rem;
  font-size: 0.625rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c-faint);
}

/* ------------------------------------------------------- 12. Collection */

/* Corner labels on the gallery covers. The covers are transformed planes,
   so without an explicit z-index a label is a tree-order sibling and loses —
   it renders, but under the photograph. */
.work__tag {
  position: absolute;
  top: 0.75rem;
  left: 0.75rem;
  z-index: 2;
  padding: 0.35em 0.7em;
  font-size: 0.5625rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  background: color-mix(in srgb, var(--c-bg) 72%, transparent);
  color: var(--c-fg);
  backdrop-filter: blur(6px);
}

/* Marks a project that is announced but not yet open. Same plate as
   .work__tag, mirrored to the opposite corner and tinted with the accent so
   it is legible as a different kind of label rather than a second type tag. */
.work__status {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  z-index: 2;
  padding: 0.35em 0.7em;
  font-size: 0.5625rem;   /* same type and padding as .work__tag, so the two
                             corner labels always render the same height */
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
  background: color-mix(in srgb, var(--c-accent) 20%, color-mix(in srgb, var(--c-bg) 72%, transparent));
  color: var(--c-accent);
  /* inset ring, not a border — a border would make this 2px taller than
     .work__tag, and the two corner labels must match height exactly */
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c-accent) 38%, transparent);
  backdrop-filter: blur(6px);
}

/* Filters (collection page) */
.filters {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.75rem;
  margin-bottom: clamp(2rem, 5vw, 3.5rem);
}

.filters button {
  position: relative;
  font-size: 0.6875rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--c-faint);
  padding-block: 0.5em;
  transition: color 0.4s var(--ease-io);
}

.filters button::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 100%; height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.5s var(--ease-out);
}

.filters button:hover { color: var(--c-muted); }
.filters button[aria-pressed="true"] { color: var(--c-fg); }
.filters button[aria-pressed="true"]::after { transform: scaleX(1); }

/* ---------------------------------------------------------- 12b. Coverflow
   Two layers, same markup. Before JS: a horizontal scroll-snap filmstrip
   with every caption visible inline — a perfectly usable gallery on its own.
   After coverflow.js adds `.is-enhanced`: the rules below it take over,
   turning each `.coverflow__cover` into an independently-transformed plane
   under one shared perspective, and each `.coverflow__caption` into an
   absolutely-positioned layer stacked exactly on top of the others, crossfaded
   by `.is-active` rather than driven by JS text-swapping. */

.coverflow {
  grid-column: 1 / -1;
  outline: none;
}

.coverflow__track {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* ---- base: horizontal scroll-snap gallery, no JS required ------------- */

.coverflow__track {
  display: flex;
  gap: clamp(1.25rem, 3vw, 2.5rem);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  padding-block: 0.25rem 1rem;
  -webkit-overflow-scrolling: touch;
}

.coverflow__item {
  flex: 0 0 auto;
  width: clamp(200px, 34vw, 320px);
  scroll-snap-align: center;
}

.coverflow__cover {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: color-mix(in srgb, var(--c-fg) 6%, transparent);
}

.coverflow__cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.coverflow__reflection { display: none; }
.coverflow__hit { display: none; }   /* JS-injected; irrelevant to the base gallery */

.coverflow__caption {
  margin-top: 1.1rem;
  text-align: left;
}

.coverflow__name {
  font-family: var(--font-display);
  font-size: 1.375rem;
  line-height: 1.15;
  margin-bottom: 0.5rem;
}

.coverflow__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1rem;
  font-size: 0.625rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c-faint);
  margin-bottom: 0.6rem;
}

.coverflow__sub { color: var(--c-accent); }

.coverflow__desc {
  font-size: 0.8125rem;
  line-height: 1.6;
  color: var(--c-muted);
  max-width: 40ch;
}

.coverflow__controls { display: none; }   /* meaningless without JS to drive it */

/* ---- enhanced: JS-driven 3D coverflow --------------------------------- */

.coverflow.is-enhanced {
  --cf-cover: clamp(270px, 40.5vw, 510px);
  --cf-stage-h: calc(var(--cf-cover) * 1.42);
  --cf-caption-h: clamp(9rem, 16vw, 11rem);
}

/* Homepage teaser: a smaller cover so the short strip sits comfortably inside
   one scroll section rather than dominating it edge to edge. Spacing itself
   comes from coverflow.js's `data-step` (see layout()), not from here. */
.coverflow.is-enhanced.coverflow--teaser {
  --cf-cover: clamp(240px, 30vw, 390px);
}

.coverflow.is-enhanced .coverflow__track {
  position: relative;
  display: block;
  overflow: visible;
  perspective: 2100px;
  perspective-origin: 50% 40%;
  height: calc(var(--cf-stage-h) + var(--cf-caption-h));
  padding: 0;
  gap: 0;
  touch-action: pan-y;   /* vertical page scroll stays native; horizontal is ours */
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
}

.coverflow.is-enhanced .coverflow__track:active { cursor: grabbing; }

.coverflow.is-enhanced .coverflow__item {
  position: absolute;
  inset: 0;
  width: auto;
  scroll-snap-align: none;
  /* `perspective` on .coverflow__track only reaches DIRECT children's own
     transforms. Without preserve-3d here, this untransformed li flattens
     .coverflow__cover's rotateY/translateZ into 2D one level down — the
     rotation silently no-ops and every cover renders dead flat. */
  transform-style: preserve-3d;
  /* preserve-3d also makes each li its own stacking context, so a z-index
     set two levels down on .coverflow__cover only ranks within that li —
     cross-item paint order falls back to raw DOM order instead of "nearest
     to the viewer wins". coverflow.js sets z-index here, on the li itself,
     where item-to-item comparison is actually meaningful.

     Every li also has inset:0 (they all fully overlap, by design), so
     without this the topmost li in DOM order would swallow every click
     across the whole track regardless of which cover is really on top.
     JS restores pointer-events on whichever cover is currently visible. */
  pointer-events: none;
}

.coverflow.is-enhanced .coverflow__cover {
  position: absolute;
  left: 50%;
  top: calc(var(--cf-stage-h) / 2);
  width: var(--cf-cover);
  transform-style: preserve-3d;
  will-change: transform, opacity, filter;
  box-shadow: 0 30px 60px -20px rgba(0, 0, 0, 0.55);
  transition: box-shadow 0.4s var(--ease-io);
}

.coverflow.is-enhanced .coverflow__item.is-active .coverflow__cover {
  box-shadow: 0 40px 80px -20px rgba(0, 0, 0, 0.65), 0 0 0 1px rgba(255, 255, 255, 0.08);
}

/* Flat, unrotated click/touch target sized to this item's slot — see the
   buildHitTargets() comment in coverflow.js for why the visible 3D cover
   isn't used directly for hit-testing. Invisible; the cover is the thing
   the user actually sees and the cursor still highlights on hover via
   [data-cursor], which the cover — not this span — carries. */
.coverflow.is-enhanced .coverflow__hit {
  display: block;
  position: absolute;
  left: 50%;
  top: 0;
  width: var(--cf-cover);
  height: var(--cf-stage-h);
  cursor: pointer;
}

/* The floor reflection: same artwork, flipped and masked to a fade — driven
   entirely by a `--cf-src` custom property set once inline per item, so no
   duplicate <img> or JS is needed to produce it. It's a child of the cover,
   so it inherits the same rotateY/translateZ and tilts convincingly with it. */
.coverflow.is-enhanced .coverflow__reflection {
  display: block;
  position: absolute;
  top: 100%;
  left: 0;
  width: 100%;
  height: 65%;
  background-image: var(--cf-src);
  background-size: cover;
  background-position: center;
  transform: scaleY(-1);
  -webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.32), transparent 70%);
          mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.32), transparent 70%);
  pointer-events: none;
}

/* A sibling of .coverflow__cover, not a child — it does NOT inherit the
   cover's 3D transform, so the text stays flat and readable while the art
   behind it rotates. */
.coverflow.is-enhanced .coverflow__caption {
  position: absolute;
  left: 50%;
  top: calc(var(--cf-stage-h) + 1.25rem);
  transform: translateX(-50%);
  width: min(46ch, 90%);
  margin-top: 0;
  text-align: center;
  opacity: 0;
  transition: opacity 0.5s var(--ease-io);
  pointer-events: none;
}

.coverflow.is-enhanced .coverflow__item.is-active .coverflow__caption {
  opacity: 1;
  pointer-events: auto;
}

.coverflow.is-enhanced .coverflow__meta,
.coverflow.is-enhanced .coverflow__desc {
  justify-content: center;
  margin-left: auto;
  margin-right: auto;
}

.coverflow.is-enhanced .coverflow__name {
  font-size: clamp(1.5rem, 2.6vw, 2rem);
}

.coverflow__controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(1rem, 3vw, 2rem);
  margin-top: 1rem;
}

.coverflow.is-enhanced .coverflow__controls { display: flex; }

.coverflow__arrow {
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--c-line);
  border-radius: 50%;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--c-muted);
  transition: color 0.3s var(--ease-io), border-color 0.3s var(--ease-io);
}

.coverflow__arrow:hover { color: var(--c-fg); border-color: var(--c-fg); }

.coverflow__index {
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  color: var(--c-faint);
  min-width: 5.5em;
  text-align: center;
}

.coverflow__scrubber {
  -webkit-appearance: none;
  appearance: none;
  width: min(320px, 40vw);
  height: 1px;
  background: var(--c-line);
  outline: none;
}

.coverflow__scrubber::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--c-accent);
  cursor: pointer;
}

.coverflow__scrubber::-moz-range-track { background: var(--c-line); height: 1px; }

.coverflow__scrubber::-moz-range-thumb {
  width: 10px;
  height: 10px;
  border: 0;
  border-radius: 50%;
  background: var(--c-accent);
  cursor: pointer;
}

/* ------------------------------------------------------------------------ */

/* ------------------------------------------------------- 13. Global reach */

.provenance__layout {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
}

.globe {
  position: relative;
  aspect-ratio: 1;
  width: 100%;
}

.globe canvas { width: 100%; height: 100%; }

.globe__tip {
  position: absolute;
  top: 0; left: 0;
  padding: 0.4em 0.75em;
  font-size: 0.5625rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
  background: var(--c-fg);
  color: var(--c-bg);
  opacity: 0;
  transform: translate(-50%, -180%);
  transition: opacity 0.3s var(--ease-io);
  pointer-events: none;
}

.globe__tip.is-on { opacity: 1; }

.stations { display: grid; gap: 0; }

.station {
  display: grid;
  grid-template-columns: 1.6rem minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 1rem;
  padding-block: 0.7rem;
  border-bottom: var(--rule) solid var(--c-line);
  cursor: pointer;
  transition: color 0.4s var(--ease-io), padding-left 0.5s var(--ease-out);
  color: var(--c-muted);
}

.station:first-child { border-top: var(--rule) solid var(--c-line); }

.station:hover,
.station.is-active {
  color: var(--c-fg);
  padding-left: 0.5rem;
}

.station__idx {
  font-size: 0.625rem;
  color: var(--c-faint);
  font-variant-numeric: tabular-nums;
}

.station__name {
  font-size: 0.9375rem;
  letter-spacing: 0.06em;
}

.station__type {
  font-size: 0.5625rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--c-faint);
}

/* ---------------------------------------------------------- 14. Impact */

.impact__lead { grid-column: 1 / -1; margin-bottom: clamp(2.5rem, 6vw, 4.5rem); }

/* ------------------------------------------- 14b. Execution process flow

   The five-step diagram from the Services page: each step feeds the next,
   and the last drops into "Execute, Execute, Execute" underneath.

   The connectors are pseudo-elements on the steps rather than markup of
   their own — an arrow between two boxes is decoration, and giving it
   elements would put it in the accessibility tree and in the reading
   order. Both pseudos belong to the step to the LEFT of each gap: ::after
   draws the shaft, ::before the head. The step number lives in a real
   <span> so ::before stays free for this.                              */

.flow { grid-column: 1 / -1; --flow-gap: clamp(0.85rem, 1.8vw, 1.75rem); }

.flow__title {
  text-align: center;
  max-width: 22ch;
  margin: 0 auto clamp(2.5rem, 6vw, 4rem);
}

.flow__steps {
  display: flex;
  align-items: stretch;
  justify-content: center;
  gap: var(--flow-gap);
}

/* The five steps ramp neutral → green as they approach the green light, so
   the diagram reads as approval being earned rather than as five equal
   boxes. One hue ramp, not five arbitrary colours.

   The route matters. Straight grey-to-green passes through 60–140°, which
   the palette deliberately skips — yellow-green at this lightness reads as
   army olive. So the ramp goes round the cool side instead:
   slate → steel → teal → emerald, landing at 150° where green is
   unmistakable but still sits in the site's cool register.

   Held deliberately quiet: the tint drives the hairline border, the step
   number and the connector, plus a background wash at 6% alpha. The boxes
   are tinted, not filled. Unlike everything else on the page these are
   fixed hues rather than palette tokens — the value here is the signal
   they carry, which has to survive whatever hue the section is passing
   through. */
.flow__step {
  --flow-tint: 214 10% 62%;
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: clamp(1.1rem, 2vw, 1.6rem) clamp(0.9rem, 1.6vw, 1.4rem);
  border: var(--rule) solid hsl(var(--flow-tint) / 0.4);
  border-radius: 0.75rem;
  background: hsl(var(--flow-tint) / 0.06);
  transition: border-color 0.5s var(--ease-io), background-color 0.5s var(--ease-io);
}

.flow__step:nth-child(1) { --flow-tint: 214 10% 62%; }   /* slate — no colour yet */
.flow__step:nth-child(2) { --flow-tint: 198 20% 61%; }   /* steel */
.flow__step:nth-child(3) { --flow-tint: 182 26% 59%; }   /* teal */
.flow__step:nth-child(4) { --flow-tint: 166 34% 56%; }   /* sea green */
.flow__step:nth-child(5) { --flow-tint: 150 46% 54%; }   /* green light */

/* The payoff step earns a little more presence than the four before it,
   without becoming a filled green box. */
.flow__step:nth-child(5) {
  border-color: hsl(var(--flow-tint) / 0.55);
  background: hsl(var(--flow-tint) / 0.1);
}

.flow__step:hover {
  border-color: hsl(var(--flow-tint) / 0.75);
  background: hsl(var(--flow-tint) / 0.12);
}

.flow__num {
  font-size: 0.625rem;
  letter-spacing: 0.2em;
  color: hsl(var(--flow-tint) / 0.95);
}

.flow__label {
  font-family: var(--font-display);
  font-size: clamp(0.9375rem, 1.15vw, 1.125rem);
  line-height: 1.25;
  text-wrap: balance;
}

/* shaft */
.flow__step:not(:last-child)::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 100%;
  width: var(--flow-gap);
  height: var(--rule);
  /* Each connector carries the tint of the step it leaves, so the ramp
     runs continuously along the row instead of resetting between boxes. */
  background: hsl(var(--flow-tint) / 0.5);
}

/* head */
.flow__step:not(:last-child)::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 100%;
  transform: translate(calc(var(--flow-gap) - 5px), -50%);
  border: 4px solid transparent;
  border-left-color: hsl(var(--flow-tint) / 0.7);
  border-right-width: 0;
}

/* The elbow out of the LAST step, across, and down into the execute panel —
   the process is sequential, so the drop has to leave step 05 rather than
   fall out of the middle of the row (which reads as step 03 branching).

   The row is five equal flex items with one --flow-gap between each, so the
   last step's centre sits (W - 4g)/10 in from the right edge — i.e.
   10% - 0.4g. That expression is tied to there being five steps; adding one
   means revisiting it. */
.flow__join {
  --flow-last-centre: calc(10% - 0.4 * var(--flow-gap));
  position: relative;
  height: clamp(2.5rem, 5vw, 3.75rem);
}

.flow__join span {
  position: absolute;
  background: var(--c-line);
}

.flow__join-down {
  top: 0;
  height: 50%;
  width: var(--rule);
  right: var(--flow-last-centre);
}

.flow__join-across {
  top: 50%;
  height: var(--rule);
  right: var(--flow-last-centre);
  left: 50%;
}

.flow__join-drop {
  top: 50%;
  bottom: 0;
  width: var(--rule);
  left: 50%;
}

.flow__join-drop::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translate(-50%, 50%);
  border: 5px solid transparent;
  border-top-color: var(--c-faint);
  border-bottom-width: 0;
}

/* The terminal panel — the one filled block in the diagram, so the process
   visibly resolves somewhere instead of trailing off. Squares at opposing
   corners echo the source page's treatment. */
.flow__execute {
  position: relative;
  max-width: 32rem;
  margin: 0 auto;
  padding: clamp(1.25rem, 2.6vw, 1.9rem) clamp(1.5rem, 3vw, 2.5rem);
  text-align: center;
  font-family: var(--font-display);
  font-size: clamp(1.0625rem, 1.6vw, 1.375rem);
  letter-spacing: 0.02em;
  color: var(--c-accent);
  background: color-mix(in srgb, var(--c-accent) 12%, transparent);
  border: var(--rule) solid color-mix(in srgb, var(--c-accent) 45%, transparent);
}

.flow__execute::before,
.flow__execute::after {
  content: "";
  position: absolute;
  width: 0.75rem;
  height: 0.75rem;
  background: var(--c-accent);
}

.flow__execute::before { top: -1px; left: -1px; }
.flow__execute::after { bottom: -1px; right: -1px; }

/* ---------------------------------------- 15. Family office, disclaimer */

/* A label long enough to wrap on a phone needs real leading; .label is
   set solid (line-height: 1) for baseline alignment on one line. */
.label--wrap { line-height: 1.6; }

.fo-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2.5rem;
  margin-top: 2.5rem;
}

/* family-office.html: discipline names run to five or six words, so they
   step down from the homepage's one-word service size. */
.services--long .service__name { font-size: clamp(1.35rem, 2.2vw, 2.125rem); }

/* The one-line kicker under a discipline's name. */
.service__kicker {
  margin-top: 1rem;
  color: var(--c-accent);
  max-width: 34ch;
}

.body-copy strong { color: var(--c-fg); font-weight: 400; }

.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 2rem;
}

.tags li {
  padding: 0.45em 0.9em;
  border: var(--rule) solid var(--c-line);
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c-fg);
}

/* disclaimer.html: long-form legal text in one reading column. */
.legal {
  grid-column: 1 / -1;
  max-width: 72ch;
  color: var(--c-muted);
}

/* The source headings are set in capitals, so they take the label
   treatment rather than the display face. */
.legal h2 {
  font-family: var(--font-ui);
  font-size: clamp(0.875rem, 0.78rem + 0.28vw, 1.0625rem);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0.12em;
  text-wrap: wrap;   /* balance splits "BROKER-DEALER" across lines at this measure */
  color: var(--c-fg);
  margin: 3.5rem 0 1.25rem;
  padding-top: 1.25rem;
  border-top: var(--rule) solid var(--c-line);
}

.legal h2:first-child { margin-top: 0; }
.legal p + p, .legal p + ul, .legal ul + p { margin-top: 1em; }
.legal ul { list-style: disc; padding-left: 1.25em; }
.legal li + li { margin-top: 0.4em; }
.legal strong { color: var(--c-fg); font-weight: 400; }
.legal__caps { margin-top: 3.5rem; color: var(--c-fg); }
.legal__caps p + p { margin-top: 1em; }

/* ----------------------------------------------------------- 16. Footer */

.footer {
  position: relative;
  padding-top: clamp(4rem, 10vh, 8rem);
  padding-bottom: clamp(4rem, 10vh, 7rem);
}

.footer__cta { grid-column: 1 / -1; margin-bottom: clamp(3rem, 8vw, 6rem); }

.footer__offices {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: clamp(1.5rem, 3vw, 3rem);
  padding-top: clamp(2rem, 4vw, 3rem);
  border-top: var(--rule) solid var(--c-line);
}

.office h3 {
  font-family: var(--font-ui);
  font-size: 0.6875rem;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  margin-bottom: 1rem;
  color: var(--c-muted);
}

.office address {
  font-style: normal;
  color: var(--c-muted);
  font-size: 0.875rem;
  line-height: 1.9;
}

.office a { transition: color 0.3s var(--ease-io); }
.office a:hover { color: var(--c-accent); }

.footer__legal {
  grid-column: 1 / -1;
  margin-top: clamp(3rem, 7vw, 5rem);
  padding-top: clamp(2rem, 4vw, 3rem);
  border-top: var(--rule) solid var(--c-line);
  color: var(--c-muted);
  font-size: 0.8125rem;
  line-height: 1.7;
}

.footer__legal h3 {
  font-family: var(--font-ui);
  font-size: 0.6875rem;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  margin-bottom: 1rem;
}

.footer__legal p { max-width: 110ch; }
.footer__legal p + p { margin-top: 1em; }

.footer__bottom {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 2rem;
  margin-top: clamp(3rem, 7vw, 5rem);
  padding-top: 1.5rem;
  border-top: var(--rule) solid var(--c-line);
  /* Carries the copyright line, so it is sized and toned to stay genuinely
     readable — not the faint treatment the rest of the smallprint gets. */
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--c-muted);
}

.footer__bottom nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
}

.footer__bottom nav span { color: var(--c-muted); }

.footer__bottom a:hover { color: var(--c-fg); }

.link-underline {
  display: inline-flex;
  align-items: center;
  gap: 0.75em;
  font-size: 0.6875rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  padding-bottom: 0.4em;
  border-bottom: 1px solid var(--c-line);
  transition: border-color 0.5s var(--ease-io), color 0.4s var(--ease-io);
}

.link-underline:hover {
  border-color: var(--c-fg);
  color: var(--c-accent);
}

/* ------------------------------------------------------- 16b. Estimate form */

.estimate-form {
  grid-column: 1 / -1;
  width: 100%;
  max-width: 46rem;
  margin-inline: auto;
  padding: clamp(2rem, 5vw, 3.5rem);
  background: rgba(243, 245, 249, 0.05);
  border: var(--rule) solid var(--c-line);
  border-radius: 1.25rem;
  backdrop-filter: blur(20px) saturate(1.1);
  -webkit-backdrop-filter: blur(20px) saturate(1.1);
  box-shadow: 0 2rem 5rem rgba(0, 0, 0, 0.25);
}

.form-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0 clamp(1.5rem, 3vw, 3rem);
}

.form-row .field { flex: 1 1 16rem; }

.field { margin-bottom: clamp(1.75rem, 4vw, 2.5rem); }

.field label,
.field__label-static {
  display: block;
  font-size: 0.6875rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--c-muted);
  margin-bottom: 0.75em;
}

/* Every text-entry type the forms use, rather than an enumerated few —
   a new input type (tel, url) otherwise silently falls back to the
   browser's opaque default box and breaks the underline treatment. */
.field input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
.field textarea {
  display: block;
  width: 100%;
  font: inherit;
  font-size: 1.0625rem;
  color: var(--c-fg);
  background: transparent;
  border: 0;
  border-bottom: var(--rule) solid var(--c-line);
  border-radius: 0;
  padding-block: 0.6em;
  transition: border-color 0.4s var(--ease-io);
}

.field textarea { resize: vertical; min-height: 8em; }

.field input::placeholder,
.field textarea::placeholder { color: var(--c-faint); }

.field input:focus-visible,
.field textarea:focus-visible {
  outline: none;
  border-color: var(--c-accent);
}

.field.is-invalid input,
.field.is-invalid textarea { border-color: #e4a2a2; }

.field__error {
  min-height: 1.2em;
  margin-top: 0.5em;
  font-size: 0.75rem;
  color: #e4a2a2;
  opacity: 0;
  transition: opacity 0.3s var(--ease-io);
}

.field.is-invalid .field__error { opacity: 1; }

.field__optional {
  text-transform: none;
  letter-spacing: 0.06em;
  color: var(--c-faint);
}

/* Checkbox — the native control restyled rather than replaced, so it keeps
   its own focus ring, keyboard behaviour and screen-reader semantics.
   Selector carries two classes deliberately: this is a <label> inside
   .field, so the uppercase/tracked `.field label` rule (0,1,1) would
   otherwise out-specify a bare `.checkbox` (0,1,0) and shout the consent
   copy in caps. */
.field .checkbox {
  display: flex;
  align-items: flex-start;
  gap: 0.85rem;
  margin-bottom: 0;
  font-size: 0.8125rem;
  line-height: 1.6;
  letter-spacing: 0;
  text-transform: none;
  color: var(--c-muted);
  cursor: pointer;
}

.field .checkbox input {
  flex: none;
  width: 1.05rem;
  height: 1.05rem;
  margin: 0.2rem 0 0;
  /* Without an explicit dark scheme the unchecked box paints as a stark
     white slab against the dark page. */
  color-scheme: dark;
  accent-color: var(--c-accent);
  cursor: pointer;
}

.field.is-invalid .checkbox { color: #e4a2a2; }

.file-control {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem 1.5rem;
}

.file-control__name {
  font-size: 0.8125rem;
  color: var(--c-muted);
  word-break: break-all;
}

.file-control__remove {
  font-size: 0.6875rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--c-faint);
  text-decoration: underline;
  text-underline-offset: 0.2em;
  transition: color 0.4s var(--ease-io);
}

.file-control__remove:hover { color: var(--c-fg); }

.description {
  font-size: 0.75rem;
  color: var(--c-faint);
  margin-top: 0.75em;
}

/* Captcha — the provider renders an iframe of its own into this slot, so
   almost nothing here is ours to style. Reserve the height it will take so
   the form does not jolt when the widget finally arrives, and let the box
   scroll rather than spill: the iframe has a fixed pixel width that does not
   respond to the viewport, and on a narrow phone it is wider than the column.
   Scrolling a 5px overhang is inelegant but honest; scaling the widget down
   with a transform makes its own text illegible and its checkbox awkward to
   hit, and clipping it would cut off the provider's privacy links. */
.captcha {
  min-height: 78px;      /* the standard widget box, checkbox variant */
  max-width: 100%;
  overflow-x: auto;
}

/* Nothing has loaded — either no site key is configured yet, or the provider
   is unreachable. Either way say so plainly instead of leaving a silent gap
   where a required control should be. */
.captcha__notice {
  font-size: 0.75rem;
  color: var(--c-faint);
  border: var(--rule) dashed var(--c-line);
  padding: 1.25rem 1rem;
  text-align: center;
}

/* Honeypot — hidden from every visitor, sighted or not, and unreachable by
   keyboard. A `display:none` field is exactly what tells a scripted bot to
   skip it, so this stays visually and structurally off-canvas instead. */
.hp-field {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem 1.5rem;
  margin-top: clamp(0.5rem, 2vw, 1rem);
}

.btn-submit {
  display: inline-flex;
  align-items: center;
  gap: 0.75em;
  padding: 0.9em 1.75em;
  font-size: 0.6875rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  border: var(--rule) solid var(--c-line);
  border-radius: 999px;
  transition: border-color 0.4s var(--ease-io), background-color 0.4s var(--ease-io), color 0.4s var(--ease-io);
}

.btn-submit svg { width: 1em; height: 1em; transition: transform 0.4s var(--ease-out); }

.btn-submit:hover {
  border-color: var(--c-fg);
  background: var(--c-fg);
  color: var(--c-bg);
}

.btn-submit:hover svg { transform: translateX(3px); }

.btn-submit:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-submit:disabled:hover {
  border-color: var(--c-line);
  background: transparent;
  color: inherit;
}
.btn-submit:disabled:hover svg { transform: none; }

.form-status {
  font-size: 0.8125rem;
  color: var(--c-muted);
  min-height: 1.2em;
}

.form-status.is-success { color: var(--c-accent); }
.form-status.is-error { color: #e4a2a2; }

/* ----------------------------------------------------------- 17. Cursor */

.cursor {
  position: fixed;
  top: 0; left: 0;
  z-index: var(--z-cursor);
  width: 26px; height: 26px;
  margin: -13px 0 0 -13px;
  border: 1px solid var(--c-fg);
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  mix-blend-mode: difference;
  transition: opacity 0.3s linear, width 0.35s var(--ease-out),
              height 0.35s var(--ease-out), margin 0.35s var(--ease-out),
              background-color 0.35s var(--ease-out);
  will-change: transform;
}

.cursor.is-visible { opacity: 0.75; }

.cursor.is-hover {
  width: 64px; height: 64px;
  margin: -32px 0 0 -32px;
  background: rgba(243, 245, 249, 0.12);
}

/* ------------------------------------------------------- 18. Responsive */

@media (max-width: 1024px) {
  .rail { display: none; }

  .service__body,
  .provenance__layout {
    grid-template-columns: minmax(0, 1fr);
  }

  /* Five boxes abreast stop being legible well before the phone
     breakpoint, so the flow turns vertical here: the connectors rotate a
     quarter turn and the diagram simply reads top-to-bottom instead. */
  .flow__steps {
    flex-direction: column;
    align-items: stretch;
    max-width: 30rem;
    margin-inline: auto;
  }

  .flow__step { flex: 0 0 auto; }

  .flow__step:not(:last-child)::after {
    top: 100%;
    left: 50%;
    width: var(--rule);
    height: var(--flow-gap);
  }

  .flow__step:not(:last-child)::before {
    top: 100%;
    left: 50%;
    transform: translate(-50%, calc(var(--flow-gap) - 5px));
    border: 4px solid transparent;
    border-top-color: var(--c-faint);
    border-bottom-width: 0;
  }

  /* Stacked, the last step is already centred over the panel, so the elbow
     collapses to a straight drop. */
  .flow__join-down,
  .flow__join-across { display: none; }
  .flow__join-drop { top: 0; }
}

@media (max-width: 720px) {
  .cursor { display: none; }

  .menu-toggle { display: inline-flex; }
  .wordmark { position: relative; z-index: 2; }

  /* The nav becomes a full-screen overlay, toggled by the hamburger. It is
     a fixed child of the fixed header, which has no transform while the
     menu can be open (scroll is locked), so inset: 0 means the viewport. */
  .nav {
    position: fixed;
    inset: 0;
    z-index: 1;
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    gap: 1.75rem;
    padding-inline: var(--gutter);
    background: color-mix(in srgb, var(--c-bg) 90%, transparent);
    -webkit-backdrop-filter: blur(20px) saturate(1.1);
    backdrop-filter: blur(20px) saturate(1.1);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.4s var(--ease-io), visibility 0.4s;
  }

  .nav.is-open {
    opacity: 1;
    visibility: visible;
  }

  .nav a {
    font-size: 1.0625rem;
    letter-spacing: 0.25em;
  }

  .nav .nav__cta {
    margin-top: 1rem;
    color: var(--c-accent);
  }

  .hero__foot { flex-direction: column; align-items: flex-start; }

  .service { grid-template-columns: 2.5rem 1fr; }
}

/* ------------------------------------------------- 19. Reduced motion */

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

  [data-animation],
  .split__inner,
  .split__word > span {
    opacity: 1 !important;
    transform: none !important;
  }

  .reveal-media::after { transform: scaleY(0); }
  .reveal-media img { transform: none; }
}
