/* ============================================================
   Standing in Your Light — for Monique
   One continuous night: void → gravity → light → celebration
   ============================================================ */

/* ---------- Tokens ---------- */
:root {
  --void:           #070408;
  --night:          #100A10;
  --surface:        #191019;
  --champagne:      #E8C987;
  --champagne-deep: #C4953A;
  --pink:           #F2A9C4;
  --pink-bright:    #FFD9E6;
  --rose:           #D4899C;
  --candle:         #FFF3DC;
  --trench:         #CBA36E;   /* camel. night 3's coat, and only that */
  --ivory:          #F5EFE6;
  --ivory-dim:      rgba(245, 239, 230, 0.55);
  --ivory-faint:    rgba(245, 239, 230, 0.14);
  --champagne-dim:  rgba(232, 201, 135, 0.10);
  --border:         rgba(245, 239, 230, 0.08);
  --glow-gold:      0 0 18px rgba(232, 201, 135, 0.22);
  --glow-pink:      0 0 18px rgba(242, 169, 196, 0.26);

  --font-serif: 'Cormorant Garamond', 'Times New Roman', Georgia, serif;
  /* short aliases used by the newer sections (credits, birthday, countdown line, sky notes) */
  --serif:     var(--font-serif);
  --dust:      var(--ivory-dim);
  --parchment: var(--ivory);
  --ink:       var(--void);
  --blush:     var(--pink);
  --font-ui:    'Inter', -apple-system, sans-serif;

  --text-hero:    clamp(4.5rem, 16vw, 11rem);
  --text-display: clamp(2rem, 5vw, 3.5rem);
  --text-quote:   clamp(1.375rem, 3.2vw, 2rem);
  --text-letter:  clamp(1.1875rem, 1.5vw + 0.9rem, 1.4375rem);
  --text-label:   0.6875rem;

  --leading-letter: 1.9;
  --measure-letter: min(58ch, 88vw);
  --section-pad: clamp(6rem, 14vh, 10rem);

  --ease: cubic-bezier(0.25, 0.46, 0.45, 0.94);
  --dur-reveal: 1.1s;
  --dur-sweep: 1.6s;

  --moon-size: clamp(120px, 20vw, 240px);
  --mx: calc(100vw - var(--moon-size) - 6vw);
  --my: 11vh;
  --ms: 1;
  --phase: 0;   /* 0 = new moon, 1 = full — driven by scroll */
  /* warm champagne grade on the real lunar plates — one knob for the whole site.
     for true photographic silver: saturate(0.15) brightness(1.02) contrast(1.06) */
  --moon-grade: sepia(0.30) saturate(1.16) hue-rotate(-6deg) brightness(1.04) contrast(1.03);
}

/* ---------- Base ---------- */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  overflow-x: hidden;
}

body {
  background: var(--void);
  color: var(--ivory);
  font-family: var(--font-serif);
  font-size: var(--text-letter);
  line-height: var(--leading-letter);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

::selection { background: rgba(232, 201, 135, 0.25); color: var(--candle); }

main { position: relative; z-index: 2; }

section {
  padding: var(--section-pad) 1.5rem;
  padding-left: max(1.5rem, env(safe-area-inset-left));
  padding-right: max(1.5rem, env(safe-area-inset-right));
}

.label {
  font-family: var(--font-ui);
  font-size: var(--text-label);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--champagne);
}

/* ---------- Reveal engine ---------- */
.js [data-animate] {
  opacity: 0;
  transform: translateY(40px);
  transition:
    opacity var(--dur, var(--dur-reveal)) var(--ease),
    transform var(--dur, var(--dur-reveal)) var(--ease),
    filter var(--dur, var(--dur-reveal)) var(--ease),
    clip-path var(--dur, var(--dur-reveal)) var(--ease);
  transition-delay: var(--delay, 0s);
}
.js [data-animate].is-visible, .js [data-animate].ignite.lit { opacity: 1; transform: none; } /* ignite text only ever receives .lit from revealIO, never .is-visible */
/* arrivals from different directions (motion.js assigns these; authored values win) */
.js [data-animate="sink"] { transform: translateY(-40px); }
.js [data-animate="from-left"] { transform: translateX(-64px); }
.js [data-animate="from-right"] { transform: translateX(64px); }
.js [data-animate="scale-in"] { transform: scale(0.84); }
.js [data-animate="blur-in"] { transform: none; filter: blur(14px); }
.js [data-animate="blur-in"].is-visible, .js [data-animate="blur-in"].ignite.lit { filter: none; }
.js [data-animate="unmask"] { transform: none; opacity: 1; clip-path: inset(0 100% 0 0); }
.js [data-animate="unmask-up"] { transform: none; opacity: 1; clip-path: inset(100% 0 0 0); }
.js [data-animate="unmask"].is-visible, .js [data-animate="unmask-up"].is-visible, .js [data-animate="unmask"].ignite.lit, .js [data-animate="unmask-up"].ignite.lit { clip-path: inset(0); }

/* ---------- Beat 0 · Arrival veil ---------- */
.veil {
  position: fixed; inset: 0; z-index: 100;
  background: var(--void);
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 2.25rem;
  transition: opacity 1.1s var(--ease), visibility 1.1s;
}
.veil.lifted { opacity: 0; visibility: hidden; pointer-events: none; }
/* If script.js never runs (no JS, failed load, early throw), the page heals
   itself: the veil clears and every hidden state forces itself legible.
   script.js adds .js-live immediately, which disarms all of this. */
html:not(.js-live) .veil { animation: veil-auto 0.8s ease 2.8s forwards; }
@keyframes veil-auto { to { opacity: 0; visibility: hidden; } }
html.js:not(.js-live) [data-animate] { animation: force-visible 0.9s ease 3.5s forwards; }
html.js:not(.js-live) .ignite { animation: force-ignite 0.9s ease 3.5s forwards; }
@keyframes force-visible { to { opacity: 1; transform: none; } }
@keyframes force-ignite { to { background-position: 0% 0; } }

.veil-spark {
  width: 3px; height: 3px; border-radius: 50%;
  background: var(--candle);
  box-shadow: 0 0 18px 6px rgba(232, 201, 135, 0.55),
              0 0 42px 16px rgba(242, 169, 196, 0.18);
  animation: spark-bloom 2.4s var(--ease) infinite;
}
@keyframes spark-bloom {
  0%, 100% { transform: scale(1);   opacity: 0.75; }
  50%      { transform: scale(2.1); opacity: 1; }
}
.veil-name {
  font-family: var(--font-ui);
  font-size: var(--text-label);
  font-weight: 400;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--ivory-dim);
}

/* ---------- The sky ---------- */
#sky {
  position: fixed; inset: 0; z-index: 0;
  width: 100%; height: 100%;
  pointer-events: none;
}

/* Static sky for no-JS / reduced motion */
.static-stars {
  display: none;
  position: fixed; inset: 0; z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(1.5px 1.5px at 12% 22%, rgba(245,239,230,0.8), transparent 60%),
    radial-gradient(1px 1px     at 28% 64%, rgba(245,239,230,0.5), transparent 60%),
    radial-gradient(2px 2px     at 41% 12%, rgba(232,201,135,0.7), transparent 60%),
    radial-gradient(1px 1px     at 55% 45%, rgba(245,239,230,0.6), transparent 60%),
    radial-gradient(1.5px 1.5px at 63% 78%, rgba(242,169,196,0.5), transparent 60%),
    radial-gradient(1px 1px     at 72% 28%, rgba(245,239,230,0.55), transparent 60%),
    radial-gradient(2px 2px     at 84% 58%, rgba(232,201,135,0.6), transparent 60%),
    radial-gradient(1px 1px     at 91% 15%, rgba(245,239,230,0.5), transparent 60%),
    radial-gradient(1.5px 1.5px at 8%  85%, rgba(245,239,230,0.45), transparent 60%),
    radial-gradient(1px 1px     at 47% 92%, rgba(242,169,196,0.4), transparent 60%);
}
.no-js .static-stars, .reduced .static-stars { display: block; }
.no-js #sky, .reduced #sky { display: none; }

/* ---------- The moon ---------- */
.moon-layer {
  position: fixed; inset: 0; z-index: 1;
  overflow: hidden;
  pointer-events: none;
}
.moon {
  position: absolute; top: 0; left: 0;
  width: var(--moon-size); height: var(--moon-size);
  transform: translate(var(--mx), var(--my)) scale(var(--ms));
  transition: transform 1.8s var(--ease), opacity 1.2s var(--ease);
  pointer-events: none;
  opacity: 0.9;
}
.moon-halo {
  position: absolute; inset: -55%;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(232, 201, 135, 0.28) 0%,
    rgba(242, 169, 196, 0.14) 38%,
    rgba(242, 169, 196, 0.05) 55%,
    transparent 72%);
  opacity: calc(0.25 + var(--phase) * 0.75);
  transition: opacity 0.6s linear;
}
.moon-disc {
  position: absolute; inset: 0;
  border-radius: 50%;
  background:
    radial-gradient(circle at 38% 34%, rgba(255, 243, 220, 0.95), rgba(240, 226, 198, 0.88) 42%, rgba(214, 197, 168, 0.85) 78%, rgba(196, 178, 150, 0.85) 100%),
    radial-gradient(circle at 66% 68%, rgba(212, 137, 156, 0.14), transparent 42%);
  box-shadow: inset -8px -10px 32px rgba(7, 4, 8, 0.22);
}
.moon-shade {
  position: absolute; inset: 0;
  border-radius: 50%;
  overflow: hidden;
}
.moon-shade::after {
  content: '';
  position: absolute; inset: -8%;
  border-radius: 50%;
  background: radial-gradient(circle,
    var(--void) 58%,
    rgba(7, 4, 8, 0.85) 66%,
    rgba(7, 4, 8, 0) 76%);
  transform: translateX(calc(var(--phase) * -118%));
}
.reduced .moon, .no-js .moon { --phase: 0.8; }

/* The real moon — 25 NASA frames of the actual September 2026 lunation, one per
   night, each with its true terminator and crater relief. The CSS disc and its
   sliding shade stay underneath as the fallback if a frame never arrives. */
.moon-photo {
  position: absolute; inset: 0;
  background: var(--moon-img, none) center / contain no-repeat;
  filter: var(--moon-grade) drop-shadow(0 0 10px rgba(232, 201, 135, 0.22));
  opacity: 0;
  transition: opacity 1s var(--ease);
}
.moon.has-photo .moon-photo { opacity: 1; }
.moon.has-photo .moon-disc,
.moon.has-photo .moon-shade { display: none; }
/* scriptless visitors still get a real moon, matching the 0.8 phase above */
.no-js .moon-photo { background-image: url('/moon/v3/n20.webp'); opacity: 1; }
.no-js .moon-disc, .no-js .moon-shade { display: none; }

/* Scene positions — the moon travels with her */
body[data-scene="orbit"] {
  --mx: calc(50vw - var(--moon-size) / 2);
  --my: calc(44vh - var(--moon-size) / 2);
  --ms: 2.15;          /* she asked for it big: this is the moon's own moment */
}
/* the quote sits over the disc, so soften it just enough to read against */
body[data-scene="orbit"] .moon { opacity: 0.72; }
body[data-scene="finale"] {
  --mx: calc(50vw - var(--moon-size) / 2);
  --my: 7vh;
  --ms: 1.1;
}
/* While her constellation is on stage, the moon steps aside */
body[data-scene="goddess"] {
  --mx: calc(-0.3 * var(--moon-size));
  --my: 3vh;
  --ms: 0.85;
}
body[data-scene="goddess"] .moon { opacity: 0.55; }
/* On her birthday the cake is the subject, so the moon lifts clear of the words */
body[data-scene="bday"] {
  --mx: calc(0.34 * var(--moon-size));
  --my: -9vh;
  --ms: 0.78;
}
body[data-scene="bday"] .moon { opacity: 0.4; }
body[data-scene="orbit"] main > section:not(.orbit) { opacity: 0.35; }
main > section { transition: opacity 0.9s var(--ease); }

/* ---------- Beat 1 · Hero ---------- */
.hero {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
  position: relative;
  gap: 1.25rem;
}
.eyebrow {
  font-family: var(--font-ui);
  font-size: var(--text-label);
  font-weight: 400;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--champagne);
}
.hero-name {
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: var(--text-hero);
  line-height: 1.05;
  letter-spacing: -0.01em;
  background-image: linear-gradient(105deg,
    var(--ivory) 0%, var(--candle) 30%, var(--pink) 50%, var(--candle) 70%, var(--ivory) 100%);
  background-size: 250% 100%;
  background-position: 0% 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: name-shimmer 12s linear infinite;
}
@keyframes name-shimmer {
  0%   { background-position: 0% 0; }
  100% { background-position: 250% 0; }
}
/* the day itself: the gradient steps aside and every letter finds its own colour */
.hero-name.alight {
  background-image: none;
  -webkit-background-clip: initial;
  background-clip: initial;
  color: var(--ivory);
  animation: none;
}
.hero-name .name-letter {
  display: inline-block;
  transition: color 1.5s ease, text-shadow 1.5s ease;
  text-shadow: 0 0 0.35em rgba(0, 0, 0, 0);
}
.hero-name.alight .name-letter {
  text-shadow: 0 0 0.4em currentColor;
}
.hero-sub {
  font-style: italic;
  font-size: clamp(1.25rem, 2.4vw, 1.625rem);
  color: var(--ivory-dim);
}
.scroll-cue {
  position: absolute;
  bottom: calc(2.5rem + env(safe-area-inset-bottom));
  left: 50%; transform: translateX(-50%);
  width: 1px; height: 48px;
  overflow: hidden;
}
.scroll-cue span {
  display: block; width: 100%; height: 100%;
  background: linear-gradient(to bottom, transparent, var(--champagne));
  transform-origin: top;
  animation: cue-drop 2.6s var(--ease) infinite;
}
@keyframes cue-drop {
  0%   { transform: scaleY(0);   opacity: 0; }
  35%  { transform: scaleY(1);   opacity: 1; }
  70%  { transform: scaleY(1);   opacity: 1; }
  100% { transform: scaleY(1);   opacity: 0; }
}

/* ---------- Beat 2 · The Letter ---------- */
.letter {
  position: relative;
  max-width: var(--measure-letter);
  margin: 0 auto;
  display: flex; flex-direction: column;
  gap: 2em;
}
.letter::before {
  content: '';
  position: absolute; inset: -12% -30%;
  background: radial-gradient(ellipse at center, rgba(7, 4, 8, 0.72) 30%, transparent 75%);
  z-index: -1;
}
.letter-spark {
  width: 34px; height: 34px;
  align-self: center;
  color: var(--champagne);
  margin-bottom: 1rem;
}
.spark-path {
  stroke-dasharray: 1;
  transition: stroke-dashoffset 1.6s ease 0.2s;
}
.js .spark-path { stroke-dashoffset: 1; }
.letter-spark.is-visible .spark-path { stroke-dashoffset: 0; }

.salutation {
  font-style: italic;
  font-size: 1.18em;
}
.signoff { font-style: italic; margin-top: 0.5em; }
.signature {
  font-weight: 500;
  font-size: 1.3em;
  letter-spacing: 0.06em;
  margin-top: -1.2em;
}
.letter-rule {
  width: 32px; height: 1px;
  background: var(--champagne-deep);
  transform-origin: left;
}
.js .letter-rule[data-animate] { transform: scaleX(0) translateY(0); }
.js .letter-rule[data-animate].is-visible { transform: scaleX(1); }

/* The ignition — a warm light front sweeps through each paragraph */
.js .ignite {
  background-image: linear-gradient(100deg,
    var(--ivory) 0%,
    var(--ivory) 30%,
    var(--candle) 46%,
    var(--ivory-faint) 56%,
    var(--ivory-faint) 100%);
  background-size: 300% 100%;
  background-position: 100% 0;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  transition: background-position var(--dur-sweep) var(--ease);
}
.js .ignite.lit { background-position: 0% 0; }

/* Gleam phrases settle into glow after the sweep passes */
.gleam {
  transition: color 0.9s var(--ease), text-shadow 0.9s var(--ease);
}
.js .ignite .gleam { color: transparent; }
.js .ignite.lit .gleam {
  color: var(--champagne);
  text-shadow: var(--glow-gold);
  transition-delay: 1.3s;
}
.js .ignite.lit .gleam--pink {
  color: var(--pink);
  text-shadow: var(--glow-pink);
}
/* [[h:…]] — a heading standing at the top of a story paragraph.
   The parent .ignite paragraph paints its text through a background-clip sweep,
   which would leave any child transparent until the sweep lands, so this sets its
   own fill explicitly and stays readable from the first frame. */
.story-head {
  display: block;
  font-family: var(--serif);
  font-weight: 600;
  font-size: 1.2em;
  line-height: 1.3;
  letter-spacing: 0.01em;
  color: var(--trench);
  -webkit-text-fill-color: var(--trench);
  margin: 0 0 0.55em;
}

/* No-JS: letter is simply, fully legible */
.no-js .ignite { color: var(--ivory); }
.no-js .gleam { color: var(--champagne); }
.no-js .gleam--pink { color: var(--pink); }

/* ---------- Beat 3 · Orbit interlude ---------- */
.orbit {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex; align-items: flex-end; justify-content: center;
  padding-bottom: clamp(4rem, 12vh, 8rem);
}
.orbit-quote {
  max-width: min(30ch, 88vw);
  text-align: center;
}
.orbit-quote p {
  font-style: italic;
  font-size: var(--text-quote);
  line-height: 1.5;
  color: var(--ivory-dim);
}

/* ---------- Beat 4 · The Moment ---------- */
.moment {
  max-width: min(52ch, 88vw);
  margin: 0 auto;
  display: flex; flex-direction: column;
  gap: 1.5rem;
}
.moment-title {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: var(--text-display);
  line-height: 1.15;
}
.moment-body {
  position: relative;
  padding-left: clamp(1.25rem, 4vw, 2rem);
}
.moment-body::before {
  content: '';
  position: absolute; left: 4px; top: 0.4em; bottom: 0.4em;
  width: 2px;
  background: linear-gradient(to bottom, transparent, var(--champagne-deep) 20%, var(--champagne-deep) 80%, transparent);
}
.moment-body p {
  font-style: italic;
  color: var(--ivory);
}

/* ---------- Beat 5 · Moon Goddess constellation ---------- */
.goddess { text-align: center; }
.goddess-lede {
  font-style: italic;
  font-size: var(--text-quote);
  line-height: 1.6;
  color: var(--ivory);
  max-width: min(34ch, 88vw);
  margin: 1.5rem auto 0;
}
.star-field {
  position: relative;
  height: 140vh;
  margin-top: 3rem;
}
.star-word {
  position: absolute;
  left: var(--x); top: var(--y);
  font-style: italic;
  font-size: calc(var(--s, 1) * clamp(1rem, 2vw, 1.35rem));
  color: var(--ivory);
  opacity: 0.18;
  white-space: nowrap;
  transition: opacity 2s var(--ease), color 2s var(--ease), text-shadow 2s var(--ease);
  will-change: transform;
}
.star-word.glow {
  opacity: 1;
  color: var(--champagne);
  text-shadow: var(--glow-gold);
}
.star-word--pink.glow {
  color: var(--pink);
  text-shadow: var(--glow-pink);
}
.star-word.seen { opacity: 0.45; }
.constellation {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  overflow: visible;
}
/* dasharray/dashoffset use 3 (not the usual pathLength 1): with
   vector-effect: non-scaling-stroke inside a preserveAspectRatio="none"
   viewBox, Chrome measures dashes in screen space, so a dash of exactly 1
   can end short of the path's end. The extra headroom guarantees the
   draw-in always completes at every viewport aspect. */
.constellation-path {
  stroke: rgba(232, 201, 135, 0.65);
  stroke-width: 1.5px;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 3;
}
.js .constellation-path { stroke-dashoffset: 3; }
.constellation-path--leg { stroke: rgba(242, 169, 196, 0.5); }
.constellation.drawn .constellation-path {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 2.2s var(--ease) 0.3s;
}
.constellation.drawn .constellation-path--leg { transition-delay: 1.2s; }

/* The six stars of Libra */
.star-dot {
  position: absolute;
  left: var(--x); top: var(--y);
  width: 7px; height: 7px;
  margin: -3.5px 0 0 -3.5px;
  border-radius: 50%;
  background: var(--candle);
  box-shadow: 0 0 10px 2px rgba(232, 201, 135, 0.55);
  transition: opacity 1.2s var(--ease) 0.5s;
}
.star-dot--major {
  width: 9px; height: 9px;
  margin: -4.5px 0 0 -4.5px;
  box-shadow: 0 0 8px 2px rgba(232, 201, 135, 0.6),
              0 0 16px 4px rgba(242, 169, 196, 0.45);
}
.star-dot--minor { width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px; }
.js .star-field .star-dot { opacity: 0; }
.js .star-field .constellation.drawn ~ .star-dot { opacity: 0.95; }

/* Photo variant — enable with data-variant="photos" (see index.html notes) */
.goddess[data-variant="photos"] .star-field { display: none; }
.photo-field {
  position: relative;
  min-height: 120vh;
  margin-top: 3rem;
}
.photo-print {
  position: absolute;
  left: var(--x); top: var(--y);
  width: min(38vw, 300px);
  padding: 8px 8px 14px;
  background: rgba(25, 16, 25, 0.65);
  border: 1px solid var(--champagne-deep);
  transform: rotate(var(--tilt, 0deg));
  box-shadow: 0 20px 60px rgba(212, 137, 156, 0.10), 0 8px 32px rgba(0, 0, 0, 0.45);
}
.photo-print img {
  display: block; width: 100%; height: auto;
  filter: sepia(0.35) hue-rotate(-16deg) contrast(0.95) brightness(0.9);
  transition: filter 1.2s var(--ease);
}
.photo-print.glow img { filter: none; }

/* ---------- Beat 6 · Countdown → Celebration ---------- */
.finale {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
  gap: clamp(2.5rem, 6vh, 4rem);
  padding-top: calc(var(--moon-size) * 0.6 + 14vh);
}
.countdown { display: flex; flex-direction: column; align-items: center; gap: 2rem; }
.countdown.leaving, .prelude.leaving {
  opacity: 0;
  transition: opacity 0.9s var(--ease);
}

/* C1: the last hour and the last minute of the countdown lean in */
body[data-final] #cdSecs { color: var(--pink-bright); transition: color 1.2s var(--ease); }
body[data-final="minute"] .count-group, body[data-final="minute"] .count-sep {
  opacity: 0.35; transition: opacity 1.2s var(--ease);
}
body[data-final="minute"] .count-group:last-child { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  body[data-final="minute"] #cdSecs {
    animation: final-swell 1s var(--ease) infinite alternate;
    box-shadow: 0 0 26px rgba(242, 169, 196, 0.35), 0 8px 32px rgba(0, 0, 0, 0.45);
  }
  @keyframes final-swell { from { transform: scale(1); } to { transform: scale(1.08); } }
  /* and when midnight lands while she is watching, the first door drifts in */
  body.midnight-arrival .tonight { animation: midnight-in 2.4s var(--ease) both; }
  @keyframes midnight-in { from { opacity: 0; transform: translateY(24px); } }
}
.count-row {
  display: flex; align-items: stretch;
  gap: clamp(0.9rem, 3.5vw, 2rem);
}
.count-group {
  display: flex; flex-direction: column; align-items: center;
  gap: 0.6rem;
  min-width: 3.2ch;
}
.count-val {
  font-family: var(--font-ui);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  font-size: clamp(2rem, 6vw, 3.25rem);
  line-height: 1;
  color: var(--ivory);
  background: var(--surface);
  padding: 0.55em 0.35em;
  border-radius: 4px;
  box-shadow: 0 20px 60px rgba(212, 137, 156, 0.10), 0 8px 32px rgba(0, 0, 0, 0.45);
}
.count-val.tick { animation: tick 0.3s var(--ease); }
@keyframes tick {
  0% { opacity: 0; transform: translateY(6px); }
}
.count-unit {
  font-family: var(--font-ui);
  font-size: var(--text-label);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ivory-dim);
}
.count-sep {
  width: 1px;
  margin: 0.4em 0;
  background: linear-gradient(to bottom, transparent, rgba(232, 201, 135, 0.4), transparent);
}
/* No-JS: hide the empty digit boxes, show a plain line instead */
.nojs-count { display: none; font-style: italic; color: var(--ivory); }
.no-js .count-row { display: none; }
.no-js .nojs-count { display: block; }
.celebrate {
  display: flex; flex-direction: column; align-items: center;
  gap: 1.5rem;
}
.countdown[hidden], .celebrate[hidden] { display: none; }
.celebrate-kicker {
  font-style: italic;
  font-size: var(--text-quote);
  color: var(--ivory-dim);
}
.celebrate-title {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(2.5rem, 8vw, 5.5rem);
  line-height: 1.1;
}
.finale-echo {
  font-style: italic;
  font-size: clamp(1.0625rem, 2vw, 1.25rem);
  color: var(--ivory-dim);
  max-width: min(38ch, 88vw);
}

/* ---------- Beat 7 · Sign-off ---------- */
.signoff-footer {
  display: flex; flex-direction: column;
  align-items: center;
  gap: 1.25rem;
  padding: 3rem 1.5rem calc(3rem + env(safe-area-inset-bottom));
}
.spark-secret {
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; cursor: pointer;
  color: var(--champagne);
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  touch-action: none;
}
.spark-secret svg { width: 13px; height: 13px; opacity: 0.7; transition: opacity 0.4s, transform 0.4s var(--ease); }
.spark-secret:hover svg, .spark-secret:focus-visible svg { opacity: 1; transform: scale(1.25); }
.spark-secret:focus-visible { outline: 1px solid rgba(232, 201, 135, 0.5); outline-offset: 4px; border-radius: 50%; }
.footer-line {
  font-family: var(--font-ui);
  font-size: var(--text-label);
  letter-spacing: 0.14em;
  color: var(--ivory-dim);
}

/* ---------- Hidden bloom ---------- */
.bloom {
  position: fixed; inset: 0; z-index: 90;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.5s;
  overflow: hidden;
}
.bloom.blooming { opacity: 1; }
.bloom-light {
  position: absolute;
  left: var(--bx, 50%); top: var(--by, 50%);
  width: 120px; height: 120px;
  margin: -60px 0 0 -60px;
  border-radius: 50%;
  background: radial-gradient(circle,
    var(--pink-bright) 0%,
    var(--pink) 42%,
    rgba(242, 169, 196, 0.55) 58%,
    rgba(242, 169, 196, 0) 72%);
  transform: scale(0);
  transition: transform 1.2s var(--ease);
}
.bloom.blooming .bloom-light { transform: scale(30); }
.bloom-words {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  font-style: italic;
  font-size: clamp(2rem, 7vw, 3.5rem);
  color: #6B2140;
  opacity: 0;
  transition: opacity 0.6s var(--ease);
}
.bloom.blooming .bloom-words { opacity: 1; transition-delay: 0.7s; }

/* ============================================================
   25 Nights — advent additions
   ============================================================ */

/* ---------- Preview tag (test mode only) ---------- */
.preview-tag {
  position: fixed; right: 10px; bottom: 10px; z-index: 200;
  font-family: var(--font-ui); font-size: 0.625rem;
  letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--champagne); background: rgba(25, 16, 25, 0.8);
  border: 1px solid rgba(232, 201, 135, 0.35);
  padding: 4px 8px; border-radius: 3px;
  pointer-events: none;
}

/* ---------- Prelude teaser ---------- */
.prelude[hidden], .tonight[hidden] { display: none; }
.prelude {
  display: flex; flex-direction: column; align-items: center;
  text-align: center; gap: 1.5rem;
  padding-top: 0;
}
.prelude-line {
  font-family: var(--font-serif);
  font-size: var(--text-display);
  font-weight: 300;
  line-height: 1.2;
  max-width: min(22ch, 90vw);
}
.prelude-sub {
  font-style: italic;
  color: var(--ivory-dim);
  font-size: clamp(1.0625rem, 2vw, 1.25rem);
}

/* ---------- Tonight's door ---------- */
.tonight {
  display: flex; flex-direction: column; align-items: center;
  text-align: center; gap: 1.75rem;
}
.tonight-head {
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: var(--text-display);
  line-height: 1.15;
}
.door-btn {
  font-family: var(--font-ui);
  font-size: var(--text-label);
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--champagne);
  text-decoration: none;
  border: 1px solid rgba(232, 201, 135, 0.45);
  border-radius: 3px;
  padding: 1rem 2.25rem;
  transition: background 0.4s var(--ease), box-shadow 0.4s var(--ease), transform 0.4s var(--ease);
}
.door-btn:hover, .door-btn:focus-visible {
  background: var(--champagne-dim);
  box-shadow: 0 0 28px rgba(232, 201, 135, 0.2);
  transform: translateY(-2px);
}
.door-btn:active { transform: scale(0.98); }
.sky-link {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-style: italic;
  color: var(--ivory-dim);
  text-decoration: none;
  border-bottom: 1px solid rgba(245, 239, 230, 0.25);
  padding-bottom: 2px;
  transition: color 0.4s, border-color 0.4s;
}
.sky-link:hover, .sky-link:focus-visible { color: var(--pink); border-color: var(--pink); }

/* ---------- The birthday weave (hero, Sept 25) ---------- */
.weave { position: absolute; inset: 4% 3%; pointer-events: none; z-index: -1; }
.weave span {
  position: absolute; left: var(--x); top: var(--y);
  font-family: var(--font-serif); font-style: italic;
  font-size: clamp(0.8rem, 1.6vw, 1.05rem);
  /* centered on its point; home.js then clamps each one inside the field so a
     long fragment can never run off a phone edge (single line stays elegant) */
  white-space: nowrap;
  text-shadow: 0 0 10px rgba(7, 4, 8, 0.9), 0 0 3px rgba(7, 4, 8, 0.9);
  color: var(--ivory-dim);
  opacity: 0;
  transform: translate(-50%, 10px);
  transition: opacity 1.4s var(--ease) var(--delay, 0s), transform 1.4s var(--ease) var(--delay, 0s);
}
.weave span:nth-child(3n) { color: rgba(232, 201, 135, 0.7); }
.weave span:nth-child(5n) { color: rgba(242, 169, 196, 0.65); }
.weave span.in {
  opacity: var(--tw-base, 0.62);
  transform: translateX(-50%);
  /* ambient shimmer rides on brightness, leaving opacity free for the flare */
  animation: weave-shimmer var(--tw-dur, 7s) ease-in-out var(--tw-delay, 0s) infinite;
  transition: opacity 0.75s var(--ease), color 0.75s var(--ease), text-shadow 0.75s var(--ease);
}
@keyframes weave-shimmer {
  0%, 100% { filter: brightness(0.86); }
  50%      { filter: brightness(1.14); }
}
/* one at a time, a word catches the light all the way up */
.weave span.in.flare {
  opacity: 1;
  color: var(--candle);
  text-shadow:
    0 0 26px rgba(255, 243, 220, 0.75),
    0 0 52px rgba(242, 169, 196, 0.4),
    0 0 12px rgba(7, 4, 8, 0.9);
  transition: opacity 0.45s var(--ease), color 0.45s var(--ease), text-shadow 0.45s var(--ease);
}

/* ---------- Day page chrome ---------- */
.day-page {
  min-height: 100vh;
  min-height: 100dvh;
  max-width: min(66ch, 92vw);
  margin: 0 auto;
  display: flex; flex-direction: column;
  gap: clamp(2.5rem, 6vh, 4rem);
  padding-top: clamp(6rem, 14vh, 9rem);
  padding-bottom: 4rem;
}
.day-head { text-align: center; display: flex; flex-direction: column; gap: 1.1rem; }
.day-title {
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: var(--text-display);
  line-height: 1.12;
}
.day-mount { flex: 1; }
.day-nav {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  gap: 0.35rem 1.6rem;
  padding-top: 2rem;
}
.day-nav-link {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  font-style: italic;
  color: var(--ivory-dim);
  text-decoration: none;
  transition: color 0.4s;
}
.day-nav-link:hover, .day-nav-link:focus-visible { color: var(--champagne); }

/* Night 8: an invitation to turn the chimes on, woven into the hint line */
.sound-invite {
  display: inline; margin-left: 0.35em;
  background: none; border: none; border-bottom: 1px solid rgba(232, 201, 135, 0.4);
  color: var(--champagne); font-family: inherit; font-style: italic; font-size: inherit;
  cursor: pointer; padding: 0 0 1px;
  transition: opacity 0.7s var(--ease), color 0.4s, border-color 0.4s;
}
.sound-invite:hover, .sound-invite:focus-visible { color: var(--pink); border-color: rgba(242, 169, 196, 0.5); }
.sound-invite.gone { opacity: 0; }
.day-gate-line {
  text-align: center;
  font-style: italic;
  color: var(--ivory-dim);
  max-width: min(36ch, 88vw);
  margin: 0 auto;
}
.mini-count {
  text-align: center;
  font-family: var(--font-ui);
  font-size: 0.8125rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ivory-dim);
}
.mini-count span { color: var(--champagne); font-variant-numeric: tabular-nums; }
.noscript-note {
  text-align: center;
  padding: 30vh 1.5rem 4rem;
  font-style: italic;
  color: var(--ivory);
}
/* No-JS: don't strand visitors below an empty full-height shell */
.no-js .day-page, .no-js .sky-map-page { display: none; }
.noscript-nights {
  list-style: none;
  margin-top: 2rem;
  display: flex; flex-direction: column; gap: 0.4rem;
  color: var(--ivory-dim);
  font-style: normal;
  font-size: 0.9375rem;
}

/* ---------- Star birth — every night begins as dust ---------- */
.birth {
  position: relative;
  width: 200px; height: 200px;
  margin: -2.5rem auto -1.5rem;
}
.birth-canvas { width: 200px; height: 200px; display: block; }
.birth-canvas.settled { animation: star-settled 3.4s var(--ease) infinite; }
@keyframes star-settled {
  0%, 100% { opacity: 0.85; transform: scale(1); }
  50% { opacity: 1; transform: scale(1.05); }
}
.birth-flash {
  position: absolute; inset: -60%;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(255, 243, 220, 0.9) 0%,
    rgba(242, 169, 196, 0.4) 34%,
    rgba(232, 201, 135, 0.12) 55%,
    transparent 72%);
  opacity: 0;
  pointer-events: none;
}
.birth-flash.on { animation: birth-flash 1.1s var(--ease) forwards; }
@keyframes birth-flash {
  0% { opacity: 0; transform: scale(0.4); }
  25% { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.4); }
}
/* held breath while the star is being born */
.day-hold .day-head .label,
.day-hold .day-title,
.day-hold .day-mount,
.day-hold .day-nav { opacity: 0; }
.day-head .label, .day-title, .day-mount, .day-nav {
  transition: opacity 1.1s var(--ease);
}

/* ---------- Galaxy overture (full viewport) ---------- */
.overture {
  position: fixed; inset: 0; z-index: 60;
  background: rgba(7, 4, 8, 0.55);
  transition: opacity 1.3s var(--ease);
}
.overture-done { opacity: 0; pointer-events: none; }
.galaxy-canvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  transition: opacity 1.2s var(--ease);
}
.galaxy-fade { opacity: 0; }
.starbirth-video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 1s var(--ease);
  z-index: 1;
}
.starbirth-video.rolling { opacity: 1; }
.starbirth-video.outro { opacity: 0; transition: opacity 1.7s var(--ease); }
.overture .starbirth-video { position: fixed; }
.becoming-stage .starbirth-video { border-radius: 50%; }
.birth-flash--full {
  position: fixed; inset: -20%;
  border-radius: 0;
}
.birth-flash--reveal { position: absolute; inset: -30%; }

/* ---------- Becoming — the stars form her ---------- */
.reveal {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  margin: 0;
  /* she arrives inside a circle, the same shape as the moon above her */
  border-radius: 50%;
  overflow: hidden;
  box-shadow:
    0 0 0 1px rgba(232, 201, 135, 0.22),
    0 0 70px 14px rgba(232, 201, 135, 0.13),
    0 0 120px 30px rgba(242, 169, 196, 0.09),
    0 30px 90px rgba(7, 4, 8, 0.8);
}
.reveal[hidden] { display: none; }
.reveal-img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* framed on the crown, the moon and her face rather than the centre */
  object-position: 50% 28%;
  border-radius: 50%;
  -webkit-mask-image: radial-gradient(circle at 50% 50%, #000 93%, transparent 100%);
  mask-image: radial-gradient(circle at 50% 50%, #000 93%, transparent 100%);
  filter: sepia(0.22) hue-rotate(-12deg) saturate(0.94) brightness(0.5) contrast(1.02);
  opacity: 0;
  transform: scale(1.05);
  transition: opacity 2.8s var(--ease), transform 4.5s var(--ease), filter 3.5s var(--ease);
}
.reveal.revealed .reveal-img {
  opacity: 1;
  transform: scale(1);
  filter: sepia(0) hue-rotate(0deg) saturate(1) brightness(1);
}
.becoming {
  min-height: 150vh;
  display: flex; flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1.5rem;
}
.becoming-lede {
  font-style: italic;
  font-size: var(--text-quote);
  color: var(--ivory);
}
.becoming-stage {
  position: sticky;
  top: 10vh;
  width: min(92vw, 74vh, 560px);
  aspect-ratio: 1;
  height: auto;
}
.becoming-caption {
  position: sticky;
  bottom: 8vh;
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--text-quote);
  line-height: 1.5;
  max-width: min(30ch, 88vw);
}
.becoming-caption[hidden] { display: none; }

/* ---------- The daily hidden spark ---------- */
.day-spark {
  align-self: center;
  width: 44px; height: 44px;
  margin: 1.5rem auto 0;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; cursor: pointer;
  color: var(--champagne);
  -webkit-tap-highlight-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  touch-action: none;
}
.day-spark svg { width: 10px; height: 10px; opacity: 0.45; transition: opacity 0.4s, transform 0.4s var(--ease); }
.day-spark:hover svg, .day-spark:focus-visible svg { opacity: 1; transform: scale(1.3); }
.day-spark:focus-visible { outline: 1px solid rgba(232, 201, 135, 0.5); outline-offset: 4px; border-radius: 50%; }

/* ---------- Type: words ---------- */
.day-words { display: flex; flex-direction: column; gap: 2em; max-width: var(--measure-letter); margin: 0 auto; }
/* Poem variant (night 6): each paragraph is a stanza whose line breaks must
   survive rendering, hence pre-line - gleamHTML never emits <br>. */
.day-words--poem { gap: 1.7em; max-width: min(58ch, 94vw); }
.day-words--poem .ignite {
  white-space: pre-line;
  text-align: center;
  font-family: var(--font-serif);
  /* sized so the longest verse line (41ch) fits a 390px phone without wrapping */
  font-size: clamp(1.15rem, 4.4vw, 1.5rem);
  line-height: 1.8;
  letter-spacing: 0.01em;
}

/* ---------- Type: memory ---------- */
.day-memory { display: flex; flex-direction: column; gap: 1.75rem; max-width: min(46ch, 90vw); margin: 0 auto; }
.memory-date { text-align: center; }
.memory-thread { display: flex; flex-direction: column; gap: 1rem; }
.bubble {
  max-width: 34ch;
  padding: 0.9rem 1.2rem;
  border-radius: 16px;
  background: rgba(25, 16, 25, 0.6);
  font-size: 1.0625rem;
  line-height: 1.55;
}
.bubble--me {
  align-self: flex-start;
  border: 1px solid rgba(232, 201, 135, 0.3);
  border-bottom-left-radius: 4px;
  box-shadow: 0 0 22px rgba(232, 201, 135, 0.08);
}
.bubble--her {
  align-self: flex-end;
  border: 1px solid rgba(242, 169, 196, 0.35);
  border-bottom-right-radius: 4px;
  box-shadow: 0 0 22px rgba(242, 169, 196, 0.1);
}
.memory-note { font-style: italic; text-align: center; margin-top: 0.5rem; }
.memory-audio { display: flex; flex-direction: column; align-items: center; gap: 0.75rem; margin-top: 0.75rem; }

/* A1: her voice, behind one soft gold button instead of the grey iOS scrubber */
.voice { display: flex; align-items: center; gap: 0.9rem; width: min(320px, 80vw); }
.voice audio { display: none; }
.voice-play {
  flex: none; width: 52px; height: 52px; border-radius: 50%;
  border: 1px solid rgba(232, 201, 135, 0.45);
  background: radial-gradient(circle at 35% 30%, rgba(232, 201, 135, 0.16), rgba(25, 16, 25, 0.7));
  color: var(--champagne); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: 0 0 18px rgba(232, 201, 135, 0.12);
  transition: box-shadow 0.5s var(--ease), border-color 0.5s var(--ease), color 0.5s, transform 0.2s;
}
.voice-play:active { transform: scale(0.94); }
.voice-play:focus-visible { outline: 1px solid rgba(232, 201, 135, 0.5); outline-offset: 3px; }
.voice.playing .voice-play {
  border-color: rgba(242, 169, 196, 0.55);
  box-shadow: 0 0 26px rgba(242, 169, 196, 0.3);
  color: var(--pink-bright);
}
.voice-icon { width: 22px; height: 22px; }
.voice-icon--pause { display: none; }
.voice.playing .voice-icon--play { display: none; }
.voice.playing .voice-icon--pause { display: block; }
.voice-track {
  flex: 1; position: relative; cursor: pointer;
  height: 3px; padding: 14px 0; background-clip: content-box;
  background-color: rgba(245, 239, 230, 0.15); border-radius: 2px;
}
.voice-fill {
  position: absolute; left: 0; top: 14px; height: 3px; width: 0%;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--champagne), var(--pink));
  box-shadow: 0 0 8px rgba(242, 169, 196, 0.4);
  transition: width 0.25s linear;
}
.voice-hint {
  font-family: var(--serif); font-style: italic; font-size: 0.9rem;
  color: var(--dust); opacity: 0.8;
  transition: opacity 1.4s var(--ease);
}
.voice-hint.fade { opacity: 0; }

/* ---------- Type: photo ---------- */
.day-photo { display: flex; flex-direction: column; gap: 1.5rem; align-items: center; }
.photo-develop {
  padding: 10px 10px 16px;
  background: rgba(25, 16, 25, 0.65);
  border: 1px solid var(--champagne-deep);
  box-shadow: 0 20px 60px rgba(212, 137, 156, 0.1), 0 8px 32px rgba(0, 0, 0, 0.45);
  max-width: min(76vw, 460px);
}
.photo-develop img {
  display: block; width: 100%; height: auto;
  filter: brightness(0.06) saturate(0);
  will-change: filter;
  transition: filter 3.5s var(--ease);
}
.photo-develop.developed img.tone-warm { filter: sepia(0.25) hue-rotate(-14deg) brightness(0.98); }
.photo-develop.developed img.tone-moon { filter: saturate(0.85) brightness(1); }
.photo-develop.missing { display: none; }
.photo-caption { font-style: italic; text-align: center; max-width: min(40ch, 88vw); }

/* ---------- Type: reasons ---------- */
.day-reasons { display: flex; flex-direction: column; gap: 1.5rem; }
.reasons-intro { text-align: center; max-width: min(44ch, 88vw); margin: 0 auto; }
.reasons-hint, .reasons-count { text-align: center; }
.reasons-count { color: var(--ivory-dim); }
.reasons-field {
  position: relative;
  height: clamp(180px, 28vh, 260px);
}
.reason-star {
  position: absolute; left: var(--x); top: var(--y);
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
  background: none; border: none; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.reason-star::after {
  content: '';
  position: absolute; left: 50%; top: 50%;
  width: 7px; height: 7px; margin: -3.5px;
  border-radius: 50%;
  background: var(--candle);
  box-shadow: 0 0 12px 3px rgba(232, 201, 135, 0.5);
  transition: opacity 0.6s, transform 0.6s var(--ease);
  animation: star-breathe 2.8s var(--ease) infinite;
}
@keyframes star-breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.5); }
}
.reason-star.opened::after { opacity: 0.25; animation: none; }
.reason-star:focus-visible { outline: 1px solid rgba(232, 201, 135, 0.5); outline-offset: 2px; border-radius: 50%; }
.reason-stack { display: flex; flex-direction: column; gap: 1rem; max-width: min(46ch, 90vw); margin: 0 auto; width: 100%; }
.reason-card {
  padding: 1.1rem 1.4rem;
  background: rgba(25, 16, 25, 0.6);
  border-left: 2px solid var(--champagne-deep);
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.9s var(--ease), transform 0.9s var(--ease);
}
.reason-card.open { opacity: 1; transform: none; }
.reason-card p { font-style: italic; }

/* ---------- Type: poem ---------- */
.day-poem { display: flex; flex-direction: column; gap: 2.25rem; text-align: center; align-items: center; }
.poem-stanza { display: flex; flex-direction: column; gap: 0.35rem; }
.poem-line {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: clamp(1.25rem, 2.6vw, 1.625rem);
  line-height: 1.65;
}
.day-poem .letter-rule { align-self: center; transform-origin: center; }

/* ---------- Type: milestone ---------- */
.day-milestone { display: flex; flex-direction: column; gap: 1.75rem; text-align: center; align-items: center; }
.milestone-star { position: relative; width: 60px; height: 60px; }
.milestone-rule { width: 120px; align-self: center; transform-origin: center; }
.milestone-story { max-width: min(48ch, 90vw); }
.milestone-close { font-style: italic; color: var(--ivory-dim); }

/* ---------- Type: sky (interactive) ---------- */
.day-sky { display: flex; flex-direction: column; gap: 1.5rem; align-items: center; text-align: center; }
.trace-field, .catch-field {
  position: relative;
  width: min(88vw, 560px);
  -webkit-tap-highlight-color: transparent;
}
.trace-field { aspect-ratio: 4 / 3; }
.trace-lines { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.trace-lines line {
  stroke: rgba(232, 201, 135, 0.65);
  stroke-width: 1.5px;
  vector-effect: non-scaling-stroke;
  opacity: 0;
  transition: opacity 1.2s var(--ease);
}
.trace-lines line.drawn { opacity: 1; }
.trace-star {
  position: absolute; left: var(--x); top: var(--y);
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
  background: none; border: none; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.trace-star::after {
  content: '';
  position: absolute; left: 50%; top: 50%;
  width: 8px; height: 8px; margin: -4px;
  border-radius: 50%;
  background: var(--candle);
  box-shadow: 0 0 12px 3px rgba(232, 201, 135, 0.5);
  animation: star-breathe 2.6s var(--ease) infinite;
}
.trace-star.lit::after {
  animation: none;
  box-shadow: 0 0 18px 6px rgba(242, 169, 196, 0.55);
  background: var(--pink-bright);
}
.trace-star:focus-visible { outline: 1px solid rgba(232, 201, 135, 0.5); border-radius: 50%; }
/* Night 7 order guard: the next star in the sequence beckons with a deeper
   pulse; a star tapped out of turn shivers once and stays dark. */
.trace-star.next::after {
  animation: star-beckon 1.7s var(--ease) infinite;
}
@keyframes star-beckon {
  0%, 100% { transform: scale(1); box-shadow: 0 0 12px 3px rgba(232, 201, 135, 0.5); }
  50% { transform: scale(1.55); box-shadow: 0 0 24px 10px rgba(232, 201, 135, 0.85); }
}
.trace-star.nudge::after {
  animation: star-nudge 0.55s var(--ease);
}
@keyframes star-nudge {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-3px); }
  55% { transform: translateX(3px); }
}
@media (prefers-reduced-motion: reduce) {
  .trace-star.next::after { animation: none; box-shadow: 0 0 22px 9px rgba(232, 201, 135, 0.8); }
  .trace-star.nudge::after { animation: none; }
}
/* Night 7: after she traces her sign, the sky answers — Venus, his constellation,
   then two gold threads joining the signs through the planet that rules them both */
.trace-star--answer { pointer-events: none; }
.trace-star--answer::after { opacity: 0; animation: none; transition: opacity 1.1s var(--ease); }
.trace-star--answer.lit::after {
  opacity: 1;
  /* Taurus is drawn at true scale, and the four Hyades stars at the bull's face
     sit about 15px apart on a phone. The wider glow used elsewhere merges them
     into one smear, so his stars are drawn as tighter points. */
  width: 5px; height: 5px; margin: -2.5px;
  background: var(--candle);
  box-shadow: 0 0 8px 2px rgba(232, 201, 135, 0.6);
}
.trace-venus {
  position: absolute; left: var(--x); top: var(--y);
  width: 10px; height: 10px; margin: -5px;
  border-radius: 50%;
  background: var(--ivory);
  box-shadow: 0 0 22px 8px rgba(255, 243, 220, 0.75), 0 0 60px 22px rgba(232, 201, 135, 0.35);
  animation: venus-breathe 3.2s var(--ease) infinite;
}
.trace-venus[hidden] { display: none; }
@keyframes venus-breathe {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.35); }
}
.trace-lines line.answer { stroke: rgba(232, 201, 135, 0.45); }
.trace-lines line.gold {
  stroke: rgba(255, 227, 166, 0.85);
  stroke-width: 1.8px;
  transition: opacity 2s var(--ease);
}
.sky-phrase--answer { font-size: var(--text-body, 1rem); opacity: 0.9; }
.trace-story {
  display: grid; gap: 1.15em;
  max-width: 34em;
  margin: 2.4rem auto 0;
  text-align: left;
}
.trace-story[hidden] { display: none; }
.sky-phrase {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--text-quote);
}
.catch-field { height: clamp(240px, 42vh, 380px); overflow: hidden; }
.catch-spark {
  position: absolute; left: var(--x); top: -50px;
  width: 44px; height: 44px;
  background: none; border: none; cursor: pointer;
  animation: spark-fall var(--dur, 7s) linear forwards;
  -webkit-tap-highlight-color: transparent;
}
.catch-spark:focus-visible, .lantern:focus-visible {
  outline: 1px solid rgba(232, 201, 135, 0.5);
  outline-offset: 2px;
}
.catch-spark:focus-visible { animation-play-state: paused; border-radius: 50%; }
.catch-spark::after {
  content: '';
  position: absolute; left: 50%; top: 50%;
  width: 8px; height: 8px; margin: -4px;
  border-radius: 50%;
  background: var(--candle);
  box-shadow: 0 0 14px 4px rgba(232, 201, 135, 0.55);
}
@keyframes spark-fall {
  to { transform: translateY(calc(42vh + 120px)); }
}
.catch-row { min-height: 2.2em; display: flex; flex-wrap: wrap; gap: 0.45em; justify-content: center; max-width: min(40ch, 88vw); }
.caught-word {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: clamp(1.25rem, 2.6vw, 1.625rem);
  color: var(--ivory);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.8s var(--ease), transform 0.8s var(--ease);
}
.caught-word.in { opacity: 1; transform: none; }
.catch-row.complete .caught-word { color: var(--champagne); text-shadow: var(--glow-gold); }
.lantern-stage { position: relative; height: clamp(260px, 44vh, 400px); width: min(88vw, 560px); }
.lantern {
  position: absolute; left: 50%; bottom: 12%;
  width: 74px; height: 92px;
  transform: translateX(-50%);
  border: none; cursor: pointer;
  border-radius: 40% 40% 46% 46%;
  background: radial-gradient(circle at 50% 62%, rgba(255, 217, 230, 0.85), rgba(242, 169, 196, 0.45) 46%, rgba(232, 201, 135, 0.18) 72%, transparent 100%);
  opacity: 0.55;
  transition: opacity 0.6s var(--ease), box-shadow 0.6s var(--ease);
  -webkit-tap-highlight-color: transparent;
  touch-action: none;
}
.lantern.filling {
  opacity: 1;
  box-shadow: 0 0 44px 12px rgba(242, 169, 196, 0.4);
  animation: lantern-breathe 1.2s var(--ease) infinite;
}
@keyframes lantern-breathe {
  0%, 100% { transform: translateX(-50%) scale(1); }
  50% { transform: translateX(-50%) scale(1.07); }
}
.lantern-stage.released .lantern {
  animation: lantern-rise 6.5s var(--ease) forwards;
  opacity: 1;
  box-shadow: 0 0 44px 12px rgba(242, 169, 196, 0.4);
}
@keyframes lantern-rise {
  to { transform: translateX(-50%) translateY(calc(-44vh - 140px)) scale(0.55); opacity: 0; }
}
/* after its rise the stage lantern returns to the base, dim, ready to be held again */
.lantern-stage.released .lantern.back { animation: none; opacity: 0.55; box-shadow: none; transform: translateX(-50%); }
.lantern-stage.released .lantern.back.filling { animation: lantern-breathe 1.2s var(--ease) infinite; opacity: 1; box-shadow: 0 0 44px 12px rgba(242, 169, 196, 0.4); }
.lantern-wish {
  position: absolute; left: 50%; top: 42%;
  transform: translate(-50%, 0);
  width: min(34ch, 84vw);
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--text-quote);
  line-height: 1.55;
  opacity: 0;
  transition: opacity 1.6s var(--ease);
}
.lantern-wish.risen { opacity: 1; }

.milestone-lantern { margin-top: 2.6rem; }
.lantern-still { margin-top: 2.2rem; text-align: center; font-family: var(--font-serif); font-style: italic; font-size: var(--text-quote); line-height: 1.55; }

/* Night 22: three nights now, three stars above the title */
.three-stars { display: flex; justify-content: center; gap: 1.2rem; }
.three-stars i {
  width: 7px; height: 7px; border-radius: 50%; background: var(--candle);
  opacity: 0.16; transform: scale(0.7);
  transition: opacity 1.4s var(--ease), transform 1.4s var(--ease), box-shadow 1.4s var(--ease);
}
.three-stars i.lit { opacity: 1; transform: none; box-shadow: 0 0 14px 4px rgba(232, 201, 135, 0.55); animation: star-breathe 2.6s var(--ease) infinite; }
.three-stars i.lit:nth-child(2) { animation-delay: 0.9s; }
.three-stars i.lit:nth-child(3) { animation-delay: 1.8s; }
@media (prefers-reduced-motion: reduce) { .three-stars i { transition: none; } .three-stars i.lit { animation: none; } }

/* Night 20: the line, visible for a moment. Two stars, one at each edge;
   touch either and a thread of light draws across to the other, holds, fades. */
.line-field { margin: 2.6rem auto 0; text-align: center; }
.line-hint { transition: opacity 1.2s var(--ease); }
.line-hint.fade { opacity: 0.25; }
.line-sky { position: relative; width: min(88vw, 560px); aspect-ratio: 5 / 2; margin: 0.6rem auto 0; -webkit-tap-highlight-color: transparent; }
.line-thread {
  position: absolute; left: var(--lx, 0); top: var(--ly, 0); width: var(--len, 0); height: 1.5px; margin-top: -0.75px;
  background: linear-gradient(to right, rgba(242, 169, 196, 0.9), rgba(232, 201, 135, 0.75) 30%, rgba(232, 201, 135, 0.75) 70%, rgba(242, 169, 196, 0.9));
  box-shadow: 0 0 6px 1px rgba(232, 201, 135, 0.45);
  transform-origin: 0 50%;
  transform: rotate(var(--ang, 0deg)) scaleX(0);
  opacity: 0; pointer-events: none;
  transition: transform 2.2s var(--ease), opacity 1.6s var(--ease);
}
.line-field.held .line-thread { transform: rotate(var(--ang, 0deg)) scaleX(1); opacity: 1; }
.line-field.fading .line-thread { opacity: 0; }
.line-star {
  position: absolute; left: var(--x); top: var(--y); width: 44px; height: 44px;
  transform: translate(-50%, -50%); background: none; border: none; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.line-star::after {
  content: ''; position: absolute; left: 50%; top: 50%; width: 9px; height: 9px; margin: -4.5px; border-radius: 50%;
  background: var(--candle); box-shadow: 0 0 14px 4px rgba(232, 201, 135, 0.5);
  animation: star-breathe 2.6s var(--ease) infinite;
}
.line-star + .line-star::after { animation-delay: 1.3s; }
.line-star.lit::after { animation: none; background: var(--pink-bright); box-shadow: 0 0 20px 7px rgba(242, 169, 196, 0.6); }
.line-star:focus-visible { outline: 1px solid rgba(232, 201, 135, 0.5); border-radius: 50%; }
.line-field.still .line-thread { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .line-star::after { animation: none; }
  .line-thread { transition: none; }
}

/* ---------- Archive: Her Sky ---------- */
.sky-map-page {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex; flex-direction: column;
  gap: clamp(2rem, 5vh, 3rem);
  padding-top: clamp(6rem, 14vh, 9rem);
  padding-bottom: 3rem;
  max-width: min(640px, 94vw);
  margin: 0 auto;
}
.sky-map {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 4;
}
.map-lines { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.map-segment {
  stroke: rgba(232, 201, 135, 0.5);
  stroke-width: 1.2px;
  vector-effect: non-scaling-stroke;
  opacity: 0;
  transition: opacity 1.8s var(--ease) 0.4s;
}
.map-segment.drawn { opacity: 1; }
.map-star {
  position: absolute; left: var(--x); top: var(--y);
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.map-star .star-dot { transition: opacity 1.2s var(--ease) 0.5s, box-shadow 0.6s; }
.map-star.locked .star-dot { opacity: 0.22; box-shadow: none; background: var(--ivory-dim); }
.map-star.open:hover .star-dot, .map-star.open:focus-visible .star-dot {
  box-shadow: 0 0 16px 5px rgba(242, 169, 196, 0.5);
}
.map-star.unseen .star-dot { animation: star-breathe 2.4s var(--ease) infinite; }
.map-star:focus-visible { outline: 1px solid rgba(232, 201, 135, 0.5); outline-offset: 2px; border-radius: 50%; }
.map-caption {
  position: absolute; left: 50%; top: calc(100% - 2px);
  transform: translateX(-50%);
  white-space: nowrap;
  font-family: var(--font-ui);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  color: var(--ivory-dim);
  background: rgba(7, 4, 8, 0.85);
  padding: 3px 8px;
  border-radius: 3px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s;
}
.map-star:hover .map-caption, .map-star:focus-visible .map-caption { opacity: 1; }
/* on her phone the first touch lifts the caption; the second opens the night */
.map-star.peeked .map-caption { opacity: 1; }
.map-moon {
  position: absolute; left: var(--x); top: var(--y);
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
  display: flex; align-items: center; justify-content: center;
  text-decoration: none;
}
.map-moon-disc {
  width: 22px; height: 22px;
  border-radius: 50%;
  background: rgba(245, 239, 230, 0.12);
  box-shadow: inset -3px -3px 8px rgba(7, 4, 8, 0.6);
  transition: background 1.5s var(--ease), box-shadow 1.5s var(--ease);
}
.map-moon.open .map-moon-disc {
  background: var(--candle);
  box-shadow: 0 0 20px 6px rgba(232, 201, 135, 0.5), 0 0 34px 10px rgba(242, 169, 196, 0.3);
}

/* ---------- The Tour (key-gated, Mario only) ---------- */
.tour-page {
  max-width: min(960px, 94vw);
  margin: 0 auto;
  padding: clamp(4rem, 8vh, 6rem) 0 4rem;
  display: flex; flex-direction: column; gap: 2rem;
}
.tour-note { color: var(--ivory-dim); font-size: 0.9375rem; font-style: italic; max-width: 52ch; margin: 0 auto; }
.tour-extras, .tour-grid { display: grid; gap: 10px; }
.tour-extras { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
.tour-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
.tour-card {
  display: flex; flex-direction: column; gap: 0.5rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: rgba(25, 16, 25, 0.5);
  text-decoration: none;
  color: var(--ivory);
  transition: border-color 0.3s, transform 0.3s var(--ease);
}
.tour-card b { font-family: var(--font-serif); font-weight: 400; font-size: 1.2rem; line-height: 1.25; }
.tour-card:hover, .tour-card:focus-visible { border-color: rgba(232, 201, 135, 0.5); transform: translateY(-2px); }
.tour-tools { display: flex; flex-direction: column; align-items: center; gap: 1rem; margin-top: 1.5rem; text-align: center; }

/* ---------- Mobile ---------- */
@media (max-width: 640px) {
  :root {
    --moon-size: clamp(96px, 30vw, 140px);
    --mx: calc(100vw - var(--moon-size) - 7vw);
    --my: 9vh;
  }
  section {
    padding-left: max(1.25rem, env(safe-area-inset-left));
    padding-right: max(1.25rem, env(safe-area-inset-right));
  }
  .letter { gap: 2em; }
  .count-row { gap: 0.7rem; }
  .count-val { font-size: 1.75rem; }
  /* Re-seat the word labels so nothing collides at 375px
     (Libra's star dots and lines keep their percentage positions) */
  .star-field { height: 120vh; }
  .star-word:nth-of-type(1) { --x: 6%;  --y: 5%; }    /* peerless */
  .star-word:nth-of-type(2) { --x: 54%; --y: 15.5%; } /* luminous */
  .star-word:nth-of-type(3) { --x: 52%; --y: 25%; }   /* resplendent */
  .star-word:nth-of-type(4) { --x: 16%; --y: 33%; }   /* regal */
  .star-word:nth-of-type(5) { --x: 8%;  --y: 56.5%; } /* ethereal */
  .star-word:nth-of-type(6) { --x: 70%; --y: 52%; }   /* magnetic */
  .star-word:nth-of-type(7) { --x: 65%; --y: 74%; }   /* warm */
  .star-word:nth-of-type(8) { --x: 55%; --y: 6%; }    /* incandescent */
  .star-word:nth-of-type(9) { --x: 10%; --y: 81%; }   /* beguiling */
  .photo-print { width: min(58vw, 220px); }
}

/* ---------- Reduced motion — everything legible, nothing moves ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
  }
  .js [data-animate] { opacity: 1; transform: none; }
  .js .letter-rule[data-animate] { transform: none; }
  .js .ignite { background: none; color: var(--ivory); }
  .js .ignite .gleam { color: var(--champagne); }
  .js .ignite .gleam--pink { color: var(--pink); }
  .hero-name {
    animation: none;
    background: none;
    color: var(--ivory);
  }
  .veil { display: none; }
  .scroll-cue { display: none; }
  .spark-path, .constellation-path { stroke-dashoffset: 0; }
  .star-word { opacity: 0.45; }
  .js .star-field .star-dot { opacity: 0.95; }
  .birth-flash { display: none; }
  .day-hold .day-head .label, .day-hold .day-title,
  .day-hold .day-mount, .day-hold .day-nav { opacity: 1; }
  .becoming { min-height: 0; }
  .becoming-stage { display: none; }
  .moon { transition: none; }
  .weave span.in { animation: none; opacity: 0.7; }
  body[data-scene="orbit"] main > section:not(.orbit) { opacity: 1; }
}

/* ---------- Make a wish — a shooting star crosses her sky ---------- */
.wish {
  position: relative;
  min-height: 96vh;
  /* clear the fixed moon in the top-right before any of this text starts */
  padding-top: clamp(8rem, 22vh, 15rem);
  padding-bottom: 4rem;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
  gap: 1.25rem;
  overflow: hidden;
}
.wish-field {
  position: absolute; inset: 0;
  pointer-events: none;
  overflow: hidden;
}
/* the streak: a bright head with a tail trailing behind it */
.shooting-star::after {
  content: '';
  position: absolute; right: -3px; top: 50%;
  width: 9px; height: 9px; margin-top: -4.5px;
  border-radius: 50%;
  background: #FFF7E8;
  box-shadow: 0 0 14px 5px rgba(255, 243, 220, 0.95),
              0 0 34px 12px rgba(242, 169, 196, 0.5);
}
.shooting-star {
  position: absolute;
  top: var(--sy, 20%); left: var(--sx, -20%);
  width: clamp(260px, 62vw, 620px);
  height: 3px;
  opacity: 0;
  background: linear-gradient(90deg,
    rgba(255, 243, 220, 0) 0%,
    rgba(242, 169, 196, 0.16) 30%,
    rgba(242, 169, 196, 0.45) 62%,
    rgba(255, 243, 220, 0.9) 90%,
    rgba(255, 243, 220, 1) 100%);
  border-radius: 999px;
  filter: drop-shadow(0 0 8px rgba(255, 243, 220, 0.9))
          drop-shadow(0 0 22px rgba(242, 169, 196, 0.55));
  transform: rotate(var(--sa, 18deg));
  transform-origin: 100% 50%;
  animation: shoot var(--sd, 1.5s) cubic-bezier(0.3, 0, 0.5, 1) forwards;
}
@keyframes shoot {
  0%   { opacity: 0;   transform: rotate(var(--sa, 18deg)) translateX(0) scaleX(0.2); }
  12%  { opacity: 1; }
  78%  { opacity: 1; }
  100% { opacity: 0;   transform: rotate(var(--sa, 18deg)) translateX(var(--st, 128vw)) scaleX(1); }
}
.wish-lede {
  font-family: var(--font-serif); font-style: italic;
  font-size: var(--text-quote); color: var(--ivory);
  max-width: min(24ch, 86vw);
  opacity: 0; transition: opacity 1.4s var(--ease);
}
.wish-lede.on { opacity: 1; }
.wish-prompt {
  font-family: var(--font-serif);
  max-width: min(17ch, 86vw);
  font-size: clamp(1.9rem, 6vw, 3.2rem);
  line-height: 1.2; color: var(--candle);
  text-shadow: 0 0 30px rgba(232, 201, 135, 0.35);
  opacity: 0; transform: translateY(14px);
  transition: opacity 1.6s var(--ease), transform 1.6s var(--ease);
}
.wish-prompt.on { opacity: 1; transform: none; }
/* quieter than the main door button — this is a whisper, not a call to action */
.wish-btn {
  opacity: 0;
  transition: opacity 1.2s var(--ease), border-color 0.5s var(--ease), color 0.5s var(--ease);
  background: transparent;
  border: 1px solid rgba(232, 201, 135, 0.4);
  color: var(--champagne);
  box-shadow: none;
}
.wish-btn:hover, .wish-btn:focus-visible {
  border-color: rgba(232, 201, 135, 0.85);
  color: var(--candle);
}
.wish-btn.on { opacity: 1; }
.wish-btn[hidden] { display: none; }
.wish-reply {
  font-family: var(--font-serif); font-style: italic;
  font-size: var(--text-quote); line-height: 1.55;
  max-width: min(34ch, 88vw);
  opacity: 0; transition: opacity 2.2s var(--ease);
}
.wish-reply.on { opacity: 1; }
.wish-reply[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .shooting-star { display: none; }
  .wish-lede, .wish-prompt, .wish-btn, .wish-reply { opacity: 1; transform: none; }
}

/* the smallest phones need a shorter line so all 25 fragments find a row */
@media (max-width: 360px) {
  .weave span { font-size: 0.72rem; }
}

/* ---------- The night she won: a wall of her own words ---------- */
.day-wall { display: flex; flex-direction: column; align-items: center; gap: 1.5rem; }
.wall-intro { max-width: min(46ch, 88vw); text-align: center; }
.wall-frame {
  width: min(46ch, 92vw);
  display: flex; flex-direction: column; gap: 0.75rem;
}
.wall-count { align-self: center; letter-spacing: 0.2em; }
.wall-count span { color: var(--candle); font-variant-numeric: tabular-nums; }
.wall-scroll {
  height: min(52vh, 420px);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  border: 1px solid rgba(242, 169, 196, 0.28);
  background: rgba(242, 169, 196, 0.05);
  border-radius: 18px;
  padding: 1.25rem 1.4rem;
  /* it fades at both ends so it reads as endless rather than boxed */
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 8%, #000 92%, transparent 100%);
  mask-image: linear-gradient(180deg, transparent 0, #000 8%, #000 92%, transparent 100%);
}
.wall-scroll:focus-visible { outline: 2px solid var(--champagne); outline-offset: 3px; }
.wall-text {
  font-family: var(--font-serif);
  font-size: 0.95rem;
  line-height: 1.75;
  white-space: pre-line;
  color: var(--ivory-dim);
  margin: 0;
}
/* her last line lands in full colour */
.wall-text::first-line { color: var(--ivory); }

/* ---------- Catch mode carrying whole lines of hers ---------- */
.catch-intro {
  max-width: min(44ch, 88vw);
  text-align: center;
  margin: 0 auto 1.25rem;
}
.catch-row--lines {
  display: flex; flex-direction: column; align-items: flex-end;
  gap: 0.75rem;
  max-width: min(40ch, 92vw);
  margin: 1.5rem auto 0;
}
.caught-line {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 1s var(--ease), transform 1s var(--ease);
}
.caught-line.in { opacity: 1; transform: none; }
/* while she plays, only the newest line shows, in one fixed place under the sparks */
.catch-row--lines.collecting { display: none; }
.catch-latest {
  display: flex; flex-direction: column; align-items: center;
  gap: 0.6rem;
  min-height: 7.5rem;
  max-width: min(40ch, 92vw);
  margin: 1.25rem auto 0;
  transition: opacity 0.8s var(--ease);
}
.catch-latest.gone { opacity: 0; }
.catch-count { opacity: 0.7; }

/* ---------- The score: a quiet invitation, never a demand ---------- */
.score-toggle {
  position: fixed; left: max(1rem, env(safe-area-inset-left)); bottom: max(1rem, env(safe-area-inset-bottom));
  z-index: 80;
  display: inline-flex; align-items: center; gap: 0.55rem;
  min-height: 44px; padding: 0.5rem 0.95rem;
  border: 1px solid rgba(232, 201, 135, 0.28);
  border-radius: 999px;
  background: rgba(7, 4, 8, 0.55);
  backdrop-filter: blur(10px);
  color: var(--ivory-dim);
  font-family: var(--font-ui); font-size: var(--text-label);
  letter-spacing: 0.16em; text-transform: uppercase;
  cursor: pointer;
  transition: color 0.5s var(--ease), border-color 0.5s var(--ease), opacity 0.6s var(--ease);
  opacity: 0.55;
}
.score-toggle:hover, .score-toggle:focus-visible { opacity: 1; color: var(--ivory); border-color: rgba(232, 201, 135, 0.6); }
.score-toggle.playing { opacity: 1; color: var(--champagne); border-color: rgba(232, 201, 135, 0.55); }
.score-wave { display: inline-flex; align-items: flex-end; gap: 2px; height: 12px; }
.score-wave i {
  width: 2px; height: 4px; border-radius: 1px;
  background: currentColor; opacity: 0.75;
  transition: height 0.3s var(--ease);
}
.score-toggle.playing .score-wave i { animation: score-bob 1.5s ease-in-out infinite; }
.score-toggle.playing .score-wave i:nth-child(2) { animation-delay: 0.25s; }
.score-toggle.playing .score-wave i:nth-child(3) { animation-delay: 0.5s; }
@keyframes score-bob {
  0%, 100% { height: 4px; }
  50%      { height: 12px; }
}
@media (prefers-reduced-motion: reduce) {
  .score-toggle.playing .score-wave i { animation: none; height: 8px; }
}


/* ---------- The music credits ---------- */
.credits-page { background: var(--ink); }
.credits-wrap { max-width: 44rem; margin: 0 auto; padding: clamp(3rem, 9vw, 6rem) 1.4rem 5rem; }
.credits-title { font-family: var(--serif); font-weight: 300; font-size: clamp(1.9rem, 5vw, 2.7rem);
  text-align: center; margin: 0.6rem 0 1.2rem; color: var(--candle); }
.credits-lede { text-align: center; color: var(--dust); line-height: 1.8; margin: 0 auto 2.8rem; max-width: 32rem; }
.credits-list { list-style: none; margin: 0; padding: 0; }
.credits-list li { padding: 0.95rem 0; border-bottom: 1px solid rgba(232,201,135,0.09);
  color: var(--parchment); line-height: 1.65; font-size: 0.98rem; }
.credits-list b { display: block; font-weight: 400; font-size: 0.7rem; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--champagne-deep); margin-bottom: 0.3rem; }
.credits-list i { font-family: var(--serif); font-style: italic; color: var(--candle); }
.credits-list span { display: block; font-size: 0.8rem; color: var(--dust); opacity: 0.75; margin-top: 0.2rem; }
.credits-note { text-align: center; color: var(--dust); font-size: 0.9rem; margin: 2rem auto 0; max-width: 30rem; line-height: 1.7; }
.credits-back { text-align: center; margin-top: 3rem; }
.credits-back a { color: var(--champagne-deep); text-decoration: none; letter-spacing: 0.14em;
  text-transform: uppercase; font-size: 0.75rem; border-bottom: 1px solid rgba(232,201,135,0.3); padding-bottom: 2px; }

.footer-music { margin-top: 0.5rem; }
.footer-music a { color: var(--dust); opacity: 0.5; text-decoration: none; font-size: 0.78rem;
  letter-spacing: 0.1em; transition: opacity 0.4s var(--ease); }
.footer-music a:hover, .footer-music a:focus-visible { opacity: 0.9; color: var(--champagne-deep); }


/* ---------- The birthday itself: candles, her past birthdays, the exchange ---------- */
.bday { text-align: center; max-width: 46rem; margin: 0 auto;
  padding: clamp(3.5rem, 9vw, 6.5rem) 1.2rem clamp(2rem, 5vw, 3rem); }
.bday-heading { font-family: var(--serif); font-weight: 300; letter-spacing: -0.02em;
  font-size: clamp(1.8rem, 5vw, 2.6rem); color: var(--candle); margin: 0.5rem 0 0.5rem; }
.bday-sub { color: var(--dust); line-height: 1.7; margin: 0 auto 2.4rem; max-width: 26rem; }

.cake { display: block; margin: 0 auto; padding-top: 1.1rem; }   /* room between the caption and the flames */
.candles { display: flex; flex-wrap: nowrap; justify-content: space-between; align-items: flex-end;
  width: min(30rem, 92vw); margin: 0 auto 0.4rem; }
/* forty-eight of them (her age) on one cake: every other candle sits shorter so
   the flames land on two lines and read as a crowd, not as one bar of light */
.candle { position: relative; display: block; width: 3px; height: 26px; border-radius: 2px;
  transform: rotate(var(--lean, 0deg));
  background: linear-gradient(180deg, rgba(247,240,225,0.92), rgba(232,201,135,0.42)); }
.candle:nth-child(even) { height: 18px; }
.candle::before {                       /* the flame */
  content: ''; position: absolute; left: 50%; top: -10px; width: 7px; height: 11px;
  transform: translateX(-50%) scale(0); transform-origin: 50% 100%;
  border-radius: 50% 50% 46% 46%;
  background: radial-gradient(ellipse at 50% 72%, #fff8e2 0%, #ffdb92 42%, rgba(255,152,44,0.85) 72%, rgba(255,120,20,0) 100%);
  box-shadow: 0 0 10px 3px rgba(255,193,96,0.42);
  transition: transform 0.55s var(--ease); }
.candle.lit::before { transform: translateX(-50%) scale(1); animation: candle-flicker 1.7s ease-in-out infinite; }
.candle.out::before { transform: translateX(-50%) scale(0); box-shadow: none; }
.candle.out::after {                    /* one wisp of smoke, then gone */
  content: ''; position: absolute; left: 50%; top: -13px; width: 6px; height: 6px; border-radius: 50%;
  background: rgba(222,215,203,0.45); filter: blur(3px); animation: candle-smoke 1.5s ease-out forwards; }
@keyframes candle-flicker {
  0%, 100% { transform: translateX(-50%) scale(1, 1); }
  35%      { transform: translateX(-50%) scale(0.94, 1.08); }
  70%      { transform: translateX(-50%) scale(1.06, 0.96); }
}
@keyframes candle-smoke {
  from { opacity: 0.55; transform: translate(-50%, 0) scale(0.6); }
  to   { opacity: 0; transform: translate(-50%, -26px) scale(1.7); }
}
.cake-top { width: min(30rem, 92vw); height: 9px; margin: 0 auto; border-radius: 6px;
  background: linear-gradient(180deg, rgba(232,201,135,0.4), rgba(232,201,135,0.1));
  box-shadow: 0 10px 34px rgba(232,201,135,0.13); }

.bday-blow { margin-top: 1.7rem; background: none; border: 1px solid rgba(232,201,135,0.28);
  color: var(--champagne-deep); border-radius: 999px; padding: 0.72rem 1.5rem; min-height: 44px;
  font-family: inherit; font-size: 0.78rem; letter-spacing: 0.16em; text-transform: uppercase;
  cursor: pointer; transition: background 0.4s var(--ease), color 0.4s var(--ease),
    border-color 0.4s var(--ease), transform 0.2s var(--ease); }
.bday-blow:hover, .bday-blow:focus-visible { background: rgba(232,201,135,0.09); color: var(--candle);
  border-color: rgba(232,201,135,0.45); }
.bday-blow:active { transform: scale(0.98); }
.bday-blow.spent { opacity: 0; pointer-events: none; transition: opacity 0.6s var(--ease); }
.bday-after { font-family: var(--serif); font-size: clamp(1.15rem, 3vw, 1.5rem);
  color: var(--candle); margin-top: -2.6rem; min-height: 2.4em; display: flex;
  align-items: center; justify-content: center; }
.bday-after[hidden] { display: none; }

.bday-mem-label, .bday-gift-label { margin-top: clamp(2.5rem, 6vw, 4rem); }
.bday-memories { list-style: none; margin: 1rem auto 0; padding: 0; max-width: 34rem; text-align: left; }
.bday-memories li { padding: 0.85rem 0; border-bottom: 1px solid rgba(232,201,135,0.08);
  display: flex; gap: 0.9rem; align-items: baseline; }
.bday-year { flex: 0 0 auto; min-width: 2.6rem; color: var(--champagne-deep);
  font-size: 0.7rem; letter-spacing: 0.16em; }
.bday-quote { font-family: var(--serif); font-style: italic; font-size: 1.02rem;
  line-height: 1.6; color: var(--parchment); }
.bday-quote--me { color: var(--dust); font-style: normal; font-family: inherit; font-size: 0.95rem; }
.bday-promise { font-family: var(--serif); font-size: clamp(1.15rem, 3.2vw, 1.5rem);
  color: var(--candle); margin: 2rem auto 0; max-width: 30rem; line-height: 1.6; }
.bday-gift { margin-top: 1.2rem; }
@media (prefers-reduced-motion: reduce) {
  .candle.lit::before { animation: none; }
  .candle.out::after { display: none; }
}


/* ---------- What she is counting down to, said once, quietly ---------- */
.day-countdown {
  margin: 0.5rem 0 0;
  font-family: var(--serif);
  font-style: italic;
  font-size: 0.94rem;
  letter-spacing: 0.01em;
  color: var(--dust);
  opacity: 0.72;
}
.day-countdown[hidden] { display: none; }


/* ---------- The eclipse: Earth's shadow on the site's moon, in real time ---------- */
.moon-umbra {
  position: absolute; inset: 0; border-radius: 50%; overflow: hidden;
  opacity: 0; pointer-events: none;
  transition: opacity 1.2s var(--ease);
}
.moon.eclipsing .moon-umbra { opacity: 1; }
/* the umbra is ~2.7 moon radii: a soft-edged disc 5.4 diameters wide, centred by --ux/--uy in moon radii */
.moon-umbra::before {
  content: ''; position: absolute;
  /* left/top percentages resolve against the moon box, so --ux/--uy are in
     percent of the moon's diameter (1 moon radius = 50%) */
  left: calc(50% + var(--ux, 300%)); top: calc(50% + var(--uy, -300%));
  /* umbra radius = 2.7 moon radii, so its diameter is 2.7 moon diameters */
  width: 270%; height: 270%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(26, 8, 6, 0.96) 0%, rgba(26, 8, 6, 0.96) 88%,
    rgba(60, 22, 14, 0.75) 94%, rgba(80, 30, 18, 0) 100%);
}
/* penumbra: a faint dimming before and after the real shadow arrives */
.moon.eclipsing .moon-photo { filter: brightness(calc(1 - 0.18 * var(--pen, 0))) sepia(calc(0.35 * var(--depth, 0))) saturate(calc(1 + 0.9 * var(--depth, 0))) hue-rotate(calc(-14deg * var(--depth, 0))); transition: filter 1.2s var(--ease); }
.moon.eclipse-peak .moon-halo { opacity: 0.25; }

/* the line under the countdown that points at the real sky */
.sky-note {
  margin: 1.6rem auto 0; max-width: 28rem;
  font-family: var(--serif); font-style: italic; font-size: 1.02rem; line-height: 1.65;
  color: var(--candle); opacity: 0; transform: translateY(6px);
  transition: opacity 1.4s var(--ease), transform 1.4s var(--ease);
}
.sky-note.in { opacity: 0.9; transform: none; }
/* the two lines under tonight's door */
.door-sky { margin-top: 2rem; }
.door-sparks { margin-top: 0.7rem; font-size: 0.94rem; }
.door-sparks.in { opacity: 0.62; }
.sky-note[hidden] { display: none; }


/* ---------- A star for every night she came back ---------- */
.visit-stars { position: absolute; inset: 0; pointer-events: none; z-index: 1; }
.visit-star {
  position: absolute; left: var(--x); top: var(--y);
  width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px; border-radius: 50%;
  background: var(--candle);
  box-shadow: 0 0 8px 2px rgba(232, 201, 135, 0.55), 0 0 22px 6px rgba(242, 169, 196, 0.16);
  opacity: 0; transform: scale(0.2);
  transition: opacity 1.6s var(--ease) var(--delay, 0s), transform 1.6s var(--ease) var(--delay, 0s);
}
.visit-stars.in .visit-star { opacity: 0.9; transform: scale(1); }
.visit-stars.in .visit-star.born { animation: visit-born 2.8s var(--ease) var(--delay, 0s) 1; }
@keyframes visit-born {
  0%   { opacity: 0; transform: scale(0.2); }
  45%  { opacity: 1; transform: scale(2.6); box-shadow: 0 0 26px 10px rgba(255, 243, 220, 0.7), 0 0 60px 20px rgba(242, 169, 196, 0.3); }
  100% { opacity: 0.9; transform: scale(1); }
}
.visit-line {
  margin: 0.5rem auto 0; padding: 0 6vw; text-align: center;
  font-family: var(--serif); font-style: italic; font-size: 0.92rem;
  color: var(--dust); opacity: 0.7;
  position: relative; z-index: 1;
}
.visit-line[hidden] { display: none; }


/* ---------- Night 1: the first star, placed by her ---------- */
.first-sky {
  position: relative; height: min(52vh, 420px); margin: 0.4rem 0 1.6rem;
  border-radius: 18px; cursor: pointer; -webkit-tap-highlight-color: transparent;
  background: radial-gradient(ellipse at 50% 60%, rgba(242, 169, 196, 0.05), transparent 70%);
  outline: 1px dashed rgba(232, 201, 135, 0.16); outline-offset: -1px;
  transition: outline-color 1.2s var(--ease);
}
.first-sky.done { outline-color: transparent; cursor: default; }
.first-hint {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  margin: 0; padding: 0 2rem; text-align: center;
  font-family: var(--serif); font-style: italic; font-size: 1.05rem; line-height: 1.7; color: var(--dust);
  animation: first-hint-breathe 4s ease-in-out infinite;
  transition: opacity 0.9s var(--ease);
}
.first-hint.gone { opacity: 0; animation: none; }
@keyframes first-hint-breathe { 0%, 100% { opacity: 0.55; } 50% { opacity: 0.9; } }
.first-star {
  position: absolute; left: var(--x, 50%); top: var(--y, 50%);
  width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px; border-radius: 50%;
  background: var(--candle);
  box-shadow: 0 0 10px 3px rgba(232, 201, 135, 0.6), 0 0 30px 9px rgba(242, 169, 196, 0.22);
  opacity: 0; transform: scale(0.2);
}
.first-star[hidden] { display: none; }
.first-star.set { opacity: 1; transform: scale(1); transition: opacity 1s var(--ease), transform 1s var(--ease); }
.first-star.born { animation: first-born 2.6s var(--ease) forwards; }
@keyframes first-born {
  0%   { opacity: 0; transform: scale(0.2); }
  30%  { opacity: 1; transform: scale(4); box-shadow: 0 0 40px 16px rgba(255, 243, 220, 0.8), 0 0 90px 30px rgba(242, 169, 196, 0.35); }
  100% { opacity: 1; transform: scale(1); }
}
.first-placed { text-align: center; font-family: var(--serif); font-size: 1.08rem; line-height: 1.7; color: var(--parchment); margin: 0 0 1.6rem; }
.first-placed[hidden] { display: none; }
@media (prefers-reduced-motion: reduce) { .first-hint { animation: none; } .first-star.born { animation: none; opacity: 1; transform: none; } }


/* ---------- The first star, in her sky ---------- */
.map-first { position: absolute; left: var(--x); top: var(--y); width: 0; height: 0; pointer-events: none; }
.star-dot--first {
  position: absolute; left: 0; top: 0; width: 8px; height: 8px; margin: -4px 0 0 -4px; border-radius: 50%;
  background: var(--blush);
  box-shadow: 0 0 12px 3px rgba(242, 169, 196, 0.6), 0 0 30px 9px rgba(242, 169, 196, 0.2);
  animation: first-breathe 5s ease-in-out infinite;
}
@keyframes first-breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.25); } }
.map-caption--first { position: absolute; left: 12px; top: -0.55em; white-space: nowrap; opacity: 0.55; font-size: 0.64rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--blush); }
@media (prefers-reduced-motion: reduce) { .star-dot--first { animation: none; } }


/* ============ Per-night upgrades ============ */
/* a true thing about the sky, said quietly under the night */
.sky-fact {
  max-width: 30rem; margin: 2.2rem auto 0; text-align: center;
  font-family: var(--serif); font-style: italic; font-size: 1.04rem; line-height: 1.7;
  color: var(--candle); opacity: 0; transform: translateY(6px);
  transition: opacity 1.4s var(--ease), transform 1.4s var(--ease);
}
.sky-fact.in { opacity: 0.88; transform: none; }
.sky-fact.fade { opacity: 0.25; }

/* Night 8: the laugh becomes the smile.
   The clear beat above the head lives HERE, not on the inserted graphic, so the
   two halves stay separated even if laughTurn() returns early or throws. Padding
   rather than margin because a block child's top margin would collapse into the
   paragraph's own. Night 8 is the only night in all 25 that uses a story head,
   so this cannot reach any other night. */
.story-head { padding-top: 1.5em; }

.n8-turn {
  max-width: 11rem; margin: 2.4rem auto 2.6rem;
  opacity: 0; transition: opacity 1.2s var(--ease);
}
.n8-turn.in { opacity: 1; }
/* the face is a real button: sky.js's window pointerdown skips buttons, so a
   kiss never rings a chime, and Enter blows one from the keyboard. touch-action
   none is deliberate and small (11rem): a finger resting on the face can wander
   and the eyes follow it, the same bargain .wind-stage makes. */
.n8-face-btn {
  display: block; width: 100%; padding: 0; margin: 0; border: 0; background: none;
  color: inherit; cursor: pointer; border-radius: 50%;
  touch-action: none; user-select: none; -webkit-user-select: none;
  -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent;
}
.n8-face-btn:focus-visible { outline: 2px solid var(--champagne); outline-offset: 8px; }
.n8-face-btn:active .n8-face { transform: scale(0.97); }
.n8-face {
  display: block; width: 100%; height: auto; overflow: visible;
  transform-origin: 50% 60%; transition: transform 0.5s var(--ease);
}
.n8-face.kissing { transform: rotate(-6deg); }
.n8-stroke { fill: none; stroke: var(--champagne); stroke-width: 6; stroke-linecap: round; stroke-linejoin: round; }
.n8-glow-g circle, .n8-glow-g path { fill: none; stroke: rgba(232, 201, 135, 0.42); stroke-width: 15; stroke-linecap: round; filter: blur(5px); }
.n8-pupil { fill: var(--champagne); }
.n8-eye { transform-box: fill-box; transform-origin: center; transition: transform 0.16s ease-out; }
.n8-eye.shut { transform: scaleY(0.08); }
.n8-eye.shut .n8-pupil { opacity: 0; }
.n8-brow { transition: transform 0.4s var(--ease); }
.kissing .n8-brow { transform: translateY(-6px); }
.n8-blush { fill: var(--pink); opacity: 0; filter: blur(4px); transition: opacity 0.5s var(--ease); }
.kissing .n8-blush { opacity: 0.38; }
.n8-hint {
  display: block; text-align: center; margin-top: 1.1rem; color: var(--champagne);
  opacity: 0; transition: opacity 0.9s var(--ease);
}
.n8-hint.on { opacity: 1; }
.n8-hint.gone { opacity: 0; }
/* the hearts: born small at the lips, red then aquamarine, and they grow far
   past the face as they rise (the svg overflows on purpose) */
.n8-heart { transform-box: fill-box; transform-origin: center; animation: n8-heart-fly-r 2.6s cubic-bezier(0.2, 0.6, 0.3, 1) forwards; }
.n8-heart--red  { fill: #E8413E; filter: drop-shadow(0 0 3px rgba(232, 65, 62, 0.6)); }
.n8-heart--aqua { fill: #7FE3D4; filter: drop-shadow(0 0 3px rgba(127, 227, 212, 0.6)); animation-name: n8-heart-fly-l; }
@keyframes n8-heart-fly-r {
  0%   { opacity: 0; transform: translate(0, 0) scale(0.3); }
  10%  { opacity: 1; }
  100% { opacity: 0; transform: translate(40px, -160px) rotate(-14deg) scale(9); }
}
@keyframes n8-heart-fly-l {
  0%   { opacity: 0; transform: translate(0, 0) scale(0.3); }
  10%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-44px, -150px) rotate(14deg) scale(9); }
}

/* Night 13, second half: Let Me Count the Ways. Appended by nights.js after
   her seven caught lines. Reuses the reasons-night classes (.reasons-field,
   .reason-star, .reason-card) so it looks like a thing the site has always
   done; only the additions are here. */
.ways {
  display: flex; flex-direction: column; gap: 1.5rem;
  margin-top: 3.4rem;
  opacity: 0; transition: opacity 1.4s var(--ease);
}
.ways.in { opacity: 1; }
.ways-rule { align-self: center; width: 120px; transform-origin: center; }
.ways-head {
  margin: 0; text-align: center;
  font-family: var(--serif); font-weight: 600; font-size: 1.35rem; line-height: 1.3;
  letter-spacing: 0.01em; color: var(--trench); -webkit-text-fill-color: var(--trench);
}
.ways-intro, .ways-close { display: flex; flex-direction: column; gap: 1rem; }
.ways-p { text-align: center; max-width: min(44ch, 88vw); margin: 0 auto; }
.reasons-field--ways { height: clamp(300px, 46vh, 430px); }
.ways-n { text-align: left; margin: 0 0 0.45rem; color: var(--champagne-deep); }
.ways-card p:not(.label) { font-style: italic; }
.ways-reply { margin-top: 0.75rem; font-size: 0.95em; color: var(--ivory-dim); font-style: normal !important; }
.ways-reply span { color: var(--pink); font-style: italic; }
.ways-luther {
  color: var(--champagne); font-style: italic; text-decoration: none;
  border-bottom: 1px solid rgba(232, 201, 135, 0.4); padding-bottom: 1px;
  transition: color 0.4s, border-color 0.4s;
}
.ways-luther:hover, .ways-luther:focus-visible { color: var(--pink); border-color: rgba(242, 169, 196, 0.5); outline: none; }
.ways-close { margin-top: 0.5rem; }

/* Night 2: tilt */
.tilt-btn {
  display: block; margin: 1.2rem auto 0; background: none; border: 1px solid rgba(232,201,135,0.28);
  color: var(--champagne-deep); border-radius: 999px; padding: 0.6rem 1.3rem; min-height: 44px;
  font-family: inherit; font-size: 0.74rem; letter-spacing: 0.16em; text-transform: uppercase; cursor: pointer;
  transition: background 0.4s var(--ease), color 0.4s var(--ease);
}
.tilt-btn:hover, .tilt-btn:focus-visible { background: rgba(232,201,135,0.09); color: var(--candle); }
.tilt-btn.armed { border-color: transparent; color: var(--dust); font-style: italic; text-transform: none; letter-spacing: 0.02em; font-family: var(--serif); font-size: 0.95rem; }
/* the canvas is exactly viewport sized, so it grows a little while tilting and never shows a bare edge as it slides */
/* Night 10, long exposure: a finger held anywhere on the page must stay a hold.
   Without these, iPhone selects the paragraph under the thumb after half a second
   and the sky loses the touch (same trap as the photo frames, line ~2624). */
body.long-exposure, body.long-exposure * {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}
body.tilting #sky { scale: 1.4; transition: transform 0.18s linear, scale 1.4s var(--ease); will-change: transform; }
body.tilting .moon { transform: translate(var(--mx), var(--my)) scale(var(--ms)) var(--tilt, translate(0, 0)); transition: transform 0.18s linear, opacity 1.2s var(--ease); }
body.tilting .day-page { transform: translate(var(--tilt-x, 0), var(--tilt-y, 0)); transition: transform 0.18s linear; }

/* Night 3: dawn */
body.dawn::before {
  content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(180deg, rgba(60, 40, 70, 0) 0%, rgba(120, 70, 80, 0.35) 55%, rgba(244, 170, 110, 0.55) 100%);
  opacity: var(--dawn, 0); transition: opacity 2s var(--ease);
}
body.dawn #sky { opacity: calc(1 - var(--dawn, 0) * 0.9); transition: opacity 2s var(--ease); }

/* Night 3: her letters from that week. The morning rises with them, painted
   exactly like this night's real dawn above, on its own property so dawn()'s
   clock cannot put it out. */
body.mornings-lit::before {
  content: ''; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(180deg, rgba(60, 40, 70, 0) 0%, rgba(120, 70, 80, 0.35) 55%, rgba(244, 170, 110, 0.55) 100%);
  opacity: var(--morn, 0); transition: opacity 2.4s var(--ease);
}
body.mornings-lit #sky { opacity: calc(1 - var(--morn, 0) * 0.9); transition: opacity 2.4s var(--ease); }

.mornings { display: flex; flex-direction: column; gap: 1.5rem; margin: 3.4rem 0 2.6rem; opacity: 0; transition: opacity 1.4s var(--ease); }
.mornings.in { opacity: 1; }
.mornings-rule { align-self: center; width: 120px; transform-origin: center; }
.mornings-label { text-align: center; }
.mornings-intro, .mornings-close { display: flex; flex-direction: column; gap: 1rem; }
.mornings-hint, .mornings-count { text-align: center; transition: opacity 0.8s var(--ease); }
.mornings-hint.gone { opacity: 0; }
.mornings-close { opacity: 0; transition: opacity 1.4s var(--ease); margin-top: 0.5rem; }
.mornings-close.in { opacity: 1; }

/* the thread: one gold line down the left, a dot per letter */
.mornings-list {
  list-style: none; margin: 0.5rem 0 0; padding: 0 0 0 1.6rem;
  position: relative; display: flex; flex-direction: column; gap: 0.35rem;
}
.mornings-list::before {
  content: ''; position: absolute; left: 0.45rem; top: 0.6rem; bottom: 0.6rem; width: 1px;
  background: linear-gradient(180deg, rgba(232, 201, 135, 0.5), rgba(232, 201, 135, 0.12));
}
.morning { position: relative; }
.morning-seal {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.1rem 0.7rem;
  width: 100%; padding: 0.55rem 0.2rem; text-align: left;
  background: none; border: 0; color: inherit; font: inherit; cursor: default;
  opacity: 0.38; transition: opacity 0.8s var(--ease);
}
.morning.next .morning-seal { opacity: 1; cursor: pointer; }
.morning.open .morning-seal { opacity: 0.9; }
.morning-seal:focus-visible { outline: 1px solid rgba(232, 201, 135, 0.6); outline-offset: 4px; border-radius: 6px; }
.morning-dot {
  position: absolute; left: -1.6rem; top: 0.95rem; width: 9px; height: 9px; margin-left: 0.45rem;
  transform: translateX(-50%); border-radius: 50%;
  background: rgba(232, 201, 135, 0.35); transition: background 0.8s var(--ease), box-shadow 0.8s var(--ease);
}
.morning.next .morning-dot {
  background: var(--candle);
  box-shadow: 0 0 12px 3px rgba(232, 201, 135, 0.55), 0 0 30px 8px rgba(242, 169, 196, 0.2);
  animation: morning-wait 2.6s ease-in-out infinite;
}
.morning.open .morning-dot { background: var(--champagne); box-shadow: 0 0 10px 2px rgba(232, 201, 135, 0.35); }
@keyframes morning-wait { 0%, 100% { transform: translateX(-50%) scale(1); } 50% { transform: translateX(-50%) scale(1.6); } }
.morning-date {
  font-family: var(--serif); font-weight: 600; font-size: 1.15rem; letter-spacing: 0.01em;
  color: var(--champagne); -webkit-text-fill-color: var(--champagne);
}
.morning-when { font-size: var(--text-label); letter-spacing: 0.08em; text-transform: uppercase; color: var(--ivory-dim); }
.morning.next .morning-seal::after {
  content: 'open'; flex-basis: 100%; font-size: var(--text-label); letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--candle); margin-top: 0.1rem;
}

/* the letter itself unfolds under its date */
.morning-body { display: flex; flex-direction: column; gap: 0.9rem; padding: 0.3rem 0 1.2rem; }
.morning-body[hidden] { display: none; }
.morning-body .letter { opacity: 0; transform: translateY(14px); transition: opacity 1.1s var(--ease), transform 1.1s var(--ease); }
.morning-body.in .letter { opacity: 1; transform: none; }
.morning-body.in .letter--reply { transition-delay: 0.9s; }
.letter {
  max-width: 40ch; padding: 1rem 1.2rem; border-radius: 16px; text-align: left;
  background: rgba(25, 16, 25, 0.6); line-height: 1.6; font-size: 1.0625rem;
}
.letter--her { align-self: flex-end; border: 1px solid rgba(242, 169, 196, 0.35); border-bottom-right-radius: 4px; box-shadow: 0 0 22px rgba(242, 169, 196, 0.1); }
.letter--me { align-self: flex-start; border: 1px solid rgba(232, 201, 135, 0.3); border-bottom-left-radius: 4px; box-shadow: 0 0 22px rgba(232, 201, 135, 0.08); }
.mornings .letter .letter-p { margin: 0 0 0.7rem; font-style: italic; }
.mornings .letter .letter-p:last-child { margin-bottom: 0; }

/* Night 5: breathe the moon */
.moon.touchable { pointer-events: auto; cursor: pointer; }
.moon.touchable .moon-photo { transition: filter 2.4s var(--ease); }
/* while she holds, the full moon of her birthday blooms out of the crescent */
.moon.touchable .moon-photo::before {
  content: ''; position: absolute; inset: 0;
  background: url('/moon/v3/n25.webp') center / contain no-repeat;
  opacity: 0; transition: opacity 2.4s var(--ease);
}
.moon.touchable.breathing .moon-photo { filter: var(--moon-grade) brightness(1.2) drop-shadow(0 0 28px rgba(232, 201, 135, 0.55)); }
.moon.touchable.breathing .moon-photo::before { opacity: 1; }
.moon.touchable.breathing .moon-halo { opacity: 1; transform: scale(1.5); transition: transform 2.4s var(--ease), opacity 2.4s var(--ease); }
/* Night 6: touch "Moon Goddess" and the crescent in the corner becomes the full
   moon for a few seconds - the same glow as night 5's breathing, under its own
   class and with the moon left untouchable (pointer-events stay off). */
.moon.crowned .moon-photo {
  transition: filter 2.4s var(--ease);
  filter: var(--moon-grade) brightness(1.2) drop-shadow(0 0 28px rgba(232, 201, 135, 0.55));
}
/* the full plate exists only on night 6 (crown-ready, set by nights.js on mount),
   so no other page fetches it; it is born at opacity 0 so the crowning can fade */
.moon.crown-ready .moon-photo::before {
  content: ''; position: absolute; inset: 0;
  background: url('/moon/v3/n25.webp') center / contain no-repeat;
  opacity: 0; transition: opacity 2.4s var(--ease);
}
.moon.crowned .moon-photo::before { opacity: 1; }
.moon.crowned .moon-halo { opacity: 1; transform: scale(1.5); transition: transform 2.4s var(--ease), opacity 2.4s var(--ease); }

/* Night 11: draw your own */
.draw-own { margin-top: 2.6rem; text-align: center; }
.draw-hint { font-family: var(--serif); font-style: italic; color: var(--dust); line-height: 1.7; max-width: 28rem; margin: 0.5rem auto 1rem; }
.draw-field {
  position: relative; height: min(50vh, 400px); border-radius: 18px; cursor: crosshair; -webkit-tap-highlight-color: transparent;
  outline: 1px dashed rgba(232,201,135,0.16); outline-offset: -1px;
  background: radial-gradient(ellipse at 50% 50%, rgba(242,169,196,0.05), transparent 70%);
}
.draw-lines { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.draw-star {
  position: absolute; left: var(--x); top: var(--y); width: 8px; height: 8px; margin: -4px 0 0 -4px; border-radius: 50%;
  background: var(--pink-bright); box-shadow: 0 0 12px 3px rgba(242,169,196,0.6);
  opacity: 0; transform: scale(0.2); transition: opacity 0.8s var(--ease) var(--delay, 0s), transform 0.8s var(--ease) var(--delay, 0s);
}
.draw-star.in { opacity: 1; transform: scale(1); }
.draw-keep { margin-top: 1.2rem; background: none; border: 1px solid rgba(242,169,196,0.4); color: var(--pink); border-radius: 999px; padding: 0.7rem 1.5rem; min-height: 44px; font-family: inherit; font-size: 0.76rem; letter-spacing: 0.16em; text-transform: uppercase; cursor: pointer; }
.draw-keep:hover, .draw-keep:focus-visible { background: rgba(242,169,196,0.1); }
.draw-keep[hidden], .draw-kept[hidden] { display: none; }
.draw-kept { margin-top: 1.2rem; font-family: var(--serif); font-size: 1.05rem; color: var(--candle); }

/* Night 14: storm */
.rain { position: fixed; inset: 0; z-index: 2; pointer-events: none; opacity: 0.9; }
.rain.snow { opacity: 0.85; }
/* Night 14: the lights of Nevis, low on the water, closer as she reads (--near 0 to 1) */
.shore { position: relative; width: 100vw; max-width: 100vw; height: 15vh; min-height: 96px; pointer-events: none; --near: 0; }
.shore i {
  position: absolute; left: var(--x); bottom: calc(42% + var(--y)); width: 3px; height: 3px; margin: -1.5px; border-radius: 50%;
  background: #ffe6bd; opacity: calc(var(--near) * var(--k)); transform: scale(calc(0.5 + var(--near) * 0.8));
  box-shadow: 0 0 6px 2px rgba(255, 224, 170, calc(var(--near) * 0.6)), 0 0 20px 7px rgba(232, 201, 135, calc(var(--near) * 0.2));
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
  animation: shore-flicker 4.6s ease-in-out var(--d) infinite;
}
.shore::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 46%;
  /* the lights on the water: brightest just under them, gone before the band ends (no seam) */
  background: linear-gradient(to bottom, transparent 0%, rgba(232, 201, 135, calc(var(--near) * 0.16)) 28%, rgba(232, 201, 135, calc(var(--near) * 0.05)) 70%, transparent 100%);
}
@keyframes shore-flicker { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(0.72); } }
@media (prefers-reduced-motion: reduce) { .shore i { animation: none; transition: none; } }
body.flash::after { content: ''; position: fixed; inset: 0; z-index: 3; pointer-events: none; background: rgba(225, 232, 255, 0.22); animation: lightning 0.22s ease-out 1; }
@keyframes lightning { 0% { opacity: 1; } 35% { opacity: 0.25; } 55% { opacity: 0.9; } 100% { opacity: 0; } }

/* Night 16: typing */
.bubble.typing p::after { content: '|'; margin-left: 1px; opacity: 0.6; animation: caret 0.9s steps(1) infinite; }
@keyframes caret { 50% { opacity: 0; } }

/* Night 18: Venus */
.venus {
  position: fixed; left: 9vw; bottom: 22vh; z-index: 1; width: 9px; height: 9px; margin: -4.5px; border-radius: 50%;
  background: #fff6f0; box-shadow: 0 0 14px 5px rgba(255,236,225,0.75), 0 0 42px 14px rgba(242,169,196,0.35);
  opacity: 0; transition: opacity 3s var(--ease); pointer-events: none;
}
.venus.in { opacity: 1; }
/* Night 6: touch the word Venus and the evening star herself appears -
   low toward the west, exactly where the stanza says she keeps watch */
.venus-evening {
  position: fixed; left: 13vw; bottom: 34vh; z-index: 1; width: 11px; height: 11px; margin: -5.5px; border-radius: 50%;
  background: var(--ivory); box-shadow: 0 0 22px 8px rgba(255, 243, 220, 0.75), 0 0 62px 22px rgba(232, 201, 135, 0.35);
  opacity: 0; transition: opacity 2.4s var(--ease); pointer-events: none;
}
.venus-evening.on { opacity: 1; animation: venus-breathe 3.2s var(--ease) infinite; }
@media (prefers-reduced-motion: reduce) { .venus-evening.on { animation: none; } }

/* Night 3: the door at the top of the stairs. A warm light from the top edge of
   the screen on the last footfall, held, then dimmed. */
.door-light {
  position: fixed; left: 0; right: 0; top: 0; height: 40vh; z-index: 1; pointer-events: none;
  background: linear-gradient(to bottom, rgba(255, 214, 150, 0.42), rgba(255, 190, 110, 0.16) 40%, rgba(255, 180, 100, 0) 100%);
  box-shadow: inset 0 3px 0 rgba(255, 236, 200, 0.55);
  opacity: 0; transition: opacity 1.6s var(--ease);
}
.door-light.on { opacity: 1; }

/* Night 4: Folsom Lake. The calendar moon is mirrored on the water below it,
   blurred, brightened (night 4 is a thin crescent) and fading away from the
   shore, with a slow wobble; under it, the path of moonlight on the water. */
.moon.lake .moon-photo::after {
  content: ''; position: absolute; left: 0; right: 0; top: 104%; height: 100%;
  background: inherit;
  transform: scaleY(-1) translateY(0); filter: blur(3px) brightness(1.9);
  opacity: 0.45;
  -webkit-mask-image: linear-gradient(to top, #000 0%, transparent 78%);
  mask-image: linear-gradient(to top, #000 0%, transparent 78%);
  animation: lake-wobble 6.4s ease-in-out infinite;
  pointer-events: none;
}
.moon.lake::after {
  content: ''; position: absolute; left: -20%; width: 140%; top: 92%; height: 150%;
  background: radial-gradient(ellipse 34% 100% at 50% 0%, rgba(232, 201, 135, 0.30), rgba(232, 201, 135, 0.10) 40%, rgba(242, 169, 196, 0.03) 70%, transparent 100%);
  filter: blur(6px);
  animation: lake-path 5.2s ease-in-out infinite;
  pointer-events: none;
}
@keyframes lake-wobble {
  0%, 100% { transform: scaleY(-1) translateY(0) skewX(0deg); opacity: 0.45; }
  50% { transform: scaleY(-1) translateY(2px) skewX(1.4deg); opacity: 0.55; }
}
@keyframes lake-path {
  0%, 100% { opacity: 0.8; transform: scaleX(1); }
  50% { opacity: 1; transform: scaleX(1.18) translateY(3px); }
}
@media (prefers-reduced-motion: reduce) {
  .moon.lake .moon-photo::after, .moon.lake::after { animation: none; }
  .door-light { transition: none; }
}

/* Night 19: wind the moon */
.wind-stage { margin: 2.4rem auto 0; padding: 2rem 1rem; text-align: center; border-radius: 18px; touch-action: none; cursor: ew-resize;
  outline: 1px dashed rgba(232,201,135,0.16); outline-offset: -1px; }

/* Night 21: lanterns that stay */
.lantern-sky { position: fixed; inset: 0; z-index: 1; pointer-events: none; overflow: hidden; }
.sky-lantern {
  position: absolute; left: var(--x); bottom: -40px; width: 14px; height: 20px; border-radius: 7px 7px 9px 9px;
  background: radial-gradient(ellipse at 50% 60%, #ffe6a8, #f0a95a 60%, rgba(240,150,60,0.2));
  box-shadow: 0 0 18px 6px rgba(255, 190, 110, 0.45);
  opacity: 0; transform: translate(0, 0);
}
.sky-lantern.up { animation: sky-lantern-rise var(--dur, 40s) linear forwards; }
/* its own name: a second `lantern-rise` here used to override the stage
   lantern's rise above (later definition wins), so the stage lantern would
   have launched from opacity 0 to -120vh */
@keyframes sky-lantern-rise {
  0%   { opacity: 0; transform: translate(0, 0) scale(1); }
  6%   { opacity: 1; }
  100% { opacity: 0.55; transform: translate(var(--drift, 0px), -120vh) scale(0.45); }
}
/* Night 18: the lantern festival. Everything is scoped to `.fest`, which
   nights.js puts on the stage, so the plain lantern elsewhere is untouched. */
.lantern-stage.fest { height: clamp(380px, 56vh, 480px); user-select: none; -webkit-user-select: none; -webkit-touch-callout: none; }
.lantern-stage.fest .lantern {
  top: 5%; bottom: auto; width: 112px; height: 146px;
  border-radius: 46% 46% 30% 30% / 38% 38% 20% 20%;
  background: radial-gradient(ellipse at 50% 80%, #fff3c4 0%, #ffd27a 20%, rgba(245, 160, 70, 0.9) 52%, rgba(196, 92, 40, 0.8) 100%);
  opacity: 0.84;
  box-shadow: 0 0 36px 8px rgba(255, 180, 90, 0.3), inset 0 -20px 32px rgba(255, 240, 190, 0.55), inset 0 12px 26px rgba(140, 56, 18, 0.4);
  animation: fest-sway 5s ease-in-out infinite;
  transition: opacity 0.6s var(--ease), box-shadow 1.1s var(--ease);
}
/* the burner under the paper */
.lantern-stage.fest .lantern::before {
  content: ''; position: absolute; left: 24%; right: 24%; bottom: -7px; height: 9px; border-radius: 3px;
  background: #7a3d16; box-shadow: 0 -7px 16px 5px rgba(255, 232, 150, 0.85);
}
/* a generous place to put a thumb: the target is far bigger than the paper */
.lantern-stage.fest .lantern::after { content: ''; position: absolute; inset: -34px -54px; }
@keyframes fest-sway {
  0%, 100% { transform: translateX(-50%) rotate(-2deg); }
  50% { transform: translateX(-50%) rotate(2deg) translateY(-4px); }
}
.lantern-stage.fest .lantern.filling,
.lantern-stage.fest.released .lantern.back.filling {
  opacity: 1;
  background: radial-gradient(ellipse at 50% 78%, #fffbe6 0%, #ffe39a 26%, rgba(255, 184, 92, 0.96) 58%, rgba(224, 120, 52, 0.9) 100%);
  box-shadow: 0 0 70px 22px rgba(255, 176, 96, 0.5), inset 0 -20px 32px rgba(255, 246, 210, 0.8), inset 0 12px 26px rgba(140, 56, 18, 0.25);
  animation: lantern-breathe 1.2s var(--ease) infinite;
}
.lantern-stage.fest .lantern.ready,
.lantern-stage.fest.released .lantern.back.ready {
  background: radial-gradient(ellipse at 50% 74%, #ffffff 0%, #fff0b8 30%, rgba(255, 200, 110, 1) 62%, rgba(236, 138, 64, 0.95) 100%);
  box-shadow: 0 0 90px 30px rgba(255, 186, 104, 0.62), 0 0 0 3px rgba(242, 169, 196, 0.6), inset 0 -20px 32px rgba(255, 250, 225, 0.9);
}
.lantern-stage.fest.released .lantern {
  animation: lantern-rise 6.5s var(--ease) forwards; opacity: 1;
  box-shadow: 0 0 96px 36px rgba(255, 190, 100, 0.55), inset 0 -20px 32px rgba(255, 246, 210, 0.8);
}
.lantern-stage.fest.released .lantern.back {
  animation: fest-sway 5s ease-in-out infinite; opacity: 0.84;
  box-shadow: 0 0 36px 8px rgba(255, 180, 90, 0.3), inset 0 -20px 32px rgba(255, 240, 190, 0.55), inset 0 12px 26px rgba(140, 56, 18, 0.4);
}
/* the full sentence waits until the largest lantern has carried its words past */
.lantern-stage.fest .lantern-wish { top: 54%; transition-delay: 21s; }
/* the sky warms from below while they rise */
.lantern-sky::before {
  content: ''; position: absolute; inset: 0; opacity: 0; transition: opacity 3.2s var(--ease);
  background: radial-gradient(ellipse at 50% 112%, rgba(255, 170, 80, 0.26), rgba(255, 140, 70, 0.09) 46%, transparent 72%);
}
.lantern-sky.lit::before { opacity: 1; }
.sky-lantern--word {
  width: 30px; height: 40px; margin-left: -15px; bottom: -150px; border-radius: 12px 12px 8px 8px;
  box-shadow: 0 0 28px 10px rgba(255, 190, 110, 0.5);
}
.sky-lantern--word span {
  position: absolute; top: calc(100% + 9px); left: 50%; transform: translateX(-50%);
  width: 8.2em; text-align: center; font-family: var(--font-serif); font-style: italic; font-size: 1.08rem; line-height: 1.22;
  color: var(--champagne); text-shadow: 0 0 8px rgba(10, 6, 14, 1), 0 0 18px rgba(10, 6, 14, 0.95), 0 0 30px rgba(10, 6, 14, 0.9);
}
.sky-lantern--big { width: 48px; height: 64px; margin-left: -24px; box-shadow: 0 0 44px 18px rgba(255, 200, 120, 0.6); }
.sky-lantern--big span { font-size: 1.42rem; width: 9em; color: var(--pink); }
.sky-lantern--word.up { animation-name: sky-word-rise; }
@keyframes sky-word-rise {
  0%   { opacity: 0; transform: translate(0, 0); }
  5%   { opacity: 1; }
  84%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--drift, 0px), calc(-100vh - 320px)); }
}
@media (prefers-reduced-motion: reduce) {
  .sky-lantern.up { animation: none; opacity: 0.6; transform: translate(0, -60vh) scale(0.6); }
  body.flash::after { display: none; }
  .bubble.typing p::after { display: none; }
}


/* ============ The birthday finale ============ */
/* C1 */
.harvest-line { max-width: 30rem; margin: 1.4rem auto 0; font-family: var(--serif); font-style: italic; font-size: 1.06rem; line-height: 1.7; color: var(--candle); opacity: 0; transition: opacity 2s var(--ease) 1.2s; }
.harvest-line.in { opacity: 0.9; }
/* C2 + C3 */
.homecoming { margin-top: clamp(2.5rem, 7vw, 4.5rem); text-align: center; }
.home-field { position: relative; width: min(88vw, 420px); aspect-ratio: 3 / 4; margin: 1rem auto 0; }
.home-lines { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.home-seg { stroke-dasharray: 400; stroke-dashoffset: 400; transition: stroke-dashoffset 2.4s var(--ease); }
.home-own { stroke-dasharray: 400; stroke-dashoffset: 400; transition: stroke-dashoffset 2.4s var(--ease) 1.2s; }
.home-lines.drawn .home-seg, .home-lines.drawn .home-own { stroke-dashoffset: 0; }
.home-star {
  position: absolute; left: var(--sx); top: var(--sy); width: 6px; height: 6px; margin: -3px; border-radius: 50%;
  background: var(--candle); box-shadow: 0 0 8px 2px rgba(232,201,135,0.55);
  opacity: 0; transition: left 2.4s cubic-bezier(0.2, 0.7, 0.2, 1) var(--delay, 0s), top 2.4s cubic-bezier(0.2, 0.7, 0.2, 1) var(--delay, 0s), opacity 1s var(--ease) var(--delay, 0s);
}
.home-star.home { left: var(--x); top: var(--y); opacity: 1; }
.home-star.major { width: 9px; height: 9px; margin: -4.5px; box-shadow: 0 0 12px 4px rgba(232,201,135,0.65); }
.home-star.moon { width: 18px; height: 18px; margin: -9px; background: radial-gradient(circle at 40% 35%, #fff8e6, #e8c987 70%); box-shadow: 0 0 22px 8px rgba(255,243,220,0.5); }
.home-star.own { background: var(--pink-bright); box-shadow: 0 0 12px 4px rgba(242,169,196,0.6); }
/* B2: the star she placed herself, arriving alone after everyone else */
.home-star.first {
  width: 9px; height: 9px; margin: -4.5px;
  background: var(--pink-bright);
  box-shadow: 0 0 18px 7px rgba(242, 169, 196, 0.75);
  transition-duration: 3.4s, 3.4s, 1.6s;
}
.home-line { margin: 1.4rem auto 0; max-width: 28rem; font-family: var(--serif); font-size: 1.12rem; color: var(--candle); }
.home-line[hidden] { display: none; }
.home-first-line { font-style: italic; font-size: 1.02rem; color: var(--blush); }
/* C5 payoff: the sparks she found, and the ones that kept quiet */
.sparks-found { text-align: center; padding: clamp(3rem, 8vw, 5rem) 6vw; }
.sparks-lede {
  max-width: 30rem; margin: 1rem auto 0;
  font-family: var(--serif); font-size: 1.08rem; line-height: 1.7; color: var(--candle);
}
.sparks-list {
  list-style: none; margin: 2.2rem auto 0; padding: 0; max-width: 26rem;
  display: flex; flex-direction: column; gap: 1.2rem;
}
.sparks-item { display: flex; flex-direction: column; gap: 0.3rem; }
.sparks-n {
  font-family: var(--font-ui); font-size: 0.6875rem;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--champagne); opacity: 0.8;
}
.sparks-text {
  font-family: var(--serif); font-style: italic; font-size: 1rem;
  line-height: 1.6; color: var(--ivory-dim);
}

/* C4: the moon becomes the sky behind her */
body[data-scene="becoming"] {
  --mx: calc(50vw - var(--moon-size) / 2);
  --my: calc(42vh - var(--moon-size) / 2);
  --ms: 4.2;
}
body[data-scene="becoming"] .moon { opacity: 0.22; }
@media (prefers-reduced-motion: reduce) { .home-star { transition: none; } .home-seg, .home-own { transition: none; stroke-dashoffset: 0; } }

.memory-extra-label { margin-top: 2.4rem; }

/* Memory frame — photos through the years, one slow breath at a time */
.memory-frame {
  margin: 4.5rem auto 0;
  width: min(88vw, 440px);
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 1.6s cubic-bezier(0.25, 0.46, 0.45, 0.94), transform 1.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.memory-frame.developed { opacity: 1; transform: none; }
.memory-frame-stage {
  position: relative;
  width: 100%;
  /* Fixed. day.js no longer writes a height here, so the frame stays exactly
     where it is from the first photograph to the last and everything below it
     stays findable. object-fit: contain mats each picture inside it.
     --photo-ratio, when the night supplies one, shapes the frame to that
     night's own photographs. It is still one constant value for the night. */
  aspect-ratio: var(--photo-ratio, 4 / 5);
  border-radius: 22px;
  overflow: hidden;
  background: rgba(243, 239, 230, 0.03);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(243, 239, 230, 0.09), 0 0 60px rgba(232, 160, 180, 0.07);
}
/* ---------- Night 14: the crossing film, above the photographs ---------- */
.crossing {
  margin: 4rem auto 0;
  width: min(88vw, 440px);
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 1.6s cubic-bezier(0.25, 0.46, 0.45, 0.94), transform 1.6s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.crossing.in { opacity: 1; transform: none; }
.crossing-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: 22px;
  overflow: hidden;
  background: rgba(243, 239, 230, 0.03);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(243, 239, 230, 0.09), 0 0 60px rgba(232, 160, 180, 0.07);
}
.crossing-video { display: block; width: 100%; height: 100%; object-fit: cover; }
.crossing-stage::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  box-shadow: inset 0 0 34px rgba(7, 6, 11, 0.18);
  pointer-events: none;
}
/* the tap target covers the film: invisible while it plays, a soft mark when it waits */
.crossing-tap {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; padding: 0; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.crossing-tap i {
  width: 62px; height: 62px; border-radius: 50%;
  border: 1px solid rgba(232, 201, 135, 0.5);
  background: rgba(12, 10, 16, 0.42);
  backdrop-filter: blur(6px);
  opacity: 0; transform: scale(0.9);
  transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
  position: relative;
}
.crossing-tap i::after {
  content: ""; position: absolute; top: 50%; left: 54%;
  width: 0; height: 0; transform: translate(-50%, -50%);
  border-left: 17px solid var(--champagne, #E8C987);
  border-top: 11px solid transparent;
  border-bottom: 11px solid transparent;
}
.crossing.paused .crossing-tap i { opacity: 1; transform: none; }
.crossing-tap:focus-visible i { opacity: 1; outline: 2px solid var(--champagne, #E8C987); outline-offset: 4px; }
.crossing-label { margin-top: 0.9rem; text-align: center; }
/* the two full takes, behind a line she has to ask for */
.crossing-more { width: min(88vw, 440px); margin: 1.1rem auto 0; text-align: center; }
.crossing-more-btn {
  background: none; border: 0; padding: 0.45rem 0.1rem; cursor: pointer;
  color: var(--champagne, #E8C987); opacity: 0.66;
  border-bottom: 1px solid rgba(232, 201, 135, 0.32);
  transition: opacity 0.5s var(--ease);
  -webkit-tap-highlight-color: transparent;
}
.crossing-more-btn:hover { opacity: 1; }
.crossing-more-btn:focus-visible { opacity: 1; outline: 1px solid rgba(232, 201, 135, 0.6); outline-offset: 4px; }
.crossing-full[hidden] { display: none; }
.crossing-full {
  display: grid; gap: 1.9rem; margin-top: 1.7rem;
  opacity: 0; transform: translateY(16px);
  transition: opacity 0.9s var(--ease), transform 0.9s var(--ease);
}
.crossing-full.in { opacity: 1; transform: none; }
.crossing-full-note {
  margin: 0; text-align: center; line-height: 1.75;
  font-family: var(--font-ui); font-size: 0.8rem; letter-spacing: 0.01em;
  color: var(--champagne, #E8C987); opacity: 0.45;
}
.crossing-take { margin: 0; }
.crossing-take-video {
  display: block; width: 100%; aspect-ratio: 16 / 9;
  border-radius: 18px;
  background: rgba(243, 239, 230, 0.03);
  box-shadow: 0 20px 54px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(243, 239, 230, 0.09);
}
.crossing-take-label { margin-top: 0.75rem; text-align: center; opacity: 0.78; }
@media (prefers-reduced-motion: reduce) {
  .crossing { opacity: 1; transform: none; transition: none; }
  .crossing-full { opacity: 1; transform: none; transition: none; }
}
.memory-frame--tall { width: min(72vw, 300px); }
.memory-frame--tall .memory-frame-stage { aspect-ratio: var(--photo-ratio, 9 / 16); }
.memory-frame-stage::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 34px rgba(7, 6, 11, 0.18);
  pointer-events: none;
}
.memory-frame-stage img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  transition: opacity 1.8s ease;
}
@media (min-width: 900px) { .memory-frame { width: min(60vw, 520px); } }
.memory-frame-stage img.on { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .memory-frame-stage img.on { animation: ms-breathe 14s ease-in-out infinite alternate; }
  @keyframes ms-breathe { from { transform: scale(1); } to { transform: scale(1.045); } }
}
.memory-frame-label {
  text-align: center;
  margin-top: 1.1rem;
  color: var(--ivory-dim);
}

/* The photo frame answers to her thumb now: a swipe moves it, a tap moves it
   on, a held finger keeps a photograph still. pan-y matters, or a thumb that
   starts on a photograph can no longer scroll the page. */
/* -webkit-touch-callout is load-bearing on iPhone: without it a held finger
   raises the Save Image sheet instead of pausing the photograph, which is the
   same trap the night 5 press-and-hold hit. */
.memory-frame-stage {
  touch-action: pan-y;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
.memory-frame-stage img { -webkit-user-drag: none; -webkit-touch-callout: none; user-select: none; }
.memory-frame-stage:focus-visible {
  outline: 2px solid var(--champagne-deep);
  outline-offset: 5px;
}
/* same shape as the wall counter, quieter, so it reads as a page number and
   never competes with the photograph */
.memory-count {
  text-align: center;
  margin-top: 0.55rem;
  letter-spacing: 0.2em;
  color: var(--ivory-dim);
  opacity: 0.68;
}

/* A chevron on each side of the photograph, and one line beneath it. Until now
   every way to move was a gesture with nothing on screen to suggest it, so the
   frame looked like a slideshow she could only sit through. */
/* A fixed row under the frame, above the count. They used to be centred on the
   photograph, but fit() rewrites the stage height for every picture, so they
   moved each time one arrived and were hard to find. Off the photograph they no
   longer need a scrim to stay legible, so they take the same quiet gold ring as
   the other buttons on the site. */
.memory-arrows {
  display: flex;
  justify-content: center;
  gap: 2.6rem;
  margin-top: 1rem;
}
.memory-arrow {
  /* 44px is the smallest reliable touch target on a phone; the glyph inside
     stays small so the pair stay quiet under the photograph */
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid rgba(232, 201, 135, 0.3);
  border-radius: 50%;
  color: var(--champagne-deep);
  background: none;
  cursor: pointer;
  transition: background 0.4s ease, color 0.4s ease, border-color 0.4s ease, transform 0.2s ease;
}
.memory-arrow svg { width: 19px; height: 19px; display: block; }
.memory-arrow:hover,
.memory-arrow:focus-visible {
  background: rgba(232, 201, 135, 0.1);
  color: var(--candle);
  border-color: rgba(232, 201, 135, 0.5);
}
.memory-arrow:focus-visible { outline: 2px solid var(--champagne-deep); outline-offset: 3px; }
.memory-arrow:active { transform: scale(0.92); }
.memory-hint {
  text-align: center;
  margin-top: 0.5rem;
  font-family: var(--font-ui);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--ivory-dim);
  opacity: 0.52;
  transition: opacity 0.8s ease;
}
.memory-hint.gone { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .memory-arrow, .memory-hint { transition: none; }
}
.memory-count span { color: var(--candle); font-variant-numeric: tabular-nums; }

/* The birthday girl, then */
.bday-girl { margin: 2.6rem auto 0.6rem; width: min(52vw, 190px); }
.bday-girl img {
  width: 100%;
  border-radius: 50%;
  aspect-ratio: 1;
  object-fit: cover;
  object-position: 50% 30%;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(243, 239, 230, 0.12), 0 0 44px rgba(232, 160, 180, 0.12);
}
.bday-girl figcaption { text-align: center; margin-top: 0.9rem; color: var(--ivory-dim); }


/* Night-page story rhythm + inline photo slot */
.milestone-story p { margin-bottom: 2.4rem; }
.milestone-story p:last-child { margin-bottom: 0; }
.milestone-after { margin-top: 3rem; }
.photo-slot .memory-frame { margin: 3.8rem auto 3.2rem; }

/* Night 5 — the poem is alive */
.magic-word {
  font: inherit;
  letter-spacing: inherit;
  border: 0;
  padding: 0;
  cursor: pointer;
  background: linear-gradient(100deg, var(--champagne) 10%, var(--pink-bright) 35%, #fff 50%, var(--pink) 65%, var(--champagne) 90%);
  background-size: 300% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: ms-shimmer 4.5s linear infinite;
}
@keyframes ms-shimmer { from { background-position: 300% 0; } to { background-position: 0 0; } }
@media (prefers-reduced-motion: reduce) { .magic-word { animation: none; color: var(--champagne); background: none; } }
.poem-hint { text-align: center; margin: 2.4rem 0 0; color: var(--ivory-dim); }
/* A quiet provenance line under the night header - written by day.js only when
   the opening film carried a note (real footage, a named sky). */
.film-note {
  margin: 1.2rem auto 0;
  max-width: min(44ch, 86vw);
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 1.02rem;
  line-height: 1.65;
  color: var(--ivory-dim);
  text-align: center;
  opacity: 0;
  transition: opacity 1.8s var(--ease);
}
.film-note.lit { opacity: 1; }

.poem-symbol { display: flex; justify-content: center; margin: 0.7rem 0 0.25rem; }
.poem-symbol svg { display: block; height: auto; }
.poem-symbol.sym-ao svg { width: 96px; filter: drop-shadow(0 0 12px rgba(232, 201, 135, 0.35)); }
.poem-symbol.sym-yy svg { width: 84px; filter: drop-shadow(0 0 16px rgba(244, 234, 219, 0.3)); }
.sym-yy .yy-rotor { transform-box: fill-box; transform-origin: 50% 50%; animation: ms-yy-spin 14s linear infinite; }
@keyframes ms-yy-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .sym-yy .yy-rotor { animation: none; } }

/* ---------- Night 5: a real star, not a sparkle ---------- */
/* Needles of light that fade to nothing at the tips, a soft halo, a tiny core,
   and scintillation: fast, irregular, with the faint colour shift real stars have. */
.poem-symbol.sym-star svg { width: 84px; overflow: visible; }
.sym-star .st-all,
.sym-star .st-halo,
.sym-star .st-v,
.sym-star .st-h,
.sym-star .st-diag,
.sym-star .st-core,
.sym-star .st-cool { transform-box: fill-box; transform-origin: 50% 50%; }

/* five periods that share no common multiple, so the pattern never visibly repeats */
.sym-star .st-all  { animation: ms-st-scint 2.4s linear infinite; }
.sym-star .st-v    { animation: ms-st-v 3.7s ease-in-out infinite; }
.sym-star .st-h    { animation: ms-st-h 2.9s ease-in-out infinite; }
.sym-star .st-diag { animation: ms-st-d 4.3s ease-in-out infinite; }
.sym-star .st-halo { animation: ms-st-halo 5.1s ease-in-out infinite; }
.sym-star .st-core { animation: ms-st-core 1.9s ease-in-out infinite; }
.sym-star .st-cool { animation: ms-st-cool 6.7s linear infinite; }

/* brightness jitter — irregular on purpose; a smooth sine reads as a pulse, not a twinkle */
@keyframes ms-st-scint {
  0%   { opacity: 0.93; }  6%  { opacity: 0.58; }  11% { opacity: 1; }
  17%  { opacity: 0.76; }  23% { opacity: 0.97; }  29% { opacity: 0.52; }
  34%  { opacity: 0.88; }  41% { opacity: 1; }     47% { opacity: 0.64; }
  53%  { opacity: 0.94; }  60% { opacity: 0.72; }  66% { opacity: 1; }
  72%  { opacity: 0.55; }  79% { opacity: 0.89; }  85% { opacity: 0.68; }
  91%  { opacity: 0.99; }  100%{ opacity: 0.93; }
}
/* the needles lengthen and shorten independently — atmosphere, not a heartbeat */
@keyframes ms-st-v {
  0%, 100% { transform: scaleY(0.80) scaleX(1);    }
  38%      { transform: scaleY(1.16) scaleX(1.15); }
  61%      { transform: scaleY(0.90) scaleX(0.9);  }
  80%      { transform: scaleY(1.05) scaleX(1.05); }
}
@keyframes ms-st-h {
  0%, 100% { transform: scaleX(1.07) scaleY(1.05); }
  29%      { transform: scaleX(0.82) scaleY(0.9);  }
  55%      { transform: scaleX(1.17) scaleY(1.15); }
  78%      { transform: scaleX(0.93) scaleY(1);    }
}
@keyframes ms-st-d {
  0%, 100% { transform: scale(0.86); opacity: 0.30; }
  44%      { transform: scale(1.14); opacity: 0.62; }
  73%      { transform: scale(0.94); opacity: 0.38; }
}
@keyframes ms-st-halo {
  0%, 100% { transform: scale(0.93); opacity: 0.58; }
  43%      { transform: scale(1.10); opacity: 0.95; }
  70%      { transform: scale(1.00); opacity: 0.66; }
}
@keyframes ms-st-core {
  0%, 100% { transform: scale(0.88); opacity: 0.86; }
  50%      { transform: scale(1.18); opacity: 1;    }
}
/* the cool flash: real twinkling shifts colour, because the air bends each wavelength differently */
@keyframes ms-st-cool {
  0%, 18%, 100% { opacity: 0; }
  22%           { opacity: 0.55; }
  27%           { opacity: 0; }
  61%           { opacity: 0.42; }
  66%           { opacity: 0; }
  84%           { opacity: 0.30; }
  88%           { opacity: 0; }
}
.poem-symbol.sym-star svg { filter: drop-shadow(0 0 7px rgba(255, 241, 214, 0.5)); }

@media (prefers-reduced-motion: reduce) {
  .sym-star .st-all, .sym-star .st-v, .sym-star .st-h, .sym-star .st-diag,
  .sym-star .st-halo, .sym-star .st-core, .sym-star .st-cool { animation: none; }
  .sym-star .st-cool { opacity: 0; }
}

.dove-flight {
  position: fixed;
  left: 0; top: 0;
  width: 64px; height: 44px;
  z-index: 60;
  color: var(--ivory);
  filter: drop-shadow(0 0 14px rgba(245, 239, 230, 0.45));
  pointer-events: none;
  will-change: transform;
}
.dove-flight .dove-wing { transform-origin: 33px 21px; animation: ms-flap 0.5s ease-in-out infinite alternate; }
/* the filmed dove: black footage screened into the night, so only the bird shows */
.dove-flight.dove-real {
  width: 150px; height: auto;
  filter: none;
  mix-blend-mode: screen;
}
@keyframes ms-flap { from { transform: rotate(4deg); } to { transform: rotate(-38deg); } }
.dove-rest { transition: opacity 1.2s ease; animation: ms-hover 3s ease-in-out infinite alternate; }
@keyframes ms-hover { from { margin-top: 0; } to { margin-top: -18px; } }

.gem-veil {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(7, 4, 8, 0.92);
  opacity: 0;
  transition: opacity 0.8s ease;
}
.gem-veil.on { opacity: 1; }
.gem-stage { position: relative; width: min(80vw, 340px); text-align: center; }
/* the filmed moissanite: black footage screened over the dark veil */
.gem-video {
  width: 100%;
  display: block;
  mix-blend-mode: screen;
  transform: scale(0.4);
  transition: transform 1.1s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.gem-veil.on .gem-video { transform: scale(1); }
.gem-fallback { display: none; }
.gem-veil.gem-svg .gem-fallback { display: block; }
.gem-veil.gem-svg .gem-video { display: none; }
.gem-stage svg {
  width: 100%;
  transform: scale(0.4);
  transition: transform 1.1s cubic-bezier(0.25, 0.46, 0.45, 0.94);
  filter: drop-shadow(0 0 34px rgba(242, 169, 196, 0.5));
  animation: ms-fire 6s linear infinite;
}
.gem-veil.on .gem-stage svg { transform: scale(1); }
@keyframes ms-fire {
  0% { filter: hue-rotate(0deg) drop-shadow(0 0 34px rgba(242, 169, 196, 0.5)); }
  50% { filter: hue-rotate(180deg) drop-shadow(0 0 44px rgba(207, 232, 255, 0.55)); }
  100% { filter: hue-rotate(360deg) drop-shadow(0 0 34px rgba(242, 169, 196, 0.5)); }
}
.glint {
  position: absolute;
  width: 14px; height: 14px;
  background: radial-gradient(circle, #fff 0%, rgba(255,255,255,0) 65%),
              conic-gradient(from 0deg, transparent 0 40deg, #fff 45deg, transparent 50deg, transparent 130deg, #fff 135deg, transparent 140deg, transparent 220deg, #fff 225deg, transparent 230deg, transparent 310deg, #fff 315deg, transparent 320deg);
  border-radius: 50%;
  opacity: 0;
  animation: ms-glint 2.6s ease-in-out infinite;
}
.glint.g1 { left: 12%; top: 18%; animation-delay: 0.4s; }
.glint.g2 { right: 10%; top: 30%; animation-delay: 1.2s; }
.glint.g3 { left: 30%; bottom: 26%; animation-delay: 2s; }
.glint.g4 { right: 26%; bottom: 34%; animation-delay: 0.9s; }
@keyframes ms-glint { 0%, 100% { opacity: 0; transform: scale(0.4) rotate(0deg); } 50% { opacity: 1; transform: scale(1.6) rotate(45deg); } }
.gem-line { margin-top: 1.6rem; color: var(--ivory-dim); }
@media (prefers-reduced-motion: reduce) {
  .gem-stage svg { animation: none; transition: none; transform: scale(1); }
  .gem-video { transition: none; transform: scale(1); }
  .glint { animation: none; opacity: 0.8; }
  .dove-flight .dove-wing { animation: none; }
}

/* ---------- The sky, tonight: one small question per midnight ---------- */
.trivia {
  max-width: 30rem;
  margin: 0 auto;
  padding: clamp(2.5rem, 8vw, 4rem) 6vw 1rem;
  text-align: center;
  position: relative;
  z-index: 1;
}
.trivia-card { margin-top: 1.1rem; }
.trivia-q {
  font-family: var(--font-serif);
  font-size: clamp(1.25rem, 4.6vw, 1.55rem);
  line-height: 1.45;
  color: var(--ivory);
}
.trivia-answers {
  display: grid;
  gap: 0.65rem;
  margin: 1.4rem auto 0;
  max-width: 22rem;
}
.trivia-btn {
  appearance: none;
  font: inherit;
  font-size: 0.98rem;
  color: var(--ivory);
  background: transparent;
  border: 1px solid rgba(232, 201, 135, 0.35);
  border-radius: 999px;
  padding: 0.72rem 1.2rem;
  cursor: pointer;
  transition: border-color 0.35s ease, color 0.35s ease, opacity 0.45s ease,
              box-shadow 0.45s ease, transform 0.2s ease;
}
.trivia-btn:active { transform: scale(0.98); }
.trivia-btn.miss {
  opacity: 0.32;
  border-color: var(--ivory-faint);
  color: var(--ivory-dim);
}
.trivia-btn.right {
  color: var(--pink-bright);
  border-color: var(--pink);
  box-shadow: 0 0 22px rgba(242, 169, 196, 0.35), inset 0 0 14px rgba(242, 169, 196, 0.12);
}
.trivia-btn:disabled { cursor: default; }
.trivia-reveal {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 1.05rem;
  line-height: 1.65;
  color: var(--blush);
  margin: 1.5rem auto 0;
  max-width: 26rem;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 1.2s ease, transform 1.2s ease;
}
.trivia-reveal.show { opacity: 1; transform: none; }
.trivia-past-stack {
  margin-top: 2.4rem;
  padding-top: 1.6rem;
  border-top: 1px solid var(--ivory-faint);
  display: grid;
  gap: 1.6rem;
}
.trivia-past-q {
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ivory-dim);
  opacity: 0.75;
}
.trivia-past-a {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: 0.95rem;
  line-height: 1.6;
  color: var(--ivory-dim);
  margin-top: 0.4rem;
}
@media (prefers-reduced-motion: no-preference) {
  .trivia-btn.miss { animation: trivia-shake 0.4s ease; }
  @keyframes trivia-shake {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-5px); }
    60% { transform: translateX(4px); }
  }
}
@media (prefers-reduced-motion: reduce) {
  .trivia-reveal { transition: none; transform: none; }
}

/* ---------- Tonight only: the eclipse reminder ---------- */
.eclipse-note {
  max-width: 32rem;
  margin: clamp(1.5rem, 5vw, 2.5rem) auto clamp(3rem, 10vw, 5rem);
  padding: 1.6rem 1.5rem 1.5rem;
  text-align: center;
  position: relative;
  z-index: 1;
  border: 1px solid rgba(214, 106, 82, 0.38);
  border-radius: 18px;
  background: rgba(60, 20, 16, 0.16);
  box-shadow: 0 0 34px rgba(196, 74, 54, 0.14), inset 0 0 26px rgba(196, 74, 54, 0.07);
}
.eclipse-head {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(1.35rem, 5vw, 1.7rem);
  color: #E8A48F;
  margin-top: 0.7rem;
  text-shadow: 0 0 18px rgba(214, 106, 82, 0.35);
}
.eclipse-line {
  font-family: var(--font-serif);
  font-size: 1.05rem;
  line-height: 1.6;
  color: var(--ivory);
  margin: 0.9rem auto 0;
  max-width: 26rem;
}
.eclipse-times {
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ivory-dim);
  margin-top: 1.1rem;
}
@media (prefers-reduced-motion: no-preference) {
  .eclipse-note { animation: eclipse-breathe 6s ease-in-out infinite; }
  @keyframes eclipse-breathe {
    0%, 100% { box-shadow: 0 0 34px rgba(196, 74, 54, 0.14), inset 0 0 26px rgba(196, 74, 54, 0.07); }
    50% { box-shadow: 0 0 46px rgba(196, 74, 54, 0.24), inset 0 0 30px rgba(196, 74, 54, 0.11); }
  }
}

/* ---------- Night 4: the owl, alive ---------- */
.owl-word {
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; letter-spacing: inherit; color: var(--candle);
  border-bottom: 1px dashed rgba(232, 201, 135, 0.45);
  text-shadow: 0 0 16px rgba(232, 201, 135, 0.4);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.4s ease, text-shadow 0.4s ease;
}
.owl-word:hover, .owl-word:focus-visible {
  color: var(--champagne);
  text-shadow: 0 0 24px rgba(232, 201, 135, 0.7);
}
.owl-word:focus-visible { outline: 1px solid rgba(232, 201, 135, 0.5); outline-offset: 3px; }

/* Night 2 · the word Christmas, with the bells still inside it */
.bell-word {
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; letter-spacing: inherit; color: var(--candle);
  border-bottom: 1px dashed rgba(232, 201, 135, 0.45);
  text-shadow: 0 0 16px rgba(232, 201, 135, 0.4);
  cursor: pointer;
  transform-origin: 50% 0;
  -webkit-tap-highlight-color: transparent;
  transition: color 0.4s ease, text-shadow 0.4s ease;
}
.bell-word:hover, .bell-word:focus-visible {
  color: var(--champagne);
  text-shadow: 0 0 24px rgba(232, 201, 135, 0.7);
}
/* Until she has rung it once: a solid underline instead of a dashed one, and a
   slow breath, so the word reads as touchable on a phone where there is no hover. */
.bell-word.unfound {
  border-bottom-style: solid;
  border-bottom-color: rgba(232, 201, 135, 0.75);
}
@media (prefers-reduced-motion: no-preference) {
  .bell-word.unfound { animation: bellBreathe 2.8s ease-in-out infinite; }
}
@keyframes bellBreathe {
  0%, 100% { text-shadow: 0 0 12px rgba(232, 201, 135, 0.30); }
  50%      { text-shadow: 0 0 26px rgba(232, 201, 135, 0.85); }
}
/* A bell-word inside an .ignite paragraph has to wait for its own sentence.
   .ignite paints its text through a background-clip sweep, so before .lit the
   paragraph reads as dim grey - but .bell-word sets its own colour and glow, so
   the one touchable word sat fully lit and breathing inside an unpainted
   paragraph. It read as a glitch, not an invitation. Handled exactly the way
   .gleam already is: go transparent and let the parent's gradient paint the
   glyphs like any other word, then take gold when the sentence lights.
   Applies to night 2's Christmas and night 3's footsteps alike. */
.js .ignite .bell-word {
  color: transparent;
  text-shadow: none;
  border-bottom-color: transparent;
}
.js .ignite.lit .bell-word {
  color: var(--candle);
  text-shadow: 0 0 16px rgba(232, 201, 135, 0.4);
  border-bottom-color: rgba(232, 201, 135, 0.45);
  transition-delay: 1.3s;
}
.js .ignite.lit .bell-word.unfound {
  border-bottom-style: solid;
  border-bottom-color: rgba(232, 201, 135, 0.75);
}
.js .ignite.lit .bell-word:hover,
.js .ignite.lit .bell-word:focus-visible {
  color: var(--champagne);
  text-shadow: 0 0 24px rgba(232, 201, 135, 0.7);
}
/* The breath waits too - it glows from frame one otherwise, a halo around a
   word that has not been painted yet. */
@media (prefers-reduced-motion: no-preference) {
  .js .ignite .bell-word.unfound { animation: none; }
  .js .ignite.lit .bell-word.unfound {
    animation: bellBreathe 2.8s ease-in-out infinite;
    animation-delay: 1.3s;
  }
}

.bell-hint {
  margin: 0 0 0.9rem;
  font-family: var(--serif); font-style: italic;
  font-size: 0.95rem; line-height: 1.6;
  /* gold rather than --dust: on this page gold is the colour of things she can
     touch, so the hint reads as an instruction instead of more letter text */
  color: rgba(232, 201, 135, 0.72);
  transition: opacity 0.9s var(--ease);
}
.bell-hint.gone { opacity: 0; }
.owl-fly {
  position: fixed; left: 0; top: 0;
  width: clamp(110px, 32vw, 170px);
  pointer-events: none; z-index: 85;
  will-change: transform, opacity;
}
.owl-fly svg { width: 100%; height: auto; display: block; filter: drop-shadow(0 10px 26px rgba(0, 0, 0, 0.55)); }
.owl-fly.owl-real { height: auto; mix-blend-mode: screen; filter: none; }
.owl-rest { transition: opacity 1.2s ease; animation: ms-hover 3s ease-in-out infinite alternate; }
.owl-fly .owl-wing { transform-box: view-box; }
.owl-fly .owl-wing--near { transform-origin: 112px 74px; animation: owlFlapN 0.46s ease-in-out infinite alternate; }
.owl-fly .owl-wing--far  { transform-origin: 118px 68px; animation: owlFlapF 0.46s ease-in-out infinite alternate; }
@keyframes owlFlapN { from { transform: rotate(-28deg); } to { transform: rotate(24deg); } }
@keyframes owlFlapF { from { transform: rotate(22deg); } to { transform: rotate(-20deg); } }
.owl-fly.glide .owl-wing--near { animation: owlGlideN 1.4s ease-in-out infinite alternate; }
.owl-fly.glide .owl-wing--far  { animation: owlGlideF 1.4s ease-in-out infinite alternate; }
@keyframes owlGlideN { from { transform: rotate(-6deg); } to { transform: rotate(4deg); } }
@keyframes owlGlideF { from { transform: rotate(4deg); } to { transform: rotate(-4deg); } }
.owl-fly--still { transition: opacity 1.2s ease; opacity: 0; }
.owl-fly--still .owl-wing { animation: none; }
.owl-fly--still.in { opacity: 1; }

/* ---------- Night 6: the toast she can watch ---------- */
/* Real footage of two crystal glasses meeting, held up like a photograph.
   The veil comes on fast (0.35s) because the glasses touch half a second in;
   a slow fade would put the sound before the sight. */
.toast-veil {
  position: fixed;
  inset: 0;
  z-index: 70;
  display: flex;
  align-items: center;
  justify-content: center;
  /* nearly solid on purpose: at the gem veil's 0.92 the poem read straight
     through, and its own hint line landed under this card's caption looking
     like a second caption */
  background: rgba(7, 4, 8, 0.995);
  opacity: 0;
  transition: opacity 0.35s ease;
}
.toast-veil.on { opacity: 1; }
.toast-stage { position: relative; width: min(78vw, 330px); text-align: center; }
/* the film is already vignetted to black at its corners, so a soft ring of
   gold is all that is needed to seat it on the night */
.toast-video {
  width: 100%;
  display: block;
  border-radius: 3px;
  box-shadow: 0 0 0 1px rgba(196, 149, 58, 0.22), 0 26px 60px rgba(0, 0, 0, 0.6);
  transform: scale(0.94);
  opacity: 0;
  transition: transform 0.9s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.5s ease;
}
.toast-veil.on .toast-video { transform: scale(1); opacity: 1; }
.toast-line {
  margin: 1.1rem 0 0;
  opacity: 0;
  transition: opacity 0.9s ease 0.7s;
}
.toast-veil.on .toast-line { opacity: 0.78; }
@media (prefers-reduced-motion: reduce) {
  .toast-veil, .toast-video, .toast-line { transition: none; }
}

/* ==========================================================================
   The motion layer (motion.js). Layers at three speeds, arrivals from
   different directions, titles word by word, light along an edge, the drift
   gallery, rolling digits, her name arriving, chapters. Only transform,
   opacity, clip-path and a short blur ever animate here.
   ========================================================================== */

/* 1 · depth. The stars move in sky.js. The dust is a two-screen strip that
   repeats every screen, so it wraps without a seam. The moon rises at just over
   half her speed, no further than a hand's width, and eases smaller. */
.mdust {
  position: fixed; top: 0; left: 0; right: 0; height: 200vh; z-index: 1;
  pointer-events: none; overflow: hidden;
  translate: 0 calc(-1 * var(--dust-y, 0px));
}
.mdust i {
  position: absolute; left: var(--x); top: var(--y);
  width: var(--s); height: var(--s); border-radius: 50%;
  background: radial-gradient(circle, var(--c) 0%, transparent 68%);
  opacity: 0.5;
  animation: dust-float var(--t) ease-in-out infinite alternate;
}
@keyframes dust-float { from { transform: translate(0, 0); } to { transform: translate(var(--fx, 6px), var(--fy, -10px)); } }
.motion .moon-layer { translate: 0 calc(-1 * min(var(--scroll-y, 0px) * 0.6, 42vh)); }
.motion .moon > * { scale: calc(1 - var(--scroll-k, 0) * 0.18); }
/* the moon's own set pieces keep it exactly where they put it */
.motion body[data-scene] .moon-layer { translate: none; }
.motion body[data-scene] .moon > * { scale: none; }
.motion body.tilting .moon-layer { translate: none; }

/* 3 · the title, word by word, out of a slot, with a slight tilt that settles */
.motion .day-hold .day-title.kinetic { opacity: 1; }
.day-title.kinetic .w { display: inline-block; overflow: hidden; vertical-align: bottom; padding: 0 0.04em 0.06em 0; }
.day-title.kinetic .wi {
  display: inline-block;
  transform: translateY(112%) rotate(1.4deg);
  transform-origin: left bottom;
  transition: transform 0.95s var(--ease) calc(var(--i, 0) * 70ms), color 0.9s ease, text-shadow 0.9s ease;
}
.day-title.kinetic.on .wi { transform: none; }
.day-title.kinetic.lit .wi { color: var(--candle); text-shadow: 0 0 18px rgba(255, 243, 220, 0.28); }
.day-title.kinetic.settled .wi {
  color: inherit; text-shadow: none;
  transition: transform 0.95s var(--ease) calc(var(--i, 0) * 70ms), color 1.6s ease, text-shadow 1.6s ease;
}

/* 5 · light travels once along a one-pixel ring, then rests */
.lightpass { position: relative; }
.lightpass::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px;
  pointer-events: none; z-index: 3; opacity: 0;
  background: linear-gradient(115deg, transparent 32%, rgba(232, 201, 135, 0.85) 48%, rgba(255, 243, 220, 0.95) 50%, rgba(232, 201, 135, 0.85) 52%, transparent 68%);
  background-size: 300% 100%; background-position: 100% 0;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
}
.lightpass.lightpass-lit::before { animation: light-pass 1.7s var(--ease) var(--delay, 0s) forwards; }
@keyframes light-pass {
  0% { background-position: 100% 0; opacity: 0; }
  12% { opacity: 1; }
  88% { opacity: 1; }
  100% { background-position: 0% 0; opacity: 0; }
}

/* 4 · the drift gallery. The zone gives the scroll room; the stage pins inside
   it; the frame below is pulled up into its tail so the handoff overlaps. */
.drift-zone { position: relative; height: 190vh; height: 190svh; margin-bottom: -32vh; pointer-events: none; }
.drift-stage {
  position: absolute; top: 0; left: 0; right: 0; height: 100vh; height: 100svh;
  pointer-events: none; z-index: 1; will-change: transform, opacity;
}
.drift-ph {
  position: absolute; left: 50%; top: 50%;
  width: min(42vw, 170px); aspect-ratio: 3 / 4;
  margin: calc(min(42vw, 170px) * -0.6667) 0 0 calc(min(42vw, 170px) * -0.5);
  object-fit: cover; border-radius: 14px;
  box-shadow: 0 22px 50px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(245, 239, 230, 0.12);
  will-change: transform;
}
.drift-hint {
  position: absolute; left: 0; right: 0; bottom: calc(2.2rem + env(safe-area-inset-bottom));
  text-align: center; color: var(--ivory-dim); line-height: 0;
  transition: opacity 0.6s var(--ease);
}
.drift-hint svg { display: inline-block; animation: drift-hint-bob 2.4s ease-in-out infinite; }
@keyframes drift-hint-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(6px); } }

/* 6 · her name arrives. While the letters travel the name is plain ivory; the
   gradient shimmer it already has returns the moment they have all landed. */
.hero-name.entering {
  background-image: none; -webkit-background-clip: initial; background-clip: initial;
  color: var(--ivory); animation: none;
}
.hero-name .nl {
  display: inline-block;
  opacity: 0; filter: blur(12px);
  transform: translate(var(--dx, 0), var(--dy, 0));
  transition: opacity var(--d, 1s) var(--ease) var(--wait, 0s), filter var(--d, 1s) var(--ease) var(--wait, 0s), transform var(--d, 1s) var(--ease) var(--wait, 0s);
}
.hero-name.arrived .nl { opacity: 1; filter: none; transform: none; }
/* 7 · night 25 · the birthday headline gathers out of her sky. Same idea as
   her name above, carried much further: each letter starts as a small bright
   point outside the screen, turns as it travels, overshoots its place, then
   cools from candle light to ivory. When the last one has cooled, motion.js
   drops .assembling and the headline is an ordinary ignite line again. */
.celebrate-title.assembling {
  background-image: none; -webkit-background-clip: initial; background-clip: initial;
  color: var(--ivory);
}
.celebrate-title .bw { display: inline-block; white-space: nowrap; }
.celebrate-title .bl { display: inline-block; }
.celebrate-title.assembling .bl {
  opacity: 0;
  filter: blur(11px);
  transform: translate(var(--dx, 0), var(--dy, 0)) rotate(var(--rot, 0deg)) scale(var(--sc, 0.1));
  color: var(--candle);
  text-shadow: 0 0 24px rgba(255, 243, 220, 0.85), 0 0 60px rgba(242, 169, 196, 0.45);
  will-change: transform, opacity, filter;
  transition:
    opacity calc(var(--d, 2s) * 0.5) var(--ease) var(--wait, 0s),
    filter calc(var(--d, 2s) * 0.62) var(--ease) var(--wait, 0s),
    /* a soft overshoot at the end of the travel, so each letter settles */
    transform var(--d, 2s) cubic-bezier(0.18, 1.12, 0.28, 1) var(--wait, 0s),
    color 1.4s var(--ease) calc(var(--wait, 0s) + var(--d, 2s) * 0.45),
    text-shadow 1.4s var(--ease) calc(var(--wait, 0s) + var(--d, 2s) * 0.45);
}
.celebrate-title.assembling.gathered .bl {
  opacity: 1;
  filter: none;
  transform: none;
  color: var(--ivory);
  text-shadow: none;
}

/* and the fragments behind her sit at three depths */
.motion .weave span { translate: 0 calc(var(--scroll-y, 0px) * -1 * var(--par, 0)); }
.motion .weave span:nth-child(3n + 1) { --par: 0.08; }
.motion .weave span:nth-child(3n + 2) { --par: 0.16; }
.motion .weave span:nth-child(3n) { --par: 0.26; }

/* 7 · chapters. The ground warms from void toward night as she goes deeper,
   and the halo breathes once a chapter. */
.motion body { background-color: color-mix(in srgb, var(--void), var(--night) calc(var(--scroll-p, 0) * 100%)); }
.motion .moon-halo { opacity: calc((0.25 + var(--phase, 0.5) * 0.75) * (0.72 + 0.28 * var(--halo-b, 1))); }

/* 8 · digits roll. Eleven glyphs a column: 0 to 9 and a second 0. */
.odo { display: inline-flex; font-variant-numeric: tabular-nums; line-height: 1; vertical-align: baseline; }
.odo .d { display: inline-block; height: 1em; overflow: hidden; }
.odo .col { display: block; transition: transform 0.75s var(--ease); }
.odo .col b { display: block; height: 1em; line-height: 1; font-weight: inherit; }
.odo-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.count-val.odo { display: inline-flex; }

@media (prefers-reduced-motion: reduce) {
  .js [data-animate] { opacity: 1; transform: none; filter: none; clip-path: none; }
  .mdust, .drift-zone { display: none; }
  .moon-layer, #sky, .weave span { translate: none; }
  .moon > * { scale: none; }
  .day-title.kinetic .wi, .hero-name .nl, .celebrate-title .bl { transform: none; opacity: 1; filter: none; }
  .lightpass::before { display: none; }
  .odo .col { transition: none; }
  .drift-hint svg { animation: none; }
}

/* ============================================================
   Night 4: the drive north to Mount Shasta
   ------------------------------------------------------------
   Night 4 already carries Folsom Lake four separate ways: her twelve
   photographs, the moonrise film that opens the night, the moon mirrored on
   the water (.moon.lake above) and the path of light down the lake. Mount
   Shasta had nothing at all, and it is the one place on this night that has to
   stay DRAWN, because the whole weight of that paragraph is that no photograph
   of the day survived. A photograph here would contradict her own night.

   The zone sits in the flow between the Shasta paragraph and the drift
   gallery, so the mountains have left the screen before her Folsom pictures
   come back. The two places never share a frame.

   Depth is carried by VALUE, not by opacity: at night a nearer ridge is darker
   than a far one, and stacking translucent layers only produced mud.
   ============================================================ */
:root {
  --n4-snow:   #C6D6EE;   /* moonlit snow, never white: night 4 is a crescent */
  --n4-rock-h: #3B4A6C;   /* the mountain is the FARTHEST thing here, so it is
                             paler than every ridge in front of it */
  --n4-rock-l: #262F47;
  --n4-r3:     #232B41;
  --n4-r2:     #1A2031;
  --n4-r1:     #0E121B;
  --n4-r0:     #07080D;
}
/* The zone itself breaks out of the reading column to the full width of the
   glass. Clipping the zone instead would have cut the mountain off at the
   column edge, which is exactly what it did the first time. */
.n4-road {
  position: relative; width: 100vw; margin-left: calc(50% - 50vw);
  height: 340vh; height: 340svh; pointer-events: none;
}
.n4-stage {
  position: absolute; top: 0; left: 0; right: 0;
  height: 100vh; height: 100svh;
  overflow: hidden; will-change: transform, opacity;
}
/* the cold that arrives with altitude */
.n4-air {
  position: absolute; inset: 0; opacity: 0;
  background: linear-gradient(to top, rgba(35,43,65,.30), rgba(26,32,49,.14) 38%, rgba(26,32,49,.05) 62%, transparent 84%);
}
.n4-stars {
  position: absolute; inset: 0 0 40% 0;
  background-image:
    radial-gradient(1.4px 1.4px at 12% 14%, rgba(245,239,230,.5), transparent),
    radial-gradient(1.2px 1.2px at 71% 9%, rgba(245,239,230,.38), transparent),
    radial-gradient(1.5px 1.5px at 36% 27%, rgba(232,201,135,.45), transparent),
    radial-gradient(1.2px 1.2px at 88% 22%, rgba(245,239,230,.4), transparent),
    radial-gradient(1.3px 1.3px at 55% 38%, rgba(242,169,196,.3), transparent),
    radial-gradient(1.2px 1.2px at 22% 47%, rgba(245,239,230,.3), transparent),
    radial-gradient(1.5px 1.5px at 79% 44%, rgba(245,239,230,.34), transparent);
}
/* Each range is ONE seamless tile whose left and right edges sit at the same
   height, so translating by (travel % tile) never shows a seam. Each is also
   anchored to the floor so its fill runs off the bottom and the nearer, darker
   ranges bury the feet of the ones behind. A band that stopped short would rule
   a hard horizontal line straight across the night. */
.n4-band {
  position: absolute; left: 0; bottom: 0; width: 100%;
  background-repeat: repeat-x; background-position: left bottom;
  will-change: transform;
}
.n4-b3 { height: 52%; background-size: 520px 100%; }
.n4-b2 { height: 44%; background-size: 460px 100%; }
.n4-b1 { height: 35%; background-size: 400px 100%; }
.n4-b0 { height: 26%; background-size: 340px 100%; }
/* Shasta is a broad stratovolcano, roughly 17 miles across at the base and
   under 3 miles tall, so the drawing has to be far wider than the glass or the
   flanks come out as the steep walls of a cut-out. Only the middle quarter of
   this element is ever on screen: the flanks leave the frame, exactly as they
   do from the road. */
.n4-shasta {
  position: absolute; left: 50%; bottom: 0;
  width: 360%; margin-left: -180%;
  opacity: 0; will-change: transform, opacity;
}
.n4-shasta svg { display: block; width: 100%; height: auto; }

/* Desktop. The drawing is 2.6x wider than it is tall, so a width set as a share of
   the VIEWPORT WIDTH turns into more height than a short, wide window can hold: at
   1440x900 the phone's 360% put 1996px of mountain into a 900px stage and the whole
   screen was one blue flank, no summit and no sky. The width still has to beat the
   viewport. Sizing off the HEIGHT instead reproduces the phone's composition exactly:
   the mountain stands 64% of the stage tall, so the snow cap is a cap and there is
   rock under it, and the same formula lands its base back at the stage floor where
   the ridge bands cover it. The 118vw floor is parallax slack. The drawing's own left
   and right edges are harmless anyway: the path meets y=385 at x=0 and x=1000, so
   both edges sit at the mountain's base, behind the ridges, never as a cut wall.
   Phone layout is untouched: this whole block starts at 900px. */
@media (min-width: 900px) {
  .n4-shasta {
    --n4-w: max(166vh, 118vw);
    width: var(--n4-w);
    margin-left: calc(var(--n4-w) / -2);
    bottom: calc(64vh - 0.385 * var(--n4-w));
  }
}
/* the car she is sitting in: the one thing in the frame that never moves */
.n4-surface {
  position: absolute; left: 0; right: 0; bottom: 0; height: 12%;
  background:
    radial-gradient(60% 120% at 50% 100%, rgba(232,201,135,.16), transparent 70%),
    linear-gradient(to top, var(--n4-r0), rgba(7,8,13,0) 90%);
}
.n4-label {
  position: absolute; left: 0; right: 0; bottom: 8%;
  text-align: center; opacity: 0;
  font-family: var(--font-ui); font-size: var(--text-label);
  letter-spacing: 0.18em; text-transform: uppercase; color: var(--ivory-dim);
  transition: opacity 0.8s var(--ease);
}
.n4-label b { color: var(--champagne); font-weight: 500; }

/* No motion: one still frame of the mountain, so Shasta still exists for her,
   and the zone collapses from a drive to a single screen. */
.n4-road.n4-still { height: 78vh; height: 78svh; }
.n4-road.n4-still .n4-stage { height: 78vh; height: 78svh; transform: none; }
.n4-road.n4-still .n4-shasta { opacity: 1; }
.n4-road.n4-still .n4-air { opacity: .5; }
.n4-road.n4-still .n4-label { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .n4-road { height: 78vh; height: 78svh; }
  .n4-stage { height: 78vh; height: 78svh; transform: none !important; }
  .n4-band { transform: none !important; }
  .n4-shasta { opacity: 1 !important; transform: none !important; }
  .n4-air { opacity: .5 !important; }
  .n8-turn { transition: none; opacity: 1; }
  .n8-face, .n8-eye, .n8-brow, .n8-blush { transition: none; }
  .n8-face.kissing { transform: none; }
  .n8-heart { animation: none; opacity: 1; transform: translate(0, -40px) scale(4); }
  .n8-heart--aqua { transform: translate(-30px, -30px) scale(4); }
  .n8-hint { transition: none; }
  .ways { transition: none; opacity: 1; }
  .mornings, .mornings-close { transition: none; opacity: 1; }
  .morning.next .morning-dot { animation: none; }
  .morning-body .letter { transition: none; opacity: 1; transform: none; }
  body.mornings-lit::before, body.mornings-lit #sky { transition: none; }
  .n4-label { opacity: 1 !important; }
}

/* ---------- Night 4 · the moon that comes up over the drive ----------
   Not a drawn moon. This is the SAME photograph the page is already flying
   tonight: the url is read off `#moon`'s --moon-img at runtime and never
   constructed here, so if the calendar advances, or the fetch fails and
   `.has-photo` never lands, this draws nothing at all rather than a moon that
   is not the one in her sky. It sits behind the mountain and behind every
   range, so it genuinely rises out from the terrain instead of over it.
   Low and warm, high and clean: the halo fades as it climbs. */
.n4-moon {
  position: absolute; left: 74%; top: 98%;
  width: clamp(58px, 18vw, 104px); aspect-ratio: 1;
  transform: translate(-50%, -50%) scale(var(--msc, 1));
  background: var(--n4-moon-img, none) center / contain no-repeat;
  opacity: 0; transition: opacity 1.4s var(--ease);
  pointer-events: none;
}
.n4-moon.ready { opacity: 1; }
.n4-moon::after {
  content: ''; position: absolute; left: -70%; top: -70%; width: 240%; height: 240%;
  background: radial-gradient(circle at 50% 50%, rgba(232, 201, 135, 0.30), rgba(242, 169, 196, 0.08) 42%, transparent 70%);
  opacity: var(--halo, 0);
  pointer-events: none;
}

/* ---------- Night 4 · the words Folsom Lake, with the film still in them ----------
   The phrase keeps its .gleam, so the ignite still colours it and nothing about
   her sentence changes. This rule deliberately sets no colour: `.js .ignite.lit
   .gleam` has to keep winning, or the phrase would stop matching every other
   gleam on the site. The underline waits for .lit for the same reason, so a
   dashed line never floats in a paragraph that has not lit yet. */
.lake-word {
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; letter-spacing: inherit;
  border-bottom: 1px dashed transparent;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: border-color 0.9s var(--ease), text-shadow 0.4s ease;
}
.ignite.lit .lake-word { border-bottom-color: rgba(232, 201, 135, 0.45); }
.ignite.lit .lake-word:hover,
.ignite.lit .lake-word:focus-visible {
  border-bottom-color: rgba(232, 201, 135, 0.85);
  text-shadow: 0 0 24px rgba(232, 201, 135, 0.7);
}
.lake-word:focus-visible { outline: 1px solid rgba(232, 201, 135, 0.5); outline-offset: 3px; }

/* z-index 70 is night 6's toast contract: above the overture at 60, below the
   veil and the sound pill. */
.lake-film {
  position: fixed; inset: 0; z-index: 70;
  background: rgba(7, 4, 8, 0.94);
  opacity: 0; transition: opacity 0.6s var(--ease);
}
.lake-film.on { opacity: 1; }
.lake-film-hint {
  /* clear of the sound pill and the preview badge, both of which sit above
     this layer */
  position: absolute; left: 0; right: 0; bottom: 14%; z-index: 2;
  text-align: center;
  font-family: var(--font-ui); font-size: var(--text-label);
  letter-spacing: 0.18em; text-transform: uppercase; color: rgba(245, 239, 230, 0.72);
  text-shadow: 0 1px 12px rgba(7, 4, 8, 0.9);
  opacity: 0; transition: opacity 1.2s var(--ease);
}
.lake-film.hinting .lake-film-hint { opacity: 1; }

/* ---------- Bringing the calendar moon back when a night needs her to see it ----------
   The moon layer lifts 0.6x scroll capped at 42vh (rule above, under "the moon rides
   the scroll"), which on a phone carries the moon fully above the viewport past about
   590px. Three nights ask her to look at the moon or touch it below that line: night 5
   presses it, night 6 crowns it, night 19 winds it. Those nights bring it back for
   exactly as long as they need it, using the same escape hatch night 2's tilt uses.
   Two classes so it glides down AND back up: `moon-glide` carries the transition and
   outlives `moon-here` by the length of the glide. Reduced motion needs nothing here -
   the reduced block already sets `translate: none` on the layer, so both are inert. */
.motion body.moon-glide .moon-layer { transition: translate 1.1s var(--ease); }
.motion body.moon-here  .moon-layer { translate: none; }
.motion body.moon-here  .moon > *   { scale: none; }

/* The screenshot of the night 19 drag caught her instruction highlighted as selected text.
   `.wind-stage` already carries `touch-action: none` (which stops the page scrolling under
   the drag) but not a selection guard, so dragging across it selects the words. Same pair
   the site uses everywhere else it asks for a drag. Appended rather than edited in place. */
.wind-stage { -webkit-user-select: none; user-select: none; }
