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

html { -webkit-text-size-adjust: 100%; }

/* The reset zeroes padding, which hides the markers off-screen rather than
   removing them — they still render once a list has any indent. */
ul, ol { list-style: none; }

body {
  font-family: var(--font-body);
  font-size: var(--fs-base);
  line-height: 1.5;
  color: var(--hero-ink);
  background: var(--bg-2);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  font-family: var(--font-head);
  font-weight: var(--font-head-weight);
  line-height: 1.06;
  letter-spacing: -0.015em;
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; }
[hidden] { display: none !important; }

:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }

/* One canvas for the whole page (Core Law 1). Fixed so the phone survives
   past the hero handoff and keeps rendering down the page. */
.stage-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Above the copy, like flowty: the phone passes in front of the oversized
     headline. pointer-events stay off so links underneath remain clickable. */
  z-index: 3;
  pointer-events: none;
}

/* The phone is the top layer of the page content: it passes in FRONT of the
   copy at every size (flowty's arrangement). Only the navbar sits above it, so
   the phone slides behind the blurred header as you scroll.
   pointer-events stay off, so links underneath remain clickable. */

.page-bg {
  position: fixed;
  inset: 0;
  /* OVERSHOOT top and bottom. A `position: fixed; inset: 0` box is laid against
     the SMALL viewport on mobile Safari and Chrome — the one with the address
     bar showing. The moment that bar collapses the viewport grows underneath
     it and a strip of bare white <body> appears at the foot, which reads as the
     background having vanished. Spilling 12vh past each edge covers the whole
     range of bar states. The element is pointer-events:none at z-index -1, so
     the spill costs nothing. */
  top: -12vh;
  bottom: -12vh;
  z-index: -1;
  pointer-events: none;
  will-change: opacity;
  /* Owner-specified: a plain two-stop gradient. 0deg points to the TOP, so at
     15deg the FIRST colour is at the foot and the second at the head. */
  background: linear-gradient(
    var(--hero-grad-angle),
    var(--hero-grad-a) 0%,
    var(--hero-grad-b) 100%
  );
}

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--sp-12);
  padding: 0 var(--sp-8);
  border-radius: 999px;
  font-size: var(--fs-cta);
  font-weight: 500;
  letter-spacing: 0.01em;
  isolation: isolate;
}
/* The fill is a child, so it can grow past the button's own box on hover
   (flowty animates `inset` on exactly such a layer). */
.btn__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: 999px;
  background: var(--btn-primary);
  transition: inset 0.6s cubic-bezier(0.625, 0.05, 0, 1);
}
.btn:hover .btn__bg { inset: -4px; }
.btn--primary { color: var(--btn-ink); background: transparent; }
/* Fallback if the roll script has not run: keep the button filled. */
.btn--primary:not([data-roll]) { background: var(--btn-primary); }

/* ---- Character roll (hover) ----------------------------------------
   Each character paints a copy of itself 1.15em below with text-shadow; the
   wrapper clips it. On hover the characters slide up by that exact distance,
   so the copy rolls into place. */
.roll {
  position: relative;
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  line-height: 1.15;
}
.roll__ch {
  display: inline-block;
  position: relative;
  white-space: pre;
  text-shadow: currentColor 0 1.15em;
  transform: translateY(0) rotate(0.001deg);
  transition: transform 0.6s cubic-bezier(0.625, 0.05, 0, 1);
}
a:hover .roll__ch,
button:hover .roll__ch { transform: translateY(-1.15em) rotate(0.001deg); }

@media (prefers-reduced-motion: reduce) {
  .roll__ch { transition: none; }
  .btn__bg { transition: none; }
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

/* Loader */
/* Load screen ------------------------------------------------------------
   Logo centred at the top, the percentage in the middle, no progress bar.
   Painted from FIRST PAINT by CSS, not switched on by JS — the point is that
   nobody watches the page assemble behind it. It leaves by fading upward.

   There are no slices any more: the earlier version wiped the overlay away and
   THEN the hero intro played, which read as the site loading twice. */
.loader {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  grid-template-rows: auto 1fr;
  justify-items: center;
  padding: clamp(28px, 6vh, 64px) var(--gutter) 0;
  background: linear-gradient(
    var(--hero-grad-angle), var(--hero-grad-a) 0%, var(--hero-grad-b) 100%);
  will-change: transform, opacity;
}
html.loader-done .loader { pointer-events: none; }

.loader__inner {
  display: contents;   /* logo takes row 1, the count row 2, both centred */
}

.loader__logo { display: flex; align-items: center; gap: var(--sp-3); }
.loader__mark { height: 40px; width: auto; }
.loader__word { height: 30px; width: auto; }

/* The count IS the progress indicator. Playfair defaults to old-style figures,
   which bounce on the baseline as the number changes — lining + tabular keeps
   them level and stops the width jittering between 9 and 10. */
.loader__num {
  align-self: center;
  font-family: var(--font-head);
  font-weight: var(--font-head-weight);
  font-variant-numeric: lining-nums tabular-nums;
  font-size: clamp(4rem, 14vw, 12rem);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--text);
}
.loader__num::after { content: "%"; font-size: 0.32em; vertical-align: 0.9em; margin-left: 0.06em; }

@media (max-width: 767px) {
  .loader__mark { height: 32px; }
  .loader__word { height: 24px; }
}

/* If JS never runs at all, the overlay must not trap the page. */
html.no-js .loader { display: none; }

/* Visible to assistive tech only. The odometer's digit reels are aria-hidden
   (a screen reader would otherwise read every numeral on every reel), so the
   real figure is carried here instead. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
