/* ==========================================================================
   Storybook prototype styles
   --------------------------------------------------------------------------
   Layout philosophy:
   - Every scene is a full-viewport <section class="scene">.
   - Art lives in an absolutely-positioned .layers stack behind the text.
   - Story text floats above the art in .story-text boxes, placed with
     .pos-* utility classes.
   ========================================================================== */

:root {
  --ink: #f6f2e8;            /* story text color */
  --ink-dim: rgba(246, 242, 232, 0.65);
  --accent: #ffd84d;         /* Pip's glow yellow */
  --page-bg: #0b1026;        /* fallback behind everything */
  --text-shadow: 0 2px 18px rgba(0, 0, 0, 0.55);
  --serif: Georgia, "Iowan Old Style", "Times New Roman", serif;
  --sans: system-ui, -apple-system, "Segoe UI", sans-serif;
  --page-max: 52rem;         /* roomy-but-capped reading column on wide screens */
}

/* Vendored display serif for titles (offline, no CDN). Variable weight 400–600. */
@font-face {
  font-family: "Fraunces";
  src: url("fonts/fraunces.woff2") format("woff2");
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--page-bg);
  color: var(--ink);
  font-family: var(--serif);
  /* Lenis drives scrolling; native overscroll glow looks odd with it */
  overscroll-behavior-y: none;
}

/* Lenis recommends these so it can take over the scroll loop cleanly */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }

/* iOS Safari safe-area cap.
   With viewport-fit=cover the artwork runs full-bleed UNDER the notch/status-bar
   strip (where the clock & battery sit). During momentum scroll iOS repaints the
   pinned art a frame behind the page, so for that one strip the OUTGOING scene is
   briefly visible sliding over the pinned one — the "floating away" seam. We cap
   that exact strip with the page-frame color so the seam can never show; because
   it's a solid fill, its own iOS fixed-jitter is invisible. Height collapses to 0
   where there's no safe-area inset (desktop / non-notch), so they're untouched.
   Sits above the art (z 0–5) and below all top UI (progress z70, controls z60+). */
body::before {
  content: "";
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: env(safe-area-inset-top, 0px);
  /* opaque through the status-bar zone, then a short fade into the art so the
     bottom edge doesn't read as a hard line (the seam it hides is up top, by the
     clock, so fading the lower edge is safe) */
  background: linear-gradient(to bottom, var(--page-bg) 72%, transparent 100%);
  /* iOS paints the GPU-composited parallax art above a plain fixed element, so a
     low z-index loses (the art's effective iOS paint level sits between 40 and the
     z60 controls — the share/chip win at 60, my old z40 cap did not). Match the
     controls at z60: above the art, yet below the chip/share/progress, which are
     all z60 (later in the DOM) or z70. translateZ keeps it on its own GPU layer so
     it composites in z-order. */
  z-index: 60;
  pointer-events: none;
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
}

/* Art goes full-bleed (orientation-aware per-scene variants keep it from
   cropping); readability comes from the text cards' own max-width, not a
   column cap. */

/* --------------------------------------------------------------------------
   Scenes
   -------------------------------------------------------------------------- */

.scene {
  position: relative;
  width: 100%;
  /* svh avoids the mobile address-bar jump; vh is the fallback */
  min-height: 100vh;
  min-height: 100svh;
  overflow: clip;            /* parallax layers overflow their scene */
  display: grid;
  place-items: center;
}

/* Art layer stack. Layers are taller than the scene (and vertically
   centered via the negative inset) so parallax movement never reveals
   a gap at the top or bottom edge. */
.layers { position: absolute; inset: 0; }

.layer {
  position: absolute;
  top: -25%;
  left: 0;
  width: 100%;
  height: 150%;
  will-change: transform;
}

.layer picture { display: contents; }  /* let the <img> size to the .layer */
.layer img {
  width: 100%;
  height: 100%;
  object-fit: cover;         /* art can be any aspect ratio */
  display: block;
  user-select: none;
  -webkit-user-drag: none;
}

/* --------------------------------------------------------------------------
   Starfield overlay (HTML spans with CSS keyframes — see buildStarfield in
   main.js). Lives above bg layers but below the sprite stages and text.
   -------------------------------------------------------------------------- */

.starfield {
  /* Lives INSIDE the .layers stack (inserted just above the sky layer by
     baseScene), so it stacks by DOM order — no z-index, or it would jump
     above the foreground layers and we'd be back to stars-on-hills. */
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
}

.star {
  position: absolute;
  background: #fdf6d8;
  border-radius: 50%;
  opacity: 0.85;
}

.star.twinkles {
  /* Per-star duration/delay come from --star-dur / --star-delay set
     inline by buildStarfield. Defaults guard against a missing var. */
  animation: star-twinkle var(--star-dur, 4s) ease-in-out var(--star-delay, 0s) infinite;
}

@keyframes star-twinkle {
  0%, 100% { opacity: 0.85; }
  50%      { opacity: 0.25; }
}

/* --------------------------------------------------------------------------
   Story text
   -------------------------------------------------------------------------- */

.story-text {
  position: absolute;
  z-index: 5;
  max-width: min(32rem, 86vw);
  /* keep a long verbatim paragraph from ever spanning the whole height */
  max-height: 76vh;
  overflow: hidden;
  padding: 1.05rem 1.4rem;
  font-size: clamp(1.05rem, 2.3vw, 1.4rem);
  line-height: 1.5;
  text-shadow: var(--text-shadow);
  /* a legible caption card so verbatim paragraphs survive busy art */
  background: rgba(10, 14, 32, 0.5);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 0.9rem;
  backdrop-filter: blur(4px);
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.28);
}

/* each word is wrapped in a span by JS so it can be staggered in */
.story-text .word { display: inline-block; }

/* placement utilities — add more as your layouts need them */
.pos-top-left      { top: 8%;  left: 6%; }
.pos-top-center    { top: 8%;  left: 50%; transform: translateX(-50%); text-align: center; }
.pos-top-right     { top: 8%;  right: 6%; text-align: right; }
.pos-center        { top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; }
.pos-bottom-left   { bottom: 10%; left: 6%; }
.pos-bottom-center { bottom: 10%; left: 50%; transform: translateX(-50%); text-align: center; }
.pos-bottom-right  { bottom: 10%; right: 6%; text-align: right; }

/* --------------------------------------------------------------------------
   Read-along scene — full prose scrolls over a sticky illustration.
   The art sticks for the scene's height; the prose column is pulled up over
   it (negative margin) and flows past, so dialogue-heavy stories can be read
   in full while one image holds behind the words.
   -------------------------------------------------------------------------- */
.scene-readalong { position: relative; }

.readalong-art {
  position: sticky;
  top: 0;
  height: 100svh;
  width: 100%;
  overflow: hidden;
  z-index: 0;
  /* promote the pinned art to its own GPU layer — iOS Safari repaints sticky
     elements jerkily during momentum scroll, which made the outgoing (non-pinned)
     scene visibly "float" over it; compositing hints smooth the hand-off. */
  will-change: transform;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}
/* neutralize the parallax layer sizing (150% tall) for the sticky still */
.readalong-art .layers { position: absolute; inset: 0; }
.readalong-art .layer {
  position: absolute;
  left: 0;
  right: 0;
  top: -10%;
  height: 120%;            /* taller than the frame so depth drift never gaps */
}
.readalong-art .layer img { width: 100%; height: 100%; object-fit: cover; }
.readalong-art .starfield { position: absolute; inset: 0; }

.readalong-prose {
  position: relative;
  z-index: 2;
  margin-top: -100svh;             /* pull the column up over the sticky art */
  padding: 12svh 1.2rem 14svh;     /* art breathes around the beat's text */
  display: flex;
  flex-direction: column;
  align-items: center;
  pointer-events: none;
}
/* override .story-text's absolute placement → flow in the column, full height.
   One card per scene holds all that page's paragraphs (a "page" over the art). */
.readalong-block {
  position: relative;
  top: auto; left: auto; right: auto; bottom: auto;
  transform: none;
  max-width: min(40rem, 92vw);
  max-height: none;
  text-align: left;
}
.readalong-block .ra-para { margin: 0 0 0.9em; }
.readalong-block .ra-para:last-child { margin-bottom: 0; }

/* Reveal beat: the image fills the pinned section; the single card is centered
   and animated (in → linger → out) by initRevealBeat — not flowed past. */
.scene-reveal .readalong-art { position: absolute; inset: 0; }
.scene-reveal .readalong-prose {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 1.2rem;
  display: grid;
  place-items: center;
  pointer-events: none;
}
.scene-reveal .readalong-block { will-change: transform, opacity; }

/* --------------------------------------------------------------------------
   Outro — closing call-to-action + email capture (buildOutro)
   -------------------------------------------------------------------------- */
.scene-outro {
  background: linear-gradient(160deg, #1a1230 0%, #2a1d44 55%, #3a2742 100%);
}
.outro-inner {
  position: relative;
  z-index: 5;
  max-width: min(34rem, 88vw);
  margin: 0 auto;
  padding: 1.5rem;
  text-align: center;
}
.outro-heading {
  font-family: "Fraunces", var(--serif);
  font-optical-sizing: auto;
  font-size: clamp(1.8rem, 5vw, 2.8rem);
  font-weight: 500;
  margin: 0 0 0.5rem;
  text-shadow: var(--text-shadow);
}
.outro-body {
  font-family: var(--sans);
  font-size: clamp(1rem, 2.4vw, 1.2rem);
  line-height: 1.55;
  color: rgba(246, 242, 232, 0.9);
  margin: 0 0 1.6rem;
}
.outro-form { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; }
.outro-email {
  flex: 1 1 16rem;
  min-width: 0;
  padding: 0.75rem 1rem;
  font: 1rem var(--sans);
  color: var(--ink);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 0.6rem;
}
.outro-email::placeholder { color: rgba(246, 242, 232, 0.5); }
.outro-email:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.outro-btn {
  padding: 0.75rem 1.4rem;
  font: 600 1rem var(--sans);
  color: #1a1230;
  background: var(--accent);
  border: 0;
  border-radius: 0.6rem;
  cursor: pointer;
  transition: transform 0.15s ease, filter 0.15s ease;
}
.outro-btn:hover { filter: brightness(1.08); transform: translateY(-1px); }
.outro-btn:disabled { opacity: 0.6; cursor: default; }
.outro-status {
  font: 0.95rem var(--sans);
  color: rgba(246, 242, 232, 0.85);
  min-height: 1.4em;
  margin: 0.9rem 0 0;
}
.outro-link {
  display: inline-block;
  margin-top: 1.2rem;
  font: 0.95rem var(--sans);
  color: rgba(246, 242, 232, 0.7);
}

/* rating-gate (end-of-story review capture) */
.outro-rate { margin: 0 0 1.8rem; }
.outro-rate-q { font: 1rem var(--sans); color: rgba(246, 242, 232, 0.85); margin: 0 0 0.5rem; }
.outro-stars { display: inline-flex; gap: 0.15rem; }
.outro-star {
  background: none; border: none; cursor: pointer; padding: 0 0.1rem;
  font-size: 2rem; line-height: 1; color: rgba(246, 242, 232, 0.28);
  transition: color 0.12s, transform 0.12s;
}
.outro-star:hover { transform: translateY(-2px); }
.outro-star.on { color: var(--accent); }
.outro-rate-follow {
  max-width: 30rem; margin: 1rem auto 0; display: flex; flex-direction: column;
  gap: 0.6rem; text-align: left;
}
.outro-rate-followq { font: 0.95rem var(--sans); color: rgba(246, 242, 232, 0.85); margin: 0; text-align: center; }
.outro-rate-text, .outro-rate-name {
  width: 100%; background: rgba(0, 0, 0, 0.25); color: var(--ink);
  border: 1px solid rgba(246, 242, 232, 0.25); border-radius: 0.5rem;
  padding: 0.55rem 0.7rem; font: 0.95rem var(--sans); resize: vertical;
}
.outro-rate-text::placeholder, .outro-rate-name::placeholder { color: rgba(246, 242, 232, 0.5); }
.outro-rate-text:focus, .outro-rate-name:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.outro-rate-consent {
  display: flex; align-items: flex-start; gap: 0.5rem;
  font: 0.85rem var(--sans); color: rgba(246, 242, 232, 0.7); cursor: pointer;
}
.outro-rate-send { align-self: center; }

/* --------------------------------------------------------------------------
   Paywall — shown in place of the rest of a teaser + the outro
   -------------------------------------------------------------------------- */
.scene-paywall {
  background: linear-gradient(160deg, #11162e 0%, #1d2440 55%, #2a2150 100%);
}
.paywall-inner {
  position: relative;
  z-index: 5;
  max-width: min(34rem, 88vw);
  margin: 0 auto;
  padding: 1.5rem;
  text-align: center;
}
.paywall-heading {
  font-family: "Fraunces", var(--serif);
  font-optical-sizing: auto;
  font-size: clamp(1.7rem, 4.8vw, 2.6rem);
  font-weight: 500;
  margin: 0 0 0.4rem;
  text-shadow: var(--text-shadow);
}
.paywall-progress {
  font: 600 0.95rem var(--sans);
  color: var(--accent);
  margin: 0 0 0.8rem;
  letter-spacing: 0.02em;
}
.paywall-body {
  font-family: var(--sans);
  font-size: clamp(1rem, 2.4vw, 1.15rem);
  line-height: 1.55;
  color: rgba(246, 242, 232, 0.9);
  margin: 0 0 1.6rem;
}
.paywall-cta { display: flex; flex-direction: column; align-items: center; gap: 0.7rem; }
.paywall-btn {
  display: inline-block;
  padding: 0.8rem 1.6rem;
  font: 600 1rem var(--sans);
  border-radius: 0.6rem;
  cursor: pointer;
  text-decoration: none;
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: var(--ink);
  background: rgba(255, 255, 255, 0.06);
  transition: transform 0.15s ease, filter 0.15s ease, border-color 0.15s ease;
}
.paywall-btn:hover { transform: translateY(-1px); border-color: rgba(255, 255, 255, 0.4); }
.paywall-btn-primary {
  color: #1a1230;
  background: var(--accent);
  border-color: transparent;
}
.paywall-btn-primary:hover { filter: brightness(1.08); }
.paywall-btn-sm { padding: 0.6rem 1.1rem; font-size: 0.95rem; }
.paywall-signin { margin-top: 1.8rem; }
.paywall-signin-prompt { font: 0.95rem var(--sans); color: rgba(246, 242, 232, 0.7); margin: 0; }
.paywall-link {
  background: none; border: 0; padding: 0; cursor: pointer;
  font: 600 0.95rem var(--sans); color: var(--accent);
  text-decoration: underline; text-underline-offset: 2px;
}
.paywall-signin-form {
  display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap;
  max-width: 26rem; margin: 0.9rem auto 0;
}
.paywall-email {
  flex: 1 1 14rem; min-width: 0;
  padding: 0.7rem 0.9rem; font: 1rem var(--sans); color: var(--ink);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 0.6rem;
}
.paywall-email::placeholder { color: rgba(246, 242, 232, 0.5); }
.paywall-email:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.paywall-signin-status {
  flex-basis: 100%; margin: 0.3rem 0 0;
  font: 0.9rem var(--sans); color: rgba(246, 242, 232, 0.85);
}

/* member chip (sign-in state) — small, top-right, out of the reading column */
.oo-member-chip {
  position: fixed;
  top: 0.75rem;
  right: 0.75rem;
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.7rem;
  font: 600 0.82rem var(--sans);
  color: var(--ink);
  background: rgba(10, 14, 32, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  backdrop-filter: blur(6px);
}
.oo-member-badge { color: var(--accent); }
.oo-member-email { max-width: 12rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: 0.8; }
.oo-member-link { color: rgba(246, 242, 232, 0.85); text-decoration: underline; text-underline-offset: 2px; }

/* toast — transient sign-in / checkout messages */
.oo-toast {
  position: fixed;
  left: 50%;
  bottom: 1.25rem;
  transform: translate(-50%, 1rem);
  z-index: 80;
  max-width: min(28rem, 90vw);
  padding: 0.7rem 1.1rem;
  font: 500 0.95rem var(--sans);
  color: var(--ink);
  background: rgba(10, 14, 32, 0.92);
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 0.7rem;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
  opacity: 0;
  transition: opacity 0.3s ease, transform 0.3s ease;
}
.oo-toast.show { opacity: 1; transform: translate(-50%, 0); }

/* --------------------------------------------------------------------------
   Audio narration — "Listen" toggle + active-beat highlight
   -------------------------------------------------------------------------- */
.narrate-toggle {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.5rem 0.85rem 0.5rem 0.7rem;
  font: 600 0.9rem var(--sans);
  color: var(--ink);
  background: rgba(10, 14, 32, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  backdrop-filter: blur(6px);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.narrate-toggle:hover { border-color: rgba(255, 255, 255, 0.3); }
.narrate-toggle.is-on { background: var(--accent); color: #1a1230; border-color: transparent; }
.narrate-ico { width: 1.15rem; height: 1.15rem; flex: none; }
.narrate-toggle.is-on .narrate-wave { animation: narrate-pulse 1.4s ease-in-out infinite; }
@keyframes narrate-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
@media (prefers-reduced-motion: reduce) { .narrate-toggle.is-on .narrate-wave { animation: none; } }

/* the beat currently being read aloud */
.scene.is-narrating .readalong-block,
.scene.is-narrating .story-text {
  box-shadow: 0 0 0 1px rgba(255, 216, 77, 0.5), 0 8px 34px rgba(255, 216, 77, 0.16);
}

/* --------------------------------------------------------------------------
   Re-engagement — progress bar, resume pill, share button
   -------------------------------------------------------------------------- */
/* slim scroll-progress bar pinned to the very top */
.read-progress {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 3px;
  z-index: 70;
  background: rgba(255, 255, 255, 0.07);
  pointer-events: none;
}
.read-progress-fill {
  height: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: var(--accent);
  will-change: transform;
}

/* share button — top-right, mirrors the narrate pill's glass look */
.share-btn {
  position: fixed;
  top: 0.85rem; right: 1rem;
  z-index: 60;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.4rem; height: 2.4rem;
  color: var(--ink);
  background: rgba(10, 14, 32, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  backdrop-filter: blur(6px);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}
.share-btn:hover { border-color: rgba(255, 255, 255, 0.3); transform: translateY(-1px); }
.share-btn svg { width: 1.15rem; height: 1.15rem; }
/* when the member chip is shown (also top-right), drop the share button below it */
.oo-has-chip .share-btn { top: 3.4rem; }

/* tiny confirmation toast (copy-link fallback) */
.share-toast {
  position: fixed;
  top: 3.6rem; right: 1rem;
  z-index: 60;
  max-width: 70vw;
  padding: 0.45rem 0.7rem;
  font: 600 0.82rem var(--sans);
  color: var(--ink);
  background: rgba(10, 14, 32, 0.85);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 0.6rem;
  backdrop-filter: blur(6px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  transform: translateY(-6px);
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.share-toast.is-in { opacity: 1; transform: none; }

/* resume pill — bottom-center, slides up into view */
.resume-pill {
  position: fixed;
  left: 50%;
  bottom: 1.1rem;
  z-index: 65;
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  padding: 0.55rem 1rem 0.55rem 0.8rem;
  color: #1a1230;
  background: var(--accent);
  border: none;
  border-radius: 999px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transform: translate(-50%, calc(100% + 1.6rem));
  transition: transform 0.32s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.resume-pill.is-in { transform: translate(-50%, 0); }
.resume-ico { width: 1.2rem; height: 1.2rem; flex: none; }
.resume-text {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.15;
  font: 600 0.92rem var(--sans);
  text-align: left;
}
.resume-text small { font-weight: 600; font-size: 0.72rem; opacity: 0.72; }

@media (prefers-reduced-motion: reduce) {
  /* no slide — just fade the pill in/out */
  .resume-pill { transform: translate(-50%, 0); opacity: 0; transition: opacity 0.2s ease; }
  .resume-pill.is-in { opacity: 1; }
  .share-btn { transition: none; }
}

/* --------------------------------------------------------------------------
   Accessibility — keyboard focus, screen-reader helpers, motion safety net
   -------------------------------------------------------------------------- */
/* content for screen readers only — carries the chapter outline so SR users
   can navigate by heading without it showing on screen */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0); clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

/* a clear, consistent keyboard-focus ring on the interactive controls.
   :focus-visible keeps it to keyboard/AT users — mouse clicks won't show it.
   (The scene-nav rail has its own focus affordance: the tick + label expand.) */
.narrate-toggle:focus-visible,
.share-btn:focus-visible,
.resume-pill:focus-visible,
.outro-btn:focus-visible,
.outro-email:focus-visible,
.outro-link:focus-visible,
.flip-card:focus-visible,
.drag-knob:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 3px;
}

/* belt-and-suspenders: when the OS asks for reduced motion, neutralize every
   CSS transition/animation and smooth scroll. The engine separately disables
   its scripted (GSAP/Lenis) motion; this nets anything CSS-driven, now or later. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --------------------------------------------------------------------------
   Cover scene
   -------------------------------------------------------------------------- */

.cover-title {
  font-family: "Fraunces", var(--serif);
  font-optical-sizing: auto;
  font-size: clamp(2.4rem, 8vw, 5.5rem);
  font-weight: 500;
  letter-spacing: 0.01em;
  margin: 0 0 0.4em;
  text-align: center;
  text-shadow: var(--text-shadow);
}

.cover-title .char { display: inline-block; white-space: pre; }

.cover-subtitle {
  font-family: var(--sans);
  font-size: clamp(1rem, 2.3vw, 1.25rem);
  color: #fbf8f0;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.65), 0 2px 16px rgba(0, 0, 0, 0.8);
  text-align: center;
  margin: 0;
  line-height: 1.55;
  letter-spacing: 0.06em;
  text-transform: lowercase;
  white-space: pre-line;     /* honor newlines in the subtitle → stacked lines */
}

.cover-content {
  position: absolute;
  top: 12%;            /* sit the title in the art's calm headroom, not on her */
  left: 0;
  right: 0;
  z-index: 5;
  padding: 0 1rem;
}
/* the cover art keeps its open space up top; bias the cover-crop to show it
   (a portrait 4:5 otherwise center-crops onto the character on wide screens) */
.scene[data-scene-type="cover"] .layer img { object-position: center 18%; }
/* dark-to-clear scrim at the top so the title/subtitle read over busy art */
.scene[data-scene-type="cover"]::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  background: linear-gradient(to bottom,
    rgba(8, 10, 22, 0.78) 0%, rgba(8, 10, 22, 0.5) 22%,
    rgba(8, 10, 22, 0.28) 40%, transparent 58%);
}

.scroll-hint {
  position: absolute;
  bottom: 4%;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  font-family: var(--sans);
  font-size: 0.85rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-align: center;
}

.scroll-hint::after {
  content: "\2193";           /* ↓ */
  display: block;
  font-size: 1.4rem;
  margin-top: 0.3rem;
  animation: hint-bob 1.6s ease-in-out infinite;
}

@keyframes hint-bob {
  0%, 100% { transform: translateY(0); opacity: 0.5; }
  50%      { transform: translateY(8px); opacity: 1; }
}

/* Soft bottom scrim on the cover so the "scroll to begin" hint stays
   legible over busy foreground art. Sits above the art layers (z 0/1)
   but below the title/hint (z 5). Cover-only; the centered title is
   higher up and unaffected. */
.scene[data-scene-type="cover"]::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 28%;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(to top, rgba(8, 10, 28, 0.55), transparent);
}

/* --------------------------------------------------------------------------
   Flipbook (canvas scrub) scene
   -------------------------------------------------------------------------- */

.flipbook-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* --------------------------------------------------------------------------
   Flight scene (pinned scrub moves sprites along paths)
   -------------------------------------------------------------------------- */

.flight-stage {
  position: absolute;
  inset: 0;
  z-index: 3;                /* above .layers (0), below .story-text (5) */
  pointer-events: none;
  overflow: hidden;
}

.flight-sprite {
  position: absolute;
  top: 0;
  left: 0;
  height: auto;
  /* width comes from sprite config (set inline by JS) */
  will-change: transform, opacity;
  /* small fade so phase enter/exit isn't a hard pop */
  transition: opacity 0.25s ease;
}

/* --------------------------------------------------------------------------
   Flip-card scene (tap to flip)
   -------------------------------------------------------------------------- */

.flip-stage {
  position: relative;
  z-index: 4;                 /* under .story-text (5) so text wins overlaps */
  perspective: 1100px;        /* gives the 3D rotation its depth */
  margin-top: 16vh;           /* clear the top-center story text */
}

.flip-card {
  /* it's a <button> for keyboard/screen-reader access; strip the chrome */
  appearance: none;
  border: 0;
  padding: 0;
  background: none;
  cursor: pointer;
  display: block;
  width: min(40vw, 240px);
  aspect-ratio: 3 / 4;
  -webkit-tap-highlight-color: transparent;
}

.flip-card-inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.7s cubic-bezier(0.4, 1.4, 0.4, 1);
}

.flip-card.is-flipped .flip-card-inner { transform: rotateY(180deg); }

.flip-face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  border-radius: 45% 55% 48% 52% / 55% 45% 55% 45%; /* leaf-ish blob */
  display: grid;
  place-items: center;
  padding: 1rem;
  font-family: var(--sans);
  text-align: center;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.4);
}

/* If you swap in image faces (see README) these keep them tidy */
.flip-face img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }

.flip-face-front {
  background: linear-gradient(150deg, #3f7d44, #28522f);
  color: rgba(255, 255, 255, 0.85);
}

/* leaf vein down the front face */
.flip-face-front::before {
  content: "";
  position: absolute;
  top: 8%;
  bottom: 8%;
  left: 50%;
  width: 3px;
  background: rgba(255, 255, 255, 0.25);
  border-radius: 3px;
}

.flip-face-back {
  background: linear-gradient(150deg, #4a3f63, #2c2545);
  transform: rotateY(180deg);
  color: var(--ink);
  font-size: 1.05rem;
  line-height: 1.5;
}

.flip-face-back .pip-dot {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 30px 12px rgba(255, 216, 77, 0.55);
  margin-bottom: 0.8rem;
}

.flip-hint {
  font-family: var(--sans);
  font-size: 0.8rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-align: center;
  margin-top: 1.2rem;
}

/* --------------------------------------------------------------------------
   Drag scene (carry Pip to the lantern)
   -------------------------------------------------------------------------- */

.drag-stage {
  position: relative;
  z-index: 5;
  width: min(86vw, 700px);
  height: 220px;
}

/* the dotted path the knob travels along (drawn by JS as an SVG curve) */
.drag-path {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.drag-path path {
  fill: none;
  stroke: rgba(246, 242, 232, 0.35);
  stroke-width: 3;
  stroke-dasharray: 2 12;
  stroke-linecap: round;
}

.drag-knob {
  position: absolute;
  top: 0;
  left: 0;
  width: 56px;
  height: 56px;
  margin: -28px 0 0 -28px;   /* center the knob on its (x, y) position */
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 26px 10px rgba(255, 216, 77, 0.5);
  cursor: grab;
  /* CRITICAL for mobile: stops the browser from treating the drag
     as a scroll gesture so Pointer Events get every move */
  touch-action: none;
}

.drag-knob:active { cursor: grabbing; }

/* the lantern sitting at the end of the path */
.lantern {
  position: absolute;
  width: 64px;
  height: 96px;
  margin: -48px 0 0 -32px;
  border: 4px solid #8a7a55;
  border-radius: 12px 12px 8px 8px;
  background: rgba(20, 16, 40, 0.8);
  transition: background 0.8s ease, box-shadow 0.8s ease;
}

.lantern::before {            /* little hanging loop on top */
  content: "";
  position: absolute;
  top: -16px;
  left: 50%;
  width: 16px;
  height: 12px;
  transform: translateX(-50%);
  border: 4px solid #8a7a55;
  border-bottom: 0;
  border-radius: 8px 8px 0 0;
}

.lantern.is-lit {
  background: var(--accent);
  box-shadow: 0 0 60px 30px rgba(255, 216, 77, 0.45);
}

/* "The End" payoff text, hidden until the lantern is lit */
.the-end {
  position: absolute;
  bottom: 12%;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  font-size: clamp(1.6rem, 5vw, 2.6rem);
  letter-spacing: 0.08em;
  text-shadow: var(--text-shadow);
  opacity: 0;                 /* revealed by GSAP on success */
}

/* --------------------------------------------------------------------------
   Table of contents — a glass pill below the share button showing the live
   page count; tap it to slide in a chapter list (see buildToc). Replaces the
   old left-edge tick rail (too cluttered on phones).
   -------------------------------------------------------------------------- */

.toc-btn {
  position: fixed;
  top: 3.5rem;                 /* sits just below the share button */
  right: 1rem;
  z-index: 60;                 /* same level as share/chip; above the art */
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  height: 2.4rem;
  padding: 0 0.85rem;
  color: var(--ink);
  background: rgba(10, 14, 32, 0.72);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: 999px;
  backdrop-filter: blur(6px);
  cursor: pointer;
  font: 600 0.85rem var(--sans);
  -webkit-tap-highlight-color: transparent;
  transition: border-color 0.2s ease, transform 0.2s ease;
}
.toc-btn:hover { border-color: rgba(255, 255, 255, 0.3); transform: translateY(-1px); }
/* when the member chip is shown the share button drops to 3.4rem, so drop the
   toc button below it too */
.oo-has-chip .toc-btn { top: 6rem; }
.toc-ico { width: 1.05rem; height: 1.05rem; flex: none; }
.toc-count { font-variant-numeric: tabular-nums; letter-spacing: 0.01em; }
.toc-total { opacity: 0.55; }

/* the slide-in chapter panel */
.toc-panel { position: fixed; inset: 0; z-index: 90; font-family: var(--sans); }
.toc-panel[hidden] { display: none; }
.toc-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(4, 6, 18, 0.5);
  opacity: 0;
  transition: opacity 0.28s ease;
}
.toc-panel.is-open .toc-backdrop { opacity: 1; }
.toc-sheet {
  position: absolute;
  top: 0; right: 0; bottom: 0;
  width: min(20rem, 82vw);
  padding: calc(env(safe-area-inset-top, 0px) + 1.1rem) 1.1rem
           calc(env(safe-area-inset-bottom, 0px) + 1.4rem);
  background: linear-gradient(160deg, #141a33 0%, #1b2240 60%, #241c44 100%);
  border-left: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: -16px 0 50px rgba(0, 0, 0, 0.5);
  overflow-y: auto;
  transform: translateX(100%);
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.toc-panel.is-open .toc-sheet { transform: translateX(0); }
.toc-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.8rem;
}
.toc-heading { font: 700 1.05rem var(--sans); letter-spacing: 0.02em; color: var(--ink); }
.toc-close {
  appearance: none;
  border: 0;
  background: none;
  cursor: pointer;
  width: 2rem; height: 2rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-dim);
  border-radius: 999px;
  -webkit-tap-highlight-color: transparent;
}
.toc-close:hover { color: var(--ink); background: rgba(255, 255, 255, 0.08); }
.toc-close svg { width: 1.2rem; height: 1.2rem; }

.toc-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.15rem; }
.toc-item {
  appearance: none;
  border: 0;
  background: none;
  cursor: pointer;
  width: 100%;
  text-align: left;
  display: flex;
  align-items: baseline;
  gap: 0.7rem;
  padding: 0.7rem 0.6rem;
  border-radius: 0.6rem;
  color: var(--ink-dim);
  -webkit-tap-highlight-color: transparent;
  transition: background 0.15s ease, color 0.15s ease;
}
.toc-item:hover { background: rgba(255, 255, 255, 0.06); color: var(--ink); }
.toc-num { font: 700 0.78rem var(--sans); opacity: 0.5; font-variant-numeric: tabular-nums; min-width: 1.1rem; }
.toc-name { font-size: 1rem; line-height: 1.3; }
.toc-item.is-current { background: rgba(255, 216, 77, 0.1); color: var(--ink); }
.toc-item.is-current .toc-name { font-weight: 700; }
.toc-item.is-current .toc-num { color: var(--accent); opacity: 1; }

/* --------------------------------------------------------------------------
   Accessibility: calm everything down when the user asks for less motion
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .scroll-hint::after { animation: none; }
  .flip-card-inner { transition-duration: 0.15s; }
  .star.twinkles   { animation: none; opacity: 0.7; }
  .toc-backdrop,
  .toc-sheet { transition: none; }
}
