/* ==========================================================================
   Nau Studio — naustudio.pt
   Black / white / iridescent chrome. Avenir Nau. Editorial, spare.
   ========================================================================== */

/* Avenir Nau — custom cut. NOTE: Avenir is a commercial Monotype face;
   public webfont embedding is a licensing decision for NAU to confirm. */
@font-face {
  font-family: "Avenir Nau";
  src: url("assets/fonts/Avenir-Nau.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}

:root {
  --black: #000000;
  --near-black: #0a0a0a;
  --white: #ffffff;
  --grey: #b3b3b3;
  --muted: #666666;
  --line: #1c1c1c;
  --runlabel: #5a5a5a;
  --logo-filter: brightness(0) invert(1);
  --btn-bg: #f2f2f2; --btn-fg: #0a0a0a;
  /* lifted 2026-08-22 (was #0d0d0f→#0a0a0b / #2a2a2a): on the pure-black page
     the panel read as a hole and the field rules barely registered. These are
     the form's own tokens, so the lift can't leak anywhere else. */
  --form-panel: linear-gradient(180deg, #1a1a1e 0%, #121215 100%);
  --form-border: #45454b;
  --star-rgb: 255,255,255;
  --nav-frost: rgba(6,6,7,0.72);
  --btn-hover-fg: #0a0a0a;
  /* ink that sits ON a chrome fill (toggle glyph, .dos hub label) */
  --chrome-ink: #0a0a0c;
  --panel-shadow: inset 0 1px 0 rgba(255,255,255,0.04), 0 30px 60px -30px rgba(0,0,0,0.6);
  --panel-edge: linear-gradient(150deg, rgba(159,184,232,0.35), rgba(217,184,220,0.12) 40%, rgba(255,255,255,0.04) 70%);
  --dot-rgb: 179,179,179;

  /* the one signature: iridescent chrome. The stops are PALE — engineered to
     glow against black — so light mode needs its own deeper cut (below) or
     the wordmark washes out to nothing on white. */
  --chrome: linear-gradient(100deg,
    #9fb8e8 0%, #b9a8e0 28%, #d9b8dc 50%, #e8c9a8 72%, #f0d4b0 100%);

  --serif: Georgia, "Times New Roman", serif; /* stand-in for the deck's italic-serif micro-labels */
  --maxw: 1100px;
  --gutter: clamp(1.5rem, 5vw, 5rem);

  /* type system: Avenir Nau (the custom cut with the arrow-A) is reserved
     for titles/headings; body copy runs in regular Avenir — the system face
     on Apple devices, falling back to Avenir Next then system-ui elsewhere
     (no regular-Avenir webfont is licensed/loaded, so non-Apple visitors get
     the fallback — revisit if a webfont cut is licensed for launch) */
  --font-title: "Avenir Nau", system-ui, sans-serif;
  --font-body: "Avenir Next", Avenir, system-ui, sans-serif;
}

/* light theme — flips the handful of raw tokens every component already
   reads through (var(--black)/--near-black as background, var(--white) as
   ink), so most of the page inverts for free; the few hardcoded literals
   (logo filter, form panel, buttons, canvas star colour) get their own token */
:root[data-theme="light"] {
  --black: #ffffff;
  --near-black: #f4f4f4;
  --white: #0a0a0a;
  --grey: #4a4a4a;
  --muted: #8a8a8a;
  --line: #e2e2e2;
  --runlabel: #7a7a7a;
  --logo-filter: none;
  --btn-bg: #0a0a0a; --btn-fg: #ffffff;
  --form-panel: linear-gradient(180deg, #f7f7f8 0%, #f0f0f1 100%);
  --form-border: #d8d8d8;
  --star-rgb: 10,10,12;
  --nav-frost: rgba(255,255,255,0.82);
  --btn-hover-fg: #ffffff;
  --chrome-ink: #ffffff;
  --dot-rgb: 150,150,150;
  /* light mode wants an ink-weight panel edge, not a glow */
  --panel-shadow: inset 0 1px 0 rgba(255,255,255,0.9), 0 20px 45px -30px rgba(0,0,0,0.18);
  --panel-edge: linear-gradient(150deg, rgba(79,116,201,0.30), rgba(181,106,174,0.14) 40%, rgba(10,10,12,0.05) 70%);

  /* the deep cut of the same iridescence: identical hue order and sweep,
     roughly 35% less lightness + more saturation, so the wordmark keeps its
     chrome character while actually holding contrast against white */
  --chrome: linear-gradient(100deg,
    #4f74c9 0%, #7a5fc7 28%, #b05aa6 50%, #c2803c 72%, #b8893f 100%);
}

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

html {
  scroll-behavior: smooth; -webkit-font-smoothing: antialiased;
  /* clip at the ROOT too: with clip only on body, mobile Chrome still grows
     the layout viewport around overflowing descendants — and position:fixed
     elements (the nav) size to that grown viewport, overflowing the screen */
  overflow-x: clip;
}

/* the nav is fixed, so an anchor jump would park the section underneath it.
   Every jump target reserves the nav height plus breathing room, so a clicked
   section lands with its label clear of the bar. */
:root { --nav-h: 69px; }
:target, [id] { scroll-margin-top: calc(var(--nav-h) + 2.5rem); }

body {
  background: var(--black);
  color: var(--white);
  font-family: var(--font-title);
  font-weight: 400;
  line-height: 1.5;
  overflow-x: clip;
}

/* chrome text ------------------------------------------------------------- */
.chrome {
  background: var(--chrome);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  animation: shimmer 8s ease-in-out infinite;
}
/* The shimmer animates background-position on background-clip:text, which
   re-rasterises the clipped glyphs every frame. Standing still that's fine;
   during a fast scroll the raster falls behind the frame and the gradient
   words visibly jitter. So the shimmer PAUSES while the page scrolls —
   app.js stamps is-scrolling on <body> and lifts it ~160ms after the last
   scroll event. An 8s loop losing a few hundred ms is imperceptible. */
body.is-scrolling .chrome,
body.is-scrolling .nav__brand:hover .nav__mark { animation-play-state: paused; }
@keyframes shimmer {
  0%, 100% { background-position: 0% 50%; }
  50% { background-position: 100% 50%; }
}

.mono { font-family: ui-monospace, "SF Mono", Menlo, monospace; letter-spacing: -0.02em; }

/* screen-reader / crawler only — present in the DOM for SEO & GEO extraction,
   invisible in the visual layout */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ░░ BUTTONS ░░ ----------------------------------------------------------- */
/* one pill system, used by the nav CTA and the form submit */
.btn {
  display: inline-flex; align-items: center; gap: 0.5rem;
  border: 1px solid transparent; border-radius: 999px;
  font-family: var(--font-title); font-size: 0.95rem; letter-spacing: 0.01em;
  padding: 0.7rem 1.5rem; cursor: pointer; text-decoration: none;
  transition: transform 0.25s ease, box-shadow 0.25s ease, background 0.25s ease, border-color 0.25s ease;
}
/* nav CTA: shorter and squarer than the full pill — less top padding, a
   rounded-rect radius instead of a full stadium shape */
.nav__cta.btn { padding: 0.5rem 1.3rem; border-radius: 8px; }
.btn--light { background: var(--btn-bg); color: var(--btn-fg); }
.btn--light:hover {
  background: var(--chrome); background-size: 200% 100%;
  /* the chrome fill flips lightness between themes, so the label colour has
     to flip with it or the text disappears into the gradient */
  color: var(--btn-hover-fg);
  animation: shimmer 8s ease-in-out infinite;
  transform: translateY(-1px); box-shadow: 0 10px 24px -10px rgba(185,168,224,0.4);
}
.btn--ghost { background: transparent; color: var(--grey); border-color: var(--line); }
.btn--ghost:hover { color: var(--white); border-color: var(--muted); }
.btn span { transition: transform 0.25s ease; }
.btn:hover span { transform: translateX(3px); }

/* ░░ THEME TOGGLE ░░ ------------------------------------------------------- */
/* a small pill switch — a single dot that slides + swaps sun/moon glyph via
   ::before content, driven purely by the [data-theme] attribute on <html> */
.theme-toggle {
  /* Hidden for now — one-line toggle back; the light theme below stays intact.
     Un-hiding this is TWO steps, not one: the pre-paint theme-restore script
     also has to come back into index.html's <head>, or a visitor who once
     chose light is stranded there with no toggle to escape it. Recover it with
     `git log -S nau-theme -- clients/nau.dos/site/index.html`. */
  display: none;
  flex: none; position: relative;
  width: 2.6rem; height: 1.5rem; border-radius: 999px;
  border: 1px solid var(--line); background: var(--near-black);
  cursor: pointer; padding: 0; transition: background 0.3s ease, border-color 0.3s ease;
}
.theme-toggle__icon {
  position: absolute; top: 1px; left: 1px;
  width: calc(1.5rem - 4px); height: calc(1.5rem - 4px); border-radius: 50%;
  background: var(--chrome);
  display: grid; place-items: center; font-size: 0.6rem; line-height: 1;
  transition: transform 0.3s ease;
}
.theme-toggle__icon::before { content: "☾"; color: var(--chrome-ink); }
:root[data-theme="light"] .theme-toggle__icon { transform: translateX(1.1rem); }
:root[data-theme="light"] .theme-toggle__icon::before { content: "☀"; }
.theme-toggle:hover { border-color: var(--muted); }

/* ░░ NAV ░░ --------------------------------------------------------------- */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  border-bottom: 1px solid transparent;
  /* backdrop-filter is deliberately NOT in this list: transitioning a blur
     re-filters the whole strip every frame for 300ms, and with the toggle
     boundary crossed repeatedly on a fast up-down scroll the page under the
     nav visibly wobbled. The blur snaps; the FADE the eye reads comes from
     the background transition, which is cheap. */
  transition: background 0.3s ease, border-color 0.3s ease;
}
/* transparent over the hero; frosted + hairline once the page starts scrolling */
.nav.is-stuck {
  background: var(--nav-frost);
  border-bottom-color: var(--line);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  backdrop-filter: blur(14px) saturate(140%);
}
.nav__inner {
  max-width: var(--maxw); margin: 0 auto;
  padding: 0.85rem var(--gutter);
  display: flex; align-items: center; gap: 1.5rem;
}
.nav__brand { display: flex; align-items: center; text-decoration: none; }
/* the wordmark is the ONLY place the arrow-A appears */
.nav__mark {
  font-size: 2.55rem; line-height: 1; letter-spacing: -0.01em;
  color: var(--white);
}
/* white at rest; the chrome gradient only reveals on hover of the whole
   brand link (not just the mark), reusing the .chrome text-clip technique.
   NOT transitioned, and the hover colour is rgba(255,255,255,0) rather than
   `transparent`: CSS `transparent` is rgba(0,0,0,0), so interpolating it back
   to white travels through translucent BLACK — the flash this replaces. The
   text-fill/clip swap can't be interpolated anyway, so the gradient reveals
   on the spot and the shimmer carries the motion. */
.nav__brand:hover .nav__mark {
  color: rgba(255, 255, 255, 0);
  background: var(--chrome);
  background-size: 200% 100%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  animation: shimmer 8s ease-in-out infinite;
}
.nav__links { display: flex; gap: 1.75rem; margin-left: auto; }
.nav__links a {
  color: var(--grey); text-decoration: none; font-size: 0.9rem;
  letter-spacing: 0.01em; transition: color 0.25s ease;
}
.nav__links a:hover { color: var(--white); }
.nav__cta { flex: none; }
@media (max-width: 720px) {
  .nav__links { display: none; }
  .nav__cta { margin-left: auto; }
  .nav__inner { gap: 1rem; }
}

/* ░░ HERO ░░ -------------------------------------------------------------- */
.hero {
  position: relative; height: 100svh; min-height: 600px;
  display: grid; place-items: center; overflow: hidden;
}
.hero__canvas { position: absolute; inset: 0; width: 100%; height: 100%; }
.hero__inner { position: relative; z-index: 2; text-align: center; transform: translateY(-20px); }
.wordmark {
  font-size: clamp(5rem, 24vw, 20rem);
  line-height: 0.8; font-weight: 400;
  /* optical correction: the arrow-A glyph reads visually right-heavy at this
     size, so nudge left to sit truly centered rather than box-centered */
  transform: translateX(-0.02em);
}
.hero__descriptor {
  margin-top: 1.5rem; font-size: clamp(1.1rem, 3.4vw, 2.2rem);
  letter-spacing: 0.06em; color: var(--grey);
}
.hero__btn {
  display: inline-block; margin-top: 3rem; padding: 0.8rem 2.2rem;
  background: var(--white); color: var(--black); border: none;
  font-family: inherit; font-size: 0.95rem; letter-spacing: 0.02em;
  text-decoration: none; cursor: pointer; transition: transform 0.2s, opacity 0.2s;
}
.hero__btn:hover { transform: translateY(-2px); opacity: 0.9; }
.hero__cue {
  position: absolute; bottom: 2rem; left: 50%; transform: translateX(-50%);
  z-index: 2; color: var(--muted); text-decoration: none; font-size: 1.2rem;
  animation: bob 2.4s ease-in-out infinite;
}
@keyframes bob { 0%,100% { transform: translate(-50%,0); } 50% { transform: translate(-50%,8px); } }

/* ░░ SECTIONS ░░ ---------------------------------------------------------- */
.section {
  position: relative;
  max-width: var(--maxw); margin: 0 auto;
  /* was clamp(7rem, 18vh, 15rem) — 162px of air above every section title on
     a 900px screen; trimmed a third across the board (Francisco, 2026-08-12) */
  padding: clamp(5rem, 11vh, 9rem) var(--gutter);
}
/* full-bleed 1px divider above each section (matches the footer line),
   spanning the whole viewport rather than just the centered content */
.section::before {
  content: ""; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 100vw; height: 1px; background: var(--line);
}
/* The first section keeps its divider too — it closes the hero rather than
   letting it bleed into the page. (The LP never had the exception, which is
   why the two pages used to disagree here.) */
/* section eyebrow — mono, uppercase, letterspaced. The structural spine of the
   page: every section announces itself before it speaks. */
.runlabel {
  display: block;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.7rem; font-style: normal;
  color: var(--runlabel); margin-bottom: 2.5rem;
  letter-spacing: 0.18em; text-transform: uppercase;
}
/* label→headline distance is CANONICAL: the 2.5rem above, every section, both
   pages, all widths (2026-08-12). Four per-section "tuck closer" overrides
   (thesis/dos/work 1.6, contact 2, LP problem 2, LP pedigree 1.1) were
   removed — matching labels with different drops below them read as sloppy,
   not tuned. Don't reintroduce one without a structural reason. */
.dos .runlabel { text-align: center; }
/* same canon, phone step: 2.5rem of drop above a 1.8rem headline reads
   oversized at 375 — still ONE value for every section, just a smaller one */
@media (max-width: 720px) {
  .runlabel { margin-bottom: 1.75rem; }
}
.lead {
  font-size: clamp(2.4rem, 7vw, 5.5rem); line-height: 0.98;
  /* headline→copy drop is CANONICAL like the runlabel's: 1.5rem, matching
     .dos__statement, every section, both pages (2026-08-12 — was 2rem here,
     1.75 on the LP problem, 1.5 elsewhere; visibly uneven across sections) */
  letter-spacing: -0.03em; font-weight: 400; margin-bottom: 1.5rem;
}
.lead.small { font-size: clamp(2rem, 5vw, 3.6rem); }
.body { font-family: var(--font-body); font-size: clamp(1.05rem, 1.8vw, 1.45rem); line-height: 1.45; max-width: 46ch; color: var(--grey); margin-bottom: 1.2rem; }
/* the other prose voices share the body face; titles keep Avenir Nau */
.dos__sub, .triptych .sub, .figure__caption, .principles dd { font-family: var(--font-body); }
.body.big {
  /* big white display statements are titles in disguise — Avenir Nau,
     not the body face the base .body rule now carries */
  font-family: var(--font-title);
  /* 0.98 = the one headline leading (.lead / .dos__statement), 2026-08-12 */
  font-size: clamp(1.5rem, 3vw, 2.4rem); color: var(--white); line-height: 0.98; max-width: 20ch; letter-spacing: -0.01em;
}
.body.big.pedigree__line { max-width: none; white-space: nowrap; margin-left: auto; margin-right: auto; }
.body.muted { color: var(--muted); }
.body.small { font-size: 0.9rem; }
.dos .lead .mono { font-size: clamp(3rem, 10vw, 8rem); }

/* .dos — big centered showcase ------------------------------------------- */
/* 30px tighter at the foot than the standard .section rhythm — the diagram
   plate already carries a lot of internal air below the hub */
.dos { text-align: center; padding-bottom: calc(clamp(7rem, 18vh, 15rem) - 30px); }
.dos__statement {
  /* line-height matches .lead's 0.98 (2026-08-12; was 1.2) — the two classes
     play the same section-headline role, and side by side the 1.2 read as a
     different, airier voice, not a tuned one. One headline leading, period. */
  font-size: clamp(1.6rem, 3.6vw, 3rem); line-height: 0.98; letter-spacing: -0.02em;
  max-width: 24ch; margin: 0 auto 1.5rem; color: var(--white);
}
/* the statement is sometimes an <h2> (LP "what you get") — kill the UA bold,
   which Avenir Nau (single-weight) can only fake by smearing the strokes */
.dos__statement { font-weight: 400; }
.dos__sub {
  font-size: clamp(0.95rem, 1.5vw, 1.2rem); line-height: 1.5;
  /* now a full sentence rather than a short one-liner — needs to wrap */
  max-width: 46ch; margin: 0 auto 3.5rem; color: var(--grey);
}
.dos__showcase { margin: 0; }
.plate--showcase {
  aspect-ratio: 16 / 9; width: 100%;
  border-radius: 14px;
}
.plate--showcase::before, .plate--showcase::after { display: none; }
.plate--showcase .plate__label {
  position: absolute; bottom: 1.1rem; left: 50%; transform: translateX(-50%);
}
/* the .dos diagram — animated brand core → outputs (drawn in app.js) */
.dos-diagram {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: block;
}
.dos__showcase .caption { text-align: center; margin-top: 1.4rem; }

/* thesis grid — text left, floating accent right -------------------------- */
.thesis__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(3rem, 8vw, 8rem);
  align-items: start;
}
/* the accent geo shapes still want to sit mid-column visually, just not by
   dragging the headline down with them */
.thesis__text { display: flex; flex-direction: column; gap: 0; }
/* keep the headline to exactly three lines (the <br>s), never auto-wrap */
.thesis .lead { font-size: clamp(1.8rem, 4.6vw, 3.6rem); white-space: nowrap; }
/* the thesis paragraph sits directly under a huge nowrap headline, which made
   the standard .body scale look disproportionately large by contrast next to
   every other section's use of it — pin it to the same size as the rest */
.thesis .body { font-size: 1.05rem; }
/* rotator — the cycling word in the thesis headline (agentic / branding /
   design), typed and deleted one character at a time. Plain inline, not
   inline-flex: the width changes on every keystroke by design, and the word
   sits on its own headline line so nothing else reflows. */
.rotator { position: relative; white-space: nowrap; }
/* the caret rides outside the .chrome span — inside it, background-clip:text
   plus a transparent text-fill would erase a block element like this one */
.rotator__caret {
  display: inline-block; vertical-align: baseline;
  width: 0.028em; height: 0.72em; margin-left: 0.06em;
  background: var(--chrome);
  animation: caret-blink 1.05s steps(1) infinite;
}
@keyframes caret-blink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) { .rotator__caret { display: none; } }
/* the accent column now runs the brand teaser (replaced the geo squares
   2026-07-31; the .geo rules below stay for an easy revert). Portrait cut,
   so height-capped and centered — same discipline as every prior occupant
   of this column: never let it outgrow the text beside it. */
/* the film spans EXACTLY the height of the copy beside it: the accent
   stretches to the grid row, the wrapper is absolute (out of flow, so the
   TEXT decides the row height), and the video fills that height. */
.thesis__accent { position: relative; align-self: stretch; }
/* the film runs slightly TALLER than the copy (--film-scale) and stays
   vertically centred on it, so it reads as a deliberate accent rather than a
   thumbnail. Overflow is intentional and symmetrical — it never pushes the
   row height, because the wrapper is out of flow. */
.thesis__accent .filmwrap {
  position: absolute; inset: 0;
  display: flex; justify-content: center; align-items: center;
  --film-scale: 1.28;
  /* The runlabel sits ABOVE the grid, inside the section. Centring the film on
     the grid therefore parks it half a runlabel-block low against the section's
     own edges (the divider lines the eye actually reads) — 29px at desktop.
     Lift it by that half so it's centred on the SECTION, not on the text
     column. Derived from the runlabel's own type + the canonical
     runlabel→headline rhythm, so it tracks them instead of being a tuned
     number. Ignored on mobile, where .filmwrap goes back to static flow. */
  --runlabel-block: calc(0.7rem * 1.5 + 2.5rem);
  top: calc(var(--runlabel-block) / -2);
  bottom: calc(var(--runlabel-block) / 2);
}
.thesis__film {
  display: block; height: 100%; width: auto; max-width: 100%;
  border-radius: 14px; border: 1px solid var(--line);
}
/* shared chrome aura around both film instances — all four gradient hues,
   each thrown to its own corner, so the halo reads as the SAME iridescent
   sweep as the buttons/wordmark rather than a two-tone wash */
.thesis__film {
  /* softened 2026-08-20: opacity down ~40% and the blur widened, so the halo
     falls off further from the edge instead of ringing it. A wider, fainter
     glow reads as light in the room; a tight bright one reads as a sticker. */
  box-shadow:
    -30px -20px 104px -30px rgba(159, 184, 232, 0.27),
     30px -20px 104px -30px rgba(185, 168, 224, 0.25),
    -30px  20px 104px -30px rgba(217, 184, 220, 0.24),
     30px  20px 104px -30px rgba(240, 212, 176, 0.25);
}
/* .filmwrap is the LAYOUT box (spans the accent column so the film can be
   centred in it); .filmbox hugs the video itself and is the positioning
   context for the sound toggle — anchoring the toggle to .filmwrap would
   throw it out to the column edge, clear of the video. */
.filmwrap { position: relative; display: inline-block; line-height: 0; }
.filmbox {
  position: relative; display: inline-block; line-height: 0;
  height: calc(100% * var(--film-scale, 1));
  max-height: none;
}
.sound-toggle {
  position: absolute; right: 0.9rem; bottom: 0.9rem; z-index: 2;
  font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 0.62rem;
  letter-spacing: 0.12em; text-transform: uppercase; line-height: 1;
  color: var(--grey); background: rgba(8, 8, 10, 0.62);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
  border: 1px solid rgba(179, 179, 183, 0.28); border-radius: 999px;
  padding: 0.5em 1em; cursor: pointer;
  /* hidden at rest — reveals while hovering the film (or keyboard-focused) */
  opacity: 0;
  transition: opacity 0.3s ease, color 0.25s ease, border-color 0.25s ease;
}
.filmbox:hover .sound-toggle, .filmwrap:hover .sound-toggle, .sound-toggle:focus-visible { opacity: 1; }
.sound-toggle:hover { color: var(--white); border-color: var(--muted); }
/* two overlapping geometric squares — the chrome accent mark. border-image
   renders unreliably on a small rotated box (reads as a dotted line), so use
   the same solid-gradient-border technique as .cform::before instead: a
   gradient background clipped to a 1px ring via a masked pseudo-element. */
.geo {
  position: absolute;
}
.geo::before {
  content: ""; position: absolute; inset: 0; padding: 1px;
  background: var(--chrome);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  pointer-events: none;
}
.geo--a {
  width: 55%; aspect-ratio: 1;
  top: 8%; right: 12%;
  background: linear-gradient(135deg, rgba(185,168,224,0.10) 0%, rgba(159,184,232,0.06) 100%);
  transform: rotate(12deg);
}
.geo--b {
  width: 45%; aspect-ratio: 1;
  bottom: 10%; right: 28%;
  background: linear-gradient(315deg, rgba(232,201,168,0.08) 0%, rgba(185,168,224,0.10) 100%);
  transform: rotate(-6deg);
}
@media (max-width: 720px) {
  .thesis__grid { grid-template-columns: 1fr; }
  /* Stacked, the film's breathing room came from two DIFFERENT values — the
     grid gap above (48px) and the section's padding below (80px) — so it sat
     visibly high, and the glow ate into the smaller side. 5rem is the mobile
     `.section { padding-block }` further down this file; keep the two in step
     so the film stays equidistant. */
  .thesis__grid { gap: 5rem; }
  /* stacked single-column: back in normal flow (the absolute wrapper would
     collapse to zero height once there's no text column to stretch against) */
  .thesis__accent { align-self: auto; }
  .thesis__accent .filmwrap { position: static; }
  .filmbox { height: auto; }
  .thesis__film { height: auto; max-height: 420px; }
}

/* workshow — full-screen crossfading work slideshow ------------------------ */
.workshow {
  position: relative; width: 100vw; margin-left: calc(-50vw + 50%);
  height: 92svh; min-height: 480px; overflow: hidden;
  border-top: 1px solid var(--line);
  /* contain the note's blend to this section — without it the difference
     mode would reach through to the page background behind the slides */
  isolation: isolate;
  /* the line IS the cursor here — hide the real one so there aren't two */
  cursor: none;
}
/* keyboard users get no pointer, so the line must still be findable: show the
   system cursor back if someone tabs into the section */
.workshow:focus-within { cursor: auto; }
.workshow__slide {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  /* hard cut, no crossfade — slides swap instantly */
  opacity: 0;
}
.workshow__slide.is-on { opacity: 1; }
/* soft scrim so the centre line reads over any image */
/* the scrim is gone (2026-08-11): it existed so the old "work credentials
   available on request" note could be read over the photos. That note was
   replaced by the cursor-tracking project title in mix-blend-mode:difference,
   which a scrim actively works against — it was darkening the work by 20% at
   centre to solve a problem that no longer exists. The element stays in the
   markup as an empty hook. */
.workshow__scrim { position: absolute; inset: 0; pointer-events: none; }
/* per-slide caption — the project title IS the cursor: it rides the pointer
   1:1 (app.js writes --nx/--ny as the pointer's px offset from the section
   centre; no transition — easing would make it trail instead of track) and
   inverts against whatever slide is behind it. Pinned to literal #fff, not
   the theme token: difference blending is arithmetic on real channel values.
   With no pointer (touch, reduced motion) it simply sits centred. */
.workshow__caption-in {
  background: rgba(0, 0, 0, 0.82);
  padding: 0.55em 0.95em;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.workshow__caption {
  position: absolute; inset: 0; z-index: 2;
  display: grid; place-items: center; text-align: center;
  padding: 0 var(--gutter);
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.72rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: #fff;
  /* a solid plate replaced mix-blend-mode:difference (2026-08-11): difference
     against a black background is the identity operation, so the two can't
     coexist — and the plate is legible over every image, which difference
     was not on mid-tone frames. */
  pointer-events: none;
  --nx: 0px; --ny: 0px;
  transform: translate3d(var(--nx), var(--ny), 0);
  will-change: transform;
}

/* pointer-tracking is decoration — hold it centred when motion is unwelcome */
@media (prefers-reduced-motion: reduce) {
  .workshow__caption { transform: none; }
}

/* triptych — what we do --------------------------------------------------- */
/* hairline-separated columns (same lattice logic as the logo grid): pull the
   whole row left by one pad so the first column's CONTENT stays flush with the
   section edge while every column keeps identical padding — identical width. */
.triptych {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0;
  margin-left: calc(-1 * clamp(1rem, 2.4vw, 2.4rem));
}
.triptych article {
  min-width: 0; padding: 0 clamp(1rem, 2.4vw, 2.4rem);
  border-left: 1px solid var(--line);
}
.triptych article:first-child { border-left: none; }
.triptych h3 { font-size: clamp(1.6rem, 3vw, 2.6rem); font-weight: 400; letter-spacing: -0.02em; }
.triptych .sub { color: var(--grey); margin-bottom: 1.2rem; font-size: 1rem; }
.triptych .body { font-size: 1.05rem; line-height: 1.5; max-width: none; }

/* process — Augusta-style figure columns ---------------------------------- */
.figures {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  margin-top: 1rem;
  /* pull left by one pad so the first card's CONTENT still sits flush with the
     section edge while every card keeps identical padding (== identical width) */
  margin-left: calc(-1 * clamp(1rem, 2.4vw, 2.4rem));
}
.figure {
  display: flex; flex-direction: column; min-width: 0;
  padding: 0 clamp(1rem, 2.4vw, 2.4rem);
  border-left: 1px solid var(--line);
}
.figure__title { overflow-wrap: break-word; }
.figure:first-child { border-left: none; }
.figure__label {
  font-family: ui-monospace, Menlo, monospace; font-size: 0.72rem;
  letter-spacing: 0.16em; color: var(--muted); text-transform: uppercase;
}
/* monochrome dotted orb — CSS dot-matrix masked into a sphere */
.figure__art {
  aspect-ratio: 1; width: 100%; margin: clamp(1.5rem, 4vw, 3rem) 0;
  background-image: radial-gradient(rgba(var(--dot-rgb),0.55) 0.9px, transparent 1.4px);
  background-size: 8px 8px;
  -webkit-mask-image: radial-gradient(circle at 50% 46%, #000 26%, rgba(0,0,0,0.45) 52%, transparent 68%);
  mask-image: radial-gradient(circle at 50% 46%, #000 26%, rgba(0,0,0,0.45) 52%, transparent 68%);
}
/* subtle per-column variation so the four orbs aren't identical */
.figure:nth-child(2) .figure__art { background-size: 7px 7px; -webkit-mask-image: radial-gradient(circle at 52% 50%, #000 20%, transparent 60%); mask-image: radial-gradient(circle at 52% 50%, #000 20%, transparent 60%); }
.figure:nth-child(3) .figure__art { background-size: 9px 9px; -webkit-mask-image: radial-gradient(ellipse 55% 62% at 48% 48%, #000 30%, transparent 72%); mask-image: radial-gradient(ellipse 55% 62% at 48% 48%, #000 30%, transparent 72%); }
.figure:nth-child(4) .figure__art { background-size: 8px 8px; -webkit-mask-image: radial-gradient(circle at 50% 44%, #000 32%, rgba(0,0,0,0.4) 50%, transparent 66%); mask-image: radial-gradient(circle at 50% 44%, #000 32%, rgba(0,0,0,0.4) 50%, transparent 66%); }
.figure__title { font-size: clamp(1.4rem, 2.2vw, 2rem); font-weight: 400; letter-spacing: -0.02em; margin-bottom: 0.8rem; }
.figure__caption { color: var(--grey); font-size: 0.95rem; line-height: 1.5; }

/* principles -------------------------------------------------------------- */
.principles dl { display: grid; gap: 0; }
.principles div {
  display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;
  align-items: baseline; padding: 1.6rem 0; border-bottom: 1px solid var(--line);
}
.principles div:first-child { border-top: 1px solid var(--line); }
.principles dt { font-size: clamp(1.4rem, 3vw, 2.4rem); letter-spacing: -0.02em; }
.principles dt em { font-style: italic; font-family: var(--serif); color: var(--muted); font-size: 0.75em; }
.principles dd { color: var(--grey); font-size: 1rem; }

/* pedigree — bordered logo grid ------------------------------------------- */
.pedigree { padding-bottom: clamp(2.5rem, 7vh, 4.5rem) !important; text-align: center; }
/* desktop: the reel sits ~35px closer to its line (Francisco, 2026-08-12) —
   the !important above forces an !important here too */
@media (min-width: 721px) {
  .pedigree { padding-bottom: 1.75rem !important; }
}
/* hairline-boxed cells: static and institutional. Collapsing the shared edges
   with a negative-offset trick would fight the reveal transform, so instead
   each cell draws only its right + bottom rule and the container draws the
   opening top + left — one clean 1px lattice, no doubled lines. */
.logogrid {
  /* sized by MARGIN, never padding: a border sits outside the padding box, so
     a gutter-padded grid would overhang its own cells by one gutter. This
     matches the .section content width exactly (maxw minus its two gutters). */
  width: calc(100% - 2 * var(--gutter));
  max-width: calc(var(--maxw) - 2 * var(--gutter));
  margin: 0 auto clamp(5rem, 12vh, 9rem);
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  border-top: 1px solid var(--line); border-left: 1px solid var(--line);
}
.logogrid__cell {
  display: grid; place-items: center;
  min-height: clamp(96px, 11vw, 140px); padding: 1.5rem 1rem;
  border-right: 1px solid var(--line); border-bottom: 1px solid var(--line);
  transition: background 0.35s ease;
}
.logogrid__cell:hover { background: var(--near-black); }
.logo-tile {
  display: inline-flex; align-items: center; text-align: center;
  color: var(--grey); font-size: clamp(1rem, 1.7vw, 1.35rem); opacity: 0.5;
  letter-spacing: -0.01em; transition: opacity 0.3s, color 0.3s;
}
.logogrid__cell:hover .logo-tile { opacity: 1; color: var(--white); }
/* real brand marks — forced monochrome, optically matched to the text tiles */
.logo-img {
  max-height: 2.6rem; max-width: 78%; width: auto; object-fit: contain;
  filter: var(--logo-filter); opacity: 0.5;
  transition: opacity 0.3s;
}
.logogrid__cell:hover .logo-img { opacity: 1; }
/* wide wordmarks read oversized when height-matched to the square marks */
.logo-img--wide { max-height: 1.5rem; }
/* EE's mark is portrait — cap it so it doesn't tower over its neighbours */
.logo-img--tall { max-height: 3.2rem; }
/* Richemont is ~17:1 — width-limited no matter what, so give it the full cell
   rather than the 78% the others get, and it still lands short. */
.logo-img--xwide { max-height: 1.5rem; max-width: 92%; }
/* some marks (Simple Icons glyph-only cuts) sit well inside their own square
   viewBox, so a shared max-height reads smaller than marks that fill it
   edge-to-edge (Toyota, Budweiser, Oris) — boost these to match by eye */
.logo-img--boost { max-height: 3.3rem; }
/* second boost tier — glyphs whose ink is small even inside the boost cap */
.logo-img--boost2 { max-height: 4rem; }
@media (max-width: 900px) { .logogrid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 600px) { .logogrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* deliverables — ledger rows (big title left, detail right), deliberately a
   different rhythm from the 3-across triptych above it */
.dlist { list-style: none; margin: 1.5rem 0 0; padding: 0; }
.dlist li {
  display: grid; grid-template-columns: minmax(10rem, 18rem) 1fr;
  align-items: baseline; gap: clamp(1rem, 4vw, 4rem);
  padding: 1.8rem 0; border-bottom: 1px solid var(--line);
  /* each row fades/rises in independently once the section is revealed,
     staggered so row 1 leads, then 2, then 3 */
  opacity: 0; transform: translateY(16px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}
/* scoped to .section, NOT to .deliverables: the ledger is reused by the
   landing pages (problem / process / faq), and a rule tied to one section
   name leaves every other .dlist stuck at opacity 0 forever */
.section.in .dlist li { opacity: 1; transform: none; }
.section.in .dlist li:nth-child(1) { transition-delay: 0.05s; }
.section.in .dlist li:nth-child(2) { transition-delay: 0.22s; }
.section.in .dlist li:nth-child(3) { transition-delay: 0.39s; }
.section.in .dlist li:nth-child(4) { transition-delay: 0.50s; }
.section.in .dlist li:nth-child(5) { transition-delay: 0.58s; }
.section.in .dlist li:nth-child(6) { transition-delay: 0.64s; }
.section.in .dlist li:nth-child(7) { transition-delay: 0.69s; }
.section.in .dlist li:nth-child(n+8) { transition-delay: 0.73s; }
@media (prefers-reduced-motion: reduce) {
  .dlist li { opacity: 1; transform: none; transition: none; }
}
.dlist li:first-child { border-top: 1px solid var(--line); }
.dlist h3 {
  font-size: clamp(1.5rem, 3vw, 2.4rem); font-weight: 400;
  letter-spacing: -0.02em; margin: 0;
}
.dlist .body {
  font-size: 1.05rem; line-height: 1.5; max-width: 52ch; margin-bottom: 0;
  /* baseline-align two very different font sizes naturally sits the smaller
     text low against the larger title's baseline — nudge it up to visually
     center against the title instead */
  transform: translateY(-10px);
}
@media (max-width: 720px) {
  .dlist li { grid-template-columns: 1fr; gap: 0.5rem; }
}

/* ecosystem --------------------------------------------------------------- */
.people--cards {
  list-style: none; display: grid; grid-template-columns: repeat(4, 1fr);
  gap: clamp(1rem, 2vw, 1.5rem); margin: 2.5rem 0;
}
.person {
  display: flex; flex-direction: column; align-items: center; gap: 1.2rem;
  padding: 2rem 1rem; border: 1px solid var(--line); border-radius: 14px;
  background: transparent; transition: border-color 0.4s;
}
.person:hover { border-color: var(--muted); }
.person__pic {
  width: clamp(96px, 11vw, 140px); aspect-ratio: 1; border-radius: 50%;
  background: var(--near-black);
  overflow: hidden;
}
.person__pic img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  /* photos are now consistently framed at source, so a single uniform crop
     lands every face the same — no per-person compensation needed */
  object-position: center 35%;
}
.person__name {
  font-size: clamp(1rem, 1.5vw, 1.25rem); letter-spacing: -0.01em; text-align: center;
}

/* contact ----------------------------------------------------------------- */
.contact__grid {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: clamp(2.5rem, 6vw, 6rem);
  /* the "07 —" runlabel lives INSIDE the intro column rather than above the
     grid, so the grid's top row starts at the label — which puts the form's
     top edge level with it. Top-aligned, no negative-margin guesswork. */
  align-items: start;
}
/* the label is the column's first line, so it carries no extra top offset;
   its drop to the headline is the canonical .runlabel margin */
/* ONE size for every section-level heading that isn't the .dos statement, so
   no section shouts louder than its neighbour. Both selectors share the rule
   deliberately — split them and they drift. */
.contact__intro .lead,
.lp-problem .lead { font-size: clamp(1.8rem, 3.2vw, 2.8rem); }
.contact__intro .body { font-size: 1.05rem; margin-bottom: 1.2rem; }
/* the practical facts — gives the short left column real weight against the
   taller form, with information rather than filler */
.contact__facts {
  list-style: none; margin: 1.8rem 0 0; padding: 0;
  display: grid; gap: 0.55rem;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.68rem; letter-spacing: 0.13em; text-transform: uppercase;
  color: var(--muted);
}
.contact__facts a { color: var(--grey); text-decoration: none; transition: color 0.25s ease; }
.contact__facts a:hover { color: var(--white); }
.cform__status {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.68rem; letter-spacing: 0.06em; color: var(--grey);
  margin-top: 0.2rem; min-height: 1em;
}
/* the confirmation is the one line here worth reading — lift it out of grey */
.is-sent .cform__status { color: var(--white); }
/* Netlify honeypot. Deliberately NOT display:none — a bot that reads the CSS
   skips hidden fields, and a field it skips catches nothing. Taken out of
   flow and off-screen instead, with aria-hidden so screen readers ignore it
   and tabindex -1 on the input so keyboard users never land in it. */
.is-hp {
  position: absolute; left: -9999px;
  width: 1px; height: 1px; overflow: hidden;
}
/* form as a soft glass panel — a faint chrome hairline top edge, no boxed
   90s-style bordered inputs; labels float into the field on focus/fill */
.cform {
  position: relative;
  display: grid; gap: 1.1rem;
  padding: clamp(1.5rem, 2.6vw, 2.1rem);
  border-radius: 20px;
  background: var(--form-panel);
  box-shadow: var(--panel-shadow);
}
.cform::before {
  content: ""; position: absolute; inset: 0; border-radius: 20px; padding: 1px;
  background: var(--panel-edge);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  pointer-events: none;
}
.cform__field { position: relative; }
.cform input, .cform textarea {
  width: 100%; background: transparent; border: none; border-bottom: 1px solid var(--form-border);
  color: var(--white); font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 1.05rem; padding: 1.4rem 0 0.6rem;
  transition: border-color 0.3s;
}
.cform textarea { min-height: 5rem; resize: vertical; }
.cform input:focus, .cform textarea:focus { outline: none; border-color: var(--white); }
/* floating label: sits inline until the field is focused or filled */
.cform__field label {
  position: absolute; left: 0; top: 1.4rem;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  /* --grey, not --muted: on the lifted panel the #666 labels were the last
     thing still reading as switched-off — and the label is the field's only
     affordance until it floats */
  font-size: 1.05rem; color: var(--grey); letter-spacing: -0.005em;
  pointer-events: none; transform-origin: left top;
  transition: transform 0.2s ease, color 0.2s ease;
}
.cform input:focus + label, .cform input:not(:placeholder-shown) + label,
.cform textarea:focus + label, .cform textarea:not(:placeholder-shown) + label {
  transform: translateY(-1.15rem) scale(0.76);
  color: var(--grey);
}
.cform button {
  justify-self: start; background: var(--btn-bg); color: var(--btn-fg);
  border: none; border-radius: 999px; font-family: inherit; font-size: 1rem; letter-spacing: 0.02em;
  padding: 0.95rem 2.4rem; cursor: pointer; margin-top: 0.25rem;
  display: inline-flex; align-items: center; gap: 0.5rem;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.cform button:hover { transform: translateY(-2px); box-shadow: 0 12px 24px -8px rgba(255,255,255,0.25); }
.cform button span { transition: transform 0.25s ease; }
.cform button:hover span { transform: translateX(3px); }
.contact a { color: var(--white); text-decoration: underline; text-underline-offset: 3px; }
@media (max-width: 720px) {
  .contact__grid { grid-template-columns: 1fr; }
}

/* why now — the urgency beat before the finale ---------------------------- */
.whynow__line {
  font-family: var(--font-title);
  font-size: clamp(1.7rem, 4vw, 3.1rem); line-height: 1.08;
  letter-spacing: -0.02em; color: var(--white);
  max-width: 18ch; margin-bottom: 1.6rem;
}
.whynow .body { max-width: 52ch; }

/* ░░ CLOSE ░░ ------------------------------------------------------------- */
.close {
  position: relative; min-height: 72svh;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding: clamp(6rem, 14vh, 9rem) var(--gutter); overflow: hidden;
  border-top: 1px solid var(--line);
  /* nudge the centered line up: extra bottom padding shifts flex-centered
     content upward without touching transform (which the scroll-reveal
     system also sets on .close__line, and would silently override) */
  padding-bottom: calc(clamp(6rem, 14vh, 9rem) + 3rem);
}
.close__line {
  position: relative; font-size: clamp(3rem, 11vw, 8.5rem); letter-spacing: -0.03em;
  margin: 0;
}
/* the line types itself in when scrolled into view (see app.js). The old
   opacity fade on .close__nau is gone — the typing IS the reveal now, and a
   fade on top of it would double up. Full text stays in the HTML, so with
   JS off the line simply renders complete. */
.close__caret {
  display: inline-block; vertical-align: baseline;
  width: 0.028em; height: 0.72em; margin-left: 0.06em;
  background: var(--chrome);
  animation: caret-blink 1.05s steps(1) infinite;
}
/* retired once the sentence finishes typing */
.close__caret.is-done { opacity: 0; animation: none; transition: opacity 0.4s ease; }
@media (prefers-reduced-motion: reduce) { .close__caret { display: none; } }
/* image plates (placeholders) -------------------------------------------- */
.plate {
  position: relative; aspect-ratio: 4 / 3;
  background: var(--near-black);
  border: 1px solid var(--line); overflow: hidden;
  display: grid; place-items: center; transition: border-color 0.4s;
  /* base gradient overlay — varies per nth-child below */
  background-image:
    radial-gradient(130% 130% at 72% 18%, rgba(185,168,224,0.09), transparent 58%);
  background-color: var(--near-black);
}
.plate:hover { border-color: var(--muted); }
/* varied gradients per plate — subtle, alive but not distracting */
.plate:nth-child(1) { background-image: linear-gradient(135deg, rgba(159,184,232,0.12) 0%, rgba(185,168,224,0.08) 100%); }
.plate:nth-child(2) { background-image: linear-gradient(45deg, rgba(232,201,168,0.10) 0%, rgba(240,212,176,0.08) 100%); }
.plate:nth-child(3) { background-image: linear-gradient(225deg, rgba(217,184,220,0.10) 0%, rgba(159,184,232,0.06) 100%); }
.plate:nth-child(4) { background-image: linear-gradient(90deg, rgba(240,212,176,0.08) 0%, rgba(217,184,220,0.10) 100%); }
.plate:nth-child(5) { background-image: linear-gradient(180deg, rgba(159,184,232,0.08) 0%, rgba(232,201,168,0.10) 100%); }
.plate:nth-child(6) { background-image: linear-gradient(315deg, rgba(185,168,224,0.10) 0%, rgba(240,212,176,0.08) 100%); }
/* the .dos diagram plate stays FLAT — the canvas draws its own chrome, and a
   tinted wash behind it muddied the links. Two classes to match the
   nth-child specificity above, and placed after it so it actually wins. */
.plate.plate--showcase {
  background-image: none;
  background-color: var(--near-black);
}
/* photo plate — real imagery fills the frame */
.plate--photo { background: var(--near-black); }
.plate__img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center;
}
.plate--photo .plate__label {
  position: absolute; bottom: 0.75rem; left: 50%; transform: translateX(-50%);
  z-index: 1; color: var(--white);
}

.plate--wide { aspect-ratio: 16 / 9; }
.plate--tall { aspect-ratio: 3 / 4; }
.plate__label {
  font-family: ui-monospace, Menlo, monospace; font-size: 0.7rem;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--muted);
}
.plate__idx {
  position: absolute; top: 0.75rem; left: 1rem;
  font-family: var(--serif); font-style: italic; font-size: 0.72rem; color: var(--muted);
}
/* thin chrome corner ticks — makes an empty frame feel intentional */
.plate::before, .plate::after {
  content: ""; position: absolute; width: 14px; height: 14px; opacity: 0.5;
}
.plate::before { top: 0; right: 0; border-top: 1px solid; border-right: 1px solid;
  border-image: var(--chrome) 1; }
.plate::after { bottom: 0; left: 0; border-bottom: 1px solid; border-left: 1px solid;
  border-image: var(--chrome) 1; }

.caption { font-family: var(--serif); font-style: italic; font-size: 0.82rem; color: var(--muted); margin-top: 0.9rem; }
figure { margin: 0; }

.gallery { display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(1rem, 2.5vw, 2rem); margin-top: 2.5rem; }
.triptych .plate { margin-bottom: 1.5rem; }

/* reveal on scroll -------------------------------------------------------- */
/* the reveal moved from the SECTION to its direct children (2026-08-02), so a
   section arrives as a short sequence — label, then statement, then the body —
   instead of one flat slab. Every section is a flat list of block children
   (runlabel + 1-3 blocks), so nth-child is enough and no JS index is needed.
   Curve is an ease-out-expo: most of the travel happens early, then it settles
   — reads slower and more deliberate than the symmetric `ease` it replaced. */
.section > *, .close__line {
  opacity: 0; transform: translateY(28px);
  transition: opacity 0.9s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);
}
.section.in > *, .close__line.in { opacity: 1; transform: none; }
.section.in > *:nth-child(2) { transition-delay: 0.09s; }
.section.in > *:nth-child(3) { transition-delay: 0.18s; }
.section.in > *:nth-child(4) { transition-delay: 0.27s; }
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; }
  .section > *, .close__line { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
}

/* responsive -------------------------------------------------------------- */
@media (max-width: 900px) {
  .figures { grid-template-columns: repeat(2, 1fr); row-gap: 3rem; }
  .figure:nth-child(3) { border-left: none; padding-left: 0; }
}
@media (max-width: 720px) {
  .triptych { grid-template-columns: 1fr; gap: 3rem; margin-left: 0; }
  .triptych article { border-left: none; padding: 0; }
  .figures { grid-template-columns: 1fr; }
  .figure { border-left: none; padding-left: 0; }
  .figure__art { max-width: 220px; }
  .dos__sub { white-space: normal; max-width: 40ch; }
  .principles div { grid-template-columns: 1fr; gap: 0.4rem; }
  .gallery { grid-template-columns: 1fr; }
  .people--cards { grid-template-columns: repeat(2, 1fr); }
  .runhead { font-size: 0.62rem; }
}

/* landing pages ----------------------------------------------------------- */
/* The LPs reuse this whole stylesheet; these are the only rules unique to
   them. Ad traffic gets ONE offer above the fold, so the LP hero is shorter
   than the brand hero (which exists to be an entrance, not a pitch) and
   carries a headline + subline + CTA instead of the bare wordmark. */
.hero--lp { height: auto; min-height: 88svh; padding: 8rem var(--gutter) 5rem; }
.hero--lp .hero__inner { transform: none; max-width: 46rem; }
.lp__h1 {
  font-size: clamp(2.4rem, 7vw, 5rem);
  margin-bottom: 1.75rem;
}
.lp__sub {
  font-size: clamp(1.05rem, 1.6vw, 1.25rem);
  max-width: 44ch; margin: 0 auto 2.5rem;
}
.lp__cta {
  display: flex; flex-wrap: wrap; gap: 1.5rem;
  align-items: center; justify-content: center;
}
.lp__cta-alt {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--grey); text-decoration: none;
  transition: color 0.25s ease;
}
.lp__cta-alt:hover { color: var(--white); }
/* the problem/process/faq rows are the .dlist ledger, but their titles are
   sentences rather than two-word labels — give the label column more room and
   drop the display size a step so a full question still sets on 1-2 lines */
.lp-problem .dlist h3,
.lp-process .dlist h3,
.faq .dlist h3 { font-size: clamp(1.1rem, 1.8vw, 1.4rem); letter-spacing: -0.01em; }
/* FAQ rows are native <details>, so they collapse with no JS — and, crucially,
   a closed <details> keeps its answer in the DOM, where crawlers and LLMs
   still read it. Hiding the answers behind JS would have cost the whole
   point of the section. */
.faq .dlist li { display: block; padding: 0; }
.faq__list summary {
  display: flex; align-items: baseline; gap: clamp(1rem, 4vw, 3rem);
  padding: 1.6rem 0; cursor: pointer;
  list-style: none;                       /* Firefox marker */
  transition: opacity 0.25s ease;
}
.faq__list summary::-webkit-details-marker { display: none; }
.faq__list summary:hover { opacity: 0.72; }
.faq__list summary h3 { flex: 1; }
.faq__list .body { max-width: 62ch; padding-bottom: 1.7rem; }
/* a plus that loses its stroke on open — cheaper to read than a chevron and
   it animates on one axis only */
.faq__icon { flex: none; position: relative; width: 0.85rem; height: 0.85rem; align-self: center; }
.faq__icon::before, .faq__icon::after {
  content: ""; position: absolute; background: var(--muted);
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1), background 0.25s ease;
}
.faq__icon::before { top: 50%; left: 0; width: 100%; height: 1px; margin-top: -0.5px; }
.faq__icon::after { left: 50%; top: 0; width: 1px; height: 100%; margin-left: -0.5px; }
.faq__list details[open] .faq__icon::after { transform: scaleY(0); }
.faq__list summary:hover .faq__icon::before,
.faq__list summary:hover .faq__icon::after { background: var(--white); }
@media (max-width: 720px) {
  .hero--lp { padding-top: 6rem; }
}

/* landing-page type scale ------------------------------------------------- */
/* The LP inherits the whole site stylesheet, which was written for a page
   with a different rhythm — that left 11 distinct sizes on one screen. These
   rules collapse it to FIVE steps. Everything is scoped under .lp so the
   main site keeps its own scale untouched.

     display   59px   the H1. once per page.
     statement 29px   .dos statement + every section heading
     row       20px   every ledger row title, questions included
     body      17px   all running copy
     micro     11px   uppercase mono labels

   Anything not in that list is a bug, not a decision. */
.lp .dos__statement,
.lp .body.big { font-size: clamp(1.8rem, 3.2vw, 2.8rem); }
.lp .dlist h3,
.lp .lp-problem .dlist h3,
.lp .lp-process .dlist h3,
.lp .faq .dlist h3 { font-size: 1.25rem; letter-spacing: -0.01em; }
.lp .lp__sub,
.lp .dos__sub { font-size: 1.05rem; max-width: 64ch; }
.lp .lp__cta-alt,
.lp .contact__facts,
.lp .plate__idx { font-size: 0.7rem; }
/* the form submit sat at the inherited 1rem while every .btn ran at 0.95rem —
   two button sizes on one page. One control size. */
.lp .cform button { font-size: 0.95rem; }

/* landing-page contact: the composer -------------------------------------- */
/* Single column, because the closing beat should be the widest moment on the
   page, not a two-column form competing with its own headline. */
.contact--lp { text-align: center; }
.contact--lp__h {
  /* the one place the LP reaches past the section-heading step: this is the
     closing bookend to the H1, so it answers to the display scale instead */
  font-size: clamp(2.6rem, 6vw, 4.4rem);
  line-height: 1.05; margin-bottom: 1.5rem;
}
/* the only centred, standalone body block on the page — at full body size it
   competes with the 50px heading above it instead of supporting it. Demoted to
   the 0.95rem step (already in the scale) so it reads as the caption it is. */
.contact--lp__sub {
  font-size: 0.95rem; line-height: 1.55;
  max-width: 38ch; margin: 0 auto 3rem; color: var(--grey);
}
.contact--lp .contact__facts {
  display: flex; gap: 2rem; justify-content: center; flex-wrap: wrap;
  margin-top: 2rem; list-style: none;
}

.composer {
  position: relative; max-width: 46rem; margin: 0 auto; text-align: left;
  border-radius: 20px; background: var(--form-panel);
  box-shadow: var(--panel-shadow);
  padding: clamp(1.25rem, 2.2vw, 1.75rem);
  transition: box-shadow 0.35s ease;
}
.composer::before {
  content: ""; position: absolute; inset: 0; border-radius: 20px; padding: 1px;
  background: var(--panel-edge);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  pointer-events: none;
}
/* the whole panel lights up when the field inside has focus — it behaves like
   one control, which is the point */
.composer:focus-within { box-shadow: var(--panel-shadow), 0 0 0 1px rgba(185,168,224,0.35); }

.composer__field { position: relative; display: flex; gap: 0.75rem; align-items: flex-start; }
.composer__caret {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  color: var(--muted); line-height: 1.6; flex: none; user-select: none;
}
.composer textarea {
  flex: 1; width: 100%; background: transparent; border: none; resize: none;
  color: var(--white); font-family: var(--font-body);
  font-size: 1.05rem; line-height: 1.6; padding: 0;
  min-height: 3.4rem;
}
.composer textarea:focus { outline: none; }
/* ghost placeholder — sits under the caret, same metrics as the textarea so
   the typed text lands exactly where the real caret will be */
.composer__ghost {
  position: absolute; left: 1.6rem; top: 0; right: 0;
  font-family: var(--font-body); font-size: 1.05rem; line-height: 1.6;
  color: var(--muted); pointer-events: none;
}
.composer__ghost::after {
  content: ""; display: inline-block; vertical-align: baseline;
  width: 1px; height: 1em; margin-left: 2px; background: var(--grey);
  animation: caret-blink 1.05s steps(1) infinite;
}
.composer.is-typing .composer__ghost { display: none; }

.composer__foot {
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  margin-top: 1.1rem; padding-top: 1.1rem;
  border-top: 1px solid var(--form-border);
}
.composer__ids { display: flex; gap: 0.75rem; flex: 1; min-width: 14rem; }
.composer__ids input {
  flex: 1; min-width: 0; background: transparent;
  border: none; border-bottom: 1px solid transparent;
  color: var(--white); font-family: var(--font-body); font-size: 0.95rem;
  padding: 0.35rem 0; transition: border-color 0.3s ease;
}
.composer__ids input::placeholder { color: var(--muted); }
.composer__ids input:focus { outline: none; border-bottom-color: var(--white); }
.composer button {
  flex: none; background: var(--btn-bg); color: var(--btn-fg);
  border: none; border-radius: 999px; font-family: var(--font-title);
  font-size: 0.95rem; padding: 0.7rem 1.6rem; cursor: pointer;
  display: inline-flex; align-items: center; gap: 0.5rem;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.composer button:hover { transform: translateY(-2px); box-shadow: 0 12px 24px -8px rgba(255,255,255,0.25); }
.composer button span { transition: transform 0.25s ease; }
.composer button:hover span { transform: translateX(3px); }
@media (max-width: 560px) {
  .composer__ids { flex-direction: column; gap: 0.25rem; }
}


/* LP: the process strip --------------------------------------------------- */
/* Three steps are a sequence, so they are drawn as one: a single rule runs
   behind the numerals and the eye tracks left-to-right instead of down. Used
   ONLY by "how it works" — the ledger stays the page's default. */
.steps {
  position: relative; list-style: none;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(1.5rem, 3vw, 3rem); margin-top: 2.5rem;
}
.steps::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0.72rem;
  height: 1px; background: var(--line);
}
.steps__step { position: relative; padding-top: 2.4rem; }
/* the numeral sits ON the rule, so it needs the page colour behind it to
   knock the line out rather than overlap it */
.steps__n {
  position: absolute; top: 0; left: 0;
  width: 1.5rem; height: 1.5rem; border-radius: 50%;
  background: var(--black); border: 1px solid var(--muted);
  display: grid; place-items: center;
  /* 0.7rem = the page's micro step, same as .runlabel — a bespoke 0.62rem
     here would have been a 7th type size for one glyph */
  font-family: var(--serif); font-style: italic; font-size: 0.7rem;
  color: var(--grey);
}
/* same step as every other row title on the page — keeps the type scale at 6 */
.lp .steps__step h3 { font-size: 1.25rem; letter-spacing: -0.01em; margin-bottom: 0.6rem; }
/* 0.95rem, not the page's 1.05rem body: a 237px column at 1.05rem runs ~28
   characters per line, so the copy wraps every three words and reads oversized
   for its container. 0.95rem is already in the scale (it is the button step). */
.steps__step .body { font-size: 0.95rem; line-height: 1.5; color: var(--grey); }
@media (max-width: 760px) {
  .steps { grid-template-columns: 1fr; gap: 1.75rem; }
  .steps::before { display: none; }
  .steps__step { padding-top: 0; padding-left: 2.2rem; }
  .steps__n { top: 0.1rem; }
}

/* ░░ LP TYPE SCALE — FIVE STEPS, NO EXCEPTIONS ░░ -------------------------- */
/* Appended last so it wins over every inherited rule. If a value that is not
   one of these five appears on the landing page, it is a bug.
     display 59px · heading 29px · row 20px · body 15px · micro 11px          */

/* 1 — display. The two bookends: the H1 that opens and the CTA that closes. */
.lp .lp__h1,
.lp .contact--lp__h { font-size: clamp(2.4rem, 7vw, 5rem); line-height: 0.98; }

/* 2 — heading. Section headings and the one statement. */
.lp .lead.small,
.lp .lp-problem .lead,
.lp .dos__statement,
.lp .body.big { font-size: clamp(1.8rem, 3.2vw, 2.8rem); }

/* 3 — row. Every ledger title, step title and FAQ question. */
.lp .dlist h3,
.lp .steps__step h3 { font-size: 1.25rem; letter-spacing: -0.01em; }

/* 4 — body, 1.05rem. Same as the main site, so the two pages read alike.
   (Was 0.95rem: I had dropped ALL prose a step to fix two narrow-column
   cases, which starved the FAQ — the page's biggest block of text, at a
   perfectly comfortable 46-58 characters per line.) */
.lp .body,
.lp .lp__sub,
.lp .dos__sub,
.lp .dlist .body,
.lp .composer,
.lp .composer textarea,
.lp .composer__ghost,
.lp .composer__caret,
.lp .composer__ids input { font-size: 1.05rem; }

/* 5 — small, 0.95rem. Controls, plus the one genuinely narrow column on the
   page: the problem cards run 221px wide (~27 chars/line) and cannot take
   the body step without wrapping every three words. */
.lp .btn,
.lp .composer button,
.lp .contact--lp__sub,
.lp .steps__step .body { font-size: 0.95rem; }

/* 5 — micro. Uppercase mono labels and the step numerals. */
.lp .runlabel,
.lp .lp__cta-alt,
.lp .contact__facts,
.lp .plate__idx,
.lp .steps__n { font-size: 0.7rem; }

/* the ≠ pivot ------------------------------------------------------------- */
/* The symbol carries the argument the copy used to spell out, so it gets the
   line to itself and the only chrome in the section. Avenir Nau draws it on
   the maths axis (top 517 vs 468 for a lowercase x) — a typed letter "x"
   would sit low and read as collaboration, not opposition. */
.pivot {
  /* inline, and NO font-size of its own — a bespoke size here would have been
     a 6th step on a page whose whole rule is five. Padding does the optical
     work instead. */
  padding: 0 0.12em;
}

/* LP: logo marquee -------------------------------------------------------- */
/* Full-bleed reel. The track is TWO identical rows and animates to exactly
   -50%, which is what makes the loop seamless: at the end of the cycle the
   second row sits precisely where the first started. */
.marquee {
  position: relative;
  width: 100vw; margin-left: calc(-50vw + 50%);
  overflow: hidden;
  /* asymmetric on purpose: twice the room below, so the reel sits closer to
     what it belongs to above and clears the next section's rule */
  /* 2.5rem minus 30px — the reel sits closer to the pedigree line above */
  padding: calc(2.5rem - 30px) 0 5rem;
  /* feather both edges so logos enter and leave instead of being guillotined
     by the viewport */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}
.marquee__track {
  display: flex; width: max-content;
  animation: marquee-scroll 46s linear infinite;
}
.marquee__row {
  display: flex; align-items: center; flex: none;
  gap: clamp(2.5rem, 5vw, 4.5rem);
  padding-right: clamp(2.5rem, 5vw, 4.5rem);
}
/* A single fixed height would throw away the per-mark optical calibration the
   grid already carries (.logo-img--boost / --tall / --wide exist because some
   marks read small at a shared height). These mirror the grid's max-height
   ratios as real heights, scaled up for the reel. */
.marquee__row img {
  height: 2.2rem; width: auto; flex: none;
  filter: var(--logo-filter);
  opacity: 0.55;
}
.marquee__row .logo-img--wide   { height: 1.3rem; }
.marquee__row .logo-img--tall   { height: 2.7rem; }
.marquee__row .logo-img--boost  { height: 2.8rem; }
.marquee__row .logo-img--boost2 { height: 3.4rem; }
/* no hover styling at all: the reel moves and doesn't pause, so a hovered
   mark slides out from under the cursor within a second — any hover effect
   reads as flicker. A moving reel is not a hover surface. */
@keyframes marquee-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .marquee__track { animation: none; }
  .marquee { overflow-x: auto; }
}

/* LP: vertical tabs ------------------------------------------------------- */
.vtabs {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
  /* headline→MODULE runs the canonical 1.5rem plus 10px (Francisco's eye,
     2026-08-12): against an image block the bare text step reads too tight.
     Same value at every width. Margins COLLAPSE — the +10 must ride on top
     of the statement's own 24px, hence the calc, not a bare 10px. */
  gap: clamp(2rem, 5vw, 5rem); align-items: stretch;
  margin-top: calc(1.5rem + 10px);
}
.vtabs__list { list-style: none; margin: 0; padding: 0; }
.vtab { border-bottom: 1px solid var(--line); }
.vtab:first-child { border-top: 1px solid var(--line); }
.vtab__head {
  width: 100%; background: none; border: none; padding: 1.5rem 0; margin: 0;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  font-family: var(--font-title); color: var(--muted);
  text-align: left; cursor: pointer;
  transition: color 0.3s ease;
}
.vtab__head:hover { color: var(--grey); }
.vtab.is-open .vtab__head { color: var(--white); }
.vtab__t { font-size: 1.25rem; letter-spacing: -0.01em; }
/* the mark is a dash that becomes a full rule when its topic is open — the
   quietest possible "you are here" for a three-item list */
.vtab__mark {
  flex: none; height: 1px; width: 0.9rem; background: currentColor;
  transition: width 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.vtab.is-open .vtab__mark { width: 2.4rem; }
/* 0fr → 1fr animates a panel of unknown height without measuring it in JS */
.vtab__panel {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.vtab.is-open .vtab__panel { grid-template-rows: 1fr; }
.vtab__inner { overflow: hidden; }
.vtab__inner .body {
  color: var(--grey); max-width: 46ch;
  padding-bottom: 1.6rem;
  opacity: 0; transition: opacity 0.3s ease 0.1s;
}
.vtab.is-open .vtab__inner .body { opacity: 1; }

.vtabs__media {
  /* stretches to the exact height of the tab list beside it (grid
     align-items: stretch) — the two columns always bottom out together.
     Sources are all ~16:9 so `cover` crops the sides a little at this
     taller box; the sticky behaviour goes, stretch and sticky conflict. */
  position: relative;
  border-radius: 14px; overflow: hidden;
  background: var(--near-black); border: 1px solid var(--line);
  min-height: 320px;
}
.vtabs__img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center;
  opacity: 0; transition: opacity 0.5s ease;
}
/* every tab image is landscape now, so all three cover the box identically —
   the earlier `contain` exception is gone and with it the letterboxing */
.vtabs__img.is-on { opacity: 1; }

@media (max-width: 860px) {
  .vtabs { grid-template-columns: 1fr; }
  /* each tab's media lives INSIDE its accordion panel — app.js moves the ONE
     set of stage elements in here (duplicating them in HTML would download
     brand.mp4 twice). The stage stays in the DOM as their desktop home. */
  .vtabs__media { display: none; }
  .vtab__inner .vtabs__img {
    position: static; width: 100%; height: auto;
    aspect-ratio: 16 / 10; object-fit: cover;
    border-radius: 14px; border: 1px solid var(--line);
    margin-bottom: 1.2rem; opacity: 1; display: block;
  }
}

/* LP: the problem --------------------------------------------------------- */
/* 50/50: collage left, the whole argument (label + statement + paragraph)
   left-aligned on the right. One gravity centre, read in a single
   left-to-right gesture. */
.lp .lp-problem__grid {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2.5rem, 5vw, 5rem); align-items: start;
  text-align: left;
}
.lp .lp-problem__sub { max-width: 46ch; color: var(--grey); }

.filedrift {
  border-radius: 14px; overflow: hidden;
  background: var(--near-black); border: 1px solid var(--line);
}
.filedrift__bar {
  display: flex; gap: 6px; padding: 0.8rem 1rem;
  border-bottom: 1px solid var(--line);
}
.filedrift__bar span {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--line);
}
.filedrift__list {
  list-style: none; margin: 0; padding: 0.6rem 0 0.9rem;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.78rem; letter-spacing: 0.02em;
}
.filedrift__list li {
  padding: 0.55rem 1.25rem;
  color: var(--muted);
  border-bottom: 1px solid transparent;
}
/* the decay: each row a touch fainter than the one above, until the last */
.filedrift__list li:nth-child(n+4) { opacity: 0.8; }
.filedrift__list li:nth-child(n+6) { opacity: 0.65; }
.filedrift__worst.filedrift__worst {
  color: var(--white); opacity: 1;
  background: rgba(185, 168, 224, 0.07);
}
@media (max-width: 860px) {
  .lp .lp-problem__grid { grid-template-columns: 1fr; }
  .lp .lp-problem__text { text-align: left; }
}

/* the pain scene: the file window recedes, chat bubbles float over it — the
   same day-to-day chaos as the reference collage, drawn entirely in HTML.
   Fixed-height stage; everything inside is absolutely placed. */
.painscene { position: relative; height: 400px; }
.painscene .filedrift {
  position: absolute; top: 9%; left: 10%; width: 76%;
  opacity: 0.8;
}
.painbubble {
  position: absolute;
  display: flex; align-items: center; gap: 0.7rem;
  background: var(--btn-bg);
  border-radius: 999px; padding: 0.45rem 1.2rem 0.45rem 0.45rem;
  box-shadow: 0 18px 40px -18px rgba(0,0,0,0.8);
  max-width: 92%;
}
.painbubble p {
  margin: 0; font-family: var(--font-body);
  font-size: 0.95rem; color: var(--btn-fg); white-space: nowrap;
}
/* photo crops, not initials */
.painbubble__avatar {
  flex: none; width: 2.4rem; height: 2.4rem; border-radius: 50%;
  object-fit: cover; display: block;
}
/* status chips — the reference collage's "Build Script Validated" pills, but
   failing. Mono like the file window, so chat is human and chips are machine. */
.painchip {
  position: absolute;
  display: inline-flex; align-items: center; gap: 0.5rem;
  background: var(--near-black); border: 1px solid var(--line);
  border-radius: 999px; padding: 0.45rem 0.9rem;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.66rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--grey); white-space: nowrap;
}
.painchip__dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: #e08a8a;
}
.painchip--1 { right: 6%; bottom: 0; }
.painchip--2 { right: 0; top: 0; }
.painbubble--1 { right: 0; top: 30%; }
.painbubble--2 { left: 0; bottom: 8%; }
.painbubble--3 { left: 0; top: 12%; }
@media (max-width: 860px) {
  .painscene { height: auto; display: grid; gap: 0.8rem; }
  .painscene .filedrift { position: static; width: 100%; }
  .painbubble { position: static; }
  .painchip { position: static; justify-self: start; }
  .painbubble p { white-space: normal; }
}

/* LP: "what you get" gets a statement, like the .dos section --------------- */
/* The section carried only a runlabel while every other beat on the page
   opens with a full statement — it read as a subsection of the one above it.
   Reusing .dos__statement rather than a new class means the two are the same
   thing by construction and can never drift apart. */
.lp .deliverables { text-align: center; }
.lp .deliverables .vtabs { text-align: left; }
/* the "what you get" statement is one line by intent — the .dos statement
   above it is two, so this one needs a wide enough measure not to wrap on its
   own and accidentally mirror it. */
.lp .deliverables .dos__statement { max-width: none; text-wrap: balance; }

/* LP footer --------------------------------------------------------------- */
/* Replaces the brand site's full-height finale. Everything sits on the micro
   step and one line, so the section reads as a rule under the page rather
   than a second ending competing with the CTA. */
/* contact keeps the .section padding symmetric (same clamp top and bottom);
   the footer adds nothing on top of it — the 4rem it used to add was what
   made the void */
.lpfoot { border-top: 1px solid var(--line); margin-top: 0; }
.lpfoot__inner {
  max-width: var(--maxw); margin: 0 auto;
  padding: 1.75rem var(--gutter);
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.5rem; flex-wrap: wrap;
}
.lpfoot__mark {
  font-family: var(--font-title); font-size: 1.5rem; line-height: 1;
  color: var(--white);
}
.lpfoot__copy, .lpfoot__line {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.7rem; letter-spacing: 0.14em; text-transform: uppercase;
}
.lpfoot__copy { color: var(--runlabel); }
@media (max-width: 560px) {
  /* one line: the wordmark and the tagline go, the copyright carries it */
  .lpfoot__inner { justify-content: center; text-align: center; padding: 1.1rem var(--gutter); }
  .lpfoot__mark, .lpfoot__line { display: none; }
}

/* a tab's media can be a video as well as an image — same class, same cover
   behaviour, so the tab switcher needs no knowledge of which is which */
video.vtabs__img { display: block; }

/* marquee: per-mark optical nudges ---------------------------------------- */
/* These six read small next to their neighbours at the shared class heights —
   glyph-only cuts and wide wordmarks whose ink sits well inside the artboard.
   Targeted by src so the shared --boost/--wide tiers (and the main site's
   static grid, which has no .marquee) stay untouched. */
/* Both are SQUARE artboards (64x64) with the wordmark occupying a fraction of
   the height, so they read tiny at any shared height. `max-height: none` is
   load-bearing: .logo-img--boost2 caps at 4rem and was silently clamping
   anything taller. */
.marquee__row img[src*="siemens"],
.marquee__row img[src*="samsung"]     { height: 7.75rem; max-height: none; }
.marquee__row img[src*="jeep"]        { height: 3.25rem; max-height: none; }
.marquee__row img[src*="toyota"],
.marquee__row img[src*="bmw"]         { height: 2.75rem; max-height: none; }
.marquee__row img[src*="johnlewis"]   { height: 1.75rem; max-height: none; }

/* the problem visual is now a single exported image */
/* the export is already the right shape (840x586) — show it uncropped.
   height:auto is load-bearing: the element also carries .painscene, whose
   old HTML-stage rule pins height to 400px. */
.painscene--img { width: 100%; display: block; height: auto; }

/* ░░ LP CHROME DOSAGE ░░ --------------------------------------------------- */
/* Option B: the signature turned up from 3 words to a system — an ambient
   glow in the hero, every runlabel, and the active marks of both interactive
   components. Distributed small doses, never a painted surface. */



/* 3 — the vtabs' active dash draws in chrome instead of white */
.lp .vtab.is-open .vtab__mark { background: var(--chrome); }

/* 4 — the FAQ's open state: the remaining horizontal stroke goes chrome */
.lp .faq__list details[open] .faq__icon::before { background: var(--chrome); }

/* ░░ LP WARM CHROME (experiment) ░░ ---------------------------------------- */
/* The signature re-cut toward heat: gold → peach → burnt orange, same
   metallic sweep. Scoped to .lp — the main site keeps the cool chrome.
   NOTE the hero canvas mesh words are drawn in JS with the cool set and
   don't follow this variable. */
.lp {
  /* the tail stays luminous: the old #cf5530 end read muddy on black, so the
     ramp now peaks at a brighter coral-orange instead of descending into it */
  --chrome: linear-gradient(100deg,
    #f4d9b8 0%, #f0bd8c 30%, #eb9a68 55%, #ec8156 78%, #f07a4a 100%);
}

/* FAQ questions run in regular Avenir, not the Nau cut: they're the only
   titles with arbitrary product names in them ("ChatGPT"), and the cut's
   display letterforms (short T, arrow A) misrender words it wasn't drawn
   for. The Nau cut stays on the row titles we control. */
.lp .faq__list h3 { font-family: var(--font-body); }

/* the mobile nav keeps brand left, CTA right — the CSS auto margin on
   .nav__links dies with display:none, so the CTA carries its own */
@media (max-width: 720px) {
  .lp .nav__cta { margin-left: auto; }
}

/* ░░ MOBILE TYPE ALIGNMENT ░░ ----------------------------------------------
   Phones only, BOTH pages (2026-08-12 — was .lp-scoped, the main site was
   left out and its titles drifted left while the LP's centred).
   Titles centre: a two-line statement left-aligned leaves an ugly step on a
   narrow screen. Running copy centres WITH them — one axis down the whole
   page. (Justify was tried first, 2026-08-12: even hyphenated, a ~327px
   measure opened rivers between words. Rejected on looks.)
   Desktop keeps its own alignment untouched. */
@media (max-width: 720px) {
  .runlabel,
  .dos__statement,
  .lp-problem__text .lead,
  .thesis .lead,
  .explorations .lead,
  .contact .lead,
  .contact--lp__h,
  .pedigree__line { text-align: center; }
  .ecosystem .body.big,
  .deliverables .dlist h3 { text-align: center; }
  /* the shared 0.98 headline leading is desktop-display tight; at the phone
     sizes multi-line titles need a touch more air. Still ONE value. */
  .lead,
  .dos__statement,
  .body.big,
  .lp .lp__h1,
  .lp .contact--lp__h { line-height: 1.08; }
  .dos__statement,
  .pedigree__line,
  .contact--lp__h,
  .ecosystem .body.big,
  .contact .lead { margin-left: auto; margin-right: auto; }
  /* the contact sub is the one sub that never wraps (one 24px line vs the 5-6
     of the others), so there is no line-start for the eye to hunt — it follows
     its centred headline instead of breaking the axis under it */
  .lp .contact--lp__sub { text-align: center; }
  /* the composer joins the centred axis too — on the phone it was the one
     block still hanging left under a centred headline. The "&gt;" prompt goes
     with it: a terminal prompt pinned left beside centred copy reads as a
     bug, and the blinking caret after the ghost keeps the typed-prompt idea
     alive on its own. */
  .lp .composer__caret { display: none; }
  .lp .composer textarea { text-align: center; }
  .lp .composer__ghost { left: 0; text-align: center; }
  .lp .composer__ids input { text-align: center; }
  .lp .composer__foot { justify-content: center; }
  .lp .composer .cform__status { text-align: center; width: 100%; }
  .dos__sub,
  .lp-problem__sub,
  .thesis .body,
  .deliverables .body { text-align: center; }
  /* the exceptions: copy that sits under its own LEFT-aligned local title (a
     vtab's "the brand", a FAQ question), not under a centred section headline
     — centring it would break that local axis instead of joining the page's */
  .vtab__inner .body,
  .faq__list .body { text-align: left; }
}

/* the home's section statements ran smaller than the LP's on a phone
   (25.6/24 vs 28.8) with no reason behind the split — levelled to the shared
   statement step. LP untouched, it was already there. */
@media (max-width: 720px) {
  body:not(.lp) .dos__statement,
  body:not(.lp) .pedigree__line { font-size: 1.8rem; }
}

/* the pedigree line is nowrap for the desktop one-liner — on a phone that
   truncates at the screen edge, so it wraps (balanced, no orphan) */
@media (max-width: 720px) {
  .body.big.pedigree__line { white-space: normal; text-wrap: balance; }
}

/* the .dos diagram floats free on BOTH pages — no plate border */
.plate.plate--showcase { border: none; }

/* Samsung and Siemens ship as SQUARE artboards with the wordmark occupying a
   sliver of the height, so the shared --boost2 cap (4rem) renders them tiny
   next to marks that fill their own box. Same per-src treatment the marquee
   already carries — applied here to the static grid on the main site. */
.logogrid__cell .logo-img[src*="samsung"],
.logogrid__cell .logo-img[src*="siemens"] { max-height: 6.5rem; max-width: 92%; }
/* Jeep sits on --boost (3.3rem); +5% brings it level with its neighbours */
.logogrid__cell .logo-img[src*="jeep"] { max-height: 3.47rem; }

/* LP mobile pass (2026-08-12) --------------------------------------------- */
@media (max-width: 720px) {
  /* section rhythm: 146px of padding top AND bottom is desktop air — on an
     812px screen it costs a third of the viewport per boundary. Halved
     uniformly rather than per-section, so the rhythm stays even. */
  .section { padding-block: 5rem; }

  /* the hero sub runs six lines on a phone and the drifting words cross it;
     the starfield stays (it's the signature) but stops competing */
  .lp .hero__canvas { opacity: 0.45; }

  /* reel density: at 375 the 40px gap shows barely three marks per screen,
     so the loop reads slow */
  .lp .marquee__row { gap: 26px; }
}
