/*
 * TaroDict Web: the 介紹 page (Docs/TaroDictWeb/TaroDictWeb.md §3.9), a
 * product page in sections, as the owner's mock laid them out (2026-11-02):
 * the opening, the search demo pinned while its four queries play,
 * 台語、客語, the word page pinned while a spotlight walks it, more
 * features, the devices, then the closing on the opening's taro.
 * Every phone shows a real capture of the app (owner): the probe in
 * Tools/StoreScreenshots takes them, light and dark.
 *
 * Motion runs only for a reader who hasn't asked for less. The opening's
 * words fade in, its phone rises and its mascots come in hand in hand on load;
 * as the reader scrolls, the demo steps through its queries (intro.js), and
 * 台語、客語's switch plays once (languages.js).
 */

:root {
  /* The opening's ground, in both modes: taroMedium, which white text
     clears at 4.8:1 (owner, 2026-11-02). The store screenshots' lighter
     taro, #8D92C7, gives white only 2.97:1. */
  --intro-hero-ground: #6A6DAA;
  /* The line under the name: taroLight, 3.16:1 on that ground, enough for
     text 24 px and larger (the line never goes smaller). */
  --intro-hero-line: #CFD0E8;
  /* The one space between the name, the line, the badges and the mascots,
     measured from ink to ink, not from box to box (owner, 2026-11-02). */
  --intro-hero-gap: clamp(24px, 2.4vw, 32px);
  /* A step that waits its turn, and the one on. */
  --intro-idle: #F6F6FB;
  --intro-current: #FFFFFF;
  --intro-current-shadow: 0 8px 24px rgba(80, 86, 135, 0.18);
  /* A tag's text on taroMedium: white, and on dark mode's lighter medium,
     the page's dark grey (5.68:1). */
  --intro-tag-text: #FFFFFF;
  /* The phones' bezel. */
  --intro-bezel: #1C1C1E;
  /* The word page's spotlight: the dimming around it, and its ring (none on
     a light screen, where the dimming alone sets the frame apart). */
  --intro-spot-dim: rgba(20, 20, 24, 0.55);
  --intro-spot-ring: transparent;
}

@media (prefers-color-scheme: dark) {
  :root {
    --intro-idle: #242426;
    --intro-current: #2C2C2E;
    --intro-current-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
    --intro-tag-text: #1C1C1E;
    --intro-bezel: #000000;
    /* A dark screen barely darkens under the light screen's dimming
       (owner, 2026-11-02): dim harder, and ring the frame in dark mode's
       medium taro. */
    --intro-spot-dim: rgba(0, 0, 0, 0.72);
    --intro-spot-ring: #8D91C7;
  }
}

/* The page's sections run edge to edge, not in the other pages' column. */
main.intro {
  max-width: none;
  padding: 0;
}

.intro h1,
.intro h2 {
  margin: 0;
  color: var(--dark);
  /* 粉圓 has no bold: size, colour and space carry the order. */
  font-weight: normal;
}

/* Headlines and the opening's line wrap into even lines, not a long line
   and a word left alone (owner's device check, 2026-11-03: "app" alone
   under the English line on an iPhone). */
.intro h1,
.intro h2,
.intro-value {
  text-wrap: balance;
}

/* A headline in lines: its string breaks them with \n (strings.json). */
.intro-lines {
  white-space: pre-line;
}

/* ---- A phone ----------------------------------------------------------
 * A capture inside a bezel. The capture is the whole screen, status bar
 * included, so the bezel draws only the frame around it. Each phone sets
 * --phone-width, and the bezel and corners are fractions of it: a padding
 * in % would be a fraction of the section's width, not the phone's. */

.intro-phone {
  box-sizing: border-box;
  width: var(--phone-width);
  padding: calc(var(--phone-width) * 0.032);
  /* The screen's corner (below) plus the bezel, so both curves share a
     centre. */
  border-radius: calc(var(--phone-width) * 0.16);
  background: var(--intro-bezel);
  box-shadow: 0 30px 80px rgba(80, 86, 135, 0.3);
}

.intro-phone picture {
  display: block;
}

/* An iPhone 18 Pro's screen corner: 55 of its 402 points. */
.intro-phone img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: calc(var(--phone-width) * 0.936 * 55 / 402);
}

/* ---- 1. The opening ---------------------------------------------------
 * Two columns on a taro ground (owner, 2026-11-02): on the left the words,
 * centred in the room above the three mascots, who sit at the column's foot,
 * level with the phone's; on the right the whole phone. On a phone's width,
 * one column: the words, the mascots, the phone. Nothing in it is pinned. */

/* Clipped, so the phone's shadow stays on the taro. */
.intro-hero {
  overflow: hidden;
  background: var(--intro-hero-ground);
}

.intro-hero-stage {
  box-sizing: border-box;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(32px, 6vw, 96px);
  /* At least the window's height, so on a tall screen the next section
     doesn't show under it; the columns sit in the middle. */
  align-content: center;
  min-height: 100svh;
  max-width: 1240px;
  margin: 0 auto;
  /* Just below the ☰ button (12 + 48) at the top. */
  padding: 76px clamp(24px, 5vw, 72px) 40px;
}

/* The words, the badges and the mascots under them are one group, centred
   in the column both ways (owner, 2026-11-02). The column is as tall as the
   phone beside it. */
.intro-hero-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.intro-hero-words {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* The gaps look alike only if they are alike in ink: a line of text has
   empty room above and below its glyphs, so each margin is the one gap less
   that room, in ems of its own text so it scales with it (measured in 粉圓:
   the name's glyphs end 0.08 em above its box's foot; the line's start 0.30
   em below its top and end 0.17 em above its foot). */
.intro-hero h1 {
  margin-bottom: calc(var(--intro-hero-gap) - 0.08em);
}

.intro-value {
  margin-top: -0.3em;
  margin-bottom: calc(var(--intro-hero-gap) - 0.17em);
}

.intro-hero h1 {
  color: #FFFFFF;
}

/* The one line under the name (owner, 2026-11-02), in its own colour. */
.intro-value {
  color: var(--intro-hero-line);
  font-size: clamp(24px, 2.4vw, 28px);
}

.intro-hero h1 {
  /* On one line beside the phone at full size. */
  font-size: clamp(40px, 5.2vw, 76px);
  line-height: 1.05;
  letter-spacing: 0.04em;
}


.intro-badges {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 16px;
  /* Above the mascots: falling in as they land, they pass behind the badges
     rather than over them. */
  position: relative;
  z-index: 1;
}

.intro-badges a {
  display: block;
  line-height: 0;
}

/* Both badges exactly 48 px tall. Google's file came with transparent clear
   space above and below its badge; it is trimmed off (the badge itself is
   untouched), because drawing the padded file at 62.5 px with 7.25 px
   margins put its bottom edge on a half pixel, a pixel lower than Apple's.
   The row's gap keeps the clear space. */
.intro-badges img {
  height: 48px;
  width: auto;
}

/* As tall as the window leaves below the ☰ button and above the stage's
   foot (76 + 40), between 240 and 360 wide. A phone is 2.174 times as tall
   as it is wide. */
.intro-hero .intro-phone {
  --phone-width: clamp(240px, min(26vw, calc((100svh - 116px) / 2.174)), 360px);
  align-self: center;
  box-shadow: 0 30px 80px rgba(30, 32, 70, 0.4);
}

/* The three mascots side by side, all as tall, their feet on one line
   (intro/sitting.js draws them; each one's width follows its art). The gap
   is 6% of their height, as sitting-art.js's trioGap: at that spacing their
   hands meet, raised as they come in and down as they sit. */
.intro-trio {
  --intro-sitter-height: clamp(64px, 7vw, 100px);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: calc(var(--intro-sitter-height) * 0.06);
  /* They fall this far as they come in (sitting.js), from just under the
     badges. */
  padding-top: var(--intro-hero-gap);
  pointer-events: none;
}

.intro-sitter {
  position: relative;
  height: var(--intro-sitter-height);
}

/* The figure falls and squashes about its feet; the shadow stays on the
   ground under it. */
.intro-sitter-figure {
  position: absolute;
  inset: 0;
  transform-origin: 50% 100%;
}

/* The arms, the body and the face lie on one another, the same size; the
   arms first, so a raised arm's root sinks behind the body. */
.intro-sitter img,
.intro-sitter-face {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* A soft oval on the ground under the mascot while it falls, growing as it
   nears the ground (sitting-art.js, shadowAt); once it sits, none. */
.intro-sitter-shadow {
  position: absolute;
  left: 15%;
  width: 70%;
  bottom: -7%;
  height: 14%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(20, 22, 60, 1), rgba(20, 22, 60, 0));
  opacity: 0;
}

.intro-sitter-face {
  overflow: visible;
}

/* A blink squashes each eye about its own centre. */
.intro-sitter-eye {
  transform-box: fill-box;
  transform-origin: center;
}

@media (max-width: 760px) {
  .intro-hero-stage {
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
    padding: 76px 16px 40px;
  }

  .intro-hero .intro-phone {
    --phone-width: min(64vw, 300px);
    justify-self: center;
  }
}

@media (prefers-reduced-motion: no-preference) {
  .intro-hero .intro-phone {
    animation: intro-rise 1s cubic-bezier(0.2, 0.7, 0.2, 1) 0.15s both;
  }

  .intro-hero-text {
    animation: intro-fade-up 0.8s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  }
}

@keyframes intro-rise {
  from {
    opacity: 0;
    transform: translateY(120px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes intro-fade-up {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* ---- 2. The search demo ------------------------------------------------
 * Pinned: the section is a tall track (set once intro.js runs, so without
 * the script it is an ordinary section), and its stage sticks in the window
 * while the reader scrolls through it. Each quarter of the track shows one
 * query and the app's results for it. */

.intro-search {
  background: var(--page);
}

.intro-search.is-pinned {
  height: 400svh;
}

.intro-search-stage {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(32px, 8vw, 120px);
  /* Room on the left for the sweet potato ball, which stands 28 left of the
     query on show (mascots.js): 60 wide, its gap and 4 to spare. Without it
     a long headline (English) pushed the queries to 24 from the window's
     edge, and the mascot off it (owner's tablet, 2026-11-02). A phone's
     width sets its own padding below. */
  padding: 72px 24px 32px 92px;
}

.is-pinned .intro-search-stage {
  position: sticky;
  top: 0;
  height: 100svh;
}

.intro-search-text {
  display: flex;
  flex-direction: column;
  gap: 24px;
  /* Wide enough for 華語、漢字、羅馬字、注音, twelve full-width characters,
     on one line at full size. */
  max-width: 680px;
}

.intro-kicker {
  margin: 0;
  color: var(--medium);
  font-size: 20px;
}

.intro-search h2 {
  font-size: clamp(32px, 4vw, 52px);
  line-height: 1.25;
}

.intro-queries {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 8px 0 0;
  padding: 0;
  list-style: none;
}

.intro-queries a {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 20px;
  border: 2px solid transparent;
  border-radius: 18px;
  background: var(--intro-idle);
  color: var(--dark);
  font-size: 24px;
  text-decoration: none;
  opacity: 0.6;
}

.intro-queries a[aria-current="step"] {
  border-color: var(--medium);
  background: var(--intro-current);
  box-shadow: var(--intro-current-shadow);
  opacity: 1;
}

.intro-queries a:hover {
  opacity: 1;
}

.intro-queries svg {
  flex-shrink: 0;
  fill: none;
  stroke: var(--dark);
  stroke-width: 2.4;
  stroke-linecap: round;
}

/* <b> and <small>, not spans: an element the catalog fills can't hold a
   span of its own (write-strings.mjs). */
.intro-query {
  flex-grow: 1;
  font-weight: normal;
}

.intro-kind {
  padding: 4px 10px;
  border-radius: 8px;
  background: var(--medium);
  color: var(--intro-tag-text);
  font-size: 15px;
}

.intro-try {
  align-self: flex-start;
  font-size: 20px;
  text-decoration: none;
}

.intro-try::after {
  content: " →";
}

/* The four captures lie on one another; the one on shows. */
.intro-search .intro-phone {
  --phone-width: clamp(220px, 24vw, 340px);
  position: relative;
  flex-shrink: 0;
}

.intro-shots {
  position: relative;
}

.intro-shot:not(:first-child) {
  position: absolute;
  inset: 0;
}

.intro-shot {
  opacity: 0;
}

.intro-shot.is-current {
  opacity: 1;
}

@media (prefers-reduced-motion: no-preference) {
  .intro-queries a,
  .intro-shot {
    transition: opacity 0.35s ease, background-color 0.35s ease, border-color 0.35s ease, box-shadow 0.35s ease;
  }
}

/* A phone's width: the words above, the phone below, both inside the
   window while the stage sticks. */
@media (max-width: 760px) {
  .intro-search-stage {
    flex-direction: column;
    justify-content: flex-start;
    gap: 16px;
    padding: 72px 16px 16px;
  }

  .intro-search-text {
    gap: 12px;
  }

  .intro-search h2 {
    font-size: 26px;
  }

  .intro-kicker {
    font-size: 16px;
  }

  /* The four queries two by two, smaller. */
  .intro-queries {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
  }

  .intro-queries a {
    gap: 8px;
    padding: 8px 10px;
    font-size: 17px;
  }

  .intro-queries svg {
    display: none;
  }

  .intro-kind {
    padding: 2px 6px;
    font-size: 12px;
  }

  .intro-try {
    font-size: 16px;
  }

  /* As tall as the window leaves, and never wider than that height allows. */
  .intro-search .intro-phone {
    --phone-width: max(150px, min(64vw, calc((100svh - 330px) * 0.46)));
  }
}

/* ---- The search demo's mascot ------------------------------------------
 * Built from the marketing rig's parts (intro/mascot.js) and placed and
 * moved by intro/mascots.js: the sweet potato ball stands by the query on
 * show. intro.js sets where each
 * stands (`translate`); one-off moves (a landing, a cheer, a nod)
 * are short animations the script plays. Decoration: never in the way of a
 * click, and never over the badges or a screenshot. */

/* The stages they stand on are their frame of reference. */
.intro-search-stage {
  position: relative;
}

.intro-mascot {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  pointer-events: none;
}

.intro-mascot img,
.intro-mascot-figure,
.intro-mascot-upper,
.intro-mascot-shadow {
  position: absolute;
  display: block;
}

.intro-mascot-figure,
.intro-mascot-upper,
.intro-mascot-face {
  inset: 0;
}

.intro-mascot-face {
  position: absolute;
}

/* Looking a little to the right (the search demo's mascot, which stands left
   of the queries and the phone): its face moves 6% of its width that way and
   it leans 4° toward it, about its feet (owner, 2026-11-02). */
.looks-right .intro-mascot-face {
  translate: calc(var(--mascot-size) * 0.06) 0;
}

.looks-right .intro-mascot-upper {
  rotate: 4deg;
}

/* Its feet stay planted while it stretches in the air. */
.intro-mascot-figure {
  transform-origin: 50% 100%;
  transition: scale 0.15s ease;
}

.is-airborne .intro-mascot-figure {
  scale: 0.92 1.1;
}

.intro-mascot-arm-left {
  rotate: var(--arm-left, 0deg);
  transition: rotate 0.25s ease;
}

.intro-mascot-arm-right {
  rotate: var(--arm-right, 0deg);
  transition: rotate 0.25s ease;
}

/* Happy eyes (a cheer) in place of the round ones. */
.intro-mascot-eye-happy,
.is-happy .intro-mascot-eye {
  display: none;
}

.is-happy .intro-mascot-eye-happy {
  display: block;
}

/* Its shadow on the floor: not in the air. */
.intro-mascot-shadow {
  left: 12%;
  width: 76%;
  bottom: -3%;
  height: 7%;
  border-radius: 50%;
  background: rgba(60, 60, 90, 0.18);
}

.is-airborne .intro-mascot-shadow {
  display: none;
}

/* Idle: breathing (3 s) and a blink every 2.7 s, as in the reel; paused
   while their section is off screen (mascots.js). */
@media (prefers-reduced-motion: no-preference) {
  .intro-mascot-upper {
    animation: intro-breathe 3s ease-in-out infinite alternate;
    transform-origin: 50% 100%;
  }

  .intro-mascot-eye {
    animation: intro-blink 2.7s linear infinite;
  }

  .is-offscreen .intro-mascot-upper,
  .is-offscreen .intro-mascot-eye {
    animation-play-state: paused;
  }
}

@keyframes intro-breathe {
  to {
    transform: scale(1.012, 0.985);
  }
}

@keyframes intro-blink {
  0%,
  94%,
  100% {
    transform: scaleY(1);
  }
  97% {
    transform: scaleY(0.1);
  }
}


/* ---- 3. 台語、客語 -------------------------------------------------------
 * Two halves, taro for 台語 and grass jelly for 客語, the headline across
 * the seam in white (owner's mock, 2026-11-02). Each half holds its Home
 * card, cut from a capture of the app (Tools/StoreScreenshots/cutout.swift);
 * on the seam, the app's own 台 and 客 buttons with an arrow between: the
 * switch. Under the 客語 card, its six 腔 as buttons: each shows that 腔's
 * card, its own word for "thank you" (owner, 2026-11-02). On a phone's
 * width the halves stack, the switch on the seam between them. */

:root {
  /* White clears taroMedium at 4.79:1 and grass jelly at 4.87:1. The store
     screenshots' lighter taro, #8D92C7, gives white only 2.97:1, short of
     even large text's 3:1 (owner, 2026-11-02). */
  --intro-taigi-ground: #6A6DAA;
  --intro-hakka-ground: #716F84;
  /* The cards' width, and the switch's buttons'. */
  --intro-card: clamp(220px, 26vw, 380px);
  --intro-switch: clamp(52px, 5vw, 72px);
}

@media (prefers-color-scheme: dark) {
  :root {
    /* Deeper shades of both, so the section doesn't light up a dark page
       (white on them: 9.47:1 and 9.40:1). */
    --intro-taigi-ground: #3E4270;
    --intro-hakka-ground: #46445A;
  }
}

.intro-languages {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  grid-template-areas:
    "words words words"
    "taigi switch hakka";
  row-gap: 56px;
  padding: 96px 24px 112px;
  /* The seam runs down the middle, through the switch's column, as the two
     outer columns are the same width. */
  background: linear-gradient(to right, var(--intro-taigi-ground) 50%, var(--intro-hakka-ground) 50%);
  color: #FFFFFF;
}

.intro-languages-words {
  grid-area: words;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  text-align: center;
}

.intro-languages .intro-kicker,
.intro-languages h2 {
  color: #FFFFFF;
}

.intro-languages h2 {
  font-size: clamp(32px, 4vw, 52px);
  line-height: 1.25;
}

.intro-lead {
  max-width: 36em;
  margin: 0;
  font-size: clamp(17px, 1.6vw, 20px);
  line-height: 1.6;
}

.intro-language {
  display: grid;
  /* One column, the card's width, so a row of accents wider than the card
     can't widen it and push the card off its place by the seam. */
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  align-content: start;
  gap: 20px;
  width: var(--intro-card);
}

/* Each half's card sits by the seam. */
.intro-language-taigi {
  grid-area: taigi;
  justify-self: end;
}

.intro-language-hakka {
  grid-area: hakka;
  justify-self: start;
}

.intro-language-name {
  margin: 0;
  font-size: 20px;
  line-height: 28px;
}

.intro-language img {
  display: block;
  width: var(--intro-card);
  height: auto;
}

/* The six 客語 cards in one place, only the chosen 腔's on show. Hidden
   ones are also hidden from screen readers. */
.intro-hakka-cards {
  display: grid;
}

.intro-hakka-card {
  grid-area: 1 / 1;
  opacity: 0;
  visibility: hidden;
}

.intro-hakka-card.is-current {
  opacity: 1;
  visibility: visible;
}

.intro-switch {
  grid-area: switch;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0 clamp(12px, 2vw, 32px);
  /* Level with the cards' middles: below the name (28) and its gap (20),
     half a card down (a card is 894/850 as tall as wide), less half a
     button. */
  margin-top: calc(48px + var(--intro-card) * 0.5259 - var(--intro-switch) / 2);
  align-self: start;
}

.intro-switch img {
  display: block;
  width: var(--intro-switch);
  height: auto;
}

.intro-switch svg {
  width: clamp(32px, 3.4vw, 48px);
  height: auto;
  fill: none;
  stroke: #FFFFFF;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Three by two: six in a row would leave 南四縣 alone on a second line at
   most widths. */
.intro-accents {
  display: grid;
  grid-template-columns: repeat(3, auto);
  justify-content: center;
  /* Centred under the card even where it is wider than the card. */
  justify-self: unsafe center;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The 腔 buttons: outlined, and filled white when chosen (the name then in
   grass jelly, 4.87:1). 44 px tall, a finger's reach. */
.intro-accents button {
  min-height: 44px;
  padding: 0 16px;
  border: 1.5px solid #FFFFFF;
  border-radius: 999px;
  background: transparent;
  color: #FFFFFF;
  font: inherit;
  font-size: 16px;
  cursor: pointer;
}

.intro-accents button[aria-pressed="true"] {
  background: #FFFFFF;
  color: var(--intro-hakka-ground);
}

.intro-accents button:disabled {
  cursor: default;
}

.intro-accents button:focus-visible {
  outline: 2px solid #FFFFFF;
  outline-offset: 3px;
}

/* A short wide window (a laptop, a tablet held sideways): the section fits
   one screen, so the page's snap stop shows it whole; at 1440 by 900 its
   accents ran 32 px under the window, too little for a stop of their own
   (owner, 2026-11-03). The cards and the room around them follow the
   window's height; a tall window keeps them as they were. */
@media (min-width: 761px) {
  .intro-languages {
    --intro-card: clamp(220px, min(26vw, 34svh), 380px);
    row-gap: clamp(32px, 5svh, 56px);
    padding-top: clamp(56px, 8svh, 96px);
    padding-bottom: clamp(56px, 9svh, 112px);
  }
}

@media (max-width: 760px) {
  /* One column: the words and the 台語 card on taro, then the switch, top to
     bottom, on the seam, then the 客語 card and its accents on grass jelly. */
  .intro-languages {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "words" "taigi" "switch" "hakka";
    row-gap: 0;
    padding: 72px 0 0;
    background: var(--intro-taigi-ground);
  }

  .intro-languages-words {
    padding: 0 16px 32px;
  }

  .intro-languages h2 {
    font-size: 30px;
  }

  .intro-language {
    --intro-card: min(72vw, 320px);
    justify-self: stretch;
    width: auto;
  }

  .intro-language-hakka {
    padding: 0 16px 72px;
    background: var(--intro-hakka-ground);
  }

  /* The switch runs top to bottom too, as the cards do (owner,
     2026-11-02): 台, the arrow pointing down, 客. The seam crosses the
     arrow's middle. */
  .intro-switch {
    flex-direction: column;
    justify-self: stretch;
    align-items: center;
    margin: 0;
    padding: 24px 0;
    background: linear-gradient(to bottom, var(--intro-taigi-ground) 50%, var(--intro-hakka-ground) 50%);
  }

  /* Turned, the arrow stands taller than its box (32 by 16 at this width):
     8 px of room above and below keeps it off the buttons, and 4 more
     match their gap. */
  .intro-switch svg {
    margin: 12px 0;
    transform: rotate(90deg);
  }
}

/* The switch plays once as the section comes in (intro/languages.js): the
   台語 card, then 台, the arrow drawing across, 客, and the 客語 card with
   its accents. Until then the parts wait unseen. Without the script, or with
   reduce-motion on, all of it simply shows. */
@media (prefers-reduced-motion: no-preference) {
  .intro-languages.is-waiting :is(.intro-language-taigi picture, .intro-hakka-cards, .intro-switch picture, .intro-accents button) {
    opacity: 0;
  }

  .intro-languages.is-waiting .intro-switch path {
    stroke-dashoffset: 60;
  }

  .intro-switch path {
    stroke-dasharray: 60;
  }

  .is-playing .intro-language-taigi picture {
    animation: intro-fade-up 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  }

  .is-playing .intro-switch picture:first-child {
    animation: intro-pop 0.45s cubic-bezier(0.3, 1.4, 0.5, 1) 0.3s both;
  }

  .is-playing .intro-switch path {
    animation: intro-draw 0.45s ease-in-out 0.65s both;
  }

  .is-playing .intro-switch picture:last-child {
    animation: intro-pop 0.45s cubic-bezier(0.3, 1.4, 0.5, 1) 1s both;
  }

  .is-playing .intro-hakka-cards {
    animation: intro-fade-up 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) 1.15s both;
  }

  .is-playing .intro-accents button {
    animation: intro-fade-up 0.4s cubic-bezier(0.2, 0.7, 0.2, 1) calc(1.5s + var(--intro-accent-index, 0) * 0.06s) both;
  }

  .intro-accents button:nth-child(2) { --intro-accent-index: 1; }
  .intro-accents button:nth-child(3) { --intro-accent-index: 2; }
  .intro-accents button:nth-child(4) { --intro-accent-index: 3; }
  .intro-accents button:nth-child(5) { --intro-accent-index: 4; }
  .intro-accents button:nth-child(6) { --intro-accent-index: 5; }

  /* Choosing a 腔 cross-fades its card in. */
  .intro-hakka-card {
    transition: opacity 0.25s ease, visibility 0.25s;
  }

  .intro-accents button {
    transition: background-color 0.2s ease, color 0.2s ease;
  }
}

@keyframes intro-pop {
  from {
    opacity: 0;
    transform: scale(0.6);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes intro-draw {
  from {
    stroke-dashoffset: 60;
  }
  to {
    stroke-dashoffset: 0;
  }
}

/* ---- 4. 詞頁 -------------------------------------------------------------
 * 恁仔細's page fits one screen, so the phone holds still and a spotlight
 * frames one part of the capture at a time while the rest dims (owner,
 * 2026-11-02), in the page's order: the word and its reading; the
 * comparison card, a step per tab, each its own capture; the meanings and
 * examples, with two notes beside the phone; the action bar. Pinned like
 * the search demo: six steps, one per window's height of scrolling. */

.intro-word {
  background: var(--column);
}

.intro-word.is-pinned {
  height: 600svh;
}

.intro-word-stage {
  box-sizing: border-box;
  display: grid;
  grid-template-columns: minmax(0, 340px) auto minmax(0, 300px);
  grid-template-areas:
    "words words words"
    "stops phone notes";
  justify-content: center;
  align-content: center;
  align-items: center;
  column-gap: clamp(32px, 4vw, 64px);
  row-gap: 28px;
  padding: 64px 24px 24px;
}

.is-pinned .intro-word-stage {
  position: sticky;
  top: 0;
  height: 100svh;
}

.intro-word-words {
  grid-area: words;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  text-align: center;
}

.intro-word h2 {
  font-size: clamp(28px, 3.2vw, 44px);
  line-height: 1.25;
}

.intro-word .intro-lead {
  color: var(--supporting);
}

.intro-stops {
  grid-area: stops;
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.intro-stops li {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 14px 16px;
  border: 2px solid transparent;
  border-radius: 18px;
  background: var(--intro-idle);
  opacity: 0.6;
}

.intro-stops li.is-current {
  border-color: var(--medium);
  background: var(--intro-current);
  box-shadow: var(--intro-current-shadow);
  opacity: 1;
}

.intro-stop-number {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 30px;
  height: 30px;
  border: 1.5px solid var(--medium);
  border-radius: 50%;
  color: var(--medium);
  font-size: 16px;
  font-weight: normal;
}

.is-current .intro-stop-number {
  background: var(--medium);
  color: var(--intro-tag-text);
}

.intro-stops strong {
  color: var(--dark);
  font-size: 20px;
  font-weight: normal;
}

.intro-stops p {
  margin: 4px 0 0;
  color: var(--supporting);
  font-size: 15px;
  line-height: 1.5;
}

.intro-word .intro-phone {
  --phone-width: clamp(200px, min(22vw, calc((100svh - 300px) / 2.174)), 320px);
}

.intro-word .intro-phone-room {
  grid-area: phone;
}

/* The screen clips the spotlight's dimming to its own rounded corners. */
.intro-word-screen {
  position: relative;
  overflow: hidden;
  border-radius: calc(var(--phone-width) * 0.936 * 55 / 402);
}

/* The spotlight: a clear frame whose shadow, spread far past the screen,
   dims everything else. Its place on each step is the part's, measured
   on the captures (1206 by 2622 pixels) with 12 pixels to spare; across
   the screen it spans 2.5% to 97.5%. Shown only once intro.js pins the
   section. */
.intro-spot {
  display: none;
  position: absolute;
  left: 2.5%;
  width: 95%;
  top: 13.58%;
  height: 13.73%;
  border-radius: calc(var(--phone-width) * 0.04);
  /* A ring just outside the frame, then the dimming spread far past it. */
  box-shadow:
    0 0 0 2px var(--intro-spot-ring),
    0 0 0 calc(var(--phone-width) * 3) var(--intro-spot-dim);
}

.is-pinned .intro-spot {
  display: block;
}

/* 詞與讀音: the 腔 chips, the headword and its reading (y 356–716). */
.intro-word[data-shown="0"] .intro-spot { top: 13.58%; height: 13.73%; }
/* The comparison card on each tab: 腔調對照 (y 738–1280), 語言對照
   (738–1249), 相關詞 (738–1063). */
.intro-word[data-shown="1"] .intro-spot { top: 28.15%; height: 20.67%; }
.intro-word[data-shown="2"] .intro-spot { top: 28.15%; height: 19.49%; }
.intro-word[data-shown="3"] .intro-spot { top: 28.15%; height: 12.40%; }
/* 釋義與例句, down to the sources (y 1272–2011). */
.intro-word[data-shown="4"] .intro-spot { top: 48.51%; height: 28.18%; }
/* The action bar (y 2191–2363). */
.intro-word[data-shown="5"] .intro-spot { top: 83.56%; height: 6.56%; }

.intro-notes {
  grid-area: notes;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* While pinned, the notes show only beside the meanings and examples. */
.is-pinned .intro-notes:not(.is-current) {
  opacity: 0;
  visibility: hidden;
}

.intro-note {
  padding: 16px 20px;
  border-radius: 18px;
  background: var(--intro-current);
  box-shadow: var(--intro-current-shadow);
}

.intro-note strong {
  color: var(--dark);
  font-size: 20px;
  font-weight: normal;
}

.intro-note p {
  margin: 6px 0 0;
  color: var(--supporting);
  font-size: 16px;
  line-height: 1.55;
}

/* The grass jelly tag: the feature is Hakka's alone (white on #716F84,
   4.87:1). */
.intro-note-tag {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 8px;
  border-radius: 6px;
  background: #716F84;
  color: #FFFFFF;
  font-size: 13px;
  vertical-align: 3px;
}

@media (prefers-reduced-motion: no-preference) {
  .intro-stops li,
  .intro-notes {
    transition: opacity 0.35s ease, visibility 0.35s, background-color 0.35s ease, border-color 0.35s ease, box-shadow 0.35s ease;
  }

  .intro-spot {
    transition: top 0.45s cubic-bezier(0.3, 0.7, 0.3, 1), height 0.45s cubic-bezier(0.3, 0.7, 0.3, 1);
  }
}

/* A short window beside a wide one (a tablet held sideways): only the stop
   on show keeps its line, the others their titles, so the list fits; in
   English its lines ran the fourth stop to the window's edge at 1133 by 744
   (owner's device check, 2026-11-03). */
@media (min-width: 761px) and (max-height: 820px) {
  .intro-word.is-pinned .intro-stops li:not(.is-current) p {
    display: none;
  }
}

/* A tablet held upright: two columns, the notes under the stops, so the
   phone keeps its size (three columns left it 200 px wide at 768). Only
   upright: on a short window, as a tablet held sideways, stops and notes
   stacked are taller than it. */
@media (max-width: 1100px) and (orientation: portrait) {
  .intro-word-stage {
    grid-template-columns: minmax(0, 320px) auto;
    grid-template-areas:
      "words words"
      "stops phone"
      "notes phone";
    align-items: start;
    row-gap: 20px;
  }

  .intro-word .intro-phone {
    --phone-width: clamp(200px, min(36vw, calc((100svh - 300px) / 2.174)), 320px);
  }

  .intro-word .intro-phone-room {
    align-self: center;
  }
}

/* A phone's width: the headline, the stop on show, then the phone, all in
   the window while the stage sticks. The notes wait for wider screens; the
   stop's own line says the same. */
@media (max-width: 760px) {
  .intro-word-stage {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "words" "stops" "phone";
    align-content: start;
    justify-items: center;
    row-gap: 12px;
    padding: 72px 16px 16px;
  }

  .intro-word h2 {
    font-size: 24px;
  }

  .intro-word .intro-lead,
  .intro-notes {
    display: none;
  }

  .intro-stops {
    width: 100%;
  }

  .is-pinned .intro-stops li:not(.is-current) {
    display: none;
  }

  .intro-stops li {
    padding: 10px 12px;
  }

  .intro-stops strong {
    font-size: 17px;
  }

  .intro-stops p {
    font-size: 14px;
  }

  .intro-word .intro-phone {
    --phone-width: max(150px, min(64vw, calc((100svh - 290px) / 2.174)));
  }
}

/* ---- 5. 還有更多 -----------------------------------------------------------
 * Five features, each a whole screen of the app (owner, 2026-11-02: whole
 * screens read better than crops). On a computer all five stand in one
 * row across the page's column, as scrolling sideways is awkward with a
 * mouse (owner); narrower, the row scrolls sideways, edge to edge, its
 * first phone lined up with the page's column, each snapping into place. The widget, a piece of the home screen, sits
 * in a square of the phone's size. Nothing moves by itself. */

.intro-more {
  --intro-column: min(1160px, calc(100vw - 48px));
  /* Where the page's column starts: the row's first phone lines up with it. */
  --intro-inset: calc((100vw - var(--intro-column)) / 2);
  display: flex;
  flex-direction: column;
  gap: 40px;
  padding: 96px 0 112px;
}

.intro-more h2 {
  font-size: clamp(32px, 4vw, 52px);
  line-height: 1.25;
  text-align: center;
}

.intro-gallery {
  display: flex;
  gap: clamp(24px, 3vw, 40px);
  margin: 0;
  /* Room below for the phones' shadows, which the scrolling clips. */
  padding: 8px var(--intro-inset) 48px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--intro-inset);
  list-style: none;
  scrollbar-width: none;
}

.intro-gallery::-webkit-scrollbar {
  display: none;
}

.intro-gallery:focus-visible {
  outline: 2px solid var(--medium);
  outline-offset: -2px;
}

.intro-feature {
  --phone-width: clamp(220px, 21vw, 270px);
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  gap: 20px;
  width: var(--phone-width);
  scroll-snap-align: start;
}

.intro-feature .intro-phone {
  box-shadow: 0 18px 40px rgba(80, 86, 135, 0.22);
}

/* The widget's square: a phone's size (its bezel, 6.4% of its width, and
   its screen, 2622/1206 of the rest), the widget in the middle, its
   corners the widget's own (about 9% of its width). */
.intro-feature-widget-ground {
  display: grid;
  place-items: center;
  height: calc(var(--phone-width) * (0.064 + 0.936 * 2622 / 1206));
  border-radius: calc(var(--phone-width) * 0.16);
  background: var(--column);
}

.intro-feature-widget-ground img {
  display: block;
  width: 84%;
  height: auto;
  border-radius: 9%;
  box-shadow: 0 10px 28px rgba(80, 86, 135, 0.25);
}

.intro-feature-words {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.intro-feature h3 {
  margin: 0;
  color: var(--dark);
  font-size: 22px;
  font-weight: normal;
  line-height: 1.35;
}

.intro-feature p {
  margin: 0;
  color: var(--supporting);
  font-size: 16px;
  line-height: 1.5;
}

/* The arrows, under the row at the column's right edge; shown once
   intro/gallery.js runs. */
.intro-gallery-arrows:not([hidden]) {
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  box-sizing: border-box;
  width: var(--intro-column);
  margin: -24px auto 0;
}

.intro-gallery-arrows button {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--column);
  cursor: pointer;
}

.intro-gallery-arrows button:disabled {
  opacity: 0.35;
  cursor: default;
}

.intro-gallery-arrows button:focus-visible {
  outline: 2px solid var(--medium);
  outline-offset: 2px;
}

.intro-gallery-arrows svg {
  fill: none;
  stroke: var(--dark);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@media (prefers-reduced-motion: no-preference) {
  .intro-gallery {
    scroll-behavior: smooth;
  }
}

/* A computer's width: all five in one row across the column, no scrolling
   and no arrows. The phones share the column (container units, as a
   phone's bezel and corners are fractions of its width in pixels). */
@media (min-width: 1000px) {
  .intro-gallery {
    --intro-feature-gap: clamp(20px, 2.2vw, 36px);
    container-type: inline-size;
    gap: var(--intro-feature-gap);
    box-sizing: border-box;
    /* From the section's own width, not the window's: 100vw counts the
       scrollbar, which left the row 15 px off centre. */
    width: min(1160px, 100% - 48px);
    margin: 0 auto;
    padding: 8px 0 24px;
    overflow: visible;
    scroll-snap-type: none;
  }

  .intro-feature {
    --phone-width: calc((100cqi - 4 * var(--intro-feature-gap)) / 5);
  }

  .intro-gallery-arrows:not([hidden]) {
    display: none;
  }
}

/* A phone's width: bigger phones, about one and a half on screen, so the
   next one shows there is more. */
@media (max-width: 640px) {
  .intro-more {
    --intro-column: calc(100vw - 32px);
    gap: 24px;
    padding: 72px 0 80px;
  }

  .intro-more h2 {
    font-size: 30px;
  }

  .intro-gallery {
    gap: 20px;
  }

  .intro-feature {
    --phone-width: 62vw;
  }
}

/* ---- 6. 各種裝置 -----------------------------------------------------------
 * A real capture on each device (owner, 2026-11-02). iPad, iPhone and
 * Android in a row, their feet level; then the iPhone Duo alone, in book
 * pose, drawn folded (owner, 2026-11-03). Nothing moves.
 *
 * Each device is its screen's height (--h) and shape (--ratio, width over
 * height, from its capture), inside a bezel (--bezel); its corners are
 * fractions of the screen's height. */

.intro-devices {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 48px;
  padding: 96px 24px 112px;
  background: var(--column);
}

.intro-devices h2 {
  font-size: clamp(32px, 4vw, 52px);
  line-height: 1.25;
  text-align: center;
}

.intro-device {
  --bezel: 10px;
  box-sizing: content-box;
  width: calc(var(--h) * var(--ratio));
  height: var(--h);
  padding: var(--bezel);
  border-radius: calc(var(--h) * var(--corner) + var(--bezel));
  background: var(--intro-bezel);
  box-shadow: 0 24px 60px rgba(80, 86, 135, 0.25);
}

.intro-device-screen {
  position: relative;
  overflow: hidden;
  height: 100%;
  border-radius: calc(var(--h) * var(--corner));
}

.intro-device-screen picture,
.intro-device-screen img {
  display: block;
  width: 100%;
  height: 100%;
}

.intro-device-screen img {
  object-fit: cover;
}

/* The Duo in book pose, 2853 by 2007, drawn as two halves that meet at the
   fold: each its own bezel round three sides and half the capture (the
   same image, twice as wide as its half, shifted for the right one),
   turned a little about the fold so the pair stands open like a book, the
   fold away from the reader. A shade deepening toward the fold sells the
   bend. */
.intro-duo {
  --h: 380px;
  --half: calc(var(--h) * 2853 / 2007 / 2);
  --bezel: 10px;
  --radius: calc(var(--h) * 0.06);
  --turn: 14deg;
  display: flex;
  perspective: 1800px;
  filter: drop-shadow(0 24px 40px rgba(80, 86, 135, 0.25));
}

.intro-duo-half {
  box-sizing: content-box;
  width: var(--half);
  height: var(--h);
  padding: var(--bezel);
  background: var(--intro-bezel);
}

/* The hinge: a dark line where the halves meet, 2 px on each side. */
.intro-duo-left {
  padding-right: 2px;
  border-radius: calc(var(--radius) + var(--bezel)) 0 0 calc(var(--radius) + var(--bezel));
  transform-origin: right center;
  transform: rotateY(var(--turn));
}

.intro-duo-right {
  padding-left: 2px;
  border-radius: 0 calc(var(--radius) + var(--bezel)) calc(var(--radius) + var(--bezel)) 0;
  transform-origin: left center;
  transform: rotateY(calc(-1 * var(--turn)));
}

.intro-duo-screen {
  position: relative;
  overflow: hidden;
  height: 100%;
}

.intro-duo-left .intro-duo-screen {
  border-radius: var(--radius) 0 0 var(--radius);
}

.intro-duo-right .intro-duo-screen {
  border-radius: 0 var(--radius) var(--radius) 0;
}

.intro-duo-screen picture {
  display: block;
  height: 100%;
}

.intro-duo-screen img {
  display: block;
  width: 200%;
  max-width: none;
  height: 100%;
}

.intro-duo-right .intro-duo-screen img {
  margin-left: -100%;
}

/* On a dark page the black bezels all but vanish: a faint rim draws each
   device's edge (owner's review, 2026-11-03). */
@media (prefers-color-scheme: dark) {
  .intro-device {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.16), 0 24px 60px rgba(0, 0, 0, 0.5);
  }

  .intro-duo-half {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.16);
  }
}

/* The bend's shade, deepest at the fold. */
.intro-duo-screen::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.intro-duo-left .intro-duo-screen::after {
  background: linear-gradient(to right, transparent 55%, rgba(0, 0, 0, 0.16));
}

.intro-duo-right .intro-duo-screen::after {
  background: linear-gradient(to left, transparent 55%, rgba(0, 0, 0, 0.16));
}

.intro-duo-figure {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 20px;
  margin: 0;
}

.intro-duo-figure figcaption {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  text-align: center;
}

.intro-devices strong {
  color: var(--dark);
  font-size: 20px;
  font-weight: normal;
}

.intro-duo-figure p {
  margin: 0;
  color: var(--supporting);
  font-size: 17px;
}

.intro-device-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: center;
  gap: 48px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.intro-device-row li {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
}

/* iPad, 2752 by 2064, its corners small. */
.intro-device-ipad {
  --h: 300px;
  --ratio: calc(1600 / 1200);
  --corner: 0.035;
  --bezel: 14px;
}

/* iPhone 18 Pro, 1206 by 2622, corners 55 of its 874 points. */
.intro-device-iphone {
  --h: 340px;
  --ratio: calc(1206 / 2622);
  --corner: 0.063;
}

/* Android (Pixel 9a), 1080 by 2424. */
.intro-device-android {
  --h: 340px;
  --ratio: calc(1080 / 2424);
  --corner: 0.05;
}

/* A phone's width: the Duo as wide as the window lets it open, the iPad
   across the row, the two phones side by side under it. */
@media (max-width: 640px) {
  .intro-devices {
    gap: 32px;
    padding: 72px 16px 80px;
  }

  .intro-devices h2 {
    font-size: 30px;
  }

  .intro-duo {
    --h: calc((100vw - 32px - 24px) / 1.422);
  }

  .intro-device-row {
    gap: 24px 20px;
  }

  .intro-device-row li:first-child {
    flex-basis: 100%;
  }

  .intro-device-ipad {
    --h: calc((100vw - 32px - 28px) / (1600 / 1200));
  }

  .intro-device-iphone,
  .intro-device-android {
    --h: min(300px, calc((100vw - 32px - 20px) / 2 / 0.46 - 20px));
  }
}

/* ---- 7. 結尾 -------------------------------------------------------------
 * The page ends where it began: on the opening's taro, in both modes, the
 * call to download in white, the store badges, the three mascots coming in
 * under them (intro/sitting.js, as on the opening), and a white pill to the
 * site's own search. */

.intro-closing {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
  padding: 112px 24px 120px;
  background: var(--intro-hero-ground);
  text-align: center;
}

.intro-closing h2 {
  color: #FFFFFF;
  font-size: clamp(44px, 6vw, 80px);
  line-height: 1.2;
}

/* The trio's own padding is how far they fall (sitting.js): the section's
   gap is taken back, so they come in from just under the badges, as on the
   opening. */
.intro-closing .intro-trio {
  margin-top: -28px;
}

/* taroDark on white, 6.95:1 (6.06:1 on the hover ground). */
.intro-closing-search {
  margin-top: 8px;
  padding: 12px 24px;
  border-radius: 999px;
  background: #FFFFFF;
  color: #505687;
  font-size: 19px;
  text-decoration: none;
}

.intro-closing-search::after {
  content: " →";
}

.intro-closing-search:hover {
  background: #EEEFF7;
}

.intro-closing-search:focus-visible {
  outline: 3px solid #FFFFFF;
  outline-offset: 3px;
}

@media (max-width: 640px) {
  .intro-closing {
    padding: 88px 16px 96px;
  }
}

/* ---- The pinned phones on a phone's width ---------------------------------
 * Each pinned section's phone stands in a room that takes all the height its
 * stage leaves under the words, and is as big as that room lets it be: a
 * phone is 2.1 times as tall as it is wide (its bezel, 6.4% of its width,
 * and a screen 2622/1206 of the rest). A guess at the words' height ran the
 * search demo's phone past the window in English and on short screens
 * (owner's device check, 2026-11-03: 35 px on a 390 by 664 window, 143 px
 * on 375 by 548). Only while pinned: a section the script left unpinned has
 * no fixed height to share out. */
@media (max-width: 760px) {
  .is-pinned .intro-phone-room {
    display: flex;
    flex: 1 1 0;
    justify-content: center;
    /* Both ways: a size container has no size of its own to fall back on. */
    align-self: stretch;
    justify-self: stretch;
    min-height: 0;
    container-type: size;
  }

  .is-pinned .intro-phone-room .intro-phone {
    --phone-width: min(64vw, 300px, 100cqh / 2.1);
  }

  /* The word page's stage is a grid: its last row takes what is left. */
  .intro-word.is-pinned .intro-word-stage {
    grid-template-rows: auto auto minmax(0, 1fr);
  }
}

/* ---- One part at a time -----------------------------------------------------
 * The page snaps (owner, 2026-11-03): a swipe, a flick of the wheel or a
 * page key settles on the next part, never past it (snap stops are
 * "always"). Each section's top is a stop; a pinned section has one more per
 * later step (intro.js places them), so one swipe moves one query or one
 * spotlight. Inside a section taller than the window, its parts get stops
 * too (snap.js), each bringing its part fully into view. Only this page
 * snaps. */

html:has(main.intro) {
  scroll-snap-type: y mandatory;
}

main.intro > section {
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

/* The markers the stops sit on, inside a pinned section's track. */
.intro-search.is-pinned,
.intro-word.is-pinned {
  position: relative;
}

.intro-snap-stop {
  position: absolute;
  left: 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
  scroll-snap-align: start;
  scroll-snap-stop: always;
}

/* A part inside a long section, where snap.js gives it a stop: its foot
   comes to the window's foot, so the whole part shows. */
.is-snap-stop {
  scroll-snap-align: end;
  scroll-snap-stop: always;
}
