/* =========================================================================
   Beamtouch corporate site. Phase 7R, THE LIVING SIGNAL.
   One quiet signal begins in the hero, travels the page on a continuous rail,
   and changes state at each section: emergence, philosophy, branching, discipline,
   darkness, threshold. The page is one experience, not stacked sections.

   Preserved locks: the eight-color palette (dark is built from those eight plus
   their alpha), one color gradient (the beam; the edge fade is an alpha mask),
   five type sizes, radii 12/14/16 plus 2px and 50%, the 2px Cinnamon focus ring,
   seven self-hosted faces, reduced-motion, zero external runtime requests.
   ========================================================================= */

/* ===================== 0. FONTS (locked, self-hosted) ===================== */
@font-face { font-family: "Sora"; font-style: normal; font-weight: 400; font-display: swap; src: url("/fonts/sora-latin-400.woff2") format("woff2"); }
@font-face { font-family: "Sora"; font-style: normal; font-weight: 600; font-display: swap; src: url("/fonts/sora-latin-600.woff2") format("woff2"); }
@font-face { font-family: "Hanken Grotesk"; font-style: normal; font-weight: 400; font-display: swap; src: url("/fonts/hanken-grotesk-latin-400.woff2") format("woff2"); }
@font-face { font-family: "Hanken Grotesk"; font-style: normal; font-weight: 500; font-display: swap; src: url("/fonts/hanken-grotesk-latin-500.woff2") format("woff2"); }
@font-face { font-family: "Hanken Grotesk"; font-style: normal; font-weight: 600; font-display: swap; src: url("/fonts/hanken-grotesk-latin-600.woff2") format("woff2"); }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 400; font-display: swap; src: url("/fonts/ibm-plex-mono-latin-400.woff2") format("woff2"); }
@font-face { font-family: "IBM Plex Mono"; font-style: normal; font-weight: 500; font-display: swap; src: url("/fonts/ibm-plex-mono-latin-500.woff2") format("woff2"); }

/* ============================ 1. FOUNDATIONS ============================ */
:root {
  /* Base tokens, light. The locked theme model flips these directly in dark mode. */
  --stone:    #EEEBE4;
  --surface:  #F8F6F2;
  --panel:    #E6E2DA;
  --hairline: #D8D3C9;
  --cinnamon: #B5612C;
  --gold:     #E6B15C;
  --carbon:   #211E1A;
  --clay:     #6B655C;

  /* Role tokens: LIGHT */
  --bg:            var(--stone);
  --bg-alt:        var(--panel);
  --hair:          var(--hairline);
  --ink:           var(--carbon);
  --ink-2:         var(--carbon);
  --muted:         var(--clay);
  --faint:         var(--clay);
  --hover-bg:      var(--panel);
  --accent:        var(--cinnamon);
  --node:          var(--gold);
  --btn-bg:        var(--carbon);
  --btn-fg:        var(--stone);
  --btn-border:    var(--carbon);
  --ink-bg:        var(--carbon);
  --on-ink:        var(--stone);
  --on-ink-muted:  var(--hairline);

  --carbon-a06: rgba(33, 30, 26, .06);
  --carbon-a08: rgba(33, 30, 26, .08);
  --carbon-a12: rgba(33, 30, 26, .12);
  --stone-a00:  rgba(238, 235, 228, 0);
  --gold-a70:   rgba(230, 177, 92, .70);
  --rail:       rgba(181, 97, 44, .28);      /* signal rail line, light */
  --on-ink-a22: rgba(216, 211, 201, .22);

  --shadow:        0 1px 2px var(--carbon-a06), 0 12px 30px var(--carbon-a08);
  --art-veil-hero: rgba(238, 235, 228, .18);
  --art-veil-house: rgba(238, 235, 228, .12);
  --light-art-op: 1;   /* light artworks show fully in light; faded to texture in dark */
  --statement-scrim: rgba(33, 30, 26, .42);
  --statement-scrim-2: rgba(33, 30, 26, .0);
  --contact-veil:  rgba(238, 235, 228, .0);
  --contact-art-opacity: 1;
  --gl-opacity: 0.42;

  /* ---- HERO MATERIALS: LIGHT ----
     Illuminated silk. A warm ivory atmosphere lit from the lower left, pale silk-white
     fibres with Warm Gold and Cinnamon traces, and an ivory glass plane at roughly
     half opacity so the field keeps flowing behind the words. This is deliberately not
     the dark set inverted: light subtracts a warm shadow to give each fibre a body and
     adds a white glint for the core, so the material reads as lit thread, not as glow. */
  --hero-ground:
    radial-gradient(128% 118% at 24% 96%, color-mix(in srgb, var(--gold) 12%, var(--surface)) 0%,
      color-mix(in srgb, var(--surface) 88%, var(--stone)) 42%,
      color-mix(in srgb, var(--panel) 46%, var(--stone)) 100%);
  --hero-air:
    radial-gradient(84% 70% at 88% 6%, color-mix(in srgb, var(--gold) 9%, transparent), transparent 62%),
    linear-gradient(180deg, color-mix(in srgb, var(--surface) 26%, transparent), transparent 16%,
      transparent 84%, color-mix(in srgb, var(--panel) 30%, transparent));
  --weave-glow-op: .92;
  --weave-haze-op: .13;
  --weave-strand-op: .86;
  --weave-fil-op: .54;
  --weave-mote-op: .72;
  --weave-strand-w: 2.9;
  --weave-fil-w: 1.15;

  --glass-blur: 28px;
  --glass-sat: 1.09;
  /* Held near a constant alpha on purpose. A steep gradient in the fill is what makes
     a pane read as a printed card; real glass gets its variation from what is behind
     it, so the fill stays flat and the field supplies the modulation. */
  --glass-fill: linear-gradient(152deg,
      rgba(255, 253, 248, .42) 0%,
      rgba(250, 245, 236, .37) 50%,
      rgba(246, 240, 229, .40) 100%);
  --glass-edge: rgba(181, 97, 44, .30);
  --glass-light:
    radial-gradient(circle at 102% -4%, rgba(230, 177, 92, .13), transparent 34%),
    linear-gradient(116deg, rgba(255, 255, 255, .10), transparent 38%);
  --glass-rim: linear-gradient(90deg, rgba(255, 255, 255, .58) 0%, rgba(255, 255, 255, .95) 16%, rgba(230, 177, 92, .44) 62%, transparent 96%);
  --glass-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .90),
    inset 1px 0 0 rgba(255, 255, 255, .52),
    inset 0 -1px 0 rgba(181, 97, 44, .10),
    inset 0 0 0 1px rgba(255, 255, 255, .28),
    inset 0 0 34px -10px rgba(255, 255, 255, .55),
    inset 0 30px 52px -36px rgba(255, 255, 255, .58),
    0 26px 70px rgba(33, 30, 26, .085);
  --hero-eyebrow: color-mix(in srgb, var(--cinnamon) 82%, var(--carbon));
  --hero-lead: color-mix(in srgb, var(--carbon) 86%, var(--clay));

  /* ---- ABOUT MATERIALS: LIGHT ----
     The structure drawn through warm natural light. Cinnamon carries the line work,
     Gold is reserved for the points where something is decided. */
  --about-ground:
    radial-gradient(118% 76% at 79% 16%, color-mix(in srgb, var(--gold) 6%, transparent), transparent 58%),
    var(--stone);
  --about-label:       color-mix(in srgb, var(--cinnamon) 84%, var(--carbon));
  --about-body:        color-mix(in srgb, var(--carbon) 82%, var(--clay));
  --about-muted:       var(--clay);
  --about-arrival:     color-mix(in srgb, var(--cinnamon) 44%, transparent);
  --about-datum:       color-mix(in srgb, var(--cinnamon) 58%, transparent);
  --about-datum-end:   color-mix(in srgb, var(--cinnamon) 10%, transparent);
  --about-tick:        color-mix(in srgb, var(--cinnamon) 32%, transparent);
  --about-origin-ring: color-mix(in srgb, var(--gold) 22%, transparent);
  --about-origin-glow: color-mix(in srgb, var(--gold) 26%, transparent);
  --about-descent:     color-mix(in srgb, var(--cinnamon) 42%, transparent);
  --about-descent-end: color-mix(in srgb, var(--cinnamon) 14%, transparent);
  --about-node:        color-mix(in srgb, var(--cinnamon) 68%, transparent);
  --about-rule:        var(--hairline);
  --about-glyph:       color-mix(in srgb, var(--cinnamon) 88%, var(--carbon));
  /* The seam gradient, per material. Cinnamon carries it on the warm ground and
     resolves toward Gold, which is the hero's own strand order. */
  --seam-a:            var(--cinnamon);
  --seam-b:            var(--cinnamon);
  --seam-c:            var(--gold);
  --seam-op-a:         .12;
  --seam-op-b:         .58;
  --seam-op-c:         .30;

  --beam-grad: linear-gradient(90deg, var(--stone-a00), var(--cinnamon) 24%, var(--gold) 76%, var(--stone-a00));
  --edge-fade: linear-gradient(90deg, rgba(33,30,26,0) 0%, rgba(33,30,26,1) 44%, rgba(33,30,26,1) 100%);

  --font-display: "Sora", "Helvetica Neue", Arial, sans-serif;
  --font-sans:  "Hanken Grotesk", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono:  "IBM Plex Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, Monaco, Consolas, monospace;

  --fs-display: clamp(2.9rem, 1.4rem + 6.2vw, 6rem);
  --fs-h2:      clamp(2rem, 1.3rem + 3vw, 3.6rem);
  --fs-h3:      clamp(1.15rem, 1.02rem + 0.5vw, 1.45rem);
  --fs-body:    1.0625rem;
  --fs-label:   0.78rem;

  --space-1: 0.25rem;  --space-2: 0.5rem;  --space-3: 0.75rem;
  --space-4: 1rem;     --space-5: 1.5rem;  --space-6: 2rem;
  --space-7: 3rem;     --space-8: 4rem;    --space-9: 6rem;
  --section-y:   clamp(5rem, 3rem + 7vw, 9rem);
  --head-gap:    clamp(2.5rem, 1.9rem + 1.9vw, 3.75rem);
  --stack-gap:   clamp(1.5rem, 1.1rem + 1.3vw, 2.35rem);

  --wrap: 74rem;
  --wrap-pad: clamp(1.35rem, 0.6rem + 3.4vw, 3.5rem);
  --measure: 60ch;
  --rail-x: clamp(1rem, 2.4vw, 2.6rem);

  --r-sm: 12px; --r-md: 14px; --r-lg: 16px;

  --ease: cubic-bezier(.2,.7,.2,1);
  --dur: .55s;
  --dur-fast: .2s;
  --dur-slow: .85s;

  --mx: 0; --my: 0;
  /* Locked header height and easing, ported from the approved source. The easing is
     given its own name so the locked header and hero keep their exact timing without
     altering the motion of any section built earlier. */
  --header-h: 94px;
  --ease-hdr: cubic-bezier(.22,1,.36,1);
  /* The theme-transition curve. Leaves quickly, arrives slowly and settles without
     overshoot, which is what makes the aperture wave feel like a material opening
     rather than a panel sliding. */
  --ease-material: cubic-bezier(.16,.84,.24,1);
  /* Non-flipping anchors. The locked theme model flips the base tokens themselves, so
     any surface that must stay dark in BOTH themes (the Statement field) references
     these instead of --carbon / --stone. */
  --carbon-fixed: #211E1A;
  --stone-fixed:  #EEEBE4;
  color-scheme: light;
}

/* Role tokens: DARK.
   Ported from the approved locked source, which flips the base tokens themselves
   rather than layering a second semantic set. Adopting it verbatim is required by the
   "preserve the palette" instruction for the locked header and hero. It introduces
   five warm dark values beyond the original locked eight (#28241F, #312C26, #4B443B,
   #F4F0E8, #BEB5A7); these are documented as a CEO-approved extension in the review
   notes, and the verifier allowlist was widened to exactly these five. */
:root[data-theme="dark"] {
  --stone:    #211E1A;
  --surface:  #28241F;
  --panel:    #312C26;
  --hairline: #4B443B;
  --carbon:   #F4F0E8;
  --clay:     #BEB5A7;

  /* The semantic layer below now inherits the flip automatically, so only the values
     that must NOT simply invert are restated here. */
  --bg-alt:        var(--panel);
  --hair:          var(--hairline);
  --muted:         var(--clay);
  --faint:         var(--clay);
  --hover-bg:      var(--panel);
  --ink-bg:        var(--carbon-fixed);
  --on-ink:        var(--stone-fixed);
  --on-ink-muted:  rgba(238, 235, 228, .66);

  --rail:          rgba(230, 177, 92, .3);
  --on-ink-a22:    rgba(238, 235, 228, .22);
  --shadow:        0 1px 2px rgba(33, 30, 26, .5), 0 20px 48px rgba(33, 30, 26, .62);
  /* Dark art direction: light artworks are darkened deeply and blended; the dark
     artworks (statement, contact) read at near-full strength. */
  --art-veil-hero: rgba(33, 30, 26, .3);
  --art-veil-house: rgba(33, 30, 26, .3);
  --light-art-op: 0.1;   /* the light artworks become a faint texture on Carbon */
  --statement-scrim: rgba(33, 30, 26, .3);
  --statement-scrim-2: rgba(33, 30, 26, .0);
  --contact-veil:  rgba(33, 30, 26, .28);
  --contact-art-opacity: 0.9;
  --gl-opacity: 0.66;

  /* ---- HERO MATERIALS: DARK ----
     Ember in shadow. The ground drops well below Carbon to a warm near-black so the
     Cinnamon, amber and Warm Gold strands carry the field, with bright but controlled
     highlights and very small motes. Deep smoked glass at roughly 37% keeps the
     brightest strands faintly readable straight through the pane. */
  --hero-ground:
    radial-gradient(128% 118% at 24% 96%, color-mix(in srgb, var(--cinnamon) 15%, #211E1A) 0%,
      color-mix(in srgb, #211E1A 74%, black) 44%,
      color-mix(in srgb, #211E1A 58%, black) 100%);
  --hero-air:
    radial-gradient(84% 70% at 88% 6%, color-mix(in srgb, var(--gold) 7%, transparent), transparent 62%),
    linear-gradient(180deg, rgba(33, 30, 26, .30), transparent 18%,
      transparent 78%, rgba(33, 30, 26, .42));
  --weave-glow-op: .9;
  --weave-haze-op: .13;
  --weave-strand-op: .92;
  --weave-fil-op: .5;
  --weave-mote-op: .85;
  --weave-strand-w: 2.2;
  --weave-fil-w: .9;

  --glass-blur: 30px;
  --glass-sat: 1.10;
  /* Smoked, not brown. The tint is kept close to neutral and low so the warmth in the
     pane comes from the field showing through it rather than from a brown fill. */
  --glass-fill: linear-gradient(152deg,
      rgba(19, 17, 15, .32) 0%,
      rgba(15, 13, 11, .27) 50%,
      rgba(17, 15, 13, .30) 100%);
  --glass-edge: rgba(230, 177, 92, .30);
  --glass-light:
    radial-gradient(circle at 102% -4%, rgba(230, 177, 92, .12), transparent 34%),
    linear-gradient(116deg, rgba(255, 255, 255, .032), transparent 40%);
  --glass-rim: linear-gradient(90deg, rgba(255, 246, 226, .50) 0%, rgba(255, 246, 226, .82) 15%, rgba(230, 177, 92, .38) 60%, transparent 95%);
  --glass-shadow:
    inset 0 1px 0 rgba(255, 244, 219, .175),
    inset 1px 0 0 rgba(255, 240, 210, .105),
    inset 0 -1px 0 rgba(230, 177, 92, .06),
    inset 0 0 0 1px rgba(255, 244, 219, .05),
    inset 0 0 36px -10px rgba(255, 236, 198, .11),
    inset 0 30px 56px -36px rgba(255, 236, 198, .11),
    0 32px 88px rgba(0, 0, 0, .34);
  --hero-eyebrow: rgba(238, 235, 228, .72);
  --hero-lead: rgba(238, 235, 228, .80);

  /* ---- ABOUT MATERIALS: DARK ----
     Hidden architecture becoming visible. The ground steps up from the hero's
     near-black to warm Carbon, so the section reads as calmer and more built. */
  --about-ground:
    radial-gradient(118% 76% at 79% 16%, color-mix(in srgb, var(--cinnamon) 9%, transparent), transparent 58%),
    var(--stone);
  --about-label:       color-mix(in srgb, var(--gold) 78%, var(--carbon));
  --about-body:        rgba(238, 235, 228, .80);
  --about-muted:       rgba(238, 235, 228, .64);
  --about-arrival:     color-mix(in srgb, var(--gold) 40%, transparent);
  --about-datum:       color-mix(in srgb, var(--gold) 50%, transparent);
  --about-datum-end:   color-mix(in srgb, var(--gold) 9%, transparent);
  --about-tick:        color-mix(in srgb, var(--gold) 28%, transparent);
  --about-origin-ring: color-mix(in srgb, var(--gold) 15%, transparent);
  --about-origin-glow: color-mix(in srgb, var(--gold) 32%, transparent);
  --about-descent:     color-mix(in srgb, var(--gold) 38%, transparent);
  --about-descent-end: color-mix(in srgb, var(--gold) 11%, transparent);
  --about-node:        color-mix(in srgb, var(--gold) 80%, transparent);
  --about-rule:        color-mix(in srgb, var(--hairline) 76%, transparent);
  --about-glyph:       color-mix(in srgb, var(--gold) 70%, var(--carbon));
  /* On the dark ground the seam is Gold throughout and held lower, so it reads as
     light in the material rather than as a drawn line on top of it. */
  --seam-a:            var(--gold);
  --seam-b:            var(--gold);
  --seam-c:            var(--gold);
  --seam-op-a:         .09;
  --seam-op-b:         .48;
  --seam-op-c:         .18;
  color-scheme: dark;
}

/* Dark art direction: with the light artworks faded to texture, the Cinnamon SVG
   branches, the Gold nodes, and the WebGL signal carry the hero and house on Carbon.
   Boost their presence so the dark composition reads as designed, not muddy. */
:root[data-theme="dark"] .field-branch-mid { opacity: 0.7; }
:root[data-theme="dark"] .field-branch-far { opacity: 0.4; }
:root[data-theme="dark"] .field-stem { opacity: 0.6; }
:root[data-theme="dark"] .field-node-far { opacity: 0.7; }
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-padding-top: calc(var(--header-h) + 1rem);
  scroll-behavior: auto;
  overflow-x: clip;
}
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
img, svg { max-width: 100%; }
svg { display: block; }
a { color: var(--ink); text-decoration-color: var(--cinnamon); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { text-decoration-thickness: 2px; }
h1, h2, h3 { line-height: 1.08; font-weight: 600; letter-spacing: -0.02em; color: var(--ink); }
strong { font-weight: 600; }
::selection { background: var(--gold); color: var(--carbon); }

/* ============================= 2. UTILITIES ============================= */
.wrap { width: 100%; max-width: var(--wrap); margin-inline: auto; padding-inline: var(--wrap-pad); }
.display { font-family: var(--font-display); font-weight: 600; letter-spacing: -0.025em; }

.eyebrow, .section-label {
  display: inline-flex; align-items: center; gap: 0.6rem;
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.14em; color: var(--muted);
}
.eyebrow .tick { width: 1.6rem; height: 2px; border-radius: 2px; background: var(--accent); }
.section-label { position: relative; padding-left: 1.5rem; margin-bottom: var(--space-5); }
.section-label::before { content: ""; position: absolute; left: 3px; top: 0.1em; width: 1px; height: 1.2em; background: var(--cinnamon); }
.section-label::after { content: ""; position: absolute; left: 0; top: calc(0.1em - 2px); width: 6px; height: 6px; border-radius: 50%; background: var(--gold); }

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

.skip-link {
  position: absolute; left: var(--space-4); top: -100px; z-index: 100;
  background: var(--btn-bg); color: var(--btn-fg);
  padding: 0.7rem 1rem; border-radius: var(--r-sm);
  font-size: var(--fs-body); text-decoration: none;
  transition: top var(--dur-fast) var(--ease);
}
.skip-link:focus-visible { top: var(--space-3); }

/* The former top progress line and vertical signal rail were removed: the locked
   header supplies the full-width beam and the travelling scroll node itself. */

/* Buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
  min-height: 3rem; padding: 0.8rem 1.5rem;
  border: 1px solid transparent; border-radius: var(--r-md);
  font: inherit; font-weight: 600; font-size: var(--fs-body); line-height: 1;
  text-decoration: none; cursor: pointer;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.btn-primary { background: var(--btn-bg); color: var(--btn-fg); border-color: var(--btn-border); }
.btn-primary:hover { border-color: var(--cinnamon); }
.btn-ghost { background: transparent; color: var(--ink); border-color: var(--hair); }
.btn-ghost:hover { border-color: var(--cinnamon); }
.btn:active { transform: translateY(1px); }

.signature { margin-top: var(--space-6); font-family: var(--font-mono); font-size: var(--fs-body); letter-spacing: 0.01em; color: var(--ink-2); }
.signature .spark { margin-right: 0.35rem; }

.beam { position: relative; height: 2px; border-radius: 2px; background: var(--beam-grad); }
.beam-node { position: absolute; top: 50%; right: 22%; width: 10px; height: 10px; border-radius: 50%; background: var(--gold); transform: translate(50%, -50%); }
.beam-pulse { position: absolute; top: 50%; left: 0; width: 28px; height: 2px; border-radius: 2px; background: var(--gold-a70); transform: translate(-40px, -50%); opacity: 0; }

/* ==================== 3. STRUCTURE: LOCKED HEADER ====================
   Ported from the approved locked source (SHA-256 verified). Position, height, grid,
   spacing, radii, the full-width Cinnamon into Gold beam, the travelling scroll node,
   hover and active states, and every timing value are preserved exactly.
   Two forced integration changes, both documented in the review notes:
   (a) type families are the project's self-hosted brand faces, because the
       implementation rules require self-hosted fonts and forbid external ones, and the
       locked prototype used a system stack as a stand-in;
   (b) the locked weights 560 / 650 / 660 / 700 are snapped to the nearest licensed
       weight in the shipped set (500 / 600), since only 400/500/600 are licensed. */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--header-h);
  display: grid;
  place-items: center;
  isolation: isolate;
}
.site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  background: linear-gradient(180deg, color-mix(in srgb, var(--stone) 96%, transparent), color-mix(in srgb, var(--stone) 86%, transparent));
  backdrop-filter: blur(16px) saturate(.95);
  -webkit-backdrop-filter: blur(16px) saturate(.95);
  border-bottom: 1px solid color-mix(in srgb, var(--hairline) 86%, transparent);
}
/* The locked full-width beam. */
.site-header::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 2px;
  background: linear-gradient(90deg, var(--cinnamon) 0%, var(--gold) 50%, var(--cinnamon) 100%);
  opacity: .92;
  box-shadow: 0 1px 12px color-mix(in srgb, var(--gold) 12%, transparent);
}
/* The locked travelling scroll node, riding the beam. */
.scroll-node {
  position: absolute;
  z-index: 3;
  bottom: -4px;
  left: 0;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--gold);
  /* Driven by transform, not by `left`. Animating `left` dirties layout on every
     scroll frame; a transform is handled by the compositor. The travel, the 6px end
     stops and the appearance are identical. */
  transform: translate3d(var(--scroll-x, 6px), 0, 0) translateX(-50%);
  will-change: transform;
  box-shadow:
    0 0 0 4px color-mix(in srgb, var(--gold) 13%, transparent),
    0 0 18px color-mix(in srgb, var(--gold) 45%, transparent);
  pointer-events: none;
  transition: transform 90ms linear;
}
.header-inner {
  width: min(1248px, calc(100% - 192px));
  height: 100%;
  display: grid;
  grid-template-columns: minmax(220px, 1fr) auto minmax(220px, 1fr);
  align-items: center;
  gap: 24px;
}
.brand {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  width: fit-content;
  text-decoration: none;
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.025em;
  font-size: 19px;
  color: var(--carbon);
}
.brand-symbol { width: 34px; height: 34px; display: grid; place-items: center; position: relative; }
.brand-symbol svg { width: 26px; height: 30px; fill: currentColor; }
.brand-symbol::after {
  content: "";
  position: absolute;
  width: 4px; height: 4px;
  border-radius: 50%;
  background: var(--gold);
  right: -4px; top: 4px;
  transform: scale(0);
  transition: transform 340ms var(--ease-hdr);
}
.brand:hover .brand-symbol::after { transform: scale(1); }
.brand sup { font-size: 8px; margin-left: -8px; transform: translateY(-7px); font-weight: 600; }

.nav-shell {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px;
  border-radius: 16px;
  background: color-mix(in srgb, var(--surface) 64%, transparent);
  border: 1px solid color-mix(in srgb, var(--hairline) 68%, transparent);
  box-shadow: 0 16px 40px rgba(33,30,26,.04);
}
.nav-shell::before, .nav-shell::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 52px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--hairline));
  opacity: .9;
}
.nav-shell::before { right: calc(100% + 14px); }
.nav-shell::after { left: calc(100% + 14px); transform: scaleX(-1); }

.nav-link {
  position: relative;
  min-height: 44px;
  padding: 0 25px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  text-decoration: none;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 500;
  color: var(--clay);
  transition: color 220ms ease, background 260ms ease, transform 260ms var(--ease-hdr);
  overflow: hidden;
}
.nav-link::before {
  content: "";
  position: absolute;
  left: 50%; bottom: 7px;
  width: 0; height: 1px;
  background: linear-gradient(90deg, var(--cinnamon), var(--gold));
  transform: translateX(-50%);
  transition: width 320ms var(--ease-hdr);
}
.nav-link::after {
  content: "";
  position: absolute;
  left: 50%; bottom: 5px;
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--gold);
  transform: translate(-50%, 10px) scale(.4);
  opacity: 0;
  transition: transform 320ms var(--ease-hdr), opacity 220ms ease;
}
.nav-link:hover { color: var(--carbon); transform: translateY(-1px); }
.nav-link:hover::before { width: 28px; }
.nav-link:hover::after { transform: translate(-50%, 0) scale(1); opacity: 1; }
.nav-link[aria-current="page"] { color: var(--carbon); background: color-mix(in srgb, var(--panel) 76%, transparent); }
.nav-link[aria-current="page"]::before { width: 34px; }
.nav-link[aria-current="page"]::after { transform: translate(-50%,0) scale(1); opacity: 1; }

.header-actions { justify-self: end; display: flex; align-items: center; gap: 10px; }
.theme-button, .menu-button {
  width: 48px; height: 48px;
  border: 1px solid var(--hairline);
  background: color-mix(in srgb, var(--surface) 76%, transparent);
  color: var(--carbon);
  border-radius: 14px;
  display: grid; place-items: center;
  cursor: pointer;
  position: relative;
  transition: transform 280ms var(--ease-hdr), border-color 220ms ease, background 220ms ease;
}
.theme-button:hover, .menu-button:hover { transform: translateY(-2px); border-color: color-mix(in srgb, var(--cinnamon) 55%, var(--hairline)); }

/* The locked aperture theme control. */
.aperture {
  width: 25px; height: 25px;
  position: relative;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--clay) 55%, transparent);
  transition: transform 520ms var(--ease-hdr);
}
.aperture::before {
  content: "";
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--cinnamon) 65%, transparent);
}
.aperture::after {
  content: "";
  position: absolute;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--gold);
  left: 50%; top: 50%;
  transform: translate(-50%, -50%) translateX(7px);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--gold) 12%, transparent);
  transition: transform 520ms var(--ease-hdr), box-shadow 520ms var(--ease-hdr);
}
html[data-theme="dark"] .aperture { transform: rotate(180deg); }
html[data-theme="dark"] .aperture::after {
  transform: translate(-50%, -50%) translateX(-7px);
  box-shadow: 0 0 16px 4px color-mix(in srgb, var(--gold) 28%, transparent);
}

.contact-link {
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 22px;
  border-radius: 14px;
  background: var(--carbon);
  color: var(--stone);
  text-decoration: none;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 15px;
  position: relative;
  overflow: hidden;
  transition: transform 280ms var(--ease-hdr);
}
.contact-link::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(110deg, transparent 20%, rgba(255,255,255,.14) 48%, transparent 72%);
  transform: translateX(-120%);
  transition: transform 720ms var(--ease-hdr);
}
.contact-link:hover { transform: translateY(-2px); }
.contact-link:hover::before { transform: translateX(120%); }

.menu-button { display: none; }
.menu-lines { width: 20px; height: 14px; position: relative; }
.menu-lines::before, .menu-lines::after {
  content: "";
  position: absolute;
  left: 0;
  width: 20px; height: 1px;
  background: currentColor;
  transition: transform 280ms var(--ease-hdr), top 280ms var(--ease-hdr);
}
.menu-lines::before { top: 3px; }
.menu-lines::after { top: 10px; }

.mobile-panel {
  display: none;
  position: fixed;
  inset: var(--header-h) 0 0;
  z-index: 90;
  background: var(--stone);
  padding: 38px 24px 48px;
  transform: translateY(-10px);
  opacity: 0;
  pointer-events: none;
  transition: transform 420ms var(--ease-hdr), opacity 260ms ease;
}
.mobile-panel.open { transform: none; opacity: 1; pointer-events: auto; }
.mobile-panel a {
  display: flex;
  justify-content: space-between;
  align-items: center;
  min-height: 74px;
  font-family: var(--font-display);
  font-size: clamp(28px, 9vw, 44px);
  font-weight: 600;
  letter-spacing: -.04em;
  text-decoration: none;
  color: var(--carbon);
  border-bottom: 1px solid var(--hairline);
}
.mobile-panel a span { font: 500 12px/1 var(--font-mono); color: var(--clay); letter-spacing: .08em; }

/* The header is fixed, so the document is offset by its height. */
main { padding-top: var(--header-h); }

/* Footer: the signal resolved. Tight, aligned, minimal. */
/* THE FOOTER'S APPROACH WAS COUNTED TWICE.
   Every section-to-section boundary on this page is made of the two sections'
   own `--section-y` paddings and nothing else, which is why all six of them
   measure a seam of exactly 0. The footer was the one boundary that also carried
   a full `--section-y` MARGIN on top of Contact's `--section-y` padding, and
   then its own `--space-7` padding after that -- three separations where every
   other boundary has two.

   It showed: Contact's photograph ends on a hard horizontal edge exactly at the
   section bottom, and 144 px of empty page followed before the footer hairline
   at 1440. Measured last-ink to first-ink, Contact -> Footer was 444.9 px, the
   largest gap on the page against a 311 px mean, and it landed at the one moment
   the page should be closing rather than relaxing.

   Halving the margin removes the double count without making the ending cramped:
   the hairline still clears the photograph by 72 px at 1440 and 40 px at 390,
   and the total falls to 372.9 px -- still the page's most generous transition,
   which is right for an ending, but no longer an outlier. One token, both ends,
   no negative margins and no per-viewport patch. */
.site-footer { padding-top: var(--space-8); margin-top: calc(var(--section-y) * .5); background: color-mix(in srgb, var(--bg) 76%, var(--bg-alt)); position: relative; }
.footer-inner { display: flex; flex-wrap: wrap; gap: var(--space-7) var(--space-8); justify-content: space-between; align-items: flex-start; padding-bottom: var(--space-7); }
/* The lockup at a deliberate size: the same order as the header, not a banner.
   (Unsized, the two SVGs fell back to the 300x150 default -- the accidental
   giant wordmark this replaces.) */
.footer-brand .brand { margin-bottom: var(--space-5); gap: 16px; }
.footer-brand .brand-mark { width: 37px; height: 47.1px; fill: currentColor; }
.footer-brand .brand-word { width: 182px; height: 27.8px; fill: currentColor; }
.footer-tagline { color: var(--ink-2); font-size: 1.1875rem; line-height: 1.55; max-width: 26ch; }
.footer-signature { margin-top: var(--space-3); font-family: var(--font-mono); font-size: 0.84rem; color: var(--muted); letter-spacing: 0.02em; }
.footer-signature .spark { color: var(--gold); }
/* Navigate and Legal read as one compact index, not two floating lists. */
.footer-index { display: flex; gap: clamp(2.5rem, 6vw, 5rem); }
.footer-col-label { font-family: var(--font-mono); font-size: 0.72rem; font-weight: 500; letter-spacing: 0.18em; text-transform: uppercase; color: var(--muted); margin: 0 0 var(--space-3); }
.footer-nav ul { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.2rem; }
/* No bullet. The Legal column is deliberately not linked yet, so it could not carry
   the same dot, and two adjacent lists with different decoration read as an accident
   rather than as two halves of one index. Both are now plain, which is also the more
   institutional of the two options. */
.footer-nav a { display: inline-flex; align-items: center; min-height: 2.5rem; color: var(--muted); text-decoration: none; font-size: var(--fs-body); transition: color var(--dur-fast) var(--ease); }
.footer-nav a:hover { color: var(--ink); }
.footer-legal-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.2rem; }
.footer-legal-list span, .footer-legal-list a, .footer-legal-list button { display: inline-flex; align-items: center; min-height: 2.5rem; color: var(--muted); font-size: var(--fs-body); }
.footer-legal-list a, .footer-legal-list button { text-decoration: none; transition: color var(--dur-fast) var(--ease); }
.footer-legal-list a:hover, .footer-legal-list button:hover { color: var(--ink); }
/* "Cookie settings" reopens the consent card (analytics.js); styled to match the footer links. */
.footer-legal-list button { background: none; border: 0; padding: 0; margin: 0; font-family: inherit; font-weight: inherit; line-height: inherit; letter-spacing: inherit; cursor: pointer; text-align: left; }
/* padding-block, not the padding shorthand. The shorthand was overriding .wrap's own
   56px inline padding, so the terminal strip and its rule sat 56px outside the footer
   body above them -- the copyright line began at 128 while the brand began at 184. */
.footer-terminal { display: flex; flex-wrap: wrap; gap: var(--space-3) var(--space-6); justify-content: space-between; padding-block: var(--space-4) var(--space-6); position: relative; font-size: var(--fs-label); color: var(--muted); font-family: var(--font-mono); letter-spacing: 0.01em; }
/* The rule is drawn inside the wrap's own inline padding rather than as a border on
   the element, so it starts and ends on the same edges as the content it separates.
   As a border it ran the full container width and overhung the copy by 56px a side. */
.footer-terminal::before {
  content: "";
  position: absolute;
  top: 0;
  left: var(--wrap-pad); right: var(--wrap-pad);
  height: 1px;
  background: var(--hair);
}

/* ============================ 4. SECTIONS ============================= */
.section { padding-block: var(--section-y); position: relative; }
/* Each section anchors to the rail with a node at its heading. */
.section-head { position: relative; max-width: 52rem; margin-bottom: var(--head-gap); }
.section-title { font-size: var(--fs-h2); }
.section-intro { color: var(--muted); font-size: var(--fs-h3); max-width: var(--measure); margin-top: var(--space-5); }

/* ---- HERO: FULL-FIELD GLASS (Phase 7R-B) ----
   CEO-approved override of the previous split composition. One artwork occupies the
   entire hero; the copy sits on a transparent glass plane the field moves behind.
   There is no cream block, no grey block, and no seam between text and artwork.

   Layer order, asserted by the verifier:
     .hero-base (0) < .hero-weave (1) < #fieldCanvas (2) < .hero-air (3)
     < .hero-inner (4) < .site-header (100)

   The 18px panel radius is the one documented extension to the locked radius set
   (12/14/16 + 2px + 50%); the approved direction specifies 17 to 22px for the glass. */
.hero {
  position: relative;
  min-height: calc(100svh - var(--header-h));
  display: grid;
  align-items: center;
  overflow: hidden;
  isolation: isolate;
}

/* 1. base material. Warm, never neutral: light is an ivory ground lit from the lower
      left, dark is a warm near-black well below Carbon so the strands can sit on it. */
.hero-field { position: absolute; inset: 0; z-index: 0; overflow: hidden; touch-action: pan-y; }
.hero-base {
  position: absolute; inset: 0; z-index: 0;
  background: var(--hero-ground);
  transition: background var(--dur-slow) var(--ease);
}

/* 2. the guaranteed fallback. Painted at all times; it is what shows if WebGL is
      absent, still initialising, refused, or lost. It is never display:none, so the
      hero cannot render blank. */
.hero-weave {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%;
  opacity: 1;
  visibility: visible;
  transition: opacity 900ms var(--ease), visibility 0s;
}
.weave-glow { fill: url(#btFieldGlow); opacity: var(--weave-glow-op); }
.weave-haze path { fill: none; stroke: var(--cinnamon); stroke-width: 44; opacity: var(--weave-haze-op); }
.weave-strands path {
  fill: none; stroke: url(#btStrand); stroke-linecap: round;
  stroke-width: var(--weave-strand-w); vector-effect: non-scaling-stroke;
  opacity: var(--weave-strand-op);
}
.weave-filaments path {
  fill: none; stroke: url(#btFilament); stroke-linecap: round;
  stroke-width: var(--weave-fil-w); vector-effect: non-scaling-stroke;
  opacity: var(--weave-fil-op);
}
.weave-motes { fill: var(--gold); opacity: var(--weave-mote-op); }
/* The early-opening family is held a shade lighter so the upper field reads as
   distance rather than as a second, competing set of strands. */
.weave-strands-high path { stroke-width: 1.9; opacity: .72; }
.weave-strands, .weave-filaments { transform-origin: 30% 88%; }

/* 3. the live field. Fades in over the fallback so there is no blank frame and no
      pop; on context loss the class is dropped and the fallback returns. */
/* Isolating the field limits repaint scope while the hero scrolls. */
.hero-field { contain: layout paint; -webkit-user-select: none; user-select: none; }
#fieldCanvas {
  position: absolute; inset: 0; z-index: 2;
  width: 100%; height: 100%;
  display: block;
  opacity: 0;
  transition: opacity 900ms var(--ease);
}
.hero-field.gl-live #fieldCanvas { opacity: 1; }
/* MEASURED FIX. SVG group and circle transform animations are not compositor
   accelerated in Chrome: they run on the main thread and dirty layout on every
   frame. Because the weave sits under an opaque canvas at opacity 0 it was
   invisible, but opacity 0 does not stop an animation, so it was forcing a full
   layout about 37 times a second, for ever, and compounding during scroll.
   Stopping the animations takes idle layout from 37/s to 0/s. Visibility is
   dropped only after the cross-fade completes, and returns instantly if the
   canvas is ever lost. */
.hero-field.gl-live .hero-weave {
  opacity: 0;
  visibility: hidden;
  transition: opacity 900ms var(--ease), visibility 0s 900ms;
}
.hero-field.gl-live .hero-weave *,
.hero-field.gl-live .weave-strands,
.hero-field.gl-live .weave-filaments,
.hero-field.gl-live .weave-motes circle { animation: none !important; }

/* 4. atmosphere. A whisper of depth over the field, never a scrim over the text. */
.hero-air {
  position: absolute; inset: 0; z-index: 3;
  pointer-events: none;
  background: var(--hero-air);
}

/* 5. the glass plane. */
.hero-inner {
  position: relative;
  z-index: 4;
  /* Transparent to the pointer, so every part of the hero outside the copy plane
     reaches the field. The panel below re-enables events for itself. */
  pointer-events: none;
  width: min(1280px, calc(100% - 48px));
  margin-inline: auto;
  display: flex;
  align-items: center;
  padding-block: clamp(40px, 5vh, 64px);
}
.hero-glass {
  position: relative;
  width: min(628px, 47.5%);
  min-height: clamp(604px, 72.4vh, 654px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(38px, 3.5vw, 52px);
  /* A touch more room below than above, so the copy sits slightly above the
     optical centre of the plane rather than dead centre. */
  padding-bottom: calc(clamp(38px, 3.5vw, 52px) + 10px);
  border-radius: 18px;
  border: 1px solid var(--glass-edge);
  background: var(--glass-fill);
  box-shadow: var(--glass-shadow);
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  overflow: hidden;
  pointer-events: auto;
  transition: background var(--dur-slow) var(--ease),
              border-color var(--dur-slow) var(--ease),
              box-shadow var(--dur-slow) var(--ease);
}
/* Internal lighting: a warm reflection gathering at the upper right edge, and one
   soft diagonal sheen across the pane. Both sit under the text. */
.hero-glass::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
  background: var(--glass-light);
}
/* The thin top-edge highlight that gives the pane its thickness. */
.hero-glass::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  z-index: 0;
  height: 1px;
  pointer-events: none;
  background: var(--glass-rim);
}
.hero-glass > * { position: relative; z-index: 1; }

.hero .eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  color: var(--hero-eyebrow);
  font: 500 12.5px/1 var(--font-mono);
  letter-spacing: .14em;
}
.hero .eyebrow::before { content: ""; width: 26px; height: 1px; background: var(--cinnamon); }
.hero h1 {
  margin: 38px 0 38px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(54px, 5.9vw, 88px);
  line-height: .92;
  letter-spacing: -.068em;
  /* Holds the approved three-line break: We build / consumer / brands. */
  max-width: 7.2ch;
  color: var(--carbon);
}
.hero-glass p {
  margin: 0;
  max-width: 39ch;
  font-family: var(--font-sans);
  font-size: clamp(18px, 1.4vw, 20px);
  line-height: 1.6;
  color: var(--hero-lead);
}

/* ---- ABOUT: THE STANDARD (Phase 7R-E) ----
   The hero is a living field. This section is what that energy becomes once it is
   owned: a drawing. The flow arrives from the page edge, loses density, resolves
   into one origin, and from that origin becomes a datum, the straight reference
   line every measurement is taken from. Three paths depart it and land exactly on
   the three principles, which hang off them and continue the line downward.

   Same universe as the hero, different chapter: same palette, same warm light, same
   line quality, but drawn rather than flowed, and still rather than moving. */

.about {
  position: relative;
  /* The lower interval used to be small because the datum drawing and its three
     hanging rules already carried the section's ending. With those gone the last
     paragraph was finishing 43px off the boundary, which read as a crop rather than
     as a close. */
  padding-block: clamp(66px, 6.2vw, 92px) clamp(72px, 6.4vw, 104px);
  background: var(--about-ground);
  overflow: hidden;
}
.about-wrap {
  position: relative;
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--wrap-pad);
  --pgap: clamp(26px, 3.2vw, 54px);
  --col: calc((100% - 2 * var(--pgap)) / 3);
  --step: calc(var(--col) + var(--pgap));
  /* One editorial grid for the whole section. Every element is placed on it, so the
     two right-hand paragraphs, the heading, the conviction, the datum and the three
     principles all share the same vertical edges. The previous version gave the head
     and the conviction their own column ratios, which put the two right-hand
     paragraphs on different left edges. */
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--pgap);
}
/* The two wrappers exist for meaning, not for layout: their children join the grid. */
.about-head, .about-conviction { display: contents; }

/* The section marker is a site-wide system, not a per-section choice: Thinking,
   The house, Standards and Contact all announce themselves with the same gold
   node over a cinnamon tick (`.section-label`). About was the only section still
   carrying a horizontal dash -- a leftover from the composition that was replaced
   this phase, and the one place where a reader scrolling the page meets a
   different device for the same job. It now uses the same marker, at the same
   0.14em tracking, so the five labels read as one system.

   The rules stay local rather than adopting `.section-label` wholesale, because
   About's label is also a grid item and must keep its own placement and its own
   `--about-label` ink. */
.about-eyebrow {
  grid-column: 1 / -1;
  grid-row: 1;
  position: relative;
  display: inline-flex;
  align-items: center;
  width: max-content;
  padding-left: 1.5rem;
  font: 500 12.5px/1 var(--font-mono);
  letter-spacing: .14em;
  color: var(--about-label);
}
.about-eyebrow::before { content: ""; position: absolute; left: 3px; top: 0.1em;
                         width: 1px; height: 1.2em; background: var(--cinnamon); }
.about-eyebrow::after  { content: ""; position: absolute; left: 0; top: calc(0.1em - 2px);
                         width: 6px; height: 6px; border-radius: 50%; background: var(--gold); }

.about-title {
  grid-column: 1 / span 7;
  grid-row: 2;
  margin-top: clamp(26px, 2.4vw, 36px);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(2.5rem, 1.2rem + 3.9vw, 4.25rem);
  line-height: 1.0;
  letter-spacing: -.038em;
  color: var(--ink);
  text-wrap: balance;
}
.about-lead {
  grid-column: 9 / span 4;
  grid-row: 2;
  /* Hung from the top of the heading rather than from the bottom of the row. Bottom
     alignment only looked deliberate because the two blocks happened to be a similar
     height; at any width where the paragraph rewraps it drifted. */
  align-self: start;
  margin-top: clamp(36px, 3.3vw, 50px);
  padding-bottom: 0;
  font-size: clamp(1rem, .96rem + .28vw, 1.14rem);
  line-height: 1.66;
  color: var(--about-body);
}

.about-statement {
  position: relative;
  grid-column: 1 / span 10;
  grid-row: 4;
  /* The transition from the heading to the conviction used to be carried by a field
     of threads, with this margin only clearing their lower edge. The threads are gone
     and the space is now doing that work by itself, so it has to be a real interval
     rather than the leftover clearance it used to be -- roughly the height of one
     line of the conviction. The short rule still hangs off the statement, so it can
     never drift from the text it introduces. */
  --rule-lift: clamp(14px, 1.4vw, 22px);
  margin-top: clamp(64px, 7vw, 116px);
  /* The section read as heading / paragraph / heading / paragraph / three columns --
     structurally fine and completely predictable, because the two headings carried the
     same weight and neither was the point. This one is the point. It takes the full
     measure and the largest type in the section, so the progression is legible as
     definition -> conviction -> principles rather than as four equal blocks. */
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.8rem, .9rem + 3.15vw, 3.6rem);
  line-height: 1.1;
  letter-spacing: -.028em;
  color: var(--ink);
  text-wrap: balance;
}
/* The rule that opens the conviction, hung off the statement itself so it can never
   drift from the text it introduces. */
.about-statement::before {
  content: "";
  position: absolute;
  top: calc(-1 * var(--rule-lift));
  left: 0;
  width: clamp(64px, 8vw, 120px);
  height: 1px;
  background: var(--cinnamon);
  opacity: .8;
}
.about-close {
  /* Below the conviction, not beside it: beside it the conviction could never use the
     full measure and the two competed. Hung LEFT rather than right -- hanging it right
     left a 560x140 hole in the lower left that read as a gap in the grid rather than as
     space. On the left it sits directly under the sentence it qualifies, and the open
     ground to its right belongs to the conviction above it. */
  grid-column: 1 / span 5;
  grid-row: 5;
  align-self: start;
  /* Sits on the same interval as the statement it accompanies, plus the same optical
     drop the lead paragraph takes under the heading, so the two right-hand paragraphs
     hang from their headings identically. */
  margin-top: clamp(22px, 2.2vw, 34px);
  padding-bottom: 0;
  font-size: clamp(.96rem, .93rem + .22vw, 1.06rem);
  line-height: 1.7;
  color: var(--about-muted);
}



/* ---- the three principles, hanging off the paths ---- */
.principles {
  grid-column: 1 / -1;
  grid-row: 6;
  list-style: none;
  padding: 0;
  /* The datum drawing used to hold this interval open. Nothing occupies it now, so
     the interval has to be stated. It is deliberately larger than the one above the
     conviction: this is a change of register, from the section's argument to its
     supporting detail, and the reader should feel the section step down here. */
  margin: clamp(72px, 7.6vw, 116px) 0 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: clamp(38px, 5vw, 86px);
  /* Only ever applies once the three stack, which is the moment they stop being
     three columns and become one reading sequence. Without it they run together:
     one principle's last line sits directly under the next one's mark. */
  row-gap: clamp(46px, 10vw, 68px);
  /* Start, not stretch. Nothing is drawn down the side of a column any more, so the
     three no longer need equal box heights to make their rules end together -- and
     letting them size to their own content is what stops them reading as cells. */
  align-items: start;
}
/* No border, no background, no card. Each principle is a mark, a title and a
   paragraph, held together by proximity and separated from its neighbours by space.
   The left padding is gone too: the three now start on the section's own grid, so
   the first one aligns with the heading above it instead of hanging off a rule. */
.principle {
  position: relative;
  max-width: 34ch;
}
.principle-mark {
  display: block;
  width: clamp(52px, 4.4vw, 64px);
  aspect-ratio: 1;
  height: auto;
  margin-bottom: clamp(20px, 2vw, 28px);
  color: var(--about-glyph);
}
/* The owner's three marks, each an alpha mask painted with currentColor so all
   three inherit --about-glyph and change material with the theme rather than being
   locked to one gold.

   Sizing is an optical correction, not a nominal one. A CSS alpha mask paints
   currentColor through the mask's ALPHA channel, so what the eye reads as the
   weight of a mark is the integral of that alpha over the painted box -- not the
   mask's pixel dimensions, and not the ink coverage of the original artwork. The
   three derived masks are all 104x104, but their alpha mass differs a lot:

     origin 6.02% of its box · foundation 13.63% · ownership 13.72%

   At the previous sizes (100 / 82 / 96%) that rendered as mass 1.00 / 1.52 / 2.10.
   Ownership was more than twice the weight of origin and also the largest extent,
   so the third principle's anchor dominated a set of three that are meant to be
   peers -- visible on a phone plate, and confirmed by measuring ink in the plate
   (1.00 / 1.56 / 2.09) and alpha in the mask files independently.

   Matching mass exactly would need 100 / 66.5 / 66.3%, which overcorrects: origin's
   strokes are intrinsically the thinnest, so the other two end up obviously smaller
   OBJECTS while still reading darker. The sizes below take the balance instead --
   the spread falls from 2.10x to 1.44x, foundation and ownership become equals, and
   origin is allowed the larger extent that its lighter line earns.

   Anchored bottom, not centre: shrinking a mask inside a fixed box would otherwise
   push the smaller two ~6px further from their titles than origin, giving three
   different mark-to-title intervals. Bottom keeps that interval identical.

   Static. No reveal, no construction, no motion, nothing for the animation layer
   to drive. */
.principle-mark-origin,
.principle-mark-foundation,
.principle-mark-ownership {
  background: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: left bottom; mask-position: left bottom;
}
.principle-mark-origin {
  -webkit-mask-image: url("/assets/images/about/origin-mark-mask.png");
  mask-image: url("/assets/images/about/origin-mark-mask.png");
  -webkit-mask-size: 100%; mask-size: 100%;
}
.principle-mark-foundation {
  -webkit-mask-image: url("/assets/images/about/foundation-mark-mask.png");
  mask-image: url("/assets/images/about/foundation-mark-mask.png");
  -webkit-mask-size: 79%; mask-size: 79%;
}
.principle-mark-ownership {
  -webkit-mask-image: url("/assets/images/about/ownership-mark-mask.png");
  mask-image: url("/assets/images/about/ownership-mark-mask.png");
  -webkit-mask-size: 78.5%; mask-size: 78.5%;
}

/* These are the company's foundational statements, not feature labels. At the old
   size they sat between the body copy and the conviction and read as the third tier of
   a feature list. Raised a step and tightened, so each one carries as a statement in
   its own right while staying clearly subordinate to the conviction above. */
.principle h3 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(1.18rem, 1.02rem + .62vw, 1.56rem);
  margin-bottom: clamp(10px, 1vw, 15px);
  letter-spacing: -.018em;
  line-height: 1.2;
  color: var(--ink);
}
.principle p {
  margin-top: clamp(12px, 1.3vw, 18px);
  font-size: clamp(.94rem, .92rem + .18vw, 1.02rem);
  line-height: 1.66;
  color: var(--about-muted);
  max-width: 34ch;
}
.principle:focus-within { outline: none; }

/* ---- THINKING: one decision system, four compositions (Phase 7R-I) ----------
   The wide compositions are a cross: the datum is a zero-content row of the same grid
   the beliefs sit in, so it is exactly between the two rows at every width by
   construction and the core cannot drift off the intersection. The narrow ones turn
   that same wrapper into a rail at the reading edge, with the same core travelling
   along it. One element tree, four roles, no duplicated geometry.

   Active state is progressive enhancement and nothing more. Neutral is complete, no
   copy is hidden, no box moves, and every rule below changes only colour, opacity or
   a transform. --think-active is written by thinking-interaction.js; without it the
   section is a finished static composition. */
.section-alt { background: var(--bg-alt); }

#thinking {
  --think-channel:  26%;
  --think-row:      clamp(1.9rem, 0.7rem + 2.6vw, 3.4rem);
  --think-meta:     clamp(1.9rem, 1.6rem + 1vw, 2.35rem);
  --think-bleed:    max(0px, calc(var(--wrap-pad) - 14px));
  --think-core-d:   clamp(20px, 0.6rem + 1.2vw, 32px);
  --think-lead:     clamp(1.5rem, 0.4rem + 2.4vw, 3rem);
  --think-trail:    clamp(2.5rem, 0.8rem + 4vw, 5.5rem);
  --fil-up:         clamp(139px, 17.2vw, 246px);
  --fil-down:       clamp(170px, 21vw, 300px);
  --fil-w:          calc(var(--think-channel) - 48px);
  --core-dx:        0px;
  --core-dy:        0px;

  --think-rule:     color-mix(in srgb, var(--cinnamon) 50%, transparent);
  --think-rule-end: color-mix(in srgb, var(--cinnamon) 16%, transparent);
  --think-ring:     color-mix(in srgb, var(--cinnamon) 46%, transparent);
  --think-ring-on:  color-mix(in srgb, var(--cinnamon) 82%, transparent);
  --think-fil:      color-mix(in srgb, var(--cinnamon) 15%, transparent);
  --think-fil-2:    color-mix(in srgb, var(--gold) 22%, transparent);
  --think-mark:     color-mix(in srgb, var(--cinnamon) 62%, transparent);
  --think-mark-on:  var(--cinnamon);
  --think-num:      color-mix(in srgb, var(--clay) 88%, var(--cinnamon));
  --think-num-on:   var(--cinnamon);
  --think-halo:     color-mix(in srgb, var(--gold) 20%, transparent);
  --think-halo-2:   color-mix(in srgb, var(--gold) 9%, transparent);
  --think-ease:     cubic-bezier(.22, 1, .36, 1);
}
/* Dark keeps Cinnamon in the line work rather than moving to Gold as About's does, so
   the core stays the only place in the section where the material turns to light. */
:root[data-theme="dark"] #thinking {
  --think-rule:     color-mix(in srgb, var(--cinnamon) 78%, transparent);
  --think-rule-end: color-mix(in srgb, var(--cinnamon) 30%, transparent);
  --think-ring:     color-mix(in srgb, var(--cinnamon) 68%, transparent);
  --think-ring-on:  color-mix(in srgb, var(--gold) 80%, transparent);
  --think-fil:      color-mix(in srgb, var(--cinnamon) 34%, transparent);
  --think-fil-2:    color-mix(in srgb, var(--gold) 26%, transparent);
  --think-mark:     color-mix(in srgb, var(--cinnamon) 80%, transparent);
  --think-mark-on:  var(--gold);
  --think-num:      color-mix(in srgb, var(--clay) 88%, var(--cinnamon));
  --think-num-on:   var(--gold);
  --think-halo:     color-mix(in srgb, var(--gold) 26%, transparent);
  --think-halo-2:   color-mix(in srgb, var(--gold) 12%, transparent);
}

.think-field { position: relative; padding-block: clamp(1.25rem, 0.4rem + 2vw, 2rem); }

/* ---- the wide vertical axis ---- */
.think-axis {
  position: absolute; left: 50%; width: 1px; z-index: 1;
  top: calc(-1 * var(--think-lead)); bottom: calc(-1 * var(--think-trail));
  transform: translateX(-50%); pointer-events: none;
  background: linear-gradient(180deg,
    transparent 0, var(--think-rule) 7%, var(--think-rule) 74%, var(--think-rule-end) 92%, transparent 100%);
}
.think-axis::before {
  content: ""; position: absolute; top: 0; left: 50%;
  width: 7px; height: 7px; border-radius: 50%;
  border: 1px solid var(--think-ring); transform: translate(-50%, -50%);
}

/* ---- the grid ---- */
.beliefs {
  list-style: none; margin: 0; padding: 0; position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  grid-template-rows: auto 1px auto;
  column-gap: var(--think-channel); row-gap: var(--think-row);
  align-items: start;
}
.belief:nth-child(1) { grid-area: 1 / 1; }
.belief:nth-child(2) { grid-area: 1 / 2; }
.belief:nth-child(3) { grid-area: 3 / 1; }
.belief:nth-child(4) { grid-area: 3 / 2; }

/* ---- a belief ---- */
.belief { position: relative; padding-top: var(--think-meta); }
/* The register. A real button, so the relationship a pointer can show is also
   reachable from the keyboard and a phone reader can jump between beliefs. The digits
   stay small; the touch target is opened up with invisible padding and a negative
   offset so nothing moves and the target never covers body copy. */
.belief-reg {
  position: absolute; top: 0; left: 0;
  display: inline-flex; align-items: flex-start; justify-content: flex-start;
  margin: -14px 0 0 -14px; padding: 14px 18px 14px 14px;
  min-width: 44px; min-height: 44px;
  border: 0; background: none; cursor: pointer; -webkit-appearance: none; appearance: none;
  font-family: var(--font-mono); font-size: var(--fs-label);
  letter-spacing: 0.14em; line-height: 1.35; color: var(--think-num);
  transition: color .24s var(--think-ease);
}
.belief-reg::before {
  content: ""; position: absolute; top: 18px; left: 1px;
  width: 6px; height: 6px; background: var(--think-mark); transform: rotate(45deg);
  transition: background .24s var(--think-ease), opacity .24s var(--think-ease);
}
.belief-reg > * { pointer-events: none; }
.belief h3 {
  font-family: var(--font-display); font-weight: 600;
  font-size: clamp(1.2rem, 0.92rem + 1vw, 1.5rem);
  letter-spacing: -0.02em; line-height: 1.14;
  color: color-mix(in srgb, var(--ink) 90%, transparent);
  margin: 0 0 var(--space-3); text-wrap: balance;
  transition: color .24s var(--think-ease);
}
.belief p { margin: 0; color: var(--muted); font-size: var(--fs-body); max-width: 42ch; text-wrap: pretty; }

/* ---- the centre ---- */
.think-cross { grid-area: 2 / 1 / 2 / -1; position: relative; height: 1px; pointer-events: none; }
.think-datum {
  position: absolute; top: 50%; margin-top: -0.5px; height: 1px; z-index: 1;
  left: calc(-1 * var(--think-bleed)); right: calc(-1 * var(--think-bleed));
  background: linear-gradient(90deg,
    var(--think-rule-end) 0, var(--think-rule) 12%, var(--think-rule) 88%, var(--think-rule-end) 100%);
}
.think-datum::before, .think-datum::after {
  content: ""; position: absolute; top: 50%;
  width: 8px; height: 8px; border-radius: 50%;
  border: 1px solid var(--think-rule); background: var(--bg-alt);
}
.think-datum::before { left: 0;  transform: translate(-50%, -50%); }
.think-datum::after  { right: 0; transform: translate( 50%, -50%); }

.think-filaments {
  position: absolute; left: 50%; transform: translateX(-50%);
  top: calc(50% - var(--fil-up)); width: var(--fil-w); z-index: 0;
  height: calc(var(--fil-up) + var(--fil-down));
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 16%, #000 62%, transparent 97%);
          mask-image: linear-gradient(180deg, transparent 0, #000 16%, #000 62%, transparent 97%);
}
.think-filaments path { fill: none; stroke-width: 1; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.fil-gather path { stroke: var(--think-fil); }
.fil-open   path { stroke: var(--think-fil); }
.fil-open   path:nth-child(4n+1), .fil-open path:nth-child(4n+2) { stroke: var(--think-fil-2); }
.fil-gather, .fil-open { transition: opacity .3s var(--think-ease); }

/* The core. One radial fill lit from the upper left, so it reads as a small body with
   depth rather than a bloom, plus two very low halos. Only Gold in the section.

   ONE TRANSFORM OWNER, TWICE.
   .think-core is the POSITION. Its transform carries the centring and, on the narrow
   compositions, --lens-y: where the ball stands. Only thinking-interaction.js writes
   into it.
   .think-core-visual is the BALL. It carries the material, the scroll-scrubbed scale
   from --tp, and the pointer lean from --core-dx/dy. Those two arrive from different
   sources, so they are given different CSS properties rather than one transform list:
   the scrub owns `transform`, the lean owns the independent `translate`. Neither can
   overwrite the other and neither needs to know the other exists.

   The split exists because they were one element. A reader scrolling with the cursor
   resting over the copy crosses a belief boundary without moving the mouse, which
   fires pointerenter, which wrote --core-dx/dy into the same transform the scroll was
   scrubbing: an untransitioned eight-pixel step, mid-scroll, four times on the way
   past. That is the skip. */
.think-core {
  position: absolute; top: 50%; left: 50%; z-index: 2;
  width: var(--think-core-d); height: var(--think-core-d);
  transform: translate(-50%, -50%);
}
.think-core-visual {
  position: absolute; inset: 0; border-radius: 50%;
  /* The lean, on its own property so it never touches the scrubbed transform. A
     discrete pointer state, not scroll, so here a CSS transition is the right tool:
     it is the only interpolator on this property and nothing is chasing it. */
  translate: var(--core-dx) var(--core-dy);
  transition: translate .26s var(--think-ease);
  background: radial-gradient(circle at 34% 30%,
    color-mix(in srgb, var(--gold) 40%, var(--surface)) 0%,
    var(--gold) 32%,
    color-mix(in srgb, var(--gold) 66%, var(--cinnamon)) 68%,
    var(--cinnamon) 100%);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--cinnamon) 20%, transparent),
    0 0 18px 5px var(--think-halo),
    0 0 52px 16px var(--think-halo-2);
}
/* The signal. Hidden on the wide compositions, where the datum already reaches the
   copy; on the rail it is the short line that says which belief is being read. */
.think-core-visual::after {
  content: ""; position: absolute; top: 50%; left: 100%;
  width: var(--signal-w, 0px); height: 1px; margin-left: 6px;
  background: linear-gradient(90deg, var(--think-ring-on), var(--think-rule-end));
  transform: translateY(-50%) scaleX(var(--signal-s, 0));
  transform-origin: 0 50%;
  transition: transform .34s var(--think-ease);
}

/* ---- ACTIVE STATE, all modes ------------------------------------------------
   Only colour and opacity change on the copy, and only on the belief being read.
   The three others are never dimmed: softening body text to mark a state trades
   contrast for decoration, which is a bad trade. Their registers soften instead,
   because a register is decoration and can afford it. */
#thinking[data-active] .belief:not([data-on]) .belief-reg { color: color-mix(in srgb, var(--think-num) 62%, transparent); }
#thinking[data-active] .belief:not([data-on]) .belief-reg::before { opacity: .5; }
.belief[data-on] .belief-reg { color: var(--think-num-on); }
.belief[data-on] .belief-reg::before { background: var(--think-mark-on); opacity: 1; }
.belief[data-on] h3 { color: var(--ink); }

/* AUTHORIZED REPAIR 1, light theme only. The register number is text inside a live
   button, and in light it measured 4.371:1 at rest, 2.294:1 once another belief was
   active, and 3.451:1 when active, against a 4.5:1 AA floor. Dark already passed.

   Two things had to change together, because neither alone is enough. The token was
   below the floor at full opacity, so darkening it is required; and the de-emphasis
   was done with alpha, where no colour in the locked palette can reach 4.5:1 at all
   (Carbon, the darkest, tops out at 4.26). So the recession is carried by tone here
   instead of by transparency.

   This is the brand rule applied literally: Cinnamon is a graphic colour, not a text
   colour. The diamond keeps Cinnamon because it is a graphic. The number becomes ink.
   Locked palette only, no new value. Dark mode is untouched.

   Measured after: resting 6.211:1, active 12.847:1. */
:root:not([data-theme="dark"]) #thinking {
  --think-num:    color-mix(in srgb, var(--clay) 70%, var(--carbon));
  --think-num-on: var(--carbon);
}
:root:not([data-theme="dark"]) #thinking[data-active] .belief:not([data-on]) .belief-reg {
  color: var(--think-num);
}

/* AUTHORIZED FINAL CONTRAST REPAIR, dark theme only. The dark mirror of the rule
   above. Dark's inactive-while-a-sibling-is-active register carried the same 62% alpha
   fade from styles.css:1397 over the dark --think-num, measuring 3.345:1 against the
   4.5:1 AA floor while resting (6.212) and active (7.116) already passed. This drops
   the alpha and lets the state recede by tone, exactly as light now does, taking the
   inactive number from 3.345 to ~6.212. Locked palette only, no new value; dark
   --think-num stays color-mix(#BEB5A7 88%, #B5612C) and --think-num-on stays Gold.
   Light mode is untouched. */
:root[data-theme="dark"] #thinking[data-active] .belief:not([data-on]) .belief-reg {
  color: var(--think-num);
}
.belief-reg:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

/* The nearest structural segment. Beliefs one and two sit above the datum and are
   held by the gathering half of the bundle; three and four sit below it and are held
   by the opening half. Activating a belief lifts only the half that reaches it, which
   is the smallest true statement the drawing can make about which quadrant is in
   play. Opacity only: no stroke changes weight, so nothing appears to thicken. */
#thinking[data-active="1"] .fil-gather,
#thinking[data-active="2"] .fil-gather,
#thinking[data-active="3"] .fil-open,
#thinking[data-active="4"] .fil-open { opacity: 1.75; }
#thinking[data-active="1"] .fil-open,
#thinking[data-active="2"] .fil-open,
#thinking[data-active="3"] .fil-gather,
#thinking[data-active="4"] .fil-gather { opacity: .7; }

/* ---- HOUSE: THE PODIUM GALLERY --------------------------------------------
   Copy on the left, a sculptural stage on the right. The stage is a live WebGL
   gallery (assets/house/house-scene.js + house-controller.js) that UPGRADES a
   static CSS/SVG fallback: three brand symbols standing on three stone pedestals.
   Exactly three symbols, no names, equal importance, center pedestal tallest for
   composition — not rank. The section is complete with no JS and no WebGL. */
#house {
  --house-ink:    var(--ink);
  --house-amb:    5%;
  --house-stone:  color-mix(in srgb, var(--stone) 84%, #ffffff 16%);
  --house-stone2: color-mix(in srgb, var(--stone) 72%, var(--carbon) 8%);
  --house-edge:   color-mix(in srgb, #ffffff 42%, transparent);
}
:root[data-theme="dark"] #house {
  --house-amb: 9%;
  --house-stone:  #322D26;
  --house-stone2: #24201B;
  --house-edge:   color-mix(in srgb, var(--gold) 16%, transparent);
}

/* THE SECTION'S OWN GROUND, MATCHED TO THE RENDERER.
   The WebGL environment is opaque and covers this section exactly -- measured
   0.00 px delta on all four edges at every one of the six rigs -- so this
   colour is not normally visible at all. It exists for three reasons and each
   one matters:

     it is what shows if the canvas is one subpixel short at some future
     zoom or DPR, and it is the renderer's own mean edge colour, so a sliver
     of it is indistinguishable from the render;

     it is the ground for the no-JS and no-WebGL fallback, which hides the
     canvas entirely -- previously that fallback sat on the global page beige
     and the pedestal diagram floated on it;

     and it stops the House from being a page-coloured rectangle with a
     differently-coloured picture pasted into the middle, which is the exact
     read this pass exists to remove.

   Values are the measured mean of the four canvas edge strips:
     light  #D8D1C6   (top #E8E4DD  bottom #D3CBC0  left #CFC6B8  right #D7CFC4)
     dark   #322B25   (top #524840  bottom #1F1913  left #2C2621  right #2C261F)
   The edges genuinely differ top to bottom -- wall above, floor below -- so a
   single flat colour cannot match all four. It is not asked to: it has to be
   invisible against whichever edge it borders, and the mean is within 12 levels
   of the worst of them and within 4 of the sides. */
.section-house {
  position: relative; overflow: clip; padding-block: var(--section-y);
  background: #D8D1C6;
}
:root[data-theme="dark"] .section-house { background: #322B25; }
/* THE PAINTED ROOM LIGHT IS GONE.
   It was a CSS radial gradient standing in for an environment the canvas did
   not cover -- a warm wash at 8% 6% of the section, i.e. behind the copy, which
   is exactly the half of the section that had no real room in it. Now that the
   WebGL environment spans the whole section there is a real floor, a real wall
   and a real falloff behind the text, and a second painted gradient over the
   top of them could only fight the lighting it is imitating. */
/* Technical-marker CSS removed with the markup it styled. */

/* The copy sits ON the environment now, so it carries z-index 1: above the
   canvas (0) and below the controls (5).
   Below the side-by-side breakpoint the stage used to be an in-flow box whose
   height gave the section its lower half. It is absolute at every breakpoint
   now, so that height has to come from somewhere or the section would collapse
   to the height of the copy and the podiums would have nowhere to stand. The
   copy reserves it, with the same clamp the stage used to carry. */
.house-inner { position: relative; z-index: 1; display: grid; row-gap: clamp(1.4rem, 4.5vw, 2.4rem);
  padding-bottom: clamp(22rem, 82vw, 30rem); }
@media (min-width: 64rem) { .house-inner { padding-bottom: 0; } }
.house-lede { max-width: 34rem; }
.house-lede p { color: var(--ink-2); font-size: var(--fs-h3); }
.house-lede p + p { margin-top: var(--space-5); }
/* THE TWO QUIET LINES GET THEIR OWN INK, HOUSE ONLY.
   The global --faint (#6B655C) was chosen against the page's flat #EEEBE4. The
   House now sits on a daylight floor at luminance ~208, and the same ink
   measures 3.72:1 there -- under the 4.5:1 this section is held to. Neither
   lightening the floor (which undoes the grounding) nor hiding the line is
   acceptable, so the ink darkens: #5A544B measures 4.74:1 on the same backdrop.

   Scoped to #house so no other section's caption moves, and it is a COLOUR
   change only -- the caption's text, family, size, tracking and position are
   untouched. */
/* LIGHT-MODE CAPTION INK, MEASURED AGAINST THE PIXELS IT ACTUALLY SITS ON.
   The previous #5A544B was recorded at 4.74:1, but that figure was taken
   against a CSS backdrop token -- and the House stage is the WHOLE section, so
   this caption sits over the live WebGL environment, not over that token.
   Sampled from real screenshots, the backdrop under the line is (200,192,176)
   on desktopPrimary through (184,168,152) on desktopMedium, and #5A544B
   measured 4.46 / 3.54 / 3.77 / 3.66 / 3.61 :1 across the light rigs -- every
   one of them under AA for small text, which is exactly how it read on screen.

   #3E3934 is the same warm neutral taken darker, not a new hue: it holds
   4.94-6.32:1 on those backdrops while staying well under the lede's own
   7.85-9.90:1, so the line is clearly readable and still visibly secondary to
   the body copy. Nothing about the background, the size or the tracking moves.
   Dark mode is untouched -- it measures 7.14-9.88:1 and already passes. */
.house-caption { font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: 0.14em; color: #3E3934; }
#house .section-label { color: #5A544B; }

/* THE SAME CORRECTION, FOR THE TWO PANEL-BACKED SECTIONS.
   Found by the 2026-08-07 lock audit. `--muted` is #6B655C, and on the page
   ground #EEEBE4 it measures 4.84:1 and passes. Thinking and Standards do not
   sit on the page ground: both carry --panel, #E6E2DA, and against that the
   same ink measures 4.464:1 — under AA for normal text by 0.036. Four places
   read at that value, all of them resolving through var(--muted): Thinking's
   section label, its intro, its four belief paragraphs, and every Standards
   definition. `.standard dd` and `.section-intro` are 23.2px, which is just
   under the 24px large-text threshold, so 4.5 is the bar they must clear, not 3.

   #69635A is #6B655C multiplied by 0.983 — the same warm neutral taken very
   slightly darker, at exactly the same hue (36.0 degrees, unchanged), which is
   the smallest hue-preserving step that clears the bar with margin rather than
   landing on 4.500 and rounding back under. It measures 4.601:1 on the panel and
   4.993:1 on the page ground.

   Scoped three ways so nothing else can move: to these two sections, to the
   token those four rules already read, and away from dark mode — where the same
   text measures 6.82:1 and needs no help. `:not([data-theme="dark"])` rather
   than `[data-theme="light"]` so the correction still applies if the theme
   attribute never lands (scripting off). #house keeps its own darker label
   above; its backdrop is darker again and was corrected in an earlier pass. */
:root:not([data-theme="dark"]) #thinking,
:root:not([data-theme="dark"]) #standards { --muted: #69635A; }

/* THE AUTHORED NARROW BREAK.
   Inline by default, so nothing changes at 430 px and above. At 400 px and
   below the two sentences become their own lines, which replaces the
   line-breaker's orphaned "apart." with a break chosen on purpose. No
   font-size change, no tracking change, no nowrap -- and because each line is
   shorter than the one it replaces, this removes overflow risk rather than
   adding any. */
.house-caption > .hc-l { display: inline; }
@media (max-width: 400px) {
  .house-caption > .hc-l { display: block; }
}

/* ── THE SECTION LABEL, WHERE IT CROSSES ONTO THE CYCLORAMA ──────────────────
   "The house" is 12.48 px, so AA asks 4.5:1, and #5A544B measured 3.61:1 on
   mobileLarge and 3.77:1 on tabletPortrait against the pixels actually behind
   it. On the other four approved rigs it passes (4.60 / 4.65 / 4.69 / 5.85).

   THE LABEL SITS ON THE CYCLORAMA HORIZON, SO ITS BACKDROP IS BISTABLE.
   Below desktop staging the label lands either on the lit near-paper above the
   horizon (231-235) or on the gradient below it (187-196), and WHICH ONE is
   driven by viewport HEIGHT as much as width -- measured, 390x844 gives
   (231,227,218) and 4.5:1 is met, while 390x860 gives (188,178,163) and it is
   not. 360x640, 375x667, 390x860 and 414x896 all fail; 360x800, 375x812 and
   414x736 all pass. A min-width bound therefore cannot close this: there is no
   width at which the outcome is decided. The override runs to the top of the
   sub-desktop range instead, so the guarantee holds at every viewport rather
   than at the ones that happened to be sampled.

     #474339   darkest gradient (187,178,162)  4.70:1
               mobileLarge      (188,179,164)  4.75:1   (caption 5.58)
               tabletPortrait   (192,183,168)  4.97:1   (caption 5.83)

   THE COST, STATED RATHER THAN HIDDEN. On the near-paper half of that bistable
   backdrop this ink measures 7.63:1 against a caption at ~5.46:1, so there the
   label reads stronger than the CAPTION. That much is unavoidable, not a
   choice: the mildest ink that clears AA on the gradient (#4A453E, 4.52:1)
   still lands at 7.35:1 on near-paper, because the two backdrops differ by 0.34
   of relative luminance while the caption's does not move. No single value can
   hold both AA and subordination-to-the-caption down here, and AA governs.

   The value is nonetheless solved to keep the label under the LEDE, which is
   the tighter of the two remaining constraints: #45413A reached 7.85:1 on
   near-paper against a lede at 7.82:1 -- a tie -- so it was lightened until it
   cleared. 4.70:1 on the darkest gradient against 7.63:1 on the lightest paper
   is the widest window that satisfies both ends.

   The same warm neutral as the rest of the House copy, taken to 79 % of its
   value. Not pure black. Light mode only: the dark rule for this element is
   :root[data-theme="dark"] #house .section-label, which outranks this one, so
   dark mode is corrected separately below. Wording, position, family, size,
   weight, tracking and the gold marker are not addressed here at all. */
@media (max-width: 1023px) {
  #house .section-label { color: #474339; }
}
:root[data-theme="dark"] .house-caption,
:root[data-theme="dark"] #house .section-label { color: var(--faint); }

/* DARK-MODE LABEL INK, AND WHY IT IS NOT var(--faint).
   var(--faint) is rgb(190,181,167). Against the pixels actually behind the
   label it measures 4.2809:1 at 414x736 and 4.4221:1 on mobileLarge -- below AA
   for 12.48 px text.

   That figure was worth doubting before acting on it: with the ink fixed, the
   whole 4.49-vs-4.50 window is 0.000256 of linear luminance, which is 14 % of a
   single 8-bit step, so a coarse sampler could not have decided it either way.
   It was therefore re-measured with the glyph run masked and dilated, in linear
   light, unrounded -- and the result is FLAT at 4.2809 and 4.4221 across
   dilation radii of 3, 5, 8 and 12 px. An antialiasing halo would fall away as
   the mask grew; this does not move at all, so the backdrop is real and the
   failure is real.

   #C6BCAE is var(--faint) lifted 4 %: same warm neutral, nowhere near white.
   It takes the worst measured case to 4.63:1 and mobileLarge to 4.78:1, and it
   is one shared value -- no per-rig overrides, because none are needed. The
   label stays well under its dark-mode neighbours everywhere (on mobileLarge:
   caption 6.66, heading 8.66, lede 9.95).

   Scoped to the label alone: the caption above keeps var(--faint), and light
   mode is untouched because this selector requires data-theme="dark". */
:root[data-theme="dark"] #house .section-label { color: #C6BCAE; }

/* THE STAGE IS NOW THE WHOLE SECTION.
   Previously a box in the right-hand ~59 % of the section, which is what made
   the House read as "podiums pasted onto a flat webpage": the environment could
   only ever exist inside that box, so the copy half had no room behind it and
   the two halves could not belong to one space. The canvas is therefore
   stretched to the section and the copy is layered OVER it.

   `isolation: isolate` is removed deliberately, and it is the whole mechanism.
   It created a stacking context, which meant nothing inside the stage could
   ever paint behind `.house-inner`. Without it the stage's positioned children
   join the section's own stacking context, so they can be ordered individually:
   the canvas below the copy, the controls above it. The stage itself takes no
   z-index for the same reason -- any value would re-create the context.

   The centre mark's anchor projected to y = 0.051 of the old canvas, i.e. 33 px
   from its top edge, so the top of that symbol was cut off by the canvas
   boundary in every frame. Removing the boundary removes the crop. */
.house-stage {
  position: absolute; inset: 0;
  width: auto; height: auto;
}
#houseStage {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
  z-index: 0;                    /* the environment, behind the copy */
}
.house-stage:not([data-house-live]) #houseStage { display: none; }

/* PHASE 07R-W — THE INTERACTION BLOCKER.
   Measured cause, not a guess: `document.elementFromPoint` at the centre of the
   left and centre controls returned `.wrap.house-inner`, so their pointer events
   were being swallowed by the copy wrapper. The right control was clear of that
   wrapper's box and worked perfectly all along — real pointer events, hover,
   selection, aria-pressed and 143,206 changed pixels — which is why the failure
   looked like "nothing works" rather than "one thing is mispositioned".

   `.house-inner` carries `z-index: 1`, so it paints and hit-tests ABOVE the
   stage regardless of the stage coming later in source order. Two things are
   therefore needed together, and neither is sufficient alone:

     1. the stage must sit above the copy wrapper  -> z-index 2
     2. the stage must not itself swallow the copy -> pointer-events none on
        every part of it except the three real buttons

   Without (2), raising the stage would put a transparent 979 px canvas over the
   right-hand end of the heading and kill text selection there. With both, only
   the three buttons are interactive and everything else passes straight
   through. Overlay ALIGNMENT was never wrong — measured deviation was 0.0 px. */
.house-stage { pointer-events: none; }   /* no z-index: see the stage block above */
#houseStage { pointer-events: none; }
.house-controls { pointer-events: none; }
.house-fallback { pointer-events: none; }
.house-target { pointer-events: auto; }

/* DOM ACCESSIBILITY OVERLAY. Three real buttons over the projected logos. The
   canvas is decorative (aria-hidden); interaction is entirely DOM semantics. */
.house-controls { position: absolute; inset: 0; z-index: 5; }
.house-stage:not([data-house-live]) .house-controls { display: none; }
.house-target {
  position: absolute; top: 50%; left: 50%;
  width: clamp(52px, 13%, 108px); height: clamp(52px, 21%, 156px);
  translate: -50% -50%;
  margin: 0; padding: 0; border: 0; background: none;
  -webkit-appearance: none; appearance: none; cursor: pointer;
  border-radius: 16px; color: inherit;
}
.house-stage[data-house-stable="0"] .house-target { pointer-events: none; }
.house-target > span {   /* the positional label, for assistive tech only */
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.house-target:focus { outline: none; }
.house-target:focus-visible {
  outline: 2px solid var(--cinnamon); outline-offset: 3px;
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--gold) 32%, transparent);
}

/* THE FALLBACK (Phase 07R-V, rebuilt). One self-contained SVG, no canvas.
   The previous version was three flex-laid CSS blocks inside the bled stage box.
   It inherited that box's overflow, so at 1672 the three marks were cut down to
   slivers and the pedestals rendered as near-invisible pale rectangles — the
   no-JS state did not communicate the House at all. A single viewBox cannot
   clip: it scales the whole composition into whatever box it is handed, so the
   fallback is now correct at every viewport by construction rather than by
   tuning. Its geometry follows the reference's own proportions — broad low left,
   tall set-back centre, mid-height right — so the triangular hierarchy survives
   without any 3D. */
/* The box is given explicitly rather than by grid centring. Under
   `place-items: center` the SVG sized itself to its CONTENT — measured 931 x 695
   inside a 979 x 650 stage — so `preserveAspectRatio` had nothing to fit against
   and the composition overran the stage by 45 px. Absolute inset makes the SVG
   exactly the box, after which the viewBox can no longer overflow it.
   The bottom inset keeps the composition inside the FOLD as well as inside the
   stage: the House section is legitimately taller than a 941 px viewport, so a
   fallback that filled the stage would still have its bases below the screen. */
.house-fallback {
  /* Constrained to the region the podiums actually occupy rather than to the
     whole section, which the stage now spans: given the full box the fallback's
     viewBox would scale its diagram across the copy as well. */
  position: absolute; inset: 34% 0 6% 0; z-index: 0;
  padding: 0 clamp(0.5rem, 2vw, 1.5rem);
}
.house-stage[data-house-live] .house-fallback { display: none; }
.fb-svg { position: absolute; inset: 0; display: block; width: 100%; height: 100%; }
/* Explicit stone values rather than the theme vars. --house-stone resolves to
   84 % stone + 16 % white, which against the section's own #EEEBE4 left the
   pedestals reading as near-invisible pale rectangles — the audited fault. These
   are the master specification's own stone range (section 8), which separates
   clearly from the background while staying warm ivory limestone. */
.fb-svg .fb-s0 { stop-color: #EFE7DC; }
.fb-svg .fb-s1 { stop-color: #E2D6C6; }
.fb-svg .fb-s2 { stop-color: #CDBEAC; }
.fb-body { fill: url(#fbStone); }
.fb-side { fill: url(#fbStoneSide); }
/* the cap is the same stone one step lighter — a crown, not a lid */
.fb-cap  { fill: #F1E9DF; }
:root[data-theme="dark"] .fb-svg .fb-s0 { stop-color: #4A433A; }
:root[data-theme="dark"] .fb-svg .fb-s1 { stop-color: #3C362E; }
:root[data-theme="dark"] .fb-svg .fb-s2 { stop-color: #2C271F; }
:root[data-theme="dark"] .fb-cap { fill: #554D42; }
.fb-reveal { stroke: var(--gold); stroke-width: 1; opacity: 0.55; }
.fb-datum { stroke: var(--gold); stroke-width: 1; opacity: 0.3; }
.fb-datum-node { fill: var(--gold); opacity: 0.45; }
.fb-mark svg { overflow: visible; }
.fb-mark { fill: var(--house-ink); }
.fb-pod { filter: drop-shadow(0 14px 18px color-mix(in srgb, var(--carbon) 20%, transparent)); }

/* TABLET LANDSCAPE BAND (Phase 07R-V), 64rem to 80rem.
   Stacking this band put the installation ~614 px down the page, so at
   1194 x 834 the scene fell below the fold and no single screenshot could show
   the copy and the installation together. It is therefore side-by-side like
   desktop, but with its own proportions: a narrower copy column and a stage that
   is anchored to the section, so the canvas still cannot leave the viewport. */
@media (min-width: 64rem) and (max-width: 79.999rem) {
  .section-house { min-height: min(94vh, 52rem); display: flex; align-items: center; }
  .house-inner { width: 100%; grid-template-columns: minmax(0, 26rem); }
  /* the stage is the section here too; the composition is set by the camera */
  .house-stage { position: absolute; inset: 0; width: auto; height: auto; min-height: 0; }
}

/* PHASE 07R-V — the side-by-side House layout now begins at 80rem (1280 px),
   not 62rem (992 px).
   At 62rem the copy column and an anchored stage had to share a 992-1279 px
   window, which is what forced the stage to be bled outside the wrap in the
   first place. Between 1024 and 1279 the section now stacks — copy above, stage
   below at full width — which is what gives the tablet-landscape rig a genuinely
   independent composition instead of a squeezed desktop one, and is why
   1194 x 834 no longer cuts the right podium. */
@media (min-width: 80rem) {
  .section-house { min-height: min(92vh, 58rem); display: flex; align-items: center; }
  .house-inner {
    width: 100%;
    /* measured from the master reference: text column ~24% of the frame, the
       sculptural stage taking the right ~60% */
    grid-template-columns: minmax(0, 34%) minmax(0, 66%);
    grid-template-areas: "head stage" "lede stage" "cap stage";
    grid-template-rows: auto auto 1fr;
    column-gap: clamp(2rem, 5vw, 5.5rem); align-items: start;
  }
  .house-inner > .section-head { grid-area: head; }
  .house-inner > .house-lede { grid-area: lede; max-width: 27rem; }
  .house-inner > .house-caption { grid-area: cap; align-self: end; padding-top: 1.5rem; }
  /* A landscape stage box. The showroom is a wide composition: a box taller than
     it is wide would force the camera to pull back to keep all three in frame,
     leaving a large empty floor. Centred in the row, so the copy sets the height. */
  /* The reference cluster occupies ~44% of frame width and ~63% of its height:
     the stage must be a large architectural field, not a small illustration. */
  /* AUTHORIZED House-scoped layout correction.
     The measured reference cluster occupies x 0.432-0.950 and rests on a base
     line at y 0.865 of the SECTION. The wrap's right padding stopped the stage
     at x 0.872, so no camera value could reach the reference's right edge.
     The stage is therefore positioned against the section itself, bleeding past
     the wrap and bottom-aligned to the measured base line. The copy column
     keeps the wrap's grid; the two no longer share a column. */
  /* THE STAGE IS THE SECTION.
     The solved insets that used to live here -- left 41.03 %, right 0.42 %,
     top 29.49 %, bottom 0.54 % -- placed a 970 x 650 canvas in the right-hand
     part of the frame. They are gone. Every one of them was a way of describing
     where the picture stopped, and the picture is not supposed to stop: the
     environment has to exist behind the copy as well or the two halves of the
     section cannot read as one room.

     The composition is now set entirely by the camera (fov + the two lens
     shifts, re-solved per rig), which is where a composition belongs. The
     cluster lands on the same page pixels it did before; what changed is that
     there are another 680 px of room to the left of it and 274 px above it,
     and that room now has a floor and a wall in it. */
  .house-stage { position: absolute; inset: 0; width: auto; height: auto; min-height: 0; aspect-ratio: auto; }
  /* the copy column no longer shares a grid track with the stage */
  .house-inner {
    grid-template-columns: minmax(0, 30rem);
    grid-template-areas: "head" "lede" "cap";
  }
  .house-inner > .house-lede { max-width: 27rem; }
}

/* ==========================================================================
   HOUSE COPY — REFERENCE-MATCHED COMPOSITION (House-scoped, desktop only)

   Measured against HOUSE_MASTER_REFERENCE.png (1672 x 941,
   sha256 b87d373d…c3a082) the copy sat 171 px too far right, its
   heading-to-body gap was 98 px against the reference's 50, and the closing
   line hung 47 px too low. The cause of the horizontal error is that the copy
   inherits `.wrap`, which is a CENTRED 74rem column: at 1672 that puts its
   left edge at 244 + 56 = 300 px and no amount of column re-sizing can move it
   to the reference's 129 px while it stays centred. So inside the House, and
   only inside the House, the copy column is placed against the SECTION.

   Everything here is position and rhythm. The copy itself, the font family and
   the heading's size are untouched; the body's size is reduced because the
   reference's body is set noticeably smaller than the heading and the target
   box (409 -> 644, seven lines) cannot be met at the inherited 23.2 px.
   ========================================================================== */
@media (min-width: 80rem) {
  /* THE SECTION IS THE REFERENCE FRAME.
     The master is a single 1672 x 941 frame and every y-target in the brief is
     stated against it. The section measured 928.6 tall, so the installation's
     bottom margin could not reach the specified 80-95 px however the camera was
     placed -- 853 + 75.6 is all a 928.6 px frame has. Filling the viewport makes
     the section the same frame the reference is, which is the only way the
     absolute targets mean anything. The 52rem floor keeps the copy from
     crowding on a short window. */
  /* The section's own 144 px block padding is dropped here: it exists to space
     a section from its neighbours in a scrolling document, and this section is
     a full-frame composition whose internal placement is set against the
     reference. Leaving it in put the copy 144 px below wherever it was asked
     to be. */
  /* The section keeps its scene height -- the installation needs it -- but its
     block padding comes back to the site's own --section-y. Dropping it was how
     the copy got anchored to an absolute y taken off the reference frame; with
     the copy back on the shared grid its entry point has to be governed by the
     same rhythm every other section uses. */
  .section-house { min-height: max(52rem, 100vh); align-items: flex-start;
                   padding-block: var(--section-y); }

  /* ── THE HOUSE COPY IS BACK ON THE SHARED SITE GRID ──────────────────────
     What was here overrode `.wrap` outright -- `max-width: none;
     margin-inline: 0; padding-inline: 7.72% 0` -- to reproduce the reference
     frame's 129 px left margin. Measured live, that put the House copy at
     content-left 129.1 px at 1672 and 111.2 px at 1440 while About, Thinking
     and Standards all sat at 300.0 and 184.0: the House was the only section on
     the page not using the site's own column, and the gap was 170.9 px at the
     primary desktop rig. Below 80rem it already matched, because this block is
     the only thing that ever broke it.

     Those three declarations are simply removed. `.house-inner` carries `.wrap`
     in the markup, so deleting the overrides hands the column straight back to
     `max-width: var(--wrap)` + `margin-inline: auto` +
     `padding-inline: var(--wrap-pad)` -- the governing tokens, not a copy of
     their current values. Nothing here restates a gutter in pixels, so the
     House now follows --wrap-pad's clamp through every breakpoint for free.

     The column WIDTH stays 25.3rem: that is the copy's measure, not its
     position, and it is what keeps the heading on two lines and the body at the
     reference's line length. The installation is re-framed to clear it -- see
     the camera solve in the pass report. */
  .section-house > .house-inner {
    grid-template-columns: minmax(0, 25.3rem);
    row-gap: 0;
  }
  .section-house > .house-inner > .house-lede { max-width: 25.3rem; }

  /* ── SECTION ENTRY RHYTHM, HANDED BACK TO THE SITE'S TOKENS ──────────────
     This block used to set the copy's vertical placement absolutely:
     `.section-head { margin: 0 }` plus `padding-block-start: 12.06rem` on the
     column, chosen so the label landed on the reference frame's y = 197. With
     the section's own --section-y restored above, that 12.06 rem would stack on
     top of it and open exactly the "giant empty strip above House copy" the
     brief rules out. Both are gone: the head now carries the same --head-gap
     every other section's head carries, and the label-to-heading interval is
     the shared --space-5 that `.section-label` already sets. */
  .section-house .section-head { margin: 0 0 var(--head-gap); }
  /* The heading keeps its inherited size. Measured as INK rather than as the
     block box -- a block-level heading fills its column, so its border box
     reports the column edge, and an earlier reading of 534 against the
     reference's 510 was measuring the column, not the letters. The glyphs at
     the inherited 57.6 px run to 505 against the reference's 510, i.e. it was
     already right; only the leading needed tightening to reach the reference's
     113 px two-line block. */
  .section-house .section-head > h2 { margin-top: 0.3rem; line-height: 1.0; }
  /* The 12.06rem top anchor is removed with the rest of the absolute placement;
     the section's --section-y is now what sets the copy's entry point, exactly
     as it does on About, Thinking and Standards. The lede's own top margin goes
     with it -- the head's --head-gap is that interval now. */
  .section-house > .house-inner > .house-caption {
    padding-top: 0; margin-top: var(--head-gap); align-self: start;
  }
  .section-house .house-lede p { font-size: 1.19rem; line-height: 1.62; }
  .section-house .house-lede p + p { margin-top: 1.25rem; }
}

/* Reduced motion and the site's still mode: the controller renders the scene
   settled with no animation; nothing here moves either. */
@media (prefers-reduced-motion: reduce) {
  .house-target { transition: none !important; }
}


/* ---- STANDARDS: a vertical precision register ---- */
.standards { margin: 0; counter-reset: std; }
.standard { position: relative; display: grid; grid-template-columns: minmax(0, 31rem) 1fr; gap: var(--space-4) clamp(2rem, 1rem + 4vw, 5rem); align-items: baseline; padding: clamp(1.75rem, 1rem + 2vw, 2.75rem) 0; border-top: 1px solid var(--hair); }
.standard:last-child { border-bottom: 1px solid var(--hair); }
/* Numeral reads the AA-corrected muted token, not --faint. In light --muted is the
   #standards override (#69635A, 4.601:1) so the numeral clears the 4.5 text floor where
   --faint measured 4.464:1; in dark --muted and --faint resolve to the same value, so
   dark is unchanged. Locked palette only, no new colour, still recessive. */
.standard::before { counter-increment: std; content: counter(std, decimal-leading-zero); position: absolute; left: 0; top: calc(clamp(1.75rem, 1rem + 2vw, 2.75rem) - 0.2em); font-family: var(--font-mono); font-size: var(--fs-label); color: var(--muted); letter-spacing: 0.1em; }
.standard::after { content: ""; position: absolute; left: 0; top: -4px; width: 7px; height: 7px; border-radius: 50%; background: var(--hair); transition: background var(--dur) var(--ease), transform var(--dur) var(--ease); }
.standard:hover::after { background: var(--gold); transform: scale(1.4); }
.standard dt { font-family: var(--font-display); font-weight: 600; font-size: clamp(1.4rem, 1rem + 1.7vw, 2.15rem); letter-spacing: -0.02em; color: var(--ink); line-height: 1.05; padding-left: 2.5rem; text-wrap: balance; }
.standard dd { color: var(--muted); font-size: var(--fs-h3); margin: 0; max-width: 46ch; }

/* ---- STATEMENT: cinematic environment ---- */
.statement { position: relative; overflow: clip; background: var(--ink-bg); color: var(--on-ink); min-height: min(80vh, 720px); display: flex; align-items: center; padding-block: clamp(5rem, 3rem + 7vw, 9rem); }
.statement-art { position: absolute; inset: 0; z-index: 0; }
.statement-art img { width: 100%; height: 100%; object-fit: cover; object-position: center 60%; display: block; }
.statement-scrim { position: absolute; inset: 0; z-index: 1; background: var(--statement-scrim); pointer-events: none; }
.statement-inner { position: relative; z-index: 2; max-width: 46rem; text-align: center; margin-inline: auto; }
.statement-frame { position: absolute; inset: -2rem; pointer-events: none; }
.statement-frame span { position: absolute; width: 18px; height: 18px; border-color: var(--on-ink-a22); border-style: solid; border-width: 0; }
.statement-frame span:nth-child(1) { top: 0; left: 0; border-top-width: 1px; border-left-width: 1px; }
.statement-frame span:nth-child(2) { top: 0; right: 0; border-top-width: 1px; border-right-width: 1px; }
.statement-frame span:nth-child(3) { bottom: 0; left: 0; border-bottom-width: 1px; border-left-width: 1px; }
.statement-frame span:nth-child(4) { bottom: 0; right: 0; border-bottom-width: 1px; border-right-width: 1px; }
.statement-beam { width: min(18rem, 50%); margin: 0 auto var(--space-7); }
.statement-text { font-size: clamp(1.9rem, 1.1rem + 3.4vw, 3.4rem); line-height: 1.06; letter-spacing: -0.025em; color: var(--on-ink); font-weight: 600; text-wrap: balance; }
.statement-sub { margin-top: var(--space-6); color: var(--on-ink-muted); font-family: var(--font-mono); font-size: var(--fs-h3); letter-spacing: 0.02em; line-height: 1.5; }

/* ---- CONTACT ----
   The section carried seven ornaments across as many attempts and every one was
   rejected, the last for being a thread funnel. The eighth is none: there is no
   drawing here at all. "A quiet door" is not something to depict, it is something to
   enact -- by giving the section more ground than any other on the page, and exactly
   one thing to act on. */
.contact { position: relative; overflow: clip; }
/* Two anchors and air. The copy holds the left edge, the action holds the right,
   and what lies between them is the section's whole argument -- so it is left empty
   rather than filled. Both hang from the same top line: centring them made the
   action drift against a block of text it has no relationship to. */
.contact-grid {
  display: grid;
  /* max-content, not a fixed track: a 21rem track left the action's widest line 90px
     short of the right margin, so the block read as having stopped rather than as
     being aligned to the edge. Sized to its own content it sits flush, and
     space-between then puts real air between the two halves rather than inside one. */
  grid-template-columns: minmax(0, 30rem) max-content;
  grid-template-rows: auto auto;
  justify-content: space-between;
  column-gap: clamp(2.5rem, 5vw, 7rem);
  align-items: start;
}
.contact-grid .section-head { grid-column: 1; grid-row: 1; margin-bottom: 0; }
.contact-lede {
  grid-column: 1; grid-row: 2;
  font-size: var(--fs-h3); color: var(--ink-2);
  max-width: 32ch; margin-top: var(--space-4);
}

/* The action. Three things in descending authority: the control, the address it
   resolves to, and the sentence that sets expectations. The address is deliberately
   not a footnote to the button -- it is the door itself, and the only place on the
   page where the way in is written out. */
.contact-body {
  grid-column: 2; grid-row: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: clamp(14px, 1.3vw, 20px);
  /* Matches the lede's own top margin, so the button's first line and the lede's
     first line start together. That shared line is the only thing holding the two
     halves of the section to each other, now that nothing is drawn between them. */
  margin-top: var(--space-4);
}
.contact-btn { margin: 0; }
.contact-address {
  font-family: var(--font-mono);
  font-size: clamp(.95rem, .9rem + .3vw, 1.12rem);
  letter-spacing: .005em;
  color: var(--ink-2);
}
/* One step down from the address, not level with it. #4F4940 holds the small line
   near 5.9:1 on the light ground (exact-glyph measured); dark mode's --muted already
   clears 8:1 unaided. */
.contact-fine { color: var(--muted); font-size: var(--fs-body); max-width: 29ch; line-height: 1.6; }
:root:not([data-theme="dark"]) .contact-fine { color: #4F4940; }

/* ============================ 5. RESPONSIVE =========================== */
/* Locked breakpoints, ported from the approved source: the header collapses to the
   brand plus controls at 980px, and the hero stacks to a single column. */
@media (max-width: 980px) {
  :root { --header-h: 72px; }
  .header-inner { width: min(100% - 32px, 900px); grid-template-columns: 1fr auto; }
  .nav-shell { display: none; }
  .contact-link { display: none; }
  .menu-button { display: grid; }
  .mobile-panel { display: block; }
  /* Tablet: the glass concept is kept, not abandoned. The plane widens but stays a
     plane, held to the left with the field open beside and behind it, so the
     composition stays art-directed instead of becoming a generic full-width card. */
  .hero-inner { width: min(100% - 32px, 900px); padding-block: clamp(36px, 4.5vh, 56px); }
  .hero-glass {
    width: min(600px, 82%);
    min-height: clamp(440px, 46vh, 512px);
    padding: clamp(36px, 4.4vw, 48px);
  }
  .hero h1 { font-size: clamp(56px, 8.2vw, 86px); max-width: 8.2ch; }
  .hero-glass p { font-size: clamp(17px, 2.1vw, 20px); max-width: 34ch; }
}
@media (max-width: 600px) {
  .header-inner { width: calc(100% - 24px); }
  .brand { font-size: 17px; }
  .brand-symbol { width: 30px; height: 30px; }
  .theme-button, .menu-button { width: 48px; height: 48px; }
  /* Mobile: the plane fits the viewport with the field still reading above, below
     and straight through it. Nothing is clipped and nothing overflows sideways. */
  .hero { min-height: calc(100svh - var(--header-h)); }
  .hero-inner { width: calc(100% - 24px); padding-block: 34px 40px; }
  .hero-glass {
    width: 100%;
    min-height: min(clamp(360px, 44vh, 424px), calc(100svh - var(--header-h) - 96px));
    padding: 32px 26px 36px;
    border-radius: 16px;
  }
  .hero h1 { margin: 26px 0 26px; font-size: clamp(52px, 13.9vw, 64px); max-width: none; }
  .hero-glass p { font-size: 17px; max-width: none; }
}

/* ---- Fourth nav item: "The standards" (2026-08-05) --------------------
   Measured, not guessed. Three pills make a 340.69px shell; the fourth takes it
   to 493.05px. The shell is the `auto` middle column of .header-inner, so the
   width it can have is the container minus two 24px gaps minus the two side
   columns' 220px floor: 493.05 + 48 + 440 = 981.05px of .header-inner, which
   `min(1248px, 100% - 192px)` first delivers at a 1181px viewport.

   Two things break below that, and both are hard failures rather than matters
   of taste. The shell gets squeezed under its own content, and .nav-link's
   overflow:hidden starts eating "About" and "Thinking"; and the shell's two
   decorative hairlines -- 52px long, sitting 14px outside it -- run into the
   brand. Measured: the brand needs 168.91px and the actions 156.39px, so the
   hairline still has clear space only while the shell-to-brand gap stays above
   66px, which it does down to 1155px and no further.

   So the header collapses to the menu button and the existing mobile panel at
   1180px instead of 980px. Nothing else changes: no font is shrunk to force the
   fit, the 25px pill padding and 44px pill height are untouched, and every pill
   keeps its locked geometry at every width where it renders. The panel is the
   existing pattern and already carries all five links. .header-inner itself is
   not touched -- at 1181px and up its locked 220px floor is never binding.
   Contact is deliberately NOT hidden here: it stays exactly where it is today
   at these widths, beside the theme control. */
@media (min-width: 981px) and (max-width: 1180px) {
  .nav-shell { display: none; }
  .menu-button { display: grid; }
  .mobile-panel { display: block; }
}

/* About: the wrap tracks the locked hero container at every breakpoint so the section
   aligns with the hero above it rather than drifting to a different gutter. */
/* Tablet: the same relationship, restacked. Head and conviction go to one column so
   the measure stays readable. */
@media (max-width: 980px) {
  .about-title { grid-column: 1 / -1; grid-row: 2; }
  .about-lead  { grid-column: 1 / -1; grid-row: 3; align-self: start;
                 margin-top: clamp(24px, 3vw, 38px); padding-bottom: 0; max-width: 58ch; }
  .about-statement { grid-column: 1 / -1; grid-row: 5; }
  .about-close     { grid-column: 1 / -1; grid-row: 6; align-self: start;
                     margin-top: clamp(24px, 3vw, 38px); padding-bottom: 0; max-width: 58ch; }
  .principles { grid-row: 8; }
  .principle p { max-width: none; }
}
/* Tablet, 768 to 1024: a triptych, not a stack of rows.
   The row version was technically responsive and visually weak: the three principles
   sat in the left two thirds with a dead third beside them, the marks became small
   annotations to the left of text instead of anchors, and three full-width rows read
   as a settings list rather than as the company's foundational statements. It also
   put the principles on a different grid from the argument above them.

   The triptych fixes all four at once -- it uses the whole measure, puts the marks back
   on top where they carry weight, and shares the upper block's grid. At 1024 each
   column is a comfortable ~300px; at 768 it is ~230px, so the type steps down slightly
   rather than the layout changing shape again. */
@media (min-width: 768px) and (max-width: 1024px) {
  /* The 980 breakpoint collapses the argument to one column, which at tablet width left
     the heading and both paragraphs inside ~72% of the measure while the triptych below
     used all of it -- a narrow article sitting in a wide device. The argument goes back
     to two columns here so the whole section is built on one width. */
  .about-title { grid-column: 1 / span 7; grid-row: 2; }
  .about-lead  { grid-column: 8 / span 5; grid-row: 2; align-self: start;
                 margin-top: clamp(20px, 2.4vw, 32px); max-width: none; }
  /* The conviction grew for desktop, where it owns 10 of 12 columns. At tablet it only
     has 7, so the same scale wrapped it to four lines and made the section tall without
     making it stronger. Capped here rather than narrowed further. */
  .about-statement { grid-column: 1 / span 7; grid-row: 4;
                     font-size: clamp(1.85rem, 1.05rem + 2.5vw, 2.85rem); }
  .about-close     { grid-column: 8 / span 5; grid-row: 4; align-self: start;
                     margin-top: calc(clamp(64px, 7vw, 116px) + clamp(8px, 1vw, 14px));
                     max-width: none; }
  .principles {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    column-gap: clamp(20px, 3vw, 38px);
    row-gap: 0;
    /* the argument and the principles are one composition, not two sections */
    margin-top: clamp(58px, 6.5vw, 84px);
  }
  .principle { max-width: none; }
  .principle-mark {
    /* Capped at 64 rather than 68 so it meets the desktop rule below exactly at the
       1024/1025 boundary -- see the note there. */
    width: clamp(54px, 7vw, 64px);
    margin-bottom: clamp(15px, 1.9vw, 21px);
  }
  .principle h3 { font-size: clamp(1.08rem, .92rem + .5vw, 1.3rem); }
  .principle p  { font-size: clamp(.9rem, .84rem + .28vw, .99rem); line-height: 1.62; }
}

/* ---- THE CONVICTION AT SMALL TABLET ----------------------------------------
   Same defect that the phone rule solves, in the other direction. The sentence is
   three clauses and every other width sets it as three clauses; between 768 and
   about 870 the tablet scale overflowed its 7-column measure and broke it into
   four lines, twice through the middle of a clause:

     768-834   We build our / brands. We do not / buy them, and we / do not sell them.
     900+      We build our brands. / We do not buy them, / and we do not sell them.

   Measured on rendered line boxes, the clean setting returns at 33.6px at 768 and
   36.0px at 834 -- 4 to 7 percent below the current scale, which is small enough
   to be invisible as a size change and large enough to fix the grammar. This ramps
   between those two points and hands back to the unchanged tablet scale at 880. */
@media (min-width: 768px) and (max-width: 879px) {
  .about-statement { font-size: clamp(2.03rem, -.154rem + 4.554vw, 2.35rem); }
}

/* ---- MARK SCALE ABOVE THE TABLET BAND --------------------------------------
   The base mark size tracks the VIEWPORT (4.4vw), but from 1025 up the section's
   content stops tracking the viewport -- it caps at 1072px, so the principle
   columns are ~300-321px from 1180 all the way to 2560. Sizing the anchor off vw
   while its column is fixed made the mark a different fraction of its column at
   every width, and it broke twice:

     viewport   column   mark    mark / column
       834       236     58.4       24.7%     tablet
      1024       291     68.0       23.4%     tablet
      1180       321     52.0       16.2%   <-- landscape tablet, anaemic
      1280       317     56.3       17.8%   <-- small laptop, anaemic
      1440       309     63.4       20.5%
      1920       300     64.0       21.3%

   Two consequences, both visible on a plate: the anchors were smallest exactly on
   landscape tablets and small laptops, where the columns are widest; and rotating
   an iPad across the 1024/1025 line made the marks SHRINK by 24% while the screen
   grew, because the tablet rule ended at 68px and the base rule restarted at 52px.

   This ramps gently over a range where the content barely changes width, and its
   floor is the tablet rule's new ceiling, so the two meet at 64px with no step.
   Everything now sits between 20% and 22% of its column. */
@media (min-width: 1025px) {
  .principle-mark { width: clamp(64px, 1.4vw + 44px, 66px); }
}

/* Mobile: the three principles become one reading sequence. Spacing separates them;
   nothing is drawn between them. */
@media (max-width: 767px) {
  .about { padding-block: clamp(64px, 13vw, 92px) clamp(72px, 13vw, 104px); }
  .principles { grid-template-columns: minmax(0, 1fr); }

  /* The conviction is three clauses, and it should be set as three clauses. The
     base size floors at 1.8rem, which governs every phone width, and at that size
     the sentence only fits its own grammar from 390px up:

       390+   We build our brands. / We do not buy them, / and we do not sell them.
       375    We build our / brands. We do not / buy them, and we / do not sell them.
       360    (same four-line break)

     So the two narrowest common phones -- 375 is the iPhone SE and mini class,
     360 the compact Android class -- were breaking the section's climax across
     the middle of two of its three clauses. Measured on rendered line boxes, the
     clean break returns at <=28.0px at 375 and <=26.4px at 360.

     This ramps between exactly those two points and caps at the base 1.8rem, so
     390 and up are untouched and every phone gets the same three-line reading
     rather than a different one. */
  .about-statement { font-size: clamp(1.5rem, -.15rem + 8vw, 1.8rem); }
}

/* ---- THINKING, TABLET LANDSCAPE 1024 to 1279: the datum is dropped ----------
   Two sides survive because the measure is still comfortable, but the horizontal
   datum does not: a full-width rule at this size stops being a reference line and
   becomes a divider cutting the section in half. What remains is one axis carrying
   four registered nodes, the rows staggered so no two nodes share a height, and the
   core travelling between them as the reader scrolls. Touch first: nothing here
   depends on a pointer. */
@media (min-width: 1024px) and (max-width: 1279px) {
  /* ── THE ONE BAND WHERE THE COPY COLUMN CROSSES ONTO THE FLOOR ───────────
     At these widths the column runs far enough down the section that the lede's
     last lines and the whole closing line sit over the environment's shadowed
     floor rather than its lit cyclorama. Measured per LINE against the pixels
     behind that line, the worst backdrops in this band are (145,140,127) under
     the lede and (131,125,113) under the caption -- so the caption is on
     genuinely darker ground than the body copy, and the two cannot be treated
     as one surface.

     Those backdrops cap what any ink can reach: 6.26:1 for the lede, 5.14:1 for
     the caption. The pair below is solved inside those ceilings.

       lede    #141210  5.57:1   (was #211E1A / var(--ink-2) at 4.95:1)
       caption #0F0E0C  4.72:1   (was #090808 at 4.89:1)

     Both clear AA. The lede now leads the caption by 0.85 of contrast ratio
     where the previous pair was separated by 0.06 -- effectively equal, which
     is what broke the hierarchy. Note the caption's ink is also LIGHTER than
     the body's here (15,14,12 against 20,18,16), so the order reads correctly
     as ink as well as measured contrast. Neither is pure black.

     LIGHT MODE ONLY for the lede: it normally inherits var(--ink-2), which is
     theme-aware, so a bare colour here would follow into dark mode and put
     near-black text on a near-black floor. Dark mode measures 13.29-14.46:1 on
     the lede and 9.57-9.65:1 on the caption and is deliberately untouched. The
     caption rule is left at low specificity so the existing
     :root[data-theme="dark"] .house-caption rule keeps winning. */
  :root:not([data-theme="dark"]) .house-lede p { color: #141210; }
  .house-caption { color: #0F0E0C; }
  #thinking {
    /* A length, not a percentage: this value is also used in offsets on the belief,
       where a percentage would resolve against the column rather than the grid and
       land the nodes well short of the axis. */
    --think-channel: clamp(9rem, 19vw, 15rem);
    --think-row:     clamp(2.5rem, 1rem + 2.4vw, 3.25rem);
    --think-core-d:  clamp(17px, 0.4rem + 1vw, 21px);
    --think-step:    clamp(2.5rem, 0.5rem + 4vw, 4rem);
  }
  .think-datum, .think-filaments { display: none; }
  .belief:nth-child(2), .belief:nth-child(4) { margin-top: var(--think-step); }
  .belief h3 { position: relative; }
  .belief h3::before {
    content: ""; position: absolute; top: 0.55em; width: 9px; height: 9px;
    border-radius: 50%; border: 1px solid var(--think-ring); background: var(--bg-alt);
    transform: translateY(-50%);
    transition: border-color .26s var(--think-ease), transform .26s var(--think-ease);
  }
  .belief h3::after {
    content: ""; position: absolute; top: 0.55em; height: 1px;
    width: calc(var(--think-channel) / 2 - 30px);
    background: linear-gradient(90deg, var(--think-rule-end), var(--think-ring));
    transform: translateY(-50%) scaleX(.34); opacity: .55;
    transition: transform .3s var(--think-ease), opacity .3s var(--think-ease);
  }
  .belief:nth-child(odd)  h3::before { right: calc(-1 * var(--think-channel) / 2 - 4.5px); }
  .belief:nth-child(even) h3::before { left:  calc(-1 * var(--think-channel) / 2 - 4.5px); }
  .belief:nth-child(odd)  h3::after  { right: calc(-1 * var(--think-channel) / 2 + 12px); transform-origin: 100% 50%; }
  .belief:nth-child(even) h3::after  { left:  calc(-1 * var(--think-channel) / 2 + 12px); transform-origin: 0 50%; }
  .belief[data-on] h3::before { border-color: var(--think-ring-on); transform: translateY(-50%) scale(1.15); }
  .belief[data-on] h3::after  { transform: translateY(-50%) scaleX(1); opacity: 1; }
  .think-cross {
    grid-area: 1 / 1 / -1 / -1; position: absolute; inset: 0 auto 0 50%;
    height: auto; width: 0;
  }
  .think-core { top: 0; left: 0; transform: translate(-50%, -50%) translateY(var(--lens-y, 52px)); }
  .belief p { max-width: 44ch; }
}

/* ---- THINKING, TABLET PORTRAIT 744 to 1023: the offset register --------------
   Two text columns at this width give a 31 to 35 character measure, which is a worse
   read than one good column, so the section turns instead. One column, and the number
   steps out of the stack into its own gutter beside the title, which is the move the
   extra width over a phone actually buys. The rail runs down that gutter. */
@media (min-width: 744px) and (max-width: 1023px) {
  #thinking {
    --lens-x:       clamp(4.25rem, 10vw, 6.25rem);
    --lens-num:     clamp(1.5rem, 2.6vw, 2rem);   /* rail to the register digits */
    --think-row:    clamp(2.5rem, 1rem + 2.6vw, 3.5rem);
    --think-core-d: clamp(17px, 0.4rem + 1.2vw, 22px);
    --signal-w:     clamp(18px, 2.4vw, 26px);
  }
  .think-datum, .think-filaments, .think-axis { display: none; }
  .beliefs {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto; column-gap: 0;
    row-gap: calc(var(--think-row) * 1.15);
    padding-left: var(--lens-x);
  }
  .belief:nth-child(n) { grid-area: auto; margin-top: 0; padding-top: 0; }
  /* The register steps into the gutter beside the title. The ring keeps the rail, so
     the two never stack: at 834 that is a 79px gutter carrying a 26px number with the
     rail on one side of it and the copy on the other. */
  .belief-reg { top: 0; left: calc(var(--lens-num) - var(--lens-x)); margin: -14px 0 0 -14px; }
  .belief-reg::before { display: none; }
  .belief h3 { position: relative; }
  .belief h3::before {
    content: ""; position: absolute; top: 0.55em; left: calc(-1 * var(--lens-x));
    width: 10px; height: 10px; border-radius: 50%;
    border: 1px solid var(--think-ring); background: var(--bg-alt);
    transform: translate(-50%, -50%);
    transition: border-color .26s var(--think-ease), transform .26s var(--think-ease);
  }
  .belief[data-on] h3::before { border-color: var(--think-ring-on); transform: translate(-50%, -50%) scale(1.15); }
  .belief p { max-width: 52ch; }
  .think-cross {
    grid-area: auto; position: absolute; width: 1px; height: auto;
    left: 0; top: 0; bottom: calc(-1 * var(--think-trail));
    background: linear-gradient(180deg,
      transparent 0, var(--think-rule) 3%, var(--think-rule) 82%, var(--think-rule-end) 93%, transparent 100%);
  }
  .think-core { top: 0; left: 0; transform: translate(-50%, -50%) translateY(var(--lens-y, 40px)); }
  .think-core-visual::after { --signal-s: 1; }
}

/* ---- THINKING, PHONE below 744: the Decision Lens ---------------------------
   The cross is gone. A rail runs at the reading edge through the whole belief system,
   each belief registers on it with an open ring at the height of its own title, and
   one Gold core travels between those rings as the reader moves, with a short signal
   reaching toward the belief being read. The core moves between belief positions, not
   with the scrollbar, so it never jitters. Everything is readable before it moves,
   and it never moves a word. */
@media (max-width: 743px) {
  #thinking {
    --lens-x:       clamp(1.9rem, 9.5vw, 3.25rem);
    --think-row:    clamp(2rem, 0.8rem + 4vw, 3rem);
    --think-core-d: clamp(15px, 0.3rem + 3vw, 20px);
    --signal-w:     clamp(14px, 4.5vw, 22px);
  }
  .think-datum, .think-filaments, .think-axis { display: none; }
  .think-field { padding-block: clamp(1.25rem, 4vw, 2rem) 0; }
  .beliefs {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto; column-gap: 0; row-gap: var(--think-row);
    padding-left: var(--lens-x);
  }
  .belief:nth-child(n) { grid-area: auto; margin-top: 0; padding-top: 0; }
  .belief-reg {
    position: static; display: block; margin: 0 0 0.42rem; padding: 0;
    min-width: 0; min-height: 0; text-align: left;
  }
  .belief-reg::before { display: none; }
  /* The touch target reaches left over the rail and up, never right over the
     paragraph, so a thumb aiming at a register never lands on body copy. */
  .belief-reg::after {
    content: ""; position: absolute; top: -12px; left: calc(-1 * var(--lens-x) - 14px);
    width: calc(var(--lens-x) + 58px); height: 44px;
  }
  .belief h3 { position: relative; }
  .belief h3::before {
    content: ""; position: absolute; top: 0.57em; left: calc(-1 * var(--lens-x));
    width: 11px; height: 11px; border-radius: 50%;
    border: 1px solid var(--think-ring); background: var(--bg-alt);
    transform: translate(-50%, -50%);
    transition: border-color .26s var(--think-ease), transform .26s var(--think-ease);
  }
  /* The ring gives way where the core stands, so the two never stack. */
  .belief[data-on] h3::before { border-color: transparent; transform: translate(-50%, -50%) scale(.9); }
  /* AUTHORIZED REPAIR 2. Uncapped, this column reached 83 characters at 743px, past
     the 75 readability ceiling and well past the 62 to 68 house target. An iPhone SE
     in landscape is 667px and lands here.

     52ch is not a new number: it is the value the tablet-portrait composition
     immediately above this breakpoint already uses. Measured, it makes the two sides
     of the breakpoint identical, 67/63/60/67 characters and 3/3/2/2 lines at both 743
     and 744, so crossing 743 no longer changes the paragraph at all.

     It engages only above about 620px. From 320 to 607 the box is narrower than the
     cap and nothing changes: 34/38/40/44/52/60/67 characters, exactly as before. */
  .belief p { max-width: 52ch; }
  .think-cross {
    grid-area: auto; position: absolute; width: 1px; height: auto;
    left: 0; top: 0; bottom: calc(-1 * clamp(1.75rem, 6vw, 2.75rem));
    background: linear-gradient(180deg,
      transparent 0, var(--think-rule) 3%, var(--think-rule) 84%, var(--think-rule-end) 94%, transparent 100%);
  }
  .think-core { top: 0; left: 0; transform: translate(-50%, -50%) translateY(var(--lens-y, 34px)); }
  .think-core-visual::after { --signal-s: 1; }
}

/* Later sections, unchanged from the previous phase. */
/* Below 64rem the two halves stop sitting side by side and become one column, so the
   shared line they were aligned on no longer exists. What holds them apart instead is
   a real interval: the action has to read as the thing you arrive at after the
   sentence, not as the sentence's next line. */
/* Tablet keeps the two anchors. Collapsing to one column at 1024 put a short left-
   aligned column in the middle of a wide device: the section read as miniature and the
   void around it as accidental rather than architectural. The split is tuned for the
   narrower measure, and the heading steps up so it still commands the section. */
@media (min-width: 768px) and (max-width: 1024px) {
  .contact-grid {
    grid-template-columns: minmax(0, 1fr) max-content;
    grid-template-rows: auto auto;
    justify-content: space-between;
    column-gap: clamp(2rem, 4vw, 3.5rem);
    align-items: start;
  }
  .contact-grid .section-head { grid-column: 1; grid-row: 1; }
  .contact-lede { grid-column: 1; grid-row: 2; max-width: 34ch; }
  .contact-body { grid-column: 2; grid-row: 2; margin-top: var(--space-4); }
  .contact-fine { max-width: 26ch; }
  #contact .section-title { font-size: clamp(2.4rem, 1.5rem + 3.4vw, 3.2rem); }
}

@media (max-width: 767px) {
  .contact-grid {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto;
    justify-content: start;
    gap: var(--space-5);
  }
  .contact-grid .section-head { grid-column: 1; grid-row: 1; }
  .contact-lede { grid-column: 1; grid-row: 2; max-width: 46ch; }
  .contact-body { grid-column: 1; grid-row: 3; margin-top: clamp(20px, 4vw, 34px); }
  .contact-fine { max-width: 42ch; }
}

/* Footer at tablet: the brand must stay dominant. At this width the index columns were
   taking the same optical weight as the signature, so the signature scales up and the
   index steps down and tightens. */
@media (min-width: 768px) and (max-width: 1024px) {
  .footer-brand .brand-mark { width: 42px; height: 53.5px; }
  .footer-brand .brand-word { width: 208px; height: 31.8px; }
  .footer-tagline { font-size: clamp(1rem, .92rem + .34vw, 1.14rem); max-width: 24ch; }
  .footer-index { gap: clamp(2rem, 4.6vw, 3.4rem); }
  .footer-col-label { font-size: .68rem; }
  .footer-nav a, .footer-legal-list span, .footer-legal-list a, .footer-legal-list button { font-size: .95rem; min-height: 2.3rem; }
}
/* Standards holds its one-column composition through 1024px inclusive; the two-column
   desktop grid begins only at 1025px. Below that the right-hand explanation column has
   no room and collapses to a narrow ragged rail (measured 212px / 4-5 lines at 833px),
   so the clean stack is kept until the desktop grid can breathe. 1024px matches the
   tablet upper bound already used elsewhere in this file. Only the grid changes; spacing,
   rows, numbers, rules and motion are untouched. */
@media (max-width: 1024px) {
  .standard { grid-template-columns: 1fr; gap: var(--space-3); }
  .standard dt { padding-left: 2.2rem; }
  /* Only in the stacked composition: let the last line pull a word down rather than
     stranding one. On narrow phones Standard 01 otherwise left "flip." alone (6 chars
     at 360-390); this keeps "to flip." together with no copy change. Scoped to <=1024
     so the two-column desktop wrapping stays exactly as approved. */
  .standard dd { text-wrap: pretty; }
}
/* Signal Field's mobile min-height stays at its own 52rem boundary — not moved. */
@media (max-width: 52rem) {
  .statement { min-height: auto; }
}
@media (max-width: 34rem) {
  .footer-inner { gap: var(--space-6); }
  .footer-index { gap: clamp(2rem, 9vw, 3rem); }
}

/* ==================== 6. ACCESSIBILITY & MOTION ==================== */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---- THEME TRANSITION: the material opens from the aperture ----
   Not a colour inversion and not a cross-fade. The new material is revealed by a wave
   that expands from the aperture control itself, while the old material settles very
   slightly back, so the page reads as one substance changing state. --tt-x / --tt-y are
   written by script.js from the live position of the control; --tt-r is the exact
   distance to the furthest viewport corner, so the wave finishes flush and there is
   never an unpainted edge, a white flash or a black flash. Both layers are opaque
   snapshots throughout, so no opacity step occurs at any point. */
::view-transition-old(root), ::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
::view-transition-old(root) { z-index: 1; }
::view-transition-new(root) { z-index: 2; }
::view-transition-group(root) { animation-duration: 860ms; }

/* Capture affordance only (?still=1). Smooth scrolling is dropped so fragment jumps
   land instantly, and the hero's 100svh is pinned so a tall-window full-page capture
   can show the hero and the sections beneath it in one frame. Neither rule is active
   in normal browsing, and neither changes the design. */
html[data-still] { scroll-behavior: auto !important; }
html[data-still] .hero { min-height: 620px; }
html[data-still] #thinking *,
html[data-still] #thinking *::before,
html[data-still] #thinking *::after { transition: none; }

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }

  /* The aperture wave. 860ms on a slow-out curve that moves decisively then settles,
     so it reads as material rather than as a UI wipe. */
  ::view-transition-new(root) { animation: aperture-wave 860ms var(--ease-material) both; }
  ::view-transition-old(root) { animation: material-settle 860ms var(--ease-material) both; }
  @keyframes aperture-wave {
    from { clip-path: circle(0px at var(--tt-x, 90%) var(--tt-y, 42px)); }
    to   { clip-path: circle(var(--tt-r, 150vmax) at var(--tt-x, 90%) var(--tt-y, 42px)); }
  }
  /* The outgoing material eases back a hair and loses a little of its saturation as
     the new one arrives. No fade: the old layer stays fully opaque underneath. */
  @keyframes material-settle {
    from { transform: scale(1); filter: saturate(1); }
    to   { transform: scale(.994); filter: saturate(.88); }
  }

  /* The fallback field breathes so it is alive even without WebGL. */
  html[data-anim="full"] .weave-strands { animation: weave-drift-a 26s var(--ease) infinite alternate; }
  html[data-anim="full"] .weave-filaments { animation: weave-drift-b 34s var(--ease) infinite alternate; }
  html[data-anim="full"] .weave-motes circle { animation: mote-breath 7s var(--ease) infinite; transform-box: fill-box; transform-origin: center; }
  html[data-anim="full"] .weave-motes circle:nth-child(2n) { animation-delay: -2.1s; }
  html[data-anim="full"] .weave-motes circle:nth-child(3n) { animation-delay: -4.3s; }
  @keyframes weave-drift-a { from { transform: translate3d(-10px, 7px, 0) scale(1.008); } to { transform: translate3d(14px, -11px, 0) scale(1.022); } }
  @keyframes weave-drift-b { from { transform: translate3d(9px, -6px, 0) scale(1.004); } to { transform: translate3d(-15px, 10px, 0) scale(1.018); } }
  @keyframes mote-breath { 0%, 100% { opacity: .22; transform: scale(.7); } 50% { opacity: 1; transform: scale(1.2); } }

  /* ================= SCROLL-SCRUBBED REVEAL =================================
     Scroll position is the only input. script.js writes a normalised 0..1 progress
     into --rv on each element every frame the reader is actually moving, and these
     rules are a pure function of it: no animation, no transition, nothing with a
     life of its own. Stop scrolling and the value stops; scroll back and it falls
     again, because it is derived from geometry rather than replayed from a
     timeline.

     Every property here defaults to the settled state, so if script.js never runs,
     never sets --rv, or fails after setting it, the page is simply finished:
     opacity 1 and no transform. Visibility is not something JavaScript grants.

     The endpoints match the entrance this replaces exactly: opacity 0 -> 1 and
     translateY 12px -> 0, so the settled composition is unchanged to the pixel. */
  html[data-scrub="on"] .reveal {
    opacity: var(--rv, 1);
    transform: translate3d(0, calc((1 - var(--rv, 1)) * 12px), 0);
  }


  /* The three principle marks are deliberately static. There is no symbol reveal,
     no staged construction, no stroke drawing and no mask animation: they are simply
     painted with the section ink and left alone. The only motion anywhere near them
     is the column's own .reveal fade, which is the section wide behaviour shared with
     Thinking and Standards and is not specific to the marks. */
  /* ---- THINKING: the structure is struck under the reader's hand ----
     The order of the drawing is unchanged: core, then the datum opening out along
     the width, then the axis along the height, then the bundle. What changed is
     that the order is now a set of windows on scroll progress rather than a chain
     of transition-delays, so it assembles as the reader arrives and comes apart as
     they leave, in the same order, at their speed.

     The drawing no longer has to be cued off the first belief. That indirection
     existed because an axis at scaleY(0) has no height and could never satisfy an
     intersection threshold, so it could not observe itself. Geometry does not have
     that problem: --tp is measured from the .beliefs box, which has real height
     whatever the drawing is doing, so the drawing is driven directly.

     Two different things drive this drawing and they are kept apart on purpose.
     --tp is the section's ENTRANCE progress, written from scroll geometry: it is
     what assembles and disassembles the composition. --lens-y and --core-dx/dy are
     the LIVE reading state, written by thinking-interaction.js from which belief is
     being read. They no longer meet: --lens-y positions the outer .think-core,
     --core-dx/dy leans the inner .think-core-visual on its own `translate`, and only
     --tp writes the inner's `transform`. Nothing composes two clocks into one
     property any more.

     --tp is mapped straight through, not smoothed. The trackpad already supplies
     macOS momentum; a second exponential on top of it is a second easing curve, and
     what the reader sees is a ball converging on where the scroll already is. At a
     3000 px/s flick that lag put the rendered ball up to 5.9px off its true diameter
     and 50px of scroll behind the hand. Read the scroll in the frame, write the frame:
     the ball is where the scroll is, and stopping the trackpad stops it dead. */
  html[data-scrub="on"] .think-core-visual {
    --w: clamp(0, calc((var(--tp, 1) - 0.00) / 0.48), 1);
    opacity: var(--w);
    transform: scale(calc(0.35 + 0.65 * var(--w)));
    /* The one element on the page that is scrubbed every frame, so the one element
       that earns a compositor layer: the 52px halo is then transformed rather than
       repainted on the main thread each time the scale moves. */
    will-change: transform, opacity;
  }
  html[data-scrub="on"] .think-datum {
    --w: clamp(0, calc((var(--tp, 1) - 0.10) / 0.50), 1);
    transform: scaleX(var(--w)); transform-origin: 50% 50%;
  }
  html[data-scrub="on"] .think-axis {
    --w: clamp(0, calc((var(--tp, 1) - 0.16) / 0.52), 1);
    opacity: var(--w);
    transform: translateX(-50%) scaleY(var(--w)); transform-origin: 50% 50%;
  }
  html[data-scrub="on"] .think-filaments {
    --w: clamp(0, calc((var(--tp, 1) - 0.28) / 0.52), 1);
    opacity: var(--w);
  }

  /* On the narrow compositions the core also travels the rail. That used to need an
     override here, because position and scale shared one transform and the rail's
     --lens-y had to be threaded back into the scrubbed declaration. It does not any
     more: --lens-y is on the outer .think-core in each composition's own media block,
     the scale is on the inner, and the two compose by nesting instead of by string. */

  html[data-anim="full"] .field-node { animation: node-breath 5s var(--ease) infinite; }
  html[data-anim="full"] .field-node:nth-of-type(2) { animation-delay: .8s; }
  html[data-anim="full"] .field-node:nth-of-type(3) { animation-delay: 1.7s; }
  html[data-anim="full"] .field-node:nth-of-type(4) { animation-delay: 2.5s; }
  html[data-anim="full"] .field-node:nth-of-type(5) { animation-delay: 3.3s; }
  html[data-anim="full"] .field-node:nth-of-type(6) { animation-delay: 1.2s; }
  @keyframes node-breath { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.4); } }

  @keyframes beam-pulse { 0% { transform: translate(-40px, -50%); opacity: 0; } 12% { opacity: 1; } 82% { opacity: 1; } 100% { transform: translate(calc(100% + 40px), -50%); opacity: 0; } }
  html[data-anim="full"] .statement-beam .beam-node { animation: node-breath 6s var(--ease) infinite; }

  /* ---- HOUSE: the three rise off the floor as the reader arrives ----
     One value, --dr, mapped straight through from scroll position with no
     smoothing (see script.js). Each brand rises in its own turn and settles into
     its float. Stopping the trackpad stops it at exactly that state, scrolling
     back runs it backwards, and there is no timeline, no trigger and nothing to
     replay. It writes --dr and nothing else; the transform it feeds lives on
     .house-rise, which no other rule in this section ever touches. */
  html[data-scrub="on"] .house-field { --dr: clamp(0, calc((var(--hp, 1) - 0.02) / 0.34), 1); }
  html[data-scrub="on"] .house-hold { opacity: clamp(0, calc((var(--dr, 1) - var(--in)) / 0.30), 1); }

}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; }
  html { scroll-behavior: auto; }
  .field-layer { transform: none !important; }
  .beam-pulse { opacity: 0 !important; }
  ::view-transition-old(root), ::view-transition-new(root) { animation: none !important; }
  /* The hero field holds a still, fully composed frame: the fallback weave is painted
     and motionless, and script.js never starts a render loop. The composition is
     identical, only the movement is gone. */
  .weave-strands, .weave-filaments, .weave-motes circle { animation: none !important; transform: none !important; }
  .hero-weave, #fieldCanvas { transition: none !important; }
}

/* ==========================================================================
   14. STATEMENT — THE SIGNAL FIELD
   Integrated 2026-08-07 from the approved isolated study
   experiments/closing-static-directions/direction-3-final/. The study's rules
   are carried over verbatim; only the two lines noted below differ, and both
   are context, not design.

   CONCEPT. The one thing on this page that belongs to nobody else is the
   Beamtouch mark. Here it stops being a 34px glyph in the header and becomes
   architecture: the exact locked silhouette in a low-contrast relief, with the
   same silhouette echoing outward from it as hairline outlines, each cropped
   by the section. The brand direction is Signal — the logo reads as a beacon
   and the system emanates from it — drawn with the only shape that is
   Beamtouch's, which is why it cannot read as radio-wave clip art.

   NOTHING IS REDRAWN. Relief and every echo are <use> of the single #bt-mark
   symbol. The echoes are the same geometry at larger scale with fill:none and
   stroke:currentColor; vector-effect="non-scaling-stroke" on the symbol's path
   (stroke rendering only, inert on filled instances) holds every outline at one
   hairline weight.

   THE RELIEF. Two stacked instances: an edge layer offset toward the lower
   right in a lighter surface tone, and the face above it in a carbon-tinted
   mix. The exposed sliver reads as the lit edge of a shape pressed into the
   stone. No blur, no shadow, no filter — two fills and one offset. Every
   colour is a color-mix() of tokens defined in section 1; this block
   introduces no new palette value, no new font and no asset.
   ========================================================================== */

/* Full bleed, no card, no frame. The section clips its own field, which is what
   lets the outer echoes be cropped by the boundary. */
.cs { position: relative; isolation: isolate; overflow: clip; }

/* One sentence, one element, in normal reading order. The visual line breaks are
   block spans inside it, so a screen reader receives it continuously. */
.cs-statement { margin: 0; }
.cs-statement .ln { display: block; }
/* Measured, not precautionary: at 430px and below the supporting line wrapped so
   that "endure." sat alone. `pretty` is the property that exists to stop that. */
.cs-support { margin: 0; text-wrap: pretty; }

.cs-sf {
  background: var(--bg);
  display: flex;
  /* No min-height here. Each of the four states below owns its own section
     height — tablet, bridge, desktop, mobile — and every width falls inside
     exactly one of them, so a value at this level would only ever be shadowed. */
  padding-block: clamp(4rem, 2.5rem + 5vw, 6.5rem) clamp(2.5rem, 1.5rem + 3vw, 4rem);

  /* Relief tones, LIGHT. Face is carbon mixed into the ground; the edge is the
     existing surface tone, which sits just above the ground in value. */
  --sf-face: color-mix(in srgb, var(--ink) 8.5%, var(--bg));
  --sf-edge: var(--surface);
}

/* Relief tones, DARK. Same construction, same direction of light: no neon, no
   glow, no bright watermark — the strongest value stays far below the copy. */
:root[data-theme="dark"] .cs-sf {
  --sf-face: color-mix(in srgb, var(--ink) 12%, var(--bg));
  --sf-edge: color-mix(in srgb, var(--ink) 24%, var(--bg));
}

/* One anchor box carries the mark's own aspect ratio; the relief and every echo
   centre on it, so the family is concentric by construction. */
/* Placement and size are the one thing that differs between the four states, so
   they are not set here — only what every state shares. Tablet and mobile centre
   the box on a percentage point and keep the translate below; the bridge and
   desktop anchor it to the right edge and cancel the translate. */
.sf-field {
  position: absolute;
  z-index: 0;
  aspect-ratio: 50.42 / 64.17;          /* the mark's proportion, never stretched */
  transform: translate(-50%, -50%);
  color: var(--ink);                     /* echoes stroke with the page ink */
  pointer-events: none;
}

.sf-field svg {
  position: absolute;
  left: 50%;
  top: 50%;
  /* Both dimensions are the SAME percentage of the field, and the field carries
     the mark's ratio, so every instance keeps the locked proportion and nothing
     can stretch. (aspect-ratio on the svg elements is unreliable here: the svg
     default width="100%" acts as an intrinsic size and wins over the ratio.) */
  width: 100%;
  height: 100%;
  /* the global `img, svg { max-width: 100% }` reset would clamp the echoes back
     to the field width; the field is the intended boundary-breaker */
  max-width: none;
  transform: translate(-50%, -50%);
  display: block;
}

/* The relief pair: edge first (offset toward the lower right), face on top. */
.sf-field .sf-mark-edge { fill: var(--sf-edge); transform: translate(calc(-50% + .13rem), calc(-50% + .13rem)); }
.sf-field .sf-mark      { fill: var(--sf-face); }

/* The echoes: same silhouette, larger, hairline, progressively quieter. Scale
   steps are near-geometric so the intervals read as one signal spreading. */
.sf-field .sf-echo { fill: none; stroke: currentColor; stroke-width: 1.25px; }
.sf-field .sf-e1 { width: 133%; height: 133%; opacity: .13; }
.sf-field .sf-e2 { width: 174%; height: 174%; opacity: .085; }
.sf-field .sf-e3 { width: 226%; height: 226%; opacity: .055; }

:root[data-theme="dark"] .sf-field .sf-e1 { opacity: .17; }
:root[data-theme="dark"] .sf-field .sf-e2 { opacity: .11; }
:root[data-theme="dark"] .sf-field .sf-e3 { opacity: .07; }

/* The supporting line opens the field at the upper left; the statement closes it
   at the lower left, resting on the boundary it shares with Contact. Both are
   normal flow, so user text spacing and zoom reflow the field, never collide. */
.sf-inner {
  position: relative;
  z-index: 1;
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: clamp(2.5rem, 6vh, 5rem);
}

.sf-support {
  color: var(--ink-2);
  font-size: clamp(1.0625rem, .95rem + .55vw, 1.5rem);
  line-height: 1.45;
  max-width: 38ch;
  padding-bottom: clamp(1rem, .7rem + 1vw, 1.6rem);
  border-bottom: 1px solid var(--hair);
  align-self: flex-start;
  /* the rule spans only the supporting line's measure, so it marks the boundary
     between premise and conclusion the way Standards' hairlines mark its rows */
}

.sf-statement {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.02;
  color: var(--ink);
  /* Measured, not chosen: on the real Sora 600 face at -0.025em the longest
     authored line occupies 16.17x the font size. The available measure is NOT
     100vw — .wrap caps at 74rem — so the capped content measure is divided by 17,
     which holds the three authored lines as three lines. */
  font-size: min(3.6rem, calc((min(var(--wrap), 100vw) - 2 * var(--wrap-pad)) / 17));
  max-width: none;
}

/* ========================================== desktop, 1181 and up.
   One state, not three. This replaces both the old `min-width: 1201` text block
   and the 1201-1439 walk-back band, and it is also the transition: the desktop
   composition is written as a TRAVEL that begins at the bridge's own values at
   1181 and arrives at the approved desktop values, after which it stays there
   for every wider window. There is no second breakpoint to cross, so there is
   nothing left to snap.

   WHY THE TWO ENDS DIFFER IN KIND. Below 1181 `space-between` pins the premise
   and the statement to opposite ends of the section, so the distance between
   them is whatever is left over — 190px at 1180x850. The approved desktop uses
   `flex-end`, which makes the gap an authored value (110.6px at 1440x900) and
   puts the free space above the premise instead. Those are different
   distributions of the same slack, so no single value reconciles them; what
   does is that they are the SAME layout at the moment the authored gap equals
   the leftover. So this block runs `flex-end` throughout and starts the gap at
   the leftover, which is why 1180 and 1181 render alike.

   THE TRAVEL. `clamp(-A, D, A)` is the whole mechanism. D is how far this
   quantity has to move, signed; A is how far the window has come past 1181,
   scaled. At 1181 A is zero, the clamp returns zero, and the value is the
   bridge's. Once A covers the whole of D the clamp returns D and the value is
   the desktop's, permanently. Between them it is a straight line of slope A-per
   -pixel. Signed, so it works whichever end happens to be larger — below about
   778px of window height the desktop section is the TALLER of the two and the
   travel runs upward instead.

   THE SLOPES ARE THE POINT. An earlier attempt spread the same travel over 39px
   and had to size its slope for the largest delta any window height can produce,
   which made it sit still and then compress at ~4.8px per pixel. Here the travel
   is ~185px wide, so the same worst case rides at 0.5-0.7px per pixel and an
   ordinary 850-tall window is done by ~1285. Nothing is held back and then
   released; the motion starts at 1181 and eases out when it arrives. Two rates
   only: .5 for the section and the artwork, .7 for the gap, whose delta is the
   largest and which would otherwise still be travelling at 1440.

   MIRRORED VALUES. `--sf-stack` is everything in the column that is not the gap,
   so that `bridge-h - stack` IS the leftover `space-between` would have handed
   back. Its four terms are a COPY of `.cs-sf`'s padding-block, `.sf-support`'s
   font, line-height, padding and rule, and `.sf-statement`'s three lines at
   1.02 — not a reference to them. Change any of those rules and change this
   with them, or 1180/1181 quietly reopens. Measured error today: 0.1px. */
@media (min-width: 1181px) {
  .cs-sf {
    /* the two compositions this range travels between */
    --sf-bridge-h: min(72vh, 45rem);
    --sf-desk-h: clamp(35rem, 64vh, 37.5rem);

    --sf-stack: calc(clamp(4rem, 2.5rem + 5vw, 6.5rem)
                     + clamp(2.5rem, 1.5rem + 3vw, 4rem)
                     + 2.9 * clamp(1.0625rem, .95rem + .55vw, 1.5rem)
                     + clamp(1rem, .7rem + 1vw, 1.6rem) + 1px
                     + 3.06 * min(3.6rem, calc((min(74rem, 100vw)
                        - 2 * clamp(1.35rem, 0.6rem + 3.4vw, 3.5rem)) / 17)));

    /* the bridge's gap is its leftover while its min-height binds, and its
       authored gap on windows too short for that to happen */
    --sf-bridge-gap: max(clamp(2.5rem, 6vh, 5rem),
                         calc(var(--sf-bridge-h) - var(--sf-stack)));
    --sf-desk-gap: clamp(6.9rem, 5rem + 3.4vh, 8.5rem);

    /* How far past 1181 this window is, scaled — zero at the boundary. Three
       rates, and WHEN EACH ONE LANDS is the reason there are three rather than
       one. Two of the quantities on screen are not travelling under their own
       power: the premise's top is whatever the section has left after the gap,
       and the mark's centre is its top plus half its height. Land something they
       depend on at the wrong moment and they change direction after arriving —
       a 2.5px hook, small, but a reversal you can see in a resize.

       So the rates are chosen by arrival, not by distance. The section and the
       gap are set to land together (~1285 on an 850-tall window), which holds
       the premise at one steady .27/px for the whole travel instead of creeping
       and then sliding. The mark's height and right edge run at the same .5 as
       the section, and its top at .35 — slow enough that the mark's centre
       arrives just AFTER the section has stopped shrinking, so it rises to the
       section's midline once and stays there rather than overshooting and
       settling back. Every quantity now moves one way only, 1181 to arrival. */
    --sf-a1: calc((100vw - 1181px) * .5);    /* section, mark height, mark right */
    --sf-a2: calc((100vw - 1181px) * .65);   /* the gap — a longer way to travel */
    --sf-a3: calc((100vw - 1181px) * .35);   /* mark top — deliberately last */

    min-height: calc(var(--sf-bridge-h)
                     - clamp(calc(-1 * var(--sf-a1)),
                             calc(var(--sf-bridge-h) - var(--sf-desk-h)),
                             var(--sf-a1)));
  }

  .sf-inner {
    justify-content: flex-end;
    gap: calc(var(--sf-bridge-gap)
              - clamp(calc(-1 * var(--sf-a2)),
                      calc(var(--sf-bridge-gap) - var(--sf-desk-gap)),
                      var(--sf-a2)));
  }

  /* The artwork travels the same way: the bridge parks it against the right
     edge with its body above the headline, the desktop centres it on 84% of the
     width at half the section's height. Height first, because the half-width
     that positions it is derived from the height the travel has reached. */
  .sf-field {
    --sf-bridge-mh: min(calc((var(--sf-bridge-h) - 15rem) * 1.1), 40rem);
    --sf-desk-mh: min(55vh, 33rem);
    --sf-h: calc(var(--sf-bridge-mh)
                 - clamp(calc(-1 * var(--sf-a1)),
                         calc(var(--sf-bridge-mh) - var(--sf-desk-mh)),
                         var(--sf-a1)));
    --sf-half: calc(var(--sf-h) * .392865);          /* locked ratio 50.42/64.17 */
    --sf-desk-r: calc(16vw - var(--sf-half));        /* 100% - the desktop's 84% */
    --sf-desk-t: calc(50% - var(--sf-h) / 2);

    transform: none;
    height: var(--sf-h);
    right: calc(-1.5rem - clamp(calc(-1 * var(--sf-a1)),
                                calc(-1.5rem - var(--sf-desk-r)),
                                var(--sf-a1)));
    /* the slowest rate of the three: the target it is chasing is itself falling
       as the section shrinks and the mark grows, so the two converge at about
       .6/px even though this side only moves at .35 */
    top: calc(.5rem - clamp(calc(-1 * var(--sf-a3)),
                            calc(.5rem - var(--sf-desk-t)),
                            var(--sf-a3)));
  }
}

/* ------------------------------------------------- wide desktop, 1441 and up.
   WHAT WAS WRONG. Above the 1440 anchor the composition came apart, and the two
   symptoms had one cause. The text column caps at 74rem, so the longest headline
   line's right edge advances at .5px per px of viewport. The relief was anchored
   to the VIEWPORT -- `16vw - half` centres it on 84vw -- so its left edge
   advanced at .84, and its size was capped at `min(55vh, 33rem)` so it stopped
   growing altogether. Measured: the ink overlap that carries the picture at 1440
   (84.65px) thinned to 30.26 at 1600 and inverted to a 67.11px CLEAR GAP at
   1920, while the empty band to the mark's right grew without bound and the
   relief stopped meeting the right boundary at any desktop width. It read as an
   illustration parked in the corner rather than a field the page is cut out of.

   THE CORRECTION, AND WHY IT IS THE ROOT CAUSE AND NOT THE SCREENSHOT. The two
   edges of the relief answer to two different masters, so they are anchored to
   two different things instead of both to the viewport:

     LEFT EDGE  -> the text column. The wrap's content edge is a fixed distance
                   from the longest line (148.09px, constant, because the line
                   does not reflow), so pinning the mark's left edge to the
                   column pins the OVERLAP. It cannot drift again at any width.
     RIGHT EDGE -> the viewport boundary, via the mark's own size. The relief
                   grows to span the widening gap, so it keeps reaching the edge
                   instead of being left behind by it.

   Everything is a clamped travel measured from 1440, so every term is exactly
   zero at and below the anchor and the approved 1440 composition is untouched
   -- not approximately, identically. Two segments, not one: establishing the
   crop from 1440's -74px deficit needs ~.28/px, and holding that same crop once
   established needs only the ~.05/px that keeps pace with the shape's own inset
   as it scales. One linear rate cannot do both -- it either never crops by 1920
   or over-crops by 2560 -- which is why the second segment exists.

   THE HEIGHT CAP IS THE SAFETY VALVE, and it fails in the correct direction. On
   a short ultrawide window (2560x1080 and the like) there is not enough vertical
   room for a relief that wide, so the cap binds, the mark shrinks, and it is the
   CROP that is given up while the headline relationship -- the thing this pass
   exists to fix -- is preserved. Nothing is animated, nothing is scripted: four
   clamps and a min(), resolved once at layout. */
@media (min-width: 1441px) {
  .cs-sf {
    --sf-x1: clamp(0px, calc(100vw - 1440px), 480px);   /* 1440 -> 1920 */
    --sf-x2: clamp(0px, calc(100vw - 1920px), 640px);   /* 1920 -> 2560 */
    /* The section takes the extra height the taller relief needs, so the relief
       is CROPPED by the boundary rather than amputated by it. `64vh` already
       tracked viewport height and was simply capped at 37.5rem; the cap now
       travels with the same width term that grows the mark, so the two stay in
       proportion. Below 1441 both terms are zero and this is the old formula. */
    --sf-desk-h: clamp(35rem,
                       calc(64vh + (var(--sf-x1) + var(--sf-x2)) * .18),
                       calc(37.5rem + (var(--sf-x1) + var(--sf-x2)) * .35));
  }

  .sf-field {
    /* the span the relief has to cover: from its column anchor to the boundary,
       plus the two travels. At 1440 this evaluates to 388.94px -- the approved
       width -- which is what makes the anchor bit-identical rather than close. */
    --sf-span: calc(50vw - 33.5rem + 204.94px
                    + var(--sf-x1) * .2833 + var(--sf-x2) * .0491);

    /* vw, NOT %, and that is the whole of the reason: the state this travels out
       of anchors on `16vw`, and % resolves against the section's layout box,
       which on a platform with classic scrollbars is narrower than the viewport
       by the scrollbar. Mixing the two bases put a half-scrollbar step at 1441 --
       measured 8.57px. One basis, no step, on every platform. 33.5rem is the
       wrap's half-measure less its padding (37rem - 3.5rem), so this still reads
       as "the column's content edge, less the overlap". */
    left: calc(50vw + 33.5rem - 240.87px);
    right: auto;
    height: min(calc(var(--sf-span) * 1.27271), calc(var(--sf-desk-h) * 1.7));
    top: 50%;
    transform: translateY(-50%);
  }
}

/* --------------------------------------------------------------- tablet.
   Same left-text / right-mark relationship, one echo fewer. At the study's
   original 85%/centred/507px the relief sat under BOTH headline lines; moved
   right, moved up, then trimmed the minimum amount that puts the whole face
   above the headline on tall tablets. */
@media (max-width: 1024px) {
  .cs-sf { min-height: min(60vh, 37rem); padding-block: clamp(3rem, 2rem + 4vw, 5rem) clamp(2rem, 1.5rem + 2vw, 3rem); }
  .sf-field { left: 91%; top: 36%; height: min(42vh, 26rem); }
  .sf-field .sf-e3 { display: none; }
  .sf-support { max-width: 34ch; }
}

/* ------------------------------------------------- bridge band, 981-1180.
   Between the tablet and desktop compositions the relief crossed the headline
   (up to 10,908 px2 of painted ink under line two at 1050x800). The locked
   silhouette is a wide body over a narrow foot — its bottom sixth paints only a
   central stem 17-18% of the face width — so the body is placed entirely ABOVE
   the headline and only the stem stands beside it, shifted right until even the
   stem clears the longest line. Desktop section, padding, echo count and support
   measure are restated here so 1025 is seamless. The height is derived: 15rem is
   the fixed stack below the mark's room (three headline lines plus the bottom
   padding), so `100% - 15rem` IS the space above the headline at any height.
   Upper bound moved 1200 -> 1180 on 2026-08-07: the desktop state now begins its
   travel at 1181 from exactly these values, so this band ends where that starts
   rather than overlapping it. Nothing inside changed. */
@media (min-width: 981px) and (max-width: 1180px) {
  .cs-sf {
    min-height: min(72vh, 45rem);
    padding-block: clamp(4rem, 2.5rem + 5vw, 6.5rem) clamp(2.5rem, 1.5rem + 3vw, 4rem);
  }
  .sf-field .sf-e3 { display: block; }
  .sf-support { max-width: 38ch; }

  .sf-field {
    left: auto;
    right: -1.5rem;        /* the crop stays constant across the band */
    top: .5rem;
    transform: none;
    height: min(calc((100% - 15rem) * 1.1), 40rem);
  }
}

/* The 1201-1439 walk-back band that used to sit here is gone. It existed only to
   bridge two states that disagreed at a breakpoint; the desktop state now starts
   from the bridge's values and travels, so there is no disagreement left to
   patch and no third set of formulas to keep in step. */

/* --------------------------------------------------------------- mobile.
   Transformed, not shrunk. The statement stays the dominant foreground at the
   base, the supporting line keeps the upper left, and the mark moves behind the
   upper right corner, cropped by the right edge while the silhouette stays
   identifiable. One echo, and the relief lightens one step so no text ever sits
   on a noisy part of the mark. */
@media (max-width: 600px) {
  .cs-sf {
    min-height: min(72svh, 28.5rem);
    padding-block: clamp(3rem, 2rem + 5vw, 4.5rem) clamp(2rem, 1.5rem + 2vw, 3rem);
    --sf-face: color-mix(in srgb, var(--ink) 6.5%, var(--bg));
    --sf-edge: color-mix(in srgb, var(--ink) 2%, var(--surface));
  }
  :root[data-theme="dark"] .cs-sf {
    --sf-face: color-mix(in srgb, var(--ink) 9%, var(--bg));
    --sf-edge: color-mix(in srgb, var(--ink) 18%, var(--bg));
  }
  .sf-field {
    left: 88%;
    top: 28%;
    height: clamp(12rem, 34vh, 15rem);
  }
  .sf-field .sf-e2, .sf-field .sf-e3 { display: none; }
  .sf-support { max-width: 24ch; font-size: 1.0625rem; }
  .sf-statement { font-size: clamp(1.95rem, 1.2rem + 3.4vw, 2.6rem); }
  .sf-statement .ln { text-wrap: balance; }
}

/* Forced colours: the relief and echoes are decoration and must not survive as
   opaque blocks over the statement when the user's palette replaces our own. */
@media (forced-colors: active) {
  .sf-field { display: none; }
}

/* ---- FORCED COLOURS: THINGS DRAWN WITH A BACKGROUND ------------------------
   Forced-colors mode replaces author colours with the user's palette. Anything
   whose INK is a background-color therefore gets repainted as Canvas -- the same
   colour as the surface behind it -- and disappears. Measured in
   forced-colors: active, `.principle-mark` came back as
   color rgb(0,0,0) on background rgb(255,255,255) against a white Canvas: the
   three About anchors were invisible.

   Every element below draws its mark with a background rather than with glyphs:

     .principle-mark-*        currentColor painted through an alpha mask
     .section-label::after    the gold node that opens all five section labels
     .section-label::before   its cinnamon tick
     .about-eyebrow::after/::before  the same pair on the About label
     .about-statement::before the short rule that opens the conviction

   Repainting them in CanvasText keeps each one visible and on the user's own
   palette. This cannot affect normal rendering: the whole block is inert unless
   forced colours are active, where the current result is already a blank space.

   .section-label is shared with Thinking, The house, Standards and Contact, so
   this reaches locked sections -- as an accessibility repair in a mode where they
   currently render nothing, never as a visual change to the approved design. */
@media (forced-colors: active) {
  .principle-mark-origin,
  .principle-mark-foundation,
  .principle-mark-ownership { background: CanvasText; }
  .section-label::before,
  .section-label::after,
  .about-eyebrow::before,
  .about-eyebrow::after,
  .about-statement::before { background: CanvasText; opacity: 1; }
}
