/* ============================================================
   modamedia.io v1

   One flat colour per surface. The light comes from one point.
   Indigo means one thing only: measured, or clickable.
   ============================================================ */

@font-face {
  font-family: "Space Grotesk";
  src: url("fonts/space-grotesk-v22-latin-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Space Grotesk";
  src: url("fonts/space-grotesk-v22-latin-700.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("fonts/geist-v5-latin-regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Geist";
  src: url("fonts/geist-v5-latin-500.woff2") format("woff2");
  font-weight: 500; font-style: normal; font-display: swap;
}

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

:root {
  --ink: #24241F;
  --paper: #FBF7F4;
  --indigo: #4A52D6;
  --periwinkle: #A9B6F5;
  --lime: #C6F24E;

  --bg: var(--paper);
  --text: var(--ink);
  --text-soft: rgba(36, 36, 31, 0.55);   /* large type only: 3.56:1 */
  --text-quiet: rgba(36, 36, 31, 0.68);  /* small type: 5.27:1, AA */
  --text-muted: rgba(36, 36, 31, 0.68);
  --accent: var(--indigo);
  --accent-solid: var(--indigo);
  --accent-solid-hover: #3E45B8;
  --hairline: rgba(169, 182, 245, 0.55);

  /* the inverted surface: used twice, for the evidence and for the partner */
  --surface-invert: var(--ink);
  --on-invert: var(--paper);
  --on-invert-muted: rgba(251, 247, 244, 0.70);
  --on-invert-soft: rgba(251, 247, 244, 0.45);
  --invert-hairline: rgba(169, 182, 245, 0.28);
  --invert-accent: #8E97F4;
  /* lime exists exactly once on the page, and that is the dot inside
     the GRVTY mark itself. The rule under the link is not a second one. */
  --invert-rule: var(--on-invert);
  --invert-hover: var(--lime);

  /* light adds luminance. On paper that means going up towards white,
     never a tinted veil that darkens the surface it claims to light. */
  --wordmark-light: rgba(255, 255, 255, 0.92);
  --wordmark-blend: screen;

  --font-display: "Space Grotesk", system-ui, sans-serif;
  --font-body: "Geist", system-ui, -apple-system, sans-serif;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
  --pad: clamp(20px, 5vw, 48px);
  --indent: clamp(0px, 12vw, 176px);
  --h2-major: clamp(2rem, 1rem + 3vw, 3.25rem);
  --measure: 46ch;
  --lede-gap: 22px;
  --bleed-right: clamp(14px, 1.4vw, 26px);  /* one right edge for every
                                               object that breaks the grid */
  --ghost-op: 0.34;   /* one value, used by the declaration and by nothing else */

  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #191915;
    --text: #F4F0EB;
    --text-soft: rgba(244, 240, 235, 0.52);
    --text-quiet: rgba(244, 240, 235, 0.66);
    --text-muted: rgba(244, 240, 235, 0.66);
    --accent: #7B85F1;
    --accent-solid: #4C55DE;   /* 4.5:1 was exactly on the line */
    --accent-solid-hover: #5A63E8;
    --hairline: rgba(169, 182, 245, 0.26);

    /* the inversion flips with the theme: paper panels in a dark page */
    --surface-invert: var(--paper);
    --on-invert: var(--ink);
    --on-invert-muted: rgba(36, 36, 31, 0.70);
    --on-invert-soft: rgba(36, 36, 31, 0.48);
    --invert-hairline: rgba(74, 82, 214, 0.26);
    --invert-accent: var(--indigo);
    --invert-rule: var(--ink);  /* lime on paper is 1.17:1, so ink here */
    --invert-hover: var(--indigo);

    --wordmark-light: rgba(90, 99, 232, 0.22);
    --wordmark-blend: normal;
  }
}

/* an inverted panel simply re-points the tokens, so every rule inside
   it keeps working without a single override */
.invert {
  background: var(--surface-invert);
  color: var(--on-invert);
  --text: var(--on-invert);
  --text-muted: var(--on-invert-muted);
  --text-quiet: var(--on-invert-muted);
  --text-soft: var(--on-invert-soft);
  --hairline: var(--invert-hairline);
  --accent: var(--invert-accent);
  --bg: var(--surface-invert);
}

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

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

html {
  overflow-x: clip;
  /* the bar is 76px and sticky: without this, backwards tab navigation
     parks the focused element completely underneath it */
  scroll-padding-top: 92px;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: clamp(1rem, 0.96rem + 0.2vw, 1.0625rem);
  line-height: 1.6;
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  caret-color: var(--accent);
  scrollbar-color: var(--periwinkle) transparent;
}

::selection { background: var(--indigo); color: var(--paper); }
.invert ::selection { background: var(--paper); color: var(--ink); }
@media (prefers-color-scheme: dark) {
  .invert ::selection { background: var(--indigo); color: var(--paper); }
}

img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }

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

.wrap { max-width: 1160px; margin-inline: auto; padding-inline: var(--pad); }

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

.skip {
  position: absolute; top: 12px; left: 12px; z-index: 10;
  padding: 10px 16px;
  background: var(--indigo); color: var(--paper);
  font: 500 0.9375rem/1 var(--font-body);
  border-radius: 2px;
  transform: translateY(-64px); opacity: 0;
}
.skip:focus-visible { transform: none; opacity: 1; }

/* one underline behaviour for every inline link on the page */
.link {
  position: relative;
  color: var(--accent);
  padding-bottom: 1px;
}
.link::after {
  content: "";
  position: absolute; inset: auto 0 0 0; height: 1px;
  background: currentColor;
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform 220ms var(--ease);
}
.link:hover::after { transform: scaleX(1); }

/* ---------- masthead ---------- */

.masthead { position: sticky; top: 0; z-index: 1; }

/* The bar's surface is its own layer, so it can be absent at rest (the
   light runs through it, uncut) and present the moment type starts
   passing underneath. */
.masthead::before {
  content: "";
  position: absolute; inset: 0;
  background: var(--chrome-bg, var(--bg));
  border-bottom: 1px solid var(--chrome-line, var(--hairline));
  /* the surface lands first, the content follows. Equal opposite
     interpolations would cross in the middle, where text and ground
     meet at the same grey and contrast falls to 1:1. */
  transition: background-color 140ms var(--ease), border-color 140ms var(--ease);
}

/* the chrome takes on the surface it is standing over, so the bar never
   cuts a contrasting stripe across an inverted panel */
.masthead[data-invert] {
  --chrome-bg: var(--surface-invert);
  --chrome-line: var(--invert-hairline);
  color: var(--on-invert);
}

.masthead-in {
  position: relative;
  display: flex; align-items: center; justify-content: space-between;
  height: 76px;
  transition: color 140ms var(--ease) 140ms;
}

.brand { display: flex; align-items: center; gap: 14px; }

/* both marks are always laid out, stacked in one cell, and swap on
   opacity with the same delay as the text. A display swap cannot
   interpolate, so the mark used to flip at t=0 and sit paper on paper
   while the surface was still travelling. */
.brand-marks { display: grid; }
.mark {
  grid-area: 1 / 1;
  transition: opacity 140ms var(--ease) 140ms;
}
.mark-on-ink { opacity: 0; }

@media (prefers-color-scheme: dark) {
  .mark-on-paper { opacity: 0; }
  .mark-on-ink { opacity: 1; }
}
.masthead[data-invert] .mark-on-paper { opacity: 0; }
.masthead[data-invert] .mark-on-ink { opacity: 1; }
@media (prefers-color-scheme: dark) {
  .masthead[data-invert] .mark-on-paper { opacity: 1; }
  .masthead[data-invert] .mark-on-ink { opacity: 0; }
}

.brand-name { font: 500 1.0625rem/1 var(--font-display); letter-spacing: 0.01em; }

.nav-link {
  position: relative;
  font: 500 0.9375rem/1 var(--font-body);
  padding-bottom: 3px;
}
.nav-link::after {
  content: "";
  position: absolute; inset: auto 0 0 0; height: 1px;
  background: currentColor;
  transform: scaleX(0); transform-origin: 0 50%;
  transition: transform 220ms var(--ease);
}
.nav-link:hover::after { transform: scaleX(1); }

/* ---------- 1. hero ---------- */

/* no clip here: html and body already clip horizontally, and a clip on
   this box would cut the light source in half at the masthead edge */
.hero {
  position: relative;
  padding-block: clamp(64px, 10vh, 112px) clamp(80px, 12vh, 140px);
}

/* The one light source, falling from the wordmark.

   Dark only, and that is a statement rather than an omission: paper has
   no headroom above itself, so on paper a light source can only be a
   tinted veil that darkens what it claims to light. The brand lights up
   in the dark, and on paper it is simply ink. */
.hero::before { content: none; }

@media (prefers-color-scheme: dark) {
  .hero::before {
    content: "";
    position: absolute; top: -358px;
    left: calc(max(0px, 50% - 580px) + var(--pad) + 22px - 320px);
    width: 640px; height: 640px;
    background: radial-gradient(circle closest-side, var(--wordmark-light), transparent 74%);
    pointer-events: none; z-index: -1;
  }
}

.hero-in { position: relative; }

/* The number is already here, unmeasured, holding the right half of the
   fold. The proof section collects it.

   It is anchored to the visible edge, never to the wrap's padding box:
   the figure whose whole point is that it is exact may never be cut. */
.hero-ghost {
  position: absolute;
  right: calc(50% - 50vw + var(--bleed-right));
  bottom: -0.14em;
  z-index: -1;
  font: 700 clamp(4rem, 1rem + 9.5vw, 10.5rem)/0.9 var(--font-display);
  font-variant-numeric: proportional-nums;
  letter-spacing: -0.05em;
  color: var(--text);
  opacity: var(--ghost-op);   /* a shape, not a stain: it must read as a figure */
  white-space: nowrap;
  user-select: none;
  pointer-events: none;
}

/* On a phone the fold is the whole page, so the figure has to exist
   there too. It gets its own band under the argument, clear of the
   button, at full glyph height, and runs off the right edge, where
   being cut is the meaning rather than a defect: the uncollected
   number. Never behind the one thing the reader is meant to press. */
@media (max-width: 900px) {
  .hero { padding-bottom: clamp(178px, 34vw, 300px); }

  .hero-ghost {
    /* Anchored below the argument block, so it can never sit behind the
       one thing the reader is meant to press, and ending on the same
       right rail as the other two objects that break the grid. */
    top: calc(100% + clamp(34px, 8vw, 56px));
    bottom: auto;
    left: auto;
    right: var(--bleed-right);
    font-size: clamp(4.5rem, 2rem + 16vw, 8rem);
  }
}

.eyebrow {
  font: 500 0.8125rem/1.4 var(--font-body);
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-quiet);          /* indigo is reserved for meaning */
  margin-bottom: 28px;
}

h1 {
  font: 700 clamp(2.125rem, 0.75rem + 4.4vw, 4.25rem)/1.06 var(--font-display);
  letter-spacing: -0.03em;
  max-width: 40ch;
}

.h1-turn {
  display: block;
  margin-left: calc(var(--indent) / 4);
  color: var(--text-soft);
  text-wrap: balance;
}

.sub {
  margin-top: 30px; max-width: 44ch;
  font-size: clamp(1.0625rem, 1rem + 0.4vw, 1.1875rem);
  color: var(--text-muted);
}

.cta {
  display: inline-block; margin-top: 44px;
  padding: 15px 24px;
  background: var(--accent-solid); color: var(--paper);
  font: 500 1.0625rem/1.3 var(--font-display);
  border-radius: 2px;
  transition: background-color 160ms var(--ease);
}
.cta:hover, .cta:active { background: var(--accent-solid-hover); }

/* ---------- 2. problem (deliberately still) ---------- */

.problem { padding-block: clamp(72px, 11vh, 128px); border-top: 1px solid var(--hairline); }

.statement {
  font: 500 clamp(1.375rem, 1.05rem + 1.6vw, 2.0625rem)/1.35 var(--font-display);
  letter-spacing: -0.01em;
  max-width: 38ch;
  margin-left: var(--indent);
}

/* ---------- shared heading scale ---------- */

h2 {
  font: 700 clamp(1.6875rem, 1.3rem + 1.9vw, 2.5625rem)/1.1 var(--font-display);
  letter-spacing: -0.015em;
}

.lede {
  margin-top: var(--lede-gap);
  max-width: var(--measure);
  font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.1875rem);
  color: var(--text-muted);
}

/* ---------- 3. proof, on its own surface ---------- */

.proof { padding-block: clamp(96px, 14vh, 176px) clamp(88px, 12vh, 148px); }

.num {
  font-family: var(--font-display);
  font-weight: 700;
  font-variant-numeric: proportional-nums;
  letter-spacing: -0.035em;
  line-height: 0.95;
  display: block;
}

/* the moment breaks the text column: it starts on the rail and runs out
   past the grid. Everything under it returns to measure. */
.stats {
  --rail: calc(max(0px, (100% - 1160px)) / 2 + var(--pad));
  --air: var(--bleed-right);
  margin-block: clamp(56px, 9vh, 104px) 0;
  padding-left: var(--rail);
  padding-right: var(--air);
}
.stats > .stat-row,
.stats > .proof-note,
.stat-lead .stat-label {
  margin-right: calc(var(--pad) - var(--air));
}
.stats > .stat-row { max-width: calc(1160px - 2 * var(--pad)); }

@property --measured {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 103%;
}

/* the whole figure is always present; the measurement fills it in, so no
   scroll position can ever show a complete number that is not 809,581 */
.num-lead {
  position: relative;
  font-size: clamp(4.25rem, 1rem + 16.5vw, 16.25rem);
  line-height: 1;
  letter-spacing: -0.045em;
  margin-left: -0.02em;
  --measured: 103%;
  /* the second stop sits past the rule, so the rule is never rendered
     through the faint half of its own mask */
  mask-image: linear-gradient(90deg,
    #000 0 calc(var(--measured) + 3px),
    rgba(0, 0, 0, 0.52) calc(var(--measured) + 3px), rgba(0, 0, 0, 0.52) 100%);
}

.num-lead::after {
  content: "";
  position: absolute; top: 0; bottom: 0; left: var(--measured);
  width: 2px; background: var(--accent); opacity: 0;
}

/* Negative letter-spacing pulls the element box inside the last glyph,
   so the box is not the ink. These values are calibrated against the
   rendered ink, measured from screenshots, not against the box. */
@supports (font-size: 1cqi) {
  .stats { container-type: inline-size; }
  .num-lead { font-size: 28.7cqi; }
}

@media (max-width: 600px) {
  @supports (font-size: 1cqi) {
    .num-lead { font-size: 28.0cqi; }
  }
}

.stat-label {
  display: block; margin-top: 12px;
  font: 400 0.9375rem/1.4 var(--font-body);
  color: var(--text-muted);
}
.stat-lead .stat-label { margin-top: 10px; font-size: 1.0625rem; color: var(--text); }

/* No pin here. It was a second device on an object that already has one
   (the measurement), and because the arriving row created a stacking
   context it painted straight through the pinned figure. The number
   holds attention by being the number. */

.stat-row {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 32px clamp(40px, 7vw, 96px);
  margin-top: clamp(40px, 8vh, 96px);
  padding-top: clamp(28px, 4vh, 40px);
  border-top: 1px solid var(--hairline);
}
/* supporting figures stay under the page's own headline */
.stat .num { font-size: clamp(1.875rem, 1.4rem + 2.1vw, 3.25rem); }

.proof-note {
  margin-top: clamp(56px, 9vh, 104px);
  max-width: var(--measure);
  color: var(--text-muted);
}

@media (max-width: 479px) {
  .stat-row { grid-template-columns: 1fr; gap: 28px; }
}

/* ---------- 4. the ratio, drawn to scale ---------- */

.ratio { padding-block: clamp(96px, 14vh, 168px) clamp(72px, 10vh, 120px); }

.chart {
  --rail: calc(max(0px, (100% - 1160px)) / 2 + var(--pad));
  timeline-scope: --chart;  /* the caption is a sibling of the plot */
  margin-block: clamp(56px, 8vh, 88px) clamp(40px, 6vh, 64px);
  padding-left: var(--rail);
  padding-right: var(--bleed-right);
}

.bar-row + .bar-row { margin-top: clamp(28px, 4vh, 48px); }

.bar-head {
  display: flex; align-items: baseline; gap: 12px;
  margin-bottom: 14px;
}
/* a bar's label may not outrank the heading it sits under */
.bar-value {
  font: 700 clamp(1.375rem, 1rem + 1.4vw, 2.125rem)/1 var(--font-display);
  font-variant-numeric: proportional-nums;
  letter-spacing: -0.03em;
}
.bar-name {
  font: 400 0.9375rem/1.4 var(--font-body);
  color: var(--text-muted);
}

/* Each bar is exactly as long as its own value: no empty track, so no
   invented maximum. The bar itself is a ghost of the true length, and
   the ink fills it in, so the real 8.3:1 is readable at every scroll
   position and a half-drawn pair can never state a false ratio.
   One material for both: length is the only variable that differs. */
.bar {
  display: block;
  height: clamp(16px, 1.8vw, 24px);
  /* The ghost carries the true length, so it has to clear 3:1 against
     the page or the real ratio is only readable on a good screen in a
     dark room. A background, not an opacity: opacity would dim the ink
     filling it in. */
  background: color-mix(in srgb, var(--text) 52%, transparent);
}
.bar-fill {
  display: block; width: 100%; height: 100%;
  background: var(--text);
  transform-origin: 0 50%;
}

/* one scale for both. 37.7 / 313.5 = 12.0255% */
.bar-in .bar  { width: 12.0255%; }
.bar-out .bar { width: 100%; }

.chart-punch {
  margin-top: clamp(32px, 5vh, 56px);
  display: flex; align-items: baseline; gap: 16px;
  font: 400 clamp(1rem, 0.9rem + 0.5vw, 1.25rem)/1.3 var(--font-body);
  color: var(--text-muted);
}
/* the derived figure stays under the H1 and under the conclusion: the
   hierarchy may not peak where the argument is only being set up */
.chart-figure {
  font: 700 clamp(1.75rem, 1.2rem + 2.4vw, 3rem)/0.9 var(--font-display);
  font-variant-numeric: proportional-nums;
  letter-spacing: -0.04em;
  color: var(--accent);
}

.chart-note { max-width: var(--measure); color: var(--text-muted); }
.chart-note + .chart-note { margin-top: 14px; }

/* ---------- 5. the hinge: conclusion alone, one register up ---------- */

.hinge { padding-block: clamp(112px, 18vh, 232px); }

.thesis {
  font: 700 clamp(2rem, 1.1rem + 3.4vw, 3.5rem)/1.2 var(--font-display);
  letter-spacing: -0.025em;
  max-width: 20ch;
  margin-left: var(--indent);
}
/* no accent here: indigo is reserved for measured or clickable. This
   sentence gets its weight from scale and from standing alone. */
.thesis-turn { display: block; }

@media (max-width: 767px) {
  .statement, .thesis, .h1-turn { margin-left: 0; }
}

/* ---------- 6. how we work ---------- */

.how { padding-block: clamp(64px, 9vh, 104px) clamp(96px, 13vh, 152px); }

.steps { list-style: none; margin-top: clamp(48px, 7vh, 72px); max-width: 760px; }

.step { display: grid; grid-template-columns: 76px 1fr; gap: 24px; }
.step + .step { margin-top: 44px; padding-top: 44px; border-top: 1px solid var(--hairline); }

.step-num {
  font: 500 1.0625rem/1.5 var(--font-display);
  font-variant-numeric: tabular-nums;
  color: var(--text-quiet);
}
.step-body h3 {
  font: 700 clamp(1.1875rem, 1.05rem + 0.7vw, 1.5rem)/1.25 var(--font-display);
  letter-spacing: -0.01em;
}
.step-body p { margin-top: 12px; max-width: var(--measure); color: var(--text-muted); }

@media (max-width: 560px) {
  .step { grid-template-columns: 1fr; gap: 10px; }
}

/* ---------- 7. GRVTY, as a poster ---------- */

.grvty { padding-block: clamp(104px, 16vh, 192px); }

/* One column, mark above the heading, on the same rail as everything
   else. The poster grid belongs to the team section, where a 320px mark
   fills the column; a 101px stamp in a 440px column would promise the
   same composition and leave 276px of nothing beside it. */
.grvty-in {
  display: block;
}

/* never rendered above its own native ink resolution: the source asset
   is 400px, so anything larger would be an upscaled bitmap on a page
   whose entire argument is exactness */
/* 1:1 at device pixel ratio 2, because the source is a 400px bitmap and
   this is the screen the founder reads the cold email on.
   TODO: ask GRVTY for the vector and inline it as an svg. */
.grvty-mark img { width: min(100%, 101px); height: auto; }

/* the partner's section does not outrank the page's own voice: it sits
   on the same heading scale as every other section */
.grvty-body {
  margin-top: var(--lede-gap);
  max-width: 42ch;
  font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.1875rem);
  line-height: 1.6;
  color: var(--text-muted);
}

.grvty-link {
  display: inline-block; margin-top: 40px;
  font: 500 1.1875rem/1.3 var(--font-display);
  padding-bottom: 4px;
  border-bottom: 2px solid var(--invert-rule);
  transition: color 160ms var(--ease);
}
.grvty-link:hover { color: var(--invert-hover); }
.grvty-link .ext { margin-left: 6px; }

.grvty-mark { display: block; margin-bottom: clamp(32px, 4vh, 48px); }

@media (max-width: 820px) {
  /* stays below the sender's own mark at every width */
  .grvty-mark img { width: min(100%, 96px); }
}

/* ---------- 8. team ---------- */

.team { padding-block: clamp(88px, 13vh, 152px); }

/* the sender gets the poster. The partner's mark is capped at its own
   native resolution, so on this page the biggest sharp thing is Moda. */
.team-in {
  display: grid;
  grid-template-columns: minmax(0, 0.42fr) minmax(0, 1fr);
  align-items: start;
  gap: clamp(40px, 6vw, 88px);
}

/* floor of 220px keeps the sender's mark above the partner's 202px cap
   in the 820 to 918 band, where a vw-based size would otherwise dip
   under it (iPad Pro 11 landscape is 834, Surface is 912) */
.team-mark img { width: clamp(220px, 22vw, 320px); height: auto; }

@media (max-width: 820px) {
  .team-in { grid-template-columns: minmax(0, 1fr); }
  .team-mark img { width: clamp(148px, 34vw, 200px); }
}

.people {
  margin-top: clamp(48px, 7vh, 72px);
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 40px clamp(40px, 6vw, 88px);
  max-width: 720px;
}
.person h3 {
  font: 700 clamp(1.1875rem, 1.05rem + 0.7vw, 1.5rem)/1.25 var(--font-display);
  letter-spacing: -0.01em;
}
.person p { margin-top: 8px; color: var(--text-muted); }

@media (max-width: 560px) { .people { grid-template-columns: 1fr; } }

/* ---------- 9. close ---------- */

.close {
  padding-block: clamp(104px, 16vh, 192px) clamp(96px, 14vh, 160px);
  border-top: 1px solid var(--hairline);
}
/* the one amplified heading belongs to the page's own ask */
.close h2 { font-size: var(--h2-major); max-width: 18ch; }

/* the underline exists at rest: on touch there is no hover, and this is
   the only conversion on the page */
.close-mail {
  display: inline-block; margin-top: 48px;
  font: 700 clamp(1.4375rem, 1rem + 2.2vw, 2.5625rem)/1.2 var(--font-display);
  letter-spacing: -0.015em;
  color: var(--accent);
  border-bottom: 3px solid color-mix(in srgb, var(--accent) 42%, transparent);
  transition: border-color 160ms var(--ease);
}
.close-mail:hover { border-bottom-color: var(--accent); }

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

.footer { border-top: 1px solid var(--hairline); padding-block: 26px; }

.foot-in {
  display: flex; flex-wrap: wrap; row-gap: 10px;
  font: 400 0.8125rem/1.5 var(--font-body);
  color: var(--text-muted);
}
.foot-in > * { padding-inline: 16px; border-left: 1px solid var(--hairline); }
.foot-in > *:first-child { padding-left: 0; border-left: none; }
.foot-in a:hover { color: var(--accent); }

@media (max-width: 640px) {
  .foot-in { flex-direction: column; row-gap: 6px; }
  .foot-in > * { padding-inline: 0; border-left: none; }
}

/* ============================================================
   Motion

   Five sections move, four are deliberately still. Every movement
   below encodes something: a measurement, a comparison, a sequence,
   or a conclusion that has to arrive after its evidence. Nothing
   plays on its own; every value is tied to scroll position, so the
   reader is the one moving it. No scroll listeners, no hijacking.
   ============================================================ */

@keyframes arrive {
  from { opacity: 0; transform: translateY(18px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .cta, .nav-link, .link, .grvty-link, .close-mail, .masthead::before, .masthead-in { transition: none; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {

    /* the bar has no surface until something needs to pass under it,
       so at rest the light reaches the top of the page uncut */
    .masthead::before {
      animation: chrome-in linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 48px;
    }
    @keyframes chrome-in { from { opacity: 0; } to { opacity: 1; } }

    /* the light is a place: you leave it behind */
    .hero::before {
      animation: light-leave linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 80vh;
    }
    @keyframes light-leave { from { opacity: 1; } to { opacity: 0; } }

    /* WOW 1, part one. The unmeasured number is handed over to the
       proof section: it recedes exactly as you travel towards it. */
    .hero-ghost {
      animation: hand-over linear both;
      animation-timeline: scroll(root block);
      animation-range: 0 70vh;
    }
    /* No from-opacity. A from-value with fill: both overrides the
       declaration underneath it at scroll 0, which silently made the
       rest value of .hero-ghost dead code. The implicit from is the
       declared --ghost-op, so there is exactly one source of truth. */
    @keyframes hand-over {
      to { opacity: 0; transform: translateY(-24px); }
    }

    /* WOW 1, part two. The measurement fills the figure from the left
       with the rule that does it riding at the front. Expressed in
       screen heights, so it always completes while the number is
       clear of the bar, on any viewport. */
    .stats { view-timeline: --stats block; }

    /* the range is tied to the figure itself, not to the viewport: the
       number is sized by width (cqi) so a vh window drifts against it */
    .num-lead {
      animation: measure linear both;
      animation-timeline: --stats;
      animation-range: entry 22% entry 96%;
    }
    @keyframes measure { from { --measured: 0%; } to { --measured: 103%; } }

    .num-lead::after {
      animation: measure-edge linear both;
      animation-timeline: --stats;
      animation-range: entry 22% entry 96%;
    }
    @keyframes measure-edge {
      from { opacity: 0.85; }
      84%  { opacity: 0.85; }
      to   { opacity: 0; }
    }

    /* The proof section does not fade in. Three facts of the same rank
       and a unit label encode no sequence, no measurement and no
       conclusion, so under this page's own rule they get no movement.
       The measurement above them is the event.

       Every remaining arrival is timed on its own box with a
       subject-relative range: for a subject shorter than the viewport an
       entry-range is only as long as the element itself, so a
       cover-in-pixels window would land entirely in the bottom strip of
       the screen and the reader would never see it happen. */

    /* WOW 2. The comparison is drawn with the same rule that measured
       the number. Both bars are on one scale, so 8.3x is a length you
       see before it is a figure you read. */
    .chart-plot { view-timeline: --chart block; }

    .bar-fill {
      animation: draw linear both;
      animation-timeline: --chart;
      transform: scaleX(0);
    }
    @keyframes draw { to { transform: scaleX(1); } }

    /* Both bars leave at the same instant and travel at the same speed,
       so the long one simply takes 8.3x as long to arrive. Two things
       drawn one after the other cannot compare a speed; two things that
       depart together can. The ratio is the motion, not just the shape. */
    .bar-in .bar-fill  { animation-range: cover 26vh cover 30.8vh; }
    .bar-out .bar-fill { animation-range: cover 26vh cover 66vh; }

    /* the ratio lands only once both bars exist to compare */
    .chart-punch {
      animation: arrive linear both;
      animation-timeline: --chart;
      animation-range: cover 62vh cover 78vh;
    }

    /* The ask does not animate. It is the only conversion on the page
       and it sits in the last section, so any scroll-linked range there
       depends on scroll that may not exist: measured, the address
       reached 0.55 opacity on a 1440px-tall window and 0.09 on 2400px,
       and stayed there. An element that a window height can leave
       permanently dimmed has no business being animated, and its
       arrival encoded nothing the section did not already say. */

    /* the conclusion arrives after its evidence */
    .hinge { view-timeline: --hinge block; }
    .thesis-turn {
      animation: arrive linear both;
      animation-timeline: --hinge;
      animation-range: cover 34vh cover 58vh;
    }

    /* the only section whose content is a numbered order gets one.
       Each step is timed on itself, so the third one does not play out
       below the fold on a short viewport. */
    .step {
      view-timeline: --step block;
      animation: arrive linear both;
      animation-timeline: --step;
      animation-range: entry 45% contain 40%;
    }

    /* Deliberately still: the problem statement, the whole proof
       section, the inverted GRVTY panel, the team, the close and the
       footer. A page that moves everywhere is a page where nothing
       moves. Four things move, and each of them encodes something. */
  }
}
