/* WYLD demo — tokens, reset, type scale, grid, reveal primitives */

@font-face { font-family: 'Fraunces fallback'; src: local('Georgia'); size-adjust: 96%; }

/* ---------------------------------------------------------------
   PALETTES — demo switcher only. Every colour on the site resolves
   through these tokens, so a palette is a token set and nothing else.
   Stripping the switcher before launch means deleting palette.js, its
   script tag and markup, the .pbar block in components.css, and the
   html[data-palette] blocks below. Nothing else changes.
   --------------------------------------------------------------- */


/* Sodium-vapour amber, blue-black ground */
html[data-palette="sodium"] {
  --ink:    #0C0F14;
  --ink-2:  #161B22;
  --bone:   #F1EEE6;
  --paper:  #F9F7F1;
  --moss:   #4A4636;
  --ember:  #F2A20C;
  --stone:  #8C8A80;
  --stone-lit: #5D5B51;
  --line:   rgba(12,15,20,.15);
  --line-d: rgba(241,238,230,.18);
  --bone-rgb: 241,238,230;
  --ink-rgb: 12,15,20;
  --ember-rgb: 242,162,12;
  --ember-focus: #B87B09;
  --ember-text: #916107;
  --on-ember: var(--ink);
}

/* Grass green on cool graphite */
html[data-palette="fern"] {
  --ink:    #101311;
  --ink-2:  #1C211D;
  --bone:   #EAEDE8;
  --paper:  #F4F7F2;
  --moss:   #3F4E3C;
  --ember:  #4C9E22;
  --stone:  #868C82;
  --stone-lit: #565C52;
  --line:   rgba(16,19,17,.15);
  --line-d: rgba(234,237,232,.18);
  --bone-rgb: 234,237,232;
  --ink-rgb: 16,19,17;
  --ember-rgb: 76,158,34;
  --ember-focus: #4C9E22;
  --ember-text: #397619;
  --on-ember: var(--ink);
}

/* Copper verdigris on green-black */
html[data-palette="verdigris"] {
  --ink:    #08160F;
  --ink-2:  #12221A;
  --bone:   #E6EEEA;
  --paper:  #F1F8F4;
  --moss:   #31544A;
  --ember:  #0FA08E;
  --stone:  #7E908A;
  --stone-lit: #4E605A;
  --line:   rgba(8,22,15,.15);
  --line-d: rgba(230,238,234,.18);
  --bone-rgb: 230,238,234;
  --ink-rgb: 8,22,15;
  --ember-rgb: 15,160,142;
  --ember-focus: #0E9A88;
  --ember-text: #0B7669;
  --on-ember: var(--ink);
}

/* Azure blue on prussian black */
html[data-palette="azurite"] {
  --ink:    #08111C;
  --ink-2:  #121D2B;
  --bone:   #E8EDF3;
  --paper:  #F2F6FB;
  --moss:   #33475C;
  --ember:  #1B64CC;
  --stone:  #84909C;
  --stone-lit: #546069;
  --line:   rgba(8,17,28,.15);
  --line-d: rgba(232,237,243,.18);
  --bone-rgb: 232,237,243;
  --ink-rgb: 8,17,28;
  --ember-rgb: 27,100,204;
  --ember-focus: #1B64CC;
  --ember-text: #1B64CC;
  --on-ember: var(--bone);
}

/* Glacier cyan on slate black */
html[data-palette="glacier"] {
  --ink:    #0A1316;
  --ink-2:  #152023;
  --bone:   #E7EFF1;
  --paper:  #F1F8FA;
  --moss:   #334C51;
  --ember:  #00B0D4;
  --stone:  #829296;
  --stone-lit: #525F63;
  --line:   rgba(10,19,22,.15);
  --line-d: rgba(231,239,241,.18);
  --bone-rgb: 231,239,241;
  --ink-rgb: 10,19,22;
  --ember-rgb: 0,176,212;
  --ember-focus: #009BBB;
  --ember-text: #00748C;
  --on-ember: var(--ink);
}

:root {
  /* Sulphur — locked in by the client, 2 Sep 2026. Acid yellow-green on an
     olive-tinted near-black, with a warm off-white paper. This is the site's
     palette, not a variant: the five alternatives below it are switcher
     options only and go with the switcher before launch. */
  --ink:    #101208;
  --ink-2:  #1C1F11;
  --bone:   #EDEDE0;
  --paper:  #F6F6EB;
  --moss:   #4B5233;
  --ember:  #B4BF16;
  --stone:  #8B8D78;
  --stone-lit: #5B5E48;
  --line:   rgba(16,18,8,.15);
  --line-d: rgba(237,237,224,.18);
  --bone-rgb: 237,237,224;
  --ink-rgb: 16,18,8;
  --ember-rgb: 180,191,22;

  /* Light grounds captured at the root, before any band re-points --bone.
     Light mode reads these; nothing else does. */
  --band-a: var(--paper);
  --band-b: var(--bone);

  /* Scout — locked in by the client, 1 Sep 2026. Scoutie Sans is a variable
     grotesque (200–800); Public Sans carries the labels in place of a
     monospace, and tabular figures come from font-variant-numeric rather than
     from the face. */
  --f-display: 'Scoutie Sans', system-ui, sans-serif;
  --f-ui: 'Inter Tight', system-ui, -apple-system, sans-serif;
  --f-mono: 'Public Sans', system-ui, sans-serif;
  --d-track: -.03em;
  --d-weight: 500;
  --d-weight-hero: 400;
  --d-lh: .9;
  --d-vary: normal;

  /* Sized from both axes, not just width. The hero headline is the only element on
     the site that can outgrow its own box, so it has to answer to viewport height —
     but it used to do that with a max-height step that took it from 152px to 77px
     across one pixel of viewport height. Every laptop lands just under that step and
     every desktop monitor just over it, so the same page looked like two different
     designs depending on the machine.

     min() makes the shorter axis the binding one continuously: on a tall screen the
     width term governs and nothing changes, on a laptop the height term takes over
     and the size falls off smoothly instead of halving. */
  /* Reduced twice on 2026-09-20, both times on client feedback that the headline
     read too large: 9.4vw -> 8vw -> 6.8vw, with the floor, the height term and the
     ceiling cut to match. The min() pairing and its reasoning above are untouched —
     only the four numbers moved, so tall-screen and laptop behaviour still agree.
     This token also sizes every inner page hero, since .phero__h carries .d--hero. */
  --fs-hero:  clamp(2.25rem, min(6.8vw, 11.2vh), 6.8rem);
  --fs-d1:    clamp(2.5rem, 6.2vw, 5.75rem);
  --fs-d2:    clamp(1.9rem, 3.6vw, 3.375rem);
  --fs-d3:    clamp(1.4rem, 2.1vw, 2rem);
  --fs-state: clamp(1.3rem, 2.55vw, 2.125rem);
  --fs-body:  1.0625rem;

  --gutter: clamp(20px, 4.4vw, 72px);
  --maxw: 1560px;
  --colgap: clamp(16px, 2vw, 34px);
  /* 76px until 2026-09-20. Raised with the mark, which went up 1.3x the same day:
     at 190px the trimmed artwork is 65.6px tall and the old bar could not contain
     it. 99px is that same 1.3x, so the mark keeps the clearance it had — it sat at
     66% of the bar's height before and still does. */
  --header-h: 99px;
  /* The bar once the page has scrolled past the top (2026-09-24). The nav type keeps
     its full client-approved size; only the bar's height and the mark come in, so the
     header costs a third less of the screen while reading. --hdr-now is whichever of
     the two applies — anything that sits UNDER the header (sticky sub-bars, the
     reading line) follows it, while page padding keeps --header-h, because at the top
     of a page the bar is always the full one. ui.js sets .is-hdr-compact on <html>. */
  --header-h-stuck: 66px;
  --hdr-now: var(--header-h);

  /* The house arrival curve. It is expo.out, and it is the same value
     Antinomy Studio, 14islands, darkroom.engineering and Studio Freight all
     ship as their primary token — arrived at independently, kept because it
     was already right. Everything that enters uses it. */
  --ease: cubic-bezier(.16,1,.3,1);
  /* The only other curve on the site. Entrances decelerate, exits accelerate;
     reusing an -out curve on an exit makes the element look yanked rather
     than dismissed. */
  --ease-exit: cubic-bezier(.3,0,.8,.15);

  /* Three duration tiers. Resist adding a fourth. */
  --mo-fast: 200ms;   /* chrome, hover, state */
  --mo-base: 600ms;   /* content entrances */
  --mo-slow: 1200ms;  /* photography only */
  --mo-stagger: 90ms;
  --mo-line-stagger: 60ms;
  --ember-focus: #7E860F;
  /* The accent as TEXT on paper. --ember is engineered as a ground, with
     --on-ember picking what reads against it; a vivid mid-luminance accent can
     never clear 4.5:1 as an 11px label on near-white, so accent text gets its
     own darkened value. Solved per palette by the same loop as --ember-focus,
     against a 4.5:1 target rather than 3:1.

     Solved against --bone, NOT --paper. Bone is the darker of the two light
     grounds, so a value that clears bone clears paper as well; solving against
     paper leaves every .g-light band short of AA by about 0.2. */
  --ember-text: #676D0D;
  --on-ember: var(--ink);

  /* ---------------------------------------------------------------
     BOOTSTRAP BRIDGE

     Bootstrap 5.2 is built on CSS custom properties, so the cheapest way to
     put its components on brand is to feed it our tokens rather than to
     override its rules. Everything below is a Bootstrap variable, not one of
     ours: set these once and .btn, .table, .accordion, .nav-tabs, .offcanvas
     and .form-control all come out in WYLD's palette and type with no
     component-level CSS at all.

     Because the palette switcher re-points --ember and friends on <html>,
     these follow a palette change automatically.
     --------------------------------------------------------------- */
  --bs-body-font-family: var(--f-ui);
  --bs-body-font-size: var(--fs-body);
  --bs-body-line-height: 1.65;
  --bs-body-color: var(--ink);
  --bs-body-bg: var(--bone);
  --bs-emphasis-color: var(--ink);
  --bs-secondary-color: var(--stone);
  --bs-tertiary-color: var(--stone);
  --bs-border-color: var(--line);
  --bs-border-color-translucent: var(--line);
  --bs-link-color: var(--ink);
  --bs-link-hover-color: var(--ember-text);
  --bs-font-monospace: var(--f-mono);

  /* The brief caps border-radius at 3px, so every rounded Bootstrap corner is
     squared at the source instead of being unrounded component by component. */
  --bs-border-radius: 0;
  --bs-border-radius-sm: 0;
  --bs-border-radius-lg: 0;
  --bs-border-radius-xl: 0;
  --bs-border-radius-2xl: 0;
  --bs-border-radius-pill: 0;

  --bs-primary: var(--ember);
  --bs-primary-rgb: var(--ember-rgb);
  --bs-dark: var(--ink);
  --bs-light: var(--bone);

}

/* Two container overrides. Bootstrap's widest tier stops at 1320px and this
   design runs to 1560px; and Bootstrap pads a container by half its gutter
   each side, so one gutter of twice --gutter reproduces the design's gutter
   exactly. The gutter must be set HERE rather than on :root — Bootstrap's own
   .container* rule sets --bs-gutter-x on the element, and that beats any
   inherited value. */
.container-xxl {
  max-width: var(--maxw);
  --bs-gutter-x: calc(var(--gutter) * 2);
}

/* Reboot sets a heading font stack, weight and margin we do not want; the
   display scale lives on .d and the mono scale on .m. */
h1, h2, h3, h4, h5, h6 { font-family: inherit; font-weight: inherit; line-height: inherit; margin: 0; }

/* Bootstrap's focus ring is a box-shadow; ours is an outline in the darkened
   accent, and it has to win on the components too. */
.btn:focus-visible, .form-control:focus-visible, .accordion-button:focus-visible,
.nav-link:focus-visible, .btn-close:focus-visible {
  box-shadow: none;
  outline: 2px solid var(--ember-focus, var(--ember));
  outline-offset: 3px;
}

/* ---------- type sets (demo switcher) ----------
   Five directions. Each names a display, a UI and a mono face, plus the
   tracking, weight and line-height that face actually wants — a Bodoni set at
   a Fraunces's tracking looks broken, and vice versa. Delete these blocks with
   the switcher. */
/* A grotesque display face sets tighter than a serif at the same size, so the
   hero measure comes in and the label tracking opens up to compensate for the
   smaller counters. */
.hero__h { max-width: 13ch; }
.m { letter-spacing: .15em; }

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--ink);
  color: var(--bone);
  font-family: var(--f-ui);
  font-size: var(--fs-body);
  line-height: 1.65;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  /* clip, not hidden: hidden turns this into a scroll container and silently
     breaks position:sticky in its descendants, which the pinned track and the
     expedition rail both depend on. */
  overflow-x: clip;
}
/* The full-bleed article figures overshoot by the scrollbar's width because
   every viewport unit counts it. Clipped at the root so the document never
   gains a horizontal scrollbar. */
html { overflow-x: clip; }
img, video { display: block; max-width: 100%; height: auto; }
figure { margin: 0; }
h1, h2, h3, h4, p, ul, ol, dl, dd, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; text-transform: inherit; letter-spacing: inherit; text-align: inherit; }
input { font: inherit; }
table { border-collapse: collapse; width: 100%; }
::selection { background: var(--ember); color: var(--bone); }

:focus-visible {
  /* The darkened twin of the accent: the ring has to clear 3:1 on both grounds,
     which several of the louder accents cannot do on paper. */
  outline: 2px solid var(--ember-focus, var(--ember));
  outline-offset: 3px;
}

/* ---------- display type ---------- */
.d {
  font-family: var(--f-display);
  font-optical-sizing: auto;
  font-weight: var(--d-weight, 400);
  font-variation-settings: var(--d-vary, 'SOFT' 0, 'WONK' 0);
  line-height: var(--d-lh, .92);
  letter-spacing: var(--d-track, -.025em);
  text-wrap: balance;
}
.d--hero { font-size: var(--fs-hero); letter-spacing: calc(var(--d-track, -.025em) - .01em); font-weight: var(--d-weight-hero, 300); }
.d--1 { font-size: var(--fs-d1); letter-spacing: -.03em; }
.d--2 { font-size: var(--fs-d2); }
.d--3 { font-size: var(--fs-d3); line-height: 1.04; letter-spacing: -.015em; }
.d--it { font-style: italic; }

/* ---------- mono furniture ---------- */
.m {
  font-family: var(--f-mono);
  font-size: 11px;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: .14em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
.m--500 { font-weight: 500; }
.m--9 { font-size: 9px; letter-spacing: .16em; }
.m--12 { font-size: 12px; }
.m--stone { color: var(--stone); }
.m--moss { color: var(--moss); }

.eyebrow { display: flex; align-items: baseline; gap: .9em; }
.eyebrow__n { color: var(--stone); }

.body { max-width: 62ch; }
.body + .body { margin-top: 1.1em; }
.lead { font-size: 1.1875rem; line-height: 1.6; }
.tnum { font-variant-numeric: tabular-nums; }

/* ---------- grounds ---------- */
.g-dark { background: var(--ink); color: var(--bone); }
.g-dark-2 { background: var(--ink-2); color: var(--bone); }
.g-light { background: var(--bone); color: var(--ink); }
/* Meta text needs a darker grey on paper than on ink: it is set at 11px, where
   AA wants 4.5:1, and the value that passes on ink measures 2.8:1 on paper.
   One global value cannot serve both grounds, so the ground picks. */
.g-light, .g-paper { --stone: var(--stone-lit); }
/* On ink the undarkened accent measures 7:1 or better, so it needs no
   substitute — the darkened value is a light-ground concern only. */
html:not([data-mode="light"]) .g-dark,
html:not([data-mode="light"]) .g-dark-2,
html:not([data-mode="light"]) .ftr,
.pin__vp, .hero, .phero,
/* The header sits on photography until it sticks, and the drawer is always ink. */
.hdr:not(.is-light), .drawer { --ember-text: var(--ember); }
.g-paper { background: var(--paper); color: var(--ink); }
.g-dark .m--stone { color: rgba(var(--bone-rgb), .52); }
.g-dark a:hover { color: var(--bone); }
.g-light a:hover, .g-paper a:hover { color: var(--ink); }

/* ---------------------------------------------------------------
   LIGHT MODE — visitor-controlled, persisted.

   The site's narrative is dark to light. Light mode does not delete that
   narrative; it lifts the *typographic* dark grounds onto paper while leaving
   the *photographic* ones dark, because type set over a photograph has to stay
   light whatever the mode. So the hero, the pinned track and the ground-change
   band keep their cinema, and every reading surface becomes paper.

   Mechanically this is a role swap done locally: inside a dark band, --bone
   stops meaning "ink on dark" and starts meaning ink. Every descendant that
   drew itself in --bone or --line-d follows automatically, which is why there
   are so few rules here.
   --------------------------------------------------------------- */

html[data-mode="light"] .g-dark,
html[data-mode="light"] .g-dark-2,
html[data-mode="light"] .ftr {
  --bone: var(--ink);
  --bone-rgb: var(--ink-rgb);
  --line-d: var(--line);
  background: var(--band-a);
  color: var(--ink);
}
/* These must NOT read var(--bone): the rule above has already re-pointed it at
   --ink on these very elements, so reading it back would paint an ink ground
   under ink text. --band-a / --band-b were captured at the root and are
   immune to the swap. */
html[data-mode="light"] .g-dark-2 { background: var(--band-b); }
html[data-mode="light"] .ftr { background: var(--band-b); }
/* Alternate the two light values so consecutive bands still read as separate
   sections rather than one undifferentiated sheet. */
html[data-mode="light"] .g-dark + .g-dark { background: var(--band-b); }
html[data-mode="light"] .g-dark .grain::after,
html[data-mode="light"] .g-dark-2 .grain::after { opacity: .03; }
/* Photography keeps its own ground: a caption over a frame stays bone. The day
   timeline joins them — it lays a photograph and a dark scrim of its own under
   every line of it, so flipping it to a light band would put ink on that scrim. */
html[data-mode="light"] .pin__vp,
html[data-mode="light"] .panel--data,
html[data-mode="light"] .day-sec {
  --bone: #F1EDE4; --bone-rgb: 241,237,228;
  --line-d: rgba(241,237,228,.16);
  background: var(--ink); color: #F1EDE4;
}
html[data-mode="light"] .pin__vp .grain::after { opacity: .085; }

/* Meta text inside a flipped band. The dark-ground rules draw it as translucent
   bone, which on a lifted band is ink at half strength — about 3.5:1, and this
   is 11px type. On a light ground the right colour is the solid --stone token,
   which is what every other light band already uses. */
/* A flipped band is a light ground, so it takes the light-ground grey. */
html[data-mode="light"] .g-dark,
html[data-mode="light"] .g-dark-2,
html[data-mode="light"] .ftr { --stone: var(--stone-lit); }
html[data-mode="light"] .pin__vp { --stone: #8B887E; }

html[data-mode="light"] .g-dark .m--stone,
html[data-mode="light"] .g-dark-2 .m--stone,
html[data-mode="light"] .ftr .m--stone,
html[data-mode="light"] .g-dark .dl-m dt,
html[data-mode="light"] .g-dark-2 .dl-m dt { color: var(--stone); }
/* The pinned track keeps its dark ground, so it keeps the dark-ground values. */
html[data-mode="light"] .pin__vp .m--stone,
html[data-mode="light"] .pin__vp .dl-m dt { color: rgba(241,237,228,.52); }
/* The day timeline is on that same list three blocks up — a ground that stays dark
   because it carries a photograph — but it was missed here. The .g-dark rule above
   matches it (the section carries both classes) and was repainting its meta text in
   the light-ground grey: dark olive on a dark photograph, for the section dek and
   both "05:10 — 10:30" labels. It takes a higher value than the pinned track
   because its scrim is lighter; see .day__bg::after in pages.css. */
html[data-mode="light"] .day-sec .m--stone { color: rgba(241,237,228,.75); }

/* ---------- layout ---------- */
/* .container-xxl replaces .wrap; .row/.col-* replace .g12. */
.sec { position: relative; }
.sec--xl { padding-block: clamp(96px, 15vh, 190px); }
.sec--lg { padding-block: clamp(76px, 11vh, 148px); }
.sec--md { padding-block: clamp(56px, 8vh, 104px); }
.sec--sm { padding-block: clamp(40px, 5vh, 68px); }
.rule { height: 1px; background: var(--line); border: 0; margin: 0; }
.g-dark .rule, .rule--d { background: var(--line-d); }

/* ---------- reveals -------------------------------------------------

   THE REST STATE IS THE VISIBLE STATE. Every rule that hides an element
   lives inside the guard below, so the page is fully readable whenever
   motion is unavailable or unwanted: no script, no support for the
   `scripting` media feature, a thrown observer, or a visitor who has asked
   for reduced motion. The old system inverted this — content started at
   opacity 0 and depended on JS to reveal it, which is why motion.js carried
   a defensive settle() timeout to un-strand anything the loop missed. That
   whole failure mode is gone: nothing here can hide content permanently.

   Durations are three tiers and no more. Chrome and hover at --mo-fast,
   ordinary content entrances at --mo-base, photography at --mo-slow. The
   long tier is deliberately reserved for images: on a dark ground under a
   large photograph a 300ms fade reads as a page-load stutter, and a 1.2s
   one reads as an exposure.
   -------------------------------------------------------------------- */

@media (scripting: enabled) and (prefers-reduced-motion: no-preference) {

  /* Travel is a percentage of the element's own height, so a caption moves a
     few pixels and a headline moves tens — proportional without a single
     media query. Capped, because 40% of a tall block is a leap, not a rise. */
  .rv:not(.is-in) { opacity: 0; transform: translateY(min(var(--rv-y, 40%), 44px)); }
  .rv.rv {
    transition: opacity var(--mo-base) var(--ease), transform var(--mo-base) var(--ease);
    transition-delay: calc(var(--i, 0) * var(--mo-stagger));
  }

  /* Both ends must be an inset(): clip-path has no interpolation between a basic
     shape and `none`, so a finished state that simply omits the property makes the
     mask jump to open on the first frame and the wipe never runs. Every element
     carrying .rv-img already clips with overflow:hidden, so the resting inset(0)
     changes nothing about how it paints. */
  .rv-img { clip-path: inset(0 0 0 0); }
  .rv-img:not(.is-in) { clip-path: inset(0 0 100% 0); }
  .rv-img.rv-img {
    transition: clip-path var(--mo-slow) var(--ease);
    transition-delay: calc(var(--i, 0) * var(--mo-stagger));
  }
  /* The scale settle runs longer than the wipe that reveals it, so the frame
     is fully open while the picture is still coming to rest. */
  .rv-img:not(.is-in) > img { transform: scale(1.06); }
  .rv-img > img {
    transition: transform calc(var(--mo-slow) + 240ms) var(--ease);
    transition-delay: calc(var(--i, 0) * var(--mo-stagger));
  }

  /* Horizontal companion to .rv-img. Where that one wipes a frame open upward,
     this uncovers it from one side, and the direction lives in the markup as
     --wipe so a frame on either half of a split section can share the primitive.
     For photography the mask is the whole device: nothing fades, nothing scales
     from the outside, the picture is simply not covered any more. */
  .rv-wipe { clip-path: inset(0 0 0 0); }
  .rv-wipe:not(.is-in) { clip-path: inset(0 0 0 var(--wipe, 100%)); }
  .rv-wipe.rv-wipe {
    transition: clip-path var(--mo-slow) var(--ease);
    transition-delay: calc(var(--i, 0) * var(--mo-stagger));
  }

  .ln > i { transform: translateY(105%); transition: transform var(--mo-slow) var(--ease); transition-delay: calc(var(--l, 0) * var(--mo-line-stagger)); }
  .is-in .ln > i { transform: none; }

  /* Parallax layers. motion.js writes --px; the composition lives here, so a
     layer stays free to carry other transforms of its own. */
  [data-parallax] { transform: translate3d(0, var(--px, 0px), 0); }

  /* Opposite-side convergence. Direction is per element and lives in the
     markup as --cv-x, so one primitive serves both halves and a breakpoint
     turns the whole device off by zeroing the variable — no JS branch, no
     second rule. Apple disables the same device on tall viewports exactly
     this way. */
  .cv:not(.is-in) { opacity: 0; transform: translate3d(var(--cv-x, 0), var(--cv-y, 0), 0); }
  /* The travel runs on the long tier and the fade on the base tier, so the
     element is fully opaque well before it stops moving — it settles into
     place rather than arriving and stopping. clip-path is in the list so this
     can be combined with .rv-img on one element: without it the shorthand
     here would replace .rv-img's transition entirely and kill the wipe. */
  .cv.cv {
    transition: opacity var(--mo-base) var(--ease),
                transform var(--mo-slow) var(--ease),
                clip-path var(--mo-slow) var(--ease);
    transition-delay: calc(var(--i, 0) * var(--mo-stagger));
  }
}

/* ---------- smooth scroll ----------
   Lenis's own required rules. It scrolls the document for real rather than
   transforming a wrapper, so every scroll-position read in motion.js keeps
   working unchanged and anchors, find-in-page and the scrollbar all behave.
   These classes only exist once it has initialised, which it does not do
   under reduced motion. */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
/* Only for a genuinely vertical inner panel that opts out of Lenis. The horizontal
   rails no longer carry the attribute: paired with it, this rule was what stopped the
   page scrolling when the cursor sat over one. */
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }
.lenis.lenis-smooth iframe { pointer-events: none; }

/* Structural, and needed in every mode: without the clip there is nothing for
   the line to rise out of, and the negative margin gives descenders their
   room back so a 'g' is not shaved by the mask.

   `font-style: inherit` is load-bearing, not tidiness. splitHeading() builds
   each line as an <i>, which every browser renders italic by default, so
   without this every split headline on the site silently turns italic. */
.ln { display: block; overflow: hidden; padding-bottom: .16em; margin-bottom: -.16em; }
.ln > i { display: block; font-style: inherit; }

/* An element already on screen at first paint must not be caught mid-reveal.
   motion.js adds this class, forces a reflow and removes it, so the element
   lands on its finished state with no transition at all. */
.rv.is-instant, .rv-img.is-instant, .cv.is-instant, .rv-wipe.is-instant,
.is-instant .ln > i, .is-instant > img,
.is-instant .spx__frames { transition: none !important; }

/* Film grain over photography, weighted to the ground it sits on: nearly clean
   on bone, noticeably present on ink. Falling light means a higher ISO means
   more grain — a photographer reads the site's own exposure that way. */
.grain::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  mix-blend-mode: overlay; opacity: .035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

.g-light .grain::after, .g-paper .grain::after { opacity: .025; }
.g-dark .grain::after, .g-dark-2 .grain::after, .hero .grain::after { opacity: .075; }
.pin__vp .grain::after { opacity: .085; }

.skip {
  position: fixed; top: 8px; left: 8px; z-index: 900;
  padding: 10px 14px; background: var(--bone); color: var(--ink);
  transform: translateY(-160%);
}
.skip:focus { transform: none; }

/* .visually-hidden replaces .sr. */

/* Reduced motion needs no rules of its own any more. Every hiding rule is
   already inside a `prefers-reduced-motion: no-preference` guard, so asking
   for reduced motion simply drops that block and leaves the finished page —
   rather than racing a transition to zero and hoping nothing is stranded. */

/* See --header-h-stuck. On <html>, not :root, so it outranks the :root default. */
html.is-hdr-compact { --hdr-now: var(--header-h-stuck); }
