/* orbit: 365 days on a spiral wrapped round a centred block of words.
 *
 * The grid is switched off entirely: orbit.js registers a `place` hook and puts
 * every day at an absolute point on the spiral, so the only thing this file has
 * to agree with the script about is `--dot` (the size of one day) and the two
 * radii it hands over for the January mark.
 *
 * Nothing here uses `transform` on a day: the engine's arrival keyframe ends on
 * `transform: none`, so a day is centred with calc() on left/top instead.
 */

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

   One grotesk, set tight, for everything. Five settings were tried side by
   side at /types and this is the one that was kept; the others are in the
   history if the question ever comes back.

   Nothing is fetched: Helvetica where it exists, its clone where it does not,
   so the page is never waiting on a face to arrive. */

:root {
  --display: "Helvetica Neue", Helvetica, "Nimbus Sans", Arial, sans-serif;
  --body: "Helvetica Neue", Helvetica, "Nimbus Sans", Arial, sans-serif;
  --mono: ui-monospace, "JetBrains Mono", "SFMono-Regular", Menlo,
          "DejaVu Sans Mono", monospace;
  --display-weight: 600;
  --display-track: -.021em;
  --body-track: 0;
}

:root {
  /* The lower bound is a phone, and 6px there was wrong: a day came out 3.8px
     across, so the year read as dust settled on the border rather than as 365
     things somebody has to cover. A day is a unit of work; it has to look like
     one at arm's length. */
  --cell: clamp(11px, 1.05vw, 13px);
  --sweep-span: 700ms;   /* one full revolution of the green front */
  --sweep-dur: 440ms;     /* how long one day holds its green */
  --green: #35d47c;
  /* How far a day leaves its place when it changes hands. The room outside
     the trace (`hold` in the script) is at least 22px, so this cannot throw a
     day into the words or off the page. */
  --swap-out: clamp(15px, 1.9vw, 26px);

  --ink: #070a0e;
  --ink-lift: #0e141b;    /* the lit centre of the page: worst case for text */
  --paper: #eaf1f7;
  --soft: #ccd9e4;
  --mute: #a9b9c7;
  --hair: rgba(174, 190, 204, .16);
}

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

body {
  overflow-x: clip;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--body);
  letter-spacing: var(--body-track, 0);
  -webkit-font-smoothing: antialiased;
}

/* The background the finished year turns into. Fixed, so it stays put behind
   every section while the page scrolls, and behind everything: the sections
   above it are all slightly see-through so it reads through them. */
.drift {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

/* The moment of the hand-off. No transition on purpose -- the canvas is
   already drawing these exact circles, so a fade would show as a double
   image. */
.fill.drifting .day { opacity: 0; }

/* The curve and its January mark were the year's frame. Once the year has
   left there is nothing for them to be around, so they go too -- slowly, so
   the hero does not blink. */
.fill.drifting .trace,
.fill.drifting .mark {
  opacity: 0;
  transition: opacity 2.2s ease 600ms;
}

/* ------------------------------------------------------------- the field */

.fill {
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  gap: 0;
  padding: 32px clamp(12px, 3vw, 48px);
  min-height: 100vh;
  height: var(--fill-h, auto);
  overflow-x: clip;
  /* No background of its own. The drift is a fixed canvas behind the whole
     page, and anything painted here would sit on top of it and dim the year
     the moment it was handed over -- which read as the ring going grey. The
     lit centre this used to provide now belongs to the words themselves. */
  background: none;
}

/* The dark that the words are read against, carried by the words rather than
   by the section, so it covers the drift only where it has to. */
.copy::before {
  content: "";
  position: absolute;
  inset: -16% -26%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(ellipse at 50% 50%,
    color-mix(in srgb, var(--ink) 94%, transparent) 0%,
    color-mix(in srgb, var(--ink) 78%, transparent) 48%,
    transparent 76%);
}

.copy {
  position: relative;
  z-index: 4;
  width: var(--copy-w, min(560px, 68vw));
  text-align: center;
}

/* One day. Placed by the script; sized here. */
.day {
  position: absolute;
  z-index: 3;
  width: var(--dot, 8px);
  height: var(--dot, 8px);
  border-radius: 50%;
}

/* A halo under the day while the green front is passing over it. The engine
   owns the day's own animation, so the flare lives on a pseudo-element and
   only borrows the same delay. */
.day::after {
  content: "";
  position: absolute;
  inset: -170%;
  border-radius: 50%;
  background: radial-gradient(circle closest-side,
    rgba(53, 212, 124, .55), rgba(53, 212, 124, 0) 78%);
  opacity: 0;
  pointer-events: none;
}
.fill.sweeping .day.settled::after {
  animation: orbit-flare var(--sweep-dur) ease-out both;
  animation-delay: calc(var(--d, 0) * var(--sweep-span));
}
@keyframes orbit-flare {
  0%        { opacity: 0; }
  22%, 40%  { opacity: .9; }
  100%      { opacity: 0; }
}

/* ------------------------------------------- the ring the green runs round */

.trace {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
  pointer-events: none;
  overflow: visible;
}
.trace-track {
  fill: none;
  stroke: var(--hair);
  stroke-width: 1;
}
.trace-arc {
  fill: none;
  stroke: var(--green);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-dashoffset: var(--len, 0);
  opacity: 0;
  filter: drop-shadow(0 0 7px rgba(53, 212, 124, .6));
}
.fill.sweeping .trace-arc {
  opacity: 1;
  animation: orbit-draw var(--sweep-span) linear both;
}
.fill.done .trace-arc { opacity: .55; stroke-dashoffset: 0; }
@keyframes orbit-draw {
  from { stroke-dashoffset: var(--len, 0); }
  to   { stroke-dashoffset: 0; }
}

/* Where the year starts, so the direction of the fill is readable. */
.mark {
  position: absolute;
  left: 50%;
  bottom: calc(50% + var(--mark-r, 0px));
  transform: translateX(-50%);
  z-index: 2;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  pointer-events: none;
}
.mark-label {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .34em;
  text-indent: .34em;
  text-transform: uppercase;
  color: var(--mute);
  white-space: nowrap;
}
.mark-line {
  width: 1px;
  height: 10px;
  background: linear-gradient(rgba(174, 190, 204, .05), rgba(174, 190, 204, .5));
}

/* ------------------------------------------------------------- the words */

.eyebrow {
  margin: 0 0 14px;
  font-size: 12px;
  font-weight: 500;
  /* Half a centimetre between letters is a logo, not a label: at .46em this
     read as texture and people skipped it. */
  letter-spacing: .26em;
  text-indent: .13em;
  text-transform: uppercase;
  color: var(--mute);
}

h1 {
  margin: 0 0 12px;
  font-size: clamp(22px, 2.7vw, 36px);
  line-height: 1.18;
  font-weight: 500;
  letter-spacing: .012em;
  color: var(--paper);
  text-wrap: balance;
}
h1 em {
  font-style: italic;
  font-weight: 400;
  font-family: var(--emphasis, ui-serif), "Iowan Old Style", Georgia, serif;
  color: #fff;
}

.lede {
  margin: 0 auto 17px;
  max-width: 41ch;
  font-size: clamp(15px, 1.12vw, 17px);
  line-height: 1.62;
  color: var(--soft);
}

.acts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 10px 18px;
  margin: 0 0 17px;
}

.cta {
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .18em;
  text-indent: .18em;
  text-transform: uppercase;
  color: #05080b;
  background: var(--paper);
  border: 1px solid var(--paper);
  border-radius: 999px;
  padding: 12px 24px;
  cursor: pointer;
  transition: background .18s ease, border-color .18s ease, color .18s ease;
}
.cta:hover:not(:disabled) { background: #fff; border-color: #fff; }
.cta:disabled {
  background: transparent;
  border-color: rgba(174, 190, 204, .38);
  color: var(--soft);
  cursor: default;
}

/* The quiet action. It is a link in most of the page and a button in the
   hero, so it is reset to look like neither. */
.ghost {
  font: inherit;
  background: none;
  border: 0;
  cursor: pointer;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: .18em;
  text-indent: .18em;
  text-transform: uppercase;
  color: var(--soft);
  text-decoration: none;
  padding-bottom: 4px;
  border-bottom: 1px solid rgba(174, 190, 204, .42);
  transition: color .18s ease, border-color .18s ease;
}
.ghost:hover:not(:disabled) { color: var(--paper); border-bottom-color: var(--paper); }
.ghost:disabled { cursor: default; color: var(--mute); border-bottom-color: transparent; }

/* The loud action in the hero is a link now, not a button. */
.acts .cta { display: inline-block; text-decoration: none; }

.cta:focus-visible,
.ghost:focus-visible {
  outline: 2px solid var(--green);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ------------------------------------------------------------ the numbers */

.readout {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: 10px clamp(16px, 2.2vw, 28px);
  padding-top: 14px;
  border-top: 1px solid var(--hair);
}

.r-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  max-width: 16ch;
}
.r-item b {
  font-size: 15px;
  font-weight: 500;
  letter-spacing: .03em;
  color: var(--paper);
  font-variant-numeric: tabular-nums;
}
.r-item i {
  font-style: normal;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .12em;
  text-indent: .08em;
  text-transform: uppercase;
  line-height: 1.5;
  color: var(--mute);
}

/* The stage word changes four times while the solver runs and is much the
   longest thing in the row. On its own line it can say whatever it likes
   without rewrapping the readout, which would drag the whole orbit in and
   out with it, since the orbit is measured off this block. */
.r-item:has(b.stage) {
  flex-basis: 100%;
  max-width: none;
  margin-top: -4px;
}

.r-item b.stage {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 12px;
  letter-spacing: .1em;
  color: var(--soft);
  white-space: nowrap;
}
.r-item b.stage::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--mute);
  transition: background .3s ease;
}
.fill.done .r-item b.stage { color: var(--green); }
.fill.done .r-item b.stage::before {
  background: var(--green);
  box-shadow: 0 0 8px rgba(53, 212, 124, .8);
}

/* ------------------------------------------------------------------ calm */

@media (prefers-reduced-motion: reduce) {
  .fill.sweeping .day.settled::after { animation: none; opacity: 0; }
  .fill.sweeping .trace-arc { animation: none; opacity: .55; stroke-dashoffset: 0; }
  .cta, .ghost, .r-item b.stage::before { transition: none; }
}

/* ------------------------------------------------ waiting to be a schedule

   Before the button is pressed there is no year yet, so the days are grey and
   unassigned, circling outside the orbit. They are the same 365 elements that
   will land on the curve -- nothing is created or thrown away, they are only
   somewhere else. */

.fill.orbit-idle .day,
.fill.landing .day {
  opacity: 1;
}
.fill.orbit-idle .day {
  /* Bright enough to read as a crowd of days waiting, dim enough that it is
     plainly not the schedule yet. */
  /* Chips, not dots: squared off with a soft corner and set at their own
     angle, so a month reads as a heap of days rather than a smudge of ink. */
  background: var(--waiting, #6d7f93);
  border-radius: 2px;
  box-shadow: none;
}
/* The lane is set per day in the script; this is the lap around it. Started
   part-way through with a negative delay, so the cloud is already spread out
   on the first frame rather than filing out of one point. */
/* Each loose day wanders between three points a few pixels apart, at its own
   pace and its own size. Independent drift is what makes it read as a field
   of things floating rather than a decoration being played. */
.fill.orbit-idle .day {
  animation: orbit-float var(--fly-dur, 18s) ease-in-out infinite alternate;
  animation-delay: var(--fly-delay, 0s);
  opacity: var(--fade, .7);
}
@keyframes orbit-float {
  0% {
    transform: translate(calc(var(--px, 0px) + var(--ax, 0px)),
                         calc(var(--py, 0px) + var(--ay, 0px))) rotate(var(--rot, 0deg)) scale(var(--z, 1));
  }
  50% {
    transform: translate(calc(var(--px, 0px) + var(--bx, 0px)),
                         calc(var(--py, 0px) + var(--by, 0px))) rotate(var(--rot, 0deg)) scale(calc(var(--z, 1) * 1.08));
  }
  100% {
    transform: translate(calc(var(--px, 0px) + var(--cx, 0px)),
                         calc(var(--py, 0px) + var(--cy, 0px))) rotate(var(--rot, 0deg)) scale(var(--z, 1));
  }
}

/* The flight in. Each day carries its own lag, so the year lands a day at a
   time from January round to December rather than all at once.

   Opacity is in the list because on a rebuild each day is handed over from a
   ball in the drifting field and starts at that ball's brightness, which is
   about a third of full. Without a transition on it, every day snapped to
   full on the frame it left the canvas -- 365 small flashes announcing the
   swap the rest of this is built to hide. */
.fill.landing .day {
  transition:
    transform var(--fly-ms, 1100ms) cubic-bezier(.26, .72, .22, 1) var(--fly-lag, 0ms),
    background-color 520ms ease var(--fly-lag, 0ms),
    opacity 520ms ease var(--fly-lag, 0ms),
    box-shadow 520ms ease var(--fly-lag, 0ms);
}

/* Putting them back out for a second run is a move, not a flight: no
   transition, so the next press starts from the cloud again. */
.fill.returning .day { transition: none; }

@media (prefers-reduced-motion: reduce) {
  .fill.orbit-idle .day {
    animation: none;
    transform: translate(var(--px, 0px), var(--py, 0px)) rotate(var(--rot, 0deg)) scale(var(--z, 1));
  }
  .fill.landing .day { transition: none; }
}

/* -------------------------------------------------------------- the story */

/* The page under the orbit. Full bleed, wide rows, a hairline between each
   problem -- the shape of a ledger rather than a stack of marketing cards,
   because every line of it is a complaint somebody actually has. */

.story {
  position: relative;
  z-index: 3;
  padding: clamp(64px, 11vh, 132px) clamp(16px, 4.4vw, 76px) clamp(72px, 12vh, 140px);
  background: linear-gradient(
    color-mix(in srgb, var(--ink) 82%, transparent) 0%,
    color-mix(in srgb, #060a0f 89%, transparent) 100%);
  border-top: 1px solid var(--hair);
}

.story > .eyebrow { margin-bottom: 18px; }

.story > h2 {
  margin: 0 0 clamp(34px, 5vw, 60px);
  max-width: 22ch;
  font-size: clamp(26px, 4.1vw, 58px);
  line-height: 1.08;
  font-weight: 500;
  letter-spacing: -.014em;
  color: var(--paper);
  text-wrap: balance;
}

.close {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 28px 48px;
  margin-top: clamp(48px, 7vw, 96px);
}
.close h2 {
  max-width: 20ch;
  font-size: clamp(20px, 2.5vw, 34px);
  line-height: 1.16;
  font-weight: 500;
  letter-spacing: -.01em;
  color: var(--paper);
  text-wrap: balance;
}
.close .cta {
  display: inline-block;
  text-decoration: none;
  flex: none;
}

@media (max-width: 760px) {
}

/* Each row lifts as it comes up the page. Scroll-driven, so there is no
   observer to arm and nothing to go wrong if the script never runs -- where
   the timeline is unsupported the rows are simply already there. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .close, .story > h2 {
      animation: story-rise linear both;
      animation-timeline: view();
      animation-range: entry 6% cover 26%;
    }
    @keyframes story-rise {
      from { opacity: 0; transform: translateY(26px); }
      to   { opacity: 1; transform: none; }
    }
  }
}

/* --------------------------------------------------------- the rest of it */

/* Shared shell for every section below the story. Full bleed on purpose:
   there is no centred 900px column anywhere on this page. */
.how, .proof, .rules, .never, .facts, .end {
  position: relative;
  z-index: 3;
  padding: clamp(64px, 10vh, 128px) clamp(16px, 4.4vw, 76px);
  background: color-mix(in srgb, #060a0f 89%, transparent);
  border-top: 1px solid var(--hair);
}

.how > h2, .proof h2, .rules > h2, .never h2, .end h2 {
  margin: 0 0 clamp(28px, 4vw, 52px);
  max-width: 24ch;
  font-size: clamp(24px, 3.4vw, 48px);
  line-height: 1.1;
  font-weight: 500;
  letter-spacing: -.014em;
  color: var(--paper);
  text-wrap: balance;
}

/* A screenshot is a light rectangle on a dark page, so it is given an edge
   and a shadow rather than being allowed to glare off the background. */
figure img {
  border-radius: 12px;
  border: 1px solid rgba(174, 190, 204, .18);
  box-shadow: 0 26px 70px rgba(0, 0, 0, .55);
}
/* Every screenshot is wrapped in a <picture> so a phone can be handed a
   phone-shaped one. A picture is inline by default, which would leave a
   line-box's worth of dead space under each frame. */
figure picture { display: block; }
figcaption {
  margin-top: 12px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--mute);
}

/* ------------------------------------------------------------ the steps */

.steps { counter-reset: step; display: grid; gap: clamp(48px, 7vw, 104px); }

.steps li {
  counter-increment: step;
  display: grid;
  grid-template-columns: minmax(0, 4fr) minmax(0, 7fr);
  gap: clamp(20px, 3.4vw, 64px);
  align-items: center;
}
/* Alternated so the eye zig-zags down the page instead of running down one
   gutter. The tracks are swapped as well as the order -- moving the picture
   into column one without that put a whole screenshot in the narrow column
   and shrank it to nothing. */
.steps li:nth-child(even) {
  grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
}
.steps li:nth-child(even) .step-words { order: 2; }

/* A year of call is too dense to read at half width, so that one runs the
   full width of the page with its words above it. */
.steps li.stretch {
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(18px, 2.4vw, 34px);
}
.steps li.stretch .step-words { order: 0; }
.steps li.stretch figure img { width: 100%; }

.steps h3 {
  margin: 0 0 14px;
  font-size: clamp(19px, 2.1vw, 29px);
  line-height: 1.16;
  font-weight: 500;
  letter-spacing: -.008em;
  color: var(--paper);
}
.steps h3::before {
  content: counter(step, decimal-leading-zero);
  display: block;
  margin-bottom: 12px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .2em;
  color: var(--green);
}
.steps p {
  max-width: 44ch;
  font-size: clamp(15.5px, 1.15vw, 17.5px);
  line-height: 1.68;
  color: var(--soft);
}

/* ----------------------------------------------------------- the proof */

.proof-head { max-width: 62ch; margin-bottom: clamp(28px, 4vw, 48px); }
.proof-head h2 { margin-bottom: 16px; }
.proof .lede { margin: 0; max-width: 62ch; text-align: left; }

/* The fairness table is wider than it is tall and full of small numbers, so
   it gets the whole width of the page. */
.proof .wide img { width: 100%; }

.split {
  display: grid;
  grid-template-columns: minmax(0, 6fr) minmax(0, 5fr);
  gap: clamp(24px, 4vw, 72px);
  align-items: center;
  margin-top: clamp(48px, 7vw, 96px);
}
.split h3 {
  margin: 0 0 16px;
  font-size: clamp(20px, 2.2vw, 30px);
  line-height: 1.16;
  font-weight: 500;
  color: var(--paper);
}
.split p {
  max-width: 56ch;
  font-size: clamp(15.5px, 1.15vw, 17.5px);
  line-height: 1.68;
  color: var(--soft);
}
.split p + p { margin-top: 14px; }
.split figure img { max-height: 74vh; width: auto; margin-left: auto; }

/* ----------------------------------------------------------- the rules */

.rule-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
  gap: clamp(28px, 4.5vw, 88px);
  align-items: start;
}
.rule-cols h3 {
  margin: 0 0 20px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--mute);
}
.hard ul li {
  padding: 14px 0 14px 26px;
  border-bottom: 1px solid var(--hair);
  font-size: clamp(15.5px, 1.18vw, 18px);
  color: var(--paper);
  position: relative;
}
.hard ul li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 10px;
  height: 10px;
  margin-top: -5px;
  border-radius: 50%;
  border: 2px solid var(--green);
}
.note {
  margin-top: 18px;
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--mute);
  max-width: 46ch;
}

/* The weights are the numbers, so the bar is drawn from the number itself --
   no second set of figures to fall out of step with the code. */
.weights li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 11px 0;
  border-bottom: 1px solid var(--hair);
  position: relative;
}
.weights li::before {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: calc(var(--w) / 150 * 100%);
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--green) 30%, transparent), var(--green));
}
.weights span {
  font-size: clamp(15px, 1.1vw, 17px);
  color: var(--paper);
}
.weights b {
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--mute);
}

/* ----------------------------------------------------------- the rest */

.never { text-align: left; }
.never h2 { max-width: 16ch; }
.never .lede { margin: 0; max-width: 64ch; text-align: left; }

.fact-grid {
  display: grid;
  /* Fixed at four so the eight facts fill two rows exactly; auto-fit gave
     five columns at this width and left three empty cells lit up. */
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  background: var(--hair);
  border: 1px solid var(--hair);
  border-radius: 12px;
  overflow: hidden;
}
.fact-grid li {
  padding: clamp(20px, 2vw, 30px);
  /* A shade more solid than the sections: this is small text in small boxes. */
  background: color-mix(in srgb, #060a0f 93%, transparent);
}
.fact-grid h3 {
  margin: 0 0 10px;
  font-size: clamp(16px, 1.28vw, 19px);
  line-height: 1.28;
  font-weight: 500;
  color: var(--paper);
}
.fact-grid p {
  font-size: 15px;
  line-height: 1.62;
  color: var(--soft);
}

.end {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 28px 48px;
}
.end h2 { margin: 0 0 12px; }
.end p { color: var(--soft); font-size: clamp(15.5px, 1.15vw, 17.5px); }
.end .cta { display: inline-block; text-decoration: none; flex: none; }

@media (max-width: 1180px) {
  .fact-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* A phone is not a small desktop.
 *
 * This block used to keep the desktop screenshots at full size below 860px
 * and let each one scroll sideways inside its own frame, on the reasoning
 * that a 1500px screen scaled down to fit is a grey smudge. It is, but the
 * cure was worse: nobody wants to drag a picture around to read a website.
 *
 * The pictures are shot twice instead -- see scripts/shoot_app_screens.py --
 * and stage.html hands the phone-width ones to phones with <picture>. So
 * everything here is layout only, and every image simply fits. */
@media (max-width: 860px) {
  .steps li, .steps li:nth-child(even), .split, .rule-cols {
    grid-template-columns: minmax(0, 1fr);
  }
  .fact-grid { grid-template-columns: minmax(0, 1fr); }
  .steps li:nth-child(even) .step-words { order: 0; }
  /* The three that were sized or shifted for a two-column page: at one
     column they are all just the width of the page. */
  .steps li.stretch figure img,
  .proof .wide img,
  .split figure img { max-height: none; width: 100%; margin-left: 0; }

  /* Air that reads as generous on a 27in screen reads as a long walk on a
     phone, because one column stacks every gap that used to sit side by
     side. The page was 11,445px on a 390px handset -- thirteen and a half
     screens of scrolling -- and roughly 1,500px of that was section padding
     alone (11vh top and bottom, eight times over). Nothing is removed here;
     the same words are simply set closer together, which is what the reader
     asked for when they said it was overwhelming. */
  .story,
  .how, .proof, .rules, .never, .facts, .end { padding-block: 44px; }
  .story > h2, .how > h2, .proof h2, .rules > h2 { margin-bottom: 22px; }
  .steps { gap: 40px; }
  .steps li { gap: 16px; }
  .asked li { padding-block: 18px; }
  .asked .a-new { margin-top: 9px; }
  .proof-head { margin-bottom: 22px; }
  .split { margin-top: 40px; gap: 20px; }
  .rule-cols { gap: 30px; }
}

/* Same treatment as the story rows: they arrive as they come up the page,
   and where the timeline is unsupported they are simply already there. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .steps li, .proof .wide, .split, .rule-cols, .fact-grid, .never h2, .end {
      animation: story-rise linear both;
      animation-timeline: view();
      animation-range: entry 4% cover 24%;
    }
  }
}

/* ------------------------------------------------------------- the bar */

.topnav {
  justify-content: space-between;
  padding: 12px clamp(16px, 4.4vw, 76px);
  background: color-mix(in srgb, var(--ink) 82%, transparent);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--hair);
}
.topnav .brand {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .34em;
  text-indent: .34em;
  text-transform: uppercase;
  color: var(--paper);
  text-decoration: none;
}
.topnav nav { flex-wrap: wrap; }
.topnav nav a {
  font-size: 13px;
  color: var(--soft);
  text-decoration: none;
  transition: color .18s ease;
}
.topnav nav a:hover { color: var(--paper); }
.topnav .cta.small {
  padding: 9px 18px;
  font-size: 10.5px;
  text-decoration: none;
}
@media (max-width: 640px) {
  .topnav nav { display: none; }   /* the page is short enough to scroll */
}

/* Anchored jumps land under the bar, not behind it. */
.story, .how, .proof, .rules, .never, .facts { scroll-margin-top: 64px; }


/* Which family carries what. Written last on purpose: it is the one place the
   whole page's type is decided, and it overrides the per-element tracking the
   sections were written with so that a setting is a setting rather than five
   headings that each drifted. */
h1, h2, h3,
.steps h3, .split h3, .fact-grid h3, .close h2, .end h2 {
  font-family: var(--display);
  font-weight: var(--display-weight);
  letter-spacing: var(--display-track);
}

/* The small, spaced, upright things: labels, buttons, counts. In most settings
   they stay in the body face; the console setting hands them to the mono. */
.eyebrow, .cta, .ghost, .mark-label, .rule-cols h3,
.readout b, .weights b, .steps h3::before {
  font-family: var(--small, inherit);
}

/* The questions, and whether the schedule can answer them.

   The question was set in italic serif to mark it as somebody speaking. On a
   dark screen at this size that is the hardest thing on the page to read:
   thin strokes, sloped, reversed out. It is the same grotesk as everything
   else now, held apart by weight, size and a green rule instead. */
.asked { list-style: none; margin: 0; padding: 0; }
.asked li {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: 4px clamp(14px, 3vw, 56px);
  padding: clamp(22px, 3vw, 34px) 0;
  border-top: 1px solid var(--hair);
  align-items: start;
}
.asked li:last-child { border-bottom: 1px solid var(--hair); }
.asked .q {
  grid-column: 1;
  grid-row: 1 / span 2;
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(19px, 2vw, 26px);
  line-height: 1.26;
  letter-spacing: -.012em;
  color: #fff;
  max-width: 21ch;
  text-wrap: balance;
}
.asked .a-now, .asked .a-new {
  grid-column: 2;
  max-width: 62ch;
  font-size: clamp(15.5px, 1.15vw, 17.5px);
  line-height: 1.62;
}
.asked .a-now { color: var(--mute); }
.asked .a-new {
  margin-top: 12px;
  padding-left: 16px;
  border-left: 2px solid var(--green);
  color: var(--paper);
}

@media (max-width: 860px) {
  .asked li { grid-template-columns: minmax(0, 1fr); }
  .asked .q, .asked .a-now, .asked .a-new { grid-column: 1; grid-row: auto; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .asked li {
      animation: story-rise linear both;
      animation-timeline: view();
      animation-range: entry 4% cover 24%;
    }
  }
}
