/* Base: reset, type roles, focus, motion. No element keeps a default margin, so spacing only ever comes from a flex
   container's gap and padding (BRIEF §5: the Figma importer turns those into auto-layout). */
*, *::before, *::after { box-sizing: border-box; }
/* scroll-padding: fragment links (#main, #faq-…, #model), and focus moved by keyboard, land clear of the sticky header
   (WCAG 2.4.11), 24 px under it */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; interpolate-size: allow-keywords;
  scroll-padding-top: calc(var(--header-h) + var(--s-24)); }
body { margin: 0; min-width: 320px; background: var(--paper); color: var(--ink); font-family: var(--sans);
  font-size: var(--body-fs); line-height: var(--body-lh); font-weight: 400; -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility; font-kerning: normal; display: flex; flex-direction: column; }
h1, h2, h3, h4, p, ul, ol, li, figure, blockquote, dl, dd, fieldset, legend, table, form { margin: 0; padding: 0; }
ul, ol { list-style: none; }
li { display: flex; flex-direction: column; }
picture { display: flex; }
fieldset { border: 0; min-width: 0; }
img, svg, picture { display: block; max-width: 100%; }
img { height: auto; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; margin: 0; }
button { background: none; border: 0; padding: 0; cursor: pointer; }
main { display: flex; flex-direction: column; }
[hidden] { display: none !important; }

/* ---------- type roles (one class per role; sizes come from tokens.css per breakpoint) ---------- */
.t-display { font-family: var(--serif); font-weight: 700; font-size: var(--display-fs); line-height: var(--display-lh); letter-spacing: var(--display-ls); }
.t-h1 { font-family: var(--serif); font-weight: 700; font-size: var(--h1-fs); line-height: var(--h1-lh); letter-spacing: var(--h1-ls); }
.t-hero { font-family: var(--serif); font-weight: 700; font-size: var(--hero-title-fs); line-height: var(--hero-title-lh); letter-spacing: var(--hero-title-ls); }
.t-h2 { font-family: var(--serif); font-weight: 600; font-size: var(--h2-fs); line-height: var(--h2-lh); letter-spacing: var(--h2-ls); }
.t-h3 { font-family: var(--serif); font-weight: 600; font-size: var(--h3-fs); line-height: var(--h3-lh); letter-spacing: 0; }
.t-quote { font-family: var(--serif); font-weight: 500; font-size: var(--quote-fs); line-height: var(--quote-lh); letter-spacing: -0.005em; }
.t-lead { font-family: var(--sans); font-weight: 400; font-size: var(--lead-fs); line-height: var(--lead-lh); letter-spacing: -0.005em; }
.t-body { font-family: var(--sans); font-weight: 400; font-size: var(--body-fs); line-height: var(--body-lh); }
.t-stat { font-family: var(--sans); font-weight: 600; font-size: var(--stat-fs); line-height: var(--stat-lh); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.t-label { font-family: var(--narrow); font-weight: 600; font-size: var(--label-fs); line-height: var(--label-lh); letter-spacing: var(--label-ls); text-transform: uppercase; }
.t-ui { font-family: var(--sans); font-weight: 500; font-size: var(--ui-fs); line-height: var(--ui-lh); }
/* headlines wrap into balanced lines (the Figma importer keeps Chrome's breaks for type >= 24 px). Reading text wraps
   greedily on purpose: Figma cannot reproduce `pretty`, and the design file must match the site line for line. */
.t-display, .t-h1, .t-hero, .t-h2, .t-h3, .t-quote { text-wrap: balance; }
/* figures: wrap every number Roger quotes in .num. Archivo is the face for figures only at 32 px and below (BRIEF §0,
   F19): stats, H3 statements and pricing lines, the TrustStrip. U+2044 fractions are shaped by the font. line-height 1:
   Archivo's taller ascent would otherwise grow the Newsreader line box by 2 px in Chrome (Figma keeps the paragraph's
   line height). The Newsreader roles above 32 px (display, H1, H2, quote: F8, F17) keep their figures in the line's
   own face, so a $10B+ or a 50% never reads as a grotesque pasted into the serif line. */
.num { font-family: var(--sans); font-variant-numeric: tabular-nums; line-height: 1; }
.t-display .num, .t-h1 .num, .t-hero .num, .t-h2 .num, .t-quote .num { font-family: inherit; font-variant-numeric: normal; line-height: inherit; }
/* typography only: keeps a short run (a hyphenated word, a closing pair) on one line; the words are unchanged */
.nowrap { white-space: nowrap; }

/* colour helpers */
.c-ink { color: var(--ink); } .c-ink-2 { color: var(--ink-2); } .c-ink-3 { color: var(--ink-3); } .c-blue { color: var(--blue); }

/* ---------- grow underline (BRIEF §0f.4: "links: underline grows from left"): a 1 px rule under a one-line link
   label that draws from its left edge on hover (150 ms, ease-out) and retracts on leave (180 ms, ease-in);
   aria-current and the drawn Hover state (.u-grow--on / .link-chev--hover) hold it drawn, and a card (.card,
   .insight-hit) draws its link's rule while the card is hovered. A pseudo-element moved by `scale`, not a gradient
   (the gates ban gradients), so the label's box and the Figma import never change. Multi-line titles (Related,
   insight titles) keep the text-decoration fade: one rule cannot follow a wrapped line.
   Who carries it: .link-chev__label (every Link/Chevron), .nav__label, .footer__link, .footer__small, or any
   one-line label given class="u-grow". ---------- */
:is(.u-grow, .link-chev__label) { position: relative; }
:is(.u-grow, .link-chev__label)::after { content: ""; position: absolute; left: 0; right: 0; top: calc(100% + 3px); height: 1px;
  background: currentColor; scale: 0 1; transform-origin: 0 50%; transition: scale var(--dur-exit) var(--ease-in); pointer-events: none; }
@media (hover: hover) {
  :is(a, button):hover > :is(.u-grow, .link-chev__label)::after, :is(a, button).u-grow:hover::after,
  :is(.card, .insight-hit):hover :is(.u-grow, .link-chev__label)::after { scale: 1 1; transition: scale var(--dur-hover) var(--ease-out); }
}
.u-grow--on::after, .link-chev--hover > .link-chev__label::after,
[aria-current="page"] > :is(.u-grow, .link-chev__label)::after, .u-grow[aria-current="page"]::after { scale: 1 1; }

/* ---------- focus: 2 px blue ring, 2 px outside; the element's own box never moves ---------- */
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.on-blue :focus-visible { outline-color: var(--on-blue); }

/* ---------- skip link: off-screen until focused ---------- */
.skip { position: absolute; left: var(--margin); top: 8px; z-index: 100; min-height: var(--tap); padding: 0 16px;
  display: flex; align-items: center; background: var(--ink); color: var(--paper); font-weight: 500; font-size: 16px; line-height: 24px;
  clip-path: inset(50%); }
.skip:focus-visible { clip-path: none; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* ---------- page changes (same-origin navigations): the old page fades out (120 ms, ease-in), then the new one fades
   in (180 ms, ease-out), the header held in place. Sequential, not a cross-fade: two pages never show through each
   other, and the paper ground (body, propagated to the canvas) shows between them, never white. Reduced motion: an
   instant swap. ---------- */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation: 120ms ease-in both vt-out; }
::view-transition-new(root) { animation: 180ms ease-out 120ms both vt-in; }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; } }
.site-header { view-transition-name: site-header; }
::view-transition-group(site-header) { animation-duration: 0s; }
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) { animation: 150ms cubic-bezier(.4, 0, 1, 1) both vt-out; }
  ::view-transition-new(root) { animation: 260ms cubic-bezier(.2, .7, .2, 1) 120ms both vt-in; }
}

/* First arrival only: every entry route gets a brief, fail-open welcome beat before the page appears.
   The head script sets .landing-load synchronously, so the page never flashes underneath it. */
.landing-loader { display: none; }
.landing-load body > :not(.landing-loader) { visibility: hidden; }
.landing-load::before { content: ""; position: fixed; inset: 0; z-index: 100; background: var(--paper); pointer-events: auto;
  transition: opacity 150ms var(--ease-in); }
.landing-load .landing-loader { display: flex; position: fixed; z-index: 101; inset: 0; align-items: center; justify-content: center;
  color: var(--blue); pointer-events: none; transition: opacity 150ms var(--ease-in); }
.landing-loader__track { position: relative; width: min(208px, 56vw); height: 34px; border-bottom: 1px solid var(--rule);
  overflow: hidden; overflow: clip; }
.landing-loader__arrow, .landing-loader__echo { position: absolute; top: 8px; left: 3px; width: 15px; height: 15px;
  border-top: 3px solid currentColor; border-right: 3px solid currentColor; rotate: 45deg; }
.landing-loader__echo { opacity: 0; }
.landing-load .landing-loader__arrow { animation: loader-cross 560ms cubic-bezier(.2, .7, .2, 1) both; }
.landing-load .landing-loader__echo { animation: loader-echo 560ms cubic-bezier(.2, .7, .2, 1) both; }
.landing-load--exit body > :not(.landing-loader) { visibility: visible; }
.landing-load--exit::before, .landing-load--exit .landing-loader { opacity: 0; }
.landing-load--exit::before, .landing-load .landing-loader__arrow, .landing-load .landing-loader__echo { transition: opacity 150ms var(--ease-in); }
@keyframes loader-cross { 0% { translate: 0 0; opacity: 0; }
  18% { opacity: 1; }
  66% { translate: calc(min(208px, 56vw) - 26px) 0; opacity: 1; }
  67% { translate: -16px 0; opacity: 0; } 82% { translate: -8px 0; opacity: .18; }
  100% { translate: 0 0; opacity: 1; } }
@keyframes loader-echo { 0%, 28% { translate: 0 0; opacity: 0; }
  38% { translate: 5px 0; opacity: .36; } 54% { translate: 12px 0; opacity: .18; }
  68%, 100% { translate: 15px 0; opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
  .landing-load body > :not(.landing-loader) { visibility: visible; }
  .landing-load::before, .landing-load .landing-loader { display: none; }
}

/* ---------- smooth anchor scrolling (BRIEF §0f.4): in-page links (#model, #philosophy, #faq-…) glide to their target,
   which lands --header-h + 24 under the top (scroll-padding above), clear of the sticky header. Only for people who
   have not asked for less motion. ---------- */
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }

/* ---------- reveal on scroll (BRIEF §0f.4): a section head, a card or a media stage fades in and rises 16 px the
   first time it enters the viewport, once, 360 ms on the enter curve. A group marks its children [data-reveal-item]:
   they follow 60 ms apart (site.js sets --reveal-i). Pages opt in with class="reveal" or [data-reveal] on the element
   (or on the group). Armed only by site.js (html.reveal-on) and only for what starts below the first screen (that is
   marked data-reveal-in="now" and never animates; the rest gets "play" as it enters), so: no JS = everything shows;
   the first screen never fades; reduced motion = never armed. An animation, not a transition,
   so a card's own hover transitions are never overridden.
   Opaque things never fade: a half-transparent card, media frame, photo or motif tile let the column lines behind it
   show through like an x-ray for the whole 360 ms. site.js marks a target that has a fill (or an image) inside it
   [data-reveal-wipe]; it stays fully opaque and is unmasked from its top edge (clip-path) while it rises, so the
   lines are covered exactly where the surface already is. Text-only targets keep the fade. Fill mode backwards:
   once played, no clip-path stays behind to cut a focus ring or a hover edge. ---------- */
.reveal-on :is(.reveal, [data-reveal]):not([data-reveal-in]):not(:has([data-reveal-item])),
.reveal-on :is(.reveal, [data-reveal]):not([data-reveal-in]) [data-reveal-item] { opacity: 0; }
.reveal-on :is(.reveal, [data-reveal])[data-reveal-in="play"]:not(:has([data-reveal-item])),
.reveal-on :is(.reveal, [data-reveal])[data-reveal-in="play"] [data-reveal-item] {
  animation: reveal-rise var(--dur-reveal) var(--ease-out) backwards; animation-delay: calc(var(--reveal-i, 0) * var(--stagger)); }
.reveal-on :is(.reveal, [data-reveal])[data-reveal-in="play"][data-reveal-wipe]:not(:has([data-reveal-item])),
.reveal-on :is(.reveal, [data-reveal])[data-reveal-in="play"] [data-reveal-item][data-reveal-wipe] { animation-name: reveal-wipe; }
/* photo parallax (site.js): the image drifts inside its clipped square; transform composes with a card's hover scale */
.drift { overflow: hidden; }
.drift > img { transform: translate3d(0, var(--drift, 0px), 0) scale(var(--overscan, 1)); }
@keyframes reveal-rise { from { opacity: 0; translate: 0 var(--reveal-rise); } to { opacity: 1; translate: none; } }
@keyframes reveal-wipe { from { clip-path: inset(0 0 100% 0); translate: 0 var(--reveal-rise); } to { clip-path: inset(0); translate: none; } }
@media print { .reveal-on :is(.reveal, [data-reveal], [data-reveal-item]) { opacity: 1 !important; animation: none !important; } }

/* ---------- motion-off: a design-state URL (?state=menu, quote-2 …) shows its state at once, so a capture never
   catches a panel mid-flight (site.js sets it for two frames while it applies the state) ---------- */
.motion-off *, .motion-off *::before, .motion-off *::after { transition: none !important; animation: none !important; }

/* ---------- reduced motion removes every transition and animation ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition: none !important; animation: none !important; scroll-behavior: auto !important; }
  /* its own rule: a browser without ::details-content would drop the whole selector list above */
  .faq__item::details-content { transition: none !important; }
}
@media (prefers-reduced-motion: reduce) { @view-transition { navigation: none; } }
