/* ===========================================================================
   Ossintheus — marketing surface
   Jet-black instrument. Pixel display face carries all personality;
   every other type element stays plain. Cyan is rare by rule.

   The page is three things: one continuous shot of the instrument opening
   into the map, the armory, and the way in. Nothing else.
   =========================================================================== */

/* --- Faces ---------------------------------------------------------------- */

@font-face {
  font-family: "Pixelify Sans";
  src: url("fonts/PixelifySans-latin.woff2") format("woff2");
  font-weight: 400 700;
  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+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Pixelify Sans";
  src: url("fonts/PixelifySans-latin-ext.woff2") format("woff2");
  font-weight: 400 700;
  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";
  src: url("fonts/Inter-latin.woff2") format("woff2");
  font-weight: 100 900;
  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+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  src: url("fonts/Inter-latin-ext.woff2") format("woff2");
  font-weight: 100 900;
  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: "IBM Plex Mono";
  src: url("fonts/IBMPlexMono-latin-400.woff2") format("woff2");
  font-weight: 400;
  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+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("fonts/IBMPlexMono-latin-ext-400.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    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: "IBM Plex Mono";
  src: url("fonts/IBMPlexMono-latin-500.woff2") format("woff2");
  font-weight: 500;
  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+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("fonts/IBMPlexMono-latin-ext-500.woff2") format("woff2");
  font-weight: 500;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    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: "IBM Plex Mono";
  src: url("fonts/IBMPlexMono-latin-600.woff2") format("woff2");
  font-weight: 600;
  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+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("fonts/IBMPlexMono-latin-ext-600.woff2") format("woff2");
  font-weight: 600;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    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;
}

/* --- Tokens --------------------------------------------------------------- */

:root {
  /* Jet-black ground. No blue-grey anywhere in the neutral ramp. */
  --void: #08080a;
  --page: #0f0f10;
  --panel: #151516;
  --raised: #1c1c1d;
  --well: #0b0b0c;
  --line: #232325;
  --line-soft: #1a1a1c;

  --bone: #f3f3f3;
  --bone-dim: #c8c5c2;
  /* Both steps carry real copy (labels, captions, metadata), so both clear
     4.5:1 on the darkest ground they ever sit on. */
  --mute: #a29c99;
  --dim: #878180;

  --signal: #66b8c9;
  --signal-deep: #315d68;
  --verified: #83b98b;
  --caution: #d6a75c;
  --critical: #d66b66;
  --inferred: #9a7ac7;

  --r-control: 10px;
  --r-panel: 14px;
  --r-sheet: 22px;

  --gutter: clamp(20px, 4.4vw, 68px);
  --measure: 62ch;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --t-fast: 130ms;
  --t-base: 220ms;
  --t-slow: 420ms;

  --font-display: "Pixelify Sans", ui-monospace, monospace;
  --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Consolas, monospace;

  color-scheme: dark;
}

/* --- Reset ---------------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--page);
  caret-color: var(--signal);
  scrollbar-color: #2c2c2f var(--void);
  scrollbar-width: thin;
  color: var(--bone);
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 450;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  /* `clip` where it is understood: `hidden` makes the body a scroll container,
     which is the classic way to kill a `position: sticky` stage. */
  overflow-x: hidden;
  overflow-x: clip;
}

img,
video,
svg,
canvas {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
  text-decoration: none;
}

button,
input,
textarea,
select {
  font: inherit;
  color: inherit;
}

h1,
h2,
h3,
p,
figure,
dl,
dd {
  margin: 0;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

hr {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 0;
}

::selection {
  background: var(--signal-deep);
  color: var(--bone);
}

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

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--bone);
  color: var(--void);
  padding: 12px 18px;
  border-radius: var(--r-control);
  font: 600 0.75rem/1.3 var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.skip:focus {
  left: 16px;
  top: 16px;
}

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

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

.mono,
.tel,
.kicker,
.idx {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.6875rem;
  line-height: 1.45;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.kicker {
  color: var(--dim);
  display: flex;
  align-items: center;
  gap: 10px;
}
.kicker::before {
  content: "";
  width: 26px;
  height: 1px;
  background: var(--line);
  flex: none;
}

.h2 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.1rem, 6.4vw, 4.6rem);
  line-height: 0.92;
  letter-spacing: 0.005em;
  text-transform: uppercase;
  color: var(--bone);
}

.h3 {
  font-family: var(--font-sans);
  font-size: 1.0625rem;
  font-weight: 650;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

.lede {
  font-size: clamp(1.0625rem, 1.55vw, 1.3125rem);
  line-height: 1.5;
  color: var(--bone-dim);
  max-width: 46ch;
  font-weight: 400;
}

.body {
  color: var(--mute);
  max-width: var(--measure);
  font-size: 0.9375rem;
  line-height: 1.62;
}

.body strong {
  color: var(--bone-dim);
  font-weight: 550;
}

.num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* The count-up must never reflow its neighbours: the span reserves its final
   width before the first frame runs. */
.n {
  display: inline-block;
  min-width: var(--nw, auto);
  font-variant-numeric: tabular-nums;
}

.figcap {
  margin-top: clamp(18px, 2.4vw, 26px);
  font: 400 0.75rem/1.7 var(--font-mono);
  letter-spacing: 0.02em;
  color: var(--dim);
  max-width: 74ch;
}
.figcap b {
  color: var(--mute);
  font-weight: 500;
}

/* --- Layout scaffold ------------------------------------------------------ */

.wrap {
  width: 100%;
  max-width: 1560px;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

section {
  position: relative;
}

.sect {
  padding-block: clamp(72px, 10.5vw, 148px);
  position: relative;
  isolation: isolate;
}

.sect-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 22px;
  margin-bottom: clamp(38px, 5vw, 68px);
}

@media (min-width: 900px) {
  .sect-head {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    gap: clamp(32px, 5vw, 90px);
    align-items: end;
  }
}

.idx {
  position: absolute;
  top: clamp(28px, 4vw, 46px);
  right: var(--gutter);
  color: var(--dim);
}
.idx span {
  color: var(--line);
  padding-inline: 4px;
}

/* --- Controls ------------------------------------------------------------- */

.pill {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  background: var(--bone);
  color: var(--void);
  border: 1px solid var(--bone);
  border-radius: 999px;
  padding: 13px 20px 13px 22px;
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 0.6875rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 140ms var(--ease), color 140ms var(--ease),
    transform 90ms var(--ease);
}
.pill:hover {
  background: #fff;
}
/* --- The film ---------------------------------------------------------------
   A generated iridescent oil-film clip lives inside the ghost CTA, revealed on
   hover. It is light on the surface of the button, not a new colour: the blend
   drops the clip's darks so only the sheen survives, and the label keeps full
   contrast because the film is capped well below it. -------------------------*/
.pill--film {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.pill--film .film {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  mix-blend-mode: screen;
  opacity: 0.14;
  pointer-events: none;
  transition: opacity var(--t-slow) var(--ease);
}
.pill--film:hover .film {
  opacity: 0.75;
}
.pill--film .pill-lbl {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: inherit;
}
html.no-js .pill--film .film {
  display: none;
}

.pill:active {
  transform: translateY(1px);
}
.pill .arw {
  transition: transform 200ms var(--ease);
}
.pill:hover .arw {
  transform: translateX(4px);
}

.pill--ghost {
  background: transparent;
  color: var(--bone-dim);
  border-color: var(--line);
}
.pill--ghost:hover {
  background: var(--raised);
  color: var(--bone);
  border-color: #34343a;
}

/* --- Nav -------------------------------------------------------------------
   A floating pill rather than a full-width bar: it reads as an object over the
   page instead of a lid on it.
   -------------------------------------------------------------------------- */

.nav {
  position: fixed;
  inset: 14px 0 auto 0;
  z-index: 40;
  pointer-events: none;
  transition: inset var(--t-slow) var(--ease);
}

.nav-in {
  pointer-events: auto;
  width: fit-content;
  max-width: min(1360px, calc(100% - var(--gutter) * 2));
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: clamp(10px, 1.4vw, 20px);
  /* Symmetric now that the row ends in a link rather than in a pill: the 7px
     right edge existed to sit tight against the CTA's own padding. */
  padding: 7px 16px 7px 18px;
  border-radius: 999px;
  border: 1px solid transparent;
  background: transparent;
  transition: background var(--t-slow) var(--ease),
    border-color var(--t-slow) var(--ease), box-shadow var(--t-slow) var(--ease),
    padding var(--t-slow) var(--ease);
}

/* No backdrop-filter. It re-blurs whatever is behind it on every frame, and
   what is behind it here is a video and a canvas that change on every frame —
   at a high refresh rate that is the single most expensive thing on the page.
   On a jet-black ground an opaque panel is indistinguishable from a blurred
   one, so the bar simply became opaque. */
.nav[data-stuck="true"] .nav-in {
  background: rgba(11, 11, 13, 0.94);
  border-color: rgba(255, 255, 255, 0.07);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.04) inset,
    0 14px 40px rgba(0, 0, 0, 0.55);
}

.lockup {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  flex: none;
}

.burger {
  /* 22x12 of bars inside a 44px target. The negative margin cancels the padding,
     so the lockup sits where it did when the button was only its bars. */
  width: 44px;
  height: 44px;
  box-sizing: border-box;
  padding: 16px 11px;
  margin: -16px -11px;
  display: grid;
  align-content: space-between;
  background: none;
  border: 0;
  cursor: pointer;
}
.burger span {
  display: block;
  height: 1.5px;
  background: var(--bone);
  transition: transform var(--t-base) var(--ease), opacity var(--t-fast) var(--ease);
}
.burger span:nth-child(2) {
  width: 15px;
  justify-self: start;
}

.mark {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  min-height: 24px;
  font-family: var(--font-sans);
  font-weight: 650;
  font-size: 1rem;
  letter-spacing: -0.012em;
  color: var(--bone);
  line-height: 1;
}
.mark-glyph {
  width: 18px;
  height: 18px;
  flex: none;
  color: var(--bone);
  overflow: visible;
  transition: color var(--t-base) var(--ease), transform 600ms var(--ease);
}
.mark:hover .mark-glyph {
  color: var(--signal);
  transform: rotate(22deg);
}

/* Links get a pill of their own on hover, so the bar has one vocabulary. */
.nav-links {
  display: none;
  align-items: center;
  gap: 2px;
}
.nav-links a {
  position: relative;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--mute);
  padding: 9px 13px;
  border-radius: 999px;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  transition: color var(--t-base) var(--ease), background var(--t-base) var(--ease);
}
.nav-links a:hover {
  color: var(--bone);
  background: rgba(255, 255, 255, 0.06);
}
/* The active destination is the one the reader is standing in. Cyan, and only
   ever one of them. */
.nav-links a[data-here] {
  color: var(--bone);
}
.nav-links a[data-here]::after {
  content: "";
  position: absolute;
  left: 13px;
  right: 13px;
  bottom: 4px;
  height: 1px;
  background: var(--signal);
}

@media (min-width: 1100px) {
  .nav-links {
    display: flex;
  }
  .burger {
    display: none;
  }
  .nav-in {
    padding-inline: 20px 14px;
  }
}

/* mobile sheet */
.sheet {
  position: fixed;
  inset: 0;
  z-index: 39;
  background: rgba(8, 8, 10, 0.97);
  display: grid;
  align-content: center;
  gap: 4px;
  padding: 0 var(--gutter);
  opacity: 0;
  visibility: hidden;
  /* `visibility` is discrete: transitioned normally it flips at the halfway
     point, so the panel is still hidden when the open handler tries to focus
     it. Delay it on the way out, apply it instantly on the way in. */
  transition: opacity 240ms var(--ease), visibility 0s linear 240ms;
}
.sheet[data-open="true"] {
  opacity: 1;
  visibility: visible;
  transition: opacity 240ms var(--ease), visibility 0s;
}
.burger[aria-expanded="true"] span:first-child {
  transform: translateY(3px) rotate(45deg);
}
.burger[aria-expanded="true"] span:nth-child(2) {
  width: 22px;
  transform: translateY(-3px) rotate(-45deg);
}
.sheet a {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.6rem, 8vw, 2.6rem);
  text-transform: uppercase;
  color: var(--bone);
  padding-block: 10px;
  border-bottom: 1px solid var(--line-soft);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

/* --- The ground texture ----------------------------------------------------
   The same bathymetric contour plate the shot and the access panel stand on,
   carried across the whole page at a fraction of the strength. It is a ground,
   not a pattern.
   -------------------------------------------------------------------------- */

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background: url("assets/chart.webp") center / cover no-repeat;
  opacity: 0.05;
  /* This was `mix-blend-mode: screen`, to drop the plate's black and keep only
     the light in its contour lines. At five percent over a near-black page the
     two are indistinguishable, and the blend made the whole viewport one blend
     group that had to be recomposited on every scrolled frame. */
}

/* ===========================================================================
   THE SHOT
   One section, four screens and a hold. The stage sticks to the glass for the
   whole of it while the words scroll past; scroll position is the only clock.

   Everything inside .atlas-stage is decoration and is marked aria-hidden —
   every word a reader needs lives in .atlas-flow, which reads as an ordinary
   stack of blocks with no script, no canvas and no video.
   =========================================================================== */

.atlas {
  position: relative;
  background: var(--void);
  isolation: isolate;
}

.atlas-stage {
  position: sticky;
  top: 0;
  z-index: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  /* The stage needs a floor of its own. Everything on it is screened onto
     whatever is behind, and `screen` against nothing is not a no-op — it
     leaves the clip's black ground painting a black square over the page. The
     plate above this fades in on arrival, so for the first second and a half
     of a cold load there was nothing behind the instrument at all, and that
     square was the first thing anyone saw. */
  background: var(--void);
}

/* The flow is pulled back over the stage: the stage occupies a screen of
   layout height, and the first beat has to stand on it, not under it. */
.atlas-flow {
  position: relative;
  z-index: 1;
  margin-top: -100vh;
  margin-top: -100svh;
  pointer-events: none;
}
/* Only the things a reader can actually use take the pointer back. */
.atlas-flow a,
.atlas-flow button,
.atlas-flow .beat-foot,
.atlas-flow .say,
.atlas-flow .figs {
  pointer-events: auto;
}

/* The chart wash and the vignette, in one layer under everything. */
.stage-ground {
  position: absolute;
  inset: -3%;
  z-index: 0;
  background: radial-gradient(
      118% 86% at 50% 44%,
      rgba(8, 8, 10, 0) 30%,
      rgba(8, 8, 10, 0.62) 72%,
      rgba(8, 8, 10, 0.94) 100%
    ),
    url("assets/chart.webp") center / cover no-repeat;
  pointer-events: none;
  /* This used to drift — a 96-second, two-percent tide across the plate. It
     cost 38ms of every frame: a viewport-sized photographic background under a
     blended layer cannot be handed to the compositor, so it re-rastered on
     every tick of the animation and took the whole shot down with it. The
     movement was imperceptible; the cost was not. It is a still plate now. */
  transform: scale(1.03);
}

/* The ground the spoken beats stand on. Pinned with the stage, faded in and
   out with the words it serves — never with the scroll. */
.stage-scrim {
  position: absolute;
  z-index: 4;
  inset: auto 0 0 0;
  height: 62%;
  background: linear-gradient(
    to top,
    rgba(8, 8, 10, 0.9) 0%,
    rgba(8, 8, 10, 0.62) 38%,
    rgba(8, 8, 10, 0) 100%
  );
  opacity: 0;
  pointer-events: none;
}

/* --- What the sphere stands on ---------------------------------------------
   Both instruments are screened onto the page, and screen cannot occlude — so
   the chart texture read through the body of the sphere and the object looked
   like glass. These sit under the blend and take the texture away over the
   sphere's own footprint, so it is screened onto clean black instead. The
   object is drawn exactly as it was; there is just nothing behind it now.

   z-index 0 puts them above the ground they cover and below both the
   instrument (1) and the clip (2). Neither is blended: painting the page's own
   black is the entire job, and `screen` would make it a no-op.
   -------------------------------------------------------------------------- */

.orb-plate,
.lift-plate {
  position: absolute;
  z-index: 0;
  left: 50%;
  translate: -50% -50%;
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
}
/* Sized and placed off the instrument, and moved by the same transform, so it
   stays under the sphere through the whole push. The stop at 38% is inside the
   rings and the ramp is long: what is wanted is the texture gone, not a shape
   drawn. */
.orb-plate {
  /* Off unless the opaque encode won. The instrument ships as a WebM with a
     real alpha channel and as an MP4 of the same picture on black; where the
     alpha one plays there is no frame to cover, and a disc under it is just a
     disc. `main.js` puts `orb-opaque` on the root once `currentSrc` has
     settled, and only then does this draw. */
  display: none;
  top: var(--orb-y, 46%);
  /* Wider than the instrument it backs, and solid well past the corners of
     that box. Sized the same as the box, its own fade began inside the box's
     edge — so a ring of plate-less texture was left for the clip's black to
     cover, and that ring was the square everyone could see. */
  width: calc(var(--orb-w, min(46vw, 50vh, 500px)) * 2.6);
  aspect-ratio: 1;
  /* The solid part has to reach the *corners* of the box it backs, not its
     edges: a square of side O has a half-diagonal of 0.707O, and a radial
     gradient measures its stops against the distance to its own farthest
     corner. Solid to 60% of that covers the box with room to spare, and the
     ramp then runs well outside it. */
  /* The render's own backdrop tone, not black. The instrument is a black
     object lit against a grey field — parts of it are darker than that field,
     and it is those parts that give it its shape. Crushing the field away is
     what made it look like a hologram, so the field stays and this disc
     carries it out past the frame's edge. Faded out by the controller exactly
     as the crush ramps in for the cut. */
  /* Solid past the frame's half-diagonal (0.707 of its width, measured
     against this gradient's own farthest-corner extent), then a long ramp so
     what is left reads as light falling off rather than as a disc. */
  background: radial-gradient(
    circle at 50% 50%,
    rgb(var(--plate-rgb, 6 6 7)) 0%,
    rgb(var(--plate-rgb, 6 6 7)) 42%,
    rgb(var(--plate-rgb, 6 6 7) / 0.5) 62%,
    rgb(var(--plate-rgb, 6 6 7) / 0) 86%
  );
}
/* Turned on only for the opaque fallback encode. */
html.orb-opaque .orb-plate {
  display: block;
}
/* The clip's globe fills ~0.85 of the box height and is centred in it, which
   is the same diameter the instrument lands on at the cut — so the two backings
   are the same disc and the hand-over does not move it. */
.lift-plate {
  top: 50%;
  height: calc(var(--lift-h, 76vmin) * 2.6);
  width: calc(var(--lift-h, 76vmin) * 2.6);
  /* Same rule, on a 16:9 box: its half-diagonal is 1.02 of its own height. */
  background: radial-gradient(circle at 50% 50%, #000 0%, #000 58%, rgba(0, 0, 0, 0) 86%);
}

/* --- The instrument --------------------------------------------------------
   A real alpha cutout — shot on green and keyed — so the letters draw straight
   over it with no plate, no mask and no blend mode. Scroll moves it to the
   middle of the glass, turns it faster, then pushes the camera through it.
   -------------------------------------------------------------------------- */

.orb {
  position: absolute;
  z-index: 1;
  left: 50%;
  top: var(--orb-y, 46%);
  width: var(--orb-w, min(46vw, 50vh, 500px));
  aspect-ratio: 1;
  translate: -50% -50%;
  display: grid;
  pointer-events: none;
  will-change: transform, opacity;
}
.orb > * {
  grid-area: 1 / 1;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* Solid, not screened.

   `screen` was here to make the clip's black ground vanish into the plate, and
   it did — but it also let the plate's contour lines show straight through the
   metal, so the instrument read as a transparency rather than an object, and
   because screen adds light it blew the highlights out on top of that.

   Drawn normally the metal is opaque. All that is left to deal with is the
   edge of its own frame, and since a circle inscribed in a square leaves only
   empty corners, fading those takes the frame away without touching the
   instrument. */
/* No mask. A circular mask on a square frame only fades the corners — at the
   middle of each edge it is still fully opaque, which is exactly where the
   frame was showing. The backing disc behind it is what takes the frame away,
   and it does it without touching the instrument at all. */
/* The clip has a real alpha channel, so the still is not hidden behind it — it
   shows through every gap between the rings as a second, motionless
   instrument. It has to be taken away once the clip is actually running. */
.orb video {
  z-index: 1;
}
.orb img {
  z-index: 0;
  transition: opacity var(--t-slow) var(--ease);
}
.orb[data-playing] img {
  opacity: 0;
}

/* --- The core --------------------------------------------------------------
   The sphere that was inside the rings, coming out of them. Screen drops the
   clip's black ground, so there is no rectangle edge to hide.
   -------------------------------------------------------------------------- */

.lift {
  position: absolute;
  z-index: 2;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  /* The globe fills ~0.83 of the clip's height, so the box is sized from the
     diameter the shot wants rather than the other way round. */
  height: var(--lift-h, 76vmin);
  width: calc(var(--lift-h, 76vmin) * 16 / 9);
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
  /* The blend has to sit on the element that blends with the stage, not on the
     clip inside it: this box carries a transform, which makes it a stacking
     context, and a blend mode on its child would then have nothing behind it
     to blend with — the clip's black ground would draw as a black rectangle
     over the page. */
  /* Solid, like the instrument it continues — see the note on .orb. The frame
     is taken away by the mask below rather than by a blend mode. */
  /* The render carries light right up to its edges — rings crossing the frame,
     glow along the top — and `screen` turns any of that into a visible
     rectangle on a black page. Both axes are faded so the clip has no edges at
     all; the sphere is centred and never reaches within 11% of any of them. */
  /* No mask here either: the backing disc covers this frame, and a mask would
     only clip the rings where they run out of shot. */
}
.lift video,
.lift img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* --- The same sphere, as points --------------------------------------------
   Drawn from Natural Earth land. It is the only form of the globe that can be
   unrolled, which is the whole reason it exists: the map at the bottom of this
   section is this canvas, flat.
   -------------------------------------------------------------------------- */

.world {
  position: absolute;
  z-index: 3;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  pointer-events: none;
}

/* --- Telemetry -------------------------------------------------------------
   Readouts printed on the glass: one type size for the whole of it, hierarchy
   from colour and position. They describe the graphic, never the product.
   -------------------------------------------------------------------------- */

.hud {
  position: absolute;
  z-index: 5;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  will-change: opacity;
}

.hud-c {
  position: absolute;
  display: grid;
  gap: 3px;
  min-width: 168px;
  /* The corner clusters stand down when the map arrives — the map's own head
     and measures take those corners. */
  opacity: var(--hud-c-a, 1);
}
.hud-c p {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  color: var(--dim);
}
.hud-c b {
  font-weight: 500;
  color: var(--bone-dim);
  font-variant-numeric: tabular-nums;
}
.hud-h {
  display: block;
  padding-bottom: 6px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--line);
  color: var(--mute);
}
.hud-tl {
  top: calc(clamp(72px, 10vh, 104px));
  left: var(--gutter);
}
.hud-tr {
  top: calc(clamp(72px, 10vh, 104px));
  right: var(--gutter);
}
/* Bottom left belongs to the beat's own words, so the ground readout takes the
   opposite corner rather than sitting under them. */
.hud-bl {
  bottom: clamp(96px, 14vh, 132px);
  right: var(--gutter);
}

/* Crop marks. The frame is the instrument's, not the layout's — it closes in
   on the map as the map arrives. */
.hud-frame {
  position: absolute;
  inset: clamp(58px, 8vh, 92px) var(--gutter) clamp(78px, 11vh, 118px);
  opacity: var(--frame-a, 0);
  transition: opacity var(--t-slow) linear;
}
.hud-frame span {
  position: absolute;
  width: 16px;
  height: 16px;
  border: 1px solid var(--line);
}
.hud-frame span:nth-child(1) {
  top: 0;
  left: 0;
  border-right: 0;
  border-bottom: 0;
}
.hud-frame span:nth-child(2) {
  top: 0;
  right: 0;
  border-left: 0;
  border-bottom: 0;
}
.hud-frame span:nth-child(3) {
  bottom: 0;
  left: 0;
  border-right: 0;
  border-top: 0;
}
.hud-frame span:nth-child(4) {
  bottom: 0;
  right: 0;
  border-left: 0;
  border-top: 0;
}

/* The transport: a ruler, not a bar. Ticks pass under the reading as the shot
   advances, and the one accent on the page marks how far in they are. */
.hud-track {
  position: absolute;
  left: var(--gutter);
  right: var(--gutter);
  bottom: clamp(26px, 4.4vh, 44px);
  display: flex;
  align-items: flex-end;
  gap: 16px;
}
.ticks {
  flex: 1;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  height: 12px;
}
.ticks li {
  width: 1px;
  height: 4px;
  background: var(--line);
  transition: height 200ms var(--ease), background 200ms var(--ease);
}
.ticks li[data-major] {
  height: 9px;
  background: #2f2f33;
}
.ticks li[data-on] {
  background: var(--signal);
}
.ticks li[data-on][data-major] {
  height: 12px;
}
.hud-p {
  color: var(--bone-dim);
  font-variant-numeric: tabular-nums;
  min-width: 5ch;
  text-align: right;
}

@media (max-width: 899px) {
  /* Four corner clusters is a desktop composition. On a phone the readout that
     matters is how far through the shot the reader is. */
  .hud-c,
  .hud-frame {
    display: none;
  }
}

/* --- The beats -------------------------------------------------------------
   The document's own content, riding over the stage.
   -------------------------------------------------------------------------- */

.beat {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
}

.beat--hero {
  grid-template-rows: 1fr auto;
  padding-top: clamp(82px, 10.5vh, 116px);
  padding-bottom: clamp(46px, 6vh, 72px);
}
.beat-mid {
  display: grid;
  place-items: center;
  min-height: min(44vh, 420px);
}

.wordmark {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.75rem, 13.4vw, 19.5rem);
  line-height: 0.9;
  letter-spacing: 0.012em;
  text-transform: uppercase;
  color: var(--bone);
  text-align: center;
  white-space: nowrap;
  will-change: transform, opacity;
}
.wordmark .ch {
  display: inline-block;
}

.beat-foot {
  display: grid;
  gap: 28px;
  align-items: end;
  will-change: transform, opacity;
}

@media (min-width: 900px) {
  /* Two columns, not three: the measures that held the right-hand one are
     gone, and a phantom column would leave the actions stranded mid-row. */
  .beat-foot {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: clamp(24px, 4vw, 64px);
    justify-content: space-between;
  }
}

.hero-claim {
  max-width: 46ch;
}
.hero-claim p {
  font-size: clamp(0.9375rem, 1.25vw, 1.0625rem);
  line-height: 1.5;
  color: var(--bone-dim);
}
.hero-claim .kicker {
  margin-bottom: 14px;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
}


/* --- The scroll cue --------------------------------------------------------
   A hairline with a mark at the end of it. It is the page's only instruction,
   it is drawn rather than written, and it goes as soon as the reader moves.
   -------------------------------------------------------------------------- */

.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: clamp(10px, 2.2vh, 24px);
  translate: -50% 0;
  width: 14px;
  color: var(--dim);
  opacity: 0;
  pointer-events: none;
  will-change: opacity;
}
.scroll-cue svg {
  width: 100%;
  height: auto;
  /* Small, promoted, transform-and-opacity only — the kind of animation that
     costs nothing, unlike the one that used to run on the plate behind it. */
  animation: cue 2.8s var(--ease) infinite;
  animation-play-state: paused;
}
.scroll-cue[data-on] svg {
  animation-play-state: running;
}
@keyframes cue {
  0%,
  100% {
    transform: translateY(-3px);
    opacity: 0.5;
  }
  50% {
    transform: translateY(3px);
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-cue svg {
    animation: none;
  }
}

/* --- Spoken beats ----------------------------------------------------------
   One idea per screen, set low and left so the instrument keeps the middle.
   -------------------------------------------------------------------------- */

/* Nothing in them: they are the scroll length the captions are cut against. */
.beat--say {
  min-height: 100vh;
  min-height: 100svh;
}
/*
  A caption in a film does not slide up the screen while the shot behind it
  moves — it holds one place and the next replaces it.

  The pin is `position: sticky` inside .atlas-flow, which is as tall as the
  whole shot, so one sticky box holds for all of it. The box itself has no
  height, so it costs the timeline nothing; the copy inside it is placed
  against the viewport. Doing this from a scroll handler instead — measuring
  where the block landed and translating it back — is a frame behind the
  compositor every time the reader moves, and that lag is what read as the
  text shaking.
*/
.cap {
  position: sticky;
  top: 0;
  height: 0;
  z-index: 2;
  opacity: 0;
  will-change: opacity;
}
.cap-in {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  height: 100svh;
  display: grid;
  align-content: end;
  padding-bottom: clamp(72px, 12vh, 132px);
}
.cap .say {
  opacity: 1;
  transform: none;
}

.say {
  max-width: 46ch;
  opacity: 0;
  transform: translateY(18px);
}
.say[data-shown] {
  opacity: 1;
  transform: none;
  transition: opacity 620ms var(--ease), transform 700ms var(--ease);
}
.say .kicker {
  margin-bottom: 16px;
}
.say-t {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.75rem, 4.6vw, 3.25rem);
  line-height: 1.02;
  letter-spacing: 0.005em;
  text-transform: uppercase;
  color: var(--bone);
  margin-bottom: 16px;
}
.say-b {
  color: var(--bone-dim);
  font-size: clamp(0.9375rem, 1.5vw, 1.0625rem);
  line-height: 1.65;
  max-width: 52ch;
}

/* --- The map beat ----------------------------------------------------------
   The last screen of the shot is the map section: the copy holds still for the
   whole of it while the sphere finishes opening underneath it.
   -------------------------------------------------------------------------- */

/* Head to the top row, measures to the bottom row, a screen and a half of
   nothing between them: that gap is where the sphere finishes opening. Both
   rows then stick, so the composed screen holds while it lands. */
.beat--map {
  min-height: 160vh;
  min-height: 160svh;
  grid-template-rows: auto;
  align-content: start;
  /* The nav's "The map" should land on the map, not on the middle of it
     opening. A negative scroll margin carries the jump past the unroll — and
     past the settle that follows it — to the screen the section is about. */
  scroll-margin-top: -35vh;
  scroll-margin-top: -35svh;
}
/* Head to the top of the glass, measures to the bottom of it, and both hold
   there for the whole beat while the sphere finishes opening between them.

   They hold as one box rather than as two sticky rows, and the box is the
   stage's twin: sticky to the top, a screen tall, in a container that ends
   where the stage's container ends. Stuck separately they outlived the stage,
   because this beat runs 160svh and the stage releases at the end of the
   atlas — so for the last 60svh the map scrolled up underneath copy that was
   still pinned, and the dots drew straight through the lede. A single box can
   only let go when the stage does. */
.beat-map-pin {
  position: sticky;
  top: 0;
  z-index: 1;
  height: 100vh;
  height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr auto;
  padding-top: clamp(84px, 12vh, 124px);
  padding-bottom: clamp(58px, 9vh, 92px);
}
.beat-map-head {
  grid-row: 1;
}
.beat-map-foot {
  grid-row: 3;
}
.beat-map-head .say {
  max-width: none;
}
.beat-map-head .lede {
  margin-top: 14px;
  opacity: 0;
  transform: translateY(18px);
}
.beat-map-head .lede[data-shown] {
  opacity: 1;
  transform: none;
  transition: opacity 620ms var(--ease) 90ms, transform 700ms var(--ease) 90ms;
}

@media (min-width: 900px) {
  .beat-map-head {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: clamp(32px, 5vw, 90px);
    align-items: end;
  }
}

/* Three measures on hairlines. No fill, because the map is behind them. */
.figs {
  display: grid;
  gap: 0 clamp(24px, 3vw, 48px);
  grid-template-columns: minmax(0, 1fr);
  border-top: 1px solid var(--line);
  padding-top: 16px;
}
@media (min-width: 760px) {
  .figs {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
.figs li {
  display: grid;
  gap: 6px;
  align-content: start;
  padding-block: 6px;
}
.figs .v {
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: clamp(1.375rem, 2.6vw, 2rem);
  line-height: 1.1;
  color: var(--bone);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.figs .k {
  font: 500 0.625rem/1.6 var(--font-mono);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--dim);
}
@media (max-width: 759px) {
  .figs li {
    padding-block: 3px;
  }
  .figs .v {
    font-size: 1.125rem;
  }
}

/* With motion turned down the shot is a cross-fade rather than a camera move,
   so it does not need four screens of scroll to tell. The section keeps its
   shape and loses the travel. */
html.reduce .beat--say {
  min-height: 70vh;
  min-height: 70svh;
}
html.reduce .beat--map {
  min-height: 110vh;
  min-height: 110svh;
  scroll-margin-top: 0;
}

/* --- Without script, or with motion turned down --------------------------- */

/* Relative, not static: the instrument is absolutely positioned against the
   stage, and a static stage hands that job to the whole section — which drops
   the instrument hundreds of pixels down the page, where the stage's own
   overflow then clips it away entirely. */
html.no-js .atlas-stage {
  position: relative;
  height: 62vh;
  height: 62svh;
}
html.no-js .atlas-flow {
  margin-top: 0;
}
html.no-js .beat {
  min-height: 0;
  padding-block: clamp(48px, 8vw, 96px);
}
html.no-js .beat--hero {
  padding-top: clamp(82px, 10.5vh, 116px);
}
html.no-js .beat--map {
  min-height: 0;
}
html.no-js .beat-map-pin {
  position: static;
  height: auto;
  display: block;
  padding: 0;
}
html.no-js .beat-map-head,
html.no-js .beat-map-foot {
  position: static;
  height: auto;
}
html.no-js .stage-scrim,
html.no-js .world,
html.no-js .hud,
html.no-js .lift,
html.no-js .scroll-cue {
  display: none;
}
html.no-js .cap,
html.reduce .cap {
  opacity: 1;
}
html.no-js .cap {
  position: static;
  height: auto;
}
html.no-js .cap-in {
  position: static;
  height: auto;
  padding-block: clamp(40px, 7vw, 80px);
}
html.no-js .beat--say {
  min-height: 0;
}
html.no-js .say,
html.no-js .beat-map-head .lede {
  opacity: 1;
  transform: none;
}

/* ===========================================================================
   On the map
   The shot says what the map is; these rows say what it does. Hairline-topped
   entries on the page's own ground — no fill, no card, nothing raised — so the
   section reads as a continuation of the measures under the map rather than as
   a feature grid bolted underneath it. Tight: one icon, one line, one rule.

   The icons are drawn in the page's own hairline weight and are decoration —
   the heading beside each one carries the meaning, and they are hidden from
   the accessibility tree. Each draws itself once as its row arrives (every
   shape carries pathLength="1", so one dash length serves paths, circles and
   ellipses alike) and then holds still until the pointer asks. Nothing on this
   page loops at the reader unprompted.
   =========================================================================== */

.caps {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0 clamp(22px, 2.6vw, 44px);
  margin: 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 700px) {
  .caps {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (min-width: 1020px) {
  .caps {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
/* Four columns once there is room for them: eleven rows then stand three deep
   instead of four, and the section costs about half a screen. */
@media (min-width: 1340px) {
  .caps {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
.caps li {
  display: grid;
  gap: 4px;
  align-content: start;
  border-top: 1px solid var(--line);
  padding-block: 11px 14px;
}

/* Icon and title on one line, so the row costs a line and a half rather than
   three. The icon is optically centred on the cap height, not on the line box. */
.caps-h {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  font-weight: 650;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--bone);
}
.caps-t {
  min-width: 0;
}
.caps-ic {
  flex: none;
  width: 18px;
  height: 18px;
  color: var(--mute);
  overflow: visible;
  transition: color var(--t-base) var(--ease);
}
.caps-ic * {
  stroke: currentColor;
  stroke-width: 1.35;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.caps-d {
  color: var(--mute);
  font-size: 0.8125rem;
  line-height: 1.55;
  max-width: 40ch;
  margin: 0;
}

/* The one live tell: the page's signal colour, spent on nothing else here. */
.caps-live {
  flex: none;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--signal);
  box-shadow: 0 0 0 0 rgba(102, 184, 201, 0.5);
  animation: caps-live 2.6s var(--ease) infinite;
}
@keyframes caps-live {
  0%,
  70%,
  100% {
    box-shadow: 0 0 0 0 rgba(102, 184, 201, 0.45);
  }
  35% {
    box-shadow: 0 0 0 4px rgba(102, 184, 201, 0);
  }
}

/* --- The icons draw themselves in ---------------------------------------- */

.caps li .caps-ic * {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}
.caps li[data-shown] .caps-ic * {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 760ms var(--ease) 120ms;
}

/* --- and then move only when asked ---------------------------------------- */

.caps li:hover .caps-ic,
.caps li:focus-within .caps-ic {
  color: var(--bone);
}
.caps li:hover [data-mv],
.caps li:focus-within [data-mv] {
  animation-duration: 1.9s;
  animation-timing-function: cubic-bezier(0.45, 0, 0.25, 1);
  animation-iteration-count: infinite;
}
.caps [data-mv] {
  transform-box: fill-box;
  transform-origin: center;
}
.caps li[data-anim="drive"]:hover [data-mv],
.caps li[data-anim="drive"]:focus-within [data-mv] {
  animation-name: mv-drive;
}
.caps li[data-anim="scan"]:hover [data-mv],
.caps li[data-anim="scan"]:focus-within [data-mv] {
  animation-name: mv-scan;
  animation-duration: 1.6s;
}
.caps li[data-anim="sweep"]:hover [data-mv],
.caps li[data-anim="sweep"]:focus-within [data-mv] {
  animation-name: mv-sweep;
}
.caps li[data-anim="pin"]:hover [data-mv],
.caps li[data-anim="pin"]:focus-within [data-mv] {
  animation-name: mv-pin;
  transform-origin: bottom;
}
.caps li[data-anim="null"]:hover [data-mv],
.caps li[data-anim="null"]:focus-within [data-mv] {
  animation-name: mv-null;
  animation-duration: 1.5s;
}
.caps li[data-anim="feed"]:hover [data-mv],
.caps li[data-anim="feed"]:focus-within [data-mv] {
  animation-name: mv-feed;
  animation-duration: 1.7s;
}
.caps li[data-anim="sail"]:hover [data-mv],
.caps li[data-anim="sail"]:focus-within [data-mv] {
  animation-name: mv-sail;
  animation-duration: 2.6s;
  transform-origin: bottom center;
}
.caps li[data-anim="fly"]:hover [data-mv],
.caps li[data-anim="fly"]:focus-within [data-mv] {
  animation-name: mv-fly;
}
.caps li[data-anim="drift"]:hover [data-mv],
.caps li[data-anim="drift"]:focus-within [data-mv] {
  animation-name: mv-drift;
  animation-duration: 1.5s;
}
.caps li[data-anim="flicker"]:hover [data-mv],
.caps li[data-anim="flicker"]:focus-within [data-mv] {
  animation-name: mv-flicker;
  animation-duration: 1.4s;
  transform-origin: bottom center;
}
.caps li[data-anim="spin"]:hover [data-mv],
.caps li[data-anim="spin"]:focus-within [data-mv] {
  animation-name: mv-spin;
  animation-duration: 3.4s;
  animation-timing-function: linear;
}

/* Amplitudes are deliberately small — a couple of pixels at 19px. The icon
   should read as instrumentation ticking over, never as a toy. */
@keyframes mv-drive {
  0% {
    transform: translateX(-1.6px);
  }
  50% {
    transform: translateX(1.6px);
  }
  100% {
    transform: translateX(-1.6px);
  }
}
@keyframes mv-scan {
  0% {
    transform: translateX(0);
    opacity: 0.25;
  }
  15%,
  85% {
    opacity: 1;
  }
  100% {
    transform: translateX(14.4px);
    opacity: 0.25;
  }
}
@keyframes mv-sweep {
  0%,
  100% {
    transform: scale(1);
  }
  45% {
    transform: scale(0.55);
  }
}
@keyframes mv-pin {
  0%,
  100% {
    transform: translateY(0);
  }
  40% {
    transform: translateY(-2.6px);
  }
}
@keyframes mv-null {
  0%,
  100% {
    stroke-dashoffset: 0;
  }
  45% {
    stroke-dashoffset: 1;
  }
}
@keyframes mv-feed {
  0% {
    transform: translateY(-2.4px);
    opacity: 0.35;
  }
  35% {
    opacity: 1;
  }
  80%,
  100% {
    transform: translateY(2.6px);
    opacity: 0;
  }
}
@keyframes mv-sail {
  0%,
  100% {
    transform: rotate(-2.6deg);
  }
  50% {
    transform: rotate(2.6deg);
  }
}
@keyframes mv-fly {
  0% {
    transform: translate(-1.8px, 1.8px);
  }
  55% {
    transform: translate(2px, -2px);
  }
  100% {
    transform: translate(-1.8px, 1.8px);
  }
}
@keyframes mv-drift {
  0% {
    transform: translateY(-2.5px);
    opacity: 0;
  }
  30% {
    opacity: 1;
  }
  100% {
    transform: translateY(2.5px);
    opacity: 0;
  }
}
@keyframes mv-flicker {
  0%,
  100% {
    transform: scaleY(1) scaleX(1);
  }
  40% {
    transform: scaleY(1.09) scaleX(0.94);
  }
  70% {
    transform: scaleY(0.96) scaleX(1.03);
  }
}
@keyframes mv-spin {
  0% {
    transform: scaleX(1);
  }
  50% {
    transform: scaleX(0.12);
  }
  100% {
    transform: scaleX(1);
  }
}

/* Nothing here is information, so with motion turned down it simply stops:
   the icons ship drawn, the live dot ships lit, and no hover starts a loop. */
html.reduce .caps li .caps-ic *,
html.no-js .caps li .caps-ic * {
  stroke-dashoffset: 0;
  transition: none;
}
@media (prefers-reduced-motion: reduce) {
  .caps li .caps-ic * {
    stroke-dashoffset: 0 !important;
    transition: none !important;
  }
  .caps-live,
  .caps li:hover [data-mv],
  .caps li:focus-within [data-mv] {
    animation: none !important;
  }
}

/* The section is a list, not a chapter: it takes less air above and below than
   a section that has to hold an argument. */
#reach {
  padding-block: clamp(58px, 7.5vw, 104px);
}
#reach .sect-head {
  margin-bottom: clamp(28px, 3.4vw, 44px);
}

/* Links drawn as text on a rule, so they cannot be mistaken for a chip or a
   pill: one weight, one underline, the signal colour only on hover. */
.lk,
.mods .lk {
  color: var(--bone-dim);
  border-bottom: 1px solid var(--line);
  padding-bottom: 2px;
  transition: color var(--t-base) var(--ease), border-color var(--t-base) var(--ease);
}
.lk:hover,
.mods .lk:hover {
  color: var(--signal);
  border-color: var(--signal-deep);
}

/* ===========================================================================
   The armory
   Each module is a drawer: name and count at rest, the data on demand. The
   whole row is the control, so the reader never hunts for a chevron.
   =========================================================================== */

.mod-filter {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: clamp(20px, 2.6vw, 30px);
}
.mod-filter input {
  flex: 1 1 260px;
  min-width: 0;
  background: var(--well);
  border: 1px solid var(--line);
  border-radius: var(--r-control);
  padding: 12px 16px;
  color: var(--bone);
  font-family: var(--font-mono);
  font-size: 0.875rem;
}
.mod-filter input::placeholder {
  color: var(--dim);
}
.mod-filter input:focus-visible {
  outline: none;
  border-color: var(--signal);
  box-shadow: 0 0 0 3px rgba(102, 184, 201, 0.22);
}
.mod-count {
  margin: 0;
  min-height: 1.4em;
  font: 500 0.6875rem/1.4 var(--font-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--dim);
}
.mod[hidden] {
  display: none;
}
.mods .mod {
  border-top: 1px solid var(--line-soft);
}
.mods .mod:last-child {
  border-bottom: 1px solid var(--line-soft);
}
.mod-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 14px;
  gap: 24px;
  align-items: center;
  width: 100%;
  padding: 20px 2px;
  background: none;
  border: 0;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
  transition: background var(--t-base) var(--ease);
}
.mod-head:hover,
.mod-head[aria-expanded="true"] {
  background: rgba(255, 255, 255, 0.018);
}
.mod-head:hover .nm {
  transform: translateX(8px);
}
.mod-head:focus-visible {
  outline: 1px solid var(--signal);
  outline-offset: 2px;
}
.mods .nm {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.125rem;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  color: var(--bone);
  transition: color var(--t-base) var(--ease), transform var(--t-base) var(--ease);
}
.mod-head[aria-expanded="true"] .nm {
  color: var(--signal);
}
.mods .ct {
  font: 500 0.6875rem/1.4 var(--font-mono);
  letter-spacing: 0.1em;
  color: var(--dim);
  text-align: right;
  white-space: nowrap;
}
/* The toggle glyph is drawn, not typed: two strokes, one of which turns away. */
.mod-x {
  position: relative;
  width: 12px;
  height: 12px;
}
.mod-x::before,
.mod-x::after {
  content: "";
  position: absolute;
  inset: 5px 0;
  background: var(--dim);
  transition: transform var(--t-slow) var(--ease), background var(--t-base) var(--ease);
}
.mod-x::after {
  transform: rotate(90deg);
}
.mod-head:hover .mod-x::before,
.mod-head:hover .mod-x::after {
  background: var(--bone-dim);
}
.mod-head[aria-expanded="true"] .mod-x::before {
  background: var(--signal);
}
.mod-head[aria-expanded="true"] .mod-x::after {
  transform: rotate(0deg);
  background: var(--signal);
}

.mod-body {
  display: grid;
  grid-template-rows: 0fr;
  /* Hidden, not merely zero pixels tall. At 0fr the rows are still in the
     accessibility tree and the link inside is still tabbable, so keyboard focus
     disappeared into a box with no height while aria-expanded said closed. The
     flip waits out the collapse so nothing is taken away mid-motion. */
  visibility: hidden;
  transition: grid-template-rows var(--t-slow) var(--ease), visibility 0s var(--t-slow);
}
.mod-body[data-open] {
  grid-template-rows: 1fr;
  visibility: visible;
  transition: grid-template-rows var(--t-slow) var(--ease), visibility 0s;
}
.mod-inner {
  overflow: hidden;
  min-height: 0;
}
.mods .ds {
  color: var(--mute);
  font-size: 0.9375rem;
  line-height: 1.65;
  max-width: 62ch;
  margin: 0 0 14px;
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity var(--t-slow) var(--ease), transform var(--t-slow) var(--ease);
}
.mod-specs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 22px;
  padding: 0;
  list-style: none;
}
.mod-specs li {
  font: 500 0.6875rem/1 var(--font-mono);
  letter-spacing: 0.06em;
  color: var(--bone-dim);
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--well);
  opacity: 0;
  transform: translateY(6px);
  transition: opacity var(--t-base) var(--ease), transform var(--t-base) var(--ease);
}
.mod-body[data-open] .ds {
  opacity: 1;
  transform: none;
}
/* The chips deal in, one after another, rather than arriving as a block. */
.mod-body[data-open] .mod-specs li {
  opacity: 1;
  transform: none;
  transition-delay: calc(90ms + var(--i, 0) * 60ms);
}
/* Without JS the drawers cannot open, so they ship open. */
html.no-js .mod-body {
  grid-template-rows: 1fr;
  visibility: visible;
}
html.no-js .mod-body .ds,
html.no-js .mod-body .mod-specs li {
  opacity: 1;
  transform: none;
}

/* ===========================================================================
   Receipts
   A page that demands provenance of its sources owes it for its own figures.
   =========================================================================== */

/* The receipt drawer is the page's argument, not a footnote, so it is drawn as
   a control: its own ground, a hairline box, a count of what it holds and the
   same glyph the module rows use. Closed it still announces itself; open it
   joins the list underneath without a seam. */
.receipts {
  margin-top: clamp(26px, 3vw, 40px);
}
.receipts summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 14px;
  align-items: center;
  gap: clamp(14px, 2vw, 28px);
  padding: 16px clamp(16px, 2vw, 22px);
  cursor: pointer;
  list-style: none;
  border: 1px solid var(--line);
  border-radius: var(--r-control);
  background: var(--well);
  transition: border-color var(--t-base) var(--ease),
    background var(--t-base) var(--ease);
  min-height: 44px;
}
.receipts summary::-webkit-details-marker {
  display: none;
}
.receipts summary:hover {
  background: rgba(255, 255, 255, 0.022);
  border-color: rgba(255, 255, 255, 0.13);
}
.receipts summary:focus-visible {
  outline: 1px solid var(--signal);
  outline-offset: 2px;
}
.receipts[open] summary {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom-color: transparent;
}
.r-lbl {
  display: grid;
  gap: 4px;
  min-width: 0;
}
.r-t {
  font: 500 0.75rem/1.4 var(--font-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bone-dim);
  transition: color var(--t-base) var(--ease);
}
.receipts summary:hover .r-t,
.receipts[open] .r-t {
  color: var(--bone);
}
.r-s {
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--dim);
}
.r-ct {
  font: 500 0.6875rem/1.4 var(--font-mono);
  letter-spacing: 0.1em;
  color: var(--dim);
  white-space: nowrap;
}
/* Two strokes, one of which turns away — the same toggle the armory uses, so
   one drawer teaches the other. */
.r-x {
  position: relative;
  width: 12px;
  height: 12px;
}
.r-x::before,
.r-x::after {
  content: "";
  position: absolute;
  inset: 5px 0;
  background: var(--dim);
  transition: transform var(--t-slow) var(--ease), background var(--t-base) var(--ease);
}
.r-x::after {
  transform: rotate(90deg);
}
.receipts summary:hover .r-x::before,
.receipts summary:hover .r-x::after {
  background: var(--bone-dim);
}
.receipts[open] .r-x::before,
.receipts[open] .r-x::after {
  background: var(--signal);
}
.receipts[open] .r-x::after {
  transform: rotate(0deg);
}
/* The list and the note it belongs to are one body, inside one box, so the
   note cannot read as page copy that wandered in. */
.r-body {
  padding: 2px clamp(16px, 2vw, 22px) clamp(16px, 2vw, 20px);
  border: 1px solid var(--line);
  border-top: 0;
  border-radius: 0 0 var(--r-control) var(--r-control);
  background: var(--well);
}
.receipts dl {
  margin: 0;
}
.receipts dl div {
  display: grid;
  gap: 5px;
  padding: 14px 0;
  border-top: 1px solid var(--line-soft);
}
@media (min-width: 780px) {
  .receipts dl div {
    grid-template-columns: 200px minmax(0, 1fr);
    gap: clamp(20px, 3vw, 44px);
    align-items: baseline;
  }
}
.receipts dt {
  font: 500 0.75rem/1.5 var(--font-mono);
  color: var(--bone-dim);
  font-variant-numeric: tabular-nums;
}
.receipts dd {
  margin: 0;
  color: var(--mute);
  font-size: 0.875rem;
  line-height: 1.65;
  max-width: 66ch;
}
.receipts code {
  font: 500 0.9em var(--font-mono);
  color: var(--bone-dim);
}
.rnote {
  margin-top: 14px;
  font: 400 0.75rem/1.7 var(--font-mono);
  color: var(--dim);
  max-width: 72ch;
}

/* --- Footer --------------------------------------------------------------- */

.foot {
  border-top: 1px solid var(--line-soft);
  padding-block: clamp(34px, 4vw, 54px);
  background: var(--page);
}
.foot-in {
  display: grid;
  gap: 26px;
}
@media (min-width: 820px) {
  .foot-in {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: end;
  }
}
.foot .tel {
  color: var(--dim);
}
.foot-links {
  display: flex;
  flex-wrap: wrap;
  gap: 22px;
}
.foot-links a {
  font: 500 0.625rem/1.4 var(--font-mono);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--mute);
  padding-block: 5px;
  min-height: 24px;
  display: inline-flex;
  align-items: center;
  transition: color 140ms var(--ease);
}
.foot-links a:hover {
  color: var(--bone);
}

/* ===========================================================================
   Motion, tightened
   One easing curve, one set of durations, applied to everything that moves.
   =========================================================================== */

.pill,
.nav-links a,
.foot-links a,
.mark,
.burger span,
.sheet a {
  transition-timing-function: var(--ease);
  transition-duration: var(--t-base);
}

.pill {
  transition-property: background, color, border-color, transform, box-shadow;
}
.pill:hover {
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.34);
}
.pill--ghost:hover {
  box-shadow: none;
}
.pill:active {
  transition-duration: 70ms;
  transform: translateY(1px) scale(0.994);
}

.nav-links a,
.foot-links a,
.mark {
  transition-property: color, border-color, opacity, background;
}

.mark-glyph {
  transition: color var(--t-base) var(--ease), transform 600ms var(--ease);
}

/* The arrow is the page's one repeated gesture, so it gets one behaviour. */
.arw {
  display: inline-block;
  transition: transform var(--t-base) var(--ease);
}
.pill:hover .arw,
.nav-links a:hover .arw {
  transform: translateX(4px);
}

/* Entrances: the same rise everywhere, staggered by position rather than by a
   number typed into each element. */
[data-reveal] {
  opacity: 0;
  transform: translateY(16px);
}
[data-reveal][data-shown] {
  opacity: 1;
  transform: none;
  transition: opacity 560ms var(--ease), transform 640ms var(--ease);
  transition-delay: var(--d, 0ms);
}

html.no-js [data-reveal],
html.reduce [data-reveal] {
  opacity: 1;
  transform: none;
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  [data-reveal],
  .say,
  .beat-map-head .lede {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .scroll-cue svg {
    animation: none !important;
  }
  .pill,
  .arw,
  .mark-glyph,
  .nav {
    transition: none !important;
  }
  /* The shot still runs, but as a slide change rather than a camera move: no
     scale, no ramp, and the map is already flat when it arrives. */
  .orb,
  .orb-plate,
  .lift,
  .lift-plate,
  .world,
  .hud {
    transition: opacity 200ms linear;
  }
}

/* ===========================================================================
   Phone
   PRODUCT.md calls mobile a first-class field surface, not a smaller desktop
   layout, and commits to 44px targets. Micro-type that is legible at arm's
   length, and controls a thumb can hit.
   =========================================================================== */

@media (max-width: 759px) {
  /* 10px mono is chrome on a monitor and unreadable on a phone. Every
     micro-type step moves up one, keeping the ratios between them. */
  .mono,
  .tel,
  .kicker,
  .idx {
    font-size: 0.75rem;
    letter-spacing: 0.14em;
  }

  .mods .ct,
  .foot-links a,
  .figs .k {
    font-size: 0.6875rem;
  }

  .body,
  .mods .ds,
  .receipts dd,
  .rnote,
  .figcap {
    font-size: 0.9375rem;
    line-height: 1.65;
  }
  .r-t {
    font-size: 0.75rem;
  }
  .r-s {
    font-size: 0.875rem;
  }
  .receipts dt {
    font-size: 0.8125rem;
  }

  /* Safe areas: the notch eats the left gutter in landscape, the home
     indicator eats the last row of the footer. */
  .wrap {
    padding-left: max(var(--gutter), env(safe-area-inset-left));
    padding-right: max(var(--gutter), env(safe-area-inset-right));
  }
  .foot {
    padding-bottom: calc(clamp(34px, 4vw, 54px) + env(safe-area-inset-bottom));
  }
  .beat--hero {
    padding-bottom: calc(clamp(46px, 6vh, 72px) + env(safe-area-inset-bottom));
  }
  .sheet {
    padding-bottom: env(safe-area-inset-bottom);
  }

  /* One tap-sized column of actions rather than two half-width pills. */
  .hero-actions {
    width: 100%;
  }
  .hero-actions .pill {
    flex: 1 1 100%;
    justify-content: space-between;
  }

  /* The word is shorter here in absolute terms, so the same proportion of
     instrument eats far more of it. The orb gives ground; the wordmark takes
     the width. */
  .wordmark {
    font-size: clamp(2.75rem, 15.6vw, 5rem);
  }
  .beat-mid {
    min-height: min(34vh, 300px);
  }
  .orb {
    width: var(--orb-w, min(48vw, 34vh, 200px));
  }
  /* The backing disc keeps its own multiple here too. Sharing the selector
     with .orb silently cancelled it, and the frame it exists to cover came
     straight back — on phones only, which is why it survived a desktop
     check. */
  .orb-plate {
    width: calc(var(--orb-w, min(48vw, 34vh, 200px)) * 2.6);
  }

  .arw {
    font-size: 0.75rem;
  }
}

/* Touch: 44px minimum on every control, and a visible press, since there is no
   hover to tell the reader anything. */
@media (pointer: coarse) {
  a,
  button,
  input,
  select,
  textarea,
  summary {
    -webkit-tap-highlight-color: rgba(102, 184, 201, 0.14);
  }

  .pill,
  .burger,
  .foot-links a,
  .nav-links a,
  .skip {
    min-height: 44px;
  }
  .skip {
    display: inline-flex;
    align-items: center;
  }

  .pill {
    padding-block: 14px;
  }
  /* Two bars, at a 44px target.

     `justify-items: start` collapsed the first span to zero width — it has no
     intrinsic width and relied on the grid stretching it — so on a touch device
     the only navigation affordance on the page rendered as a single 15px dash,
     and as a single slash when open. The bars are given their width instead of
     borrowing it from the grid's default stretch. */
  .burger {
    width: 44px;
    height: 44px;
    align-content: center;
    justify-items: start;
    gap: 5px;
  }

  .burger span { width: 22px; }
  .burger span:nth-child(2) { width: 15px; }
  .foot-links {
    gap: 8px 22px;
  }
  .pill:active {
    opacity: 0.72;
  }
}

/* Under 479px the lockup and the links share a very short line, so the pill
   the nav used to carry is gone and the row simply tightens. */

@media (max-width: 479px) {
  .nav-in {
    gap: 12px;
  }
}

@media (max-width: 369px) {
  .mark span {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* ===========================================================================
   Arrival
   A first visit opens like a shot, not like a page load: the ground is there,
   then the instrument, then the name a letter at a time, then the chrome. It
   runs once per session, it is under a second and a half, and it never gates
   content — every element's resting state is the visible one.
   =========================================================================== */

html.arrive .nav,
html.arrive .beat-foot,
html.arrive .orb,
html.arrive .orb-plate,
html.arrive .wordmark .ch {
  animation-duration: 900ms;
  animation-timing-function: var(--ease);
  animation-fill-mode: both;
}

html.arrive .orb,
html.arrive .orb-plate {
  animation-name: arrive-orb;
  animation-duration: 1500ms;
}
html.arrive .wordmark .ch {
  animation-name: arrive-letter;
  animation-duration: 800ms;
  /* Dealt left to right, a hair apart: fast enough to read as one gesture. */
  animation-delay: calc(320ms + var(--i, 0) * 55ms);
}
html.arrive .nav {
  animation-name: arrive-fade;
  animation-delay: 1050ms;
}
html.arrive .beat-foot {
  animation-name: arrive-rise;
  animation-delay: 1150ms;
}
html.arrive .stage-ground {
  animation: arrive-ground 1800ms var(--ease) both;
}

@keyframes arrive-ground {
  from {
    opacity: 0;
    transform: scale(1.06);
  }
  to {
    opacity: 1;
  }
}
/* Transform only — no opacity.

   The instrument is screened onto the page, and an element at less than full
   opacity is composited into its own group first, which takes the blend with
   it: for the second and a half this animation used to run, the clip's black
   ground stopped disappearing and painted a dark square over the plate. That
   square was the first thing anyone saw on a cold load. It rises into place
   now without fading, and the blend never breaks. */
@keyframes arrive-orb {
  from {
    transform: translateY(18px) scale(0.94);
  }
  to {
    transform: none;
  }
}
@keyframes arrive-letter {
  from {
    opacity: 0;
    transform: translateY(26px);
    filter: blur(6px);
  }
  to {
    opacity: 1;
    transform: none;
    filter: none;
  }
}
@keyframes arrive-fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes arrive-rise {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Reduced motion gets the page, immediately, with nothing withheld. */
@media (prefers-reduced-motion: reduce) {
  html.arrive .nav,
  html.arrive .beat-foot,
  html.arrive .orb,
  html.arrive .orb-plate,
  html.arrive .wordmark .ch,
  html.arrive .stage-ground {
    animation: none !important;
  }
}

/* ===========================================================================
   Open it — the door

   The last section is the only one that asks for something, so it stops being a
   section and becomes a room: the ground drops to the page's darkest value, the
   contour chart drawn for the access section comes back as its wash, the
   machined globe turns behind the word, and the content centres for the only
   time on the page. Centring is the signal. Everything above this is a
   left-aligned argument; this is the end of it.

   The page's one colour lives here, on one control. Everywhere else cyan means
   live, verified or focused; here it means there is a working instrument on the
   other side.
   ======================================================================== */

.door {
  background: var(--void);
  border-block: 1px solid var(--line-soft);
  overflow: hidden;
  padding-block: clamp(88px, 13vw, 176px);
}

/* The wash. Screen keeps only the light in the contour lines, so it can grain
   the ground without ever lifting it off black; the mask fades it out before
   the section's edges so the chart never reads as a placed rectangle. */
.door::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: url("assets/chart-crop.webp") center / cover no-repeat;
  opacity: 0.3;
  mix-blend-mode: screen;
  pointer-events: none;
  -webkit-mask-image: radial-gradient(ellipse 70% 80% at 50% 45%, #000 30%, transparent 100%);
  mask-image: radial-gradient(ellipse 70% 80% at 50% 45%, #000 30%, transparent 100%);
}

.door-in {
  position: relative;
  display: grid;
  justify-items: center;
  text-align: center;
  gap: 0;
}

/* --- The globe --------------------------------------------------------------
   Sized against the headline rather than the section, so it stands behind the
   word and the control and not behind the tour.
   ------------------------------------------------------------------------ */
.door-globe {
  position: absolute;
  left: 50%;
  top: 60%;
  translate: -50% -50%;
  width: min(1240px, 150vw);
  aspect-ratio: 16 / 9;
  z-index: -1;
  pointer-events: none;
  /* The blend sits on the host, not on the clip. The host is positioned with a
     z-index and a translate, so it is a stacking context and therefore an
     isolated group: a blend on a child would composite against the host's own
     transparent backdrop — a no-op — and the clip's black ground would land on
     the page as a dark oval. Blended as a whole, the host meets the section's
     void and wash and its black drops out. */
  mix-blend-mode: screen;
}
.door-globe video,
.door-globe img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity 1600ms var(--ease);
  -webkit-mask-image: radial-gradient(ellipse 40% 62% at 50% 50%, #000 52%, transparent 100%);
  mask-image: radial-gradient(ellipse 40% 62% at 50% 50%, #000 52%, transparent 100%);
}
.door-globe[data-ready] video,
.door-globe[data-ready] img {
  opacity: 0.62;
}
/* On a phone the facts stack under the control and the block grows tall, so
   the same fraction would put the globe behind the small print instead of
   behind the word. Pinned higher and wider, it stands through the headline and
   the control the way it does on a desk. */
@media (max-width: 719px) {
  .door-globe {
    top: 40%;
    width: 175vw;
  }
}
/* The still is removed once the clip runs rather than stacked under it. */
.door-globe video + img { display: none; }

/* The kicker's hairline lead-in is one-sided by default; centred, it needs the
   matching stroke on the other side or the label sits off its own axis. */
.door-kick {
  justify-content: center;
  margin-bottom: clamp(20px, 3vw, 34px);
}
.door-kick::after {
  content: "";
  width: 26px;
  height: 1px;
  background: var(--line);
  flex: none;
}

/* --- The word ------------------------------------------------------------- */
.door-h {
  margin: 0 0 clamp(34px, 5vw, 58px);
  display: grid;
  justify-items: center;
  gap: clamp(6px, 1vw, 12px);
}

.door-h__lead {
  font-family: var(--font-mono);
  font-size: clamp(0.75rem, 1.5vw, 0.9375rem);
  font-weight: 500;
  letter-spacing: 0.34em;
  text-indent: 0.34em;
  text-transform: uppercase;
  color: var(--mute);
}

/* Tracked until it spans the room. The size is capped against the viewport
   rather than the container so the word can never be wider than the screen it
   is set on — this face has no condensed cut to fall back to. */
.door-h__big {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.5rem, 12.4vw, 10.25rem);
  line-height: 0.9;
  letter-spacing: 0.045em;
  text-indent: 0.045em;
  text-transform: uppercase;
  color: var(--bone);
  text-wrap: nowrap;
}

/* --- The one control ------------------------------------------------------
   It names its own destination. A button that says only "open" asks the reader
   to trust an address they cannot see; this prints it under the verb, in mono,
   because an address is exact. */
.door-go {
  position: relative;
  display: inline-grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 2px clamp(20px, 3vw, 40px);
  max-width: 100%;
  padding: 12px 12px 12px clamp(22px, 3vw, 34px);
  border: 1px solid var(--signal-deep);
  border-radius: 999px;
  /* A single invalid value drops the whole declaration, and this one is the
     control's only fill, so the flat colour is stated first. */
  background: var(--raised);
  background:
    radial-gradient(120% 220% at 100% 50%,
      color-mix(in oklab, var(--signal) 13%, transparent), transparent 66%),
    var(--raised);
  color: var(--bone);
  text-align: left;
  text-decoration: none;
  transition:
    border-color var(--t-base) var(--ease),
    box-shadow var(--t-base) var(--ease),
    transform var(--t-base) var(--ease);
}

.door-go__text {
  display: grid;
  gap: 3px;
  min-width: 0;
}

.door-go__label {
  font-size: clamp(1rem, 1.5vw, 1.1875rem);
  font-weight: 650;
  letter-spacing: -0.01em;
  line-height: 1.15;
}

.door-go__addr {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.02em;
  color: var(--mute);
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color var(--t-base) var(--ease);
}

/* The arrow sits in its own well, and the well is the piece that fills. A
   border that changes colour is a state change; a disc that lights up is a
   door opening. */
.door-go__well {
  position: relative;
  display: grid;
  place-items: center;
  overflow: hidden;
  flex: none;
  width: clamp(46px, 5.4vw, 58px);
  height: clamp(46px, 5.4vw, 58px);
  border-radius: 50%;
  background: rgba(102, 184, 201, 0.14);
  background: color-mix(in oklab, var(--signal) 14%, transparent);
  color: var(--signal);
  transition: background var(--t-base) var(--ease), color var(--t-base) var(--ease);
}

.door-go__arw {
  grid-area: 1 / 1;
  width: 22px;
  height: 22px;
  transition: transform 380ms var(--ease), opacity 200ms linear;
}
/* The second arrow waits off the left edge and takes the first one's place, so
   the gesture reads as departure rather than as a nudge. */
.door-go__arw--in {
  transform: translateX(-150%);
  opacity: 0;
}

.door-go:hover,
.door-go:focus-visible {
  border-color: var(--signal);
  box-shadow:
    0 0 0 1px color-mix(in oklab, var(--signal) 34%, transparent),
    0 18px 60px -28px color-mix(in oklab, var(--signal) 70%, transparent);
}

.door-go:hover .door-go__well,
.door-go:focus-visible .door-go__well {
  background: var(--signal);
  color: var(--void);
}

.door-go:hover .door-go__addr,
.door-go:focus-visible .door-go__addr { color: var(--bone-dim); }

.door-go:hover .door-go__arw,
.door-go:focus-visible .door-go__arw {
  transform: translateX(150%);
  opacity: 0;
}
.door-go:hover .door-go__arw--in,
.door-go:focus-visible .door-go__arw--in {
  transform: translateX(0);
  opacity: 1;
}

.door-go:active { transform: translateY(1px); }

.door-go:focus-visible {
  outline: 2px solid var(--signal);
  outline-offset: 4px;
}

/* --- What the paragraphs used to say -------------------------------------
   Three facts on a rule. Label above value, hairline between columns: the
   corner telemetry of an instrument, which is what this page has been all the
   way down, rather than two more sentences at the end of it. */
.door-tel {
  margin: clamp(40px, 6vw, 72px) 0 0;
  padding-top: clamp(20px, 3vw, 28px);
  border-top: 1px solid var(--line-soft);
  width: min(100%, 880px);
  display: grid;
  gap: 20px clamp(24px, 4vw, 56px);
  text-align: left;
}

.door-tel > div { min-width: 0; }

.door-tel dt {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dim);
  margin-bottom: 7px;
}

.door-tel dd {
  margin: 0;
  color: var(--bone-dim);
  font-size: 0.9375rem;
  line-height: 1.5;
}

@media (min-width: 720px) {
  .door-tel {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .door-tel > div + div {
    padding-left: clamp(24px, 4vw, 56px);
    border-left: 1px solid var(--line-soft);
  }
  .door-tel {
    gap: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .door-go,
  .door-go__arw,
  .door-go__well,
  .door-globe video,
  .door-globe img {
    transition: none !important;
  }
  .door-go:hover .door-go__arw,
  .door-go:focus-visible .door-go__arw { transform: none; opacity: 1; }
  .door-go:hover .door-go__arw--in,
  .door-go:focus-visible .door-go__arw--in { transform: translateX(-150%); opacity: 0; }
}

/* The tour is now a sibling of the door rather than a cell inside a panel, so
   it carries its own distance from the control instead of a grid gap. */
.door .visit-clip { margin-top: clamp(56px, 8vw, 104px); }

/* The same door in the top bar, so a reader who arrives already convinced does
   not have to scroll the argument to reach it. */
.nav-go {
  padding: 7px 14px;
  border: 1px solid var(--signal-deep);
  border-radius: var(--r-control);
  color: var(--bone);
  transition: border-color var(--t-base) var(--ease), background var(--t-base) var(--ease);
}

.nav-go:hover {
  border-color: var(--signal);
  background: rgba(102, 184, 201, 0.12);
  background: color-mix(in oklab, var(--signal) 12%, transparent);
}

/* The tour slot.

   It reserves its 16:9 whether or not the file has been recorded yet, so adding
   the clip later changes what is in the box and nothing about where anything on
   the page sits. If the source is missing the frame says so in words rather than
   showing a broken player — an empty slot that reads as empty is honest; one
   that reads as broken is not. */
.visit-clip {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.visit-clip__frame {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-panel);
  background: var(--well);
}

.visit-clip__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--well);
}

/* Until the tour exists the slot is not drawn at all. Reserving its 16:9 was
   right when the clip was days away and the point was that dropping it in
   would move nothing; as a standing feature of the page it is eight hundred
   pixels of striped nothing under the one control that matters. */
.visit-clip[data-pending="1"] { display: none; }
.visit-clip[data-pending="1"] .visit-clip__video { display: none; }
/* An author display beats the user-agent [hidden] rule, so the overlay would
   otherwise sit on the player and swallow its controls the day the tour ships. */
.visit-clip__pending[hidden] { display: none; }

.visit-clip__pending {
  position: absolute;
  inset: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 24px;
  text-align: center;
  color: var(--dim);
  font-size: 0.875rem;
  background:
    repeating-linear-gradient(135deg,
      transparent 0 9px,
      color-mix(in oklab, var(--line) 55%, transparent) 9px 10px);
}

.visit-clip__pending span {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--mute);
}

/* ===========================================================================
   Fallbacks
   Two properties on this page are newer than the floor it should hold, and both
   fail loudly rather than quietly: without `translate` a centred box lands at
   its top-left corner. Anything script transforms carries an inline transform
   that wins over these anyway, so the two can never fight.
   ======================================================================== */
@supports not (translate: 0) {
  .lift-plate,
  .orb,
  .lift,
  .door-globe {
    transform: translate(-50%, -50%);
  }
  .scroll-cue {
    transform: translate(-50%, 0);
  }
}

/* 16px is the size below which iOS zooms the page on focus. */
@media (pointer: coarse) {
  .mod-filter input {
    font-size: 1rem;
  }
}
