/*
Theme Name: Elevate C2
Description: Landing page theme. Pixel-faithful port of the C2 "A arch" design exploration. Layout is fixed in code; copy is editable under Landing Copy.
Version: 1.0.0
Text Domain: elevate-c2
*/
/* Elevate Services Group — C2 "A arch" pitch page.
   Vanilla CSS, one stylesheet, tokens at :root. No build step.

   Motion policy for THIS page. House style §1 normally forbids bounce and
   overshoot springs and sudden entrances; Stephen has waived that clause for
   this pitch page only. What is NOT waived and is still enforced below:
   nothing loops unattended beside body copy, there is no scroll hijack, no
   autoplay sound, type never runs over the object, prefers-reduced-motion is
   honoured, and there is always a visible way to stop it. */

/* ---------------------------------------------------------------- fonts */

/* INTER TIGHT, the heading face, vendored here rather than fetched from Google:
   the site stays static and self-contained, the way Three.js and every other
   dependency in this project is. One variable file per subset, 400-700 on the
   weight axis, so the whole ramp is 45KB (latin) and the accented set only
   loads for a page that needs it. SIL Open Font License -- free commercially,
   no comping restriction, nothing for the client to buy. */
@font-face {
  font-family: "Inter Tight";
  src: url("assets/fonts/InterTight-Variable-latin.woff2") format("woff2");
  font-weight: 400 700; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter Tight";
  src: url("assets/fonts/InterTight-Variable-latin-ext.woff2") format("woff2");
  font-weight: 400 700; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Inter Local";
  src: url("assets/fonts/Inter-Regular.ttf") format("truetype");
  font-weight: 400; font-style: normal; font-display: swap;
}

/* --------------------------------------------------------------- tokens */

:root {
  /* Palette. Locked, measured off the Elevate dev site 2026-09-17. */
  --navy: #0F1C3F;
  --deep: #081F5C;
  --royal: #334EAC;
  --steel: #7BA7BC;
  --pale: #D0E3FF;
  --ice: #E7EEFB;
  --muted: #CCDDE5;
  --body: #6A7793;
  /* Brand body grey measures 4.49:1 on white, which misses AA by a hair. This
     is the nearest darker value in the same hue and measures 5.35:1; the brand
     grey stays as the token for anything that is not body text on white. */
  --body-ink: #5E6B87;
  --white: #FFFFFF;
  /* One accent, on one thing: the travelling light on the object. Never here. */
  --amber: #E9B55A;

  /* Type. Display ramp carries negative tracking that scales with size, the
     way Linear's does; the eyebrow is the one positive-tracked role. */
  --t-display:    clamp(36px, 3.8vw, 55px);
  --t-giant:      clamp(54px, 7.7vw, 112px);
  --t-statement:  clamp(30px, 3.5vw, 54px);
  --t-lead:       clamp(17px, 1.15vw, 19px);
  --t-body:       17px;
  --t-label:      13px;

  /* Spacing, 8-based with hallmark's wide steps for a page that breathes. */
  --s-1: 8px;  --s-2: 12px; --s-3: 16px; --s-4: 24px;
  --s-5: 32px; --s-6: 48px; --s-7: 72px; --s-8: 96px; --s-9: 144px;

  --nav-h: 84px;
  --gutter: clamp(24px, 4.4vw, 76px);

  /* Motion. One set of curves, three duration bands, one reveal spec. */
  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in:     cubic-bezier(0.7, 0, 0.84, 0);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --dur-micro: 130ms;
  --dur-short: 200ms;
  --dur-long:  420ms;
  /* 680ms plus a 70ms stagger made the reader wait for six lines of copy.
     240ms with a 35ms stagger reads as "it arrived", not "it is arriving". */
  --dur-reveal: 260ms;
  --stagger: 35ms;

  /* Elevation. Two layers, always: a tight contact shadow and a wide ambient
     one. A single blurred box-shadow is what flat vector UI looks like when it
     is pretending to have depth. */
  /* Neutral, not brand-tinted. A navy blur is invisible against the navy acts
     and reads as a coloured halo rather than as light being blocked; elevation
     is an absence of light, so it is grey. */
  --lift-1: 0 1px 2px rgba(0, 0, 0, 0.08), 0 2px 6px rgba(0, 0, 0, 0.05);
  --lift-2: 0 1px 2px rgba(0, 0, 0, 0.10), 0 10px 28px rgba(0, 0, 0, 0.12);
  --lift-3: 0 2px 4px rgba(0, 0, 0, 0.13), 0 18px 44px rgba(0, 0, 0, 0.17);

  --sans: "Inter", "Inter Local", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  /* Headings only. Safiro's @font-face stays above, unused, until the choice is
     settled -- the files are on the shelf either way. */
  --head: "Inter Tight", "Inter", "Inter Local", system-ui, sans-serif;

  /* The canvas sits ABOVE each section's background so the object composites
     onto navy or white, and BELOW every block of copy so type always wins. */
  --z-section: 1;
  --z-stage: 2;
  --z-content: 3;
  --z-nav: 30;
}

/* ----------------------------------------------------------------- base */

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

html { scroll-behavior: smooth; background: var(--white); }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  color: var(--body-ink);
  font-family: var(--sans);
  font-size: var(--t-body);
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* clip, not hidden: overflow-x hidden on body makes it a scroll container and
     silently breaks every position: sticky inside it. */
  overflow-x: clip;
}

h1, h2, h3 { margin: 0; font-family: var(--head); font-weight: 600; }
p, ul { margin: 0; }
ul, ol { list-style: none; padding: 0; }

a { color: inherit; text-decoration: none; }
a:focus-visible, button:focus-visible {
  outline: 2px solid var(--royal);
  outline-offset: 4px;
  border-radius: 4px;
}
.sec--dark a:focus-visible, .sec--dark button:focus-visible { outline-color: var(--pale); }

img { display: block; max-width: 100%; height: auto; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.skip {
  position: fixed; top: -100px; left: var(--gutter); z-index: 40;
  background: var(--white); color: var(--navy);
  padding: var(--s-2) var(--s-4); border-radius: 999px; font-weight: 500;
}
.skip:focus { top: var(--s-2); }

/* ------------------------------------------------------------------ type */

.display {
  font-size: var(--t-display);
  line-height: 1.02;
  letter-spacing: -0.015em;
  /* The line breaks are authored, so the measure is set by the longest line
     rather than by a wrap. */
  max-width: none;
}
.statement {
  font-size: var(--t-statement);
  line-height: 1.06;
  letter-spacing: -0.012em;
  max-width: 19ch;
}
.statement--centre { max-width: 26ch; margin-inline: auto; text-align: center; }

.eyebrow {
  font-family: var(--head);
  font-weight: 500;
  font-size: var(--t-label);
  letter-spacing: 0.185em;
  text-transform: uppercase;
}
.lede {
  font-size: var(--t-lead);
  line-height: 1.66;
  max-width: 48ch;
}
.lede--centre { max-width: 62ch; margin-inline: auto; text-align: center; }

/* The hero headline rises out of a mask, line by line. */
.line { display: block; overflow: hidden; padding-bottom: 0.04em; }
.line > span { display: block; }

/* --------------------------------------------------------------- buttons */

/* PRESSABLE THINGS ARE PHYSICAL. A flat colour swap is what a vector button
   does; this one has a lit top edge, a shadow that answers the lift, and a press
   that tips the far edge away from you. Transform and opacity only, 130ms, and
   only where there is a real pointer. */
/* THE PILL. Stephen's reference (2026-09-17, evening): a frosted glass
   capsule on a dark ground -- top-lit, a hairline rim, type with a faint
   vertical gradient, and nothing else. "Let's go simple." So: one object.
   Light frosted glass on the navy pages; the same object in dark glass on
   the white hero, so the two read as one family. No lift, no glare, no
   layered shadows. Press is a 3% shrink (Emil), 140 ms, ease-out. */
.pill {
  position: relative; isolation: isolate;
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  border-radius: 999px;
  padding: 14px 28px;
  font-size: 16px; font-weight: 600; letter-spacing: -0.01em;
  white-space: nowrap;
  /* dark glass, for white grounds */
  background: linear-gradient(180deg, rgba(38, 56, 108, 0.97) 0%, rgba(15, 28, 63, 0.97) 100%);
  color: #fff;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    inset 0 0 0 1px rgba(255, 255, 255, 0.08),
    0 1px 2px rgba(8, 31, 92, 0.12);
  transition: transform 140ms cubic-bezier(0.23, 1, 0.32, 1),
              background-color 140ms ease, box-shadow 140ms ease;
}
/* Solid white type. (A gradient clipped to the type was tried and, applied
   to the button itself, replaced the fill; not worth a wrapper span.) */
.pill { text-shadow: 0 1px 0 rgba(0, 0, 0, 0.12); }
.pill--sm { padding: 10px 20px; font-size: 14px; }
.pill--lg { padding: 17px 36px; font-size: 17px; }
@media (hover: hover) and (pointer: fine) {
  .pill:hover {
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.30),
      inset 0 0 0 1px rgba(255, 255, 255, 0.12),
      0 1px 2px rgba(8, 31, 92, 0.12);
  }
}
.pill:active { transform: scale(0.97); }

/* Light frosted glass on navy: the reference itself. (Not the nav's -- that
   one stays the dark glass, so it reads as the same object as the hero's and
   takes no attention on the strip.) */
.sec--dark .pill {
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.20) 0%, rgba(255, 255, 255, 0.07) 100%),
    rgba(120, 150, 210, 0.16);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
          backdrop-filter: blur(16px) saturate(1.4);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.38),
    inset 0 0 0 1px rgba(255, 255, 255, 0.14),
    0 10px 30px -18px rgba(0, 0, 0, 0.6);
}
@media (hover: hover) and (pointer: fine) {
  .sec--dark .pill:hover {
    background:
      linear-gradient(180deg, rgba(255, 255, 255, 0.26) 0%, rgba(255, 255, 255, 0.10) 100%),
      rgba(120, 150, 210, 0.20);
  }
}
/* The nav's call to action is the same frosted glass as the rest -- the
   amber "was taking too much attention". */

.text-link {
  display: inline-flex; align-items: center; gap: 10px;
  font-size: 16px; font-weight: 500;
  padding-bottom: 3px;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1px; background-repeat: no-repeat; background-position: 0 100%;
  transition: background-size var(--dur-short) var(--ease-out);
}
.text-link .icon {
  width: 20px; height: 20px;
  transition: transform var(--dur-short) var(--ease-out);
}
.text-link:active { transform: scale(0.97); }
.text-link { transition: background-size var(--dur-short) var(--ease-out),
                         transform var(--dur-micro) var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .text-link:hover { background-size: 100% 1px; }
  .text-link:hover .icon { transform: translateX(4px); }
}

.actions { display: flex; align-items: center; gap: var(--s-5); flex-wrap: wrap; }
.actions--centre { justify-content: center; }

.ghost {
  font: inherit; font-size: 13px; font-weight: 500;
  /* The hero is white, so this control lives on white. */
  color: var(--body-ink);
  background: rgba(255, 255, 255, 0.72);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(15, 28, 63, 0.16);
  border-radius: 999px;
  padding: 11px 20px;
  cursor: pointer;
  transition: border-color var(--dur-short) var(--ease-out),
              background var(--dur-short) var(--ease-out),
              transform var(--dur-micro) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .ghost:hover { border-color: rgba(15, 28, 63, 0.34); background: var(--white); box-shadow: var(--lift-1); }
}
.ghost:active {
  transform: perspective(500px) rotateX(6deg) scale(0.966);
  transition-duration: 90ms;
}
.ghost[aria-pressed="true"] { background: var(--navy); color: var(--white); border-color: var(--navy); }

/* ------------------------------------------------------------------- nav */

.nav {
  position: fixed; inset-inline: 0; top: 0; z-index: var(--z-nav);
  height: var(--nav-h);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-5);
  padding-inline: var(--gutter);
  /* One bounded translucent surface with a constant blur. The object passes
     under it and you can feel that it is there without ever reading through it
     -- which is the only place on this page transparency earns its keep. */
  background: rgba(8, 31, 92, 0.86);
  -webkit-backdrop-filter: blur(14px) saturate(1.25);
  backdrop-filter: blur(14px) saturate(1.25);
  box-shadow: inset 0 -1px 0 rgba(208, 227, 255, 0.14), 0 1px 12px rgba(0, 0, 0, 0.16);
  transition: background var(--dur-long) var(--ease-in-out),
              box-shadow var(--dur-long) var(--ease-in-out),
              transform var(--dur-long) var(--ease-out),
              opacity var(--dur-long) var(--ease-out);
  color: var(--white);
}
.nav.is-light {
  background: rgba(255, 255, 255, 0.82);
  box-shadow: inset 0 -1px 0 rgba(15, 28, 63, 0.10), 0 1px 12px rgba(0, 0, 0, 0.07);
  color: var(--navy);
}
@supports not (backdrop-filter: blur(4px)) {
  .nav { background: var(--deep); }
  .nav.is-light { background: var(--white); }
}
.wordmark {
  display: inline-flex; flex-direction: column; gap: 6px;
  --wm-ink: var(--white);
  --wm-a: #8FB3C4;              /* the sculpture's palest steel */
}
.nav.is-light .wordmark { --wm-ink: var(--navy); --wm-a: #5C8AA0; }
/* ELEVATE, cut from the top 46% of the artwork (rows 3-143 of 308). */
.wordmark__mark {
  position: relative; display: block; width: 178px; height: 24px;
  background: var(--wm-ink);
  -webkit-mask: url("assets/elevate-wordmark.png") 0 0 / 100% auto no-repeat;
          mask: url("assets/elevate-wordmark.png") 0 0 / 100% auto no-repeat;
  transition: background-color var(--dur-long) var(--ease-in-out);
}
/* The A and its dot: columns 634-783 of 1100, measured off the file. */
.wordmark__a {
  position: absolute; inset: 0;
  background: var(--wm-a);
  -webkit-mask: url("assets/elevate-wordmark.png") 0 0 / 100% auto no-repeat;
          mask: url("assets/elevate-wordmark.png") 0 0 / 100% auto no-repeat;
  clip-path: inset(0 28.8% 0 57.6%);
}
.wordmark__sub {
  display: block; padding-left: 1px;
  font: 600 10.5px/1 var(--sans);
  letter-spacing: 0.34em; text-transform: uppercase;
  color: var(--wm-ink);
  transition: color var(--dur-long) var(--ease-in-out);
}
.nav nav { display: flex; align-items: center; gap: clamp(14px, 1.9vw, 32px); }
.nav nav a { font-size: 15px; transition: color var(--dur-short) var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .nav nav a:not(.pill):hover { color: var(--steel); }
  .nav.is-light nav a:not(.pill):hover { color: var(--royal); }
}

/* Nav enters once, on load. */
.nav { opacity: 0; transform: translateY(-10px); }
body.is-ready .nav { opacity: 1; transform: none; }

/* ----------------------------------------------------------------- stage */

/* One fixed canvas for the whole page. It never takes pointer events itself;
   main.js adds a hit region only over the column the object occupies, so text
   selection and links always win. */
.stage {
  position: fixed; inset: 0; z-index: var(--z-stage);
  pointer-events: none;
}
/* Both of these belong to the narrow build and are switched on there. The
   hero's own still stands in for the stage; each word's line stands in for the
   service list the object used to pair it with. */
.hero-still { display: none; }
.word-line { display: none; }
.stage canvas { display: block; width: 100%; height: 100%; }
/* Stephen: "ALL CAPS LETTER SPACED above each pillar as the ball bounces onto
   it, revealed like that heading." left/top come from the runtime each frame;
   the transition is on the reveal only. */
.stage-labels { position: absolute; inset: 0; pointer-events: none; }
.stage-labels span {
  position: absolute; left: 0; top: 0;
  font: 600 12px/1 var(--sans); letter-spacing: 0.32em; text-transform: uppercase;
  color: var(--pale); white-space: nowrap;
  opacity: 0;
  transform: translate(-50%, -100%) translateY(10px);
  transition: opacity 320ms var(--ease-out), transform 320ms var(--ease-out);
}
.stage-labels span.is-on { opacity: 1; transform: translate(-50%, -100%); }
.stage .poster {
  position: absolute;
  top: 50%; left: 72%;
  translate: -50% -50%;
  width: auto; height: min(74vh, 660px);
  opacity: 1;
  transition: opacity var(--dur-long) var(--ease-out);
}
.stage.is-live .poster { opacity: 0; }
.stage .grab {
  position: absolute; inset: 0;
  pointer-events: auto;
  touch-action: pan-y;
  cursor: grab;
}
.stage .grab.is-dragging { cursor: grabbing; }

/* --------------------------------------------------------------- sections */

.sec {
  position: relative; z-index: var(--z-section);
  padding-inline: var(--gutter);
}
.sec-inner { max-width: 1440px; margin-inline: auto; width: 100%; }
/* Copy blocks take pointer events; everything else lets them through to the
   object behind, so a drag started on empty ground still turns the sculpture. */
.sec > * { pointer-events: none; }
.copy, .four-copy, .act-copy, .close-inner, .nav, .foot, a, button { pointer-events: auto; }
/* Copy rides above the canvas. */
.copy, .four-copy, .act-copy, .close-inner, .hero-foot {
  position: relative; z-index: var(--z-content);
}

/* THE BACKDROP. One fixed pair of layers under everything, and the navy one's
   opacity is scrubbed straight from scroll by main.js. The page used to paint a
   colour per section and fade the CANVAS out by 72% across each seam so the
   object would not cross a hard boundary -- which dissolved the sculpture at
   exactly the moment its construction mattered most. There is no boundary to
   cross now, so the object stays solid the whole way down. */
.backdrop { position: fixed; inset: 0; z-index: 0; background: var(--white); }
.backdrop__navy {
  position: absolute; inset: 0;
  background: var(--deep);
  opacity: 0;
  will-change: opacity;
}
.backdrop__glow {
  position: absolute; inset: 0;
  /* The A2 comp's ground: a broad royal pool low in the frame, a darker
     vignette at the edges. Under every navy act, scrubbed with the navy. */
  background:
    radial-gradient(ellipse 62% 58% at 64% 70%, rgba(51, 78, 172, 0.62) 0%, rgba(51, 78, 172, 0.22) 42%, rgba(8, 31, 92, 0) 72%),
    radial-gradient(ellipse 120% 105% at 50% 45%, rgba(8, 31, 92, 0) 52%, rgba(3, 12, 42, 0.62) 100%);
  opacity: 0;
  will-change: opacity;
}
/* A faint cool wash pooling at the foot of the white scene, and the ground the
   A stands on. Stephen: "there's a very faint blue glow at the base, and then
   the shadow of the A casting onto that." */
.backdrop__floor {
  position: absolute; inset-inline: 0; bottom: 0; height: 46vh;
  background:
    radial-gradient(120% 100% at 50% 118%, rgba(123, 167, 188, 0.30), rgba(123, 167, 188, 0) 68%),
    linear-gradient(rgba(231, 238, 251, 0), rgba(231, 238, 251, 0.85));
  will-change: opacity;
}

.sec--dark { color: var(--pale); }
.sec--dark h1, .sec--dark h2 { color: var(--white); }
.sec--dark .eyebrow { color: var(--steel); }
.sec--dark .lede { color: var(--pale); }

.sec--dark .lede, .sec--dark .four-head { color: var(--pale); }

.sec--light { color: var(--body-ink); }
.sec--light h1, .sec--light h2 { color: var(--navy); }
.sec--light .eyebrow { color: var(--royal); }

/* ------------------------------------------------------------------ hero */

.hero {
  min-height: 100svh;
  /* Two rows rather than absolute children: body carries overflow-x: clip, and
     an absolutely positioned caption inside a clipped ancestor is a trap. */
  display: grid; grid-template-rows: 1fr auto;
  align-items: center;
  padding-top: calc(var(--nav-h) + var(--s-6));
  padding-bottom: var(--s-6);
}
.hero-foot {
  position: relative; z-index: var(--z-content);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-4); flex-wrap: wrap;
  padding-top: var(--s-5);
}
.hero-inner { display: grid; grid-template-columns: minmax(0, 50fr) minmax(0, 50fr); }
.copy { display: grid; gap: var(--s-4); max-width: 660px; }
.copy .actions { margin-top: var(--s-3); }

.hero-caption {
  font-size: var(--t-label); letter-spacing: 0.04em;
  /* The hero is white now; steel measures 2.6:1 there. Body ink is 5.35:1. */
  color: var(--body-ink);
}

/* ---------------------------------------------------------- four pillars */

/* The I4 layout: headline centred, four columns, word above, stack, line
   below, the sphere on the ground between the second and third. White,
   opaque, above the stage: the live scene passes behind it. The stacks and
   the sphere are alpha cut-outs of the comp, so they sit on any white. */
.pillars {
  position: relative; z-index: var(--z-content);
  background: var(--white);
  padding-top: clamp(72px, 9vh, 120px); padding-bottom: clamp(72px, 9vh, 120px);
}
.pillars-head {
  font-family: var(--head); font-weight: 600; color: var(--navy);
  /* Stephen 2026-09-17: "the headline there can be 10 or 15% bigger just to
     create some bigger better hierarchy" -- +13%, against words that went down. */
  font-size: clamp(34px, 4.4vw, 68px); letter-spacing: -0.014em; line-height: 1.04;
  text-align: center; margin: 0 auto clamp(40px, 6vh, 80px); max-width: 26ch; text-wrap: balance;
}
.pillars-row {
  display: grid; grid-template-columns: 1fr 1fr 0.7fr 1fr 1fr;
  align-items: end; gap: clamp(12px, 2vw, 36px);
  max-width: 1320px; margin: 0 auto;
}
/* Three fixed rows per column, so every word shares a baseline, every stack
   shares a ground line and every caption starts at the same height. */
.pillar { display: grid; grid-template-rows: 48px auto 60px; align-items: end; justify-items: center; text-align: center; gap: 14px; }
.pillar__word {
  /* The site's default sans, not the display face, and 10% down: the four words
     are labels under the headline, not four more headlines. (Stephen, 2026-09-17) */
  font-family: var(--sans); font-weight: 600; color: var(--navy);
  font-size: clamp(23px, 2.2vw, 34px); letter-spacing: -0.006em; margin: 0 0 6px;
}
.pillar__stack { width: 100%; display: flex; justify-content: center; align-items: flex-end; }
/* Height-driven, width follows: never squash. The sphere's column is narrow
   and its image may overhang it by a few pixels; that is fine. */
.pillar__stack img { height: clamp(220px, 26vw, 406px); width: auto; max-width: none; display: block; }
.pillar__line { margin: 6px 0 0; font-size: clamp(15px, 1.15vw, 17px); line-height: 1.5; color: var(--body-ink); max-width: 22ch; align-self: start; }
.pillar--sphere .pillar__word, .pillar--sphere .pillar__line { visibility: hidden; }

/* ------------------------------------------------------------- the clip */

/* THE PILLARS CLIP. Where it runs, the row stops being a grid of stills and
   becomes the comp's own frame: one canvas at the comp's 16:9, the four words
   and lines anchored over it at the x each tower actually stands on (13.4 /
   35.9 / 62.85 / 85.12 % -- measured off the frames, not guessed). The section
   grows to two and a bit viewports and pins, so the towers build and the ball
   flies its arc while you scroll, one tower at a time. */
.pillars-canvas { display: none; }
.pillars.has-clip { min-height: 220vh; padding-bottom: 0; }
.pillars.has-clip .pillars-inner {
  position: sticky; top: var(--nav-h);
  padding-top: clamp(24px, 4vh, 56px);
  /* The captions hang below the towers inside the frame's box; this is the air
     under them, so the block never runs into the bottom of the window. */
  padding-bottom: clamp(32px, 5vh, 72px);
}
.pillars.has-clip .pillars-head { margin-bottom: clamp(16px, 2.4vh, 32px); }
/* THE HEADLINE ARRIVES WITH THE FIRST BRICKS. Everything else on this page is
   moved by the scroll, so a headline that faded in on an observer and then sat
   there for two viewports was the one still thing in a moving section. Under
   the clip it is two masked lines instead -- the hero's own move -- raised by
   the clip's own progress, just ahead of the first blocks landing. The observer
   reveal is overridden here; outside the clip it still owns the block. */
.js .pillars.has-clip .pillars-head {
  opacity: 1; transform: none; filter: none;
}
@media (min-width: 1041px) {
  .js .pillars.has-clip .pillars-head .line > span {
    display: block;
    transform: translateY(106%);
    transition: transform 780ms var(--ease-out);
  }
  .js .pillars.has-clip .pillars-head .line.is-up > span { transform: none; }
  .js .pillars.has-clip .pillars-head .line:last-child.is-up > span { transition-delay: 110ms; }
}
@media (prefers-reduced-motion: reduce) {
  .js .pillars.has-clip .pillars-head .line > span { transform: none !important; }
}
.pillars.has-clip .pillars-row {
  display: block; position: relative;
  aspect-ratio: 1280 / 720; max-height: 66vh;
  width: auto; margin-inline: auto;
}
/* The clip's white is a rendered white, a shade under the page's, so its box
   would otherwise show as a faint rectangle on the section. Feathering the
   canvas edges takes the seam out without touching either colour. */
.pillars.has-clip .pillars-canvas {
  display: block; position: absolute; inset: 0;
  width: 100%; height: 100%;
  -webkit-mask: linear-gradient(90deg, transparent 0, #000 4%, #000 96%, transparent 100%),
                linear-gradient(180deg, transparent 0, #000 5%, #000 95%, transparent 100%);
          mask: linear-gradient(90deg, transparent 0, #000 4%, #000 96%, transparent 100%),
                linear-gradient(180deg, transparent 0, #000 5%, #000 95%, transparent 100%);
  -webkit-mask-composite: source-in;
          mask-composite: intersect;
}
.pillars.has-clip .pillar {
  position: absolute; left: var(--x); transform: translateX(-50%);
  top: 0; bottom: 0; width: 23%;
  display: block; text-align: center;
}
/* The towers stand between 39.7% and 80.7% of the frame. The word sits above
   that band and the line below it -- and the line is TOP-aligned, not bottom-
   aligned: all four start on the same line whether they run to one line or two.
   (Stephen: "sub type on pg 2 under the pillars should be top aligned.") */
.pillars.has-clip .pillar__word {
  position: absolute; top: 28%; left: 0; right: 0; margin: 0;
}
.pillars.has-clip .pillar__line {
  position: absolute; top: 85%; left: 0; right: 0; margin: 0;
}
.pillars.has-clip .pillar--sphere { display: none; }
.pillars.has-clip .pillar__stack { display: none; }
.pillars.has-clip .pillar__line { max-width: 100%; }
/* Each word and line waits for its own tower to finish building. */
.js .pillars.has-clip .pillar__word,
.js .pillars.has-clip .pillar__line {
  opacity: 0; transform: translateY(8px);
  transition: opacity 420ms var(--ease-out), transform 420ms var(--ease-out);
  transition-delay: 0ms;
}
/* The ball's arc comes down onto each tower top, which is exactly where these
   labels sit -- it passes BEHIND them (the type is over the canvas), but a dark
   sphere under navy type costs legibility for the second it takes. A white halo
   is invisible on the white ground and holds the letters when it is not. */
.pillars.has-clip .pillar__word,
.pillars.has-clip .pillar__line {
  text-shadow: 0 0 16px #fff, 0 0 7px #fff, 0 0 3px #fff;
}
.js .pillars.has-clip .pillar.is-on .pillar__word,
.js .pillars.has-clip .pillar.is-on .pillar__line { opacity: 1; transform: none; }
.js .pillars.has-clip .pillar.is-on .pillar__line { transition-delay: 90ms; }

/* THE ENTRANCE. Once, when the row comes into view. --k orders it: the
   sphere is 0 and lands first, centre; then Operate, Secure, Improve, Health
   rise out of the ground 220 ms apart, each bringing its word and its line
   a beat later. Ease-out, nothing bounces, nothing loops. */
.js .pillars .pillar__stack img {
  clip-path: inset(100% 0 0 0);
  transform: translateY(26px);
  transition: clip-path 780ms var(--ease-out), transform 780ms var(--ease-out), opacity 500ms var(--ease-out);
  transition-delay: calc(140ms + var(--k) * 220ms);
}
.js .pillars .pillar--sphere .pillar__stack img {
  clip-path: none; opacity: 0; transform: translateY(10px) scale(0.72);
  transform-origin: 50% 100%;
  transition: opacity 520ms var(--ease-out), transform 620ms cubic-bezier(0.34, 1.25, 0.64, 1);
  transition-delay: 0ms;
}
.js .pillars:not(.has-clip) .pillar__word,
.js .pillars:not(.has-clip) .pillar__line {
  opacity: 0; transform: translateY(10px);
  transition: opacity 460ms var(--ease-out), transform 460ms var(--ease-out);
  transition-delay: calc(360ms + var(--k) * 220ms);
}
.js .pillars.is-live .pillar__stack img { clip-path: inset(0 0 0 0); transform: none; opacity: 1; }
.js .pillars.is-live .pillar--sphere .pillar__stack img { transform: none; }
.js .pillars.is-live:not(.has-clip) .pillar__word,
.js .pillars.is-live:not(.has-clip) .pillar__line { opacity: 1; transform: none; }
@media (max-width: 1040px) {
  .pillars-row { grid-template-columns: 1fr 1fr; }
  .pillar--sphere { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .js .pillars .pillar__stack img, .js .pillars .pillar--sphere .pillar__stack img { clip-path: none !important; transform: none !important; }
}

/* --------------------------------------------------------- four columns */

/* The four-word section is two viewports tall so the ball has somewhere to
   travel. The copy sticks while it does; the page itself never stops scrolling,
   which is the difference between a scroll story and a scroll hijack. */
.four { min-height: 260vh; padding-top: 0; padding-bottom: var(--s-9); }
.four-inner {
  position: sticky; top: var(--nav-h);
  min-height: calc(100svh - var(--nav-h));
  display: grid;
  grid-template-columns: minmax(0, 44fr) minmax(0, 56fr);
  align-items: center;
  gap: var(--s-6);
  padding-top: var(--s-6); padding-bottom: var(--s-7);
}
.four-copy { display: grid; gap: var(--s-6); align-content: center; }

/* "you're supposed to just keep the headlines in our fancy font" -- so this
   line, which is a section label rather than a headline, is the body font at its
   bold weight instead of Safiro. */
.four-head {
  font-family: var(--sans); font-weight: 700;
  font-size: clamp(18px, 1.5vw, 23px);
  line-height: 1.32; letter-spacing: -0.012em;
  max-width: 26ch; text-wrap: balance;
}

.words { display: grid; gap: clamp(2px, 0.4vw, 10px); }
.word {
  font-family: var(--head); font-weight: 600;
  font-size: var(--t-giant);
  line-height: 1.0;
  letter-spacing: -0.022em;
  /* Live text, drawn as an outline until the ball has landed on its pillar. */
  /* Screened back until the ball lands on the word's pillar -- Stephen: "not
     quite as bright". */
  color: transparent;
  -webkit-text-stroke: 1.2px rgba(208, 227, 255, 0.26);
  display: block;
  transition: color 240ms var(--ease-out),
              -webkit-text-stroke-color 240ms var(--ease-out);
}
.word.is-on { color: var(--white); -webkit-text-stroke-color: transparent; }

/* The object's column: pillars above, the copy that belongs to the one the ball
   just landed on below. The slot is empty; the runtime frames the camera to it. */
.four-object {
  display: grid; grid-template-rows: minmax(0, 1fr) auto;
  align-content: stretch; gap: 0;
  min-height: calc(100svh - var(--nav-h) - var(--s-8));
}
.act-stage { width: 100%; min-height: 0; }

/* Pulled up into the empty band under the pillars' feet -- Stephen: "bring
   the subcopy for each header closer to the illustration". The stage keeps
   its size, so the camera framing and the ball's travel are unchanged; only
   the copy moves. The runtime frames to the stage rect, not to this box. */
.act-copy {
  position: relative; z-index: var(--z-content);
  display: grid; justify-items: center; gap: var(--s-4);
  margin-top: calc(-1 * clamp(56px, 10vh, 104px));
}
/* All four occupy the same cell, so nothing below them moves as they change. */
.acts { display: grid; }
.act {
  grid-area: 1 / 1;
  text-align: center;
  /* 20% up on the old 16px, which is what "a little bit bigger, 10% or even
     20%" asked for. */
  font-size: clamp(17px, 1.35vw, 19.5px);
  line-height: 1.5;
  max-width: 36ch;
  color: var(--pale);
  opacity: 0;
  transform: translateY(14px);
  filter: blur(3px);
  /* Exit is faster than entry: the outgoing line is already read, the incoming
     one is the thing being looked at. */
  transition: opacity 150ms var(--ease-in), transform 150ms var(--ease-in),
              filter 150ms var(--ease-in);
  pointer-events: none;
}
.act.is-on {
  opacity: 1; transform: none; filter: blur(0);
  transition: opacity 280ms var(--ease-out), transform 280ms var(--ease-out),
              filter 280ms var(--ease-out);
}

/* Dots, not dashes -- Stephen: "I don't want little lines at the bottom of the
   subcopy. Dots would be preferable ... empty circles, and whichever one it
   clicks on will have the circle filled in." Only the current one is filled. */
.act-progress { display: flex; gap: 12px; }
.act-progress span {
  width: 9px; height: 9px; border-radius: 50%;
  border: 1.5px solid rgba(208, 227, 255, 0.55);
  background: transparent;
  transition: background 220ms var(--ease-out), border-color 220ms var(--ease-out),
              transform 220ms var(--ease-out);
}
.act-progress span.is-on {
  background: var(--steel); border-color: var(--steel); transform: scale(1.15);
}

/* ---------------------------------------------------------------- cracks */

.cracks {
  min-height: 190vh;
  padding-top: 0; padding-bottom: var(--s-9);
}
.cracks-inner {
  position: sticky; top: var(--nav-h);
  min-height: calc(100svh - var(--nav-h));
  display: grid; align-items: center;
  grid-template-columns: minmax(0, 45fr) minmax(0, 55fr);
}
.cracks .statement { max-width: 22ch; }
.text-link--light { color: var(--white); }

/* Where a lifestyle photograph drops in later. Marked, sized, and empty. */
.photo-slot { display: none; }

/* THE SCRUB. Stephen: "the old web animation, not using Blender ... the way
   that just feels with a shallow depth of field." The Seedance clip's frames,
   scrubbed by the cracks section's own scroll. Between the backdrop (0) and
   the stage (2); the sections' copy is at 3. */
.scrub { position: fixed; inset: 0; z-index: 1; opacity: 0; pointer-events: none; will-change: opacity; }
.scrub canvas { display: block; width: 100%; height: 100%; }
/* THE PLATE. Wherever the clip does not run -- no JS, a narrow window, reduced
   motion, save-data -- page three still has its picture: the clip's own first
   frame as the section's ground, 132KB against the sequence's 4.9MB. The
   runtime sets .is-plate (see scrubOk); the no-JS case is selector-only. */
html:not(.js) .cracks,
.cracks.is-plate {
  background: var(--deep) url("assets/i6-plate.jpg") center / cover no-repeat;
  position: relative;
}
html:not(.js) .cracks::after,
.cracks.is-plate::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(8, 23, 61, 0.86) 0%, rgba(8, 23, 61, 0.60) 100%);
}
html:not(.js) .cracks > *,
.cracks.is-plate > * { position: relative; z-index: 1; }

/* ----------------------------------------------------------------- close */

/* One viewport, one still. The A2 chevron image is the section's own ground;
   the live stage fades out over the last act (paintBackdrop) so nothing draws
   over it. Copy sits in the lower third, on the pooled light, exactly where the
   comp put it -- below the object, never over it. */
.close {
  position: relative;
  /* Width-fit, anchored to the bottom: the whole 16:9 comp shows at its own
     proportion and the copy lands where the comp put it. `cover` scaled the
     chevron up on a 16:10 window and ran the eyebrow over the bar tips. */
  min-height: max(100svh, 56.25vw);
  padding-top: 0; padding-bottom: 0;
  display: grid; align-items: end;
  overflow: hidden;
  background-color: var(--deep);
  background-image: url("assets/a2-close.jpg");
  background-repeat: no-repeat;
  background-size: 100% auto;
  /* --par is set from scroll (paintBackdrop): the chevron starts 48px low and
     rises into place as the page arrives, a slow parallax, nothing more. */
  background-position: center calc(100% + var(--par, 0px));
}
/* A quiet pool of navy under the copy, so the type holds on any crop of the
   image without dulling the glow above it. */
.close::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 44%;
  background: linear-gradient(180deg, rgba(8, 31, 92, 0) 0%, rgba(8, 31, 92, 0.30) 55%, rgba(8, 31, 92, 0.52) 100%);
  pointer-events: none;
}
.close-inner {
  position: relative; z-index: 1;
  display: grid; gap: 16px; justify-items: center; text-align: center;
  /* The block is boxed to the comp's lower third: the chevron's tips end at
     57% of the image, the eyebrow starts at 62%. */
  padding-top: calc(56.25vw * 0.655 + max(0px, 100svh - 56.25vw));
  padding-bottom: 3.6vw;
  padding-inline: var(--gutter);
}
.close .eyebrow { color: var(--steel); }
.close .statement {
  color: var(--white);
  /* The comp's own scale: ~4.3% of the frame height, two lines. */
  /* Two lines on a wide measure: a taller block would push the eyebrow up
     onto the chevron's tips, because the block is anchored to the bottom. */
  font-size: clamp(30px, 3.5vw, 53px); max-width: 28ch; line-height: 1.04;
}
.close .lede { color: var(--pale); opacity: 0.86; font-size: clamp(16px, 1.25vw, 18px); max-width: 50ch; margin-top: 4px; }
.close .actions { margin-top: 10px; }
.close .text-link { color: var(--white); }

/* ---------------------------------------------------------------- footer */

.foot {
  /* Below the stage, so the ball can finish inside the white. Stephen,
     2026-09-17: the ball no longer crosses the border -- it settles in the band
     this top padding opens, centred, with the copy pushed down under it. */
  position: relative; z-index: var(--z-section);
  background: var(--white);
  padding: clamp(190px, 18vw, 300px) var(--gutter) var(--s-7);
  display: grid; gap: var(--s-1);
  font-size: 14px; color: var(--navy);
  border-top: 1px solid rgba(15, 28, 63, 0.08);
}

/* ---------------------------------------------------------------- reveal */

/* One orchestrated entrance on load for the hero, one on first sight for each
   later block. Nothing re-fires, so the page settles and stays settled. */
/* Guarded on html.js, which the module sets synchronously. If the script never
   runs, or stalls before its first frame, the copy is simply visible rather
   than invisible: a reveal must never be the reason a page has no words on it. */
.js [data-reveal-item] {
  opacity: 0;
  transform: translateY(105%);
  transition: opacity var(--dur-reveal) var(--ease-out),
              transform var(--dur-reveal) var(--ease-out);
  transition-delay: calc(var(--i, 0) * var(--stagger));
}
/* Only masked lines travel their full height; everything else lifts a little. */
.js :not(.line) > [data-reveal-item] { transform: translateY(12px); }
body.is-ready [data-reveal-item] { opacity: 1; transform: none; }

/* TYPE THAT ARRIVES. Stephen: "there's no animation or movement, motion in
   the other type." Every observed block now comes in Emil-style: a short rise
   out of a soft blur, ease-out, 480 ms, and siblings stagger by 70 ms so a
   headline, its lede and its actions land as a sequence rather than a slab.
   Exit is instant (no transition back), so scrolling up never animates. */
.js [data-observe] {
  opacity: 0;
  transform: translateY(18px);
  filter: blur(6px);
}
[data-observe].is-in {
  opacity: 1; transform: none; filter: blur(0);
  transition: opacity 480ms var(--ease-out), transform 560ms var(--ease-out),
              filter 480ms var(--ease-out);
}
[data-observe].is-in:nth-child(2) { transition-delay: 70ms; }
[data-observe].is-in:nth-child(3) { transition-delay: 140ms; }
[data-observe].is-in:nth-child(4) { transition-delay: 210ms; }
[data-observe].is-in:nth-child(5) { transition-delay: 280ms; }

/* ------------------------------------------------------- narrow / touch */

/* The narrow build is not the desktop page with its object removed: it is the
   same five beats, re-laid for one column, each one keeping its own picture.
   (APERTURE does the same thing at 1000 and 600 -- retune every block, let the
   still stand in for the live scene, never leave a section with nothing in it.)
   Below 1041px the live stage is gone for good: no WebGL, no scrubbed frames,
   no fixed layers. Every image here is one the page already loads. */
@media (max-width: 1040px) {
  .hero-inner, .cracks-inner { grid-template-columns: 1fr; }
  .four, .cracks { min-height: 0; padding-top: var(--s-8); }
  .four-inner, .cracks-inner { position: static; min-height: 0; }
  .four-inner { grid-template-columns: 1fr; grid-template-rows: none; gap: var(--s-5); }
  .four-object { min-height: 0; grid-template-rows: none; }
  .act-stage { display: none; }
  .word { color: var(--white); -webkit-text-stroke-color: transparent; }
  .act-progress { display: none; }
  .hero { min-height: 0; padding-top: calc(var(--nav-h) + var(--s-6)); padding-bottom: var(--s-7); }
  .cracks, .close { min-height: 0; }
  .close-hint { display: none; }
  .hero-foot { display: none; }

  /* THE HERO STILL. The stage is a fixed layer that precedes <main>, so at this
     width it is hidden outright and the hero's own in-flow image takes over,
     under the copy. */
  /* The live WebGL sculpture stays off on touch; the scrubbed clips do not. */
  .stage { display: none; }
  .hero-still {
    display: block; width: auto; height: auto;
    max-width: min(78vw, 420px); margin: var(--s-6) auto 0;
  }

  /* THE FOUR, PAIRED. The words and the service lines are two separate blocks
     that only read as pairs because the object sits between them. With no
     object they must pair themselves: each word carries its own line (written
     in by the runtime from the list below) and the loose list is dropped. */
  .js .four .act-copy { display: none; }
  .word-row { display: block; }
  .word-line {
    display: block; margin: 6px 0 0;
    font-family: var(--sans); font-size: 16px; line-height: 1.55;
    color: var(--pale); opacity: 0.82; max-width: 34ch;
    letter-spacing: 0; text-transform: none; -webkit-text-stroke: 0;
  }
  .words { gap: var(--s-5); }
  /* No JS, no pairing: the loose list stays, spaced so it still reads. */
  html:not(.js) .acts { gap: var(--s-5); }
  html:not(.js) .act { grid-area: auto; opacity: 1; transform: none; filter: none; }

  /* Without the live object there is no scrubbed backdrop; the dark section
     paints its own ground again. */
  .backdrop { display: none; }
  .sec--dark { background: var(--deep); }
  .sec--light { background: var(--white); }

  /* Page three keeps its picture either way: the clip where it runs (the
     section goes transparent so the fixed canvas shows through, and a scrim
     carries the copy over it), the plate where it does not (.is-plate). */
  .cracks:not(.is-plate) { background: transparent; }
  .cracks:not(.is-plate)::after {
    content: ""; position: absolute; inset: 0;
    background: linear-gradient(180deg, rgba(8, 23, 61, 0.88) 0%, rgba(8, 23, 61, 0.58) 62%,
                                        rgba(8, 23, 61, 0.78) 100%);
  }
  .cracks { position: relative; }
  .cracks > * { position: relative; z-index: 1; }


  /* PAGE TWO ON A PHONE. The clip is a wide 16:9 shot of four towers, so the
     desktop treatment -- four labels absolutely placed across it -- gives each
     one about 75px of measure, which no line of copy survives. The section
     keeps its pin and its scroll range (the clip still scrubs) but restacks:
     headline, the clip full width as a strip, then the four words and their
     lines in a 2x2 grid under it. Each word still waits for its own tower. */
  .pillars.has-clip .pillars-row {
    display: grid; grid-template-columns: 1fr 1fr;
    gap: var(--s-5) clamp(12px, 3.5vw, 28px);
    align-items: start;
    aspect-ratio: auto; max-height: none; width: 100%;
  }
  .pillars.has-clip .pillars-canvas {
    position: static; grid-column: 1 / -1;
    width: 100%; height: auto; aspect-ratio: 1280 / 720;
    -webkit-mask: none; mask: none;
  }
  .pillars.has-clip .pillar:not(.pillar--sphere) {
    position: static; transform: none; width: auto;
    display: block; text-align: center;
  }
  /* The sphere has no label of its own; it must not hold a cell open. */
  .pillars.has-clip .pillar--sphere { display: none; }
  .pillars.has-clip .pillar__word,
  .pillars.has-clip .pillar__line { position: static; }
  .pillars.has-clip .pillar__word { font-size: clamp(20px, 5.6vw, 27px); }
  .pillars.has-clip .pillar__line { font-size: 15px; margin-top: 4px; }
  .pillars.has-clip .pillars-inner { padding-bottom: var(--s-5); }

  /* The headline's masked lines only exist for the clip. Here they go back to
     being inline, so `text-wrap: balance` sets the ragging on a phone instead
     of the sentence break forcing four lines. */
  .pillars-head .line, .pillars-head .line > span { display: inline; }

  /* THE CLOSE. The chevron is a 16:9 comp and its wings run from 17% to 83% of
     the frame: any centre crop tight enough to make it big on a phone cuts them
     off, and a crop loose enough to keep them lands on empty floor. So it is
     width-fit and sits at the top, whole, like a mark -- and the copy reads
     underneath it rather than over it. The gradient starts at the image's own
     bottom edge and takes the seam with it. */
  .close {
    /* .sec--dark above resets `background` wholesale, which took the chevron
       with it -- it has to be named again here. */
    background-color: var(--deep);
    background-image: url("assets/a2-close.jpg");
    background-repeat: no-repeat;
    background-size: 100% auto;
    background-position: center calc(var(--nav-h) + 10px);
    min-height: 0;
  }
  .close-inner {
    padding-top: calc(var(--nav-h) + 56.25vw + var(--s-4)); padding-bottom: var(--s-7);
    transform: none; opacity: 1;
  }
  .close::after {
    top: calc(var(--nav-h) + 56.25vw - 44px); height: auto;
    background: linear-gradient(180deg, rgba(8, 31, 92, 0) 0%, var(--deep) 30%);
  }
  /* No ball to land, so the footer's white band closes back up. */
  .foot { padding-top: var(--s-6); }
}

@media (max-width: 680px) {
  .nav nav a:not(.pill) { display: none; }
  /* The pill was running 15px off the right edge at 375. */
  .nav .pill { font-size: 13px; padding-inline: 16px; }
  /* 178x24 down to 132, aspect kept, so ELEVATE and the pill both fit at 375. */
  .wordmark__mark { width: 132px; height: 17.8px; }
  .wordmark__sub { font-size: 8px; letter-spacing: 0.26em; }
  .hero-still { max-width: 74vw; }
  .word-line { font-size: 15px; }
  /* 34px Safiro put "Better outcomes begin" onto two lines inside a line that
     is already a deliberate break. 27px lets each written line be one line. */
  .hero .display { font-size: 30px; }
  .hero .lede { font-size: 16px; }
  .close .statement { font-size: 29px; }
  .cracks .statement { font-size: 30px; }
}

/* ------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 140ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 140ms !important;
    transition-delay: 0ms !important;
  }
  [data-reveal-item], [data-observe] { transform: none !important; filter: none !important; }
  .act { transform: none !important; filter: none !important; }
  .close-inner { transform: none !important; }
}
