/* The homepage of both websites, built by homepage.js. The look of each website sets the fonts of the
   headings and the motion of the buttons. Only transform, opacity, the clip of the typed lines, the
   light of the dark area and the circuit lines of the bands with their packets move, and only while
   the system allows motion. Without motion the opening scene shows its last moment, the light of
   the dark area is in the middle, the chapters of the story stand one below the other and the
   circuit lines do not move and have no packets. */

/* A stage for Shelly and Jack, lit by the light inside it. */
.stage {
  --stage-border: 2px;
  position: relative;
  border: var(--stage-border) solid var(--stage-outline);
  border-radius: var(--radius);
  background: var(--stage);
}
.light { position: absolute; inset: 0; }
.light-red { background: radial-gradient(circle at 50% 75%, color-mix(in oklab, var(--jack) 32%, transparent), transparent 65%); }
.light-green { background: radial-gradient(circle at 50% 75%, color-mix(in oklab, var(--shelly) 32%, transparent), transparent 65%); }

/* --- Opening scene --- */

.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: 48px;
  padding: 72px 0 56px;
}
.hero-text { display: grid; gap: 20px; justify-items: start; }
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; }
.lead { font-size: 18px; color: var(--text-2); }
.hero .trust { font-size: 14px; color: var(--muted); }

/* High enough for the terminal above the shell. The rest of the opening scene counts from
   --scene-start: each line of the terminal uses --line-time, and once the last one is typed, the
   shell slides in for as long as it spins once at the timing of the art, --spin-time. opening-scene.js
   sets the number of --lines and of --spin-frames. */
.hero-stage {
  --scene-start: 0.4s;
  --line-time: 0.7s;
  --typed: calc(var(--scene-start) + var(--lines) * var(--line-time));
  --spin-time: calc(var(--spin-frames) * var(--art-frame-time));
  --stopped: calc(var(--typed) + var(--spin-time));
  height: 340px;
  overflow: hidden;
  container-type: inline-size;
}
.hero-stage .light-red { opacity: 0; }
/* opening-scene.js shows the parts of the stage once the spin frames are counted. */
.hero-stage.waiting > * { visibility: hidden; }
/* Each line is as wide as its text, so the clip that types it counts its characters. */
.terminal {
  position: absolute;
  top: 16px;
  left: 16px;
  display: grid;
  justify-items: start;
  gap: 4px;
  color: var(--jack);
  font: 11px/1.6 var(--pixel);
}
.terminal span { white-space: pre; }
.terminal-blocked { color: var(--shelly); }
/* The shell of Shelly in the middle of the stage, on its bottom edge, at twice the size of his art. */
.hero-shell {
  position: absolute;
  left: calc(50% - var(--sprite-size) / 2);
  bottom: 0;
}
.hero-shell .character { width: var(--sprite-size); }

/* Runs once when the page loads and ends in the look shown without motion, except that the shell
   spins in a loop in the middle of the stage once it has stopped the commands. */
@media (prefers-reduced-motion: no-preference) {
  .typed-line { animation: type-line 0.5s calc(var(--scene-start) + var(--line) * var(--line-time)) steps(var(--chars)) both; }
  .hero-stage.playing {
    .hero-shell { animation: slide-in-right var(--spin-time) var(--typed) ease-out both; }
    .hero-shell img { animation: sprite-frames var(--spin-time) steps(var(--spin-frames)) var(--typed) infinite backwards; }
    .terminal-blocked { animation: fade-in 0.3s var(--stopped) both; }
    .light-red { animation: shown-then-gone 0.6s calc(var(--stopped) + 0.05s) both; }
    .light-green { animation: fade-in 0.6s calc(var(--stopped) + 0.05s) both; }
  }
}
@keyframes type-line { from { clip-path: inset(0 100% 0 0); } }
/* From just outside the right edge of the stage. */
@keyframes slide-in-right { from { transform: translateX(calc(50cqw + 50%)); } }
@keyframes fade-in { from { opacity: 0; } }
@keyframes shown-then-gone { from { opacity: 1; } }

/* --- Dark area --- */

/* @property lets the sweep keyframes move the light after a touch. The light shows only while a
   mouse is in the area, while the sweep runs or with reduced motion, and its lit part shows the
   striped floor of the stage. Design decision, from the approved design of the fun homepage: the
   radius of the light is about a third of the height of the area. */
@property --light-x { syntax: "<length-percentage>"; inherits: false; initial-value: 50%; }
@property --light-y { syntax: "<length-percentage>"; inherits: false; initial-value: 50%; }
.dark-search {
  --area-height: 340px;
  --light-radius: calc(var(--area-height) * 0.32);
  --light-core: calc(var(--area-height) * 0.2);
  --lit-background:
    radial-gradient(circle var(--light-radius) at var(--light-x) var(--light-y), transparent 0 var(--light-core), var(--darkness) var(--light-radius)),
    repeating-linear-gradient(45deg, var(--stage) 0 12px, color-mix(in oklab, var(--stage) 90%, var(--text)) 12px 24px);
  height: var(--area-height);
  background: var(--darkness);
}
.dark-search.lit,
.dark-search.sweeping { background: var(--lit-background); }

/* While dark-area.js sets cursor-in-dark, the cursor is hidden on the whole page, also over dialogs
   and their backdrop, and the way out is the bottom right corner of the area, in front of the
   traps. The corner is blank and looks like the area around it. Design decision: the corner is a
   square of 64px. */
.dark-way-out {
  display: none;
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: var(--layer-way-out);
  width: 64px;
  height: 64px;
}
/* dark-area.js watches this line where Jack shows: along the bottom edge of the area, where he
   stands, and on a phone at the tip of his ears above the area. */
.dark-jack-line { position: absolute; inset: auto 0 0; }
:root.cursor-in-dark {
  &, *, *::backdrop { cursor: none; }
  .dark-way-out { display: block; }
}

/* Jack beside the bottom right corner of the dark area, where the way out is. Each pose stands with
   the bottom edge of its own figure on the bottom edge of the area, and with the left edge of its
   figure --gap to the right of the area. Where the gutter of the page is narrower than the figure
   that shows his laugh pose, that figure ends --gap before the right edge of the window. He walks
   in from beyond the right edge of the window, facing left, for --walk-rounds rounds of his walk
   frames, laughs for --laugh-rounds rounds at the timing of the art, and walks back out to the
   right. dark-area.js sets the number of frames and the figure of each pose. The visit itself runs
   one animation for the whole time, whose end dark-area.js waits for. It spans the area and the
   gutter of the page beside it, up to the right edge of the window, and clips Jack where he is
   beyond that edge. Design choices: two rounds of walking and two of laughing, and 8px between Jack
   and the area. */
.jack-visit {
  --width: var(--sprite-size);
  --gap: 8px;
  --walk-rounds: 2;
  --laugh-rounds: 2;
  --walk-in-time: calc(var(--walk-rounds) * var(--walk-in-frames) * var(--sprite-frame-time));
  --laugh-time: calc(var(--laugh-rounds) * var(--laugh-frames) * var(--art-frame-time));
  --walk-out-time: calc(var(--walk-rounds) * var(--walk-out-frames) * var(--sprite-frame-time));
  --walked-in-and-laughed: calc(var(--walk-in-time) + var(--laugh-time));
  --off-screen: calc(var(--page-gutter) + var(--width));
  position: absolute;
  inset: 0 auto 0 0;
  width: calc(100% + var(--page-gutter));
  overflow-x: clip;
  z-index: var(--layer-jack-beside-area);
  pointer-events: none;
  animation: visit calc(var(--walked-in-and-laughed) + var(--walk-out-time));
}
.visit-poses {
  position: absolute;
  bottom: 0;
  left: calc(100% - var(--page-gutter) + min(var(--gap), var(--page-gutter) - var(--figure-width) - var(--gap)));
  display: grid;
  place-items: end start;
}
.visit-poses > * { grid-area: 1 / 1; position: relative; opacity: 0; }
.visit-laugh .character {
  --sprite-frame-time: var(--art-frame-time);
  --strip-delay: var(--walk-in-time);
  --strip-rounds: var(--laugh-rounds);
}
.visit-ears { display: none; }

/* dark-area.js builds the visit only while the system allows motion. Design decision,
   from the approved design of the fun homepage: the sweep takes four seconds. */
@media (prefers-reduced-motion: no-preference) {
  .dark-search.lit { --light-x: var(--pointer-x); --light-y: var(--pointer-y); }
  .dark-search.sweeping { animation: sweep 4s ease-in-out; }
  .visit-walk-in { animation: visit-walk-in var(--walk-in-time) linear; }
  .visit-laugh { animation: shown var(--laugh-time) var(--walk-in-time); }
  .visit-walk-out { animation: visit-walk-out var(--walk-out-time) linear var(--walked-in-and-laughed); }
  .visit-ears .character {
    animation: ears-up var(--ears-rise-time) ease-out, ears-down var(--ears-rise-time) ease-in calc(var(--ears-rise-time) + var(--ears-flop-time)) forwards;
  }
}
@media (prefers-reduced-motion: reduce) {
  .dark-search { background: var(--lit-background); }
}
@keyframes sweep {
  0% { --light-x: 15%; --light-y: 20%; }
  35% { --light-x: 80%; --light-y: 35%; }
  70% { --light-x: 30%; --light-y: 80%; }
  100% { --light-x: 70%; --light-y: 50%; }
}
@keyframes visit { from, to { visibility: visible; } }
@keyframes shown { from, to { opacity: 1; } }
@keyframes visit-walk-in { from { transform: translateX(var(--off-screen)); opacity: 1; } to { opacity: 1; } }
@keyframes visit-walk-out { from { opacity: 1; } to { transform: translateX(var(--off-screen)); opacity: 1; } }
@keyframes ears-up { from { translate: 0 var(--ears-height); } }
@keyframes ears-down { to { translate: 0 var(--ears-height); } }

/* --- The story --- */

.showcase {
  padding: 24px 0 56px;
  overflow-x: clip;
  scroll-margin-top: var(--header-height);
}
.chapter {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
  align-items: center;
  gap: 48px;
  padding: 32px 0;
}
.chapter-mark { display: none; }
.chapter-copy { display: grid; gap: 12px; justify-items: start; }
.chapter-copy h3 { font: 700 30px/1.2 var(--sans); }
.chapter-copy p { font-size: 18px; color: var(--text-2); }

/* The room on the scene sets the size of the characters: twice --sprite-size on a large scene, that
   size on a medium one and half of it on a small one. */
.scene { height: 380px; container-type: size; }
.cast { --size: calc(var(--sprite-size) / 2); --share: 1; --width: calc(var(--size) * var(--share)); position: relative; }
@container (width >= 340px) and (height >= 300px) {
  .cast { --size: var(--sprite-size); }
}
@container (width >= 512px) and (height >= 640px) {
  .cast { --size: calc(var(--sprite-size) * 2); }
}

/* The characters stand side by side in the middle, smaller when there are two. */
.cast-row {
  position: absolute;
  inset-inline: 0;
  bottom: 14%;
  display: flex;
  justify-content: center;
  align-items: flex-end;
  gap: 24px;
}
.cast-row:has(.cast + .cast) .cast { --share: 0.5; }

.progress { position: absolute; top: 16px; left: 16px; display: flex; gap: 6px; }
.progress span { width: 8px; height: 8px; background: var(--line-strong); }
.progress .current { background: var(--text); }

/* The comic panel at the end of the story: thick outlines and a dot raster in the green of Shelly.
   Shelly stands at its left edge, with a frame as wide as --width, and the bubble beside him points
   at him. What Shelly says is a sentence, so it shows in the sans font: the pixel font is only for
   short labels. */
.comic {
  --width: var(--sprite-size);
  display: grid;
  grid-template-columns: calc(var(--width) * 0.92) minmax(0, 26em);
  align-items: end;
  min-height: calc(var(--width) * 1.1);
  margin-top: 32px;
  padding: 24px 24px 0 0;
  overflow: hidden;
  border: 4px solid var(--text);
  border-radius: var(--radius);
  background: radial-gradient(color-mix(in oklab, var(--shelly) 35%, transparent) 1.5px, transparent 2px) 0 0 / 10px 10px, var(--stage);
}
.comic-walk, .comic-talk { grid-area: 1 / 1; width: var(--width); }
.comic .character { width: var(--width); }
.comic-walk { display: none; }
.comic-bubble {
  position: relative;
  grid-area: 1 / 2;
  align-self: start;
  padding: 12px 16px;
  border: 3px solid var(--text);
  border-radius: 18px;
  background: var(--surface);
  box-shadow: 4px 4px 0 var(--text);
  color: var(--text);
  font: 700 17px/1.45 var(--sans);
}
.comic-bubble::before {
  content: "";
  position: absolute;
  bottom: 16px;
  left: -9px;
  width: 14px;
  height: 14px;
  border-bottom: 3px solid var(--text);
  border-left: 3px solid var(--text);
  background: var(--surface);
  rotate: 45deg;
}

/* With motion, every scene spans all chapters in the first column and is fixed on screen while the
   texts and their marks scroll past in the second. The scenes leave the screen with the last chapter,
   before the comic panel comes up. homepage.js marks the current scene. Each
   chapter spans one screen height of scrolling, and on a phone 80% of one. */
@media (prefers-reduced-motion: no-preference) {
  .chapters {
    --text-area-top: var(--header-height);
    --text-inset: 30svh;
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
    column-gap: 48px;
  }
  .chapter { display: contents; }
  .scene {
    grid-column: 1;
    grid-row: 1 / span var(--chapters);
    position: sticky;
    top: calc(var(--header-height) + 24px);
    align-self: start;
    height: calc(100svh - var(--header-height) - 48px);
    opacity: 0;
    transition: opacity 0.5s;
  }
  .scene.current { opacity: 1; }
  .scene .cast-row { transform: translateY(24px); transition: transform 0.6s ease-out; }
  .scene.current .cast-row { transform: none; }
  /* A strip with a rest frame plays its frames over and over while its scene is the current one, at
     the timing of the art. */
  .scene.current .character-sprite-once img { animation: sprite-frames calc(var(--frames) * var(--art-frame-time)) steps(var(--frames)) infinite; }
  .chapter-text { grid-column: 2; grid-row: var(--chapter); display: grid; }
  .chapter-copy, .chapter-mark { grid-area: 1 / 1; }
  .chapter-copy { position: sticky; top: calc(var(--text-area-top) + var(--text-inset)); align-self: start; }
  /* A sticky element stops sticking once its margin box reaches the end of its grid row. A bottom
     margin as large as the inset from the top of the text area releases the text early enough that it
     has scrolled under the header, or on a phone under the scene, when the next text sticks. */
  .chapter:not(:last-of-type) .chapter-copy { margin-bottom: var(--text-inset); }
  .chapter-mark { display: block; height: 100svh; }

  /* Once homepage.js has counted his walk frames, Shelly waits outside the panel, walks in for
     --walk-rounds rounds of them and stops where he stands without motion. Then he talks once and rests
     pointing, and the bubble shows. He walks slower than the art: each walk frame shows for
     --walk-frame-time. In each round of the frames, his feet move 32 px of the art, a quarter of his
     frame, so he moves on by --walk-step and does not slide. */
  .comic {
    --walk-rounds: 3;
    --walk-frame-time: calc(var(--art-frame-time) * 1.25);
    --walk-step: 25%;
    --walk-time: calc(var(--walk-rounds) * var(--walk-frames) * var(--walk-frame-time));
  }
  .comic.walk-ready .comic-walk { display: block; transform: translateX(calc(var(--walk-rounds) * var(--walk-step) * -1)); }
  .comic-walk .character { --sprite-frame-time: var(--walk-frame-time); }
  .comic.walk-ready .comic-talk, .comic.walk-ready .comic-bubble { opacity: 0; }
  .comic.playing .comic-walk { animation: comic-walk-in var(--walk-time) linear forwards, gone 0s var(--walk-time) forwards; }
  .comic.playing .comic-talk, .comic.playing .comic-bubble { animation: shown 0s var(--walk-time) forwards; }
  .comic.playing .comic-talk img { animation: sprite-frames calc(var(--frames) * var(--art-frame-time)) steps(var(--frames)) var(--walk-time) backwards; }
}
@keyframes comic-walk-in { to { transform: none; } }
@keyframes gone { to { opacity: 0; } }

/* --- Bands --- */

/* Each section below the hero, the waitlist too, is on a band across the whole window, with slanted
   top and bottom edges. The bands slant one way and the other in turn, each one starting on the edge
   where the band before it ends, and every other band has a background, as does the band of the
   invitation to the demo. The backdrop of a band draws the background and the edges behind the
   section, so they clip nothing of the section, such as a sticky scene of the story. Neither the band
   nor its backdrop starts a stacking context, so a part moved by a trick shows above the bands after
   it.
   Design choices: edges that slant by 28px, and circuit lines that scroll 35% slower than the text. */
.band {
  --slant: 28px;
  position: relative;
  margin-inline: calc(-1 * var(--page-gutter));
  padding: var(--slant) var(--page-gutter);
}
.band + .band { margin-top: calc(-1 * var(--slant)); }
.band:nth-child(even of .band) { --band-background: var(--band-alt); }
/* Comes after the rule above, so the band of the invitation has its background also as an even band. */
.band:has(> .teaser) { --band-background: var(--band-accent); }
/* Positioned after the backdrop, so the section shows in front of it. */
:where(.band > :not(.band-backdrop)) { position: relative; }
.band-backdrop {
  --parallax-share: 0.35;
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  background: var(--band-background, transparent);
  clip-path: polygon(0 var(--slant), 100% 0, 100% 100%, 0 calc(100% - var(--slant)));
}
.band:nth-child(even of .band) > .band-backdrop { clip-path: polygon(0 0, 100% var(--slant), 100% calc(100% - var(--slant)), 0 100%); }

/* Does not move with the parallax, so its clip stays over the section. */
.circuit-area {
  position: absolute;
  inset: 0;
}
/* The circuit lines reach above and below the band by as far as the parallax moves them, which
   is the share of the distance from the middle of the band to the middle of the screen, at most half
   the band and half the screen. So they cover the band wherever the parallax puts them. The parallax
   sets --parallax-offset on the backdrop only while the system allows motion. */
.circuit-lines {
  position: absolute;
  top: calc(-1 * var(--parallax-share) * (50% + 50vh));
  left: 0;
  width: 100%;
  height: calc(100% + 2 * var(--parallax-share) * (50% + 50vh));
  translate: 0 calc(var(--parallax-offset, 0px) * var(--parallax-share));
  will-change: translate;
}
.circuit-line { fill: none; stroke: var(--circuit-line); stroke-width: 2; }
.data-packet-core { fill: var(--data-packet); }
.packet-glow-center { stop-color: var(--data-packet-glow); }
.packet-glow-edge { stop-color: var(--data-packet-glow); stop-opacity: 0; }

/* --- Facts below the hero --- */

.trust-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 28px;
  margin: 0;
  padding: 14px 4px;
  border-block: 1px solid var(--line);
  list-style: none;
  font-size: 14px;
  color: var(--muted);
}
.trust-facts li::before { content: "✓ " / ""; font-weight: 700; color: var(--accent); }

/* --- Sections of text --- */

.text-section p { max-width: 40em; font-size: 18px; color: var(--text-2); }
/* A list that clips its items, marked as dom.js describes for CLIPS_ATTRIBUTE, makes room above and
   below itself for --overhang without moving what is around it. */
.text-items {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  margin: calc(8px - var(--overhang, 0px)) 0 calc(-1 * var(--overhang, 0px));
  padding: var(--overhang, 0) 0;
  list-style: none;
}
.text-items li {
  padding: 16px 20px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--jack);
  border-radius: var(--radius);
  background: var(--surface);
}

/* --- Cards of steps and of the software --- */

.steps li, .feature-cards li {
  padding: 16px 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}
.steps h3, .feature-cards h3 { margin: 4px 0; font-size: 17px; }
.steps p, .text-section .feature-cards p { font-size: 16px; color: var(--text-2); }
.feature-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
}
.feature-cards svg { display: block; width: 28px; height: 28px; color: var(--accent); }

/* --- Steps --- */

/* A line runs along the numbered steps. While the system allows motion and the browser ties an
   animation to the scroll position, its filled part grows from empty as the steps come onto the
   screen and is full once the middle of the steps is at the middle of the screen. Otherwise it is
   full. --line-empty is the scale of the empty fill: across a row of steps, and down a column on a
   phone. */
.steps {
  --line-empty: 0 1;
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  margin: 0;
  padding: 28px 0 0;
  list-style: none;
  counter-reset: step;
  view-timeline: --steps block;
}
.steps::before, .steps::after {
  content: "";
  position: absolute;
  inset: 8px 0 auto;
  height: 3px;
  border-radius: 2px;
  background: var(--line);
}
.steps::after { background: var(--accent); transform-origin: left; }
.steps li { counter-increment: step; }
.steps li::before { content: counter(step); display: block; font-weight: 700; color: var(--accent); }
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .steps::after {
      animation: fill-line linear both;
      animation-timeline: --steps;
      animation-range: entry 0% cover 50%;
    }
  }
}
@keyframes fill-line { from { scale: var(--line-empty); } }

/* --- Swipe cards --- */

/* swipe-cards.js marks the dot for the card in view, and gives the row the class "scrolls" while the
   system allows motion. The dots show only then. */
.swipe-dots { display: none; gap: 6px; justify-content: center; }
.swipe-dots span { width: 8px; height: 8px; border-radius: 50%; background: var(--line-strong); }
.swipe-dots .current { background: var(--text); }

/* The scrolling row snaps card by card under a finger. A row that moves with the page under a mouse
   does not snap. The next card shows at the edge, so the row reads as one that scrolls. */
.swipe-cards.scrolls {
  display: flex;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.swipe-cards.scrolls li { flex: 0 0 85%; scroll-snap-align: start; }

/* On a wide screen three cards show side by side, and the fourth shows at the edge. */
@media (width > 760px) {
  .swipe-cards.scrolls li { flex-basis: 31%; }
}
.swipe-cards.scrolls + .swipe-dots { display: flex; }
.swipe-cards.moves-with-page { scroll-snap-type: none; cursor: grab; }
.swipe-cards.dragging { cursor: grabbing; }

/* --- Timeline --- */

/* Each card of the timeline is a link to its source, with its year, then its side in its color, the
   red of Jack for an attack and the green of Shelly for a defense, and what happened. */
.timeline-attack { --side: var(--jack); }
.timeline-defense { --side: var(--shelly); }
.text-items .timeline-card { border-left-color: var(--side); }
.timeline-card a { display: grid; gap: 4px; color: inherit; text-decoration: none; }
.timeline-card a:hover { text-decoration: underline; }
.timeline-year { font-size: 22px; font-weight: 700; }
.timeline-side { font-size: 13px; font-weight: 700; color: var(--side); text-transform: uppercase; }

.certificate-preview {
  width: min(100%, 420px);
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px var(--shadow);
}

/* --- Questions --- */

.faq { max-width: 760px; }
.faq details {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}
.faq summary { padding: 14px 20px; font-weight: 600; cursor: pointer; }
.faq details p { padding: 0 20px 16px; color: var(--text-2); }

/* --- Demo teaser --- */

.teaser { justify-items: center; text-align: center; }
.teaser p { max-width: 36em; color: var(--text-2); }

/* --- Phone --- */

/* With motion the scene is fixed at the top, and the texts scroll below it. */
@media (width <= 760px) {
  .hero { grid-template-columns: 1fr; gap: 32px; padding-top: 40px; }
  .dark-search { --area-height: 260px; --ears-height: calc(var(--sprite-size) * 0.2); }
  /* On a phone the area leaves no room beside it, so only the ears of Jack show: they come up behind
     the top right corner of the area for --ears-rise-time, flop for --ears-rounds rounds of the
     frames at the timing of the art, and go down again. The visit runs for that time. The ears show
     in a box as high as --ears-height that ends on the outer edge of the border of the area, so they
     come up from behind that edge. The box shows the top fifth of his frames, down to the top of his
     hood. With motion the area has that much room above it, so the ears come up below the hero
     text, never over it. Design choices: one round of flopping, 0.6s to come up and to go down,
     and the ears 16px in from the right edge of the area. */
  .dark-jack-line { inset: calc((var(--ears-height) + var(--stage-border)) * -1) 0 auto; }
  .jack-visit {
    --width: var(--sprite-size);
    --ears-rounds: 1;
    --ears-rise-time: 0.6s;
    --ears-flop-time: calc(var(--ears-rounds) * var(--ears-frames) * var(--art-frame-time));
    animation-duration: calc(var(--ears-rise-time) * 2 + var(--ears-flop-time));
  }
  .visit-poses { display: none; }
  .visit-ears {
    display: block;
    position: absolute;
    right: calc(var(--page-gutter) + 16px - (100 - var(--figure-right)) * var(--width) / 100);
    bottom: calc(100% + var(--stage-border));
    height: var(--ears-height);
    overflow: hidden;
  }
  .visit-ears .character {
    width: var(--width);
    --sprite-frame-time: var(--art-frame-time);
    --strip-delay: var(--ears-rise-time);
    --strip-rounds: var(--ears-rounds);
  }
  .chapter { grid-template-columns: 1fr; gap: 24px; }
  .scene { height: 320px; }
  .comic { --width: calc(var(--sprite-size) / 2); padding: 16px 16px 0 0; }
  .comic-bubble { font-size: 15px; }
  .text-items, .feature-cards { grid-template-columns: minmax(0, 1fr); }
  .steps { --line-empty: 1 0; grid-template-columns: minmax(0, 1fr); padding: 0 0 0 28px; }
  .steps::before, .steps::after { inset: 0 auto 0 8px; width: 3px; height: auto; }
  .steps::after { transform-origin: top; }
}
@media (width <= 760px) and (prefers-reduced-motion: no-preference) {
  .dark-search { margin-top: var(--ears-height); }
  .chapters {
    --scene-height: 45svh;
    --text-area-top: calc(var(--header-height) + var(--scene-height));
    --text-inset: 24px;
    grid-template-columns: minmax(0, 1fr);
  }
  /* The texts scroll under the scenes, so the colour of the stage lies on a layer below them that does
     not fade, and covers the texts while one scene fades into the next. */
  .chapters::before {
    content: "";
    grid-column: 1;
    grid-row: 1 / span var(--chapters);
    position: sticky;
    top: var(--header-height);
    align-self: start;
    z-index: 1;
    height: var(--scene-height);
    border-radius: var(--radius);
    background: var(--stage);
  }
  .scene { z-index: 1; top: var(--header-height); height: var(--scene-height); }
  .chapter-text { grid-column: 1; }
  .chapter:first-of-type .chapter-text { padding-top: calc(var(--scene-height) + var(--text-inset)); }
  .chapter-mark { height: 80svh; }
}
