/* The landing page: where things stand and how they move. What they are
 * made of - the tokens, the ground, her in her rings - is theme.css,
 * loaded first.
 *
 * Three rules it keeps, the panel's own:
 *   - her three colours carry everything; the holographic pair appears
 *     only in light that moves, never as a fill or as text,
 *   - serif italic for what is said, mono for what is counted, wide
 *     capitals for her name and nothing else,
 *   - everything that moves can stand still: prefers-reduced-motion, and
 *     a page without its script, are the same page without the motion.
 */

@property --turn {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

/* ---------------------------------------------------------------------- *
 * The page
 * ---------------------------------------------------------------------- */

html {
  scroll-behavior: smooth;
  scroll-padding-top: 4.5rem;
  /* A sentence being typed, a word arriving out of its blur, a scene a
   * pixel wider than its box: any of them can put a scrollbar along the
   * bottom of a phone. Clipped here, where `overflow: hidden` on the body
   * alone was not enough - a fixed or absolutely placed thing is laid out
   * against this element, not that one.
   *
   * `clip` on both, and it has to be both. The viewport takes its overflow
   * from this element, and only while this one is `visible` does it go on
   * to take it from the body - so clipping here alone left the body with
   * the theme's own `overflow-x: hidden`, which turns its `visible` other
   * axis into `auto` and makes the body itself the thing that scrolls. The
   * page then scrolled inside an element nobody was watching: `scrollY`
   * stayed at zero, and her portrait stopped drifting as the page moved.
   * `clip` rather than `hidden` on both, because `clip` leaves the other
   * axis alone. */
  overflow-x: clip;
}

body { overflow-x: clip; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* theme.css centres one thing in the window; this page is a column of
 * sections instead, each of which keeps itself clear of the notch. */
body {
  display: block;
  padding: 0;
}

main { width: 100%; }

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

.glyph {
  width: 1.25rem;
  height: 1.25rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

a { color: inherit; }

.section-title {
  margin: 0;
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.9rem, 4.6vw, 3.1rem);
  line-height: 1.12;
  color: var(--ink-bright);
  text-wrap: balance;
}

/* Arriving as it scrolls into view. Only once the script has said it is
 * there to take it away again - without it, nothing waits to be shown. */
.js .reveal {
  opacity: 0;
  transform: translateY(22px);
  filter: blur(6px);
  transition:
    opacity var(--t-reveal) var(--ease-out),
    transform var(--t-reveal) var(--ease-out),
    filter var(--t-reveal) var(--ease-out);
  transition-delay: calc(var(--k, 0) * 90ms);
}

.js .reveal.is-in { opacity: 1; transform: none; filter: none; }

@media (prefers-reduced-motion: reduce) {
  .js .reveal { opacity: 1; transform: none; filter: none; transition: none; }
}

/* ---------------------------------------------------------------------- *
 * The mast - there once the hero has gone
 * ---------------------------------------------------------------------- */

.mast {
  position: fixed;
  z-index: 50;
  top: 0;
  left: 0;
  right: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding:
    calc(.9rem + env(safe-area-inset-top, 0px))
    max(1.4rem, env(safe-area-inset-right, 0px))
    .9rem
    max(1.4rem, env(safe-area-inset-left, 0px));
  background: linear-gradient(180deg, rgba(0, 0, 0, .72), rgba(0, 0, 0, .35));
  -webkit-backdrop-filter: blur(14px) saturate(120%);
          backdrop-filter: blur(14px) saturate(120%);
  -webkit-mask-image: linear-gradient(180deg, #000 70%, transparent);
          mask-image: linear-gradient(180deg, #000 70%, transparent);
  opacity: 0;
  transform: translateY(-8px);
  pointer-events: none;
  transition: opacity var(--t-slow) var(--ease-out), transform var(--t-slow) var(--ease-out);
}

.mast.is-on { opacity: 1; transform: none; pointer-events: auto; }

.mast__mark {
  font-size: .82rem;
  letter-spacing: .5em;
  padding-left: .5em;
  text-decoration: none;
  color: var(--ink-bright);
}

.mast__links { display: flex; gap: 1.6rem; }

.mast__links a {
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .24em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--ink-soft);
  transition: color var(--t) var(--ease-out);
}

.mast__links a:hover,
.mast__links a:focus-visible,
.mast__mark:focus-visible { color: var(--light); outline: none; }

/* ---------------------------------------------------------------------- *
 * The sound - one speaker in the corner, above the mast
 * ---------------------------------------------------------------------- */

.hush {
  position: fixed;
  z-index: 51;
  top: calc(.2rem + env(safe-area-inset-top, 0px));
  right: max(1.1rem, env(safe-area-inset-right, 0px));
  display: none;
  place-items: center;
  width: 2.3rem;
  height: 2.3rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
  transition: color var(--t) var(--ease-out), transform var(--t) var(--ease-out);
}

.js .hush { display: grid; }

.hush:hover,
.hush:focus-visible { color: var(--light); outline: none; }
.hush:active { transform: scale(.92); }

.hush .glyph { grid-area: 1 / 1; width: 1.1rem; height: 1.1rem; transition: opacity var(--t) var(--ease-out); }
.hush__off { opacity: 0; }
.hush.is-muted { color: var(--ink-faint); }
.hush.is-muted .hush__on { opacity: 0; }
.hush.is-muted .hush__off { opacity: 1; }

/* The mast's links make room for it. */
.js .mast { padding-right: calc(max(1.1rem, env(safe-area-inset-right, 0px)) + 3rem); }

/* ---------------------------------------------------------------------- *
 * The hero: her, her name, and the bar
 * ---------------------------------------------------------------------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding:
    calc(3rem + env(safe-area-inset-top, 0px))
    max(20px, env(safe-area-inset-right, 0px))
    5rem
    max(20px, env(safe-area-inset-left, 0px));
  text-align: center;
}

.hero__figure { will-change: transform, opacity; }

.hero__sigil {
  --size: clamp(150px, 30vh, 270px);
  margin-bottom: clamp(1.4rem, 4vh, 2.4rem);
}

.hero__name { font-size: clamp(2.6rem, 10vw, 4.6rem); }

/* A thread of light down from the bar into the rest of the page: where
 * it goes is the only direction there is to go. */
.hero__line {
  position: absolute;
  left: 50%;
  bottom: 0;
  width: 1px;
  height: 4.2rem;
  overflow: hidden;
  background: linear-gradient(180deg, rgba(117, 150, 243, 0), rgba(117, 150, 243, .35));
}

.hero__line::after {
  content: "";
  position: absolute;
  left: 0;
  width: 1px;
  height: 40%;
  background: linear-gradient(180deg, transparent, var(--holo-cyan), transparent);
  animation: fall 2.8s var(--ease-in-out) infinite;
}

@keyframes fall {
  from { transform: translateY(-100%); }
  to   { transform: translateY(260%); }
}

/* --- the preview ----------------------------------------------------- */

.talk {
  width: min(100%, 36rem);
  margin-top: clamp(2.2rem, 7vh, 3.8rem);
}

/* What was asked, and what she said - played, not typed by anybody.
 * The room is kept whether there is a line in it or not, so the bar
 * never moves. */
.talk__said {
  min-height: 5.4rem;
  /* And a ceiling, for the day she is feeling talkative: the bar has to
   * stay on the screen, so a long answer scrolls inside its own room
   * rather than pushing the thing it was typed into out of the window. */
  max-height: 42svh;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: .55rem;
  margin-bottom: 1.1rem;
}

.talk__said::-webkit-scrollbar { display: none; }

.talk__asked {
  margin: 0;
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .06em;
  color: var(--ink-faint);
  opacity: 0;
  transition: opacity var(--t-slow) var(--ease-out);
}

.talk__answer {
  margin: 0;
  overflow-wrap: anywhere;
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(1.05rem, 3.4vw, 1.3rem);
  line-height: 1.45;
  color: var(--ink-bright);
  text-wrap: balance;
}

.talk__said.is-shown .talk__asked { opacity: 1; }

.talk__said.is-leaving > * {
  opacity: 0;
  filter: blur(6px);
  transition: opacity 700ms var(--ease-in-out), filter 700ms var(--ease-in-out);
}

/* Her words arrive one at a time, each out of its own blur. */
.talk__answer .w {
  display: inline-block;
  opacity: 0;
  filter: blur(8px);
  transform: translateY(4px);
  animation: word-in 900ms var(--ease-out) forwards;
  animation-delay: calc(var(--w) * 70ms);
}

@keyframes word-in {
  to { opacity: 1; filter: blur(0); transform: none; }
}

/* Thinking: three points, before the words. */
.talk__answer .think { display: inline-flex; gap: .35rem; padding: .5rem 0; }

.talk__answer .think i {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--light);
  animation: pulse 1.1s ease-in-out infinite;
}

.talk__answer .think i:nth-child(2) { animation-delay: .18s; }
.talk__answer .think i:nth-child(3) { animation-delay: .36s; }

.bar {
  display: flex;
  align-items: center;
  gap: .75rem;
  /* Room on the left where her portrait used to sit: the text starts
   * where it would have ended, so the bar is a line to write on rather
   * than a field with something in front of it. */
  padding: .45rem .45rem .45rem 1.35rem;
  border-radius: 999px;
  background:
    radial-gradient(120% 140% at 50% -40%, rgba(45, 63, 171, .28), transparent 60%),
    linear-gradient(180deg, var(--surface), var(--surface-deep));
  -webkit-backdrop-filter: blur(18px) saturate(135%);
          backdrop-filter: blur(18px) saturate(135%);
  box-shadow:
    0 24px 60px -34px rgba(0, 0, 0, .9),
    0 0 70px -30px rgba(45, 63, 171, .45),
    inset 0 1px 0 rgba(233, 238, 252, .06);
  text-align: left;
}

/* The bar's edge turns while she is "listening" - the demo typing, or
 * somebody in the field - and rests otherwise, like the panel's. */
.bar.is-live::before { opacity: .8; animation-play-state: running; }

.bar__field { position: relative; flex: 1; min-width: 0; overflow: hidden; }

.bar__input {
  width: 100%;
  padding: .7rem 0;
  border: 0;
  outline: none;
  background: transparent;
  font: inherit;
  font-size: 1rem;
  color: var(--ink-bright);
  caret-color: var(--light);
}

.bar__ghost {
  position: absolute;
  inset: 0;
  max-width: 100%;
  display: flex;
  align-items: center;
  overflow: hidden;
  white-space: nowrap;
  font-size: 1rem;
  color: var(--ink-soft);
  pointer-events: none;
}

/* A caret that belongs to the ghost, not the field. */
.bar__ghost::after {
  content: "";
  width: 1px;
  height: 1.15em;
  margin-left: 2px;
  background: var(--light);
  animation: blink 1s steps(1) infinite;
}

.bar__field:focus-within .bar__ghost,
.bar__field.has-text .bar__ghost { display: none; }

.bar__send {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.75rem;
  height: 2.75rem;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  color: var(--ink-bright);
  background: linear-gradient(160deg, var(--mid), var(--dark));
  box-shadow: inset 0 1px 0 rgba(233, 238, 252, .12), 0 0 0 1px var(--line-strong);
  transition: transform var(--t) var(--ease-spring), box-shadow var(--t) var(--ease-out);
}

.bar__send:hover { box-shadow: inset 0 1px 0 rgba(233, 238, 252, .16), 0 0 0 1px var(--light), 0 0 22px rgba(117, 150, 243, .35); }
.bar__send:active { transform: scale(.94); }
.bar__send:focus-visible { outline: 1px solid var(--light); outline-offset: 3px; }
.bar__send .glyph { width: 1.15rem; height: 1.15rem; transform: translateX(-1px); }

.bar__send.is-sent { animation: sent 620ms var(--ease-out); }

@keyframes sent {
  0%   { box-shadow: inset 0 1px 0 rgba(233, 238, 252, .12), 0 0 0 1px var(--line-strong), 0 0 0 0 rgba(143, 227, 255, .5); }
  100% { box-shadow: inset 0 1px 0 rgba(233, 238, 252, .12), 0 0 0 1px var(--line-strong), 0 0 0 14px rgba(143, 227, 255, 0); }
}

.talk__state {
  min-height: 1.2rem;
  margin: .9rem 0 0;
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .12em;
  color: var(--light);
  opacity: 0;
  transition: opacity var(--t-slow) var(--ease-out);
}

.talk__state.is-on { opacity: 1; }

/* ---------------------------------------------------------------------- *
 * Abilities: the orbit, and the chapters it turns through
 * ---------------------------------------------------------------------- */

.abilities {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(2rem, 6vw, 6rem);
  width: min(100%, 76rem);
  margin: 0 auto;
  padding: clamp(5rem, 14vh, 9rem) max(24px, env(safe-area-inset-right, 0px)) 0 max(24px, env(safe-area-inset-left, 0px));
}

.stage {
  position: sticky;
  top: 0;
  height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2rem;
}

.orbit {
  --o: min(30rem, 38vw, 70svh);
  --turn: 0;
  position: relative;
  width: var(--o);
  height: var(--o);
  transition: --turn 1400ms var(--ease-glide);
}

.orbit__ring {
  position: absolute;
  inset: 12%;
  border-radius: 50%;
  border: 1px solid var(--line);
}

.orbit__ring--inner { inset: 30%; border-color: rgba(117, 150, 243, .1); }

/* The outer ring is a comet: a short arc of light running round. */
.orbit__ring--outer {
  inset: 0;
  border: 0;
  background: conic-gradient(from 0deg, transparent 0deg, rgba(143, 227, 255, .55) 22deg, transparent 60deg, transparent 360deg);
  -webkit-mask: radial-gradient(closest-side, transparent 99%, #000 99.5%);
          mask: radial-gradient(closest-side, transparent 99%, #000 99.5%);
  opacity: .3;
  animation: turn 18s linear infinite;
}

.orbit__core {
  position: absolute;
  inset: 34%;
  border-radius: 50%;
}

.orbit__core::before {
  content: "";
  position: absolute;
  inset: -40%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(117, 150, 243, .22), rgba(45, 63, 171, .08) 45%, transparent 70%);
}

.orbit__core img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  -webkit-mask-image: radial-gradient(circle at 50% 46%, #000 52%, transparent 74%);
          mask-image: radial-gradient(circle at 50% 46%, #000 52%, transparent 74%);
  animation: breathe 9s ease-in-out infinite;
}

/* Eight places at 45° apart, turned together so the chapter being read
 * sits at twelve o'clock; each node turns back by the same amount, so
 * its icon stays upright all the way round. */
.orbit__wheel {
  position: absolute;
  inset: 12%;
  transform: rotate(calc(var(--turn) * -45deg));
}

.orbit__node {
  --size: 2.9rem;
  position: absolute;
  top: 50%;
  left: 50%;
  display: grid;
  place-items: center;
  width: var(--size);
  height: var(--size);
  margin: calc(var(--size) / -2) 0 0 calc(var(--size) / -2);
  border-radius: 50%;
  color: var(--ink-faint);
  background: rgba(3, 5, 20, .9);
  box-shadow: 0 0 0 1px var(--line);
  transform:
    rotate(calc(var(--n) * 45deg))
    translateY(calc(var(--o) * -.38))
    rotate(calc(var(--n) * -45deg + var(--turn) * 45deg));
  transition: color var(--t-slow) var(--ease-out), box-shadow var(--t-slow) var(--ease-out), background var(--t-slow) var(--ease-out);
}

.orbit__node:hover { color: var(--ink); }

.orbit__node.is-on {
  color: var(--ink-bright);
  background: radial-gradient(circle at 50% 30%, rgba(45, 63, 171, .6), rgba(3, 5, 20, .95));
  box-shadow: 0 0 0 1px var(--light), 0 0 26px rgba(117, 150, 243, .45);
}

.orbit__node.is-on::after {
  content: "";
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  border: 1px solid rgba(143, 227, 255, .5);
  animation: ripple 2.4s var(--ease-out) infinite;
}

.stage__count {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: .6rem;
  font-family: var(--mono);
  letter-spacing: .14em;
  color: var(--ink-faint);
}

.stage__count b { font-weight: 400; font-size: 1.1rem; color: var(--light); }
.stage__count span { font-size: .7rem; }

/* --- a chapter ------------------------------------------------------- */

.chapters { display: flex; flex-direction: column; }

/* Room under the last chapter, so the orbit keeps standing while it is
 * still being read. The sticky stage is held by this column: the moment
 * the column ends, the orbit lets go and rides up with the page - which,
 * with the last chapter still across the middle of the window, read as
 * the scrolling having stopped early. Half a window of travel past it is
 * what lets the chapter leave the middle before the orbit does. */
@media (min-width: 861px) {
  .chapters { padding-bottom: 48svh; }
}

.chapter {
  min-height: 88svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 3rem 0;
}

.chapter__index {
  display: flex;
  align-items: center;
  gap: .9rem;
  margin: 0 0 1.1rem;
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .2em;
  color: var(--light);
}

.chapter__index span {
  display: flex;
  align-items: center;
  gap: .9rem;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.chapter__index span::before {
  content: "";
  width: 2.2rem;
  height: 1px;
  background: linear-gradient(90deg, var(--light), transparent);
}

.chapter__title {
  margin: 0;
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.8rem, 3.6vw, 2.6rem);
  line-height: 1.15;
  color: var(--ink-bright);
  text-wrap: balance;
}

.chapter__line {
  max-width: 30rem;
  margin: 1rem 0 2rem;
  font-size: .98rem;
  line-height: 1.7;
  color: var(--ink-soft);
  text-wrap: pretty;
}

/* The chapter's parts arrive in order as it comes into view. */
.js .chapter > * {
  opacity: 0;
  transform: translateY(18px);
  filter: blur(6px);
  transition:
    opacity var(--t-reveal) var(--ease-out),
    transform var(--t-reveal) var(--ease-out),
    filter var(--t-reveal) var(--ease-out);
}

.js .chapter > :nth-child(2) { transition-delay: 90ms; }
.js .chapter > :nth-child(3) { transition-delay: 180ms; }
.js .chapter > :nth-child(4) { transition-delay: 300ms; }

.js .chapter.is-in > * { opacity: 1; transform: none; filter: none; }

@media (prefers-reduced-motion: reduce) {
  .js .chapter > * { opacity: 1; transform: none; filter: none; transition: none; }
}

/* ---------------------------------------------------------------------- *
 * Scenes: one small instrument per chapter
 *
 * Each runs only while its chapter is the one being read (.is-live); the
 * rest hold still, so eight loops never run at once.
 * ---------------------------------------------------------------------- */

.scene {
  --loop: 8s;
  position: relative;
  height: clamp(13rem, 30vh, 16rem);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background:
    radial-gradient(90% 80% at 50% 0%, rgba(45, 63, 171, .16), transparent 70%),
    var(--box-bg-still);
  box-shadow: 0 0 0 1px var(--box-line), 0 30px 70px -45px rgba(0, 0, 0, .9);
}

/* The scanlines of the panel's glass, as one 3px tile - see components.css
 * in the panel for why a tile rather than a repeating gradient. */
.scene::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(rgba(200, 211, 242, .022) 0 1px, transparent 1px) 0 0 / 100% 3px;
}

.scene__ticks {
  --c: rgba(117, 150, 243, .35);
  --l: 9px;
  position: absolute;
  inset: 9px;
  pointer-events: none;
  background:
    linear-gradient(var(--c), var(--c)) top left     / var(--l) 1px no-repeat,
    linear-gradient(var(--c), var(--c)) top left     / 1px var(--l) no-repeat,
    linear-gradient(var(--c), var(--c)) top right    / var(--l) 1px no-repeat,
    linear-gradient(var(--c), var(--c)) top right    / 1px var(--l) no-repeat,
    linear-gradient(var(--c), var(--c)) bottom left  / var(--l) 1px no-repeat,
    linear-gradient(var(--c), var(--c)) bottom left  / 1px var(--l) no-repeat,
    linear-gradient(var(--c), var(--c)) bottom right / var(--l) 1px no-repeat,
    linear-gradient(var(--c), var(--c)) bottom right / 1px var(--l) no-repeat;
}

.scene *, .scene *::before, .scene *::after { animation-play-state: paused; }
.chapter.is-live .scene *,
.chapter.is-live .scene *::before,
.chapter.is-live .scene *::after { animation-play-state: running; }

@media (prefers-reduced-motion: reduce) {
  .scene *, .scene *::before, .scene *::after { animation: none !important; }
}

.scene p, .scene ol { margin: 0; padding: 0; list-style: none; }

/* --- 01 one thread through five screens ------------------------------ */

.scene--thread { --loop: 6s; }

.thread__wire {
  position: absolute;
  left: 10%;
  right: 10%;
  top: 44%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--line-strong) 8%, var(--line-strong) 92%, transparent);
}

.thread__spark {
  position: absolute;
  top: -2px;
  left: 0;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--holo-cyan);
  box-shadow: 0 0 12px 3px rgba(143, 227, 255, .55), -30px 0 24px -6px rgba(143, 227, 255, .35);
  /* Linear, and it has to be. The stops light up on a timer of their own,
   * so the only way the two can agree is if the light travels at a
   * constant speed: with an eased spark it was already at `web` while
   * `phone` was still lighting up. */
  animation: spark var(--loop) linear infinite;
}

/* Across the wire in the first four fifths of the loop, then a moment at
 * the far end before it begins again. Four fifths over four gaps is one
 * fifth per gap, which is exactly the delay between one stop and the
 * next below - that is the whole of the synchronisation. */
@keyframes spark {
  0%   { left: 0%;    opacity: 0; }
  4%   {              opacity: 1; }
  76%  {              opacity: 1; }
  80%  { left: 100%;  opacity: 1; }
  90%  { left: 100%;  opacity: 0; }
  100% { left: 100%;  opacity: 0; }
}

.thread__stops {
  position: absolute;
  left: 10%;
  right: 10%;
  top: 44%;
  display: flex;
  justify-content: space-between;
}

.thread__stops li {
  position: relative;
  width: 0;
  display: flex;
  justify-content: center;
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .12em;
  color: var(--ink-faint);
  animation: stop-lit var(--loop) linear infinite;
  /* One fifth of the loop between one stop and the next: the spark covers
   * one of the four gaps in exactly that. No offset - a stop lights as the
   * light reaches it, and the first one lights as it sets off. */
  animation-delay: calc(var(--k) * var(--loop) * .2);
}

.thread__stops li::before {
  content: "";
  position: absolute;
  top: -5px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #030514;
  box-shadow: 0 0 0 1px var(--line-strong);
  animation: stop-dot var(--loop) linear infinite;
  animation-delay: inherit;
}

.thread__stops li { padding-top: 1.1rem; }

@keyframes stop-lit {
  0%, 6%   { color: var(--ink-bright); }
  22%, 100% { color: var(--ink-faint); }
}

@keyframes stop-dot {
  0%, 6% {
    box-shadow: 0 0 0 1px var(--holo-cyan), 0 0 16px rgba(143, 227, 255, .6);
    background: var(--mid);
  }
  22%, 100% { box-shadow: 0 0 0 1px var(--line-strong); background: #030514; }
}

/* --- 02 her notebook ------------------------------------------------- */

.scene--notes { --loop: 10s; padding: 1.6rem 1.8rem; }

.notes__file {
  margin-bottom: 1rem !important;
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .2em;
  color: var(--ink-faint);
}

.notes li {
  width: max-content;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  margin-bottom: .75rem;
  font-family: var(--mono);
  font-size: clamp(.66rem, 1.5vw, .78rem);
  color: var(--ink);
  clip-path: inset(0 100% 0 0);
  animation: note-in var(--loop) steps(28, end) infinite;
  animation-delay: calc(var(--k) * 1.1s);
}

.notes li b { font-weight: 400; color: var(--light); margin-right: .4rem; }
.notes li em { font-style: normal; color: var(--holo-violet); opacity: .8; }

@keyframes note-in {
  0%       { clip-path: inset(0 100% 0 0); }
  12%      { clip-path: inset(0 0 0 0); }
  86%      { clip-path: inset(0 0 0 0); opacity: 1; }
  96%, 100% { clip-path: inset(0 0 0 0); opacity: 0; }
}

/* --- 03 a reminder asking again -------------------------------------- */

.scene--nudge { --loop: 8s; }

.nudge__track {
  position: absolute;
  left: 8%;
  right: 12%;
  top: 46%;
  height: 1px;
  background: var(--line);
}

/* The hours fill up at a constant speed, and each mark is placed and
 * timed by the same number - `--t`, its place along the track. Where the
 * fill is at any moment and which mark has just lit are then the same
 * arithmetic rather than two timers that happen to look alike: eased, and
 * with the marks on a plain count, the bar was past an hour well before
 * it rang. `--run` is the part of the loop the filling takes; the rest is
 * the answered mark being read before it starts again. */
.scene--nudge { --run: .85; }

.nudge__fill {
  position: absolute;
  inset: 0;
  transform-origin: left;
  background: linear-gradient(90deg, var(--mid), var(--light));
  animation: nudge-fill var(--loop) linear infinite;
}

@keyframes nudge-fill {
  0%        { transform: scaleX(0); opacity: 1; }
  85%       { transform: scaleX(1); opacity: 1; }
  94%, 100% { transform: scaleX(1); opacity: 0; }
}

.nudge__marks li {
  position: absolute;
  top: 46%;
  left: calc(var(--t) * 100%);
  display: flex;
  flex-direction: column;
  align-items: center;
  transform: translate(-50%, -50%);
}

.nudge__marks li i {
  position: relative;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: #030514;
  box-shadow: 0 0 0 1px var(--line-strong);
  animation: nudge-dot var(--loop) linear infinite;
  /* Lit exactly as the fill arrives: where it is, times how long the
   * filling takes. */
  animation-delay: calc(var(--t) * var(--run) * var(--loop));
}

.nudge__marks li i::after {
  content: "";
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  border: 1px solid var(--light);
  opacity: 0;
  animation: nudge-ring var(--loop) var(--ease-out) infinite;
  animation-delay: inherit;
}

.nudge__marks li span {
  position: absolute;
  top: 1.5rem;
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .12em;
  color: var(--ink-faint);
}

.nudge__done i { background: var(--dark); }
.nudge__done i::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 2px;
  width: 3px;
  height: 6px;
  border: solid var(--holo-cyan);
  border-width: 0 1px 1px 0;
  transform: rotate(45deg);
  opacity: 0;
  animation: nudge-check var(--loop) linear infinite;
  animation-delay: inherit;
}

@keyframes nudge-dot {
  0%, 16%   { box-shadow: 0 0 0 1px var(--light), 0 0 14px rgba(117, 150, 243, .5); }
  34%, 100% { box-shadow: 0 0 0 1px var(--line-strong); }
}

@keyframes nudge-ring {
  0%   { opacity: .9; transform: scale(.6); }
  12%  { opacity: 0;  transform: scale(2.2); }
  100% { opacity: 0; }
}

/* The answered one is ticked the moment the fill reaches it and stays
 * ticked to the end of the loop - it is the point of the scene, and a
 * check that blinks out reads as another alarm. A quarter of the loop is
 * what is left after the last mark (.88 of .85 of it), so that is how
 * long it holds before the next round clears it. */
@keyframes nudge-check {
  0%, 24%   { opacity: 1; }
  26%, 100% { opacity: 0; }
}

/* --- 04 six in the morning ------------------------------------------- */

.scene--dawn { --loop: 9s; }

.dawn__horizon {
  position: absolute;
  left: 8%;
  right: 8%;
  top: 68%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--line-strong), transparent);
}

.dawn__arc,
.dawn__arm {
  position: absolute;
  left: 50%;
  top: 68%;
  width: min(62%, 17rem);
  aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
}

.dawn__arc {
  border: 1px dashed rgba(117, 150, 243, .22);
  clip-path: inset(0 0 50% 0);
}

.dawn__arm { animation: dawn-rise var(--loop) var(--ease-in-out) infinite; }

.dawn__sun {
  position: absolute;
  left: -6px;
  top: calc(50% - 6px);
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff, var(--holo-cyan) 60%);
  box-shadow: 0 0 22px 6px rgba(143, 227, 255, .45), 0 0 60px 16px rgba(117, 150, 243, .2);
}

@keyframes dawn-rise {
  0%        { transform: translate(-50%, -50%) rotate(-8deg);  opacity: 0; }
  10%       { opacity: 1; }
  55%, 88%  { transform: translate(-50%, -50%) rotate(52deg);  opacity: 1; }
  100%      { transform: translate(-50%, -50%) rotate(60deg);  opacity: 0; }
}

.dawn__time {
  position: absolute;
  left: 1.8rem;
  top: 1.5rem;
  font-family: var(--mono);
  font-size: 1.4rem;
  letter-spacing: .08em;
  color: var(--ink-bright);
}

.dawn__say {
  position: absolute;
  left: 50%;
  bottom: 1.3rem;
  display: flex;
  align-items: baseline;
  gap: .9rem;
  transform: translateX(-50%);
  white-space: nowrap;
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(.9rem, 2vw, 1.05rem);
  color: var(--ink-bright);
  opacity: 0;
  animation: dawn-say var(--loop) var(--ease-out) infinite;
}

.dawn__say span {
  font-family: var(--mono);
  font-style: normal;
  font-size: .75rem;
  color: var(--light);
}

@keyframes dawn-say {
  0%, 45%   { opacity: 0; filter: blur(6px); }
  58%, 88%  { opacity: 1; filter: blur(0); }
  100%      { opacity: 0; filter: blur(4px); }
}

/* --- 05 the watch ---------------------------------------------------- */

.scene--pulse { --loop: 7s; padding: 1.6rem 1.8rem; }

.pulse__head {
  display: flex;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .14em;
  color: var(--ink-soft);
}

.pulse__head span:last-child { color: var(--light); }

.pulse__bars {
  position: absolute;
  left: 1.8rem;
  right: 1.8rem;
  bottom: 1.8rem;
  top: 3.6rem;
  display: flex;
  align-items: flex-end;
  gap: 3px;
}

.pulse__bars i {
  flex: 1;
  height: var(--h);
  border-radius: 1px;
  background: linear-gradient(180deg, var(--light), var(--mid));
  opacity: .55;
}

.pulse__bars i.is-down {
  height: 18%;
  background: var(--warn);
  opacity: .85;
  box-shadow: 0 0 12px rgba(240, 139, 168, .5);
}

.pulse__sweep {
  position: absolute;
  top: 3.2rem;
  bottom: 1.4rem;
  left: 0;
  width: 18%;
  background: linear-gradient(90deg, transparent, rgba(143, 227, 255, .12), transparent);
  animation: sweep var(--loop) linear infinite;
}

@keyframes sweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(560%); }
}

/* --- 06 three machines ----------------------------------------------- */

.scene--trio { --loop: 7s; }

.trio__wires {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.trio__wires path {
  fill: none;
  stroke: rgba(117, 150, 243, .28);
  stroke-width: 1;
  stroke-dasharray: 4 6;
  vector-effect: non-scaling-stroke;
  animation: wire-run 3s linear infinite;
}

@keyframes wire-run { to { stroke-dashoffset: -20; } }

.trio__node {
  position: absolute;
  left: var(--x);
  top: var(--y);
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .5rem .85rem;
  transform: translate(-50%, -50%);
  border-radius: 999px;
  background: rgba(3, 5, 20, .92);
  box-shadow: 0 0 0 1px var(--line-strong);
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .14em;
  color: var(--ink);
}

.trio__node i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--light);
  box-shadow: 0 0 8px rgba(117, 150, 243, .7);
  animation: pulse 2.4s ease-in-out infinite;
  animation-delay: calc(var(--k) * .5s);
}

/* The site going away and coming back - what a restart looks like. */
.trio__node--back { animation: back-node var(--loop) var(--ease-in-out) infinite; }
.trio__node--back i { animation: back-dot var(--loop) linear infinite; }

@keyframes back-node {
  0%, 40%, 70%, 100% { box-shadow: 0 0 0 1px var(--line-strong); }
  48%, 60%           { box-shadow: 0 0 0 1px var(--light), 0 0 0 7px rgba(117, 150, 243, .08), 0 0 26px rgba(117, 150, 243, .3); }
}

@keyframes back-dot {
  0%, 42%, 64%, 100% { background: var(--light); opacity: 1; }
  46%, 60%           { background: var(--ink-faint); opacity: .4; }
}

/* --- 07 her desk ----------------------------------------------------- */

.scene--tree { --loop: 9s; padding: 1.6rem 1.8rem; }

.tree li {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin: 0 0 .55rem calc(var(--d) * 1.3rem);
  font-family: var(--mono);
  font-size: .72rem;
  color: var(--ink);
  opacity: 0;
  animation: tree-in var(--loop) var(--ease-out) infinite;
  animation-delay: calc(var(--k) * .28s);
}

.tree .glyph { width: .95rem; height: .95rem; color: var(--light); }

.tree__hot { position: relative; }

.tree__hot::after {
  content: "";
  position: absolute;
  left: -.6rem;
  right: -1.2rem;
  top: -.25rem;
  bottom: -.25rem;
  z-index: -1;
  border-radius: 6px;
  background: linear-gradient(90deg, rgba(45, 63, 171, .45), transparent);
  opacity: 0;
  animation: tree-hot var(--loop) var(--ease-out) infinite;
}

@keyframes tree-in {
  0%        { opacity: 0; transform: translateX(-6px); }
  8%, 86%   { opacity: 1; transform: none; }
  96%, 100% { opacity: 0; }
}

@keyframes tree-hot {
  0%, 36%   { opacity: 0; }
  46%, 82%  { opacity: 1; }
  92%, 100% { opacity: 0; }
}

/* --- 08 the face she chose ------------------------------------------- */

.scene--card { --loop: 9s; display: grid; place-items: center; }

.card {
  position: relative;
  width: min(80%, 17rem);
  padding: 0 0 1.1rem;
  border-radius: 14px;
  overflow: hidden;
  background: rgba(6, 9, 30, .95);
  box-shadow: 0 0 0 1px var(--line), 0 20px 50px -30px rgba(0, 0, 0, .9);
}

.card__banner {
  display: block;
  height: 3.6rem;
  background:
    radial-gradient(60% 120% at 20% 0%, rgba(143, 227, 255, .35), transparent 60%),
    radial-gradient(60% 120% at 90% 100%, rgba(179, 156, 255, .35), transparent 60%),
    linear-gradient(120deg, var(--dark), var(--mid));
  background-size: 160% 100%;
  animation: banner 12s ease-in-out infinite alternate;
}

@keyframes banner { to { background-position: 100% 0; } }

.card__face {
  position: relative;
  display: block;
  width: 3.6rem;
  height: 3.6rem;
  margin: -1.8rem 0 0 1rem;
  border-radius: 50%;
  box-shadow: 0 0 0 4px rgba(6, 9, 30, .95);
}

.card__face img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }

.card__face i {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--light);
  box-shadow: 0 0 0 3px rgba(6, 9, 30, .95);
}

.card__name {
  margin: .6rem 1rem 0 !important;
  font-size: .86rem;
  letter-spacing: .3em;
  color: var(--ink-bright);
}

.card__status {
  position: relative;
  height: 1.1rem;
  margin: .35rem 1rem 0 !important;
}

.card__status span {
  position: absolute;
  inset: 0;
  font-size: .72rem;
  color: var(--ink-soft);
  opacity: 0;
  animation: status-turn var(--loop) var(--ease-out) infinite;
  animation-delay: calc(var(--k) * var(--loop) / 3);
}

@keyframes status-turn {
  0%        { opacity: 0; transform: translateY(5px); filter: blur(3px); }
  5%, 28%   { opacity: 1; transform: none; filter: none; }
  33%, 100% { opacity: 0; transform: translateY(-5px); filter: blur(3px); }
}

/* ---------------------------------------------------------------------- *
 * The rail - the orbit for a phone
 *
 * Eight stops along the bottom of the window while the chapters pass,
 * connected by one line, scrolled sideways by a finger and by the chapter
 * being read. Off on anything wide enough for the ring itself.
 * ---------------------------------------------------------------------- */

.rail { display: none; }

.rail__wire {
  position: absolute;
  left: 0;
  right: 0;
  top: 1.85rem;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--line-strong) 12%, var(--line-strong) 88%, transparent);
}

.rail__stops {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 1.9rem;
  margin: 0;
  padding: 0;
  list-style: none;
  /* Half a window either side, so the first stop starts in the middle and
   * the last one can still get there. */
  padding-inline-start: calc(50vw - 1.4rem);
}

/* The other half is an element of its own - not padding, not a
 * pseudo-element, and not zero-height. None of those three count towards
 * a scroll container's scrollable width, and each of them in turn left the
 * rail half a window short of travel, so the last stops could never reach
 * the middle however far it was scrolled. One pixel of height is what
 * makes the box real to the scroller. */
.rail__end { flex: 0 0 calc(50vw - 1.4rem); height: 1px; }

.rail__node {
  display: flex;
  flex: none;
  flex-direction: column;
  align-items: center;
  gap: .5rem;
  width: 2.8rem;
  text-decoration: none;
}

/* No scroll snapping: the rail is scrolled by a finger *and* by the
 * chapter being read, and a snap point pulling against a smooth scroll
 * that is already running leaves the lit stop beside the middle rather
 * than in it. */

.rail__node .glyph {
  flex: none;
  box-sizing: content-box;
  width: 1.15rem;
  height: 1.15rem;
  padding: .72rem;
  border-radius: 50%;
  color: var(--ink-faint);
  background: rgba(3, 5, 20, .95);
  box-shadow: 0 0 0 1px var(--line);
  transition: color var(--t-slow) var(--ease-out), box-shadow var(--t-slow) var(--ease-out), background var(--t-slow) var(--ease-out);
}

.rail__node span {
  font-family: var(--mono);
  font-size: .5rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ink-faint);
  opacity: 0;
  transition: opacity var(--t-slow) var(--ease-out), color var(--t-slow) var(--ease-out);
}

.rail__node.is-on .glyph {
  color: var(--ink-bright);
  background: radial-gradient(circle at 50% 30%, rgba(45, 63, 171, .65), rgba(3, 5, 20, .95));
  box-shadow: 0 0 0 1px var(--light), 0 0 22px rgba(117, 150, 243, .45);
}

.rail__node.is-on span { opacity: 1; color: var(--light); }

@media (max-width: 860px) {
  .rail {
    display: block;
    position: sticky;
    z-index: 30;
    bottom: 0;
    /* Out to both edges of the window, whatever the section is padded by. */
    margin: 1rem calc(max(24px, env(safe-area-inset-left, 0px)) * -1) 0 calc(max(24px, env(safe-area-inset-right, 0px)) * -1);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, .82) 38%);
    -webkit-backdrop-filter: blur(12px) saturate(120%);
            backdrop-filter: blur(12px) saturate(120%);
    -webkit-mask-image: linear-gradient(180deg, transparent, #000 34%);
            mask-image: linear-gradient(180deg, transparent, #000 34%);
  }

  .rail__track {
    position: relative;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    padding: 1.1rem 0 1.2rem;
  }

  .rail__track::-webkit-scrollbar { display: none; }

  /* The line and the stops fade out at both edges rather than being cut. */
  .rail__track {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
            mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
  }

  /* Room under the last chapter, so nothing ends up behind the rail. */
  .chapters { padding-bottom: 5.5rem; }
}

/* ---------------------------------------------------------------------- *
 * Questions
 * ---------------------------------------------------------------------- */

.questions {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr);
  gap: clamp(2rem, 6vw, 6rem);
  width: min(100%, 76rem);
  margin: 0 auto;
  padding: clamp(4rem, 9vh, 6.5rem) max(24px, env(safe-area-inset-right, 0px)) clamp(5rem, 12vh, 8rem) max(24px, env(safe-area-inset-left, 0px));
}

.questions__head { align-self: start; position: sticky; top: 6rem; }

.qa-list { border-top: 1px solid var(--line); }

.qa { border-bottom: 1px solid var(--line); }

.qa summary {
  display: flex;
  align-items: baseline;
  gap: 1.3rem;
  padding: 1.5rem .2rem;
  cursor: pointer;
  list-style: none;
  transition: color var(--t) var(--ease-out);
}

.qa summary::-webkit-details-marker { display: none; }
.qa summary:focus-visible { outline: none; }
.qa summary:focus-visible .qa__q { color: var(--light); }

.qa__n {
  flex: none;
  font-family: var(--mono);
  font-size: .64rem;
  letter-spacing: .16em;
  color: var(--ink-faint);
  transition: color var(--t) var(--ease-out);
}

.qa__q {
  flex: 1;
  font-family: var(--serif);
  font-style: italic;
  font-size: clamp(1.1rem, 2.4vw, 1.35rem);
  color: var(--ink);
  transition: color var(--t) var(--ease-out);
}

/* A plus drawn in two lines; open, the upright one lies down. */
.qa__mark {
  position: relative;
  flex: none;
  align-self: center;
  width: 14px;
  height: 14px;
}

.qa__mark::before,
.qa__mark::after {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: 1px;
  background: var(--light);
  transition: transform var(--t-slow) var(--ease-out);
}

.qa__mark::after { transform: rotate(90deg); }
.qa[open] .qa__mark::after { transform: rotate(0deg); }

.qa summary:hover .qa__q,
.qa[open] .qa__q { color: var(--ink-bright); }
.qa summary:hover .qa__n,
.qa[open] .qa__n { color: var(--light); }

.qa__a { overflow: hidden; }

.qa__a p {
  max-width: 34rem;
  margin: 0;
  padding: 0 0 1.7rem calc(1.3rem + 1.4rem);
  font-size: .95rem;
  line-height: 1.75;
  color: var(--ink-soft);
  text-wrap: pretty;
}

/* ---------------------------------------------------------------------- *
 * Paper: the two plain pages, privacy and terms
 *
 * The same ground and the same type as the front, and none of its motion.
 * A page about what is kept should not be the page that does the most.
 * ---------------------------------------------------------------------- */

.paper {
  width: min(100%, 46rem);
  margin: 0 auto;
  padding:
    calc(6rem + env(safe-area-inset-top, 0px))
    max(24px, env(safe-area-inset-right, 0px))
    clamp(3rem, 8vh, 5rem)
    max(24px, env(safe-area-inset-left, 0px));
}

.paper__head { margin-bottom: clamp(2.4rem, 7vh, 3.6rem); }

.paper__lead {
  max-width: 34rem;
  margin: 1.2rem 0 0;
  font-size: 1rem;
  line-height: 1.7;
  color: var(--ink-soft);
  text-wrap: pretty;
}

.paper__part { margin-bottom: clamp(2rem, 5vh, 3rem); }

.paper__part h2 {
  margin: 0 0 .9rem;
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.15rem, 2.6vw, 1.4rem);
  color: var(--ink-bright);
}

.paper__part p,
.paper__part li {
  margin: 0 0 .9rem;
  font-size: .95rem;
  line-height: 1.75;
  color: var(--ink-soft);
  text-wrap: pretty;
}

.paper__part ul {
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;
}

.paper__part li {
  position: relative;
  padding-left: 1.4rem;
}

/* A mark rather than a bullet: the same dot the eyebrows carry. */
.paper__part li::before {
  content: "";
  position: absolute;
  left: .2rem;
  top: .75em;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--light);
  opacity: .75;
}

.paper__part b { font-weight: 400; color: var(--ink); }

.paper a {
  color: var(--light);
  text-decoration: none;
  border-bottom: 1px solid rgba(117, 150, 243, .35);
  transition: color var(--t) var(--ease-out), border-color var(--t) var(--ease-out);
}

.paper a:hover { color: var(--ink-bright); border-bottom-color: var(--light); }

.paper__back {
  margin: clamp(2.5rem, 7vh, 4rem) 0 0;
  padding-top: 1.6rem;
  border-top: 1px solid var(--line);
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .18em;
  text-transform: uppercase;
}

.paper__back a { border-bottom: 0; }

/* ---------------------------------------------------------------------- *
 * The foot
 * ---------------------------------------------------------------------- */

.foot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .9rem;
  padding: 3rem 20px calc(3rem + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid rgba(117, 150, 243, .08);
}

.foot__face {
  width: 2.4rem;
  height: 2.4rem;
  border-radius: 50%;
  overflow: hidden;
  opacity: .8;
  box-shadow: 0 0 0 1px var(--line);
}

.foot__face img { width: 100%; height: 100%; object-fit: cover; display: block; }

.foot__name {
  margin: 0;
  font-size: .78rem;
  letter-spacing: .5em;
  padding-left: .5em;
  color: var(--ink-soft);
}

.foot__links {
  display: flex;
  align-items: center;
  gap: .7rem;
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .2em;
  text-transform: uppercase;
}

.foot__links a {
  text-decoration: none;
  color: var(--ink-faint);
  transition: color var(--t) var(--ease-out);
}

.foot__links a:hover, .foot__links a:focus-visible { color: var(--light); outline: none; }
.foot__links i { font-style: normal; color: #2A3154; }

.foot__build {
  margin: 0;
  font-family: var(--mono);
  font-size: .6rem;
  letter-spacing: .12em;
  color: #2A3154;
}

/* ---------------------------------------------------------------------- *
 * Narrow screens: no orbit, each chapter carries its own scene
 * ---------------------------------------------------------------------- */

@media (max-width: 860px) {
  .abilities { grid-template-columns: minmax(0, 1fr); gap: 0; padding-top: 3rem; }
  .stage { display: none; }
  .chapter { min-height: 0; padding: 3.5rem 0; }
  .questions { grid-template-columns: minmax(0, 1fr); gap: 2.2rem; }
  .questions__head { position: static; }
  .mast__links { gap: 1.1rem; }
}

@media (max-width: 560px) {
  /* Her portrait and the bar were sized for a desk: on a phone they took
   * the whole first screen between them. */
  .hero__sigil { --size: clamp(116px, 23vh, 168px); }
  .hero__name { font-size: clamp(2.1rem, 9vw, 2.7rem); }
  .talk { margin-top: clamp(1.6rem, 5vh, 2.4rem); }
  .talk__answer { font-size: 1rem; }
  .bar { gap: .55rem; padding: .35rem .35rem .35rem 1.1rem; }
  .bar__input, .bar__ghost { font-size: .92rem; }
  .bar__send { width: 2.4rem; height: 2.4rem; }
  .bar__send .glyph { width: 1rem; height: 1rem; }
}

@media (max-width: 470px) {
  .mast__links { display: none; }
  .talk__said { min-height: 6.2rem; }
  .qa summary { gap: .9rem; }
  .qa__a p { padding-left: 0; }
  .scene--notes, .scene--pulse, .scene--tree { padding: 1.4rem 1.3rem; }
  .pulse__bars { left: 1.3rem; right: 1.3rem; }
  .dawn__time { left: 1.3rem; }
}
