/* Livebook Scroll Reader — Warm Parchment Aesthetic */
@import url('https://fonts.googleapis.com/css2?family=Cinzel+Decorative:wght@700;900&family=EB+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&family=IM+Fell+English:ital@1&family=Inter:wght@400;500;600;700&family=Literata:ital,opsz,wght@0,7..72,400;0,7..72,600;1,7..72,400&family=Lora:ital,wght@0,400;0,500;0,600;1,400;1,500&family=MedievalSharp&family=Pirata+One&family=UnifrakturCook:wght@700&family=UnifrakturMaguntia&display=swap');

/* Bookerly Typeface */
@font-face {
  font-family: 'Bookerly';
  src: local('Bookerly'),
    url('../fonts/Bookerly-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Bookerly';
  src: local('Bookerly Italic'),
    url('../fonts/Bookerly-RegularItalic.ttf') format('truetype');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: 'Bookerly';
  src: local('Bookerly Bold'),
    url('../fonts/Bookerly-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Bookerly';
  src: local('Bookerly Bold Italic'),
    url('../fonts/Bookerly-BoldItalic.ttf') format('truetype');
  font-weight: 700;
  font-style: italic;
  font-display: swap;
}

:root {
  /* Paper colour: a soft warm off-white. The paper picker (?paper=pick,
     src_js/app.js) can override it per browser. Everything paper-coloured
     derives from --bg-primary. */
  --bg-primary: #F4EFE6;
  --bg-surface: color-mix(in srgb, var(--bg-primary) 95%, transparent);
  /* Strength of the paper overlay (body::after), 0-1: 30% of the full grain. */
  --paper-texture: 0.3;
  --border-color: rgba(42, 32, 24, 0.08);
  --text-primary: #2A2622;
  --text-secondary: #5C5852;
  --accent-crimson: #8F2D30;
  --accent-green: #1A382B;
  --accent-green-hover: #244C3B;
  --accent-gold: #DFBA73;
  --accent-gold-light: #F4E2B8;
  --accent-gold-dark: #B8934A;
  --btn-bg: #E9E0CE;
  --btn-hover: #DFD5C1;
  --btn-disabled: rgba(234, 229, 220, 0.6);
  --header-height: 44px;
  /* Mobile auto-hiding header (the script toggles body.header-hidden): how far
     down the sticky video sits, and the shared slide timing. */
  --header-offset: var(--header-height);
  --header-slide: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  --crossfade-duration: 0.8s;
  --ink-color: #342A22;
  /* Illustrations use walnut ink #2a2018 at 95% baked into the canvas LUT; no softening filter */
  --ink-filter: none;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  -webkit-user-select: none !important;
  -moz-user-select: none !important;
  -ms-user-select: none !important;
  user-select: none !important;
  -webkit-touch-callout: none !important;
}

img,
canvas,
svg {
  -webkit-user-drag: none !important;
  user-drag: none !important;
}

::selection {
  background: transparent !important;
  color: inherit !important;
}

::-moz-selection {
  background: transparent !important;
  color: inherit !important;
}

html {
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
}

body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  background-color: var(--bg-primary);
  color: var(--text-primary);
  /* Size to the *visible* viewport on mobile. Plain 100vh is the toolbar-hidden
     height, so while the URL bar shows, the page overflows and mobile browsers
     shift it up, sliding the top of the scene canvas under the fixed header. */
  height: 100vh;
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  overscroll-behavior: none;
  -webkit-user-select: none !important;
  -moz-user-select: none !important;
  -ms-user-select: none !important;
  user-select: none !important;
  -webkit-touch-callout: none !important;
}

/* ===== PAPER =====
   The reading page is a sheet of book paper: one fixed overlay laid over
   everything (text, illustration, header) so the ink of both the type and the
   animated art sits *in* the paper, the way a printed plate does. Layers, top
   first: page toning toward the edges, fine tooth and fibres, and the sheet's
   cloudy formation. The two textures are seamless tiles with their own alpha
   (brown where the paper is darker, warm white where it is lighter; built by
   scripts/make_paper_texture.py) and repeat at different periods so no repeat
   shows. They work on any paper colour (--bg-primary); --paper-texture scales
   the whole overlay. Plain alpha compositing, no blend modes, and its own
   layer, so scrolling never repaints it. Under the book menu (z-index 200) so
   that stays a clean card. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 150;
  pointer-events: none;
  opacity: var(--paper-texture, 1);
  background:
    radial-gradient(62% 70% at 50% 50%,
      transparent 55%,
      rgba(88, 62, 34, 0.025) 75%,
      rgba(88, 62, 34, 0.075) 95%,
      rgba(88, 62, 34, 0.16) 120%),
    url('../images/paper-grain.webp') 0 0 / 256px 256px repeat,
    url('../images/paper-mottle.webp') 0 0 / 900px 900px repeat;
  transform: translateZ(0);
}

/* Plain, flat paper for readers who ask for more contrast. */
@media (prefers-contrast: more), (forced-colors: active) {
  body::after {
    display: none;
  }
}

/* Paper picker (?paper=pick): choose the page colour and texture strength
   live. Above the paper overlay so it stays a clean control. */
.paper-picker {
  position: fixed;
  left: 16px;
  bottom: 16px;
  z-index: 300;
  width: 236px;
  padding: 12px 14px 14px;
  border: 1px solid rgba(42, 32, 24, 0.18);
  border-radius: 10px;
  background: #fff;
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.14);
  font: 13px/1.35 'Inter', -apple-system, sans-serif;
  color: #2A2622;
  -webkit-user-select: auto !important;
  user-select: auto !important;
}

.paper-picker-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
  font-weight: 600;
}

.paper-picker-close {
  border: 0;
  background: none;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  color: inherit;
}

.paper-picker-presets {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  margin-bottom: 10px;
}

.paper-picker-preset {
  padding: 6px 4px 5px;
  border: 1px solid rgba(42, 32, 24, 0.2);
  border-radius: 6px;
  background: #fff;
  font: inherit;
  font-size: 11px;
  cursor: pointer;
  color: inherit;
}

.paper-picker-preset[aria-pressed="true"] {
  border-color: #2A2622;
  box-shadow: inset 0 0 0 1px #2A2622;
}

.paper-picker-swatch {
  display: block;
  height: 18px;
  margin-bottom: 4px;
  border-radius: 3px;
  border: 1px solid rgba(42, 32, 24, 0.15);
}

.paper-picker-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}

.paper-picker-row label {
  flex: 0 0 58px;
}

.paper-picker-row input[type="color"] {
  width: 40px;
  height: 26px;
  padding: 0;
  border: 1px solid rgba(42, 32, 24, 0.2);
  border-radius: 4px;
  background: none;
  cursor: pointer;
}

.paper-picker-row input[type="range"] {
  flex: 1;
  min-width: 0;
}

.paper-picker-value {
  font-variant-numeric: tabular-nums;
  min-width: 58px;
  text-align: right;
  color: #5C5852;
}

/* ===== HEADER ===== */
.app-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--header-height);
  display: flex;
  justify-content: center;
  /* Items sit toward the bottom of the header (closer to the text) rather
     than vertically centred. No bottom padding: the logo image and the title
     button already leave ~8px of space below their glyphs. */
  align-items: flex-end;
  padding: 0 24px;
  z-index: 100;
  background: var(--bg-surface);
  backdrop-filter: blur(12px);
  transition: transform var(--header-slide);
}

/* ===== BOOK SELECTOR DROPDOWN ===== */
.book-selector {
  position: relative;
  min-width: 0;
  flex: 0 1 auto;
}

/* Keep a gap between the logo and the (centered) book title. */
#logo-link {
  margin-right: 12px;
}

#logo-img {
  height: 32px;
  width: auto;
  object-fit: contain;
}

/* The right-hand column may shrink (the chapter truncates) so the book title
   stays centred and keeps its fixed size. */
.header-right {
  min-width: 0;
}

.book-selector-btn {
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 100%;
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px 10px;
  border-radius: 8px;
  transition: background 0.2s ease;
}

/* Header chapter and page: one family and size, uppercase with light
   tracking. */
.header-meta {
  font-family: 'EB Garamond', Georgia, serif;
  font-size: 15px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  white-space: nowrap;
  line-height: 1.5;
}

/* Book title: each book's dropdown font and weight (copied from its
   .book-option-title by the script), mixed case like the dropdown, at one fixed
   size for every book. The size is mirrored in the script,
   which only steps it down on screens too narrow for the longest title. The
   line height is fixed in px so the title doesn't move vertically. */
.book-selector-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: 'Pirata One', 'UnifrakturMaguntia', Georgia, serif;
  font-size: 15px;
  line-height: 22.5px;
  font-weight: 500;
  color: var(--text-primary);
}

/* Chapter · Page N of M, bottom-aligned with the title (the title button has
   4px vertical padding). */
.header-meta {
  display: flex;
  align-items: baseline;
  min-width: 0;
  padding: 4px 0;
  font-weight: 500;
  color: var(--text-secondary);
  font-variant-numeric: lining-nums;
  /* 12px: the small-label step of the reading scale (19px body); 11px read as
     an afterthought next to the larger text. */
  font-size: 12px;
}

.header-meta[hidden] {
  display: none;
}

.header-chapter {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.header-page {
  flex-shrink: 0;
}

/* The separator sits on the page field so a truncated chapter doesn't clip it. */
.header-chapter:not(:empty)+.header-page::before {
  content: '\00B7';
  margin: 0 0.6em;
}

@media (max-width: 600px) {

  /* Phones: same family, but 13px, mixed case and no tracking (uppercase is
     too wide here), and just "p. N" so the book title keeps its room. */
  .header-meta {
    font-size: 13px;
    text-transform: none;
    letter-spacing: 0.01em;
  }

  /* Smaller logo and title so the longest title fits, centred, at 360px. */
  #logo-img {
    height: 20px;
    /* Optical alignment: the wordmark's lettering sits low in the PNG, so it
       is raised until its letter middles line up with the title and "p. N"
       (measured from screenshots: logo was ~4.5px low, title ~1.75px high). */
    transform: translateY(-4.5px);
  }

  #logo-link {
    margin-right: 10px;
  }

  .book-selector-btn {
    gap: 4px;
    padding: 4px;
    transform: translateY(1.75px);
  }

  .book-selector-btn .book-selector-chevron {
    width: 16px;
    height: 16px;
  }

  .book-selector-title {
    font-size: 11.5px;
    line-height: 19.5px;
  }

  .header-chapter:not(:empty)+.header-page::before {
    content: none;
  }

  .header-chapter,
  .header-page-label,
  .header-page-of {
    display: none;
  }

  #header-page-num::before {
    content: 'p.\00A0';
  }
}

@media (max-width: 480px) {
  .app-header {
    padding-left: 14px;
    padding-right: 14px;
  }
}

.book-selector-btn:hover {
  background: none;
}

.book-selector-chevron {
  /* Overrides the 14px width/height attributes in index.html. The arrow is
     drawn in the middle half of the box, so the visible mark is ~10x5px. */
  width: 20px;
  height: 20px;
  color: var(--text-primary);
  transition: transform 0.25s ease;
  flex-shrink: 0;
}

.book-selector.open .book-selector-chevron {
  transform: rotate(180deg);
}

.book-selector-dropdown {
  display: none;
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  min-width: 320px;
  background: var(--bg-primary);
  border: 1px solid var(--border-color);
  border-radius: 12px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.06);
  list-style: none;
  padding: 6px;
  z-index: 200;
  opacity: 0;
  transform: translateX(-50%) translateY(-4px);
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.book-selector.open .book-selector-dropdown {
  display: block;
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.book-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.1s ease;
}

.book-option:hover {
  background: rgba(0, 0, 0, 0.04);
}

.book-option.active {
  background: rgba(43, 43, 43, 0.07);
}

.book-option.active .book-option-title {
  color: var(--accent-green);
  font-weight: 500;
}

.book-option.disabled {
  opacity: 0.5;
  pointer-events: none;
}

.book-option-title {
  font-family: 'Pirata One', 'UnifrakturMaguntia', Georgia, serif;
  font-size: 16px;
  font-weight: 400;
  color: var(--text-primary);
  white-space: nowrap;
}

.book-option-author {
  font-family: 'Inter', sans-serif;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-secondary);
  white-space: nowrap;
  margin-left: auto;
}

.book-option-badge {
  font-family: 'Inter', sans-serif;
  font-size: 8.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent-gold-light);
  background: linear-gradient(135deg, #1A382B, #11261D);
  box-shadow: inset 0 0 0 1px rgba(223, 186, 115, 0.28);
  padding: 2px 7px;
  border-radius: 4px;
  white-space: nowrap;
  flex-shrink: 0;
}

/* ===== SCROLL READER ===== */
.scroll-reader {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  margin-top: var(--header-height);
  scroll-behavior: smooth;
  /* Keep scrolling inside the reader; never chain to (and shift) the page. */
  overscroll-behavior-y: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(120, 111, 101, 0.3) transparent;
}

.scroll-reader::-webkit-scrollbar {
  width: 6px;
}

.scroll-reader::-webkit-scrollbar-track {
  background: transparent;
}

.scroll-reader::-webkit-scrollbar-thumb {
  background: rgba(120, 111, 101, 0.25);
  border-radius: 10px;
}

.scroll-reader::-webkit-scrollbar-thumb:hover {
  background: rgba(120, 111, 101, 0.45);
}

/* ===== READER LAYOUT: sticky left + scrolling center + widgets right ===== */
:root {
  /* Gap between video | text | margin icons (the same on both sides of the text). */
  --reader-gap: clamp(32px, 4vw, 56px);
}

.reader-layout {
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: flex-start;
  max-width: 1560px;
  margin: 0 auto;
  padding: 0 clamp(20px, 3vw, 48px);
  gap: var(--reader-gap);
}

/* ===== STICKY CANVAS COLUMN ===== */
/* Sticky and as tall as the visible reading area (below the header), with the
   illustration flex-centred in it: equal paper above and below the framed art. */
.sticky-canvas-col {
  flex-shrink: 0;
  position: sticky;
  top: 0;
  align-self: flex-start;
  height: calc(100vh - var(--header-height));
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Captioned plate in a woodcut band, after the black-line borders round Bewick
   and Dore wood-engraved prints: from the art outward a paper gap, a fine rule,
   a gap, a solid ink band, a gap and a second fine rule, with the scene title
   set in italic beneath (#scene-caption, filled by the script). The frame is
   stacked box-shadow spreads on a pseudo-element (not padding/border on the
   container) so the canvases keep their exact pixel size and crisp scaling.
   Desktop: 8px gap, 1px rule, 3px, 6px band, 3px, 1px rule = 22px deep.
   Phones use a slimmer 14px band (see the max-width: 900px block). */
.crossfade-container {
  --frame-in: 4px;   /* transparent gap the pseudo-element itself adds */
  --frame-gap: 4px;  /* paper between that and the inner rule */
  --frame-rule: 1px;
  --frame-space: 3px;
  --frame-band: 6px;
}

.crossfade-container::after {
  content: '';
  position: absolute;
  inset: calc(-1 * var(--frame-in));
  pointer-events: none;
  /* Rules in the illustrations' own ink: walnut #2a2018 at 95% on the paper
     renders as --ink-color (#342A22), so frame and art read as one print. */
  box-shadow:
    0 0 0 var(--frame-gap) var(--bg-primary),
    0 0 0 calc(var(--frame-gap) + var(--frame-rule)) var(--ink-color),
    0 0 0 calc(var(--frame-gap) + var(--frame-rule) + var(--frame-space)) var(--bg-primary),
    0 0 0 calc(var(--frame-gap) + var(--frame-rule) + var(--frame-space) + var(--frame-band)) var(--ink-color),
    0 0 0 calc(var(--frame-gap) + var(--frame-rule) + 2 * var(--frame-space) + var(--frame-band)) var(--bg-primary),
    0 0 0 calc(var(--frame-gap) + 2 * var(--frame-rule) + 2 * var(--frame-space) + var(--frame-band)) var(--ink-color);
}

.scene-caption {
  position: absolute;
  top: calc(100% + 36px);
  left: 0;
  right: 0;
  margin: 0;
  text-align: center;
  text-wrap: balance;
  font-family: 'IM Fell English', Georgia, serif;
  font-style: italic;
  font-weight: 400;
  /* Fell's small x-height reads ~2px smaller than Literata, so 18px here
     sits at the type scale's "notes" step (~16px Literata). */
  font-size: 18px;
  line-height: 1.3;
  letter-spacing: 0.005em;
  color: #5A4638;
  transition: opacity 0.25s ease;
}

.scene-caption.is-fading {
  opacity: 0;
}

/* The title cross-fades on its own span. */
.scene-caption-text {
  transition: opacity 0.25s ease;
}

.scene-caption-text.is-fading {
  opacity: 0;
}

@media (min-width: 901px) {
  /* Room for the frame + caption under the art, so they centre as one unit,
     and for the 22px frame at the sides so it never crowds the window edge
     or the text column. */
  .sticky-canvas-col {
    padding-bottom: 62px;
    padding-inline: 22px;
  }
}

/* Container for the crossfade — two canvases stacked absolutely */
.crossfade-container {
  position: relative;
  /* Native size: one CSS px per scene pixel (360x640 standard, 472x707 for
     2:3; --scene-h / --scene-aspect are set per scene by the script).
     Never upscaled beyond native -- that blurred the art. Capped at 70vh /
     460px so the picture stays calm beside the text; when that cap makes the
     scale non-integer, the script adds .smooth-scale (see below).
     Before the first scene the box takes the shape remembered for this book
     (--boot-scene-*, set by the head script in index.html), else 2:3 at
     472x707, the shape of every live book, so it never reshapes on load. */
  height: min(calc(var(--scene-h, var(--boot-scene-h, 707)) * 1px), 70vh);
  aspect-ratio: var(--scene-aspect, var(--boot-scene-aspect, 2 / 3));
  transition:
    aspect-ratio var(--crossfade-duration) cubic-bezier(0.4, 0, 0.2, 1),
    height var(--crossfade-duration) cubic-bezier(0.4, 0, 0.2, 1),
    opacity 0.25s ease;
  max-width: min(48vw, 460px);
  /* Vignette reduced to 0% (no edge masking) */
  -webkit-mask-image: none !important;
  mask-image: none !important;
  -webkit-mask: none !important;
  mask: none !important;
}

.crossfade-container canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  /* Scenes keep their own aspect (e.g. 2:3 clips are letterboxed, not stretched) */
  object-fit: contain;
  /* Crisp-edge scaling: eliminates bilinear blur and soft halos for sharp, strain-free ink linework */
  image-rendering: -webkit-optimize-contrast;
  image-rendering: crisp-edges;
  image-rendering: pixelated;
  border: none;
  outline: none;
  border-radius: 0;
  -webkit-mask-image: none !important;
  mask-image: none !important;
  -webkit-mask: none !important;
  mask: none !important;
  /* Optional ink-tone filter hook (currently none; ink colour #2a2018 is set in the JS LUT) */
  filter: var(--ink-filter);
  /* Cinematic crossfade transition */
  transition: opacity var(--crossfade-duration) cubic-bezier(0.4, 0, 0.2, 1);
  will-change: opacity, filter;
}

.crossfade-container video,
.sticky-canvas-col video {
  filter: var(--ink-filter);
}

/* When the box is not an exact integer multiple of the scene's pixels (e.g. capped
   below native size), nearest-neighbour scaling draws lines 1 or 2 px wide unevenly
   and they shimmer in motion; use smooth scaling instead (toggled by the script). */
.crossfade-container.smooth-scale canvas {
  image-rendering: auto;
}

/* Layer A is the "back" canvas, Layer B on top */
.crossfade-container .canvas-layer-a {
  z-index: 1;
  opacity: 1;
}

.crossfade-container .canvas-layer-b {
  z-index: 2;
  opacity: 0;
}

/* Active state: whichever layer is active gets opacity 1 */
.crossfade-container .canvas-layer-a.active {
  opacity: 1;
}

.crossfade-container .canvas-layer-a:not(.active) {
  opacity: 0;
}

.crossfade-container .canvas-layer-b.active {
  opacity: 1;
}

.crossfade-container .canvas-layer-b:not(.active) {
  opacity: 0;
}

/* Page-open reveal: the head script in index.html adds html.lb-boot, and
   html.lb-ready once the first picture (opening still or scene) is drawn, or
   after 2.5s at the latest. Until then the frame, caption and text stay
   hidden, so they appear together in one fade instead of popping in one by
   one (empty frame, then text, then art). */
html.lb-boot .text-scroll-col {
  transition: opacity 0.25s ease;
}

html.lb-boot:not(.lb-ready) .crossfade-container,
html.lb-boot:not(.lb-ready) .text-scroll-col {
  opacity: 0;
}

/* The margin icons too, beside the text. Not on phones: there they sit in the
   header, and an opacity layer would stack them under it mid-fade. */
@media (min-width: 601px) {
  html.lb-boot .margin-icons-col {
    transition: opacity 0.25s ease;
  }

  html.lb-boot:not(.lb-ready) .margin-icons-col {
    opacity: 0;
  }
}

/* ===== TEXT COLUMN (scrolls naturally, elegant book measure) ===== */
.text-scroll-col {
  /* Measure: 55ch of the reading font (Literata at 19px), about 65 characters
     per line, the most comfortable length for continuous reading. The column
     carries the same font as .scene-text-body for `ch` to resolve against it
     (?type=v2 uses 34em). */
  font-family: 'Literata', 'Newsreader', 'EB Garamond', Georgia, serif;
  font-size: 1.1875rem;
  --measure: 55ch;
  flex: 0 1 var(--measure);
  width: 100%;
  max-width: var(--measure);
  min-width: 280px;
  padding: 48px 0 calc(50vh + 60px);
}

/* Desktop text band: text fades out over one line just below the header and
   just above the bottom of the page. The mask sits on the scroll container, so it
   stays put while the text scrolls beneath it. Two layers are unioned: the
   vertical band, and everything outside the text column's horizontal range
   (so the video, margin icons and scrollbar are never masked). the script
   (updateTextBand) sets the --tb-* lengths, in px relative to the reader. */
@media (min-width: 901px) {
  .scroll-reader.text-band {
    /* Vertical band with a short fade over --tb-fade (24px) at each edge:
       fully transparent at --tb-top / --tb-bottom (8px from the header / the
       page bottom), fully opaque --tb-fade further in. The curve stays
       dark for most of the fade (100% → 90% → 75% → 40% → 0%) so the text
       keeps its contrast until the very end. The top fade uses
       --tb-fade-top, which the script grows from 0 over the first lines of
       scrolling so the book's opening isn't dimmed. */
    --tb-band: linear-gradient(to bottom,
        transparent var(--tb-top),
        rgba(0, 0, 0, 0.2) calc(var(--tb-top) + var(--tb-fade-top, var(--tb-fade)) * 0.125),
        rgba(0, 0, 0, 0.4) calc(var(--tb-top) + var(--tb-fade-top, var(--tb-fade)) * 0.25),
        rgba(0, 0, 0, 0.75) calc(var(--tb-top) + var(--tb-fade-top, var(--tb-fade)) * 0.5),
        rgba(0, 0, 0, 0.9) calc(var(--tb-top) + var(--tb-fade-top, var(--tb-fade)) * 0.75),
        rgba(0, 0, 0, 0.97) calc(var(--tb-top) + var(--tb-fade-top, var(--tb-fade)) * 0.875),
        #000 calc(var(--tb-top) + var(--tb-fade-top, var(--tb-fade))),
        #000 calc(var(--tb-bottom) - var(--tb-fade)),
        rgba(0, 0, 0, 0.97) calc(var(--tb-bottom) - var(--tb-fade) * 0.875),
        rgba(0, 0, 0, 0.9) calc(var(--tb-bottom) - var(--tb-fade) * 0.75),
        rgba(0, 0, 0, 0.75) calc(var(--tb-bottom) - var(--tb-fade) * 0.5),
        rgba(0, 0, 0, 0.4) calc(var(--tb-bottom) - var(--tb-fade) * 0.25),
        rgba(0, 0, 0, 0.2) calc(var(--tb-bottom) - var(--tb-fade) * 0.125),
        transparent var(--tb-bottom));
    --tb-outside-col: linear-gradient(to right, #000 var(--tb-left), transparent var(--tb-left), transparent var(--tb-right), #000 var(--tb-right));
    -webkit-mask-image: var(--tb-band), var(--tb-outside-col);
    mask-image: var(--tb-band), var(--tb-outside-col);
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
    -webkit-mask-composite: source-over;
    mask-composite: add;
  }

  /* Start the book's first line inside the band (text above it could never be
     scrolled into view). */
  .scroll-reader.text-band .text-scroll-col {
    padding-top: max(48px, var(--tb-pad-top, 48px));
  }
}

/* ===== SCENE TEXT BLOCK ===== */
.scene-text-block {
  padding-bottom: 0;
  position: relative;
}

.scene-text-block:last-child,
.text-scroll-col>.scene-text-body:last-child {
  padding-bottom: 24px;
}

/* ===== MARGIN ICONS COLUMN (sticky right column, equispaced) ===== */
.margin-icons-col {
  flex-shrink: 0;
  width: 36px;
  position: sticky;
  top: 48px;
  align-self: flex-start;
  display: flex;
  justify-content: center;
  z-index: 10;
  /* The flex gap (--reader-gap) separates the icons from the text, matching the
     gap between the video and the text. The 21-24px icons are centred in this
     36px column and their strokes are inset in their viewBox, so the visible
     shapes start ~8px inside the column; pull it in by that much so the
     *visible* gap equals --reader-gap. */
  margin-left: -8px;
}

.margin-icons {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  padding: 4px 0;
}

.bookmark-indicator {
  width: 24px;
  height: 24px;
  color: var(--text-primary);
  pointer-events: none;
}

/* Antique Gold pulse on scene crossing */
@keyframes bookmark-pulse {
  0% {
    color: var(--text-primary);
  }

  50% {
    color: var(--accent-gold);
  }

  100% {
    color: var(--text-primary);
  }
}

.bookmark-indicator.pulse {
  animation: bookmark-pulse 0.8s ease-in-out;
}

/* Heart "like" button */
.like-heart {
  --heart-pink: #FF2D5F;
  width: 22px;
  height: 22px;
  color: var(--text-primary);
  cursor: pointer;
  pointer-events: auto;
  overflow: visible;
  transform-box: fill-box;
  transform-origin: center;
  transition: color 0.2s ease, fill 0.2s ease, filter 0.25s ease;
}

@media (hover: hover) {
  .like-heart:hover {
    color: var(--heart-pink);
  }
}

/* Liked: stays filled in a bright pinkish red with a soft glow. */
.like-heart.liked {
  color: var(--heart-pink);
  fill: var(--heart-pink);
  filter: drop-shadow(0 0 2px rgba(255, 45, 95, 0.85)) drop-shadow(0 0 7px rgba(255, 45, 95, 0.55));
}

/* Brief pop when a heart is first liked */
@keyframes heart-pop {
  0% {
    transform: scale(1);
  }

  40% {
    transform: scale(1.28);
  }

  70% {
    transform: scale(0.94);
  }

  100% {
    transform: scale(1);
  }
}

.like-heart.liked.pop {
  animation: heart-pop 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
}

@media (prefers-reduced-motion: reduce) {
  .like-heart.liked.pop {
    animation: none;
  }
}

/* Share button */
.share-btn {
  width: 21px;
  height: 21px;
  color: var(--text-primary);
  cursor: pointer;
  pointer-events: auto;
  transition: color 0.2s ease;
}

.share-btn:hover {
  color: var(--accent-gold);
}

/* ===== BOOK FORMATTER TYPOGRAPHY (Standard Ebooks / Fine Press Aesthetic) ===== */
.scene-text-body {
  font-family: 'Literata', 'Newsreader', 'EB Garamond', Georgia, serif;
  font-size: 1.1875rem; /* ~19px */
  font-weight: 400;
  font-style: normal;
  font-optical-sizing: auto;
  line-height: 1.72;
  /* Soft warm charcoal instead of harsh black */
  color: #26211D;
  /* OpenType book polish */
  font-kerning: normal;
  font-variant-ligatures: common-ligatures contextual;
  font-variant-numeric: oldstyle-nums proportional-nums;
  font-feature-settings: normal;
  /* Default (not antialiased) smoothing: on macOS antialiased thins the strokes
     and lowers the effective contrast of dark text on paper. */
  hanging-punctuation: first;
  /* Ragged right without hyphenation: whole words read more easily than
     words split across lines, and the rag is even with text-wrap: pretty. */
  text-align: left;
  hyphens: manual;
  -webkit-hyphens: manual;
}

/* ── Prose Paragraphs: classic book flow, 1.75em indents, with a small gap between paragraphs ── */
.scene-text-body .bf-paragraph {
  margin: 0;
  padding: 0;
  text-indent: 1.75em;
  text-align: left;
  line-height: 1.72;
  orphans: 2;
  widows: 2;
  /* Balanced rag; never a single short word alone on a paragraph's last line */
  text-wrap: pretty;
  position: relative;
}

/* Breathing room between paragraphs (~14px at the 19px body), in em so it
   scales with the phone size too. Indents stay, so each paragraph still opens
   like a book's. Each scene is its own .scene-text-body, so a paragraph that
   opens a scene mid-chapter gets the same gap (chapter openings excepted). */
.scene-text-body .bf-paragraph+.bf-paragraph,
.scene-text-body p:not([class])+p:not([class]),
.scene-text-body>.bf-paragraph:first-child:not(.bf-first-p) {
  margin-top: 0.75em;
}

/* No indent on the first paragraph of a chapter or after a heading / break.
   (Not :first-child: each scene is its own .scene-text-body, so that would
   unindent every scene's opening paragraph mid-chapter. The formatter marks
   true openings with .bf-first-p.) */
.scene-text-body .bf-paragraph.bf-first-p,
.scene-text-body .bf-chapter-heading+.bf-paragraph,
.scene-text-body .bf-part-heading+.bf-paragraph,
.scene-text-body .bf-title-page+.bf-paragraph,
.scene-text-body .bf-thematic-break+.bf-paragraph,
.scene-text-body .bf-verse+.bf-paragraph,
.scene-text-body .bf-letter+.bf-paragraph,
.scene-text-body .bf-epigraph+.bf-paragraph,
.scene-text-body .bf-illustration-note+.bf-paragraph,
.scene-text-body .bf-toc+.bf-paragraph {
  text-indent: 0 !important;
}

/* Small gap (0.11 line-height; 0.19em fallback) after standalone blocks */
.scene-text-body .bf-thematic-break+.bf-paragraph,
.scene-text-body .bf-verse+.bf-paragraph,
.scene-text-body .bf-letter+.bf-paragraph,
.scene-text-body .bf-epigraph+.bf-paragraph,
.scene-text-body .bf-illustration-note+.bf-paragraph {
  margin-top: 0.19em;
  margin-top: 0.11lh;
}

/* Legacy fallback for non-formatter paragraphs */
.scene-text-body p:not([class]) {
  margin: 0;
  text-indent: 1.75em;
  text-align: left;
  line-height: 1.72;
}

.scene-text-body p:not([class]):first-child {
  text-indent: 0;
}

/* ── Comparison variant (?type=v2): experimental reading typography ──
   Literata (optical sizing), ragged right without hyphenation,
   line-height 1.6, 34em column (~65-70 characters per line). */
body.type-v2 .text-scroll-col {
  --measure: calc(clamp(16px, 1.18vw, 18.5px) * 34);
}

body.type-v2 .scene-text-body {
  font-family: 'Literata', 'EB Garamond', Georgia, serif;
  font-optical-sizing: auto;
  line-height: 1.6;
  hyphens: manual;
  -webkit-hyphens: manual;
}

body.type-v2 .scene-text-body .bf-paragraph,
body.type-v2 .scene-text-body p:not([class]) {
  text-align: start;
  text-justify: auto;
  line-height: 1.6;
}

/* ── Architectural Drop Cap with Small-Caps Lead-In ── */
.bf-drop-cap-p {
  text-indent: 0 !important;
  overflow: visible;
  position: relative;
}

.bf-drop-quote {
  font-family: 'Bookerly', 'Literata', 'EB Garamond', Georgia, serif;
  font-size: 2.2em;
  line-height: 0;
  float: left;
  margin-left: -0.45em;
  margin-right: 0.05em;
  color: #7d6550;
  position: relative;
  top: -0.1em;
  user-select: none;
}

/* Floated drop cap in the reading face (Literata 600), about two lines tall */
.bf-drop-cap {
  float: left;
  font-family: inherit;
  font-size: 3.6em;
  line-height: 0.8;
  padding-right: 0.12em;
  padding-top: 0.06em;
  color: #5A4638;
  font-weight: 600;
}

/* Small-caps lead-in. The formatter keeps the words' original case, so capitals
   stay full height and the rest set as small caps ("To Sherlock Holmes"). */
.bf-lead-in {
  font-variant: small-caps;
  letter-spacing: 0.06em;
  font-weight: 600;
}

/* ── Comparison variant (?type=lab): the text formatting from color-lab.html ──
   Ragged right, no hyphenation, default letter/word spacing and figures,
   1.4em indents with a small gap between paragraphs, plain opening words. */
body.type-lab .scene-text-body {
  font-family: 'Bookerly', 'EB Garamond', Georgia, serif;
  letter-spacing: normal;
  word-spacing: normal;
  font-feature-settings: normal;
  text-rendering: auto;
  hanging-punctuation: first;
  hyphens: manual;
  -webkit-hyphens: manual;
}

body.type-lab .scene-text-body .bf-paragraph,
body.type-lab .scene-text-body p:not([class]) {
  text-align: start;
  text-justify: auto;
  text-indent: 1.4em;
  margin-bottom: 0.2em;
  text-wrap: wrap;
}

body.type-lab .bf-lead-in {
  font-variant: normal;
  font-variant-caps: normal;
  letter-spacing: normal;
  font-size: inherit;
  font-weight: inherit;
  color: inherit;
}

body.type-lab .bf-drop-cap,
body.type-lab .bf-drop-quote {
  float: none;
  -webkit-initial-letter: normal;
  initial-letter: normal;
  font: inherit;
  line-height: inherit;
  margin: 0;
  padding: 0;
  top: 0;
  color: inherit;
  text-shadow: none;
}

/* ── Chapter Headings ──
   Type scale for everything in the text column, in em of the 19px body
   (17px on phones), ratio ~1.25: 0.74 labels · 0.84 notes · 1 body ·
   1.25 chapter titles · 1.4 story / part titles. Spacing is kept tight so a
   chapter opening doesn't push the first line halfway down the page. */
.bf-chapter-heading {
  text-align: center;
  padding: 1.3em 0 1.1em;
  margin: 1.2em 0 1em;
  position: relative;
}

.bf-chapter-heading::before,
.bf-chapter-heading::after {
  content: '';
  display: block;
  width: 38%;
  max-width: 150px;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(140, 115, 85, 0.45) 30%, rgba(140, 115, 85, 0.45) 70%, transparent);
  margin: 0 auto;
}

.bf-chapter-heading::before {
  margin-bottom: 0.9em;
}

.bf-chapter-heading::after {
  margin-top: 0.9em;
}

.bf-chapter-label {
  display: block;
  font-family: 'Cinzel Decorative', 'EB Garamond', Georgia, serif;
  font-size: 0.74em;
  font-weight: 700;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: #7d6550;
  margin-bottom: 0.45em;
}

.bf-chapter-title {
  display: block;
  font-family: 'Lora', 'EB Garamond', Georgia, serif;
  font-size: 1.25em;
  font-weight: 600;
  font-style: italic;
  color: #332418;
  line-height: 1.35;
  letter-spacing: 0.015em;
}

/* ── Story Titles (tales within a collection, e.g. "Mowgli's Brothers") ── */
.bf-story-heading {
  padding: 1.6em 0 0.6em;
  margin: 1.2em 0 0.6em;
}

/* A story title followed straight by its chapter label is one heading group. */
.bf-story-heading+.bf-chapter-heading {
  margin-top: 0;
  padding-top: 0.4em;
}

.bf-story-heading::before {
  width: 55%;
  max-width: 220px;
  margin-bottom: 1em;
}

/* No ornament below story titles (the ❦ fleuron was removed). */
.bf-story-heading::after {
  content: none;
}

.bf-story-heading .bf-chapter-title {
  font-family: 'Cinzel Decorative', 'EB Garamond', Georgia, serif;
  font-size: 1.4em;
  font-weight: 700;
  font-style: normal;
  letter-spacing: 0.04em;
  line-height: 1.3;
  color: #2b1e13;
}

.bf-story-heading .bf-chapter-title .bf-dialogue {
  color: inherit;
  font-style: inherit;
}

/* ── Part / Book / Volume Division Headings ── */
.bf-part-heading {
  text-align: center;
  padding: 2em 0 1.5em;
  margin: 1.8em 0 1.2em;
  border-top: 1px solid rgba(140, 115, 85, 0.28);
  border-bottom: 1px solid rgba(140, 115, 85, 0.28);
}

.bf-part-label {
  display: block;
  font-family: 'Cinzel Decorative', Georgia, serif;
  font-size: 0.74em;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: #6d5540;
  margin-bottom: 0.5em;
}

.bf-part-title {
  font-family: 'Lora', 'EB Garamond', Georgia, serif;
  font-size: 1.4em;
  font-weight: 700;
  font-style: italic;
  color: #2b1d12;
}

/* ── Title Pages & Front Matter ── */
.bf-title-page {
  text-align: center;
  padding: 3em 1em 2.5em;
  margin: 1.5em 0 2.5em;
  border-bottom: 1px double rgba(140, 115, 85, 0.4);
}

.bf-book-title {
  font-family: 'Cinzel Decorative', 'EB Garamond', Georgia, serif;
  font-size: clamp(1.3em, 2vw, 1.6em);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #2e2016;
  line-height: 1.4;
  margin-bottom: 1em;
}

.bf-book-author {
  font-family: 'Lora', 'EB Garamond', Georgia, serif;
  font-size: 1.1em;
  font-style: italic;
  color: #554436;
  margin-bottom: 0.8em;
}

.bf-book-edition {
  font-family: 'Inter', sans-serif;
  font-size: 0.68em;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #8c7664;
  margin-top: 1.4em;
}

/* ── Centered Title & Inscription Blocks ── */
.bf-title-block {
  text-align: center;
  padding: 1.8em 0;
  margin: 1.2em 0;
  font-family: 'Cinzel Decorative', 'EB Garamond', Georgia, serif;
  font-size: 0.95em;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #4a3d33;
  line-height: 1.6;
  font-weight: 700;
}

.bf-centered-block {
  text-align: center;
  padding: 1em 0;
  margin: 0.8em 0;
  font-family: 'Lora', 'EB Garamond', Georgia, serif;
  font-style: italic;
  font-size: 1em;
  color: #5a4d42;
  line-height: 1.55;
}

.bf-inscription {
  text-align: center;
  margin: 1.8em auto;
  padding: 0.9em 1.6em;
  max-width: 78%;
  border: 1px solid rgba(140, 115, 85, 0.35);
  background: color-mix(in srgb, var(--bg-primary), #fff 35%);
  border-radius: 4px;
  font-variant: small-caps;
  letter-spacing: 0.14em;
  line-height: 1.5;
  font-weight: 600;
  color: #3d2a1d;
}

/* ── Table of Contents ── */
.bf-toc {
  margin: 2.2em auto;
  max-width: 88%;
  padding: 1.5em 2em;
  background: color-mix(in srgb, var(--bg-primary) 96%, #8c7355);
  border: 1px solid rgba(140, 115, 85, 0.22);
  border-radius: 6px;
}

.bf-toc-title {
  text-align: center;
  font-family: 'Cinzel Decorative', Georgia, serif;
  font-size: 0.84em;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: #6d5540;
  margin-bottom: 1.2em;
  padding-bottom: 0.5em;
  border-bottom: 1px solid rgba(140, 115, 85, 0.2);
}

.bf-toc-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.bf-toc-item {
  padding: 0.42em 0;
  border-bottom: 1px dotted rgba(140, 115, 85, 0.28);
  font-family: 'Lora', 'EB Garamond', Georgia, serif;
  font-size: 0.92em;
  color: #3d2f23;
  display: flex;
  justify-content: space-between;
}

.bf-toc-item:last-child {
  border-bottom: none;
}

/* ── Verse / Poetry / Songs / Ballads ── */
.bf-verse {
  margin: 1.8em auto;
  max-width: 88%;
  padding: 1em 0 1em 1.8em;
  border-left: 2px solid rgba(140, 115, 85, 0.2);
  font-style: italic;
  color: #3e3126;
  /* A lighter tint of the page colour (same hue), kept faint. */
  background: color-mix(in srgb, var(--bg-primary), #fff 35%);
  border-radius: 0 4px 4px 0;
}

.bf-verse-stanza {
  margin-bottom: 1.15em;
}

.bf-verse-stanza:last-child {
  margin-bottom: 0;
}

.bf-verse-line {
  display: block;
  padding-left: 2.2em;
  text-indent: -2.2em;
  line-height: 1.68;
  text-align: left;
}

/* ── Epistolary / Letter Styling ── */
.bf-letter {
  margin: 2em 4%;
  padding: 1.5em 2em;
  background: color-mix(in srgb, var(--bg-primary) 96%, #8c7355);
  border: 1px solid rgba(140, 115, 85, 0.25);
  border-left: 3px solid #8c7355;
  border-radius: 4px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.03);
}

.bf-letter-header {
  text-align: right;
  font-style: italic;
  font-size: 0.88em;
  color: #685444;
  margin-bottom: 1em;
}

.bf-letter-salutation {
  font-style: italic;
  font-weight: 500;
  color: #332418;
  margin-bottom: 0.8em;
  text-indent: 0 !important;
}

.bf-letter-p {
  margin: 0;
  text-indent: 1.5em;
  line-height: 1.75;
  text-align: justify;
  text-justify: inter-word;
  color: #2e231a;
}

.bf-letter-p:first-of-type {
  text-indent: 0;
}

.bf-letter-footer {
  text-align: right;
  margin-top: 1.2em;
  font-style: italic;
  color: #48382c;
}

.bf-letter-valediction {
  display: inline-block;
}

.bf-letter-signature {
  display: block;
  font-variant: small-caps;
  letter-spacing: 0.06em;
  font-weight: 600;
  color: #2b1d12;
  margin-top: 0.3em;
}

/* ── Plays & Dramatic Dialogue ── */
.bf-play-dialogue {
  margin: 0.7em 0;
  padding-left: 4.2em;
  text-indent: -4.2em;
  text-align: justify;
  text-justify: inter-word;
  line-height: 1.75;
}

.bf-speaker {
  font-variant: small-caps;
  font-variant-caps: all-small-caps;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: #38281d;
  margin-right: 0.5em;
}

.bf-stage-direction {
  font-style: italic;
  color: #705b4a;
  font-weight: normal;
}

.bf-stage-direction-p {
  text-align: center;
  margin: 1em 0;
  text-indent: 0 !important;
}

/* ── Epigraphs & Blockquotes ── */
.bf-epigraph {
  margin: 2em 8% 2.2em;
  padding: 0.6em 0 0.6em 1.4em;
  border-left: 2px solid rgba(140, 115, 85, 0.4);
  font-style: italic;
  color: #4a3a2d;
}

.bf-epigraph p {
  margin: 0;
  line-height: 1.65;
  text-indent: 0 !important;
}

.bf-attribution {
  display: block;
  text-align: right;
  margin-top: 0.6em;
  font-style: normal;
  font-size: 0.84em;
  letter-spacing: 0.05em;
  color: #78624e;
}

/* ── Illustration Captions & Figures ── */
.bf-illustration-note {
  margin: 2em auto;
  text-align: center;
  max-width: 80%;
}

.bf-illustration-note figcaption {
  font-family: 'Lora', 'EB Garamond', Georgia, serif;
  font-size: 0.84em;
  font-style: italic;
  color: #705e50;
  padding: 0.5em 1em;
  border-top: 1px solid rgba(140, 115, 85, 0.2);
  border-bottom: 1px solid rgba(140, 115, 85, 0.2);
  display: inline-block;
}

/* ── Small Caps & Emphasis ── */
.bf-small-caps {
  font-variant: small-caps;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.06em;
  font-size: 1em;
  font-weight: 500;
}

.bf-italic {
  font-style: italic;
}

/* ── Dialogue ── */
.bf-dialogue {
  color: #26211D;
}

/* ── Thematic Break / Asterism ── */
.bf-thematic-break {
  text-align: center;
  margin: 2.2em 0;
  line-height: 1;
}

.bf-thematic-break .bf-fleuron,
.bf-thematic-break .bf-asterism {
  font-size: 1.15em;
  color: #a8937e;
  letter-spacing: 0.5em;
  display: inline-block;
  user-select: none;
}

/* ── Seamless Scene Flow ── */
.scene-text-block+.scene-text-block {
  margin-top: 0;
  padding-top: 0;
}

.scene-text-block+.scene-text-block::before {
  content: none;
}


/* ===== SCENE SENTINEL (legacy placeholder) ===== */
.scene-sentinel {
  min-height: 0;
  width: 100%;
}

/* Legacy scene-block (kept for compatibility during transition) */
.scene-block {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: center;
  gap: clamp(32px, 5vw, 72px);
  max-width: 1400px;
  margin: 0 auto;
  padding: 48px clamp(24px, 4vw, 64px);
}

/* ===== RESPONSIVE ===== */
@media (max-width: 900px) {
  /* Auto-hiding header: the reader runs under the fixed header (its first
     --header-height of content is padding), so when the header slides up the
     sticky video and icons can rise into the space it leaves. */
  .scroll-reader {
    margin-top: 0;
  }

  /* Negative: with the header gone the video rises a little past its own top
     padding, leaving a 4px paper margin above the frame. */
  body.header-hidden {
    --header-offset: -4px;
  }

  body.header-hidden .app-header {
    transform: translateY(-100%);
  }

  /* Opaque: text now scrolls underneath the header and would ghost through. */
  .app-header {
    background: var(--bg-primary);
  }

  .reader-layout {
    flex-direction: column;
    align-items: center;
    gap: 0;
    padding: var(--header-height) 0 0;
  }

  .sticky-canvas-col {
    position: sticky;
    top: var(--header-offset);
    height: auto;
    width: 100%;
    z-index: 10;
    background: var(--bg-primary);
    /* 22px above: 14px frame + 8px paper. 44px below: 14px frame, a 6px gap,
       one 18.2px line of caption (14px x 1.3) and a 6px gap to the text. */
    padding: 22px 0 44px;
    transform: none;
    transition: top var(--header-slide);
  }

  /* Paper band one header tall above the video, moving with it. The header
     slides on the compositor while the video's `top` animates on the main
     thread, so on phones the two can drift apart mid-slide; this fills any
     gap between them so text never shows through. At rest it sits behind
     the header (or above the screen when the header is hidden). */
  .sticky-canvas-col::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 100%;
    height: var(--header-height);
    background: var(--bg-primary);
  }

  .crossfade-container {
    /* svh = visible height with browser toolbars shown: always fits, never resizes mid-scroll.
       The picture itself is at most 45% of the screen; the frame and caption
       (66px) and, until it slides away, the header come on top of that. */
    height: min(calc(var(--scene-h, var(--boot-scene-h, 707)) * 1px), 45vh);
    height: min(calc(var(--scene-h, var(--boot-scene-h, 707)) * 1px), 45svh);
    max-width: 85vw;
    margin: 0 auto;
    /* Slimmer band: 4px gap, 1px rule, 2px, 4px band, 2px, 1px rule = 14px */
    --frame-in: 2px;
    --frame-gap: 2px;
    --frame-space: 2px;
    --frame-band: 4px;
  }

  /* One line only: the band's height is fixed, so a wrapped title would spill
     onto the text. */
  .scene-caption {
    /* 14px frame + 6px gap */
    top: calc(100% + 20px);
    left: -14px;
    right: -14px;
    font-size: 14px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .text-scroll-col {
    max-width: 100%;
    min-width: auto;
    /* No top padding: the band above already ends 6px below the caption. */
    padding: 0 14px calc(50vh + 40px);
  }

  /* The book's opening heading drops its own top space so its rule sits 6px
     under the caption. Later headings keep it to separate them from the text
     above. */
  .text-scroll-col>.scene-text-body>.bf-chapter-heading:first-child,
  .scene-text-block:first-child>.scene-text-body>.bf-chapter-heading:first-child {
    margin-top: 0;
    padding-top: 0;
  }

  /* Phones: 17px at line-height 1.65 gives ~43 characters per line on a 390px
     screen (19px gave ~38, splitting phrases and leaving a ragged edge). */
  .scene-text-body {
    font-size: 1.0625rem;
  }

  .scene-text-body,
  .scene-text-body .bf-paragraph,
  .scene-text-body p:not([class]) {
    line-height: 1.65;
  }

  .scene-text-block {
    min-height: 0;
    padding: 0;
  }

  .margin-icons-col {
    position: static;
    width: auto;
    margin-left: 0;
  }

  .margin-icons {
    position: fixed;
    top: calc(var(--header-offset) + 8px);
    transition: top var(--header-slide);
    bottom: auto;
    right: 12px;
    left: auto !important;
    flex-direction: column;
    gap: 16px;
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    padding: 0;
    border-radius: 0;
    border: none;
    box-shadow: none;
    z-index: 50;
  }
}

/* Phones: the reader icons move into the header's top-right corner and the
   page number ("p. N") is not shown. Kept after the max-width: 900px block so
   these win over its icon column. */
@media (max-width: 600px) {
  .header-right .header-meta {
    display: none;
  }

  /* As a flex item with a z-index the column would be its own stacking
     context under the header (z-index 100), hiding the icons placed over it. */
  .margin-icons-col {
    z-index: auto;
  }

  /* One row, vertically centred on the book title (whose letters sit ~32px
     down the 44px header), sliding away with the header. 12px gaps leave
     ~14px between the visible glyphs. The header title's fit (app.js)
     reserves this row's width so the title stays centred. */
  .margin-icons {
    top: 21px;
    right: 14px;
    height: 22px;
    flex-direction: row;
    align-items: center;
    gap: 12px;
    z-index: 101;
    transform: none;
    transition: transform var(--header-slide);
  }

  body.header-hidden .margin-icons {
    transform: translateY(calc(-1 * var(--header-height)));
  }

  /* The bookmark is only an indicator (the position saves automatically), so
     it gives up its place to keep the row uncluttered and the title full
     size. */
  .bookmark-indicator {
    display: none;
  }

  /* Sized to the header's type: glyphs ~14-16px tall, like the logo and
     title, instead of the 21-24px margin icons. */
  .like-heart {
    width: 17px;
    height: 17px;
  }

  .share-btn {
    width: 16px;
    height: 16px;
  }

  /* Scoped under .margin-icons: the base ring rules come later in the file. */
  .margin-icons .reading-progress {
    width: 22px;
    height: 22px;
  }

  /* Holds its place until the first progress update, so the row (and the
     title centred against it) doesn't shift when the ring appears. */
  .margin-icons .reading-progress[hidden] {
    display: inline-flex;
    visibility: hidden;
  }

  .margin-icons .reading-progress-label {
    font-size: 7px;
    letter-spacing: -0.02em;
  }
}

/* ===== HEADER BUTTONS ===== */
.nav-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--btn-bg);
  border: 1px solid var(--border-color);
  color: var(--text-primary);
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  padding: 8px 16px;
  border-radius: 20px;
  cursor: pointer;
  transition: all 0.2s ease;
  user-select: none;
}

.nav-btn:hover:not(:disabled) {
  background: linear-gradient(180deg, #244C3B 0%, #1A382B 60%, #11261D 100%);
  border-color: #1A382B;
  color: var(--accent-gold-light);
  box-shadow: 0 4px 14px rgba(26, 56, 43, 0.25), inset 0 1px 0 rgba(223, 186, 115, 0.25);
  transform: translateY(-1px);
}

.nav-btn:active:not(:disabled) {
  transform: translateY(0);
}

.nav-btn.active {
  background: linear-gradient(180deg, #244C3B 0%, #1A382B 60%, #11261D 100%);
  border-color: #1A382B;
  color: var(--accent-gold-light);
  box-shadow: 0 2px 8px rgba(26, 56, 43, 0.2), inset 0 1px 0 rgba(223, 186, 115, 0.25);
}

.nav-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
}

/* ===== READING PROGRESS RING (margin icon column, below share) ===== */
.reading-progress {
  position: relative;
  width: 34.5px;
  height: 34.5px;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-primary);
  cursor: default;
}

.reading-progress[hidden] {
  display: none;
}

.reading-progress svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Start at 12 o'clock, then mirror so the arc fills counter-clockwise */
  transform: scaleX(-1) rotate(-90deg);
}

.reading-progress-track {
  fill: none;
  stroke: var(--text-primary);
  stroke-opacity: 0.14;
  stroke-width: 3.48;
  /* ~3.3px on screen at 34.5px */
}

.reading-progress-arc {
  fill: none;
  stroke: var(--accent-green);
  stroke-width: 3.48;
  stroke-linecap: round;
  stroke-dasharray: 100;
  /* pathLength="100" so dashoffset = 100 - percent */
  stroke-dashoffset: 100;
  transition: stroke-dashoffset 0.35s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.2s ease;
}

.reading-progress-label {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: -0.04em;
  font-variant-numeric: tabular-nums;
  color: currentColor;
  line-height: 1;
}

@media (prefers-reduced-motion: reduce) {
  .reading-progress-arc {
    transition: none;
  }

  .app-header,
  .sticky-canvas-col,
  .margin-icons {
    transition: none !important;
  }
}

/* ===== LOADING / HIDDEN STATES ===== */
.loading-overlay,
.spinner,
.loading-text {
  display: none !important;
}

/* ===== SIGN-IN PROMPT (src_js/auth_gate.js) =====
   A full-screen <dialog> opened with showModal(): the scrim layer frosts the
   page behind it (the blur ramps in gradually, then out on close) and the card
   sits centred on top. Classes: .is-open (entering/open), .is-closing (exit),
   .is-busy (sign-in in progress). Open/close timing lives in the script. */
.lb-auth {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 16px;
  border: 0;
  background: transparent;
  color: var(--text-primary);
  overflow: hidden;
  display: none;
  place-items: center;
}

.lb-auth[open] {
  display: grid;
}

.lb-auth::backdrop {
  background: transparent;
}

/* Frosted page: transparent and unblurred at first, then the blur and a warm
   paper wash build up over ~0.9s so the story fades back rather than vanishing. */
.lb-auth-scrim {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 70% 60% at 50% 48%, rgba(248, 241, 224, 0.30) 0%, rgba(244, 236, 216, 0.16) 60%, rgba(52, 42, 34, 0.18) 100%);
  -webkit-backdrop-filter: blur(0px) saturate(100%);
  backdrop-filter: blur(0px) saturate(100%);
  opacity: 0;
  transition:
    opacity 0.7s cubic-bezier(0.2, 0.7, 0.2, 1),
    -webkit-backdrop-filter 0.9s cubic-bezier(0.2, 0.7, 0.2, 1),
    backdrop-filter 0.9s cubic-bezier(0.2, 0.7, 0.2, 1);
}

.lb-auth.is-open .lb-auth-scrim {
  opacity: 1;
  -webkit-backdrop-filter: blur(14px) saturate(115%);
  backdrop-filter: blur(14px) saturate(115%);
}

/* The card: a sheet of the reader's paper with a thin gold double rule (like
   the scene frames) and a soft lift shadow. */
.lb-auth-card {
  position: relative;
  width: min(408px, 100%);
  max-height: calc(100dvh - 32px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 34px 32px 22px;
  text-align: center;
  background: linear-gradient(180deg, #FAF4E6 0%, #F3EAD5 100%);
  border-radius: 6px;
  box-shadow:
    inset 0 0 0 1px rgba(184, 147, 74, 0.45),
    0 1px 2px rgba(36, 30, 25, 0.08),
    0 24px 60px -18px rgba(36, 30, 25, 0.45),
    0 10px 24px -12px rgba(36, 30, 25, 0.22);
  opacity: 0;
  transform: translateY(18px) scale(0.97);
  transition:
    opacity 0.45s cubic-bezier(0.2, 0.8, 0.2, 1) 0.12s,
    transform 0.6s cubic-bezier(0.2, 0.8, 0.2, 1) 0.12s;
}

.lb-auth-card::before {
  content: '';
  position: absolute;
  inset: 6px;
  border: 1px solid rgba(184, 147, 74, 0.28);
  border-radius: 3px;
  pointer-events: none;
}

.lb-auth.is-open .lb-auth-card {
  opacity: 1;
  transform: none;
}

/* Exit: card first, blur follows. */
.lb-auth.is-closing .lb-auth-card {
  transition-delay: 0s;
  transition-duration: 0.28s;
}

.lb-auth.is-closing .lb-auth-scrim {
  transition-duration: 0.5s;
}

.lb-auth-logo {
  display: block;
  height: 30px;
  width: auto;
  margin: 0 auto 14px;
  object-fit: contain;
}

.lb-auth-eyebrow {
  font-family: 'EB Garamond', Georgia, serif;
  font-size: 15px;
  font-weight: 500;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.18em;
  color: var(--text-secondary);
}

.lb-auth-title {
  margin-top: 6px;
  font-family: 'EB Garamond', Georgia, serif;
  font-size: 25px;
  font-weight: 600;
  line-height: 1.22;
  color: var(--text-primary);
  text-wrap: balance;
}

/* The book's own title typeface (copied from the book menu by the script). */
.lb-auth-book {
  font-weight: 400;
  letter-spacing: 0.01em;
}

.lb-auth-desc {
  margin: 10px auto 0;
  max-width: 32ch;
  font-family: 'EB Garamond', Georgia, serif;
  font-size: 17px;
  line-height: 1.4;
  color: #5b5048;
  text-wrap: balance;
}

/* Thin gold rule with a centred diamond (as under the landing titles). */
.lb-auth-rule {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin: 16px 0 18px;
  color: var(--accent-gold-dark);
  opacity: 0.7;
}

.lb-auth-rule::before,
.lb-auth-rule::after {
  content: '';
  width: 42px;
  height: 1px;
  background: currentColor;
}

.lb-auth-rule span {
  font-size: 7px;
  line-height: 1;
}

.lb-auth-providers {
  display: grid;
  gap: 10px;
}

/* Provider buttons: paper pills with an ink hairline and the brand mark on
   the left; on hover a pale-gold band sweeps through (like "Begin reading"). */
.lb-auth-btn {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 48px;
  padding: 0 52px;
  border: 1px solid rgba(42, 32, 24, 0.16);
  border-radius: 999px;
  background: rgba(255, 252, 245, 0.78);
  color: var(--text-primary);
  font-family: 'EB Garamond', Georgia, serif;
  font-size: 17px;
  font-weight: 500;
  letter-spacing: 0.01em;
  cursor: pointer;
  box-shadow: 0 1px 2px rgba(36, 30, 25, 0.06);
  transition:
    transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1),
    box-shadow 0.18s ease,
    border-color 0.18s ease,
    background-color 0.18s ease,
    opacity 0.2s ease;
}

.lb-auth-btn::after {
  content: '';
  position: absolute;
  z-index: -1;
  top: -50%;
  left: -70px;
  width: 40px;
  height: 200%;
  background: rgba(244, 226, 184, 0.75); /* --accent-gold-light */
  transform: skewX(-24deg);
  transition: none;
}

.lb-auth-btn:hover:not(:disabled),
.lb-auth-btn:focus-visible {
  background: #FFFCF5;
  border-color: rgba(184, 147, 74, 0.6);
  transform: translateY(-1px);
  box-shadow: 0 6px 16px -8px rgba(36, 30, 25, 0.35);
}

.lb-auth-btn:hover:not(:disabled)::after,
.lb-auth-btn:focus-visible::after {
  left: calc(100% + 70px);
  transition: left 0.7s cubic-bezier(0.3, 0.6, 0.2, 1);
}

.lb-auth-btn:active:not(:disabled) {
  transform: translateY(0) scale(0.99);
}

.lb-auth-btn:focus-visible {
  outline: 2px solid var(--accent-green);
  outline-offset: 2px;
}

.lb-auth-btn:disabled {
  cursor: default;
}

.lb-auth.is-busy .lb-auth-btn:not(.is-loading) {
  opacity: 0.5;
}

.lb-auth-btn-icon {
  position: absolute;
  left: 20px;
  top: 50%;
  width: 20px;
  height: 20px;
  margin-top: -10px;
  display: grid;
  place-items: center;
  color: #111;
}

.lb-auth-btn-icon svg {
  width: 20px;
  height: 20px;
  display: block;
}

/* Waiting for the provider: the brand mark gives way to a small ink ring. */
.lb-auth-btn.is-loading .lb-auth-btn-icon svg {
  opacity: 0;
}

.lb-auth-btn.is-loading .lb-auth-btn-icon::after {
  content: '';
  position: absolute;
  inset: 1px;
  border: 2px solid rgba(42, 32, 24, 0.18);
  border-top-color: var(--accent-green);
  border-radius: 50%;
  animation: lb-auth-spin 0.8s linear infinite;
}

@keyframes lb-auth-spin {
  to { transform: rotate(360deg); }
}

.lb-auth-status {
  min-height: 0;
  margin-top: 10px;
  font-family: 'EB Garamond', Georgia, serif;
  font-size: 15px;
  line-height: 1.35;
  color: var(--text-secondary);
  text-wrap: balance;
}

.lb-auth-status:empty {
  margin-top: 0;
}

.lb-auth-status[data-tone="error"] {
  color: var(--accent-crimson);
}

.lb-auth-later {
  margin-top: 12px;
  padding: 6px 12px;
  border: 0;
  background: none;
  font-family: 'EB Garamond', Georgia, serif;
  font-size: 16px;
  font-weight: 500;
  font-variant-caps: all-small-caps;
  letter-spacing: 0.18em;
  color: var(--text-secondary);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 4px;
  transition: color 0.2s ease, text-decoration-color 0.2s ease;
}

.lb-auth-later:hover,
.lb-auth-later:focus-visible {
  color: var(--text-primary);
  text-decoration-color: rgba(184, 147, 74, 0.7);
}

.lb-auth-later:focus-visible,
.lb-auth-close:focus-visible {
  outline: 2px solid var(--accent-green);
  outline-offset: 2px;
  border-radius: 6px;
}

.lb-auth-fine {
  margin: 8px auto 0;
  max-width: 36ch;
  font-family: 'EB Garamond', Georgia, serif;
  font-size: 13px;
  line-height: 1.4;
  color: rgba(92, 88, 82, 0.85);
  text-wrap: balance;
}

.lb-auth-close {
  position: absolute;
  top: 12px;
  right: 12px;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: rgba(42, 32, 24, 0.55);
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease;
}

.lb-auth-close:hover {
  background: rgba(42, 32, 24, 0.06);
  color: var(--text-primary);
}

.lb-auth-close svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
}

.lb-auth.is-busy .lb-auth-close,
.lb-auth.is-busy .lb-auth-later {
  opacity: 0.4;
  pointer-events: none;
}

/* Signed in: a gold check draws itself, then the prompt closes. */
.lb-auth-done {
  padding: 18px 0 12px;
}

.lb-auth-check {
  width: 64px;
  height: 64px;
  margin: 0 auto 8px;
  display: block;
  fill: none;
  stroke: var(--accent-gold-dark);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.lb-auth-check circle {
  stroke-dasharray: 151;
  stroke-dashoffset: 151;
}

.lb-auth-check path {
  stroke: var(--accent-green);
  stroke-width: 2.6;
  stroke-dasharray: 34;
  stroke-dashoffset: 34;
}

.lb-auth-done.is-shown .lb-auth-check circle {
  animation: lb-auth-draw 0.7s cubic-bezier(0.6, 0, 0.3, 1) forwards;
}

.lb-auth-done.is-shown .lb-auth-check path {
  animation: lb-auth-draw 0.4s cubic-bezier(0.6, 0, 0.3, 1) 0.55s forwards;
}

@keyframes lb-auth-draw {
  to { stroke-dashoffset: 0; }
}

.lb-auth-done.is-shown > :not(svg) {
  animation: lb-auth-rise 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) 0.35s both;
}

@keyframes lb-auth-rise {
  from { opacity: 0; transform: translateY(6px); }
}

@media (max-width: 480px) {
  .lb-auth {
    padding: 12px;
  }

  .lb-auth-card {
    padding: 28px 20px 16px;
  }

  .lb-auth-logo {
    height: 26px;
    margin-bottom: 10px;
  }

  .lb-auth-title {
    font-size: 22px;
  }

  .lb-auth-desc {
    font-size: 16px;
  }

  .lb-auth-rule {
    margin: 13px 0 14px;
  }

  .lb-auth-btn {
    min-height: 46px;
    font-size: 16.5px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .lb-auth-card,
  .lb-auth-scrim {
    transform: none !important;
    transition-duration: 0.15s !important;
    transition-delay: 0s !important;
  }

  .lb-auth-btn,
  .lb-auth-btn::after {
    transition: none !important;
  }

  .lb-auth-done.is-shown .lb-auth-check circle,
  .lb-auth-done.is-shown .lb-auth-check path,
  .lb-auth-done.is-shown > :not(svg) {
    animation-duration: 0.01s;
    animation-delay: 0s;
  }
}