/* ============================================================
   The Story of Jackie — /timeline
   One day over the lake: the page moves through a full day of
   light as her life unfolds. Night (prologue) → dawn (2012) →
   day (the seasons) → snow (2023) → day → dusk (the last weeks)
   → night (her death) → first light again (what remains).
   Each chapter carries its own phase palette by REDEFINING the
   site's standard tokens locally, so every inherited component
   (source chips, backlinks, type colors) adapts on its own.
   ============================================================ */

/* ---- phase palettes ----
   Applied twice: to the chapter sections themselves (static, no JS
   needed) and to <body data-phase> (JS-driven, so the sticky chrome
   follows the reader through the day). */

.ph-night, body[data-phase="night"] {
  --ground: #0F1613; --surface: #17201B; --surface-2: #1D2822;
  --ink: #E6ECE8; --ink-soft: #C2CDC6; --muted: #93A39B;
  --line: #263229; --lake: #7BB8BE; --lake-soft: #1A2A2C;
  --gold: #D9AE4B; --gold-graphic: #D9AE4B;
  --veil: 58%;
  --shadow: none; --shadow-lift: 0 2px 4px rgba(0,0,0,.35), 0 14px 32px rgba(0,0,0,.45);
}
.ph-dawn, body[data-phase="dawn"] {
  --ground: #171E27; --surface: #1D2530; --surface-2: #232D3A;
  --ink: #E8EBEE; --ink-soft: #C4CCD4; --muted: #93A0AD;
  --line: #2A3541; --lake: #8FC0C6; --lake-soft: #1B2A31;
  --gold: #D9AE4B; --gold-graphic: #D9AE4B;
  --veil: 62%;
  --shadow: none; --shadow-lift: 0 2px 4px rgba(0,0,0,.35), 0 14px 32px rgba(0,0,0,.45);
}
.ph-day, body[data-phase="day"] {
  --ground: #F1F4F2; --surface: #FBFCFB; --surface-2: #E9EEEB;
  --ink: #1D2B25; --ink-soft: #45524C; --muted: #5C6B64;
  --line: #D5DED9; --lake: #2F6D74; --lake-soft: #E1EBEC;
  --gold: #8C6819; --gold-graphic: #C89A2B;
  --veil: 79%;
  --shadow: 0 1px 2px rgba(29,43,37,.06), 0 8px 24px rgba(29,43,37,.07);
  --shadow-lift: 0 2px 4px rgba(29,43,37,.07), 0 14px 32px rgba(29,43,37,.11);
}
.ph-snow, body[data-phase="snow"] {
  --ground: #F6F9FA; --surface: #FDFEFE; --surface-2: #EBF1F3;
  --ink: #16232B; --ink-soft: #3E4E58; --muted: #64757F;
  --line: #D9E3E8; --lake: #33636F; --lake-soft: #E3EDF0;
  --gold: #56707E; /* cold steel replaces gold in the white chapter */
  --gold-graphic: #7C97A3;
  --veil: 79%;
  --shadow: 0 1px 2px rgba(22,35,43,.05), 0 8px 24px rgba(22,35,43,.06);
  --shadow-lift: 0 2px 4px rgba(22,35,43,.06), 0 14px 32px rgba(22,35,43,.1);
}
.ph-dusk, body[data-phase="dusk"], body[data-phase="gaze"] {
  --ground: #251C11; --surface: #2C2216; --surface-2: #33281A;
  --ink: #F0E7D8; --ink-soft: #D8CCB6; --muted: #AB9878;
  --line: #3C2F1E; --lake: #A9C7CA; --lake-soft: #2E2A1E;
  --gold: #E0B45C; --gold-graphic: #E0B45C;
  --veil: 60%;
  --shadow: none; --shadow-lift: 0 2px 4px rgba(0,0,0,.35), 0 14px 32px rgba(0,0,0,.45);
}

/* ============================================================
   THE SCENE — a fixed, full-viewport backdrop behind the whole
   page. Blurred, dimmed imagery of her world (the lake, the
   shoreline, the blizzard, the sunset nest) cross-fades as the
   reader crosses phases; at night it is a field of stars. The
   chapters above it are translucent (see --veil), so the page
   reads as light moving over a place, not sections on a page.
   Everything here is compositor-only: opacity crossfades on
   pre-blurred layers, no per-frame paint.
   ============================================================ */
#scene { position: fixed; inset: 0; z-index: -1; pointer-events: none; }
#scene .sc {
  position: absolute; inset: 0;
  opacity: 0; visibility: hidden;
  transition: opacity 1400ms ease, visibility 0s 1400ms;
}
#scene .sc-img {
  background-size: cover;
  background-position: center;
  filter: blur(22px) saturate(0.82);
  transform: scale(1.15); /* hides blur-darkened edges */
}
.sc-dawn { background-image: url(/media/tl/scene/dawn.jpg); }
.sc-day  { background-image: url(/media/tl/scene/day.jpg); }
.sc-snow { background-image: url(/media/tl/scene/snow.jpg); }
.sc-dusk { background-image: url(/media/tl/scene/dusk.jpg); }
/* "gaze" is a chapter-local phase: dusk's palette, but the backdrop is
   the blurred twin of the portrait its unveiling pulls into focus */
.sc-gaze { background-image: url(/media/tl/scene/gaze.jpg); }
/* the light hours get lifted shadows (so texture, never murk, shows through
   the lighter veil) and the day carries a low sun over the lake */
.sc-day, .sc-snow { filter: blur(22px) saturate(0.95) brightness(1.16); }
.sc-day::after {
  content: "";
  position: absolute; inset: 0;
  background: radial-gradient(58% 48% at 76% 4%, rgba(200,154,43,0.2), transparent 70%);
}
body[data-phase="dawn"] #scene .sc-dawn,
body[data-phase="day"]  #scene .sc-day,
body[data-phase="snow"] #scene .sc-snow,
body[data-phase="dusk"] #scene .sc-dusk,
body[data-phase="gaze"] #scene .sc-gaze,
body[data-phase="night"] #scene .sc-night {
  opacity: 1; visibility: visible;
  transition: opacity 1400ms ease, visibility 0s;
}

/* night: no photograph — a thin moon-glow and two layers of stars */
.sc-night {
  background: radial-gradient(60% 42% at 72% 16%, rgba(123,184,190,0.09), transparent 70%);
}
.sc-night .stars {
  position: absolute; top: 0; left: 0;
  width: 2px; height: 2px; border-radius: 50%;
  background: rgba(230,236,232,0.9);
}
.sc-night .s1 { box-shadow:
  4vw 12vh 0 0 rgba(230,236,232,.75), 11vw 31vh 0 0 rgba(230,236,232,.5),
  17vw 6vh 0 0 rgba(230,236,232,.85), 24vw 22vh 0 0 rgba(230,236,232,.45),
  31vw 9vh 0 0 rgba(230,236,232,.7), 37vw 38vh 0 0 rgba(230,236,232,.4),
  44vw 15vh 0 0 rgba(230,236,232,.8), 52vw 27vh 0 0 rgba(230,236,232,.5),
  59vw 5vh 0 0 rgba(230,236,232,.65), 66vw 33vh 0 0 rgba(230,236,232,.45),
  71vw 11vh 0 0 rgba(230,236,232,.85), 78vw 24vh 0 0 rgba(230,236,232,.5),
  84vw 7vh 0 0 rgba(230,236,232,.7), 91vw 18vh 0 0 rgba(230,236,232,.55),
  96vw 36vh 0 0 rgba(230,236,232,.4), 8vw 52vh 0 0 rgba(230,236,232,.35),
  27vw 58vh 0 0 rgba(230,236,232,.3), 49vw 55vh 0 0 rgba(230,236,232,.35),
  68vw 61vh 0 0 rgba(230,236,232,.28), 88vw 54vh 0 0 rgba(230,236,232,.32);
  animation: star-breathe 9s ease-in-out infinite alternate;
}
.sc-night .s2 { width: 1px; height: 1px; box-shadow:
  7vw 21vh 0 0 rgba(230,236,232,.5), 14vw 44vh 0 0 rgba(230,236,232,.35),
  20vw 14vh 0 0 rgba(230,236,232,.55), 28vw 47vh 0 0 rgba(230,236,232,.3),
  34vw 26vh 0 0 rgba(230,236,232,.5), 41vw 4vh 0 0 rgba(230,236,232,.4),
  47vw 41vh 0 0 rgba(230,236,232,.35), 55vw 17vh 0 0 rgba(230,236,232,.55),
  62vw 45vh 0 0 rgba(230,236,232,.3), 69vw 23vh 0 0 rgba(230,236,232,.45),
  75vw 42vh 0 0 rgba(230,236,232,.3), 82vw 13vh 0 0 rgba(230,236,232,.5),
  89vw 39vh 0 0 rgba(230,236,232,.32), 94vw 8vh 0 0 rgba(230,236,232,.45),
  3vw 35vh 0 0 rgba(230,236,232,.4), 16vw 63vh 0 0 rgba(230,236,232,.24),
  39vw 66vh 0 0 rgba(230,236,232,.22), 58vw 69vh 0 0 rgba(230,236,232,.2),
  79vw 65vh 0 0 rgba(230,236,232,.24), 97vw 71vh 0 0 rgba(230,236,232,.18);
  animation: star-breathe 12s ease-in-out 4s infinite alternate;
}
@keyframes star-breathe { from { opacity: 0.55; } to { opacity: 1; } }

/* a soft cinema vignette in the dark hours, and a whisper of grain */
.sc-vignette, .sc-grain {
  position: absolute; inset: 0;
  opacity: 0;
  transition: opacity 1400ms ease;
}
.sc-vignette { background: radial-gradient(130% 100% at 50% 38%, transparent 55%, rgba(0,0,0,0.3) 100%); }
.sc-grain {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
}
body[data-phase="night"] .sc-vignette, body[data-phase="dawn"] .sc-vignette,
body[data-phase="dusk"] .sc-vignette, body[data-phase="gaze"] .sc-vignette { opacity: 1; }
body[data-phase="night"] .sc-grain, body[data-phase="dawn"] .sc-grain,
body[data-phase="dusk"] .sc-grain, body[data-phase="gaze"] .sc-grain { opacity: 0.05; }

/* The sticky chrome eases through the day with the reader. */
body[data-page="life"] { background: var(--ground); transition: background-color 900ms ease; }
body[data-page="life"] .topnav { transition: background-color 900ms ease, border-color 900ms ease; }
body[data-page="life"] .topnav .wordmark,
body[data-page="life"] .topnav .fresh,
body[data-page="life"] .topnav .links a { transition: color 900ms ease; }

/* ---- page frame ---- */
/* the global `main` is a 780px reading column; this page IS the stage */
main.life { max-width: none; margin: 0; padding: 0; overflow-x: clip; }
.ch {
  position: relative;
  background: var(--ground);
  color: var(--ink);
  padding: clamp(4.5rem, 10vh, 8rem) 1.25rem;
}
/* long sky-gradient hand-offs where the light changes */
.ch.from-night   { background: linear-gradient(to bottom, #0F1613 0, var(--ground) 24rem); }
.ch.from-dawn    { background: linear-gradient(to bottom, #171E27 0, var(--ground) 26rem); }
.ch.from-day     { background: linear-gradient(to bottom, #F1F4F2 0, var(--ground) 24rem); }
.ch.from-snow    { background: linear-gradient(to bottom, #F6F9FA 0, var(--ground) 24rem); }
.ch.from-dusk    { background: linear-gradient(to bottom, #251C11 0, var(--ground) 26rem); }
/* the page's final breath: a band of pre-dawn light rises behind the coda,
   then the sky settles back to night beneath the footer */
.ch.to-morning   { background: linear-gradient(to bottom, #0F1613 0, #16202A 45%, #22313F 72%, #101820 100%); }

/* With JS the chapters turn translucent and the fixed scene shows through —
   the veil percentages live in the phase palettes (--veil). Solid grounds
   above remain the no-JS/crawler fallback. Contrast holds because light
   phases keep an 82–86% veil and the scene imagery beneath is blurred and
   tone-matched to its phase. */
html.js body[data-page="life"] .ch {
  background: color-mix(in srgb, var(--ground) var(--veil), transparent);
}
html.js body[data-page="life"] .ch.from-night { background: linear-gradient(to bottom, color-mix(in srgb, #0F1613 58%, transparent) 0, color-mix(in srgb, var(--ground) var(--veil), transparent) 24rem); }
html.js body[data-page="life"] .ch.from-dawn  { background: linear-gradient(to bottom, color-mix(in srgb, #171E27 62%, transparent) 0, color-mix(in srgb, var(--ground) var(--veil), transparent) 26rem); }
html.js body[data-page="life"] .ch.from-day   { background: linear-gradient(to bottom, color-mix(in srgb, #F1F4F2 79%, transparent) 0, color-mix(in srgb, var(--ground) var(--veil), transparent) 24rem); }
html.js body[data-page="life"] .ch.from-snow  { background: linear-gradient(to bottom, color-mix(in srgb, #F6F9FA 79%, transparent) 0, color-mix(in srgb, var(--ground) var(--veil), transparent) 24rem); }
html.js body[data-page="life"] .ch.from-dusk  { background: linear-gradient(to bottom, color-mix(in srgb, #251C11 60%, transparent) 0, color-mix(in srgb, var(--ground) var(--veil), transparent) 26rem); }
/* the pre-dawn band goes translucent too, so the last stars shine through it */
html.js body[data-page="life"] .ch.to-morning { background: linear-gradient(to bottom, rgba(15,22,19,0.86) 0, rgba(22,32,42,0.8) 45%, rgba(34,49,63,0.78) 72%, rgba(16,24,32,0.9) 100%); }

.ch-inner { max-width: 680px; margin: 0 auto; position: relative; }

/* ---- the ghost year: sequence marker as watermark, never a shout ---- */
.ch-year {
  font-family: var(--display);
  font-size: clamp(4.5rem, 16vw, 10rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ink);
  opacity: 0.2;
  user-select: none;
  margin: 0 0 -0.35em -0.04em;
  font-variant-numeric: tabular-nums;
}
.ch h2 {
  font-family: var(--display);
  font-size: clamp(1.8rem, 4.6vw, 2.7rem);
  font-weight: 500;
  line-height: 1.15;
  text-wrap: balance;
  margin: 0 0 0.4em;
}
.ch-kicker {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: var(--track-eyebrow, 0.14em);
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 1rem;
}

/* ---- narrative prose: her story is set in the serif ---- */
.ch p.prose, .ch .prose p {
  font-family: var(--display);
  font-size: 1.17rem;
  line-height: 1.75;
  max-width: 60ch;
  margin: 0 0 1.05em;
}
.ch p.lead { font-size: 1.28rem; line-height: 1.65; }
.ph-night p.prose, .ph-night .prose p, .ph-dawn p.prose, .ph-dawn .prose p,
.ph-dusk p.prose, .ph-dusk .prose p { line-height: 1.8; letter-spacing: 0.01em; }
.ch p.aside-sans {
  font-family: var(--body);
  font-size: 0.98rem;
  color: var(--ink-soft);
  max-width: 58ch;
  margin: 0 0 1em;
}
.ch strong { font-weight: 600; }
.ch a { color: var(--lake); }

/* ---- full-viewport hero: a film title card, not a page header ---- */
.ch-hero {
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-top: 3rem;
  padding-bottom: 4rem;
}
/* staged arrival: each element rises out of the night in order.
   Uses translate/filter keyframes — safe here because .rise elements are
   never .reveal elements (the site reveal system owns that property). */
html.js .ch-hero .rise {
  animation: hero-rise 1400ms cubic-bezier(0.16, 1, 0.3, 1) both;
  animation-delay: var(--d, 0ms);
}
@keyframes hero-rise {
  from { opacity: 0; translate: 0 22px; filter: blur(12px); }
  to   { opacity: 1; translate: 0 0; filter: blur(0); }
}
/* the portrait breathes once, slowly, like a held shot */
html.js .ch-hero .fig-frame img {
  animation: hero-breathe 26s cubic-bezier(0.22, 0.61, 0.36, 1) both;
  transform-origin: 50% 38%;
}
@keyframes hero-breathe { from { scale: 1.09; } to { scale: 1; } }
.ch-hero .ch-inner { text-align: center; max-width: 760px; }
.hero-place {
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 1.6rem;
}
.hero-name {
  font-family: var(--display);
  font-size: clamp(3.4rem, 13vw, 6rem);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.015em;
  margin: 0;
}
.hero-dates {
  font-family: var(--mono);
  font-size: 0.95rem;
  letter-spacing: 0.3em;
  color: var(--gold);
  margin: 1.1rem 0 2.2rem;
  font-variant-numeric: tabular-nums;
}
.hero-figure { margin: 0 auto 1.9rem; max-width: 480px; }
.hero-figure img {
  width: 100%;
  height: auto;
  border-radius: 8px;
  display: block;
}
.hero-epigraph {
  font-family: var(--display);
  font-style: italic;
  font-size: clamp(1.15rem, 2.8vw, 1.45rem);
  line-height: 1.5;
  max-width: 34ch;
  margin: 0 auto 0.7rem;
  text-wrap: balance;
}
.hero-epigraph cite { display: block; font-style: normal; }
.hero-note {
  font-family: var(--body);
  font-size: 0.92rem;
  color: var(--muted);
  max-width: 44ch;
  margin: 1.6rem auto 0;
}
.scroll-cue {
  margin: 2.4rem auto 0;
  width: 1px;
  height: 56px;
  background: linear-gradient(to bottom, transparent, var(--gold));
  animation: cue-drift 2.6s ease-in-out infinite;
}
@keyframes cue-drift { 0%,100% { opacity: .35; } 50% { opacity: 1; } }

/* ---- figures ---- */
.ch figure { margin: 2.2rem 0; }
.ch figure img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 6px;
}
.fig-frame { overflow: hidden; border-radius: 6px; }
/* Breakouts use margin math, NOT translate — the site's .reveal system
   animates the translate property and would stomp a translate-based breakout. */
.ch .fig-wide {
  width: min(960px, calc(100vw - 2.5rem));
  margin-left: calc(50% - min(480px, 50vw - 1.25rem));
}
.ch .fig-bleed {
  width: 100vw;
  max-width: none;
  margin-left: calc(50% - 50vw);
}
.fig-bleed img, .fig-bleed .fig-frame { border-radius: 0; }
.fig-inset { max-width: 620px; }
.fig-half  { max-width: 440px; }
.ch .fig-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  width: min(960px, calc(100vw - 2.5rem));
  margin-left: calc(50% - min(480px, 50vw - 1.25rem));
}
.fig-pair figure { margin: 0; }
@media (max-width: 560px) { .fig-pair { grid-template-columns: 1fr; gap: 1.4rem; } }
.ch figcaption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.9rem;
  padding: 0.55rem 0.15rem 0;
  max-width: 68ch;
}
.fig-bleed figcaption { padding-inline: 1.25rem; margin: 0 auto; }
.ch .cap {
  font-family: var(--display);
  font-style: italic;
  font-size: 0.92rem;
  color: var(--ink-soft);
}
.ch .cred {
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: var(--track-label, 0.08em);
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

/* ============================================================
   HELD SCENES — the page stops scrolling like a page.
   A full-viewport image (or the night sky itself) pins in place
   while short beats of text play over it. The beats live INSIDE
   the pinned frame, stacked at the bottom-left; scroll position
   only picks which line is on — the entrance and exit themselves
   are time-based transitions, so a line dissolves out and the
   next rises in on its own once the reader crosses the trigger,
   never dragged pixel-by-pixel by the scroll.
   The empty .held-runway below provides the scroll distance; the
   page script maps progress through it to .is-on / .is-past.
   Without JS the whole apparatus stands down (see html:not(.js)
   rules at the end of this block): static image, then the lines
   as plain text on a dark ground. No scroll-jacking either way.
   padding: 0 kills the site-wide section padding — without it the
   page ground peeks through above the pinned image. */
.held { position: relative; padding: 0; color: #F2F5F3; }
.held-media {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
}
.held-media img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
/* one steady scrim on the media, not the beats, so it never pumps
   between texts: a bottom-left pool of dark where the words live,
   with a soft floor that keeps the credit line readable */
.held-media::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to top,
    rgba(9, 15, 13, 0.85),
    rgba(9, 15, 13, 0.6) 36%,
    rgba(9, 15, 13, 0.22) 64%,
    rgba(9, 15, 13, 0.04));
}
@media (min-width: 760px) {
  .held-media::after {
    background:
      radial-gradient(135% 120% at 0% 100%,
        rgba(9, 15, 13, 0.86),
        rgba(9, 15, 13, 0.62) 40%,
        rgba(9, 15, 13, 0.3) 62%,
        rgba(9, 15, 13, 0) 80%),
      linear-gradient(to top, rgba(9, 15, 13, 0.42), rgba(9, 15, 13, 0) 24%);
  }
}
.held-cred {
  position: absolute;
  right: 1.1rem;
  bottom: max(1rem, env(safe-area-inset-bottom));
  z-index: 1;
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: var(--track-label, 0.08em);
  text-transform: uppercase;
  color: rgba(240,245,243,0.68);
  text-shadow: 0 1px 8px rgba(0,0,0,.5);
}
/* the runway: pure scroll distance, ~72svh of travel per line plus the
   viewport the media releases through. Pulled up over the sticky media.
   72svh per line keeps the switches coming readily under a normal scroll
   without turning a single wheel-tick into a slide change. */
.held-runway {
  margin-top: -100svh;
  height: calc(var(--beats, 3) * 72svh + 100svh);
  pointer-events: none;
}
/* the stage: all lines stacked in one bottom-left cell of the frame */
.held-stage {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  align-content: end;
  justify-content: start;
  padding: 0 1.5rem calc(12svh + env(safe-area-inset-bottom));
  pointer-events: none;
}
@media (min-width: 760px) {
  .held-stage { padding: 0 3rem 12svh clamp(3rem, 8vw, 7.5rem); }
}
/* Beat type is the hero's language at scene scale: display serif set
   against the scrim's quiet, left-ragged like a caption in a film. */
.beat-text {
  grid-area: 1 / 1;
  align-self: end;
  font-family: var(--display);
  font-size: clamp(1.65rem, 3.4vw, 2.7rem);
  line-height: 1.34;
  font-weight: 400;
  max-width: 22ch;
  text-wrap: balance;
  margin: 0;
  text-shadow: 0 1px 22px rgba(0, 0, 0, 0.45);
}
.beat-text .beat-sub {
  display: block;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: var(--track-eyebrow, 0.14em);
  text-transform: uppercase;
  opacity: 0.8;
  margin-top: 1.3rem;
  text-shadow: inherit;
}
/* the switch is cinematic, not scrubbed: off-states sit a breath below
   (or above, once passed) and blurred; .is-on rises in on the hero's
   curve after the outgoing line has begun to clear. Transitions, not
   animations, so an interrupted change reverses smoothly mid-flight. */
html.js .beat-text {
  opacity: 0;
  translate: 0 1.8rem;
  filter: blur(10px);
  transition:
    opacity 620ms ease,
    translate 620ms ease,
    filter 620ms ease;
}
html.js .beat-text.is-past { translate: 0 -1.2rem; }
html.js .beat-text.is-on {
  opacity: 1;
  translate: 0 0;
  filter: blur(0);
  transition:
    opacity 1150ms cubic-bezier(0.16, 1, 0.3, 1) 320ms,
    translate 1150ms cubic-bezier(0.16, 1, 0.3, 1) 320ms,
    filter 1150ms cubic-bezier(0.16, 1, 0.3, 1) 320ms;
}
/* the passing has no photograph — the beats hold on the starfield, the
   scrim only a breath of deeper night in the reading corner */
.held-dark { background: linear-gradient(to bottom, color-mix(in srgb, #251C11 60%, transparent) 0, transparent 22rem); }
.held-dark .held-media { background: transparent; }
.held-dark .held-media::after {
  background: linear-gradient(to top, rgba(4, 8, 7, 0.5), rgba(4, 8, 7, 0) 45%);
}
@media (min-width: 760px) {
  .held-dark .held-media::after {
    background: radial-gradient(130% 115% at 0% 100%, rgba(4, 8, 7, 0.55), rgba(4, 8, 7, 0) 72%);
  }
}
/* no JS, no theatre: static image, then the lines as plain text on a
   dark ground. Crawlers and text browsers read the same order. */
html:not(.js) .held { background: #101815; }
html:not(.js) .held-media { position: static; height: auto; overflow: visible; }
html:not(.js) .held-media img { height: auto; }
html:not(.js) .held-media::after { content: none; }
html:not(.js) .held-stage { position: static; display: block; padding: 2rem 1.5rem; }
html:not(.js) .beat-text { margin: 0 0 1.2em; }
html:not(.js) .held-runway { display: none; }

/* ============================================================
   UNVEILINGS — the chapter's own backdrop steps forward. The
   reader has been living inside a blurred photograph the whole
   chapter (#scene); at the chapter's end the sharp twin of that
   photograph fades in WEARING the backdrop's exact treatment
   (blur 22px, saturate .82, scale 1.15) — an invisible hand-off,
   since it is the same image in the same state — then pulls into
   focus in place, holds with a caption, recedes back to ambient
   blur, and dissolves before it ever moves. No rectangle slides
   into view; nothing pins visibly; the background simply focuses
   and lets go. The scrub is pure CSS (named view-timeline);
   without scroll-driven-animation support the image is simply
   sharp from the start, and without JS it degrades to a plain
   full-width figure.
   ============================================================ */
.unveil { position: relative; padding: 0; color: #F2F5F3; }
.unveil-media {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
}
.unveil-media img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
/* a quiet floor so the caption reads on any photograph */
.unveil-media::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(9, 15, 13, 0.72), rgba(9, 15, 13, 0.24) 26%, rgba(9, 15, 13, 0) 42%);
}
.unveil-cap {
  position: absolute;
  left: 1.5rem;
  right: 1.5rem;
  bottom: calc(1.4rem + env(safe-area-inset-bottom));
  z-index: 1;
  margin: 0;
}
@media (min-width: 760px) {
  .unveil-cap { left: clamp(3rem, 8vw, 7.5rem); bottom: 2.6rem; }
}
.unveil-line {
  font-family: var(--display);
  font-size: clamp(1.25rem, 2.4vw, 1.9rem);
  line-height: 1.35;
  font-weight: 400;
  max-width: 30ch;
  text-wrap: balance;
  margin: 0 0 0.55rem;
  text-shadow: 0 1px 18px rgba(0, 0, 0, 0.5);
}
.unveil-cred {
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: var(--track-label, 0.08em);
  text-transform: uppercase;
  color: rgba(240, 245, 243, 0.72);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.5);
}
.unveil-runway {
  margin-top: -100svh;
  height: 260svh;
  pointer-events: none;
}
/* Timed against the sticky maths (260svh section + 100svh entry =
   360svh cover): the media is pinned from ~28% to ~72%. The image
   is only ever opaque in the backdrop's own state or in focus —
   every fade and every physical move happens at ambient blur, so
   no edge is ever visible against the matching backdrop behind.
   The caption and its floor gradient exist only around the sharp
   hold. */
@supports (animation-timeline: view()) {
  html.js .unveil { view-timeline: --unveil block; }
  html.js .unveil-media img {
    animation: unveil-focus linear both;
    animation-timeline: --unveil;
  }
  html.js .unveil-media::after {
    animation: unveil-floor linear both;
    animation-timeline: --unveil;
  }
  html.js .unveil-cap {
    animation: unveil-cap-in linear both;
    animation-timeline: --unveil;
  }
  @keyframes unveil-focus {
    0%, 6%    { opacity: 0; filter: blur(22px) saturate(0.82); scale: 1.15; }
    22%, 30%  { opacity: 1; filter: blur(22px) saturate(0.82); scale: 1.15; }
    46%, 62%  { opacity: 1; filter: blur(0px) saturate(1); scale: 1; }
    70%       { opacity: 1; filter: blur(22px) saturate(0.82); scale: 1.15; }
    82%, 100% { opacity: 0; filter: blur(22px) saturate(0.82); scale: 1.15; }
  }
  @keyframes unveil-floor {
    0%, 40%   { opacity: 0; }
    48%, 60%  { opacity: 1; }
    68%, 100% { opacity: 0; }
  }
  @keyframes unveil-cap-in {
    0%, 42%   { opacity: 0; translate: 0 0.9rem; }
    50%, 60%  { opacity: 1; translate: 0 0; }
    68%, 100% { opacity: 0; translate: 0 0; }
  }
}
/* no JS: an ordinary full-width figure with its caption below */
html:not(.js) .unveil { background: #101815; }
html:not(.js) .unveil-media { position: static; height: auto; overflow: visible; }
html:not(.js) .unveil-media img { height: auto; }
html:not(.js) .unveil-media::after { content: none; }
html:not(.js) .unveil-cap { position: static; padding: 1.2rem 1.5rem 1.6rem; }
html:not(.js) .unveil-runway { display: none; }

/* While a held scene owns the viewport, the site chrome steps out of the
   frame — nav, progress hairline, mobile tab bar. body.held-active is set
   by the page script from the same rAF pass as the progress bar. */
body[data-page="life"] .topnav,
body[data-page="life"] .tabbar,
body[data-page="life"] .tl-progress {
  transition: opacity 600ms ease, visibility 0s 0s,
              background-color 900ms ease, border-color 900ms ease;
}
body[data-page="life"].held-active .topnav,
body[data-page="life"].held-active .tabbar,
body[data-page="life"].held-active .tl-progress {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 600ms ease, visibility 0s 600ms,
              background-color 900ms ease, border-color 900ms ease;
}

/* ---- spoken moments: the voices of the record ---- */
.voice {
  margin: 2.6rem 0;
  max-width: 620px;
}
.voice p {
  font-family: var(--display);
  font-style: italic;
  font-size: clamp(1.3rem, 3.4vw, 1.75rem);
  line-height: 1.45;
  margin: 0 0 0.7rem;
  text-wrap: balance;
}
.voice p::before { content: "\201C"; color: var(--gold); }
.voice p::after { content: "\201D"; color: var(--gold); }
.voice cite {
  font-family: var(--mono);
  font-style: normal;
  font-size: 0.7rem;
  letter-spacing: var(--track-label, 0.08em);
  text-transform: uppercase;
  color: var(--muted);
}
.voice cite a { color: inherit; }
.voice-grand { max-width: 720px; margin: 3.4rem auto; text-align: center; }
.voice-grand p { font-size: clamp(1.5rem, 4.2vw, 2.2rem); }

/* ---- field notes: what the record cannot say ---- */
.fieldnote {
  border: 1px dashed var(--line);
  border-radius: 8px;
  padding: 1.1rem 1.3rem 1.2rem;
  margin: 2.2rem 0;
  max-width: 620px;
}
.fieldnote .fn-label {
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: var(--track-eyebrow, 0.14em);
  text-transform: uppercase;
  color: var(--gold);
  margin: 0 0 0.5rem;
}
.fieldnote p {
  font-family: var(--body);
  font-size: 0.97rem;
  line-height: 1.65;
  color: var(--ink-soft);
  margin: 0 0 0.6em;
  max-width: 60ch;
}
.fieldnote p:last-child { margin-bottom: 0; }

/* ---- her life, counted ---- */
.count {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 1.8rem 1.2rem;
  margin: 2.8rem 0;
  max-width: 680px;
}
.count .n {
  font-family: var(--display);
  font-size: clamp(2.1rem, 5vw, 2.9rem);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--gold);
  display: block;
  margin-bottom: 0.3rem;
}
.count .l {
  font-family: var(--body);
  font-size: 0.86rem;
  line-height: 1.45;
  color: var(--ink-soft);
  display: block;
  max-width: 20ch;
}

/* ---- the blood numbers, plainly ---- */
.pcv {
  margin: 2.4rem 0;
  max-width: 430px;
  border-top: 1px solid var(--line);
}
.pcv-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 0.55rem 0.1rem;
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.85rem;
  letter-spacing: 0.03em;
  font-variant-numeric: tabular-nums;
}
.pcv-row .d { color: var(--muted); }
.pcv-row .v { color: var(--ink); }
.pcv-row.low .v { color: var(--gold); }
.pcv-note {
  font-family: var(--body);
  font-size: 0.82rem;
  color: var(--muted);
  padding-top: 0.5rem;
}

/* ---- receipts ---- */
.receipts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 1.8rem 0 0;
  max-width: 640px;
}
.ch .src { box-shadow: none; }
.ch .src:hover { background: var(--lake-soft); }

/* ---- chapter close rule: a quiet horizon line ---- */
.horizon {
  border: 0;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--line) 25%, var(--line) 75%, transparent);
  margin: 3.2rem auto 0;
  max-width: 380px;
}

/* ---- coda ---- */
.coda-cta {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  margin: 2.6rem 0 0;
  max-width: 640px;
}
@media (min-width: 640px) { .coda-cta { grid-template-columns: 1fr 1fr; } }
.coda-card {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 1.15rem 1.25rem 1.2rem;
  text-decoration: none;
  color: var(--ink);
  background: color-mix(in srgb, var(--surface) 55%, transparent);
  transition: border-color var(--duration-fast, 250ms) var(--ease-smooth-out, ease-out),
              translate var(--duration-fast, 250ms) var(--ease-smooth-out, ease-out);
}
.coda-card:hover, .coda-card:focus-visible { border-color: var(--gold); translate: 0 -2px; }
.coda-card .t { font-family: var(--display); font-size: 1.24rem; line-height: 1.3; }
.coda-card .d { font-family: var(--body); font-size: 0.92rem; color: var(--ink-soft); }
.coda-card .k {
  font-family: var(--mono);
  font-size: 0.64rem;
  letter-spacing: var(--track-eyebrow, 0.14em);
  text-transform: uppercase;
  color: var(--gold);
}

.finis {
  text-align: center;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 4rem 0 0;
  font-variant-numeric: tabular-nums;
}

/* ---- reading progress hairline ---- */
.tl-progress {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 2px;
  z-index: 40;
  pointer-events: none;
}
.tl-progress span {
  display: block;
  height: 100%;
  background: var(--gold-graphic, #C89A2B);
  transform-origin: 0 50%;
  transform: scaleX(0);
}

/* ---- reveals: this page breathes a little slower ---- */
html.js body[data-page="life"] .reveal.is-in {
  transition-duration: 800ms;
}

/* ---- scroll-driven depth (where supported) ----
   Small figures settle softly as they arrive; the wide and full-bleed
   images drift inside their frames at a different rate than the page —
   parallax without a single scroll listener. The hero portrait is excluded
   (it has its own one-time breath). */
@supports (animation-timeline: view()) {
  body[data-page="life"] .ch:not(.ch-hero) :is(.fig-inset, .fig-half, .fig-pair, .hero-figure) .fig-frame img {
    animation: tl-settle linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 42%;
  }
  @keyframes tl-settle {
    from { transform: scale(1.055) translateY(10px); }
    to   { transform: scale(1) translateY(0); }
  }
  body[data-page="life"] .ch :is(.fig-wide, .fig-bleed) .fig-frame img {
    scale: 1.14;
    animation: tl-parallax linear both;
    animation-timeline: view();
    animation-range: cover 0% cover 100%;
  }
  @keyframes tl-parallax {
    from { transform: translateY(-5%); }
    to   { transform: translateY(5%); }
  }
  body[data-page="life"] .ch-year {
    animation: tl-drift linear both;
    animation-timeline: view();
    animation-range: entry 0% cover 60%;
  }
  @keyframes tl-drift {
    from { transform: translateY(0.5em); opacity: 0; }
    to   { transform: translateY(0); opacity: 0.2; }
  }
}

/* ---- small screens ---- */
@media (max-width: 479px) {
  .ch { padding-inline: 1.1rem; }
  .ch p.prose, .ch .prose p { font-size: 1.0625rem; }
  .ch p.lead { font-size: 1.14rem; }
  .voice p { font-size: 1.22rem; }
  .voice-grand p { font-size: 1.4rem; }
  .hero-figure { max-width: 340px; }
}

/* ---- stillness for those who asked for it ---- */
@media (prefers-reduced-motion: reduce) {
  .scroll-cue { animation: none; opacity: 0.6; }
  body[data-page="life"], body[data-page="life"] .topnav,
  body[data-page="life"] .topnav .wordmark,
  body[data-page="life"] .topnav .fresh,
  body[data-page="life"] .topnav .links a { transition: none; }
  html.js .ch-hero .rise,
  html.js .ch-hero .fig-frame img { animation: none; }
  .sc-night .stars { animation: none; opacity: 0.8; }
  #scene .sc, .sc-vignette, .sc-grain { transition: opacity 0.01s, visibility 0.01s; }
  /* held-scene lines: a plain crossfade, no travel, no blur */
  html.js .beat-text,
  html.js .beat-text.is-on {
    translate: 0 0;
    filter: none;
    transition: opacity 400ms ease;
  }
  /* unveilings: no scrub — the photograph is simply there, sharp */
  html.js .unveil-media img,
  html.js .unveil-cap { animation: none; translate: 0 0; }
  @supports (animation-timeline: view()) {
    body[data-page="life"] .fig-frame img,
    body[data-page="life"] .ch-year { animation: none; }
    body[data-page="life"] .ch :is(.fig-wide, .fig-bleed) .fig-frame img { scale: 1; }
  }
  .coda-card { transition: none; }
}
