/* A book is a stack of spreads, one per screen, snapped.
 *
 * The argument for scrolling is that it is native and frictionless. The
 * argument against it is that a picture book's whole power is the *turn* —
 * the page you cannot see yet. Scroll-snap gets both: the reader flicks,
 * swipes, presses space or an arrow key, and the next spread arrives whole
 * rather than sliding into view a third at a time.
 */

.reader {
  height: 100svh;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  scroll-behavior: smooth;
  overscroll-behavior-y: contain;
  scrollbar-width: none;
}
.reader::-webkit-scrollbar { display: none; }

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

.spread {
  height: 100svh;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: grid;
  position: relative;
  overflow: hidden;
}

/* Portrait: the plate on top, the words beneath it on the paper.
 *
 * The plate is capped rather than merely given what is left over. A spread
 * whose passage runs long would otherwise squeeze the drawing to a stripe;
 * capping it at a little over half the screen means the picture is always a
 * picture and the text always has room to breathe under it. */
.spread {
  grid-template-rows: minmax(0, 1fr) auto;
}
.spread > .plate { max-height: 56svh; }

.plate {
  position: relative;
  min-height: 0;
  background: var(--p-paper);
  overflow: hidden;
  isolation: isolate;
  /* Keeps a plate's blending and paint work to itself, and lets the browser
   * skip offscreen plates entirely — there are a dozen of them in a book and
   * only one is ever being looked at. */
  content-visibility: auto;
  contain-intrinsic-size: auto 60svh;
}
.plate svg { display: block; width: 100%; height: 100%; }

/* Ink on damp paper.
 *
 * One tiling noise for the whole site rather than a filter inside every
 * plate: the browser rasterises this 180px tile once and blits it, where a
 * per-plate feTurbulence is recomputed per plate and, on mobile Safari,
 * covering a large area with live turbulence is a reported cause of tab
 * crashes rather than merely of jank. `stitchTiles` is what makes the tile
 * seamless — without it every 180px there is a visible seam. */
.plate::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'><feTurbulence type='fractalNoise' baseFrequency='0.72' numOctaves='4' stitchTiles='stitch'/><feColorMatrix type='matrix' values='0 0 0 0 0.07 0 0 0 0 0.06 0 0 0 0 0.05 1.25 0.4 0 0 -0.28'/></filter><rect width='180' height='180' filter='url(%23n)'/></svg>");
  background-size: 180px 180px;
  mix-blend-mode: var(--p-blend);
  opacity: var(--p-grain-a);
}

/* Until the drawing has been generated there is a quiet field of paper, not a
 * spinner. A spinner tells a reader to wait; paper tells them nothing, which
 * is correct, because the wait is a quarter of a second. */
.plate:empty { background: var(--paper-deep); }

.words {
  padding: clamp(18px, 3.4vh, 34px) var(--page-pad)
    max(clamp(18px, 3.4vh, 34px), env(safe-area-inset-bottom));
  max-width: var(--measure);
  margin-inline: auto;
  width: 100%;
}

.words p {
  margin: 0 0 0.85em;
  /* The story is set a shade larger than ordinary body text. It is the thing
   * on the page that matters most and it should look like it. */
  font-size: 1.06em;
}
.words p:last-child { margin-bottom: 0; }

/* A line that stands alone — the turn, the sting, the question. Set apart and
 * a little larger, never in bold: emphasis by air, not by weight. */
.words p.beat {
  font-size: 1.24em;
  line-height: 1.42;
  margin: 0.7em 0;
  text-indent: 0;
}

/* Landscape and desktop: a true spread — plate on the left, words on the
 * right, both full height. This is the shape a printed picture book has and
 * it is worth having whenever the screen is wide enough to hold it. */
@media (min-aspect-ratio: 1/1) and (min-width: 700px) {
  .spread {
    grid-template-rows: 1fr;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  }
  .words {
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding: 6vh clamp(28px, 4vw, 72px);
    max-width: none;
  }
  .words .inner { max-width: 30rem; margin-inline: auto; }
  /* Alternate which side the plate sits on, so a long book does not become a
   * column. The parity is set on the element by the reader script. */
  .spread.flip .plate { order: 2; }
  .spread > .plate { max-height: none; }
}

@media not all and (min-aspect-ratio: 1/1) and (min-width: 700px) {
  .words .inner { display: contents; }
}

/* --------------------------------------------------------------- the title */

.spread.title {
  grid-template-rows: 1fr;
  grid-template-columns: 1fr;
  place-items: stretch;
}
.spread.title .plate {
  grid-area: 1 / 1;
  /* The cover is full-bleed and the title sits on it. The 56svh cap that
   * keeps an ordinary spread's picture from crowding out its text would put
   * a band of empty paper under the cover instead. */
  max-height: none;
}
.spread.title .titling {
  grid-area: 1 / 1;
  z-index: 2;
  align-self: end;
  padding: var(--page-pad);
  padding-bottom: max(var(--page-pad), env(safe-area-inset-bottom));
  background: linear-gradient(transparent, var(--paper) 62%);
}
.titling h1 {
  font-size: clamp(2.2rem, 9vw, 4.4rem);
  line-height: 1.02;
  margin: 0 0 0.3em;
  font-weight: 600;
  letter-spacing: -0.015em;
  max-width: 14ch;
}
.titling .kicker {
  font-family: var(--sans);
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 0.9em;
}
.titling .blurb {
  margin: 0 0 1.2em;
  color: var(--ink-soft);
  max-width: 30rem;
  font-size: 1.02rem;
}

/* A hint that there is more below, which fades the moment the reader moves. */
.nudge {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--sans);
  font-size: 0.78rem;
  color: var(--ink-soft);
  animation: breathe 3.2s ease-in-out infinite;
}
@keyframes breathe {
  0%, 100% { opacity: 0.55; transform: translateY(0); }
  50% { opacity: 1; transform: translateY(3px); }
}
@media (prefers-reduced-motion: reduce) {
  .nudge { animation: none; opacity: 0.8; }
}
body.moved .nudge { opacity: 0; transition: opacity 0.4s ease; }

/* ------------------------------------------------------------- the sentence
 * being read aloud. Highlighting the current sentence is the one thing that
 * makes read-aloud useful rather than decorative, for a struggling reader
 * and for a confident one following at speed. */
.words .lit {
  background: color-mix(in oklab, var(--accent) 20%, transparent);
  border-radius: 3px;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* ------------------------------------------------------------- the afterword
 * The last spread of every book, and the part that makes this educational
 * rather than merely nice. It is addressed to whoever is sitting alongside —
 * a teacher, a parent, or the reader themselves an hour later. */
.spread.after {
  grid-template-rows: 1fr;
  grid-template-columns: 1fr;
  height: auto;
  min-height: 100svh;
  overflow-y: auto;
  scroll-snap-align: start;
}
.after .sheet {
  max-width: 40rem;
  margin-inline: auto;
  padding: clamp(46px, 9vh, 96px) var(--page-pad)
    max(56px, env(safe-area-inset-bottom));
}
.after h2 {
  font-size: clamp(1.5rem, 5vw, 2rem);
  margin: 0 0 0.1em;
  font-weight: 600;
}
.after .sub {
  font-family: var(--sans);
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 2em;
}
.after h3 {
  font-family: var(--sans);
  font-size: 0.74rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 2.4em 0 0.7em;
  padding-top: 0.9em;
  border-top: 1px solid var(--rule);
}
.after ol, .after ul { padding-left: 1.15em; margin: 0; }
.after li { margin-bottom: 0.7em; }
.after .quiet { color: var(--ink-soft); font-size: 0.94em; }

/* The questions are the point of the afterword. They are numbered because a
 * teacher will use them in order, and they never have an answer printed
 * under them, because none of them has one. */
.after ol.questions { list-style: decimal; }
.after ol.questions li { margin-bottom: 1em; }

.tags { display: flex; flex-wrap: wrap; gap: 7px; margin: 0.4em 0 0; padding: 0; list-style: none; }
.tags li {
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
  border: 1px solid var(--rule);
  border-radius: 999px;
  padding: 5px 11px;
  margin: 0;
}

.after .next {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-top: 2.6em;
  font-family: var(--sans);
  font-size: 0.88rem;
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 4px;
}
.after .next:hover { border-color: var(--accent); color: var(--accent); }

/* ------------------------------------------------------------------- print
 *
 * Teachers print. There are no image files on this site to fall back on, so
 * if printing is left to chance a class set arrives as twelve blank boxes.
 *
 * Four things have to be undone: the scroller (a fixed-height snapping
 * viewport prints as one page), the dark palette (a dark theme prints as a
 * sheet of ink), the blend modes and grain (they render as mud on paper),
 * and the chrome. What is left is a plate and its words per sheet, which is
 * what a handout wants. */
@media print {
  :root, :root[data-theme="dark"] {
    --paper: #fff; --paper-deep: #fff; --ink: #000; --ink-soft: #333;
    --rule: #bbb; --p-paper: #fff; --p-ink: #000;
    --p-blend: normal; --p-grain-a: 0;
  }
  body { background: #fff; font-size: 11.5pt; }
  .bar, .progress, .nudge, #speak, #ease { display: none !important; }

  .reader { height: auto; overflow: visible; scroll-snap-type: none; }
  .spread {
    height: auto;
    min-height: 0;
    display: block;
    overflow: visible;
    break-inside: avoid;
    break-after: page;
    padding-bottom: 1.5em;
  }
  .spread:last-child { break-after: auto; }
  .spread > .plate,
  .spread.title .plate {
    max-height: none;
    height: 9.5cm;
    break-inside: avoid;
    border: 0.5pt solid #ccc;
  }
  .plate::after { display: none; }
  .words, .after .sheet { max-width: none; padding: 1em 0 0; }
  .words .inner { display: block; max-width: none; }
  .spread.title .titling { background: none; padding: 1em 0 0; position: static; }
  .spread.title { display: block; }

  /* An afterword is a worksheet and wants to start on its own sheet. */
  .spread.after { break-before: page; }
  .after ol.questions li { break-inside: avoid; }

  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 8pt; color: #555; }
}

/* The four-year ladder.
 *
 * The same story, one rung per school year, each with the descriptor it
 * answers and the job that year is actually being asked to do. It is the part
 * a head of department reads, so the codes are set apart and quiet and the
 * work is set in the body face — the job matters more than the code. */
.after ol.ladder {
  list-style: none;
  padding: 0;
  counter-reset: rung;
}
.after ol.ladder li {
  position: relative;
  margin: 0 0 1.4em;
  padding: 0 0 1.3em;
  border-bottom: 1px solid var(--rule);
}
.after ol.ladder li:last-child { border-bottom: 0; padding-bottom: 0; }
.after ol.ladder b {
  font-family: var(--sans);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.after ol.ladder .code {
  font-family: var(--sans);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: 2px 6px;
  margin-left: 8px;
  white-space: nowrap;
}
.after ol.ladder .what {
  display: block;
  color: var(--ink-soft);
  font-style: italic;
  margin: 0.45em 0 0.35em;
  font-size: 0.95em;
}
