/* Voice Planner — one stylesheet, two pages, two themes, two languages.
   The phone is sized from viewport height and its width is derived from it,
   so it can never grow wider than the column it stands in. */

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

:root {
  --bg: #f7f6fa;
  --bg-2: #efedf5;
  --ink: #131218;
  --ink-soft: #4b4959;
  --ink-faint: #757287;
  --line: rgba(19, 18, 24, 0.11);
  --line-soft: rgba(19, 18, 24, 0.055);
  --card: #ffffff;
  --card-2: #f2f0f7;
  --violet: #6d4df0;
  --violet-soft: #5a37e0;
  --violet-ink: #ffffff;
  --violet-glow: rgba(109, 77, 240, 0.22);
  --mint: #0f9b86;
  --amber: #b86a0c;
  --rose: #c93a6a;
  --sky: #2c6fd1;
  --rail-1: #cbc9d6;
  --rail-2: #f2f1f6;
  --rail-3: #a8a5b6;
  --back: #e6e4ee;
  --shadow: 0 26px 60px -34px rgba(24, 18, 60, 0.4);
  --shadow-soft: 0 10px 26px -20px rgba(24, 18, 60, 0.4);
  --r-sm: 0.7rem;
  --r-md: 1.05rem;
  --r-lg: 1.6rem;
  --gut: clamp(1.1rem, 4vw, 2.6rem);
  --rail: 3rem;
  /* The lane the copy keeps clear: the line, the stop, and the name that slides
     out of it. Where there is no room for the name, only the first two. */
  --lane: clamp(9.5rem, 22vw, 15.5rem);
  --navh: 3.6rem;
  --ease: cubic-bezier(0.22, 0.68, 0, 1);
}

:root[data-theme="dark"] {
  --bg: #0b0b10;
  --bg-2: #0f0f16;
  --ink: #f3f2f7;
  --ink-soft: #b4b1c4;
  --ink-faint: #8b87a1;
  --line: rgba(255, 255, 255, 0.12);
  --line-soft: rgba(255, 255, 255, 0.06);
  --card: #14141d;
  --card-2: #191922;
  --violet: #8b5cf6;
  --violet-soft: #b6a0ff;
  --violet-ink: #0d0b1a;
  --violet-glow: rgba(139, 92, 246, 0.3);
  --mint: #5fe0c2;
  --amber: #f3b45a;
  --rose: #ff8fb4;
  --sky: #7fb6ff;
  --rail-1: #3a3a46;
  --rail-2: #6f6f80;
  --rail-3: #22222b;
  --back: #16161e;
  --shadow: 0 30px 70px -34px rgba(0, 0, 0, 0.85);
  --shadow-soft: 0 12px 30px -22px rgba(0, 0, 0, 0.9);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0b0b10;
    --bg-2: #0f0f16;
    --ink: #f3f2f7;
    --ink-soft: #b4b1c4;
    --ink-faint: #8b87a1;
    --line: rgba(255, 255, 255, 0.12);
    --line-soft: rgba(255, 255, 255, 0.06);
    --card: #14141d;
    --card-2: #191922;
    --violet: #8b5cf6;
    --violet-soft: #b6a0ff;
    --violet-ink: #0d0b1a;
    --violet-glow: rgba(139, 92, 246, 0.3);
    --mint: #5fe0c2;
    --amber: #f3b45a;
    --rose: #ff8fb4;
    --sky: #7fb6ff;
    --rail-1: #3a3a46;
    --rail-2: #6f6f80;
    --rail-3: #22222b;
    --back: #16161e;
    --shadow: 0 30px 70px -34px rgba(0, 0, 0, 0.85);
    --shadow-soft: 0 12px 30px -22px rgba(0, 0, 0, 0.9);
  }
}

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

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

html {
  /* The page is one column and it is never wider than the screen. Said here as
     well as on the body because the body's own clipping is only carried up to
     the screen while the root has none of its own to carry — and something a
     hair over the edge (a soft light behind the phone, as it happens) was enough
     to let a phone drag the whole page sideways onto blank paper. `clip` rather
     than `hidden`: hidden would make the root a scroller and break every sticky
     block on the page. */
  overflow-x: clip;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--navh) + 1rem);
  -webkit-text-size-adjust: 100%;
  /* A tap on a control is answered by the control itself, not by a blue box the
     system paints over it. */
  -webkit-tap-highlight-color: transparent;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: "Plus Jakarta Sans", system-ui, sans-serif;
  font-size: 1rem;
  line-height: 1.55;
  letter-spacing: -0.005em;
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
  transition: background 0.6s var(--ease), color 0.6s var(--ease);
}

/* Paper: a fine grain over the whole page, so the flat fills read as a printed
   sheet rather than a screen wash. It is drawn once, sits above everything and
   is never in the way of a click. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 90;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}
:root[data-theme="dark"] body::after { opacity: 0.055; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body::after { opacity: 0.055; }
}

h1, h2, h3 {
  margin: 0;
  font-weight: 800;
  line-height: 1.02;
  letter-spacing: -0.045em;
  text-wrap: balance;
}
h1 { font-size: clamp(2.6rem, 5vw, 4.5rem); }
h2 { font-size: clamp(1.9rem, 3.6vw, 3rem); }
h3 { font-size: 1.0625rem; letter-spacing: -0.02em; line-height: 1.25; }
p { margin: 0; text-wrap: pretty; }
img { max-width: 100%; display: block; }
a { color: inherit; }
button { font: inherit; color: inherit; }

/* The surfaces the browser draws for us still belong to the design. */
::selection { background: var(--violet); color: var(--violet-ink); }
:root { accent-color: var(--violet); caret-color: var(--violet); scrollbar-color: var(--line) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--ink) 22%, transparent); border-radius: 99px; border: 3px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--ink) 34%, transparent); }
::-webkit-scrollbar-track { background: transparent; }

/* The ring follows the shape of the control it is on — a round button gets a
   round ring, not a square drawn over it. Nothing is painted on a plain tap;
   the ring is only for the keyboard. */
:focus-visible {
  outline: 2px solid var(--violet);
  outline-offset: 3px;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0.5rem;
  z-index: 60;
  padding: 0.6rem 1rem;
  border-radius: var(--r-sm);
  background: var(--violet);
  color: var(--violet-ink);
  font-weight: 700;
  text-decoration: none;
}
.skip:focus { left: 0.75rem; }

.shell {
  width: min(88rem, 100%);
  margin-inline: auto;
  padding-inline: var(--gut);
}
.shell--narrow { width: min(52rem, 100%); }

/* ---------- nav ---------- */

.nav {
  position: sticky;
  top: 0;
  z-index: 40;
  padding: 0.7rem var(--gut) 0;
}
.nav__island {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: min(88rem, 100%);
  margin-inline: auto;
  padding: 0.42rem 0.42rem 0.42rem 0.85rem;
  border: 1px solid transparent;
  border-radius: 999px;
  transition: background 0.5s var(--ease), border-color 0.5s var(--ease), box-shadow 0.5s var(--ease);
}
.nav.is-stuck .nav__island {
  border-color: var(--line);
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(18px) saturate(1.5);
  -webkit-backdrop-filter: blur(18px) saturate(1.5);
  box-shadow: var(--shadow-soft);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  font-weight: 800;
  letter-spacing: -0.03em;
  text-decoration: none;
}
/* The app's own icon, at the size of a favicon: the site and the thing it is
   about carry the same mark. */
.brand__mark {
  width: 1.85rem;
  height: 1.85rem;
  border-radius: 0.62rem;
  box-shadow: 0 6px 16px -8px var(--violet-glow);
}

.nav__links {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  margin-left: auto;
}
.nav__link {
  padding: 0.42rem 0.75rem;
  border-radius: 999px;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink-soft);
  text-decoration: none;
  transition: color 0.35s var(--ease), background 0.35s var(--ease);
}
.nav__link:hover, .nav__link[aria-current="page"] { color: var(--ink); background: var(--card-2); }

.nav__tail { display: flex; align-items: center; gap: 0.4rem; margin-left: 0.4rem; }

/* One pill under two labels: it slides from EN to PL and back, so the change of
   language is a movement instead of a background blinking out and in. */
.seg {
  position: relative;
  display: flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: 999px;
}
.seg__thumb {
  position: absolute;
  left: 2px;
  top: 2px;
  bottom: 2px;
  width: calc(50% - 2px);
  border-radius: 999px;
  background: var(--violet);
  box-shadow: 0 6px 14px -10px var(--violet-glow);
  transition: transform 0.5s var(--ease);
}
:root[data-lang="pl"] .seg__thumb { transform: translateX(100%); }
.seg button {
  position: relative;
  z-index: 1;
  flex: 1;
  padding: 0.3rem 0.62rem;
  border: 0;
  border-radius: 999px;
  background: transparent;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--ink-faint);
  cursor: pointer;
  /* The label turns white only once the pill is under it. Changed at the same
     moment as the slide it would be white on white for half a second, which
     read as the label blinking out. */
  transition: color 0.3s var(--ease) 0.22s;
}
.seg button[aria-pressed="true"] { color: var(--violet-ink); }

.tgl {
  display: grid;
  place-items: center;
  width: 2.05rem;
  height: 2.05rem;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--card);
  cursor: pointer;
  transition: transform 0.55s var(--ease), background 0.35s var(--ease);
}
.tgl:hover { transform: rotate(-20deg); }
.tgl__sun { display: none; }
:root[data-theme="dark"] .tgl__sun { display: block; }
:root[data-theme="dark"] .tgl__moon { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .tgl__sun { display: block; }
  :root:not([data-theme="light"]) .tgl__moon { display: none; }
}

.burger {
  display: none;
  position: relative;
  width: 2.05rem;
  height: 2.05rem;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--card);
  cursor: pointer;
}
.burger span {
  position: absolute;
  left: 50%;
  width: 0.8rem;
  height: 1.5px;
  margin-left: -0.4rem;
  border-radius: 2px;
  background: currentColor;
  transition: transform 0.45s var(--ease), opacity 0.3s var(--ease);
}
.burger span:nth-child(1) { top: 0.7rem; }
.burger span:nth-child(2) { top: 0.98rem; }
.burger span:nth-child(3) { top: 1.26rem; }
.nav.is-open .burger span:nth-child(1) { transform: translateY(0.28rem) rotate(45deg); }
.nav.is-open .burger span:nth-child(2) { opacity: 0; }
.nav.is-open .burger span:nth-child(3) { transform: translateY(-0.28rem) rotate(-45deg); }

/* ---------- language visibility ---------- */

:root[data-lang="en"] [data-only="pl"],
:root[data-lang="pl"] [data-only="en"] { display: none !important; }

/* ---------- buttons ---------- */

.btn-row { display: flex; flex-wrap: wrap; gap: 0.6rem; }

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.55rem 0.55rem 0.55rem 1.15rem;
  border: 1px solid transparent;
  border-radius: 999px;
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: -0.015em;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.5s var(--ease), background 0.4s var(--ease), border-color 0.4s var(--ease), box-shadow 0.5s var(--ease);
}
.btn__pin {
  display: grid;
  place-items: center;
  width: 1.7rem;
  height: 1.7rem;
  border-radius: 50%;
  transition: transform 0.5s var(--ease), background 0.4s var(--ease);
}
.btn--main {
  background: var(--violet);
  color: var(--violet-ink);
  box-shadow: 0 16px 34px -20px var(--violet-glow);
}
.btn--main .btn__pin { background: rgba(255, 255, 255, 0.18); }
.btn--main:hover { transform: translateY(-2px); box-shadow: 0 22px 40px -20px var(--violet-glow); }
.btn--main:hover .btn__pin { transform: scale(1.08); }
.btn--main:active { transform: translateY(0) scale(0.985); }

.btn--store { gap: 0.75rem; padding: 0.45rem 1.35rem 0.45rem 0.45rem; }
.btn--store .btn__pin { width: 2.3rem; height: 2.3rem; }
.btn--main.btn--store .btn__pin { background: #fff; box-shadow: 0 6px 14px -8px rgba(0, 0, 0, 0.45); }
.btn__stack { display: grid; line-height: 1.1; text-align: left; }
.btn__stack small { font-size: 0.625rem; font-weight: 600; letter-spacing: 0.09em; text-transform: uppercase; opacity: 0.82; }
.btn__stack span { font-size: 1.0625rem; }

.btn--lead { gap: 0.7rem; padding: 0.4rem 1.4rem 0.4rem 0.4rem; font-size: 1rem; }
.btn--lead .btn__pin { width: 2.3rem; height: 2.3rem; }
.btn--main.btn--lead .btn__pin { background: rgba(255, 255, 255, 0.2); }

.btn--ghost { border-color: var(--line); background: var(--card); color: var(--ink); }
.btn--ghost .btn__pin { background: var(--card-2); color: var(--ink-soft); }
.btn--ghost:hover { border-color: color-mix(in srgb, var(--violet) 45%, var(--line)); transform: translateY(-2px); }
.btn--ghost:hover .btn__pin { transform: translate(2px, -2px); }
.btn--ghost:active { transform: translateY(0) scale(0.985); }

/* ---------- the rig: a sticky stage the copy scrolls past ---------- */

.rig { position: relative; }

/* The line is pinned to the screen for the whole story: it never travels with
   the scroll, it only fills. JavaScript fades it in as the rig arrives and out
   again once the last lamp is reached. */
.rig__rail {
  position: fixed;
  inset: 0;
  z-index: 0;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
/* The track owns the whole width of the screen: the stops keep to the left
   margin, and between them the line is free to bow across the page, under the
   words and behind the phone. */
.rig__railbox {
  position: absolute;
  inset: 0;
  overflow: visible;
}
/* The line itself is taller than the screen and travels upwards as you scroll:
   each stop rides down the lane, parks level with the phone while its block is
   read, and moves on. JavaScript sets the height and the offset. */
.rig__track {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  will-change: transform;
}

.rig__inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
  gap: clamp(1.5rem, 3vw, 2.75rem);
  width: min(88rem, 100%);
  margin-inline: auto;
  padding-inline: var(--gut);
}

/* The phone holds the screen while the story runs by it. */
.rig__stage {
  position: sticky;
  top: var(--navh);
  z-index: 2;
  align-self: start;
  grid-column: 2;
  grid-row: 1;
  height: calc(100vh - var(--navh));
  height: calc(100dvh - var(--navh));
  min-width: 0;
}
/* The track runs underneath the whole page, so the copy keeps the full width of
   its column and starts where every other section starts. */
.rig__copy {
  position: relative;
  z-index: 1;
  grid-column: 1;
  grid-row: 1;
  min-width: 0;
  padding-left: var(--lane);
}
/* Room after the last block, so the phone is still held while that block is
   read and only then lets go and leaves with the page. */
.rig__copy::after { content: ""; display: block; height: 44vh; height: 44dvh; }

/* ---------- the cuts of copy ---------- */

/* Each cut owns more than a screen. Its block sticks halfway down, holds while
   the phone answers it, then lets go and leaves upwards on its own. */
.cut {
  display: grid;
  align-content: center;
  min-height: 112vh;
  min-height: 112dvh;
}
/* A stop parks dead on the middle of the screen, out in the left margin, and the
   block it names is held level with it rather than under it: the name is out to
   the left, the words it names are beside it, and both sit on the middle line of
   the screen. The block is stuck by its top edge and then pulled up by half its
   own height, which is what centres it on that line whatever its length. */
.cut__in {
  position: sticky;
  top: 50vh;
  transform: translateY(-50%);
  opacity: var(--fade, 1);
  transition: opacity 0.35s linear;
}
/* The opening block is not held: the title is read once and leaves upwards with
   the page, and the phone takes the screen over behind it. It is set a touch
   above the middle, where a title reads as placed rather than as fallen. */
.cut[data-cut="0"] .cut__in { position: static; transform: translateY(-3.5vh); }
.cut[data-cut="0"] { min-height: 100vh; min-height: 100dvh; }

.cut h1 em { font-style: normal; color: var(--violet-soft); }
/* The name over the title, set as a standing line rather than as a first line of
   the display type: it has to be read as the name of the thing, and it has to be
   in the heading — see the note in the markup. */
.cut h1 .brand {
  display: block;
  margin-bottom: 0.7rem;
  font-size: clamp(0.72rem, 0.9vw, 0.82rem);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--violet-soft);
}
.cut h2 { font-size: clamp(1.9rem, 3.4vw, 2.9rem); }
.cut__lead {
  max-width: 46ch;
  margin-top: 1.3rem;
  font-size: clamp(1rem, 1.15vw, 1.125rem);
  color: var(--ink-soft);
}
.cut__body {
  max-width: 52ch;
  margin-top: 1.25rem;
  color: var(--ink-soft);
}
/* The aside under the block: true, worth knowing, and not what the section is
   about — so it is set small and quiet and kept to a line or two. */
/* The rule over it is drawn as a hairline that runs out rather than as a border:
   a border is the full width of the block and it cut clean across the line
   running down the page behind it, which read as the line being broken. */
.cut__note {
  max-width: 44ch;
  margin-top: 1rem;
  padding-top: 0.8rem;
  background: linear-gradient(to right, var(--line-soft), transparent 68%) top left / 100% 1px no-repeat;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--ink-faint);
  opacity: 0.9;
}
.cut .btn-row { margin-top: 1.8rem; }
.cut__hint { margin: 0.8rem 0 0; max-width: 46ch; font-size: 0.8125rem; color: var(--ink-faint); }
.only-narrow { display: none; }

.status {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.6rem;
  margin: 2rem 0 0;
  padding-top: 1.1rem;
  border-top: 1px solid var(--line-soft);
  font-size: 0.8125rem;
}
/* Centred, not on the baseline: the lamp in front of "in testing" is an empty
   box, so a baseline row hangs the words off the bottom of it and the state ends
   up sitting lower than the platform it belongs to. */
.status__row { display: flex; align-items: center; gap: 0.5rem; }
.status dt { font-weight: 700; letter-spacing: -0.01em; }
.status dd { margin: 0; display: flex; align-items: center; gap: 0.4rem; color: var(--ink-faint); }
.status__row--live dd { color: var(--mint); }
.status__pulse {
  width: 0.42rem;
  height: 0.42rem;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 0 currentColor;
  animation: pulse 3.4s var(--ease) infinite;
}
@keyframes pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 60%, transparent); }
  60%, 100% { box-shadow: 0 0 0 0.5rem transparent; }
}

/* ---------- the trace: a line that fills from the top down ---------- */

.trace {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
/* It runs under the whole page, so the unlit part stays a faint shape and the
   lit stretch behind it carries a wide, soft glow. */
.trace path { fill: none; stroke-linecap: round; }
.trace__base { stroke: var(--line); stroke-width: 2.5; opacity: 0.42; }
/* The light around the lit stretch is blurred rather than drawn as a fat second
   stroke: a hard-edged band followed the line around like a tube, which is not
   how light behaves next to anything. */
.trace__halo {
  stroke: var(--violet);
  stroke-width: 14;
  opacity: 0.13;
  filter: blur(11px);
}
.trace__lit { stroke: var(--violet); stroke-width: 3; opacity: 0.55; }

/* The stops are on the line but not in the same sheet as it: the line runs
   behind everything, the names have to be read, and on a narrow screen the phone
   stands right where a name slides out. So they are given a layer over the phone
   and under the bar, moved by the same amount as the line under them. */
.rig__lamps {
  position: fixed;
  inset: 0;
  z-index: 3;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
:root:not(.js) .rig__lamps { display: none; }
.marks { position: absolute; inset: 0; will-change: transform; }
/* The button is a tap target centred on the point of the line; the label hangs
   off it, so the lamp itself sits exactly where the line reaches. */
.mark {
  position: absolute;
  display: grid;
  place-items: center;
  width: 2.9rem;
  height: 2.9rem;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-faint);
  font: inherit;
  transform: translate(-50%, -50%);
  cursor: pointer;
  pointer-events: auto;
  /* Not on the page when the page opens. A stop is something the line arrives
     at, so it is nowhere until the line is on its way to it; JavaScript brings
     it up as the run before it fills. */
  opacity: 0;
  transition: color 0.5s var(--ease);
}
/* The name of the screen slides out of the stop as the stop arrives, and it
   stands to the left of the block it names, level with it — the title said out
   in the margin, the words that answer it beside it. It is set smaller than the
   heading it introduces, so the two read as one thing at two weights instead of
   two headings fighting; and it is kept short enough on one line to clear the
   column of copy at every width. */
.mark__label {
  position: absolute;
  left: 100%;
  top: 50%;
  margin-left: 1.05rem;
  font-size: clamp(1.25rem, 1.6vw, 1.7rem);
  font-weight: 800;
  letter-spacing: -0.025em;
  line-height: 1.1;
  color: var(--violet-soft);
  white-space: nowrap;
  transform: translate(-0.6rem, -50%);
  opacity: 0;
  clip-path: inset(-0.3em 100% -0.3em 0);
  pointer-events: none;
  transition: opacity 0.4s var(--ease), transform 0.55s var(--ease), clip-path 0.55s var(--ease);
}
.mark.is-on .mark__label {
  opacity: 1;
  transform: translate(0, -50%);
  clip-path: inset(-0.3em 0 -0.3em 0);
}
.mark__dot {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--bg);
  color: var(--ink-faint);
  transition: background 0.55s var(--ease), border-color 0.55s var(--ease),
              color 0.55s var(--ease), transform 0.6s var(--ease), box-shadow 0.6s var(--ease);
}
.mark__dot svg { width: 1.25rem; height: 1.25rem; }
.mark:hover .mark__dot { border-color: color-mix(in srgb, var(--violet) 45%, var(--line)); color: var(--ink-soft); }
/* A lamp the line has already passed stays warm, just not the current one. */
.mark.is-past { color: var(--ink-soft); }
.mark.is-past .mark__dot {
  border-color: color-mix(in srgb, var(--violet) 60%, var(--line));
  background: color-mix(in srgb, var(--violet) 13%, var(--bg));
  color: var(--violet);
}
.mark.is-on { color: var(--violet-soft); }
.mark.is-on .mark__dot {
  border-color: transparent;
  background: var(--violet);
  color: var(--violet-ink);
  transform: scale(1.12);
  box-shadow: 0 0 0 0.45rem color-mix(in srgb, var(--violet) 12%, transparent),
              0 0 2rem 0.3rem var(--violet-glow);
}

/* ---------- the phone ---------- */

.rig__phone { position: absolute; inset: 0; opacity: 0; transition: opacity 0.6s var(--ease); }
.rig__phone canvas { display: block; width: 100%; height: 100%; }
.rig.is-3d .rig__phone { opacity: 1; }

/* Light behind the phone, not a disc under it: wide, soft-edged and weak, so it
   reads as the room the phone is standing in. A tighter, stronger version of
   this sat behind the body as a grey plate. */
.rig__glow {
  position: absolute;
  left: 50%;
  top: 46%;
  width: 108%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--violet-glow) 70%, transparent) 0%,
    color-mix(in srgb, var(--violet-glow) 22%, transparent) 34%,
    transparent 68%);
  opacity: 0.42;
  filter: blur(18px);
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* Only the fallback for no script or no WebGL — never a loading state, so the
   stage stays empty (just the ambient glow) rather than showing a flat, dim,
   shadowed stand-in of the phone while the real one builds. */
.rig__flat { position: absolute; inset: 0; display: none; place-items: center; }
:root:not(.js) .rig__flat { display: grid; }
.rig.no-3d .rig__flat { display: grid; }
.rig__flat-frame {
  position: relative;
  height: min(74%, 32rem);
  aspect-ratio: 0.4808;
  padding: 0.28rem;
  border: 1px solid var(--line);
  border-radius: 9% / 4%;
  background: #0d0d13;
  box-shadow: var(--shadow);
}
.rig__flat img {
  position: absolute;
  inset: 0.28rem;
  width: auto;
  height: auto;
  border-radius: 8% / 3.6%;
  opacity: 0;
  /* Same cut as the 3D screen: the status bar off the top, the gesture line
     off the bottom, the app's own top bar kept whole. */
  object-fit: cover;
  object-position: 50% 46.9%;
  transition: opacity 0.6s var(--ease);
}
.rig__flat img.is-on { opacity: 1; }

/* Without scripting the rig is already an ordinary stack; only the line, which
   nothing would drive, steps out of the way. */
:root:not(.js) .cut { min-height: 0; padding-block: 2.6rem; }
:root:not(.js) .cut__in { position: static; }
:root:not(.js) .rig__rail { display: none; }

/* ---------- how it started, then what you can say ---------- */

/* The line above ends here. It is not cut off: a wash of the page colour stands
   in front of it, solid where the story begins and gone by the time it is a
   screenful higher, so the line thins out into the page instead of stopping. */
.story-band {
  position: relative;
  padding: clamp(3.5rem, 12vh, 8rem) 0 clamp(1rem, 3vh, 2rem);
}
.story-band::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 100%;
  height: clamp(7rem, 16vh, 12rem);
  z-index: 1;
  background: linear-gradient(to bottom, transparent, var(--bg) 82%);
  pointer-events: none;
}
.says { padding: clamp(2.5rem, 7vh, 5rem) 0 clamp(3rem, 9vh, 6rem); }
/* Both of these sections are read as one column, set in the middle of the page
   so the empty room falls on either side of it rather than all on the right. */
.says__head { max-width: 62rem; margin-inline: auto; }
.eyebrow {
  margin: 0 0 0.9rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--violet-soft);
}
.says__lead { margin-top: 1rem; max-width: 60ch; color: var(--ink-soft); }

/* The story runs down one hairline: a number, then the step it belongs to. */
.story {
  max-width: 62rem;
  margin: clamp(2.4rem, 6vh, 3.6rem) auto 0;
  padding: 0;
  list-style: none;
  counter-reset: none;
}
.story__step {
  position: relative;
  display: grid;
  grid-template-columns: 3.1rem minmax(0, 1fr);
  gap: clamp(1rem, 2.2vw, 1.9rem);
  padding-bottom: clamp(1.9rem, 4.4vh, 3rem);
  transition: opacity 0.5s var(--ease);
}
/* A step that has not been reached is held back a little, so the one the light
   is on is the one being read and the three are a sequence rather than a list. */
.story__step:not(.is-lit):not(.is-done) { opacity: 0.58; }
/* The rail is drawn by the step above it, so it stops at the last number. */
.story__step::before {
  content: "";
  position: absolute;
  left: 1.55rem;
  top: 3.4rem;
  bottom: -0.3rem;
  width: 1px;
  background: var(--line);
}
/* The light that runs between two numbers: the same hairline, drawn over itself
   in violet and grown downwards by the scroll. It leaves one number, reaches the
   next, and that one takes over. */
.story__step::after {
  content: "";
  position: absolute;
  left: 1.55rem;
  top: 3.4rem;
  bottom: -0.3rem;
  width: 1px;
  background: var(--violet);
  box-shadow: 0 0 0.6rem 0.05rem var(--violet-glow);
  transform: scaleY(var(--fill, 0));
  transform-origin: 50% 0;
  opacity: calc(0.35 + 0.65 * var(--fill, 0));
}
.story__step--last::before,
.story__step--last::after { display: none; }

.story__no {
  display: grid;
  place-items: center;
  width: 3.1rem;
  height: 3.1rem;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--bg);
  font-size: 0.8125rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  color: var(--violet-soft);
  transition: background 0.5s var(--ease), border-color 0.5s var(--ease),
              color 0.5s var(--ease), transform 0.55s var(--ease), box-shadow 0.55s var(--ease);
}
/* The line runs on down into these three and goes out in them, one at a time. */
.story__step.is-lit .story__no {
  border-color: transparent;
  background: var(--violet);
  color: var(--violet-ink);
  transform: scale(1.1);
  box-shadow: 0 0 0 0.4rem color-mix(in srgb, var(--violet) 12%, transparent),
              0 0 1.8rem 0.25rem var(--violet-glow);
}
.story__step.is-done .story__no {
  border-color: color-mix(in srgb, var(--violet) 55%, var(--line));
  background: color-mix(in srgb, var(--violet) 12%, var(--bg));
  color: var(--violet);
}
.story__body { padding-top: 0.35rem; }
.story__body h3 {
  margin: 0;
  font-size: clamp(1.15rem, 1.9vw, 1.5rem);
  letter-spacing: -0.03em;
  transition: color 0.5s var(--ease);
}
.story__step.is-lit .story__body h3 { color: var(--violet-soft); }
.story__body > p {
  margin: 0.65rem 0 0;
  max-width: 62ch;
  color: var(--ink-soft);
}

/* What you can say: one table. Each sentence is marked up by part, and what
   the app saves is shown in the same colours, so it reads as a translation. */
.parse {
  max-width: 62rem;
  margin: clamp(1.8rem, 4.5vh, 2.6rem) auto 0;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  background: var(--card);
  box-shadow: var(--shadow-soft);
  overflow: hidden;
}
.parse .a { --c: var(--violet); }
.parse .w { --c: var(--sky); }
.parse .d { --c: var(--mint); }
.parse .t { --c: var(--amber); }
.parse .x { --c: var(--rose); }

/* One sentence taken apart: every piece underlined in its colour, with what
   the app takes it for written right under it. */
.anat {
  padding: clamp(1.6rem, 4vw, 2.6rem) clamp(1.1rem, 3vw, 2.2rem) clamp(1.3rem, 3vw, 1.8rem);
  text-align: center;
}
.anat__say {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: 1rem 0.5rem;
  margin: 0;
}
.pc { display: inline-flex; flex-direction: column; align-items: center; }
.pc__w {
  padding-bottom: 0.2rem;
  font-size: clamp(1.3rem, 2.4vw, 1.6rem);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.2;
  color: var(--ink-faint);
  white-space: nowrap;
}
.pc[class*=" "] .pc__w { color: var(--c); border-bottom: 3px solid var(--c); }
.pc__l {
  position: relative;
  margin-top: 0.7rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c);
  white-space: nowrap;
}
.pc__l::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 100%;
  width: 1.5px;
  height: 0.55rem;
  background: var(--c);
  transform: translateX(-50%);
}
.anat__res {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
  margin-top: 1.5rem;
}
.anat__arrow { color: var(--ink-faint); }
.anat__kind { font-size: 0.75rem; font-weight: 600; color: var(--ink-faint); }
.parse__toks { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 0.3rem; }
.parse__toks b {
  padding: 0.22rem 0.55rem;
  border: 1px solid color-mix(in srgb, var(--c) 30%, transparent);
  border-radius: 0.5rem;
  font-size: 0.875rem;
  font-weight: 650;
  line-height: 1.3;
  color: var(--c);
  background: color-mix(in srgb, var(--c) 10%, var(--card));
}

/* The rest is what can be done, one card per kind: the tag names the thing,
   the quote beside it is one way of saying it. */
.cmds {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.75rem;
  padding: clamp(0.75rem, 2vw, 1.1rem);
  border-top: 1px solid var(--line-soft);
  background: var(--card-2);
}
.g-a { --c: var(--violet); }
.g-w { --c: var(--sky); }
.g-d { --c: var(--mint); }
.g-t { --c: var(--amber); }
.cmds__group {
  padding: 1rem 1.05rem 1.1rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--card);
}
.cmds__head { display: flex; align-items: center; gap: 0.7rem; margin-bottom: 0.85rem; }
.cmds__icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 2.2rem;
  height: 2.2rem;
  border-radius: 0.7rem;
  color: var(--c);
  background: color-mix(in srgb, var(--c) 13%, var(--card));
}
.cmds h3 { margin: 0; font-size: 1.02rem; letter-spacing: -0.02em; color: var(--ink); }
.cmds__head p { margin: 0.1rem 0 0; font-size: 0.78rem; line-height: 1.35; color: var(--ink-faint); }
.cmds ul {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  align-items: baseline;
  gap: 0.5rem 0.7rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.cmds li { display: contents; }
.cmd__tag {
  justify-self: start;
  padding: 0.12rem 0.5rem;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 650;
  line-height: 1.5;
  white-space: nowrap;
  color: var(--c);
  background: color-mix(in srgb, var(--c) 11%, var(--card));
}
.cmd__say { font-size: 0.875rem; line-height: 1.4; color: var(--ink-soft); }

.mailhelp {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  padding: 1rem;
  background: rgba(10, 10, 16, 0.45);
  backdrop-filter: blur(4px);
}
.mailhelp__card {
  position: relative;
  width: min(100%, 24rem);
  padding: 1.6rem 1.4rem 1.4rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  background: var(--card);
  box-shadow: var(--shadow-soft);
  text-align: center;
}
.mailhelp__x {
  position: absolute;
  top: 0.7rem;
  right: 0.7rem;
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border: 0;
  border-radius: 50%;
  background: var(--card-2);
  color: var(--ink-soft);
  cursor: pointer;
}
.mailhelp__t { margin: 0; font-size: 1.125rem; font-weight: 700; color: var(--ink); }
.mailhelp__s { margin: 0.5rem 0 0; font-size: 0.875rem; color: var(--ink-soft); }
.mailhelp__addr { margin: 0.3rem 0 1.2rem; font-weight: 700; color: var(--violet); user-select: all; }
.mailhelp__row { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.6rem; }
.mailhelp__row .btn { padding: 0.7rem 1.2rem; font-size: 0.9375rem; }

/* ---------- closing band ---------- */

.close { padding: 0 0 clamp(4rem, 10vh, 7rem); }
.access {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1.2rem 2rem;
  max-width: 62rem;
  margin: clamp(1.6rem, 4vh, 2.2rem) auto 0;
  padding: clamp(1.1rem, 2.4vw, 1.5rem) clamp(1.1rem, 2.4vw, 1.6rem);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-lg);
  background: var(--card);
  box-shadow: var(--shadow-soft);
}
.access__info { display: grid; gap: 0.55rem; }
.access .status { margin: 0; padding: 0; border: 0; }
.access__mail { font-size: 0.875rem; color: var(--ink-faint); }

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

.foot {
  border-top: 1px solid var(--line);
  padding: clamp(2.2rem, 6vh, 3.6rem) 0 1.8rem;
  background: var(--bg-2);
}
.foot__grid {
  display: grid;
  grid-template-columns: 1.5fr 1fr 1fr;
  gap: 1.6rem 2rem;
}
.foot__blurb { margin-top: 0.85rem; max-width: 24rem; font-size: 0.875rem; color: var(--ink-faint); }
.foot__label {
  margin-bottom: 0.75rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.foot ul { margin: 0; padding: 0; list-style: none; display: grid; gap: 0.45rem; }
.foot a { font-size: 0.875rem; color: var(--ink-soft); text-decoration: none; }
.foot a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
.foot__base {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.5rem;
  margin-top: 2.2rem;
  padding-top: 1.2rem;
  border-top: 1px solid var(--line-soft);
  font-size: 0.8125rem;
  color: var(--ink-faint);
}

/* ---------- documents page ---------- */

.legal { padding-block: clamp(2rem, 7vh, 4.5rem) clamp(3rem, 9vh, 6rem); }
.crumbs { display: flex; gap: 0.5rem; margin-bottom: 1.6rem; font-size: 0.8125rem; color: var(--ink-faint); }
.crumbs a { text-decoration: none; }
.crumbs a:hover { text-decoration: underline; }

.legal__head { max-width: 46rem; }
.legal__head h1 { font-size: clamp(2.1rem, 5vw, 3.2rem); }
.legal__head p { margin-top: 1.1rem; color: var(--ink-soft); }

/* The head is read, then the documents start. Without a gap the first folded
   row reads as part of the sentence above it. */
.docs { display: grid; gap: 0.8rem; margin-top: clamp(2rem, 5vh, 3.25rem); }
.doc {
  /* A jump lands the title below the bar that was tapped, not under it. */
  scroll-margin-top: calc(var(--navh) + 4.2rem);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--card);
  overflow: hidden;
  transition: border-color 0.4s var(--ease);
}
.doc[open] { border-color: color-mix(in srgb, var(--violet) 34%, var(--line)); }
.doc > summary {
  display: flex;
  align-items: baseline;
  gap: 1rem;
  padding: clamp(1rem, 2vw, 1.5rem) clamp(1.1rem, 2.4vw, 1.9rem);
  font-size: clamp(1.1rem, 1.9vw, 1.4rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  cursor: pointer;
  list-style: none;
  transition: color 0.4s var(--ease), background 0.4s var(--ease);
}
.doc > summary::-webkit-details-marker { display: none; }
.doc > summary:hover { color: var(--violet-soft); background: var(--card-2); }
.doc > summary::after {
  content: "";
  flex: none;
  margin-left: auto;
  width: 0.55rem;
  height: 0.55rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 0.5s var(--ease);
}
.doc[open] > summary::after { transform: translateY(2px) rotate(-135deg); }
/* The document itself sits on the page colour, so the long read is not a wall
   of card inside a card. */
.doc__body {
  padding: clamp(1.2rem, 2.6vw, 2rem) clamp(1.1rem, 2.4vw, 1.9rem) clamp(1.6rem, 3vw, 2.4rem);
  border-top: 1px solid var(--line-soft);
  background: var(--bg);
}
/* The document can be taken off the page: the same text, saved as one file. */
.doc__get { margin-top: clamp(1.4rem, 3vw, 2.2rem); padding-top: 1.1rem; border-top: 1px solid var(--line-soft); }
.doc__dl {
  padding: 0.5rem 1.1rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink-soft);
  cursor: pointer;
  transition: color 0.4s var(--ease), border-color 0.4s var(--ease);
}
.doc__dl:hover { color: var(--violet-soft); border-color: color-mix(in srgb, var(--violet) 40%, var(--line)); }

.prose { max-width: 72ch; font-size: 0.9375rem; line-height: 1.72; color: var(--ink-soft); overflow-wrap: anywhere; }
.prose h1, .prose h2, .prose h3 { color: var(--ink); margin: 2rem 0 0.7rem; letter-spacing: -0.02em; line-height: 1.25; }
.prose h1 { font-size: 1.35rem; }
.prose h2 { font-size: 1.1rem; }
.prose h3 { font-size: 0.95rem; }
.prose h1:first-child, .prose h2:first-child { margin-top: 0; }
.prose p { margin: 0 0 0.95rem; }
.prose a { color: var(--violet-soft); }
.prose ul, .prose ol { padding-left: 1.2rem; }
.prose li { margin-bottom: 0.4rem; }
.prose table {
  display: block;
  width: 100%;
  overflow-x: auto;
  border-collapse: collapse;
  font-size: 0.8125rem;
}
.prose td, .prose th { border: 1px solid var(--line); padding: 0.5rem 0.65rem; text-align: left; }
.prose th { background: var(--card-2); color: var(--ink); }
.prose img { display: none; }

/* ---------- the two documents pasted out of Termly ---------- */
/* The privacy policy and the terms arrive as one blob of vendor HTML that
   carries its own palette for white paper: a <style> block of `!important`
   rules plus a few thousand inline `color: rgb(89, 89, 89)`. Both outrank the
   page — an inline style beats a plain rule, and their `!important` beats ours
   on a tie — so in the dark theme the binding wording came out near-black on a
   near-black page, which is the one document we promise is readable.

   Repainted with the site's own tokens, so this needs no dark-mode copy: the
   tokens flip themselves. Two things make it stick — `!important` (that is what
   beats an inline style) and one class more than Termly's selectors carry (that
   is what wins the tie against their `!important`). Order inside this block
   matters as much as specificity: the blanket rule comes first, the headings
   and links below it win on being later. */
.doc__body [style],
.doc__body [data-custom-class],
.doc__body [data-custom-class] * {
  color: var(--ink-soft) !important;
  background-color: transparent !important;
}
.doc__body [data-custom-class="title"],
.doc__body [data-custom-class="title"] *,
.doc__body [data-custom-class="heading_1"],
.doc__body [data-custom-class="heading_1"] *,
.doc__body [data-custom-class="heading_2"],
.doc__body [data-custom-class="heading_2"] * { color: var(--ink) !important; }
/* `--ink-faint` would be the obvious pick for the "Last updated" line, but at
   this size it lands on 4.3:1 against the light page — under AA. `--ink-soft`
   keeps the line quiet and legible in both themes. */
.doc__body [data-custom-class="subtitle"],
.doc__body [data-custom-class="subtitle"] * { color: var(--ink-soft) !important; }
/* Links need the `[style]` on the selector too: `.doc__body a` alone is a class
   short of the blanket rule above and would lose to it on every link Termly
   left an inline colour on. */
.doc__body a,
.doc__body a[style],
.doc__body a *,
.doc__body a [style],
.doc__body [data-custom-class="link"],
.doc__body [data-custom-class="link"] * { color: var(--violet-soft) !important; }
/* Termly's own logo is a black SVG baked into a background image (their free
   plan requires it to stay). Flipped to light so it does not vanish into the
   dark page — inverted rather than hidden, the branding has to remain visible. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .doc__body span[style*="base64"] { filter: invert(1) brightness(1.7); }
}
:root[data-theme="dark"] .doc__body span[style*="base64"] { filter: invert(1) brightness(1.7); }

/* ---------- 404 ---------- */

.oops { min-height: 60vh; display: grid; place-items: center; text-align: center; padding-block: 3.5rem; }
.oops__code {
  font-size: clamp(4rem, 15vw, 8.5rem);
  font-weight: 800;
  letter-spacing: -0.06em;
  line-height: 1;
  color: var(--violet-soft);
}
.oops p { max-width: 32rem; margin: 1rem auto 0; color: var(--ink-soft); }
.oops .btn-row { justify-content: center; margin-top: 1.8rem; }

/* ---------- reveal ---------- */

[data-reveal] {
  opacity: 0;
  transform: translateY(0.9rem);
  filter: blur(3px);
  transition: opacity 0.9s var(--ease), transform 0.9s var(--ease), filter 0.9s var(--ease);
}
[data-reveal].is-in { opacity: 1; transform: none; filter: none; }

/* ---------- tablet ---------- */

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

/* ---------- the rig stacks: phone above, story beneath ---------- */

@media (max-width: 900px) {
  /* No lane down the side: the phone and the words take the whole width, and
     the track runs across the page behind both of them, exactly as it does on
     a wide screen — only tighter. */
  :root { --rail: 0rem; --lane: 0rem; --bar: 0rem; }
  /* How tall the phone is allowed to stand. Half the screen is what it wants,
     but the words are parked underneath it and have to fit in what is left: on
     a short screen — and every screen is short while the address bar is up —
     half the height for the phone left the foot of every block below the fold.
     So it takes half the screen or whatever is left after the words, whichever
     is less; on a tall phone that is the same 51dvh it always was. */
  :root { --stageh: min(51dvh, calc(100dvh - var(--navh) - 21rem)); }
  .rig__inner { display: block; }
  /* The phone parks under the bar and stays there for the whole story, then
     lets go with the last cut and leaves upwards with it. */
  /* Held clear of the bar rather than under it — at 0.4rem the top corner of the
     phone ran into it — and given the room that buys it back in height. */
  .rig__stage {
    top: calc(var(--navh) + 1.5rem);
    height: 51vh;
    height: var(--stageh);
    margin-bottom: 1.4rem;
  }
  /* The words leave upwards under the phone and are taken out as they go, by a
     mask that JavaScript lays over the foot of the phone — see `dress` in
     rig.js. A sheet of the page's own colour was tried here first and had to go:
     it hid the line as well, and the line has to run on behind the phone. */
  /* Kept inside the screen. At 130% the light stood a good inch past the right
     edge, and on a phone that is not a light — it is thirty-six pixels of blank
     page you can drag the whole site onto. */
  .rig__glow { top: 44%; width: 100%; }

  .mark { width: 2.4rem; height: 2.4rem; }
  .mark__dot svg { width: 1.05rem; height: 1.05rem; }
  /* The name still slides out sideways, just sized for the screen it is on. Here
     it slides out over the phone rather than beside it, so it carries a little of
     the page with it and is read off the phone instead of getting lost in it. */
  .mark__label {
    margin-left: 0.4rem;
    font-size: 1.15rem;
    padding: 0.15rem 0.5rem;
    border-radius: 0.65rem;
    background: color-mix(in srgb, var(--bg) 74%, transparent);
    -webkit-backdrop-filter: blur(7px);
    backdrop-filter: blur(7px);
  }
  .trace__halo { stroke-width: 11; filter: blur(8px); }

  .rig__copy { padding-left: 0; }
  /* Long enough that a block holds its place under the phone, is let go of, and
     then has a stretch of screen to leave over — which is the whole handover
     here: the words ride up into the sheet the phone stands on and are gone by
     the time the next ones have come up to the line. At 82dvh there was no room
     for that and each block simply dissolved where it stood. */
  .cut { min-height: 118vh; min-height: 118dvh; align-content: start; }
  /* Here the stop is over the words rather than beside them, so the block is
     held by its top edge as it always was — nothing to centre on. */
  /* Parked directly under the phone, off the same height the phone is given,
     rather than off a copy of it: the two used to be written out separately and
     a gap of 3.5rem was left between them, which on a short screen was 3.5rem
     of the block hanging off the bottom of the page. */
  .cut__in {
    top: calc(var(--navh) + 51dvh + 5rem);
    top: calc(var(--navh) + 1.5rem + var(--stageh) + 1.25rem);
    transform: none;
  }
  .cut[data-cut="0"] .cut__in { transform: none; }
  .cut[data-cut="0"] { min-height: 82vh; min-height: 82dvh; }
  .cut h1 { font-size: clamp(1.9rem, 7.6vw, 2.5rem); }
  .cut h2 { font-size: clamp(1.5rem, 6.2vw, 2.1rem); }
  .cut__lead, .cut__body { max-width: 46ch; font-size: 0.9375rem; }
  .cut__lead { margin-top: 1rem; }
  .cut__body { margin-top: 0.9rem; }
  .cut__note { margin-top: 0.8rem; padding-top: 0.7rem; font-size: 0.75rem; }
  .cut .btn-row { margin-top: 1.3rem; }
  .only-wide { display: none; }
  .only-narrow { display: block; }
  .status { margin-top: 1.3rem; }

  .access { flex-direction: column; align-items: stretch; }
  .access .btn--lead { justify-content: center; }

  .story__step { grid-template-columns: 2.5rem minmax(0, 1fr); }
  .story__step::before, .story__step::after { left: 1.25rem; top: 2.8rem; }
  .story__no { width: 2.5rem; height: 2.5rem; font-size: 0.75rem; }
}

/* ---------- phone ---------- */

@media (max-width: 720px) {
  .cmds { grid-template-columns: minmax(0, 1fr); }
  .cmds__group { padding: 0.9rem 0.85rem 1rem; }
  .cmds ul { gap: 0.5rem 0.55rem; }
  .anat__say { gap: 0.9rem 0.4rem; }
  .nav__links {
    position: absolute;
    left: var(--gut);
    right: var(--gut);
    top: calc(100% + 0.4rem);
    flex-direction: column;
    align-items: stretch;
    gap: 0.15rem;
    padding: 0.55rem;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: color-mix(in srgb, var(--bg) 96%, transparent);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    box-shadow: var(--shadow);
    opacity: 0;
    transform: translateY(-0.5rem);
    pointer-events: none;
    transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
  }
  .nav.is-open .nav__links { opacity: 1; transform: none; pointer-events: auto; }
  .nav__link { padding: 0.6rem 0.8rem; }
  .burger { display: block; margin-left: auto; }
  .nav__tail { margin-left: 0; }


  .doc { scroll-margin-top: calc(var(--navh) + 0.8rem); }
  .foot__grid { grid-template-columns: 1fr; gap: 1.3rem; }
  .foot__base { margin-top: 1.6rem; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, ::before, ::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  [data-reveal] { opacity: 1; transform: none; filter: none; }
}
