/* ==========================================================================
   styles.css — the look.
   Frosted glass panels floating over a slow-drifting colour field.
   Palette lives at the top; change six values and the whole site changes mood.
   ========================================================================== */

/* ──────────────────────────────────────────────────────────────────────────
   THE WHOLE THEME IS THE TWELVE VALUES BELOW.
   Four ready-made alternates sit directly underneath. To switch: comment out
   the active twelve, uncomment one alternate. Everything follows — background,
   buttons, headings, confetti, balloons, the bunting, the cursor light.
   ────────────────────────────────────────────────────────────────────────── */
:root {
  /* ACTIVE THEME — "Coastal Bloom"
     Coastal Calm makes the ground, Main Character supplies the accents, and
     Soft Bloom is used for anything that is meant to be paper.
     --aurora is how strongly the colour bleeds through the dark: lower is
     deeper and calmer, higher is more of a party. */
  --bg-0:   #04070F;      /* darkest — page base, near black */
  --bg-1:   #080E1F;      /* mid                             */
  --bg-2:   #0D1A38;      /* lightest — a hint of navy       */
  --gold:   #FFC107;      /* Main Character amber           */
  --pink:   #FF0A6C;      /* Main Character magenta         */
  --cyan:   #00B4D8;      /* Coastal Calm cyan              */
  --mint:   #A6CCC2;      /* Soft Bloom sage — "correct"    */
  --coral:  #F2C9CB;      /* Soft Bloom blush               */
  --orange: #FF6B00;      /* Main Character orange          */
  --deep:   #3B82F6;      /* Main Character blue            */
  --aurora: .24;

  /* ── ALTERNATE: "Coastal Calm" only — quieter, no hot accents
  --bg-0: #000B2E;  --bg-1: #001A6E;  --bg-2: #0077B6;
  --gold: #8FDDEF;  --pink: #00B4D8;  --cyan: #C9EDF7;  --mint: #A6CCC2;
  --coral: #8FDDEF; --orange: #0077B6; --deep: #3B82F6;  --aurora: .5;
  */

  /* ── ALTERNATE: "Main Character" — loud, high contrast, unapologetic
  --bg-0: #0A0118;  --bg-1: #1B0538;  --bg-2: #3B0A5E;
  --gold: #FFC107;  --pink: #FF0A6C;  --cyan: #3B82F6;  --mint: #FFC107;
  --coral: #FF6B00; --orange: #FF6B00; --deep: #8B3DEB;  --aurora: .6;
  */

  /* ── ALTERNATE: "Ink & Emerald" — the previous theme, if you want it back
  --bg-0: #06090a;  --bg-1: #0a1512;  --bg-2: #10291f;
  --gold: #ffd45e;  --pink: #ff6f91;  --cyan: #2dd4bf;  --mint: #4ade80;
  --coral: #ff8a5b; --orange: #ffa62b; --deep: #10b981;  --aurora: .36;
  */

  /* paper (letter, polaroids, envelope) */
  --paper:   #F7EFEA;     /* Soft Bloom off-white  */
  --paper-2: #E8D6D0;     /* Soft Bloom blush edge */
  --cream:   #FFFBF8;

  /* ink */
  --text:       #EAF3FF;
  --text-dim:   rgba(234, 243, 255, .74);
  --text-faint: rgba(234, 243, 255, .46);

  /* glass */
  --glass:      rgba(255, 255, 255, .06);
  --glass-up:   rgba(255, 255, 255, .10);
  --glass-brd:  rgba(255, 255, 255, .15);
  --glass-hi:   rgba(255, 255, 255, .30);
  --blur: 20px;

  --r-xl: 30px; --r-lg: 24px; --r-md: 16px; --r-sm: 10px;

  --font-ui:   'Karla', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-disp: 'Instrument Serif', ui-serif, Georgia, 'Times New Roman', serif;
  --font-hand: 'Kalam', 'Segoe Script', cursive;

  --ease: cubic-bezier(.2, .8, .2, 1);
  --ease-back: cubic-bezier(.34, 1.56, .64, 1);
}

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }
html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* The fixed decoration layers (stars, bunting, bloom, confetti) size to the
     viewport INCLUDING the scrollbar, so on desktop they sit a scrollbar-width
     wider than the page and cause a sideways scroll. clip stops that without
     creating a scroll container the way overflow:hidden would. */
  overflow-x: clip;
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;        /* phones: the real height, not the one behind the address bar */
  font-family: var(--font-ui);
  color: var(--text);
  background: var(--bg-0);
  overflow-x: hidden;
  line-height: 1.6;
}

/* ==========================================================================
   BACKGROUND STACK
   ========================================================================== */

/* 1. drifting colour field */
#aurora {
  position: fixed; inset: -25%;
  z-index: 0; pointer-events: none;
  filter: blur(90px);
  opacity: var(--aurora, .5);
  background:
    linear-gradient(160deg, var(--bg-0) 0%, var(--bg-1) 45%, var(--bg-2) 72%, var(--bg-0) 100%);
}
.blob { position: absolute; border-radius: 50%; mix-blend-mode: screen; opacity: .72; will-change: transform; }
/* The two warm blobs lead and the two cool ones sit behind them. Without the
   opacities the emerald and the teal are the biggest, brightest things on the
   page and everything picks up a green cast as they drift across. */
.b1 { width: 44vw; height: 44vw; background: var(--pink);   top: -4%;  left: -4%;  animation: drift1 28s ease-in-out infinite; }
.b2 { width: 34vw; height: 34vw; background: var(--deep);   top: 22%;  right: -6%; animation: drift2 34s ease-in-out infinite; opacity: .5; }
.b3 { width: 28vw; height: 28vw; background: var(--cyan);   bottom: 2%; left: 18%; animation: drift3 30s ease-in-out infinite; opacity: .42; }
.b4 { width: 32vw; height: 32vw; background: var(--gold);   top: 48%;  left: 42%;  animation: drift4 38s ease-in-out infinite; }
@keyframes drift1 { 50% { transform: translate3d(14vw, 10vh, 0) scale(1.18); } }
@keyframes drift2 { 50% { transform: translate3d(-12vw, 14vh, 0) scale(.86); } }
@keyframes drift3 { 50% { transform: translate3d(16vw, -12vh, 0) scale(1.14); } }
@keyframes drift4 { 50% { transform: translate3d(-14vw, -8vh, 0) scale(1.22); } }

/* 2. film grain so the gradients never band */
#grain {
  position: fixed; inset: 0; z-index: 1; pointer-events: none;
  opacity: .16; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
}

/* 3. balloons — the look and the markup now live in css/party.css and
      js/party.js, together with the rest of the birthday decoration. */

/* 4. stars */
#sky { position: fixed; inset: 0; z-index: 1; pointer-events: none; }
#sky i {
  position: absolute; display: block; border-radius: 50%;
  background: #fff; box-shadow: 0 0 7px rgba(255, 255, 255, .8);
  animation: twinkle 4.5s ease-in-out infinite;
}
@keyframes twinkle { 0%, 100% { opacity: .18; transform: scale(.75); } 50% { opacity: 1; transform: scale(1.2); } }

/* 5. cursor warmth (desktop only, set by app.js) */
#spotlight {
  position: fixed; inset: 0; z-index: 2; pointer-events: none;
  opacity: 0; transition: opacity .5s;
  /* first line is the plain fallback; the second wins where color-mix is
     supported, so the cursor light follows whichever theme is active */
  background: radial-gradient(340px circle at var(--mx, 50%) var(--my, 50%),
              rgba(255, 193, 7, .13), rgba(255, 143, 163, .06) 45%, transparent 72%);
  background: radial-gradient(340px circle at var(--mx, 50%) var(--my, 50%),
              color-mix(in srgb, var(--gold) 14%, transparent),
              color-mix(in srgb, var(--pink) 7%, transparent) 45%, transparent 72%);
}
body.has-pointer #spotlight { opacity: 1; }

/* 6. flash when a chapter changes */
#bloom {
  position: fixed; inset: 0; z-index: 80; pointer-events: none; opacity: 0;
  background: radial-gradient(circle at 50% 42%,
              rgba(255, 255, 255, .42), rgba(255, 143, 163, .22) 32%, transparent 66%);
  background: radial-gradient(circle at 50% 42%,
              rgba(255, 255, 255, .42),
              color-mix(in srgb, var(--gold) 26%, transparent) 32%, transparent 66%);
}
#bloom.go { animation: bloom .75s ease-out; }
@keyframes bloom {
  0%   { opacity: 0; transform: scale(.85); }
  28%  { opacity: 1; }
  100% { opacity: 0; transform: scale(1.35); }
}

#confetti { position: fixed; inset: 0; pointer-events: none; z-index: 90; }

/* test-mode banner — only exists when content.js has testMode: true */
#testbar {
  position: fixed; left: 50%; bottom: 14px; transform: translateX(-50%);
  z-index: 95; max-width: min(680px, 94vw);
  pointer-events: none;          /* visible, but never blocks a tap underneath */
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  justify-content: center; text-align: center;
  padding: 10px 18px; border-radius: 999px;
  background: rgba(255, 122, 0, .92);
  border: 1px solid rgba(255, 255, 255, .5);
  box-shadow: 0 14px 40px -12px rgba(255, 122, 0, .8);
  color: #221000; font-size: .78rem; line-height: 1.4;
}
#testbar strong { letter-spacing: .16em; font-size: .7rem; }
#testbar code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .74rem; background: rgba(0, 0, 0, .18); padding: 1px 6px; border-radius: 5px;
}
/* on a phone the full sentence wrapped into a big orange blob sitting on top
   of buttons and captions, so there it is just the tag, small, at the bottom */
@media (max-width: 600px) {
  #testbar { font-size: .62rem; padding: 6px 12px; bottom: 8px; gap: 0; }
  #testbar span { display: none; }
}

/* ==========================================================================
   GLASS + TYPE PRIMITIVES
   ========================================================================== */
.cd-unit, .media-block, .qcard, .qopt, .map-wrap, .map-stat,
.btn-ghost, .blow-meter, .qunlock {
  backdrop-filter: blur(var(--blur)) saturate(150%);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(150%);
}
.cd-unit, .media-block, .qcard, .map-wrap, .map-stat {
  background: var(--glass);
  border: 1px solid var(--glass-brd);
  box-shadow:
    0 26px 60px -28px rgba(0, 0, 0, .95),
    inset 0 1px 0 var(--glass-hi);
}

@keyframes slide-grad { 50% { background-position: 100% 0; } }

/* GRADIENT HEADLINES.
   Each of these paints its text with a gradient instead of a flat colour, by
   making the text itself transparent and clipping the gradient to it. That
   means the gradient is not decoration — without one the text renders as
   NOTHING AT ALL. Three of them were missing it: the quiz score, the distance
   figures and the line after the candles were all invisible. If you add
   another headline to this list, give it a background here too.
   (.gate-title-big and .finale-line define their own, cooler gradient below.)
   The finale line takes it per WORD: each word animates on its own, and
   Safari will not paint a parent's clipped gradient through children with
   their own transform/filter - on iPhone the whole line came out invisible. */
.gate-title-big, .finale-word, .qscore-big, .map-stat-value, .cake-done-big {
  background-image: linear-gradient(100deg, var(--gold), #fff2d0 38%, var(--pink) 78%, var(--coral));
  background-size: 220% 100%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}


/* ==========================================================================
   BUTTONS
   ========================================================================== */
.btn {
  position: relative; overflow: hidden;
  font-family: var(--font-ui);
  font-size: 1rem; font-weight: 600; letter-spacing: .01em;
  padding: .85em 1.7em;
  border-radius: 999px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform .2s var(--ease-back), box-shadow .3s var(--ease), background .3s, border-color .3s;
  will-change: transform;
}
.btn:hover  { transform: translateY(-3px) scale(1.02); }
.btn:active { transform: translateY(-1px) scale(.99); }
.btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }

.btn-primary {
  background: linear-gradient(120deg, var(--gold), var(--orange) 46%, var(--pink));
  background-size: 180% 100%;
  color: #2b0f22;
  box-shadow: 0 14px 36px -12px rgba(255, 61, 154, .75), inset 0 1px 0 rgba(255, 255, 255, .5);
  animation: slide-grad 7s ease-in-out infinite;
}
.btn-primary:hover { box-shadow: 0 20px 48px -14px rgba(255, 61, 154, .95), inset 0 1px 0 rgba(255, 255, 255, .6); }
/* light sweeping across the button */
.btn-primary::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(110deg, transparent 34%, rgba(255, 255, 255, .55) 50%, transparent 66%);
  transform: translateX(-130%);
  animation: sweep 4.2s ease-in-out infinite;
}
@keyframes sweep { 0%, 55% { transform: translateX(-130%); } 100% { transform: translateX(130%); } }
.btn-big { font-size: 1.12rem; padding: 1.05em 2.3em; }

.btn-ghost {
  background: var(--glass);
  border-color: var(--glass-brd);
  color: var(--text);
  box-shadow: inset 0 1px 0 var(--glass-hi);
}
.btn-ghost:hover { background: var(--glass-up); border-color: rgba(255, 193, 7, .55); }

/* ==========================================================================
   THE GATE
   ========================================================================== */
.gate {
  position: relative; z-index: 4;
  min-height: 100vh; min-height: 100dvh;
  display: grid; place-items: center;
  padding: clamp(24px, 5vw, 60px) 20px;
  transition: opacity .6s var(--ease), transform .6s var(--ease), filter .6s;
}
.gate.is-gone { opacity: 0; transform: scale(.96) translateY(-18px); filter: blur(10px); pointer-events: none; }
.gate-inner { width: min(780px, 100%); text-align: center; }

.eyebrow {
  /* inline-block, not inline-flex: the spans inside must wrap as ordinary
     text, otherwise each becomes its own flex item and the line shatters. */
  display: inline-block; text-align: center; text-wrap: balance;
  font-size: .7rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-faint);
  padding: 8px 18px; border-radius: 999px;
  background: var(--glass); border: 1px solid var(--glass-brd);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  margin: 0 0 clamp(22px, 4vw, 38px);
  animation: fade-up .8s var(--ease) both;
}

.gate-title { margin: 0 0 clamp(26px, 4vw, 42px); }
.gate-title-small {
  display: block;
  font-weight: 300; font-size: clamp(1rem, 3.4vw, 1.3rem);
  letter-spacing: .3em; text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: .45em; padding-left: .3em;
  animation: fade-up .8s var(--ease) .08s both;
}
.gate-title-big {
  display: block;
  font-family: var(--font-disp); font-weight: 400;
  font-size: clamp(3.6rem, 16vw, 8.6rem);
  line-height: .9; letter-spacing: -.035em;
  background: linear-gradient(100deg, var(--gold) 0%, #fff2d0 24%, var(--pink) 56%, var(--deep) 88%);
  background-size: 220% 100%;
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  filter: drop-shadow(0 10px 40px rgba(255, 61, 154, .45));
  animation: slide-grad 9s ease-in-out infinite, drop-in 1s var(--ease-back) .1s both;
}
@keyframes drop-in { from { opacity: 0; transform: translateY(-26px) scale(.9); filter: blur(12px); } }
@keyframes fade-up { from { opacity: 0; transform: translateY(16px); } }

/* clocks */
.clocks {
  display: flex; align-items: center; justify-content: center;
  gap: clamp(10px, 3vw, 26px);
  margin-bottom: clamp(28px, 5vw, 46px);
  animation: fade-up .8s var(--ease) .2s both;
}
.clock { min-width: 96px; }
.clock-face {
  font-family: var(--font-disp); font-weight: 400;
  font-size: clamp(1.2rem, 4.6vw, 1.75rem);
  font-variant-numeric: tabular-nums;
  color: var(--cream);
  text-shadow: 0 0 26px rgba(255, 193, 7, .3);
}
.clock-label {
  font-size: .64rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-faint); margin-top: 4px;
}
.clock-gap { display: flex; align-items: center; gap: 9px; flex: 1; max-width: 230px; }
.clock-gap-line { flex: 1; height: 1px; background: linear-gradient(90deg, transparent, var(--glass-brd), transparent); }
.clock-gap-text {
  font-size: .62rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--gold); white-space: nowrap; opacity: .85;
}

/* countdown */
.countdown-caption {
  font-size: .74rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-dim); margin: 0 0 16px;
}
.countdown {
  display: flex; align-items: flex-start; justify-content: center;
  gap: clamp(5px, 1.6vw, 14px);
  margin-bottom: clamp(30px, 5vw, 46px);
}
.cd-unit {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column; align-items: center;
  min-width: clamp(62px, 17vw, 102px);
  padding: clamp(12px, 2vw, 18px) 8px clamp(10px, 1.6vw, 14px);
  border-radius: var(--r-md);
  animation: fade-up .8s var(--ease) both;
}
.cd-unit:nth-child(1) { animation-delay: .26s; }
.cd-unit:nth-child(3) { animation-delay: .32s; }
.cd-unit:nth-child(5) { animation-delay: .38s; }
.cd-unit:nth-child(7) { animation-delay: .44s; }
.cd-unit::before {
  content: ''; position: absolute; inset: 0 0 auto; height: 42%;
  background: linear-gradient(180deg, rgba(255, 255, 255, .1), transparent);
  pointer-events: none;
}
.cd-num {
  display: block;
  font-family: var(--font-disp); font-weight: 400;
  font-size: clamp(1.8rem, 7.6vw, 3.2rem);
  line-height: 1; font-variant-numeric: tabular-nums;
  color: var(--cream);
  text-shadow: 0 0 30px rgba(255, 193, 7, .35);
}
.cd-num.pop { animation: pop-num .42s var(--ease-back); }
@keyframes pop-num {
  0%   { transform: translateY(-10px) scale(1.16); opacity: .35; filter: blur(4px); }
  100% { transform: none; opacity: 1; filter: none; }
}
.cd-lbl {
  font-size: .56rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--text-faint); margin-top: 10px;
}
.cd-sep {
  font-family: var(--font-disp); font-size: clamp(1.1rem, 4vw, 1.9rem);
  color: var(--text-faint); padding-top: clamp(16px, 3vw, 28px);
  animation: blink 2s ease-in-out infinite;
}
@keyframes blink { 50% { opacity: .25; } }
.countdown.is-close .cd-num { color: var(--gold); }
.countdown.is-close .cd-unit { border-color: rgba(255, 193, 7, .5); box-shadow: 0 0 40px -8px rgba(255, 193, 7, .55), inset 0 1px 0 var(--glass-hi); }

/* the sealed parcel */
.seal-wrap { display: grid; place-items: center; margin-bottom: 26px; }
.parcel {
  position: relative;
  width: min(330px, 80vw); padding: 30px 24px 22px;
  border-radius: var(--r-lg);
  transform: rotate(-1.4deg);
  animation: fade-up .9s var(--ease) .5s both, sway 7s ease-in-out 1.4s infinite;
}
@keyframes sway { 50% { transform: rotate(1.2deg) translateY(-6px); } }
.parcel {
  background: linear-gradient(160deg, rgba(255, 255, 255, .13), rgba(255, 255, 255, .05));
  border: 1px solid var(--glass-brd);
  backdrop-filter: blur(var(--blur)) saturate(150%);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(150%);
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .95), inset 0 1px 0 var(--glass-hi);
}
.parcel-stripes { position: absolute; inset: 0; border-radius: var(--r-lg); overflow: hidden; opacity: .9; }
.parcel-stripes::before, .parcel-stripes::after {
  content: ''; position: absolute; left: 0; right: 0; height: 8px;
  background: repeating-linear-gradient(45deg,
    var(--pink) 0 10px, transparent 10px 20px, var(--cyan) 20px 30px, transparent 30px 40px);
}
.parcel-stripes::before { top: 0; }
.parcel-stripes::after  { bottom: 0; }
.parcel-seal { position: absolute; top: -30px; left: 50%; transform: translateX(-50%); width: 66px; height: 66px; }
.seal-svg { width: 100%; height: 100%; filter: drop-shadow(0 8px 20px rgba(255, 61, 154, .7)); }
.seal-disc { fill: var(--pink); }
.seal-ring { fill: none; stroke: rgba(255, 255, 255, .35); stroke-width: 2; }
.seal-letter { font-family: var(--font-disp); font-weight: 400; font-size: 40px; fill: #fff; }
.parcel-note { margin: 16px 0 0; font-size: .9rem; line-height: 1.55; color: var(--text); }
.parcel-note strong { color: var(--gold); font-weight: 600; }
.parcel.is-bursting { animation: burst .9s var(--ease) forwards; }
@keyframes burst {
  0%   { transform: rotate(-1.4deg) scale(1); }
  32%  { transform: rotate(2.5deg) scale(1.1); }
  100% { transform: rotate(-10deg) scale(.35); opacity: 0; filter: blur(8px); }
}

.tz-note { font-size: .8rem; color: var(--text-faint); max-width: 44ch; margin: 0 auto; }
.tz-note strong { color: var(--gold); font-weight: 600; }

/* the reveal */
.reveal-box { opacity: 0; transform: translateY(20px); transition: opacity .8s var(--ease), transform .8s var(--ease); }
.reveal-box.in { opacity: 1; transform: none; }
.reveal-kicker {
  font-family: var(--font-disp); font-weight: 400;
  font-size: clamp(1.2rem, 4.2vw, 1.8rem);
  margin: 0 0 20px; line-height: 1.28;
  color: var(--gold);
}
.reveal-message { max-width: 55ch; margin: 0 auto 28px; }
.reveal-message p {
  margin: 0 0 .9em; color: var(--text-dim); font-size: clamp(.96rem, 2.6vw, 1.06rem);
  opacity: 0; animation: rise .8s var(--ease) forwards; animation-delay: var(--d, .2s);
}
.reveal-box.in .reveal-message p { opacity: 1; }
@keyframes rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }

.reveal-media { display: grid; gap: 20px; max-width: 580px; margin: 0 auto 32px; }
.media-block { padding: 18px; border-radius: var(--r-lg); }
.media-tag {
  margin: 0 0 10px; font-size: .64rem; letter-spacing: .2em;
  text-transform: uppercase; color: var(--gold);
}
.media-title { margin: -4px 0 12px; font-size: .9rem; color: var(--text-dim); }
.media-frame { border-radius: var(--r-sm); overflow: hidden; background: #000; }
.media-frame iframe { display: block; width: 100%; border: 0; }
.media-16x9 { aspect-ratio: 16 / 9; }
.media-16x9 iframe { height: 100%; }
.media-spotify { background: transparent; }
.media-spotify iframe { height: 352px; }
.media-audio { width: 100%; }
.reveal-hint { font-size: .76rem; color: var(--text-faint); margin-top: 18px; }
.resume-btn { margin-top: 16px; font-size: .85rem; }

/* ==========================================================================
   THE JOURNEY
   ========================================================================== */
.journey { position: relative; z-index: 4; }

/* ── one moment at a time ───────────────────────────────────────────────────
   Each moment owns the screen, because every composition in here was drawn to
   own one. What is deliberately absent: a nav bar, section titles, locks and a
   score. The hand-off between moments does the work a heading would. */
.journey { position: relative; }

.stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr);   /* never let a child widen the stage */
  align-content: safe center;
  gap: clamp(26px, 5vw, 48px);
  min-height: 100svh;
  max-width: 1100px;
  margin: 0 auto;
  padding: clamp(46px, 9vw, 96px) clamp(18px, 5vw, 40px) clamp(34px, 6vw, 64px);
  animation: stage-in .8s var(--ease) both;
}
/* the long one starts at the top and is scrolled through */
#ch-memories { align-content: start; }
#ch-finale   { align-content: safe center; }

@keyframes stage-in {
  from { opacity: 0; transform: translateY(30px) scale(.985); filter: blur(7px); }
}
/* coming back the other way, it arrives from above instead */
.stage.is-back { animation: stage-in-back .8s var(--ease) both; }
@keyframes stage-in-back {
  from { opacity: 0; transform: translateY(-30px) scale(.985); filter: blur(7px); }
}
/* and the one being left behind sinks away rather than blinking out */
.stage.is-leaving { animation: stage-out .4s var(--ease) forwards; }
@keyframes stage-out {
  to { opacity: 0; transform: translateY(-26px) scale(.99); filter: blur(8px); }
}

/* ── the way onward ─────────────────────────────────────────────────────────
   Not a button that says Next. A line in his handwriting and an arrow that
   keeps nodding downward until she takes it. */
.onward { display: grid; place-items: center; padding-top: clamp(10px, 3vw, 26px); }
.onward-btn {
  display: grid; place-items: center; gap: 10px;
  background: none; border: 0; cursor: pointer;
  padding: 12px 20px; border-radius: var(--r-md);
  color: var(--gold);
  font-family: var(--font-hand);
  font-size: clamp(1.25rem, 3.6vw, 1.6rem); line-height: 1;
  text-shadow: 0 0 30px rgba(255, 193, 7, .3);
  transition: transform .25s var(--ease-back), color .25s, text-shadow .25s;
  animation: onward-appear .7s var(--ease) both;
}
@keyframes onward-appear { from { opacity: 0; transform: translateY(-8px); } }
.onward-btn:hover { transform: translateY(3px); color: var(--cream); }
.onward-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 4px; }

.onward-arrow {
  width: 22px; height: 22px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  opacity: .75;
  animation: onward-nod 2.1s ease-in-out infinite;
}
@keyframes onward-nod {
  0%, 100% { transform: rotate(45deg) translate(0, 0); opacity: .55; }
  50%      { transform: rotate(45deg) translate(4px, 4px); opacity: 1; }
}

/* sound, tucked away rather than sitting in a toolbar */
/* ---------------------------------------------------------------- photos */
.photo {
  position: relative; overflow: hidden; border-radius: var(--r-sm);
  background: rgba(255, 255, 255, .05);
  aspect-ratio: 4 / 3;
}
.photo img { display: block; width: 100%; height: 100%; object-fit: cover; opacity: 0; transition: opacity .6s; }
.photo.is-loaded img { opacity: 1; }
.photo.is-video { aspect-ratio: 4 / 5; background: #05070f; }
/* clips are moving photos: they fill their frame the way a still does, with
   no progress bar, just a sound button in the corner */
.photo video { display: block; width: 100%; height: 100%; object-fit: cover; background: #05070f; cursor: pointer; }
.clip-sound {
  position: absolute; top: 10px; right: 10px; z-index: 3;
  width: 40px; height: 40px; padding: 0; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(5, 7, 15, .55); color: #fff;
  border: 1px solid rgba(255, 255, 255, .3);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  cursor: pointer; transition: transform .2s var(--ease), background .2s, color .2s;
}
.clip-sound svg { width: 20px; height: 20px; }
.clip-sound:hover { transform: scale(1.08); }
.clip-sound.is-on { background: var(--gold); color: #1a1200; border-color: transparent; }
.mem-grid .clip-sound { top: 7px; right: 7px; width: 34px; height: 34px; }
.mem-grid .clip-sound svg { width: 17px; height: 17px; }
/* paused by her, or waiting for a tap (reduced motion): show it's a clip */
.photo.is-video.is-held::after, .photo.is-video.is-still::after {
  content: ''; position: absolute; inset: 0; margin: auto; z-index: 2;
  width: 54px; height: 54px; border-radius: 50%; pointer-events: none;
  background: rgba(5, 7, 15, .55) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M8 5v14l11-7z' fill='white'/%3E%3C/svg%3E") 56% 50% / 24px no-repeat;
}

.photo-ph {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 9px; padding: 14px; text-align: center;
  background:
    linear-gradient(140deg,
      hsl(calc(var(--ph-h) * 1deg) 62% 32%),
      hsl(calc((var(--ph-h) + 54) * 1deg) 54% 18%));
}
.photo-ph::after { content: ''; position: absolute; inset: 9px; border: 1px dashed rgba(255, 255, 255, .26); border-radius: 6px; }
.photo-ph-emoji { font-size: clamp(1.7rem, 6vw, 2.5rem); opacity: .9; }
.photo-ph-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .67rem; color: rgba(255, 255, 255, .88);
  background: rgba(0, 0, 0, .34); padding: 3px 9px; border-radius: 6px;
}
.photo-ph-hint { font-size: .6rem; letter-spacing: .12em; text-transform: uppercase; color: rgba(255, 255, 255, .5); }

.mem-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.mem-grid .photo { aspect-ratio: 1 / 1; border-radius: 7px; }
.mem-grid .photo.is-video { aspect-ratio: 1 / 1; }
.mem-grid .photo-ph-hint { display: none; }
.mem-grid .photo-ph-code { font-size: .56rem; }
.mem-grid .photo-ph-emoji { font-size: 1.4rem; }

/* ---------------------------------------------------------------- 1. memory lane */
/* Full-width spreads: big media, a glass caption card lapping over one
   corner of it, and a huge ghosted index number behind. */
.lane { display: grid; gap: clamp(72px, 12vw, 152px); }

.mem {
  position: relative;
  opacity: 0; transform: translateY(42px);
  transition: opacity .9s var(--ease), transform .9s var(--ease);
}
.mem.in { opacity: 1; transform: none; }

/* Scroll-in sequence, run when a memory gets .in: the photos wipe open from
   the side they sit on (grid tiles one after another) while settling from a
   slight zoom, then the caption card rises in and the ghosted number fades
   up behind. Clip and zoom live on the pictures, not .mem-media, because
   .mem-media's transform is the parallax and a clip there would cut its shadow. */
.mem-media > .photo,
.mem-media > .mem-grid > .photo {
  clip-path: inset(0 100% 0 0);
  transition: clip-path 1.15s var(--ease);
}
.mem-flip .mem-media > .photo,
.mem-flip .mem-media > .mem-grid > .photo { clip-path: inset(0 0 0 100%); }
.mem.in .mem-media > .photo,
.mem.in .mem-media > .mem-grid > .photo { clip-path: inset(0); }
.mem-grid > .photo:nth-child(2) { transition-delay: .14s; }
.mem-grid > .photo:nth-child(3) { transition-delay: .28s; }
.mem-grid > .photo:nth-child(4) { transition-delay: .42s; }

.mem .photo img, .mem .photo video {
  transform: scale(1.14);
  transition: opacity .6s, transform 1.7s var(--ease);
}
.mem.in .photo img, .mem.in .photo video { transform: none; }

.mem-card {
  opacity: 0; transform: translateY(48px);
  transition: opacity .8s var(--ease) .38s, transform 1s var(--ease) .38s;
}
.mem.in .mem-card { opacity: 1; transform: none; }

.mem-index { opacity: 0; transition: opacity 1.4s var(--ease) .2s; }
.mem.in .mem-index { opacity: 1; }

/* Scroll-driven depth: --py is set per card by app.js from its position in
   the viewport. The photo drifts one way and the big ghosted number twice as
   far the other, so scrolling reads as moving through the picture. */
.mem-media { transform: translateY(var(--py, 0px)); will-change: transform; }
.mem-index { transform: translateY(calc(var(--py, 0px) * -2.1)); }
.mem-flip .mem-index { transform: translateY(calc(var(--py, 0px) * -2.1)); }

.mem-index {
  position: absolute; z-index: 0; top: -.34em; left: -.05em;
  font-family: var(--font-disp); font-weight: 400;
  font-size: clamp(5.5rem, 19vw, 13rem); line-height: .8;
  color: transparent;
  -webkit-text-stroke: 1.5px rgba(234, 243, 255, .12);
  pointer-events: none; user-select: none;
}
.mem-flip .mem-index { left: auto; right: -.05em; }

.mem-media {
  position: relative; z-index: 1;
  width: min(100%, 820px);
  margin-right: auto;
  border-radius: var(--r-lg); overflow: hidden;
  border: 1px solid var(--glass-brd);
  box-shadow: 0 44px 96px -36px rgba(0, 0, 0, 1);
}
.mem-flip .mem-media { margin-right: 0; margin-left: auto; }
.mem-media > .photo { aspect-ratio: 16 / 10; border-radius: 0; }
.mem-media.is-video-media { width: min(100%, 460px); }
.mem-media > .photo.is-video { aspect-ratio: 3 / 4; }

/* A grid of shots fills exactly the same box a single photo would, so a
   four-photo memory doesn't tower over a one-photo memory. */
.mem-media > .mem-grid {
  display: grid; gap: 3px;
  aspect-ratio: 16 / 10;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: repeat(2, 1fr);
}
.mem-media > .mem-grid[data-count="2"] { grid-template-rows: 1fr; }
.mem-grid .photo { aspect-ratio: auto; height: 100%; border-radius: 0; }
/* clips fill their cell like the photos beside them (the square rule above
   would otherwise leave a video a narrow box in a wide cell) */
.mem-grid .photo.is-video { aspect-ratio: auto; }
/* three shots stand side by side: nearly everything here is a portrait phone
   photo, and three tall columns crop them far less than one wide strip would */
.mem-media > .mem-grid[data-count="3"] { grid-template-columns: repeat(3, 1fr); grid-template-rows: 1fr; }
@media (max-width: 560px) {
  /* on a phone a 16:10 box of four is too shallow to show a face */
  .mem-media > .mem-grid[data-count="4"] { aspect-ratio: 1 / 1; }
}
.mem-grid .photo-ph-hint { display: none; }
.mem-grid .photo-ph-code { font-size: .58rem; }
.mem-grid .photo-ph-emoji { font-size: 1.5rem; }

.mem-card {
  position: relative; z-index: 2;
  width: min(100%, 470px);
  margin: -68px 0 0 auto;
  padding: clamp(22px, 3vw, 32px);
  border-radius: var(--r-lg);
  background: var(--glass);
  border: 1px solid var(--glass-brd);
  backdrop-filter: blur(var(--blur)) saturate(150%);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(150%);
  box-shadow: 0 30px 66px -30px rgba(0, 0, 0, .95), inset 0 1px 0 var(--glass-hi);
}
.mem-flip .mem-card { margin-left: 0; margin-right: auto; }

.mem-when {
  margin: 0 0 10px;
  font-family: var(--font-disp); font-style: italic; font-size: 1.02rem;
  letter-spacing: 0; text-transform: none; color: var(--gold); opacity: .85;
}
.mem-title {
  margin: 0 0 12px; font-family: var(--font-disp); font-weight: 400;
  font-size: clamp(1.3rem, 3.6vw, 1.9rem); line-height: 1.12; color: var(--cream);
}
.mem-caption { margin: 0 0 16px; color: var(--text-dim); font-size: clamp(.9rem, 2.4vw, 1rem); }
.mem-joke {
  margin: 0; font-family: var(--font-hand);
  font-size: clamp(1.2rem, 3.6vw, 1.55rem); line-height: 1.3;
  color: var(--gold); text-shadow: 0 0 26px rgba(255, 193, 7, .35);
}

@media (max-width: 820px) {
  .mem-media { width: 100%; }
  .mem-card { width: 92%; margin: -46px auto 0; }
  .mem-flip .mem-card { margin: -46px auto 0; }
  .mem-index { font-size: clamp(4rem, 22vw, 8rem); }
}

/* ---------------------------------------------------------------- 2. quiz */
.quiz { max-width: 780px; margin: 0 auto; }
.quiz-intro,
.ow-intro {
  text-align: center; font-family: var(--font-hand);
  font-size: clamp(1.25rem, 4.2vw, 1.7rem); color: var(--gold); margin: 0 0 26px;
  text-shadow: 0 0 30px rgba(255, 193, 7, .3);
}
.ow-intro {
  max-width: 32ch; margin-inline: auto;
  font-size: clamp(1.2rem, 4vw, 1.55rem);
  transform: rotate(-.8deg);
  margin-bottom: clamp(20px, 3.5vw, 30px);
}

/* dots instead of a bar, so the whole run is visible at a glance */

.qcard {
  position: relative;
  padding: clamp(24px, 4vw, 40px);
  border-radius: var(--r-xl);
  opacity: 0; transform: translateY(18px) scale(.985);
  transition: opacity .55s var(--ease), transform .55s var(--ease);
}
.qcard.in { opacity: 1; transform: none; }
.qcard-count { margin: 0 0 12px; font-size: .6rem; letter-spacing: .14em; text-transform: uppercase; color: var(--text-faint); }
.qcard-q {
  margin: 0 0 26px; font-family: var(--font-disp); font-weight: 400;
  font-size: clamp(1.25rem, 4.2vw, 1.8rem); line-height: 1.22; color: var(--cream);
}

/* answers are tiles, two across on anything wide enough */
.qcard-opts { display: grid; gap: 12px; }
@media (min-width: 760px) { .qcard-opts { grid-template-columns: 1fr 1fr; } }

.qopt {
  display: flex; flex-direction: column; align-items: flex-start; gap: 11px;
  width: 100%; min-height: 112px;
  padding: 18px; border-radius: var(--r-md); text-align: left;
  background: rgba(255, 255, 255, .05);
  border: 1px solid var(--glass-brd);
  color: var(--text); font-family: var(--font-ui); font-size: .97rem; line-height: 1.4;
  cursor: pointer; transition: all .25s var(--ease);
}
.qopt:nth-child(1) { --accent: var(--gold); }
.qopt:nth-child(2) { --accent: var(--cyan); }
.qopt:nth-child(3) { --accent: var(--pink); }
.qopt:nth-child(4) { --accent: var(--mint); }
.qopt:hover:not(:disabled) {
  background: var(--glass-up);
  border-color: var(--accent);
  transform: translateY(-3px);
  box-shadow: 0 14px 34px -18px var(--accent);
}
.qopt:disabled { cursor: default; }
.qopt-key {
  flex: 0 0 auto; width: 30px; height: 30px; border-radius: 10px;
  display: grid; place-items: center; font-size: .74rem; font-weight: 700;
  background: color-mix(in srgb, var(--accent) 26%, transparent);
  color: var(--accent);
  transition: all .25s var(--ease);
}
.qopt.is-right { background: rgba(52, 229, 164, .16); border-color: var(--mint); box-shadow: 0 0 34px -10px var(--mint); }
.qopt.is-right .qopt-key { background: var(--mint); color: #04372a; }
.qopt.is-wrong { background: rgba(255, 10, 108, .14); border-color: var(--pink); }
.qopt.is-wrong .qopt-key { background: var(--pink); color: #fff; }

/* a fifth option gets its own colour, and when the tiles sit two across a
   lone last one takes the whole row rather than half of it */
.qopt:nth-child(5) { --accent: var(--coral); }
@media (min-width: 760px) { .qcard-opts > .qopt:last-child:nth-child(odd) { grid-column: 1 / -1; min-height: 0; } }

/* photos to pick between: two across at every width, the photo is the tile */
.qcard-opts.has-photos { grid-template-columns: 1fr 1fr; }
.qcard-opts.has-photos .qopt {
  flex-direction: column; align-items: stretch; gap: 0;
  min-height: 0; padding: 8px;
}
.qcard-opts.has-photos .qopt .photo { aspect-ratio: 1 / 1; border-radius: calc(var(--r-md) - 6px); }
.qcard-opts.has-photos .qopt-key { position: absolute; top: 16px; left: 16px; z-index: 2; backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }

/* a typed answer: one field, one button, and a way past after two misses */
.qtype-row { display: flex; flex-wrap: wrap; gap: 10px; }
.qtype-input {
  flex: 1 1 220px; min-width: 0;
  padding: 15px 18px; border-radius: var(--r-md);
  background: rgba(255, 255, 255, .06); border: 1px solid var(--glass-brd);
  color: var(--cream); font-family: var(--font-ui); font-size: 1.1rem;
  transition: border-color .25s var(--ease), box-shadow .25s var(--ease);
}
.qtype-input::placeholder { color: var(--text-faint); }
.qtype-input:focus { outline: none; border-color: var(--gold); box-shadow: 0 0 0 3px rgba(255, 193, 7, .22); }
.qtype-row.is-wrong .qtype-input { border-color: var(--pink); animation: qwrong .45s ease-in-out; }
.qtype-row.is-right .qtype-input { border-color: var(--mint); background: rgba(52, 229, 164, .12); }
.qtype-note { margin: 12px 0 0; min-height: 1.4em; color: var(--pink); font-weight: 500; }
.qtype-note:empty { min-height: 0; margin: 0; }
/* each miss hands over the next hint, in the handwriting, fading in */
.qtype-hint { margin: 8px 0 0; font-family: var(--font-hand); font-size: 1.25rem; line-height: 1.35; color: var(--gold); }
.qtype-hint:empty { display: none; }
.qtype-hint.is-new { animation: qhint .5s var(--ease); }
@keyframes qhint { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }
.qtype-skip {
  margin-top: 10px; padding: 4px 0; background: none; border: 0; cursor: pointer;
  color: var(--text-faint); font-family: var(--font-hand); font-size: 1.1rem;
  text-decoration: underline; text-underline-offset: 3px;
}
.qtype-skip:hover { color: var(--cream); }

.qresult {
  margin-top: 24px; padding-top: 22px;
  border-top: 1px solid var(--glass-brd);
  opacity: 0; transform: translateY(12px);
  transition: opacity .5s var(--ease), transform .5s var(--ease);
}
.qresult.in { opacity: 1; transform: none; }
.qresult-line { margin: 0 0 16px; font-size: 1.02rem; font-weight: 500; }
.qresult.is-right .qresult-line { color: var(--mint); }
.qresult.is-wrong .qresult-line { color: var(--pink); }

.qunlock {
  margin: 0 0 18px; padding: 16px; border-radius: var(--r-lg);
  background: rgba(255, 193, 7, .08);
  border: 1px dashed rgba(255, 193, 7, .42);
}
.qunlock-tag { margin: 0 0 12px; font-size: .58rem; letter-spacing: .14em; text-transform: uppercase; color: var(--gold); }
.qunlock .photo { border-radius: var(--r-sm); }
/* an unlocked photo is the reward, so it is shown whole at its own shape
   rather than cropped to a wide box (most of them are tall phone photos) */
.qunlock .photo:not(.is-video) { aspect-ratio: auto; display: flex; justify-content: center; background: none; }
.qunlock .photo:not(.is-video) img {
  width: auto; max-width: 100%; height: auto; max-height: min(70vh, 620px);
  border-radius: var(--r-sm);
}
.qunlock-text { margin: 13px 0 0; font-family: var(--font-hand); font-size: 1.3rem; color: var(--gold); }
.qnext { margin-top: 8px; }
.qagain { margin-top: 26px; }

.qscore { text-align: center; opacity: 0; transform: translateY(16px); transition: opacity .7s var(--ease), transform .7s var(--ease); }
.qscore.in { opacity: 1; transform: none; }
.qscore-verdict { margin: 0 auto 32px; max-width: 42ch; font-family: var(--font-hand); font-size: clamp(1.3rem, 4.6vw, 1.8rem); color: var(--cream); }
.qgrid-tag { font-size: .6rem; letter-spacing: .14em; text-transform: uppercase; color: var(--text-faint); margin: 0 0 16px; }
.qgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 170px), 1fr)); gap: 18px; text-align: left; }
.qgrid-cell { margin: 0; }
.qgrid-cell figcaption { margin-top: 10px; font-family: var(--font-hand); font-size: 1.15rem; color: var(--gold); }

/* ---------------------------------------------------------------- 3. letter */
.envelope-stage { display: grid; place-items: center; gap: 38px; }

.envelope {
  position: relative; display: block; cursor: pointer;
  width: min(410px, 86vw); aspect-ratio: 3 / 2;
  padding: 0; border: 0; background: none;
  perspective: 1100px;
  transition: transform .8s var(--ease), opacity .8s var(--ease);
  animation: sway 6s ease-in-out infinite;
}
.env-body {
  position: absolute; inset: 0; border-radius: 10px;
  background: linear-gradient(165deg, var(--paper) 0%, var(--paper-2) 100%);
  box-shadow: 0 40px 80px -30px rgba(0, 0, 0, 1), 0 0 70px -24px rgba(255, 61, 154, .45), inset 0 1px 0 rgba(255, 255, 255, .6);
}
.env-body::before {
  content: ''; position: absolute; inset: 0; border-radius: 10px;
  background:
    linear-gradient(45deg, transparent calc(50% - 1px), rgba(0, 0, 0, .07) 50%, transparent calc(50% + 1px)),
    linear-gradient(-45deg, transparent calc(50% - 1px), rgba(0, 0, 0, .07) 50%, transparent calc(50% + 1px));
}
.env-body::after {
  content: ''; position: absolute; inset: 0; border-radius: 10px;
  border: 7px solid transparent;
  background: repeating-linear-gradient(45deg,
    var(--pink) 0 9px, transparent 9px 18px, var(--cyan) 18px 27px, transparent 27px 36px) border-box;
  -webkit-mask: linear-gradient(#000 0 0) padding-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  opacity: .8;
}
.env-flap {
  position: absolute; top: 0; left: 0; right: 0; height: 62%;
  background: linear-gradient(180deg, #F3E6E1, #E2CFC9);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  transform-origin: top center;
  transition: transform .85s var(--ease);
  box-shadow: 0 8px 18px -10px rgba(0, 0, 0, .6);
  z-index: 3;
}
.envelope.is-open .env-flap { transform: rotateX(178deg); z-index: 1; }
.env-stamp {
  position: absolute; top: 15px; right: 17px; z-index: 4;
  width: 58px; height: 68px; padding: 5px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 1px;
  background: #fffdf7; color: #3d2b18;
  border: 1px dashed rgba(0, 0, 0, .3);
  transform: rotate(4deg);
  box-shadow: 0 5px 12px -5px rgba(0, 0, 0, .6);
  transition: opacity .6s;
}
.envelope.is-open .env-stamp { opacity: .35; }
.stamp-country { font-size: .42rem; letter-spacing: .08em; font-weight: 700; text-align: center; line-height: 1.1; }
.stamp-emoji { font-size: 1.15rem; line-height: 1; }
.stamp-value { font-family: var(--font-disp); font-weight: 400; font-size: .9rem; line-height: 1; }

.wax {
  position: absolute; left: 50%; top: 58%; z-index: 5;
  width: 90px; height: 90px; margin: -45px 0 0 -45px;
  transition: transform .35s var(--ease-back);
  animation: seal-pulse 2.6s ease-in-out infinite;
}
@keyframes seal-pulse { 50% { transform: scale(1.05); } }
.envelope:hover .wax { transform: scale(1.12) rotate(-5deg); animation: none; }
.wax-svg { width: 100%; height: 100%; filter: drop-shadow(0 10px 22px rgba(255, 61, 154, .8)); }
.wax-blob { fill: var(--pink); }
.wax-ring { fill: none; stroke: rgba(255, 255, 255, .38); stroke-width: 2.5; }
.wax-letter { font-family: var(--font-disp); font-weight: 400; font-size: 38px; fill: #fff; }
.envelope.is-cracking .wax { animation: crack .55s var(--ease) forwards; }
@keyframes crack {
  0%   { transform: scale(1); }
  28%  { transform: scale(1.3) rotate(-10deg); }
  100% { transform: scale(.4) rotate(30deg) translateY(80px); opacity: 0; }
}
.env-hint {
  position: absolute; left: 50%; bottom: -38px; transform: translateX(-50%);
  font-family: var(--font-hand); font-size: 1.35rem; color: var(--gold);
  white-space: nowrap; animation: nudge 2.2s ease-in-out infinite;
  text-shadow: 0 0 24px rgba(255, 193, 7, .5);
}
.envelope.is-open .env-hint { opacity: 0; }
@keyframes nudge { 50% { transform: translateX(-50%) translateY(6px); } }

/* Once the letter is out, the envelope lies back and drops away, which is
   what an envelope actually does when you pull something out of it. */
.envelope-stage.is-open .envelope {
  transform: perspective(900px) translateY(20px) rotateX(28deg) scale(.5);
  opacity: .4; animation: none;
  transition: transform 1.1s cubic-bezier(.22, .8, .3, 1), opacity 1.1s var(--ease);
}

.letter {
  /* Every vertical measurement below is a multiple of --rule, so the text
     actually sits on the ruled lines instead of drifting across them. */
  --rule: 32px;
  width: min(680px, 92vw);
  padding: calc(var(--rule) * 2) clamp(24px, 6vw, 56px);
  border-radius: 6px;
  background:
    repeating-linear-gradient(180deg,
      transparent 0 calc(var(--rule) - 1px),
      rgba(60, 40, 20, .07) calc(var(--rule) - 1px) var(--rule)),
    linear-gradient(170deg, var(--cream), var(--paper));
  color: #241a0e;
  box-shadow: 0 50px 100px -34px rgba(0, 0, 0, 1), 0 0 90px -30px rgba(255, 193, 7, .35);
  /* It comes OUT of the envelope and THEN opens, in that order, because
     that order is the entire trick. Three movements: it rises while still
     folded almost shut, it tips open and overshoots slightly the way stiff
     paper does, and then it settles flat. The shadow grows underneath it the
     whole way, so it reads as a sheet arriving in front of her rather than a
     panel fading in. */
  position: relative;
  transform-origin: top center;
  transform: perspective(1400px) translateY(70px) rotateX(-89deg) scale(.88);
  opacity: 0;
  box-shadow: 0 6px 18px -12px rgba(0, 0, 0, .95);
}
.letter.in { animation: letter-open 1.5s cubic-bezier(.2, .78, .25, 1) forwards; }
@keyframes letter-open {
  0%   { opacity: 0;
         transform: perspective(1400px) translateY(70px) rotateX(-89deg) scale(.88);
         box-shadow: 0 6px 18px -12px rgba(0, 0, 0, .95); }
  16%  { opacity: 1; }
  36%  { transform: perspective(1400px) translateY(4px) rotateX(-78deg) scale(.945); }
  76%  { transform: perspective(1400px) translateY(0) rotateX(8deg) scale(1.015); }
  90%  { transform: perspective(1400px) translateY(0) rotateX(-2.6deg) scale(.997); }
  100% { opacity: 1;
         transform: perspective(1400px) translateY(0) rotateX(0deg) scale(1);
         box-shadow: 0 50px 100px -34px rgba(0, 0, 0, 1), 0 0 90px -30px rgba(255, 193, 7, .28); }
}
/* the fold catching the light as it comes level, and losing it as it settles */
.letter::before {
  content: ''; position: absolute; inset: 0; z-index: 0;
  border-radius: 6px; pointer-events: none; opacity: 0;
  background: linear-gradient(178deg,
    rgba(255, 255, 255, .92) 0%,
    rgba(255, 244, 224, .24) 34%,
    rgba(120, 80, 40, .1) 72%,
    transparent 100%);
}
.letter.in::before { animation: letter-crease 1.5s var(--ease) forwards; }
@keyframes letter-crease { 0% { opacity: .95; } 46% { opacity: .5; } 100% { opacity: 0; } }
.letter > * { position: relative; z-index: 1; }

.letter p { opacity: 0; animation: rise .9s var(--ease) forwards; animation-delay: calc(var(--d, .2s) + .95s); }
.letter.in p, .letter.in .letter-sign { opacity: 1; }
.letter-salutation {
  font-family: var(--font-disp); font-weight: 400;
  font-size: clamp(1.6rem, 5vw, 2.2rem);
  line-height: var(--rule); margin: 0 0 var(--rule); color: #17100a;
}
.letter-p {
  margin: 0 0 var(--rule);
  font-size: clamp(.98rem, 2.7vw, 1.06rem);
  line-height: var(--rule);
  color: #33261a;
}
.letter-sign {
  display: flex; flex-direction: column; gap: 2px; margin: var(--rule) 0 0;
  opacity: 0; animation: rise .8s var(--ease) forwards; animation-delay: calc(var(--d, .2s) + .95s);
}
.letter-sign-pre { font-size: .9rem; color: #6b563d; }
.letter-sign-name { font-family: var(--font-hand); font-size: clamp(2.1rem, 7vw, 3rem); color: #C4004F; line-height: 1; }
.letter-ps {
  margin: var(--rule) 0 0;
  padding-top: calc(var(--rule) - 1px);
  line-height: var(--rule);
  border-top: 1px dashed rgba(60, 40, 20, .28);
  font-family: var(--font-hand); font-size: clamp(1.2rem, 3.6vw, 1.5rem); color: #6b3a1f;
}

/* ---------------------------------------------------------------- 5. cake */
.cake-stage { display: grid; place-items: center; gap: clamp(12px, 2vw, 20px); }
.cake-wrap { width: min(360px, 80vw, 36vh); filter: drop-shadow(0 30px 60px rgba(0, 0, 0, .7)); }
.cake-svg { display: block; width: 100%; height: auto; overflow: visible; }
/* The cake is drawn flat-on, so what sells it as a round object is the
   shading: an elliptical top on each tier, and a horizontal light-to-dark
   wash across the sides. Without those two it reads as stacked rectangles. */
.plate { fill: rgba(234, 243, 255, .16); }
.plate-rim { fill: rgba(234, 243, 255, .1); }
.stand { fill: rgba(234, 243, 255, .13); }
.stand-foot { fill: rgba(234, 243, 255, .16); }
.cake-shadow { fill: rgba(0, 0, 0, .45); }

.tier { stroke: rgba(0, 0, 0, .12); stroke-width: 1; }
.tier-top { stroke: rgba(0, 0, 0, .08); stroke-width: 1; }
.tier-round { pointer-events: none; }          /* the cylinder shading */

.drip { fill: #fff6ea; }
.drip-2 { fill: #fffaf3; }

.sprinkles rect, .sprinkles circle { fill: var(--pink); }
.sprinkles rect:nth-child(2n), .sprinkles circle:nth-child(2n) { fill: var(--cyan); }
.sprinkles rect:nth-child(3n), .sprinkles circle:nth-child(3n) { fill: var(--gold); }
.sprinkles rect:nth-child(5n), .sprinkles circle:nth-child(5n) { fill: var(--mint); }

/* The pool of candlelight sitting on the top tier. It goes out with the last
   candle — buildCake already puts .all-out on the svg when that happens. */
.cake-glow {
  opacity: .85;
  transition: opacity 1.1s var(--ease);
  pointer-events: none;
  animation: glow-breathe 3.6s ease-in-out infinite;
}
@keyframes glow-breathe { 50% { opacity: .6; } }
.cake-svg.all-out .cake-glow { opacity: 0; animation: none; }

.candle { cursor: pointer; }
.candle-body { fill: #fdf3e6; }
.candle-stripe { fill: var(--pink); }
.wick { stroke: #3a2a18; stroke-width: 2.4; stroke-linecap: round; }
/* transform-box matters here. Its default (view-box) resolves "center"
   against the whole 420-wide drawing, so every flame was pivoting and scaling
   around x=210 — the middle candle — which threw the outer flames sideways on
   every flicker and made them collapse toward the centre when blown out.
   fill-box measures each flame's own box, so each one flickers where it is. */
.flame-g {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: flicker 1.1s ease-in-out infinite alternate;
}
.flame { fill: var(--orange); }
.flame-core { fill: #fff6d6; }
.flame-glow { fill: var(--gold); opacity: .4; filter: blur(3px); }
@keyframes flicker {
  0%   { transform: scale(1) translateX(0) rotate(-1.5deg); opacity: .95; }
  100% { transform: scale(1.1) translateX(1px) rotate(2deg); opacity: 1; }
}
.smoke { opacity: 0; }
.smoke circle { fill: rgba(234, 243, 255, .42); }
.candle.is-out .flame-g { opacity: 0; transform: scale(.15); transition: opacity .34s, transform .34s; animation: none; }
.smoke { transform-box: fill-box; transform-origin: 50% 100%; }
.candle.is-out .smoke { animation: smoke 1.6s ease-out forwards; }
@keyframes smoke {
  0%   { opacity: .8; transform: translateY(0) scale(.7); }
  100% { opacity: 0; transform: translateY(-50px) scale(1.6); }
}

.cake-prompt {
  margin: 0; font-family: var(--font-hand); font-size: clamp(1.6rem, 5.4vw, 2.3rem); color: var(--gold);
  text-shadow: 0 0 34px rgba(255, 193, 7, .45);
}
.cake-controls { display: flex; flex-wrap: wrap; gap: 13px; justify-content: center; }
.cake-hint { margin: 0; max-width: 46ch; text-align: center; font-size: .84rem; color: var(--text-faint); }
.blow-meter {
  width: min(320px, 80vw); height: 10px; border-radius: 999px;
  background: rgba(255, 255, 255, .09); border: 1px solid var(--glass-brd); overflow: hidden;
}
.blow-meter-fill {
  display: block; height: 100%; width: 0;
  background: linear-gradient(90deg, var(--cyan), var(--gold), var(--pink));
  box-shadow: 0 0 20px rgba(255, 193, 7, .9);
  transition: width .08s linear;
}
/* The meter fills as she sustains a blow rather than just bouncing with the
   volume, so a harder candle still feels fair — she can see it filling. */
.blow-meter.is-blowing {
  border-color: rgba(255, 193, 7, .65);
  box-shadow: 0 0 26px -6px rgba(255, 193, 7, .8);
}
.blow-meter.is-blowing .blow-meter-fill { box-shadow: 0 0 28px rgba(255, 193, 7, 1); }

.cake-hint.is-warn { color: var(--coral); max-width: 42ch; }
.cake-bail {
  background: none; border: 0; cursor: pointer;
  font-family: var(--font-ui); font-size: .76rem;
  color: var(--text-faint); text-decoration: underline; text-underline-offset: 4px;
}
.cake-bail:hover { color: var(--text-dim); }

.cake-done { text-align: center; opacity: 0; transform: translateY(16px); transition: opacity .8s var(--ease), transform .8s var(--ease); }
.cake-done.in { opacity: 1; transform: none; }
.cake-done-big {
  margin: 0 0 12px; font-family: var(--font-disp); font-weight: 400;
  font-size: clamp(1.7rem, 6vw, 2.8rem); -webkit-text-fill-color: transparent; color: transparent;
}
.cake-done-sub { margin: 0; max-width: 42ch; color: var(--text-dim); }

/* ---------------------------------------------------------------- 6c. open when */
.ow-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
  gap: 22px; max-width: 980px; margin: 0 auto;
}
.ow-card {
  position: relative; overflow: hidden;
  min-height: 220px; padding: 26px 22px 44px;
  text-align: left; cursor: pointer; border: 0;
  border-radius: var(--r-lg);
  background: linear-gradient(160deg, var(--paper), var(--paper-2));
  color: #2a1d10; font-family: var(--font-ui);
  box-shadow: 0 30px 66px -32px rgba(0, 0, 0, .95);
  opacity: 0; transform: translateY(26px);
  transition: opacity .7s var(--ease), transform .4s var(--ease), box-shadow .4s var(--ease);
}
.ow-card.in { opacity: 1; transform: none; }
.ow-card:hover:not(.is-open) {
  transform: translateY(-7px) rotate(-.7deg);
  box-shadow: 0 40px 78px -30px rgba(0, 0, 0, 1), 0 0 54px -20px var(--gold);
}
.ow-card:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }

.ow-seal {
  position: absolute; top: -16px; right: -16px; width: 82px; height: 82px; z-index: 2;
  transition: transform .55s var(--ease-back), opacity .45s;
  filter: drop-shadow(0 6px 16px rgba(0, 0, 0, .45));
}
.ow-card.is-open .ow-seal { transform: scale(.3) rotate(58deg) translateY(48px); opacity: 0; }
/* the card gives a little as the seal lets go */
.ow-card.is-open { animation: ow-break .75s var(--ease-back); }
@keyframes ow-break {
  0%   { transform: none; }
  22%  { transform: translateY(-9px) rotate(-1.4deg) scale(1.035); }
  55%  { transform: translateY(2px) rotate(.5deg) scale(.996); }
  100% { transform: none; }
}
.ow-seal svg { width: 100%; height: 100%; }
.ow-seal-disc { fill: var(--pink); }
.ow-seal-ring { fill: none; stroke: rgba(255, 255, 255, .45); stroke-width: 2.5; }
.ow-seal-num { fill: #fff; font-family: var(--font-disp); font-weight: 400; font-size: 34px; }

.ow-when { display: block; padding-right: 64px; margin-bottom: 14px; }
.ow-when em {
  display: block; font-style: normal;
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase;
  color: #8a7150; margin-bottom: 5px;
}
.ow-when b {
  display: block; font-family: var(--font-hand); font-weight: 700;
  font-size: clamp(1.5rem, 4vw, 1.85rem); line-height: 1.1; color: #C4004F;
}
.ow-note {
  margin: 0; font-size: .95rem; line-height: 1.65; color: #3a2a1a;
  opacity: 0;
  transform-origin: top center;
  transform: perspective(900px) rotateX(-72deg);
  transition:
    opacity .5s .22s var(--ease),
    transform .8s .22s cubic-bezier(.16, .84, .28, 1);
}
.ow-card.is-open .ow-note { opacity: 1; transform: perspective(900px) rotateX(0deg); }
.ow-hint {
  position: absolute; left: 22px; bottom: 18px;
  font-size: .58rem; letter-spacing: .2em; text-transform: uppercase;
  color: #9c8a6d;
  transition: opacity .3s;
}
.ow-card.is-open .ow-hint { opacity: 0; }

/* ---------------------------------------------------------------- 7. finale */
#ch-finale { display: grid; place-items: center; }
.finale { width: 100%; display: grid; place-items: center; }
.finale-card {
  position: relative;
  text-align: center; max-width: 46ch;
  padding: clamp(20px, 4vw, 34px);
  opacity: 0; transform: translateY(22px) scale(.97);
  transition: opacity 1.1s var(--ease), transform 1.1s var(--ease);
}
.finale-card.in { opacity: 1; transform: none; }

/* a slow warm glow behind the whole thing, breathing */
.finale-card::before {
  content: ''; position: absolute; inset: -18% -12%;
  z-index: -1; border-radius: 50%; pointer-events: none;
  background: radial-gradient(closest-side,
    color-mix(in srgb, var(--gold) 16%, transparent),
    color-mix(in srgb, var(--pink) 8%, transparent) 55%, transparent);
  filter: blur(26px);
  animation: finale-breathe 7s ease-in-out infinite;
}
@keyframes finale-breathe { 50% { opacity: .55; transform: scale(1.07); } }

.finale-kicker { margin: 0 0 20px; font-size: .66rem; letter-spacing: .34em; text-transform: uppercase; color: var(--text-faint); }

.finale-line {
  margin: 0 0 6px; font-family: var(--font-disp); font-weight: 400;
  font-size: clamp(2.4rem, 11vw, 4.6rem); line-height: 1.02; letter-spacing: -.02em;
  color: var(--cream);
}
/* one word at a time, so it reads as someone saying it */
.finale-word {
  display: inline-block;
  opacity: 0; transform: translateY(18px) rotate(1.5deg); filter: blur(6px);
  animation: word-land .85s var(--ease) forwards;
  animation-delay: var(--w, .2s);
}
.finale-card.in .finale-word { animation-play-state: running; }
@keyframes word-land { to { opacity: 1; transform: none; filter: none; } }

/* the swash draws itself under the line */
.finale-swash { width: min(340px, 78%); margin: 2px auto clamp(18px, 3vw, 26px); }
.finale-swash svg { display: block; width: 100%; height: 26px; overflow: visible; }
.finale-swash path {
  fill: none; stroke: var(--gold); stroke-width: 3; stroke-linecap: round;
  stroke-dasharray: 460; stroke-dashoffset: 460;
  filter: drop-shadow(0 0 10px color-mix(in srgb, var(--gold) 60%, transparent));
  animation: swash-draw 1.5s cubic-bezier(.22, .8, .3, 1) forwards;
  animation-delay: 1.05s;
}
@keyframes swash-draw { to { stroke-dashoffset: 0; } }

.finale-sub {
  margin: 0 0 26px; color: var(--text-dim); font-size: clamp(1rem, 3vw, 1.14rem);
  animation: rise .9s var(--ease) 1.5s both;
}
.finale-sign {
  margin: 0 0 30px; font-family: var(--font-hand);
  font-size: clamp(1.6rem, 5vw, 2.2rem); color: var(--gold);
  text-shadow: 0 0 30px color-mix(in srgb, var(--gold) 40%, transparent);
  animation: rise .9s var(--ease) 1.8s both;
}
.finale-call { display: inline-block; text-decoration: none; margin-bottom: 16px; animation: rise .9s var(--ease) 2.05s both; }
.finale-callback { display: block; margin: 0 auto 14px; width: fit-content; text-decoration: none; animation: rise .9s var(--ease) 2.2s both; }

@media (prefers-reduced-motion: reduce) {
  .finale-word, .finale-sub, .finale-sign,
  .finale-call, .finale-callback { animation: none !important; opacity: 1 !important; transform: none !important; filter: none !important; }
  .finale-swash path { animation: none !important; stroke-dashoffset: 0 !important; }
  .finale-card::before { animation: none !important; }
}

/* ==========================================================================
   SMALLER SCREENS + REDUCED MOTION
   ========================================================================== */
@media (max-width: 700px) {
  :root { --blur: 14px; }
  #aurora { filter: blur(62px); opacity: calc(var(--aurora, .5) * .9); }
  #spotlight { display: none; }
  #grain { opacity: .1; }
  .eyebrow { font-size: .62rem; letter-spacing: .16em; max-width: 88vw; }
  .gate-title-small { letter-spacing: .14em; }
}

/* Karla's numerals are wider than the old face, which pushed the two clocks
   past the right edge on narrow phones. Let them shrink instead of clip. */
@media (max-width: 430px) {
  .clocks { gap: 6px; }
  .clock { min-width: 0; flex: 1 1 auto; }
  .clock-gap { max-width: none; min-width: 0; flex: 0 1 auto; }
  .clock-gap-line { display: none; }
  .clock-gap-text { white-space: normal; text-align: center; font-size: .54rem; letter-spacing: .06em; }
  .clock-face { font-size: 1.08rem; }
  .clock-label { font-size: .56rem; letter-spacing: .08em; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  #aurora, #balloons, #grain, #spotlight { display: none; }
  body { background: linear-gradient(180deg, var(--bg-0), var(--bg-1) 55%, var(--bg-0)); }
  .qcard, .qresult, .qscore, .map-stats, .map-final, .cake-done,
  .letter, .letter p, .reveal-box, .reveal-message p, .finale-card {
    opacity: 1 !important; transform: none !important; filter: none !important;
  }
  /* memory lane keeps its scroll-in even here - put back the timings the
     blanket rule above flattened */
  .lane .mem { transition-duration: .9s, .9s !important; }
  .lane .mem-media > .photo, .lane .mem-media > .mem-grid > .photo { transition-duration: 1.15s !important; }
  .lane .mem .photo img, .lane .mem .photo video { transition-duration: .6s, 1.7s !important; }
  .lane .mem-card { transition-duration: .8s, 1s !important; }
  .lane .mem-index { transition-duration: 1.4s !important; }
  .letter, .letter.in { animation: none !important; }
  .letter::before, .letter.in::before { display: none !important; }
}

/* ── Numerals ───────────────────────────────────────────────────────────────
   Instrument Serif has proportional figures — a "1" is roughly half the width
   of an "8" — so anything that ticks would visibly jump every second. Karla
   supports real tabular figures (measured: zero variance across 0-9), so every
   live or counting number uses it. Static numerals stay in the serif, where
   proportional figures look better. */
.cd-num, .clock-face {
  font-family: var(--font-ui);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  letter-spacing: -.02em;
}

/* ==========================================================================
   PRINT / SAVE AS PDF
   If she ever hits print or "share -> print" on her phone, she gets the
   letter on clean paper instead of a dark screenshot with a nav bar on it.
   That makes the letter something she can actually keep.
   ========================================================================== */
@media print {
  #aurora, #grain, #balloons, #sky, #spotlight, #bloom, #confetti,
  #testbar, .envelope, .env-hint,
  .btn { display: none !important; }

  html, body { background: #fff !important; color: #14100a !important; }
  body { font-size: 12pt; }

  /* memories she never scrolled to would otherwise print blank */
  .mem, .mem-card, .mem-index { opacity: 1 !important; transform: none !important; }
  .mem-media > .photo, .mem-media > .mem-grid > .photo { clip-path: none !important; }

  .scene { min-height: 0; padding: 0; max-width: none; }
  .scene + .scene::before { display: none; }

  .letter {
    width: auto; max-width: 100%;
    margin: 0; padding: 0;
    background: #fff !important;
    box-shadow: none !important;
    border-radius: 0;
    opacity: 1 !important; transform: none !important;
    animation: none !important;
    color: #14100a !important;
  }
  .letter::before { display: none !important; }
  .letter p, .letter .letter-sign { opacity: 1 !important; animation: none !important; }
  .letter-salutation, .letter-p, .letter-ps { color: #14100a !important; }
  .letter-sign-name { color: #7a1029 !important; }

  /* keep paragraphs whole across a page break */
  .letter-p, .letter-sign, .ow-card { break-inside: avoid; page-break-inside: avoid; }

  .ow-card, .qcard, .mem-card {
    background: #fff !important; border: 1px solid #ccc !important;
    box-shadow: none !important; color: #14100a !important;
    opacity: 1 !important; transform: none !important;
  }
  .mem-title, .mem-caption, .ow-note { color: #14100a !important; }
  .mem-joke, .ow-when b, .qunlock-text { color: #7a1029 !important; }

  a[href^="http"]::after { content: ""; }   /* no URL clutter */
}

/* ==========================================================================
   THE ASK — sound + microphone, before anything starts
   ========================================================================== */
.stage-permit { --stage-max: 640px; }
.permit { display: grid; place-items: center; }
.permit-card {
  width: min(560px, 100%);
  padding: clamp(28px, 5vw, 44px);
  border-radius: var(--r-xl);
  background: var(--glass);
  border: 1px solid var(--glass-brd);
  backdrop-filter: blur(var(--blur)) saturate(150%);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(150%);
  box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .95), inset 0 1px 0 var(--glass-hi);
  opacity: 0; transform: translateY(22px) scale(.98);
  transition: opacity .8s var(--ease), transform .8s var(--ease);
}
.permit-card.in { opacity: 1; transform: none; }
.permit-kicker {
  margin: 0 0 10px; font-size: .64rem; letter-spacing: .22em;
  text-transform: uppercase; color: var(--gold);
}
.permit-line {
  margin: 0 0 28px; color: var(--text-dim);
  font-family: var(--font-disp); font-style: italic;
  font-size: clamp(1.02rem, 2.8vw, 1.2rem); line-height: 1.5;
}
.permit-rows { display: grid; gap: 14px; }
.permit-row {
  display: grid; grid-template-columns: auto 1fr; gap: 15px; align-items: start;
  padding: 16px; border-radius: var(--r-md);
  background: rgba(255, 255, 255, .04);
  border: 1px solid var(--glass-brd);
  transition: border-color .4s var(--ease), background .4s var(--ease);
}
.permit-row.is-done { border-color: color-mix(in srgb, var(--mint) 60%, transparent); background: color-mix(in srgb, var(--mint) 9%, transparent); }
.permit-row.is-refused { border-color: color-mix(in srgb, var(--pink) 45%, transparent); }
.permit-icon { width: 26px; height: 26px; color: var(--gold); }
.permit-icon svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.permit-row.is-done .permit-icon { color: var(--mint); }
.permit-btn {
  background: none; border: 0; padding: 0; cursor: pointer;
  font-family: var(--font-ui); font-size: 1rem; font-weight: 600;
  color: var(--cream); text-align: left;
  border-bottom: 1px solid color-mix(in srgb, var(--gold) 55%, transparent);
  transition: color .3s, border-color .3s;
}
.permit-btn:hover:not(:disabled) { color: var(--gold); }
.permit-btn:disabled { cursor: default; border-color: transparent; }
.permit-row.is-done .permit-btn { color: var(--mint); }
.permit-why { margin: 7px 0 0; font-size: .84rem; color: var(--text-faint); line-height: 1.5; }
/* There is no skip on this screen. The only note that ever appears is the one
   telling her how to undo a browser-level block, so it has to be readable
   rather than quiet. */
.permit-row.is-refused .permit-why { color: color-mix(in srgb, var(--coral) 88%, #fff); }
.permit-row.is-refused .permit-btn { color: var(--coral); border-color: color-mix(in srgb, var(--coral) 60%, transparent); }

/* ==========================================================================
   THE THREE OF US, CLAPPING
   ========================================================================== */
.clappers {
  display: flex; justify-content: center; align-items: flex-end;
  gap: clamp(16px, 5vw, 44px); flex-wrap: wrap;
  margin-top: clamp(18px, 4vw, 34px);
}
.clapper {
  margin: 0; display: grid; justify-items: center; gap: 8px;
  opacity: 0; transform: translateY(18px);
  transition: opacity .8s var(--ease), transform .8s var(--ease);
  transition-delay: var(--d, 0s);
}
.clapper.in { opacity: 1; transform: none; }

.clapper-body { display: block; width: clamp(78px, 17vw, 116px); }
.ch-fig { display: block; width: 100%; height: auto; overflow: visible; }

/* the shading that makes them read as little rendered figures rather than
   flat cartoons: a soft body gradient, a rim light down one side, and a
   cast shadow on the ground */
.ch-fig .ch-rim { fill: none; stroke: #FFE9C8; stroke-width: 2.4; opacity: .3; stroke-linecap: round; }
.ch-shadow { fill: rgba(0, 0, 0, .5); filter: blur(3px); }
.ch-sleeve { fill: currentColor; }
.clapper .ch-fig { color: #1C2230; }
.clapper:nth-child(2) .ch-fig { color: #16181F; }
.clapper:nth-child(3) .ch-fig { color: #1E7A4C; }

/* the arms swing from the shoulder end of their own box */
.ch-fig .arm { transform-box: fill-box; }
.ch-fig .arm-l { transform-origin: 0% 50%; transform: rotate(-15deg); }
.ch-fig .arm-r { transform-origin: 100% 50%; transform: rotate(15deg); }

.clapper figcaption {
  font-size: .62rem; letter-spacing: .14em; text-transform: uppercase;
  color: var(--text-faint);
}

.clappers.is-clapping .arm-l { animation: clap-l .52s ease-in-out infinite; animation-delay: var(--d, 0s); }
.clappers.is-clapping .arm-r { animation: clap-r .52s ease-in-out infinite; animation-delay: var(--d, 0s); }
.clappers.is-clapping .clapper-body { animation: clap-bob .52s ease-in-out infinite; animation-delay: var(--d, 0s); }
.clappers.is-cheering .arm-l,
.clappers.is-cheering .arm-r { animation-duration: .3s; }
@keyframes clap-l { 0%, 100% { transform: rotate(-15deg); } 50% { transform: rotate(4deg); } }
@keyframes clap-r { 0%, 100% { transform: rotate(15deg); }  50% { transform: rotate(-4deg); } }
@keyframes clap-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }

@media (prefers-reduced-motion: reduce) {
  .clappers.is-clapping .arm-l,
  .clappers.is-clapping .arm-r,
  .clappers.is-clapping .clapper-body { animation: none; }
  .clapper { opacity: 1 !important; transform: none !important; }
  .permit-card { opacity: 1 !important; transform: none !important; }
}

/* The cake shares its screen with three people clapping, so everything on it
   is measured against the viewport height as well as the width — a staged
   moment that scrolls is not a staged moment. */
#ch-cake {
  gap: clamp(10px, 1.6vw, 18px);
  padding-top: clamp(28px, 4vw, 52px);
  padding-bottom: clamp(24px, 3vw, 40px);
}
#ch-cake .cake-prompt { font-size: clamp(1.35rem, 4.2vw, 1.9rem); }
#ch-cake .cake-hint { margin-top: 2px; font-size: .8rem; }
.clapper-body { width: clamp(74px, 16vw, 104px); }
.clappers { margin-top: clamp(10px, 2vw, 20px); gap: clamp(14px, 4vw, 36px); }
.clappers-note { margin-top: 10px; font-size: 1.05rem; }
@media (max-height: 780px) {
  .cake-wrap { width: min(280px, 72vw, 32vh); }
  .clapper-body { width: clamp(62px, 13vw, 84px); }
  .clappers-note { display: none; }
}

/* ==========================================================================
   THE QUIZ — more of a game, less of a form
   ========================================================================== */
.quiz-intro {
  font-size: clamp(1.4rem, 4.6vw, 2.05rem);
  transform: rotate(-1.2deg);
  margin-bottom: clamp(22px, 4vw, 34px);
}

/* the question is dealt in from the side rather than fading up */
.qcard {
  transform: translateX(38px) rotate(1.2deg) scale(.97);
  transition: opacity .5s var(--ease), transform .6s var(--ease-back);
}
.qcard.in { transform: none; }
.qcard-count { color: var(--gold); opacity: .7; }
.qcard-q { font-size: clamp(1.4rem, 4.8vw, 2.1rem); line-height: 1.18; }

/* the tiles get weight, a press, and a colour of their own */
.qopt {
  position: relative; overflow: hidden;
  min-height: 118px;
  transition: transform .22s var(--ease-back), background .25s, border-color .25s, box-shadow .25s, opacity .35s, filter .35s;
}
.qopt::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(120% 80% at 12% 0%, color-mix(in srgb, var(--accent) 20%, transparent), transparent 62%);
  opacity: 0; transition: opacity .3s;
}
.qopt:hover:not(:disabled)::after { opacity: 1; }
.qopt:hover:not(:disabled) { transform: translateY(-5px) scale(1.015); }
.qopt:active:not(:disabled) { transform: translateY(-1px) scale(.99); }
.qopt-key { width: 34px; height: 34px; border-radius: 11px; font-size: .8rem; }

/* once she has answered, everything she did not pick steps back */
.qcard[data-done="1"] .qopt:not(.is-right):not(.is-wrong) { opacity: .32; filter: saturate(.5); }
.qopt.is-right { animation: qright .7s var(--ease-back); }
@keyframes qright {
  0%   { transform: none; }
  35%  { transform: scale(1.045); box-shadow: 0 0 46px -6px var(--mint); }
  100% { transform: none; }
}
.qopt.is-wrong { animation: qwrong .45s ease-in-out; }
@keyframes qwrong {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-7px); }
  60% { transform: translateX(5px); }
}

/* the reward panel arrives properly */
.qresult { transform: translateY(20px); transition: opacity .55s var(--ease), transform .65s var(--ease-back); }
.qresult-line { font-size: clamp(1.02rem, 3vw, 1.18rem); line-height: 1.4; }
.qunlock { animation: unlock-in .7s var(--ease-back) both; }
@keyframes unlock-in { from { opacity: 0; transform: translateY(14px) scale(.97); } }
.qunlock-tag { color: var(--gold); }

.qscore-verdict { font-size: clamp(1.45rem, 5vw, 2.1rem); }

@media (prefers-reduced-motion: reduce) {
  .qcard, .qresult { transform: none !important; }
  .qopt.is-right, .qopt.is-wrong, .qunlock, .qtype-row.is-wrong .qtype-input, .qtype-hint { animation: none !important; }
}

/* ==========================================================================
   PHONES — from a 320px iPhone SE up
   The stage grid is pinned to minmax(0,1fr) above, which is what stops a wide
   child widening the page. These are the size steps on top of that.
   ========================================================================== */
.photo-ph-code, .permit-btn, .qopt-text, .ow-note, .mem-caption { overflow-wrap: anywhere; }
.stage, .stage * { min-width: 0; }

@media (max-width: 560px) {
  .stage { padding-left: 16px; padding-right: 16px; gap: clamp(18px, 4vw, 30px); }

  /* one answer per row — two columns of long sentences is unreadable here */
  .qcard { padding: 20px 16px; }
  .qcard-opts { grid-template-columns: 1fr; gap: 9px; }
  .qopt { min-height: 0; padding: 14px; flex-direction: row; align-items: center; gap: 12px; }
  .qopt-key { width: 28px; height: 28px; border-radius: 9px; }
  .qcard-q { font-size: clamp(1.15rem, 5.4vw, 1.5rem); }
  .quiz-intro, .ow-intro { font-size: clamp(1.15rem, 5vw, 1.45rem); }

  /* the memory caption stops overlapping the photo and sits under it - and
     the photo stops drifting on scroll, which pushed it down into the card
     and hid the faces in the bottom row */
  .mem-card, .mem-flip .mem-card { width: 100%; margin: 12px auto 0; padding: 18px 16px; }
  /* will-change gave each of the 12 memories its own GPU layer full of big
     photos; a phone ran out of room for them and drew some tiles blank */
  .mem-media { transform: none; will-change: auto; }
  .mem-index { font-size: clamp(3.4rem, 20vw, 6rem); }

  /* sealed notes: one per row, seal tucked in */
  .ow-grid { grid-template-columns: 1fr; gap: 14px; }
  .ow-card { min-height: 0; padding: 22px 18px 38px; }
  .ow-seal { width: 62px; height: 62px; top: -10px; right: -8px; }
  .ow-when { padding-right: 46px; }

  /* the three of us stay on one row, but the caption must still drop to its
     own line underneath — nowrap here pinned it alongside them */
  .clappers { gap: 10px; flex-wrap: wrap; justify-content: center; }
  .clapper-body { width: clamp(58px, 23vw, 84px); }
  .clappers-note { flex-basis: 100%; font-size: .95rem; }

  .permit-card { padding: 22px 18px; }
  .permit-row { padding: 14px; gap: 12px; }
  .letter { width: 100%; padding: 26px 18px; }
  .envelope { width: min(300px, 88vw); }
  .onward-btn { font-size: 1.15rem; }
}

/* short phones — an SE is only 568px tall, so the single-screen moments have
   to give up height, not width */
@media (max-height: 620px) {
  .stage { min-height: 0; padding-top: 26px; padding-bottom: 26px; }
  #ch-permit, #ch-cake, #ch-finale { min-height: 100svh; }
  .cake-wrap { width: min(220px, 62vw, 30vh); }
  .clapper-body { width: clamp(48px, 18vw, 66px); }
  .clappers-note { display: none; }
  .finale-line { font-size: clamp(2rem, 9vw, 3rem); }
  .finale-kicker { margin-bottom: 12px; }
  .finale-sign { margin-bottom: 18px; }
}

/* ==========================================================================
   TOUCH SCREENS
   A tap leaves :hover stuck on whatever was tapped until she taps somewhere
   else, so the lifted / scaled / tilted hover states only belong on devices
   with a real pointer. On a phone they would leave buttons floating and the
   wax seal frozen mid-wiggle.
   ========================================================================== */
@media (hover: none) {
  .btn:hover, .onward-btn:hover, .clip-sound:hover,
  .qopt:hover:not(:disabled), .ow-card:hover:not(.is-open) { transform: none; }
  .qopt:hover:not(:disabled)::after { opacity: 0; }
  .envelope:hover .wax { transform: none; animation: seal-pulse 2.6s ease-in-out infinite; }
  .envelope.is-cracking .wax { animation: crack .55s var(--ease) forwards; }
}
