/* Retainory — landing page.

   The page has one idea: a single line runs the whole narrative column. Its
   vertical position is time; its horizontal position inside a fixed 250px
   corridor is the probability you can still recall the material, on a labelled
   50–100% scale. The line falls through the first section, is caught by timed
   reviews in the second, holds through the third and fourth, and then leaves the
   corridor entirely to become the tail of the distribution at "Set the Curve".
   After that the page drops the device and turns into plain documentation.

   No JavaScript: _headers ships a Content-Security-Policy with no script-src, so
   every path here is pre-computed by scratchpad gen_geometry.py from the app's own
   FSRS-6 retrievability function. The reveal is a CSS scroll-driven animation and
   degrades to "already drawn" wherever that isn't supported. */

/* ============================================================
   NARRATIVE COLUMN + THE SPINE
   ============================================================ */

.narrative {
  position: relative;
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 var(--s6);

  /* How far above the viewport's bottom edge the drawn end of the line sits.
     The reveal's whole job is to keep the line level with what the reader can
     see, so this is the one number that decides the feel: at 20vh the line is
     always complete through the top 80% of the screen — well past anything
     being read — and the drawing itself happens in the bottom fifth, where
     it's visible as motion rather than as a line that was simply already
     there. Larger sends the drawing higher up the screen (less already drawn
     at first paint, but the line stops further above the fold); smaller
     leaves more of the hero's curve drawn before the reader scrolls at all.
     Nothing downstream depends on the value — see .spine-track. */
  --spine-lead: 20vh;
}

/* The reveal's timeline subject: exactly as tall as the narrative column and
   shifted down the page by --spine-lead, which is what puts the "drawn this
   far" line that distance above the viewport's bottom edge — the offset lives
   in the box, so no part of the geometry has to know the viewport height.
   It is also the spine's own parent, so the timeline name resolves by
   ordinary ancestry and doesn't need timeline-scope.

   The svg inside is pulled back up by the same amount, so where the line is
   painted is unchanged — with the animation, without it, and in the fallbacks
   below alike. */
.spine-track {
  position: absolute;
  left: var(--s6);
  top: var(--spine-lead);
  width: 250px;
  height: 100%;
  pointer-events: none;
}

.spine {
  position: absolute;
  left: 0;
  top: calc(-1 * var(--spine-lead));
  width: 100%;
  height: 100%;
}

.spine__path {
  fill: none;
  stroke: url(#spine-grad);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

/* The corridor's x axis is a real scale, so it gets labelled once, at the top. */
.spine__scale {
  position: absolute;
  left: var(--s6);
  top: 232px;
  width: 250px;
  height: 22px;
  pointer-events: none;
}

.spine__scale span {
  position: absolute;
  top: 0;
  font-family: var(--mono);
  font-size: var(--t-micro);
  color: var(--tx2);
}

.spine__scale span:nth-child(1) {
  left: 28px;
}
.spine__scale span:nth-child(2) {
  left: 188px;
}
.spine__scale span:nth-child(3) {
  left: 28px;
  top: 16px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.75;
}

.node {
  position: absolute;
  width: 9px;
  height: 9px;
  margin: -4.5px 0 0 -4.5px;
  border-radius: 50%;
  background: var(--brand);
  pointer-events: none;
}

.node--1 {
  left: calc(var(--s6) + 200.0px);
  top: 44.832%;
}

.node--2 {
  left: calc(var(--s6) + 200.0px);
  top: 47.239%;
}

.node--3 {
  left: calc(var(--s6) + 200.0px);
  top: 51.688%;
}

.node--4 {
  left: calc(var(--s6) + 200.0px);
  top: 58.823%;
}

.node--5 {
  left: calc(var(--s6) + 200.0px);
  top: 68.861%;
}

.node--6 {
  left: calc(var(--s6) + 200.0px);
  top: 81.925%;
}

/* ============================================================
   HERO PHOTOGRAPH
   ============================================================ */

/* Positioned against <body> rather than <main> so it runs up behind the nav —
   the nav is in its own .shell above <main>, so anything scoped to <main> would
   start below it and leave a band of bare ink across the top of the image. */
body {
  position: relative;
}

.hero-media {
  position: absolute;
  top: 0;
  right: 0;
  width: min(62%, 1180px);
  height: 900px;
  overflow: hidden;
  z-index: 0;
  pointer-events: none;
}

.hero-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 32%;
}

/* Two overlays, both painted in --ink itself.
   This is the one gradient on the page that isn't decoration, and it is worth
   being explicit about why, because the house rule is "gradients only on the
   wordmark": its job is to stop a rectangle being a rectangle. A photograph
   dropped on this ground has four hard edges, and hard edges are what make an
   image read as pasted onto a layout rather than placed in one. Every stop is
   --ink at some alpha, so there is no colour seam to find — the image simply
   stops existing partway across.

   It also does the legibility work. The horizontal ramp is opaque across the
   whole measure of the hero copy and only opens up past it, which is why the
   photograph has to be a naturally dark, low-key frame: on a bright image these
   same stops leave the headline sitting on mid-grey. */
.hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(
      to bottom,
      transparent 58%,
      rgba(var(--ink-rgb), 0.6) 82%,
      var(--ink) 100%
    ),
    linear-gradient(
      to right,
      var(--ink) 8%,
      rgba(var(--ink-rgb), 0.94) 34%,
      rgba(var(--ink-rgb), 0.55) 60%,
      rgba(var(--ink-rgb), 0.15) 85%,
      transparent 100%
    );
}

/* Everything the reader reads sits above the image. */
main,
.nav {
  position: relative;
}
main {
  z-index: 1;
}
.nav {
  z-index: 2;
  /* The shared rule underlines the nav. Over a photograph that hairline becomes a
     rule drawn across someone's face, so the landing page drops it. */
  border-bottom: 0;
}

/* ============================================================
   NARRATIVE SECTIONS
   The min-heights are load bearing: they are what keeps the line's events
   aligned with the words next to them without measuring anything at runtime.
   ============================================================ */

/* 250px of corridor plus a 60px gutter, so the line never crowds the text. */
.hero,
.proof,
.stage {
  padding-left: 310px;
}

.hero {
  min-height: 800px;
  display: grid;
  align-content: center;
  padding-top: var(--s9);
  padding-bottom: var(--s7);
}

.hero__title {
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--t-hero);
  line-height: 1;
  letter-spacing: -0.04em;
  margin: var(--s5) 0 0;
  max-width: 13ch;
  text-wrap: balance;
}

.hero__title em {
  display: block;
  font-style: normal;
  font-weight: 300;
  color: var(--tx2);
}

.hero__sub {
  font-size: var(--t-t3);
  line-height: 1.55;
  color: var(--tx3);
  max-width: 46ch;
  margin: var(--s6) 0 var(--s4);
}

.hero__hint {
  font-family: var(--mono);
  font-size: var(--t-caption);
  color: var(--tx2);
  margin: 0 0 var(--s7);
  max-width: 46ch;
}

.proof {
  min-height: 140px;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s5);
  align-content: center;
  border-top: 1px solid var(--stroke-soft);
  border-bottom: 1px solid var(--stroke-soft);
}

.proof__item b {
  display: block;
  font-size: var(--t-label);
  font-weight: 600;
  margin-bottom: 2px;
}

.proof__item span {
  font-size: var(--t-caption);
  color: var(--tx3);
  line-height: 1.55;
}

.stage {
  padding-top: var(--s10);
  padding-bottom: var(--s10);
}

/* These four must stay in step with the section boundaries in gen_geometry.py —
   together they are what keeps each event on the line beside the words describing it. */
.stage--decay {
  min-height: 821px;
}
.stage--reviews {
  min-height: 804px;
}
.stage--weak {
  min-height: 652px;
}
.stage--ready {
  min-height: 710px;
}

.stage__grid {
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
  gap: var(--s8);
  align-items: center;
}

.stage__marker {
  display: flex;
  align-items: center;
  gap: var(--s3);
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--tx2);
  margin: 0;
}

.stage__marker::before {
  content: "";
  width: 22px;
  height: 1px;
  background: var(--brand);
  flex: 0 0 22px;
}

.stage h2 {
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--t-big);
  line-height: 1.08;
  letter-spacing: -0.035em;
  margin: var(--s4) 0 var(--s4);
  max-width: 16ch;
  text-wrap: balance;
}

.stage p {
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--tx3);
  max-width: 48ch;
  margin: 0 0 var(--s4);
}

.stage p:last-child {
  margin-bottom: 0;
}

/* ---------- panels: the app's own components, at full size ---------- */

.panel {
  background: var(--surface);
  border: 1px solid var(--stroke);
  border-radius: var(--r-lg);
  padding: var(--s6);
  box-shadow: var(--elev);
}

.panel__cap {
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--tx2);
  margin: 0 0 var(--s4);
}

.panel__foot {
  font-family: var(--mono);
  font-size: var(--t-micro);
  color: var(--tx2);
  margin: var(--s4) 0 0;
  line-height: 1.6;
}

/* interval growth */

.intervals {
  display: grid;
  gap: var(--s3);
  margin: 0;
}

.intervals div {
  display: grid;
  grid-template-columns: 62px 1fr 74px;
  align-items: center;
  gap: var(--s4);
  padding-bottom: var(--s3);
  border-bottom: 1px solid var(--stroke-soft);
}

.intervals div:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.intervals dt {
  font-family: var(--mono);
  font-size: var(--t-caption);
  color: var(--tx2);
}

.intervals dd {
  margin: 0;
  font-family: var(--mono);
  font-size: var(--t-caption);
  color: var(--tx);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.intervals i {
  display: block;
  height: 3px;
  border-radius: 2px;
  background: var(--brand);
}

.iv-1 {
  width: 6%;
}
.iv-2 {
  width: 14%;
}
.iv-3 {
  width: 32%;
}
.iv-4 {
  width: 62%;
}
.iv-5 {
  width: 100%;
}

/* how the cards get there */

.chain {
  margin: var(--s6) 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  font-size: var(--t-caption);
  color: var(--tx2);
  max-width: 48ch;
}

.chain li {
  display: flex;
  align-items: center;
  gap: var(--s4);
}

.chain li + li::before {
  content: "";
  width: 14px;
  height: 1px;
  background: var(--stroke);
}

/* ---------- product screenshots ----------

   These are captures of the running app, not recreations. They are cropped so
   the app's own background sits in the margins — and because the site's --ink
   IS that colour (both come from the .signal palette), the cards' real rounded
   corners read correctly with nothing added here: no border, no radius, no
   device bezel. A screenshot in a fake chrome frame is the tell of a site that
   had nothing real to show, so the crop does the framing and CSS does none.

   Where a screenshot replaces a panel, the panel chrome goes with it: two
   nested borders 28px apart is the "screenshot inside a screenshot" look. */

.shot {
  margin: 0;
}

.shot img {
  display: block;
  width: 100%;
  height: auto;
}

/* ---------- photographs ----------

   A different object from .shot above, and it wants the opposite treatment.
   A screenshot is frameless because its own crop does the framing: the app's
   ink behind its rounded corners IS this page's ground, so an added border
   would be a second frame around a picture that already has one.

   A photograph shares nothing with the page. It is a lit, warm, rectangular
   thing that would otherwise stop dead against the ink on four hard edges,
   which is the look of an image pasted onto a layout rather than placed in
   one. So it gets a real plate: the closed scale's largest radius, a hairline
   so the bright edges have somewhere to end, and the neutral two-layer
   elevation — never tinted, per the house rules in RetainComponents.swift.

   The file is exported pre-cropped to 4:5, centred on the device, so it carries
   no pixels the layout would throw away. aspect-ratio and object-fit are kept
   anyway: they cost nothing, they reserve the box before the image loads (no
   layout shift on a page whose spine geometry is measured), and they keep the
   slot safe if the file is ever swapped for one framed differently. */
.photo {
  margin: 0;
  border-radius: var(--r-lg);
  overflow: hidden;
  border: 1px solid var(--stroke-soft);
  box-shadow: var(--elev);
}

.photo img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center;
}

/* ============================================================
   SET THE CURVE — the line leaves the corridor
   ============================================================ */

.curve {
  border-top: 1px solid var(--stroke-soft);
  background: var(--recessed);
  padding: var(--s10) 0;
  margin-top: var(--s9);
}

.curve__grid {
  display: grid;
  grid-template-columns: 430px minmax(0, 1fr);
  gap: var(--s8);
  align-items: center;
}

.curve h2 {
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--t-big);
  line-height: 1.1;
  letter-spacing: -0.035em;
  margin: var(--s4) 0 var(--s4);
  text-wrap: balance;
}

.curve p {
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--tx3);
  margin: 0 0 var(--s6);
}

.bell {
  width: 100%;
  height: auto;
}

.bell__axis {
  stroke: rgba(255, 255, 255, 0.1);
}

.bell__fill {
  fill: url(#bell-grad);
  opacity: 0.09;
}

.bell__tail {
  fill: url(#bell-grad);
  opacity: 0.2;
}

.bell__line {
  fill: none;
  stroke: url(#bell-grad);
  stroke-width: 2;
}

.bell__sigma {
  stroke: rgba(255, 255, 255, 0.14);
  stroke-dasharray: 3 4;
}

.bell__tick {
  font-family: var(--mono);
  font-size: 11px;
  fill: var(--tx2);
}

.bell__marker circle {
  fill: var(--brand);
}

.bell__marker text {
  font-family: var(--ui);
  font-size: 12px;
  font-weight: 600;
  fill: var(--tx);
  text-anchor: middle;
}

.figcap {
  font-family: var(--mono);
  font-size: var(--t-micro);
  color: var(--tx2);
  margin: var(--s4) 0 0;
  line-height: 1.6;
}

/* ============================================================
   DOCUMENTATION MODE — the device is spent; the page goes plain
   ============================================================ */

.section {
  padding: var(--s10) 0;
  border-top: 1px solid var(--stroke);
}

.section__head {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
  gap: var(--s7);
  margin-bottom: var(--s7);
}

.section__head h2 {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(24px, 2.8vw, 34px);
  letter-spacing: -0.03em;
  line-height: 1.12;
  margin: 0 0 var(--s3);
  max-width: 20ch;
  text-wrap: balance;
}

.section__head p {
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--tx3);
  max-width: var(--measure);
  margin: 0;
}

.rh {
  font-family: var(--mono);
  font-size: var(--t-micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--tx2);
  padding-top: 6px;
  border-top: 1px solid var(--tx);
  margin: 0;
  height: fit-content;
}

.principle {
  display: grid;
  grid-template-columns: 200px minmax(0, 1.05fr) minmax(0, 1fr);
  gap: var(--s7);
  padding: var(--s6) 0;
  border-top: 1px solid var(--stroke-soft);
  align-items: start;
}

.principle h3 {
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--t-t3);
  letter-spacing: -0.02em;
  margin: 0 0 var(--s2);
}

.principle p {
  font-size: var(--t-label);
  line-height: 1.65;
  color: var(--tx3);
  margin: 0;
}

.principle__impl {
  padding: var(--s4) var(--s5);
  background: var(--recessed);
  border: 1px solid var(--stroke-soft);
  border-radius: var(--r-card);
}

.principle__impl b {
  display: block;
  font-family: var(--mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--tx2);
  margin-bottom: var(--s2);
}

.principle__impl p {
  color: var(--tx);
}

/* The principle definitions are quoted from the literature, so the clause the
   product actually acts on is lifted out of the grey rather than paraphrased
   away. Weight stays at 500 — bold inside 15px body text is a shout. */
.principle p strong {
  color: var(--tx);
  font-weight: 500;
}

/* ---------- citations, as a disclosure ----------

   Sixteen references is the right amount of evidence and the wrong amount of
   page: open, it was the longest single run of text on the site, sitting between
   the argument and the pricing. Closed by default, it becomes one line that says
   the evidence exists and how much of it there is.

   <details>/<summary> because the site ships no JavaScript — _headers sets a CSP
   with no script-src for this origin, so a scripted accordion would not merely be
   against the grain here, it would be silently dead. The browser does the whole
   thing, keyboard and screen-reader behaviour included.

   The accent fill is the one place on the page that inverts: --brand ground with
   --on-brand type, the pairing the token set already defines for a brand-filled
   surface. It measures 9.22:1, comfortably AAA. Every link inside has to be
   restated, because .refs a is --brand — the same colour as this background, so
   inherited it would be invisible. */
.cites {
  background: var(--brand);
  color: var(--on-brand);
  border-radius: var(--r-card);
  margin: var(--s7) 0 0;
  overflow: hidden;
}

.cites > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s4) var(--s5);
  cursor: pointer;
  font-family: var(--mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  /* both spellings: the marker is a ::-webkit-details-marker in older Safari and
     a ::marker via list-style everywhere else */
  list-style: none;
}

.cites > summary::-webkit-details-marker {
  display: none;
}

.cites > summary:hover {
  background: var(--brand-lift);
}

.cites__n {
  opacity: 0.72;
  letter-spacing: 0.1em;
}

/* Chevron, drawn rather than shipped: two borders on an empty box, rotated. */
.cites > summary::after {
  content: "";
  width: 7px;
  height: 7px;
  flex: 0 0 7px;
  margin-right: 3px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 0.16s linear;
}

.cites[open] > summary::after {
  transform: translateY(1px) rotate(-135deg);
}

/* Two columns, for two reasons that happen to point the same way. .refs carries
   max-width: var(--measure) for its life on the ink, which inside a shell-wide
   panel would leave half of it empty accent. And sixteen references stacked in
   one column is a tall saturated field — halving the rows halves how much of the
   page this colour owns. column-span on the group heads keeps "Cited above" and
   "Further reading" as full-width rules with their own items flowing beneath. */
.cites .refs {
  display: block;
  max-width: none;
  columns: 2;
  column-gap: var(--s8);
  margin: 0;
  padding: 0 var(--s5) var(--s5);
  border-top: 0;
  color: var(--on-brand);
}

.cites .refs p {
  margin: 0 0 var(--s3);
  break-inside: avoid;
}

.cites .refs__head {
  column-span: all;
  margin-top: var(--s5);
}

.cites .refs__head:first-child {
  margin-top: 0;
}

.cites .refs a {
  color: var(--on-brand);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.cites .refs__head {
  color: var(--on-brand);
  border-bottom: 1px solid rgba(4, 33, 30, 0.28);
}

.refs {
  display: grid;
  gap: var(--s3);
  margin: var(--s7) 0 0;
  padding: var(--s5) 0 0;
  border-top: 1px solid var(--stroke-soft);
  font-size: var(--t-caption);
  color: var(--tx2);
  max-width: var(--measure);
}

.refs a {
  color: var(--brand);
  text-underline-offset: 3px;
}

/* Two groups: the works the principles cite, then everything else. Without the
   split the reader can't tell which references back a claim on this page and
   which are just recommended. */
.refs__head {
  font-family: var(--mono);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--tx);
  margin-top: var(--s5);
  padding-bottom: var(--s2);
  border-bottom: 1px solid var(--stroke-soft);
}

.refs__head:first-child {
  margin-top: 0;
}

.band {
  padding: var(--s10) 0;
  border-top: 1px solid var(--stroke);
  background: var(--recessed);
}

.price {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
  gap: var(--s7);
  align-items: start;
}

.price__amount {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(40px, 6vw, 68px);
  letter-spacing: -0.04em;
  line-height: 1;
  margin: 0 0 var(--s4);
}

.price p {
  font-size: var(--t-body);
  line-height: 1.7;
  color: var(--tx3);
  max-width: var(--measure);
  margin: 0 0 var(--s5);
}

.closer {
  text-align: center;
  padding: var(--s10) 0;
  border-top: 1px solid var(--stroke);
}

.closer h2 {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(44px, 8vw, 104px);
  letter-spacing: -0.05em;
  line-height: 0.95;
  margin: var(--s4) 0 var(--s5);
}

.closer p {
  font-size: var(--t-t3);
  color: var(--tx3);
  margin: 0 auto var(--s7);
  max-width: 40ch;
}

.closer .btns {
  justify-content: center;
}

/* ============================================================
   MOTION — two moments, both answering scroll. Everything degrades to
   "already in its final state" without scroll-driven animation support.
   ============================================================ */

/* Percentages here are positions down the narrative column itself — 40% is
   40% of the way down it — because the timeline this runs on is exactly one
   column-height long (see .spine__path). What isn't linear is the value: each
   stop is pinned to the spine's real arc-length fraction at that y, since the
   review stages' up-and-down teeth carry far more arc length per pixel than
   the smooth hero/decay descent does. */
@keyframes spine-draw {
  0.0% { stroke-dashoffset: 1000.0; }
  7.637% { stroke-dashoffset: 1000.0; }
  23.956% { stroke-dashoffset: 838.2; }
  24.873% { stroke-dashoffset: 825.6; }
  28.284% { stroke-dashoffset: 790.1; }
  44.832% { stroke-dashoffset: 625.8; }
  44.908% { stroke-dashoffset: 588.9; }
  47.225% { stroke-dashoffset: 564.7; }
  47.301% { stroke-dashoffset: 555.8; }
  51.68% { stroke-dashoffset: 511.7; }
  51.757% { stroke-dashoffset: 502.9; }
  58.809% { stroke-dashoffset: 432.6; }
  58.885% { stroke-dashoffset: 423.8; }
  68.839% { stroke-dashoffset: 324.9; }
  68.915% { stroke-dashoffset: 316.1; }
  81.925% { stroke-dashoffset: 187.1; }
  82.001% { stroke-dashoffset: 178.3; }
  100.0% { stroke-dashoffset: 0.0; }
}

@keyframes node-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes marker-travel {
  from {
    offset-distance: 45.57%;
  }
  to {
    offset-distance: 63.92%;
  }
}

@supports (view-timeline-name: --x) and (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .spine-track {
      view-timeline-name: --spine-reveal;
      view-timeline-axis: block;
    }

    .spine__path {
      stroke-dasharray: 1000;
      stroke-dashoffset: 1000;
      animation: spine-draw linear both;
      animation-timeline: --spine-reveal;
      /* Both ends are pinned to the viewport's *bottom* edge, which is what
         makes this length exactly one .spine-track — 3768px — with no
         viewport-height term in it:

           cover 0%    the track's top edge reaching the viewport's bottom
           contain 100% the track's bottom edge reaching that same edge

         The second one is only true because the track is taller than the
         viewport: "contain" is fully-contained OR fully-covering, so for a
         subject this size its 100% is the last moment the track still covers
         the viewport, i.e. its bottom edge arriving at the bottom edge. (A
         viewport taller than 3768px would flip that back to the small-subject
         reading and shorten the reveal to one viewport-height; the spine is
         hidden below 1180px wide, and no viewport is 1180x3768.)

         Not "entry", which reads as the same thing and isn't: entry ends at
         contain *0%*, which for a tall subject is its top edge reaching the
         viewport's *top* — one viewport-height, the very quantity this is
         built to avoid. See gen_geometry.py's spine-draw comment for the
         three viewport-height constants that preceded this. */
      animation-range: cover 0% contain 100%;
    }

    .node {
      opacity: 0;
      animation: node-in linear both;
      animation-timeline: view();
      animation-range: contain 25% contain 45%;
    }
  }
}

@supports (offset-path: path("M0 0")) and (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .bell__marker {
      transform: none;
      offset-path: path("M0.0 251.3L4.0 251.2L8.0 251.1L12.0 251.0L16.0 250.9L20.0 250.8L24.0 250.7L28.0 250.5L32.0 250.4L36.0 250.2L40.0 250.1L44.0 249.9L48.0 249.6L52.0 249.4L56.0 249.2L60.0 248.9L64.0 248.6L68.0 248.3L72.0 248.0L76.0 247.6L80.0 247.2L84.0 246.8L88.0 246.3L92.0 245.8L96.0 245.3L100.0 244.7L104.0 244.1L108.0 243.5L112.0 242.8L116.0 242.0L120.0 241.2L124.0 240.4L128.0 239.5L132.0 238.6L136.0 237.6L140.0 236.5L144.0 235.4L148.0 234.2L152.0 232.9L156.0 231.6L160.0 230.2L164.0 228.7L168.0 227.1L172.0 225.5L176.0 223.8L180.0 222.0L184.0 220.1L188.0 218.2L192.0 216.1L196.0 214.0L200.0 211.8L204.0 209.5L208.0 207.1L212.0 204.6L216.0 202.1L220.0 199.4L224.0 196.7L228.0 193.8L232.0 190.9L236.0 187.9L240.0 184.9L244.0 181.7L248.0 178.5L252.0 175.2L256.0 171.8L260.0 168.4L264.0 164.9L268.0 161.3L272.0 157.7L276.0 154.0L280.0 150.3L284.0 146.6L288.0 142.8L292.0 139.0L296.0 135.2L300.0 131.4L304.0 127.6L308.0 123.7L312.0 119.9L316.0 116.1L320.0 112.4L324.0 108.6L328.0 105.0L332.0 101.3L336.0 97.7L340.0 94.2L344.0 90.8L348.0 87.5L352.0 84.2L356.0 81.1L360.0 78.1L364.0 75.1L368.0 72.4L372.0 69.7L376.0 67.2L380.0 64.8L384.0 62.6L388.0 60.6L392.0 58.7L396.0 57.0L400.0 55.5L404.0 54.1L408.0 53.0L412.0 52.0L416.0 51.2L420.0 50.6L424.0 50.2L428.0 50.0L432.0 50.0L436.0 50.2L440.0 50.6L444.0 51.2L448.0 52.0L452.0 53.0L456.0 54.1L460.0 55.5L464.0 57.0L468.0 58.7L472.0 60.6L476.0 62.6L480.0 64.8L484.0 67.2L488.0 69.7L492.0 72.4L496.0 75.1L500.0 78.1L504.0 81.1L508.0 84.2L512.0 87.5L516.0 90.8L520.0 94.2L524.0 97.7L528.0 101.3L532.0 105.0L536.0 108.6L540.0 112.4L544.0 116.1L548.0 119.9L552.0 123.7L556.0 127.6L560.0 131.4L564.0 135.2L568.0 139.0L572.0 142.8L576.0 146.6L580.0 150.3L584.0 154.0L588.0 157.7L592.0 161.3L596.0 164.9L600.0 168.4L604.0 171.8L608.0 175.2L612.0 178.5L616.0 181.7L620.0 184.9L624.0 187.9L628.0 190.9L632.0 193.8L636.0 196.7L640.0 199.4L644.0 202.1L648.0 204.6L652.0 207.1L656.0 209.5L660.0 211.8L664.0 214.0L668.0 216.1L672.0 218.2L676.0 220.1L680.0 222.0L684.0 223.8L688.0 225.5L692.0 227.1L696.0 228.7L700.0 230.2L704.0 231.6L708.0 232.9L712.0 234.2L716.0 235.4L720.0 236.5L724.0 237.6L728.0 238.6L732.0 239.5L736.0 240.4L740.0 241.2L744.0 242.0L748.0 242.8L752.0 243.5L756.0 244.1L760.0 244.7L764.0 245.3L768.0 245.8L772.0 246.3L776.0 246.8L780.0 247.2L784.0 247.6L788.0 248.0L792.0 248.3L796.0 248.6L800.0 248.9L804.0 249.2L808.0 249.4L812.0 249.6L816.0 249.9L820.0 250.1L824.0 250.2L828.0 250.4L832.0 250.5L836.0 250.7L840.0 250.8L844.0 250.9L848.0 251.0L852.0 251.1L856.0 251.2L860.0 251.3L864.0 251.4L868.0 251.4L872.0 251.5L876.0 251.5L880.0 251.6L884.0 251.6L888.0 251.7L892.0 251.7L896.0 251.7L900.0 251.8L904.0 251.8L908.0 251.8L912.0 251.8L916.0 251.9L920.0 251.9L924.0 251.9L928.0 251.9L932.0 251.9L936.0 251.9L940.0 251.9L944.0 251.9L948.0 251.9L952.0 252.0L956.0 252.0L960.0 252.0");
      offset-rotate: 0deg;
      offset-distance: 45.57%;
      animation: marker-travel linear both;
      animation-timeline: view();
      animation-range: entry 70% contain 60%;
    }
  }
}

/* ============================================================
   NARROW VIEWPORTS — the corridor needs 250px it doesn't have, so the
   line steps aside rather than being squeezed into a squiggle.
   ============================================================ */

/* Below 1180px the shell stops being capped, the columns narrow, and the copy
   reflows taller than the min-heights the spine's geometry was measured against.
   Rather than let the line drift out of step with the words, the corridor stands
   down and the page reflows to a single column. */
@media (max-width: 1179px) {
  .spine-track,
  .spine__scale,
  .node,
  /* "The line on the left…" is a lie once the line is gone. */
  .hero__hint {
    display: none;
  }

  /* ---------- the hero photograph, rotated ----------

     The wide layout runs the ink ramp horizontally: photograph on the right,
     copy on the left, the fade handing over between them. In one column there
     is no "left of it", so the same idea turns ninety degrees — the photograph
     becomes a band across the top and the ramp runs top-to-bottom into the ink
     the copy sits on. Same technique, same rule about never leaving a hard
     edge, and the reading matter is now beside the image rather than on top of
     it, which is what makes this safe: legibility stops depending on which
     frame happens to be installed. On the desktop layout a bright photograph
     would put the headline on mid-grey; here it cannot, because the headline is
     never over the photograph at all.

     --hero-band is declared here rather than inlined because two rules need it:
     the band's own height, and the padding that drops the copy below it. */
  :root {
    /* 40vh, not half the screen: the band, the gap, the headline, the
       sub-headline and the buttons all have to clear the fold on a small phone,
       and at 52vh the primary CTA landed on it. */
    --hero-band: clamp(240px, 38vh, 360px);
    /* .nav is s5 top + s5 bottom around a 45px button (14 + 15 line + 14 + 2
       border). The band is measured from the top of the document and .hero
       starts below the nav, so the copy's offset has to discount it. */
    --nav-h: 85px;
  }

  .hero-media {
    left: 0;
    right: 0;
    width: auto;
    height: var(--hero-band);
  }

  .hero-media img {
    /* Pushed right of centre: the subject sits around 59% across the frame, and
       a narrow band crops hard off both sides. */
    object-position: 66% 26%;
  }

  .hero-media::after {
    background: linear-gradient(
      to bottom,
      /* enough scrim under the nav for the wordmark and the button to hold */
      rgba(var(--ink-rgb), 0.62) 0%,
      rgba(var(--ink-rgb), 0.22) 26%,
      rgba(var(--ink-rgb), 0.66) 74%,
      var(--ink) 100%
    );
  }

  .hero {
    padding-top: calc(var(--hero-band) - var(--nav-h) + var(--s7));
  }

  .hero,
  .proof,
  .stage {
    padding-left: 0;
  }

  .hero,
  .stage--decay,
  .stage--reviews,
  .stage--weak,
  .stage--ready {
    min-height: 0;
  }

  .stage__grid,
  .curve__grid,
  .section__head,
  .principle,
  .price {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s5);
  }

  .stage {
    padding-top: var(--s9);
    padding-bottom: var(--s9);
  }
}

@media (max-width: 680px) {
  /* Two columns of 13px references need width neither a phone nor a small
     tablet has; below this they would be four words wide. */
  .cites .refs {
    columns: 1;
  }

  /* --t-t3 is a 20px reading size chosen for a 46ch measure. On a 375px screen
     that measure is about 30ch, so the same paragraph runs five lines and puts
     the primary CTA under the fold on a small phone. Dropping to the body step —
     still on the closed scale — buys back a line and a few px of leading. */
  .hero__sub {
    font-size: var(--t-body);
  }

  .proof {
    grid-template-columns: 1fr 1fr;
    gap: var(--s5);
    padding-top: var(--s6);
    padding-bottom: var(--s6);
    min-height: 0;
  }

  .intervals div {
    grid-template-columns: 54px 1fr 64px;
  }
}
