/* ============================================================================
   AISWARYA & ALBERT — "The Premiere"
   Mobile first, on paper. The cinema language stays, but read as the printed
   ticket in daylight rather than a dark auditorium — quieter, and an exact
   match for the physical invitation.
   ========================================================================== */

/* ---------- 1. tokens ----------------------------------------------------- */
:root {
  /* Light and warm — the printed ticket in daylight. The artwork's *structure*
     is followed exactly (amber section heads, red accents, a deep-maroon
     tear-off stub); only its darkness is dropped. */

  /* paper */
  --paper:          #FBF7F0;   /* the page */
  --paper-2:        #F4EDE1;   /* tinted bands */
  --paper-3:        #EBDFCD;   /* deeper band, so white cards lift off it */
  --panel:          #FFFCF7;   /* cards */
  --linen:          #E7DCCC;   /* hairlines */

  /* type */
  --ink:            #2E241D;   /* page text, warm near-black */
  --ink-on-paper:   #2A211C;   /* text printed on the ticket itself */
  --ink-soft:       var(--paper-2);
  --cream-dim:      #5A4A3E;   /* secondary text */
  --cream-faint:    rgba(58, 44, 36, 0.62);

  /* accents */
  --cream:          #FFFCF7;   /* the ticket's own paper */
  --oxblood:        #7B2436;   /* the red accent */
  --oxblood-deep:   #F0E6D8;
  --oxblood-dark:   var(--paper);
  --stub:           #5C1523;   /* the tear-off panel, as in the artwork */
  --salmon:         #9C4A48;   /* role labels — the artwork's salmon, darkened
                                  enough to hold up on paper */
  --gold:           #9A7B33;   /* section heads and kickers */
  --gold-bright:    #7E6428;
  --gold-faint:     rgba(154, 123, 51, 0.30);
  --sage:           #77876B;

  /* roles */
  --bg:             var(--paper);
  --fg:             var(--ink);
  --rule:           rgba(154, 123, 51, 0.28);
  --on-accent:      #FFFCF7;

  --shadow-1:       0 2px 10px rgba(74, 56, 40, 0.06);
  --shadow-2:       0 10px 30px -12px rgba(74, 56, 40, 0.20);
  --shadow-3:       0 22px 50px -22px rgba(74, 56, 40, 0.30);

  /* type — two families doing every job on the page.

     DM Serif Display carries the headings and the two display marks. It ships
     in one weight only, so nothing here asks for a heavier one: a browser
     told to bold it would smear the thin strokes rather than find a bold.

     IBM Plex Sans carries the running text and the labels. The slots below
     stay separate even where they name the same family, because they are
     roles rather than fonts — running text, tracked-out labels, the couple's
     names — and each is sized and tracked for its own job. */
  --sans:    'IBM Plex Sans', system-ui, -apple-system, 'Segoe UI', Arial, sans-serif;

  --display: 'DM Serif Display', 'Didot', Georgia, serif;
  --body:    var(--sans);
  --label:   var(--sans);   /* uppercase, tracked out */
  --script:  var(--display);/* the foil monogram — a mark, so it takes the serif */
  --names:   var(--display);/* the couple's names sit with the headings */

  /* rhythm — tightened: the page was asking for too much scrolling before
     the reader got to anything useful */
  --gutter: clamp(1.25rem, 5vw, 4rem);
  --section-y: clamp(3.4rem, 9vh, 6rem);
  --measure: 62ch;
  --maxw: 1180px;

  /* motion */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --dur: 900ms;
}

/* ---------- 2. reset ------------------------------------------------------ */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--body);
  font-size: 0.9375rem;   /* 15px — the size the body copy settles on */
  line-height: 1.7;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* `clip`, not `hidden` — overflow-x:hidden makes body a scroll container,
     which silently kills position:sticky and every pinned scene with it */
  overflow-x: clip;
}

img, svg { display: block; max-width: 100%; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
a { color: inherit; }

::selection { background: var(--gold); color: var(--on-accent); }

/* ---------- 3. film grain + vignette (fixed overlay) ---------------------- */
.grain,
.vignette {
  position: fixed;
  inset: -50%;
  pointer-events: none;
  z-index: 90;
}

.grain {
  inset: 0;
  /* on paper the grain reads as tooth in the stock, not film noise:
     much lighter, and multiplied in rather than overlaid */
  opacity: 0.045;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");
  /* background-position, not transform — a transform on a fixed overlay can
     push the document's scroll width past the viewport */
  animation: grain-shift 620ms steps(3) infinite;
}

@keyframes grain-shift {
  0%   { background-position: 0 0; }
  33%  { background-position: -30px 14px; }
  66%  { background-position: 16px -28px; }
  100% { background-position: 0 0; }
}

.vignette {
  inset: 0;
  background: radial-gradient(ellipse 120% 90% at 50% 45%, transparent 55%, rgba(120, 96, 62, 0.10) 100%);
  z-index: 89;
}


/* ---------- 3b. LOADER — a flower drawing itself -------------------------- */
.loader {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--paper);
  display: grid;
  place-items: center;
  transition: opacity 620ms var(--ease), visibility 0s linear 620ms;
}
.is-loaded .loader {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.loader__flower {
  width: clamp(118px, 40vw, 190px);
  height: auto;
  overflow: visible;
  animation: bloom-turn 5s var(--ease) forwards;
}
@keyframes bloom-turn { from { rotate: -10deg } to { rotate: 4deg } }

/* Each petal draws its outline, then washes in. pathLength="100" normalises
   every path so one dasharray value works for all of them. */
.loader__petals .pt {
  fill: var(--oxblood);
  fill-opacity: 0;
  stroke: var(--oxblood);
  stroke-width: 2.2;
  stroke-linejoin: round;
  stroke-dasharray: 100;
  stroke-dashoffset: 100;
  animation:
    petal-draw 560ms var(--ease-out-expo) forwards calc(var(--k) * 72ms),
    petal-wash 460ms ease-out forwards calc(980ms + var(--k) * 36ms);
}
/* alternate petals in gold so the bloom is not flat */
.loader__petals .pt:nth-child(2n) { fill: var(--gold); stroke: var(--gold); }

@keyframes petal-draw { to { stroke-dashoffset: 0 } }
@keyframes petal-wash { to { fill-opacity: 0.18 } }

.loader__core {
  fill: var(--gold);
  transform-origin: 100px 100px;
  scale: 0;
  animation: core-pop 520ms cubic-bezier(0.2, 1.5, 0.4, 1) forwards 1180ms;
}
@keyframes core-pop { to { scale: 1 } }

@media (prefers-reduced-motion: reduce) {
  /* no drawing — show the finished bloom, then get out of the way */
  .loader__flower { animation: none; rotate: 0deg; }
  .loader__petals .pt { animation: none; stroke-dashoffset: 0; fill-opacity: 0.18; }
  .loader__core { animation: none; scale: 1; }
  .loader { transition-duration: 1ms; }
}


/* ---------- 3c. INTRO — the photo-collage that plays after the loader ------ */
.intro {
  position: fixed;
  inset: 0;
  z-index: 150;              /* under the loader (200), over the page */
  overflow: hidden;
  background: var(--paper);
  pointer-events: none;
}
.intro.is-gone {
  opacity: 0;
  transition: opacity 420ms var(--ease);
}

.intro__stack {
  position: absolute;
  inset: 0;
}

/* No frame and no shadow — each photograph lands squarely on the one before
   it and covers it, so there is nothing to separate. */
.intro__photo {
  position: absolute;
  left: 50%;
  top: 50%;
  overflow: hidden;
  border-radius: 3px;
  will-change: transform, opacity;
  backface-visibility: hidden;
}

.intro__img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;         /* real photographs crop, never stretch */
}

@media (prefers-reduced-motion: reduce) {
  .intro { display: none; }
}




/* ---- reactions floating up, and the monogram that follows --------------- */
.intro__hearts {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 6;
}
.intro__heart {
  position: absolute;
  width: clamp(18px, 5vw, 30px);
  will-change: transform, opacity;
}
.intro__heart svg { width: 100%; height: auto; display: block; fill: var(--oxblood); }

.intro__mono {
  position: absolute;
  left: 50%;
  bottom: clamp(9%, 13vh, 16%);
  translate: -50% 0;
  z-index: 7;
  display: flex;
  align-items: baseline;
  gap: clamp(0.4rem, 2vw, 0.9rem);
  font-family: var(--names);
  font-size: clamp(2.6rem, 1.2rem + 8vw, 5rem);
  line-height: 1;
  color: var(--oxblood);
  pointer-events: none;
}
.intro__mono i { font-style: normal; font-size: 0.6em; color: var(--gold); }

/* ---- the wave with no track loaded -------------------------------------- */
.wave.is-silent { border-style: dashed; }
.wave.is-silent .wave__line { stroke: var(--cream-dim); }

/* ---------- 3d. TOP BAR — the background-music control --------------------- */
.topbar {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 120;                 /* under the loader (200) and the intro (150) */
  display: flex;
  justify-content: flex-end;
  padding: clamp(0.7rem, 2.5vw, 1.2rem) clamp(0.7rem, 3vw, 1.6rem);
  pointer-events: none;         /* only the button itself is clickable */

  /* Held back until the opening has finished, so it never sits over the
     loader or the collage — and then it arrives rather than appearing. It
     comes in last, after the title block has settled, because it is a control
     and not part of the poster. */
  opacity: 0;
  transform: translateY(-14px);
  transition:
    opacity 700ms var(--ease) 1400ms,
    transform 900ms var(--ease-out-expo) 1400ms;
}
.is-open .topbar { opacity: 1; transform: none; }

.wave {
  pointer-events: auto;
  display: grid;
  place-items: center;
  width: 44px;                  /* a comfortable tap target */
  height: 44px;
  border-radius: 50%;
  border: 1px solid var(--gold-faint);
  background: color-mix(in srgb, var(--paper) 82%, transparent);
  backdrop-filter: blur(6px);
  cursor: pointer;
  transition: border-color 300ms var(--ease), background 300ms var(--ease);
}
.wave:hover,
.wave:focus-visible { border-color: var(--gold); }

/* two horizontal waves; the svg clips whatever runs past its viewBox, which is
   what lets the lines slide sideways for ever */
.wave__waves {
  display: block;
  width: 22px;
  height: 15px;
}
.wave__line {
  fill: none;
  stroke: var(--oxblood);
  stroke-width: 1.5;
  stroke-linecap: round;
  transform-box: view-box;
}

/* resting: each wave settles almost flat about its own centreline, so the two
   stay two — the paused state */
.wave__row {
  transform-box: view-box;
  transform-origin: 12px var(--y);
  transform: scaleY(0.16);
  transition: transform 340ms var(--ease);
}
.wave.is-playing .wave__row { transform: scaleY(1); }

/* playing: they roll past one another, the second slower and the other way */
.wave.is-playing .wave__line {
  animation: wave-drift 1.7s linear infinite;
}
.wave.is-playing .wave__row + .wave__row .wave__line {
  animation-duration: 2.6s;
  animation-direction: reverse;
}
@keyframes wave-drift {
  from { transform: translateX(0); }
  to   { transform: translateX(-12px); }   /* exactly one wavelength */
}

@media (prefers-reduced-motion: reduce) {
  /* the control still works; it just does not dance */
  .wave.is-playing .wave__line { animation: none; }
  .topbar { transition: none; }
}

/* ---------- 4. scroll progress (a reel of film unspooling) ---------------- */
.reel {
  position: fixed;
  inset: 0 0 auto 0;
  height: 4px;
  z-index: 100;
  background: rgba(58, 44, 36, 0.08);
}
.reel__fill {
  height: 100%;
  width: var(--p, 0%);
  background: linear-gradient(90deg, var(--oxblood) 0%, var(--gold) 60%, var(--gold-bright) 100%);
  box-shadow: 0 0 14px rgba(154, 123, 51, 0.7);
  transition: width 120ms linear;
}

/* ---------- 5. shared primitives ----------------------------------------- */
.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--narrow { max-width: 800px; }

section { padding-block: var(--section-y); position: relative; }

.kicker {
  font-family: var(--label);
  font-weight: 500;
  font-size: clamp(0.7rem, 0.62rem + 0.35vw, 0.86rem);
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: var(--gold);
  display: flex;
  align-items: center;
  gap: 0.9em;
}
.kicker::before,
.kicker::after {
  content: '';
  height: 1px;
  flex: 1;
  background: linear-gradient(90deg, transparent, var(--gold-faint), transparent);
}
.kicker--plain::before,
.kicker--plain::after { display: none; }
.h-section {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2rem, 1.2rem + 3.6vw, 3.6rem);
  line-height: 1.04;
  letter-spacing: -0.01em;
}
.amp {
  font-family: var(--display);
  font-style: italic;
  font-weight: 400;
  color: var(--gold);
}

/* film-strip divider between acts */
.filmstrip {
  height: 38px;
  background-color: var(--oxblood);
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='46' height='38'%3E%3Crect x='11' y='12' width='24' height='14' rx='4' fill='%23F5EFE3' fill-opacity='0.82'/%3E%3C/svg%3E");
  background-repeat: repeat-x;
  /* The perforations travel sideways with the scroll — JS writes --slide off
     the scroll position, so the reel runs one way going down and back the
     other going up. It tiles, so there is no end to reach. */
  background-position: var(--slide, 0px) center;
  border-block: 1px solid rgba(154, 123, 51, 0.2);
  position: relative;
  z-index: 2;
}

/* ---------- 6. HERO — the marquee ---------------------------------------- */
.hero {
  min-height: 100svh;
  display: grid;
  text-align: center;
  overflow: hidden;
  isolation: isolate;

  /* The botanical ground, on phones only. Mobile-first like the rest of the
     file, so it is set here and lifted again at the wide breakpoint: the
     artwork is a 9:16 portrait, and stretching it across a desktop screen
     turns a piece of printed stock into wallpaper.

     It paints as the element's own background, which in this isolated
     stacking context puts it under both pseudo-elements — the projector beam
     and the stage glow lie over it, and .hero-wash still takes the whole lot
     to paper as you scroll into the next act. */
  background-image: url('assets/bg/hero-mobile.webp');
  background-size: cover;
  background-position: 50% 0;
  background-repeat: no-repeat;
}

/* projector beam */
.hero::before {
  content: '';
  position: absolute;
  top: -30%;
  left: 50%;
  width: 130vmax;
  height: 130vmax;
  translate: -50% 0;
  background: conic-gradient(from 200deg at 50% 0%,
    transparent 0deg,
    rgba(154, 123, 51, 0.11) 24deg,
    rgba(154, 123, 51, 0.035) 40deg,
    transparent 62deg);
  filter: blur(26px);
  z-index: -2;
  animation: beam-flicker 7s ease-in-out infinite;
  pointer-events: none;
}
@keyframes beam-flicker {
  0%, 100% { opacity: 0.85; }
  42%      { opacity: 1; }
  47%      { opacity: 0.7; }
  49%      { opacity: 0.95; }
}

/* deep red stage glow */
.hero::after {
  content: '';
  position: absolute;
  inset: auto 0 -20% 0;
  height: 60%;
  background: radial-gradient(ellipse 80% 100% at 50% 100%, rgba(123, 36, 54, 0.10), transparent 70%);
  z-index: -2;
  pointer-events: none;
}

/* ---- the poster: kicker, names, tagline, release line, billing ---------- */
/* Mobile first: a full-height three-row grid. The kicker sits at the top,
   the poster block takes the flexible middle, and the footer holds the
   bottom — so the composition is set by the rows rather than by one centred
   block with dead air under it. */
.hero__grid {
  width: 100%;
  max-width: 34rem;
  margin-inline: auto;
  min-height: 100svh;
  padding: clamp(2.4rem, 9vh, 4.5rem) var(--gutter) clamp(1.1rem, 3.5vh, 2rem);
  display: grid;
  /* 1fr / auto / 1fr with row 1 left empty: the title block lands on the
     true centre of the viewport, and the footer sits at the bottom of the
     lower half. Centring it in the space *above* the footer instead pulled
     the whole poster visibly high. */
  grid-template-rows: 1fr auto 1fr;
  gap: clamp(1rem, 3vh, 1.8rem);
  align-items: center;
}
.hero__mid { grid-row: 2; }
.hero__foot { grid-row: 3; }

/* the poster block, centred in the row it is given */
.hero__mid {
  display: grid;
  align-content: center;
  justify-items: center;
  gap: clamp(0.9rem, 3vh, 1.5rem);
}
/* the production line reads as part of the title, so it sits tight above it */
.hero__mid .hero__kicker { margin-bottom: clamp(-0.6rem, -1.4vh, -0.2rem); }

.hero__foot {
  display: grid;
  justify-items: center;
  gap: clamp(0.8rem, 2.6vh, 1.4rem);
  align-self: end;
}

.hero__kicker {
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.34em;
  font-size: clamp(0.66rem, 0.6rem + 0.3vw, 0.8rem);
  color: var(--gold);
  text-indent: 0.34em;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1em;
}
.hero__kicker::before,
.hero__kicker::after {
  content: '';
  width: clamp(18px, 7vw, 46px);
  height: 1px;
  background: var(--gold-faint);
}

.hero__names {
  font-family: var(--names);
  /* One weight only — see the type block at the top. The serif runs narrower
     than the sans it replaced, so the clamp goes back up a little to hold the
     same footprint, and the tracking eases off: this face is already tightly
     fitted and pulling it in further closed up the counters. */
  font-weight: 400;
  font-size: clamp(2.9rem, 1rem + 11vw, 6.9rem);
  line-height: 1.04;
  letter-spacing: -0.008em;
  text-transform: none;
  color: var(--ink);
  max-width: 100%;
}
.hero__names span { display: block; }
.hero__names .amp {
  font-family: var(--display);
  font-style: italic;
  font-weight: 400;
  text-transform: none;
  font-size: 0.44em;
  color: var(--oxblood);
  line-height: 1.1;
  margin-block: 0.04em;
}

.hero__tag {
  font-family: var(--body);
  font-style: italic;
  font-size: clamp(1rem, 0.9rem + 0.7vw, 1.35rem);
  line-height: 1.5;
  color: var(--gold);
  text-wrap: balance;
}

.hero__release {
  margin-top: clamp(0.4rem, 1.6vh, 0.9rem);
  font-family: var(--label);
  text-transform: uppercase;
  /* tracked in from 0.22em: the label face is no longer condensed, and at the
     old value this line broke across "JANUARY / 2027" on a desktop screen */
  letter-spacing: 0.15em;
  font-size: clamp(0.72rem, 0.64rem + 0.35vw, 0.92rem);
  color: var(--cream-dim);
  text-indent: 0.22em;
  /* `balance` used to be free here — the condensed face fit this on one line
     on a desktop screen, so it only ever split on a phone. The wider face
     wraps it everywhere, and an even split lands the break inside the date:
     "JANUARY / 2027". Filling the line normally keeps the date whole and puts
     the place on the second line. */
  text-wrap: pretty;
}

/* the strip along the bottom of the poster */
.hero__foot {
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(0.9rem, 3vh, 1.8rem);
  padding-inline: var(--gutter);
  display: grid;
  justify-items: center;
  gap: clamp(1rem, 3vh, 1.8rem);
}

.billing {
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-size: clamp(0.52rem, 0.46rem + 0.28vw, 0.64rem);
  line-height: 2;
  color: rgba(58, 44, 36, 0.42);
  max-width: 52ch;
  text-align: center;
  text-wrap: balance;
}

.scroll-cue {
  font-family: var(--label);
  font-size: 0.68rem;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--cream-faint);
  text-indent: 0.34em;
  display: grid;
  justify-items: center;
  gap: 0.6rem;
}
.scroll-cue::after {
  content: '';
  width: 1px; height: 30px;
  background: linear-gradient(180deg, var(--gold), transparent);
  animation: cue 2.2s var(--ease) infinite;
  transform-origin: top;
}
@keyframes cue {
  0%   { transform: scaleY(0); opacity: 0; }
  35%  { transform: scaleY(1); opacity: 1; }
  100% { transform: scaleY(1); opacity: 0; transform-origin: bottom; }
}

/* ---------- 7. CERTIFICATE card ------------------------------------------ */
.cert-wrap { display: grid; place-items: center; }

.cert {
  width: min(100%, 700px);
  background: var(--cream);
  color: var(--ink-on-paper);
  padding: clamp(1.6rem, 5vw, 3rem);
  border: 3px double var(--oxblood);
  box-shadow: var(--shadow-3);
  position: relative;
}
.cert::after {
  content: '';
  position: absolute;
  inset: 8px;
  border: 1px solid rgba(90, 15, 34, 0.28);
  pointer-events: none;
}
.cert__top {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 4vw, 1.8rem);
  border-bottom: 1px solid rgba(90, 15, 34, 0.25);
  padding-bottom: 1.2rem;
}
.cert__rating {
  flex: none;
  width: clamp(58px, 14vw, 78px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  border: 2px solid var(--oxblood);
  border-radius: 50%;
  font-family: var(--display);
  font-size: clamp(1.7rem, 5vw, 2.4rem);
  font-weight: 400;
  color: var(--oxblood);
}
.cert__head {
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.22em;
  font-size: clamp(0.68rem, 0.6rem + 0.3vw, 0.8rem);
  color: rgba(23, 7, 12, 0.62);
  line-height: 1.9;
}
.cert__head strong {
  display: block;
  font-family: var(--display);
  font-size: clamp(1.1rem, 0.9rem + 1vw, 1.6rem);
  letter-spacing: 0.02em;
  color: var(--oxblood);
  font-weight: 400;
  text-transform: none;
}
.cert__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1rem 1.6rem;
  padding-block: 1.4rem;
}
.cert__cell dt {
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-size: 0.68rem;
  color: rgba(23, 7, 12, 0.5);
  margin-bottom: 0.2rem;
}
.cert__cell dd {
  font-family: var(--body);
  font-size: 1.02rem;
  color: var(--ink-on-paper);
  font-weight: 500;
}
.cert__note {
  border-top: 1px solid rgba(90, 15, 34, 0.25);
  padding-top: 1.2rem;
  font-style: italic;
  font-size: 1rem;
  line-height: 1.6;
  color: rgba(23, 7, 12, 0.78);
}
.cert__stamp {
  position: absolute;
  right: clamp(0.6rem, 3vw, 1.6rem);
  bottom: clamp(0.4rem, 2vw, 1rem);
  font-family: var(--label);
  font-size: 0.62rem;
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: rgba(90, 15, 34, 0.55);
  border: 1px solid rgba(90, 15, 34, 0.4);
  padding: 0.3em 0.7em;
  /* thumps down a beat after the card itself has landed */
  opacity: 0;
  rotate: 14deg;
  scale: 1.6;
  transition:
    opacity 300ms var(--ease) 540ms,
    rotate 520ms cubic-bezier(0.2, 1.4, 0.4, 1) 540ms,
    scale 520ms cubic-bezier(0.2, 1.4, 0.4, 1) 540ms;
}
.is-in .cert__stamp { opacity: 1; rotate: -6deg; scale: 1; }


/* ---------- 9b. NUMBERED SECTION HEADS ------------------------------------ */
.sec-head {
  display: flex;
  align-items: baseline;
  gap: clamp(0.6rem, 2.5vw, 1rem);
}
.sec-head__no {
  font-family: var(--label);
  font-size: clamp(0.72rem, 0.64rem + 0.3vw, 0.86rem);
  letter-spacing: 0.16em;
  color: var(--gold);
}
.sec-head__title {
  font-family: var(--display);
  font-weight: 400;
  text-transform: uppercase;
  font-size: clamp(1.9rem, 1.25rem + 3.15vw, 3.25rem);
  letter-spacing: -0.005em;
  line-height: 1;
  color: var(--gold);
}

/* ---------- 9c. 01 · SYNOPSIS ----------------------------------------------
   Act 2 washes to this colour over its last stretch of scroll, and this
   section simply *is* it — so the change of ground resolves into the page
   rather than flashing back to paper. The section after sets paper again,
   which is what makes the colour change as you arrive there. */
.synopsis {
  background: var(--oxblood);
  color: var(--cream);
}
.synopsis .sec-head .sec-head__no    { color: var(--gold-bright); }
.synopsis .sec-head .sec-head__title { color: var(--cream); }
.synopsis .synopsis__body p          { color: rgba(255, 252, 247, 0.9); }
.synopsis .synopsis__body .has-dropcap { border-left-color: var(--gold); }
.synopsis .synopsis__body .has-dropcap::first-letter { color: var(--gold-bright); }
.synopsis p.rating-badge {
  color: rgba(255, 252, 247, 0.74);
  border-color: rgba(255, 252, 247, 0.3);
}

/* and the act that follows returns to paper, so the change is deliberate */
.scene--cards .stage { background: var(--paper); }

.synopsis__body {
  margin-top: clamp(1.4rem, 4vw, 2rem);
  display: grid;
  gap: clamp(1rem, 3vw, 1.4rem);
}
.synopsis__body p {
  font-size: 0.9375rem;   /* 15px */
  line-height: 1.78;
  color: var(--ink);
}

/* first paragraph gets the printed-page treatment: a wine rule down the left
   and a drop cap, so the section opens like a page rather than a card */
.synopsis__body .has-dropcap {
  border-left: 3px solid var(--oxblood);
  padding-left: clamp(0.9rem, 3vw, 1.3rem);
}
.synopsis__body .has-dropcap::first-letter {
  font-family: var(--display);
  font-weight: 400;
  font-size: 3.1em;
  line-height: 0.82;
  float: left;
  margin: 0.06em 0.09em 0 0;
  color: var(--oxblood);
}

.rating-badge {
  display: inline-block;
  margin-top: clamp(1.6rem, 4.5vw, 2.4rem);
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-size: clamp(0.66rem, 0.6rem + 0.28vw, 0.78rem);
  color: var(--cream-dim);
  border: 1px solid var(--linen);
  padding: 0.95em 1.4em;
  text-indent: 0.2em;
}

/* ---------- 9d. 02 · STARRING --------------------------------------------- */
.star {
  border: 1px solid var(--linen);
  background: var(--panel);
  border-radius: 14px;
  padding: clamp(0.9rem, 3.5vw, 1.8rem) clamp(0.7rem, 3vw, 1.6rem);
  text-align: center;
  display: grid;
  justify-items: center;
  gap: clamp(0.5rem, 1.6vw, 0.8rem);
  align-content: start;
  box-shadow: var(--shadow-2);
  height: 100%;
}

.star__avatar {
  position: relative;
  width: clamp(74px, 22vw, 132px);
  aspect-ratio: 1;
  border-radius: 50%;
  overflow: hidden;
  display: grid;
  place-items: center;
  background: linear-gradient(165deg, #C4413A 0%, #93303A 55%, #5E2A28 100%);
  box-shadow: 0 0 0 2px var(--cream), 0 0 0 3px var(--gold);
}
.star__avatar img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.star__mono {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(1.9rem, 8vw, 3.2rem);
  line-height: 1;
  color: var(--cream);
}

.star__role {
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: clamp(0.14em, 0.6vw, 0.26em);
  font-size: clamp(0.6rem, 0.52rem + 0.3vw, 0.74rem);
  color: var(--salmon);
  margin-top: 0.3rem;
}
.star__name {
  font-family: var(--names);
  font-weight: 400;
  font-size: clamp(1.5rem, 0.98rem + 2.5vw, 2.3rem);
  line-height: 1.14;
  letter-spacing: -0.005em;
  text-transform: none;
  color: var(--ink);
}
.star__full {
  font-family: var(--body);
  font-style: italic;
  font-size: clamp(0.8rem, 0.72rem + 0.3vw, 0.98rem);
  color: var(--cream-faint);
  margin-top: -0.25rem;
}
.star__parents {
  font-size: 0.9375rem;   /* 15px */
  line-height: 1.45;
  color: var(--cream-dim);
  margin-top: 0.35rem;
  text-wrap: pretty;
}
.star__family {
  font-family: var(--body);
  font-style: italic;
  font-size: clamp(0.76rem, 0.68rem + 0.3vw, 0.92rem);
  line-height: 1.45;
  color: var(--cream-faint);
  text-wrap: pretty;
}

.star__bio {
  font-size: 0.9375rem;   /* 15px */
  line-height: 1.55;
  color: var(--cream-dim);
  text-wrap: pretty;
}
/* ---------- 10. STORY — the film strip ------------------------------------ */
.story-track {
  margin-top: clamp(2.5rem, 7vw, 4rem);
  display: grid;
  gap: 0;
  position: relative;
}
.story-track::before {
  content: '';
  position: absolute;
  left: 6px; top: 0; bottom: 0;
  width: 1px;
  background: linear-gradient(180deg, transparent, var(--gold-faint) 8%, var(--gold-faint) 92%, transparent);
}
/* the ground is phone-only — see .hero */
@media (min-width: 760px) {
  .hero { background-image: none; }
}

@media (min-width: 760px) {
  .story-track::before { left: 50%; }
}

.beat {
  position: relative;
  padding: clamp(1.1rem, 2.6vw, 1.7rem) 0 clamp(1.1rem, 2.6vw, 1.7rem) 2.6rem;
}
.beat::before {
  content: '';
  position: absolute;
  left: 0; top: calc(clamp(1.1rem, 2.6vw, 1.7rem) + 0.6rem);
  width: 13px; height: 13px;
  border-radius: 50%;
  background: var(--oxblood-dark);
  border: 1px solid var(--gold);
  transition: background 500ms var(--ease), box-shadow 500ms var(--ease);
}
.beat.is-in::before {
  background: var(--gold);
  box-shadow: 0 0 0 5px rgba(154, 123, 51, 0.14), 0 0 18px rgba(154, 123, 51, 0.6);
}
@media (min-width: 760px) {
  .beat { width: 50%; padding-left: 0; padding-right: 3.4rem; text-align: right; }
  .beat::before { left: auto; right: -6.5px; }
  .beat:nth-child(even) {
    margin-left: 50%;
    padding-left: 3.4rem;
    padding-right: 0;
    text-align: left;
  }
  .beat:nth-child(even)::before { right: auto; left: -6.5px; }
}

.beat__year {
  font-family: var(--label);
  font-size: clamp(1.5rem, 1rem + 2vw, 2.4rem);
  letter-spacing: 0.14em;
  color: rgba(154, 123, 51, 0.42);
  line-height: 1;
}
.beat__title {
  font-family: var(--display);
  font-size: clamp(1.3rem, 1rem + 1.2vw, 1.9rem);
  margin-top: 0.35rem;
}
.beat__text {
  color: rgba(58, 44, 36, 0.66);
  font-size: 1.02rem;
  margin-top: 0.4rem;
  max-width: 42ch;
}
@media (min-width: 760px) {
  .beat__text { margin-left: auto; }
  .beat:nth-child(even) .beat__text { margin-left: 0; margin-right: auto; }
}

.stub-shell {
  /* two shadows: a tight one to define the die-cut edge against the band,
     and a soft one for lift. On paper a single soft shadow disappears. */
  filter:
    drop-shadow(0 1px 0 rgba(74, 56, 40, 0.14))
    drop-shadow(0 12px 20px rgba(74, 56, 40, 0.16));
}

/* ---- the ticket ---------------------------------------------------------- */
.tk {
  --stub-w: clamp(34px, 11vw, 54px);
  --notch: 13px;
  display: grid;
  grid-template-columns: 1fr var(--stub-w);
  background: var(--cream);
  color: var(--ink-on-paper);
  border-radius: 12px;
  /* semicircles bitten out of the tear line, top and bottom */
  -webkit-mask:
    radial-gradient(circle var(--notch) at right var(--stub-w) top 0, #0000 98%, #000),
    radial-gradient(circle var(--notch) at right var(--stub-w) bottom 0, #0000 98%, #000);
  -webkit-mask-composite: source-in;
  mask:
    radial-gradient(circle var(--notch) at right var(--stub-w) top 0, #0000 98%, #000),
    radial-gradient(circle var(--notch) at right var(--stub-w) bottom 0, #0000 98%, #000);
  mask-composite: intersect;
}

.tk__main {
  padding: clamp(1.3rem, 5vw, 2.2rem) clamp(1.2rem, 4.5vw, 2.1rem);
  border-right: 1.5px dashed rgba(123, 36, 54, 0.32);
  display: grid;
  align-content: start;
  gap: clamp(1rem, 3.6vw, 1.5rem);
}

.tk__top {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-size: clamp(0.6rem, 0.54rem + 0.28vw, 0.72rem);
  color: var(--cream-dim);
}

.tk__title {
  font-family: var(--display);
  font-weight: 400;
  text-transform: uppercase;
  font-size: clamp(1.25rem, 0.95rem + 1.9vw, 2.1rem);
  line-height: 1.05;
  letter-spacing: -0.01em;
  color: var(--ink-on-paper);
  padding-bottom: clamp(0.8rem, 3vw, 1.2rem);
  border-bottom: 1px solid rgba(123, 36, 54, 0.16);
}

.tk__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* generous row gap: these are five separate facts, not a paragraph */
  gap: clamp(1.15rem, 4.2vw, 1.7rem) clamp(0.8rem, 4vw, 1.8rem);
}
.tk__cell--wide { grid-column: 1 / -1; }

.tk__cell dt {
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-size: clamp(0.56rem, 0.5rem + 0.26vw, 0.68rem);
  color: rgba(58, 44, 36, 0.5);
  margin-bottom: 0.42rem;
}
.tk__cell dd {
  font-family: var(--body);
  font-size: clamp(0.92rem, 0.84rem + 0.4vw, 1.1rem);
  line-height: 1.35;
  color: var(--ink-on-paper);
  font-weight: 500;
}

.tk__dir {
  display: table;          /* own line, but only as wide as its text */
  margin-top: 0.3rem;
  padding-block: 0.95rem;  /* padding, not min-height — table boxes ignore it */
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 0.66rem;
  font-weight: 500;
  color: var(--oxblood);
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-color: rgba(123, 36, 54, 0.4);
}

/* printed barcode, drawn rather than imaged so it stays crisp at any size */
.barcode {
  width: 100%;
  height: 40px;
  background-image: repeating-linear-gradient(90deg,
    var(--ink-on-paper) 0 2px, transparent 2px 4px,
    var(--ink-on-paper) 4px 5px, transparent 5px 9px,
    var(--ink-on-paper) 9px 12px, transparent 12px 13px,
    var(--ink-on-paper) 13px 14px, transparent 14px 17px);
}

.tk .barcode {
  margin-top: clamp(0.4rem, 1.5vw, 0.7rem);
  max-width: none;
  height: clamp(30px, 8vw, 44px);
  opacity: 0.9;
}

/* the tear-off stub: the only deep colour on the ticket */
.tk__stub {
  background: var(--stub);
  display: grid;
  place-items: center;
}
.tk__stub span {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.3em;
  font-size: clamp(0.56rem, 0.5rem + 0.26vw, 0.7rem);
  color: rgba(255, 252, 247, 0.72);
  white-space: nowrap;
}

/* ---------- 12. COUNTDOWN -------------------------------------------------- */
.countdown {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(0.6rem, 2.4vw, 1.4rem);
  margin-top: clamp(1.8rem, 5vw, 2.6rem);
}
.cd {
  min-width: clamp(66px, 17vw, 92px);
  padding: 0.85rem 0.4rem 0.6rem;
  border: 1px solid var(--gold-faint);
  background: rgba(58, 44, 36, 0.035);
  text-align: center;
}
.cd__n {
  font-family: var(--display);
  font-size: clamp(1.6rem, 1.1rem + 2.2vw, 2.6rem);
  line-height: 1;
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  color: var(--gold-bright);
}
.cd__l {
  display: block;
  margin-top: 0.45rem;
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.24em;
  font-size: 0.6rem;
  color: rgba(58, 44, 36, 0.55);
}
@media (min-width: 800px) { }


/* ---------- 13b. 03 · NOW SHOWING ------------------------------------------ */
.scene-list {
  margin-top: clamp(1.5rem, 4vw, 2.2rem);
  border: 1px solid var(--linen);
  border-radius: 12px;
  background: var(--panel);
  overflow: hidden;
}

.scene-row {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: clamp(0.7rem, 3vw, 1.4rem);
  padding: clamp(1rem, 3.6vw, 1.6rem);
}
.scene-row + .scene-row { border-top: 1px solid var(--linen); }

.scene-row__no {
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: clamp(0.62rem, 0.56rem + 0.28vw, 0.74rem);
  line-height: 1.5;
  color: var(--salmon);
  /* fixed column so the three titles line up down the list */
  width: clamp(3.4rem, 15vw, 4.6rem);
  padding-top: 0.35em;
}

.scene-row__title {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(1.15rem, 0.9rem + 1.5vw, 1.8rem);
  line-height: 1.12;
  color: var(--ink);
}
.scene-row__meta {
  margin-top: 0.35rem;
  font-size: clamp(0.88rem, 0.8rem + 0.36vw, 1.05rem);
  line-height: 1.5;
  color: var(--cream-dim);
}
.scene-row__meta em { font-style: italic; color: rgba(58, 44, 36, 0.45); }

.scene-row__dir {
  display: table;
  margin-top: 0.3rem;
  padding-block: 0.95rem;  /* padding, not min-height — table boxes ignore it */
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 0.68rem;
  color: var(--oxblood);
  text-decoration: underline;
  text-underline-offset: 4px;
  text-decoration-color: rgba(123, 36, 54, 0.4);
}

/* the audience pill on the right of each row */
.pill {
  grid-column: 2;
  justify-self: start;
  margin-top: 0.7rem;
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: clamp(0.6rem, 0.54rem + 0.26vw, 0.7rem);
  color: var(--gold-bright);
  border: 1px solid var(--gold-faint);
  border-radius: 999px;
  padding: 0.55em 1em;
  white-space: nowrap;
}
/* from tablet up the pill sits inline at the end of the row, as in the artwork */
@media (min-width: 640px) {
  .scene-row { grid-template-columns: auto 1fr auto; align-items: center; }
  .pill { grid-column: 3; margin-top: 0; justify-self: end; }
}

.now-showing .map-panel { margin-top: clamp(1.4rem, 4vw, 2rem); }

/* ---------- 13c. 04 · BOOK YOUR SEAT --------------------------------------- */
.book__panel {
  margin-top: clamp(1.5rem, 4vw, 2.2rem);
  border: 1px dashed var(--gold-faint);
  border-radius: 12px;
  padding: clamp(1.5rem, 5vw, 2.6rem) clamp(1.1rem, 4vw, 2.4rem);
  text-align: center;
  background: var(--panel);
}

.book__line {
  font-size: 0.9375rem;   /* 15px */
  line-height: 1.65;
  color: var(--ink);
  max-width: 34ch;
  margin-inline: auto;
  text-wrap: balance;
}

.book__actions {
  margin-top: clamp(1.3rem, 4vw, 2rem);
  display: grid;
  gap: 0.8rem;
  justify-items: center;
}
.btn--wide {
  width: 100%;
  max-width: 22rem;
  justify-content: center;
  letter-spacing: 0.22em;
}

.book__foot {
  margin-top: clamp(1.2rem, 3.5vw, 1.8rem);
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: clamp(0.62rem, 0.56rem + 0.28vw, 0.74rem);
  line-height: 2;
  color: var(--cream-faint);
}
.book__foot a { color: var(--oxblood); text-underline-offset: 4px; }

/* ---------- 13d. CLOSING CARD ---------------------------------------------- */
.credits__dedication {
  font-family: var(--body);
  font-style: italic;
  font-size: clamp(1.1rem, 0.95rem + 1vw, 1.6rem);
  line-height: 1.62;
  color: var(--oxblood);
  max-width: 30ch;
  margin-inline: auto;
  text-wrap: balance;
}
.credits__signature {
  margin-top: clamp(1.6rem, 4.5vw, 2.4rem);
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-size: clamp(0.66rem, 0.6rem + 0.3vw, 0.8rem);
  line-height: 2;
  color: var(--cream-dim);
  text-indent: 0.2em;
  text-wrap: balance;
}
.credits__footnote {
  margin-top: clamp(0.8rem, 2.5vw, 1.2rem);
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.13em;
  font-size: clamp(0.56rem, 0.5rem + 0.26vw, 0.68rem);
  line-height: 1.9;
  color: rgba(58, 44, 36, 0.42);
  text-wrap: balance;
}
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.7em;
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.26em;
  font-size: clamp(0.74rem, 0.68rem + 0.25vw, 0.86rem);
  padding: 1.05em 1.9em;
  text-decoration: none;
  border: 1px solid rgba(123, 36, 54, 0.45);
  color: var(--oxblood);
  position: relative;
  overflow: hidden;
  min-height: 44px;                 /* comfortable tap target */
  transition: color 380ms var(--ease);
}
.btn::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--oxblood);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 460ms var(--ease-out-expo);
  z-index: -1;
}
.btn:hover::before,
.btn:focus-visible::before { transform: scaleX(1); }
.btn:hover, .btn:focus-visible { color: var(--on-accent); }
.btn--solid { background: var(--oxblood); color: var(--on-accent); border-color: var(--oxblood); }
.btn--solid::before { background: #5F1A28; }

/* ---------- 16. CREDITS ---------------------------------------------------- */
.credits {
  background: var(--paper-2);
  text-align: center;
  padding-block: clamp(5rem, 16vh, 10rem);
}
.credits__verse {
  font-family: var(--body);
  font-style: italic;
  font-size: clamp(1.2rem, 1rem + 1.2vw, 1.8rem);
  color: rgba(58, 44, 36, 0.8);
  max-width: 30ch;
  margin-inline: auto;
  line-height: 1.55;
}
.credits__ref {
  font-family: var(--label);
  letter-spacing: 0.3em;
  text-transform: uppercase;
  font-size: 0.7rem;
  color: var(--gold);
  margin-top: 1.1rem;
}
/* The monogram, struck in gold foil.

   The previous version extruded the letters with a stack of hard offset
   shadows. On a fine cursive that reads as fake — the extrusion is thicker
   than the strokes themselves, so it looks like plastic rather than metal.

   Real foil on paper has almost no depth. What sells it is the *surface*:
   several bright bands running across the glyphs where the light catches,
   dark bands between them, and a very tight contact shadow because the foil
   sits right on the sheet. So the depth is gone and the gradient does the
   work instead. */
.monogram {
  font-family: var(--script);
  /* Large — the banding needs glyph area before it can read as foil. Smaller
     than the cursive this replaced, which carried far less ink per em. */
  font-size: clamp(3.2rem, 1.7rem + 8.6vw, 6.2rem);
  line-height: 1.05;
  margin-bottom: clamp(1.8rem, 6vw, 3rem);
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: clamp(0.6rem, 2.5vw, 1.4rem);

  filter:
    drop-shadow(0 1px 0 rgba(255, 252, 247, 0.5))    /* catch along the top edge */
    drop-shadow(0 1px 1px rgba(88, 66, 28, 0.35))    /* tight contact shadow */
    drop-shadow(0 5px 12px rgba(74, 56, 40, 0.16));  /* a little ambient lift */
}

/* The foil goes on each mark rather than on the row that holds them.

   background-clip: text only clips to text the element lays out itself, and
   the marks are flex items — each one paints in its own box, which left the
   row with nothing but whitespace to clip its gradient to, and the whole
   monogram rendered as nothing at all. The marks have to stay their own
   boxes, because the entrance in lib/reveal.js transforms them and a plain
   inline element cannot be transformed. So the gradient moves down to them.

   The angle is near-vertical, so bands run across each glyph rather than
   along the row: three separate copies read as one continuous stamping. */
.monogram span {
  /* Uneven stops on purpose: evenly spaced bands look like a gradient,
     uneven ones look like metal. */
  background-image: linear-gradient(172deg,
    #F6E9C3  0%,
    #D8B368 15%,
    #9C7429 31%,
    #F3E2B0 46%,   /* the bright band across the middle */
    #BE9540 57%,
    #86631E 71%,
    #E4CC8B 86%,   /* a second, softer catch low down */
    #F6E9C3 100%);
  /* Three times the width of the glyph, and left to tile. The highlight sweep
     in lib/reveal.js drives background-position-x well past both edges; the
     first and last stops are the same cream, so it tiles seamlessly and no
     position in that sweep can uncover the letter. */
  background-size: 300% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.monogram__amp {
  font-size: 0.62em;
}

/* Safari and Firefox both honour background-clip: text, but if a browser
   ever does not, transparent text would vanish entirely — so fall back to a
   solid gold fill. */
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .monogram span { color: var(--gold); background-image: none; }
}
.credits__hash {
  margin-top: 2.6rem;
  font-family: var(--label);
  letter-spacing: 0.16em;
  font-size: 0.8rem;
  color: var(--gold);
}

/* canvas the confetti is thrown onto — sits behind the type, never blocks it */
.confetti {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
}

/* ---------- 16c. SHOWTIME BAND (the date, made unmissable) ----------------- */
.showtime {
  background:
    linear-gradient(180deg, rgba(154, 123, 51, 0.09), transparent 55%),
    var(--ink);
  border-block: 1px solid var(--rule);
  text-align: center;
  padding-block: clamp(2.6rem, 7vh, 4.4rem);
  overflow: hidden;
}
.showtime__day {
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.5em;
  font-size: clamp(0.68rem, 0.6rem + 0.34vw, 0.88rem);
  color: var(--gold);
  text-indent: 0.5em;
}
.showtime__date {
  font-family: var(--display);
  font-weight: 400;
  /* the single largest thing on the page after the couple's names */
  font-size: clamp(3.1rem, 1rem + 13vw, 9.5rem);
  line-height: 0.94;
  letter-spacing: -0.03em;
  color: var(--ink);
  margin-block: clamp(0.6rem, 2vw, 1.2rem);
  display: flex;
  justify-content: center;
  align-items: baseline;
  gap: clamp(0.3rem, 1.6vw, 1rem);
  flex-wrap: wrap;
}
.showtime__date i {
  font-style: normal;
  color: var(--gold);
  font-size: 0.42em;
  transform: translateY(-0.28em);
}
.showtime__where {
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.28em;
  font-size: clamp(0.68rem, 0.6rem + 0.3vw, 0.84rem);
  color: rgba(58, 44, 36, 0.66);
  text-indent: 0.28em;
}
.showtime .countdown { margin-top: clamp(1.6rem, 4vw, 2.4rem); }

/* ---------- 16d. MAP ------------------------------------------------------- */
.map-panel {
  margin-top: clamp(1.6rem, 4vw, 2.4rem);
  border: 1px solid var(--rule);
  background: rgba(58, 44, 36, 0.03);
}

.map-tabs {
  display: flex;
  border-bottom: 1px solid var(--rule);
}
.map-tab {
  flex: 1;
  padding: 1em 0.8em;
  min-height: 48px;
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-size: clamp(0.66rem, 0.58rem + 0.3vw, 0.8rem);
  color: rgba(58, 44, 36, 0.55);
  background: none;
  position: relative;
  transition: color 300ms var(--ease), background 300ms var(--ease);
}
.map-tab + .map-tab { border-left: 1px solid var(--rule); }
.map-tab[aria-selected="true"] {
  color: var(--on-accent);
  background: var(--oxblood);
}
.map-tab:not([aria-selected="true"]):hover { color: var(--oxblood); background: rgba(123, 36, 54, 0.06); }

.map-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  max-height: 62vh;
  background: var(--paper-2);
}
/* squarer on a phone, where 4/3 left the map a squat letterbox strip that
   did not fill its panel */
@media (max-width: 560px) { .map-frame { aspect-ratio: 1 / 1; } }

.map-frame iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  /* warm the Google tiles into the page's palette */
  filter: grayscale(0.30) sepia(0.22) saturate(0.92) contrast(1.02) brightness(1.02);
}

.map-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: center;
  justify-content: space-between;
  padding: clamp(1rem, 3vw, 1.4rem);
  border-top: 1px solid var(--rule);
}
.map-meta__text { text-align: left; }
.map-meta__name {
  font-family: var(--display);
  font-size: clamp(1.05rem, 0.9rem + 0.7vw, 1.4rem);
  line-height: 1.2;
}
.map-meta__addr {
  font-size: 0.95rem;
  color: rgba(58, 44, 36, 0.58);
  margin-top: 0.15rem;
}

/* ============================================================================
   16f. SCENES — pinned, scroll-driven acts
   Each .scene is a tall block; the .stage inside sticks to the viewport while
   you scroll through it. JS writes a 0→1 progress onto the scene as custom
   properties and everything below is pure CSS off those numbers.
   ========================================================================== */

@property --p     { syntax: '<number>'; initial-value: 0; inherits: true; }
@property --hz       { syntax: '<number>'; initial-value: 0; inherits: true; }
@property --hblack   { syntax: '<number>'; initial-value: 0; inherits: true; }
@property --handoff  { syntax: '<number>'; initial-value: 0; inherits: true; }
@property --vis      { syntax: '<number>'; initial-value: 1; inherits: true; }
@property --drift    { syntax: '<number>'; initial-value: 0.5; inherits: true; }
@property --centre   { syntax: '<number>'; initial-value: 1; inherits: true; }
@property --l     { syntax: '<number>'; initial-value: 0; inherits: true; }
@property --r     { syntax: '<number>'; initial-value: 0; inherits: true; }
@property --t1    { syntax: '<number>'; initial-value: 0; inherits: true; }
@property --t2    { syntax: '<number>'; initial-value: 0; inherits: true; }
@property --i     { syntax: '<number>'; initial-value: 0; inherits: false; }
@property --hi    { syntax: '<number>'; initial-value: 0; inherits: false; }
@property --photos{ syntax: '<number>'; initial-value: 0; inherits: true; }
@property --vis   { syntax: '<number>'; initial-value: 1; inherits: true; }

/* No padding: the base `section { padding-block: var(--section-y) }` rule
   otherwise leaves a band of section background above and below every stage
   (a visible horizontal stripe at each scene seam) and desynchronises the
   sticky range from the progress maths, which assumes height - viewport. */
.scene { position: relative; padding-block: 0; }

.stage {
  position: sticky;
  top: 0;
  height: 100svh;
  overflow: hidden;
  display: grid;
  place-items: center;
  perspective: 1200px;
  transform-style: preserve-3d;
}

/* ---- ACT 1 · the title card recedes and the lights go out ---------------- */
.scene--hero .hero__inner {
  /* pulls back and dims as you scroll into the dark */
  transform: scale(calc(1 - var(--hz) * 0.14)) translateY(calc(var(--hz) * -5vh));
  opacity: calc(1 - var(--hz) * 0.85);
  will-change: transform, opacity;
}
/* the hero does not fade to black any more — it washes out to paper */
.hero-wash {
  position: absolute;
  inset: 0;
  background: var(--paper);
  opacity: var(--hblack);
  pointer-events: none;
  z-index: 3;
}

/* ---- ACT 2 · the monogram, and the photographs that pass it -------------

   A sticky stage always spends its last viewport of scroll un-pinning and
   sliding away, which here left a dead screen between the hero washing out
   and this act starting. Pulling it up by exactly that viewport makes it pin
   at the moment the hero releases. Both stages are opaque paper and this one
   is later in the DOM, so it simply covers the outgoing one. */
.scene--drift { margin-top: -100svh; }

.scene--drift .stage {
  background: var(--paper);
  text-align: center;
  padding-inline: var(--gutter);
  opacity: var(--vis, 1);
}

/* ---- the field the photographs travel through --------------------------- */
.driftfield {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 1;
}

.driftfield__ph {
  /* How far through its own pass this shot is: 0 entering at the foot of the
     screen, 1 leaving over the top. --at offsets it against its neighbours,
     and the 1.9 gives each one a pass and a bit across the scene, so the
     field is never empty and never arrives all at once. */
  --L: calc(var(--drift, 0.5) * 1.9 + var(--at));

  position: absolute;
  /* Lanes are authored for a wide screen and pushed outward from the middle
     on a narrow one — see --spread below. The monogram takes a far larger
     share of a phone, so the band the photographs have to keep out of is
     much wider there than the authored positions assume. */
  left: calc(50% + (var(--lane) - 50%) * var(--spread, 1));
  top: 50%;
  width: calc(var(--w) * var(--zoom, 1));
  max-width: 320px;
  aspect-ratio: 3 / 4;
  translate: -50% -50%;

  /* Nearer shots — the ones with more depth — sweep further for the same
     scroll. That difference is the whole illusion; with one travel distance
     the field reads as a flat sheet sliding by. No tilt: they hang straight,
     which also keeps their boxes tight enough to stay out of the middle. */
  transform:
    translateY(calc((0.5 - var(--L)) * 190vh * var(--depth)))
    scale(calc(0.88 + var(--depth) * 0.12));

  /* Fades at both ends of the pass, so nothing pops against an edge. Two
     ramps multiplied: the first opens it, the second closes it, and outside
     the pass one of them is zero. */
  opacity: calc(
    clamp(0, calc(var(--L) * 6), 1) *
    clamp(0, calc((1 - var(--L)) * 6), 1) * 0.95);

  border-radius: 3px;
  overflow: hidden;
  will-change: transform, opacity;
}
.driftfield__ph img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* On a phone the lanes fan out and the monogram comes down a little, which
   together open a clear band through the middle. Shots at the outer lanes
   run off the edge of the screen, which reads as a field continuing past the
   frame rather than as a crop. */
@media (max-width: 759px) {
  /* Bigger, and further out. At the authored width these read as stamps on a
     phone; at this size they are photographs, and the extra spread means the
     growth goes off the edge of the screen rather than into the middle. */
  .driftfield { --spread: 1.32; --zoom: 1.35; }
  .monogram--centre { font-size: clamp(3.6rem, 1.9rem + 13vw, 11rem); }
}

/* ---- what holds the middle ---------------------------------------------- */
.drift-centre {
  position: relative;
  z-index: 3;
  display: grid;
  justify-items: center;
  /* arrives once, early, then holds while the photographs keep coming */
  opacity: clamp(0, calc(var(--centre, 1) * 1.6), 1);
  transform: translateY(calc((1 - var(--centre, 1)) * 26px));
}

/* Paper breathing behind the letterforms. The photographs pass behind this
   block, and foil on a photograph is unreadable — this keeps a pool of the
   page around the monogram without drawing a panel. */
.drift-centre::before {
  content: '';
  position: absolute;
  inset: -60% -34%;
  background: radial-gradient(ellipse 54% 52% at 50% 50%,
    var(--paper) 54%,
    color-mix(in srgb, var(--paper) 78%, transparent) 70%,
    transparent 88%);
  z-index: -1;
  pointer-events: none;
}

.drift-line {
  font-family: var(--label);
  text-transform: uppercase;
  letter-spacing: 0.2em;
  font-size: clamp(0.6rem, 0.55rem + 0.22vw, 0.72rem);
  color: var(--cream-dim);
  text-indent: 0.2em;
  margin-bottom: clamp(1rem, 3vh, 1.7rem);
  max-width: 34ch;   /* two lines on a phone, one on a wide screen */
  text-wrap: balance;
}

/* the monogram is the centrepiece here, not a footer mark — so it is set
   larger than the one in the credits and carries the block on its own */
.monogram--centre {
  font-size: clamp(4.4rem, 2rem + 15vw, 11rem);
  margin-bottom: clamp(1.2rem, 3.6vh, 2.2rem);
}

.drift-names {
  font-family: var(--names);
  font-size: 0.9375rem;   /* 15px, like the rest of the copy */
  line-height: 1.35;
  letter-spacing: 0.02em;
  color: var(--ink);
  text-wrap: balance;
}

/* the last beat: wash to the colour act 01 opens on, so the change of ground
   is arrived at rather than cut to */
.drift-handoff {
  position: absolute;
  inset: 0;
  background: var(--oxblood);
  opacity: var(--handoff, 0);
  pointer-events: none;
  z-index: 6;
}

@media (prefers-reduced-motion: reduce) {
  /* nothing travels: the photographs lay out as a plain wrapped row under the
     monogram, which is the same material without the movement */
  .driftfield {
    position: static;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: clamp(0.6rem, 2.4vw, 1.1rem);
    margin-top: clamp(2rem, 6vh, 3.4rem);
  }
  .driftfield__ph {
    position: static;
    width: clamp(104px, 28vw, 172px);
    max-width: none;
    translate: none;
    transform: none;
    opacity: 1;
  }
  .drift-centre { opacity: 1; transform: none; }
  .drift-handoff { display: none; }
}


/* ---- ACT 3 · the two leads fly in from the corners ----------------------- */
.scene--cards .stage { background: var(--bg); }

.flydeck {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(0.8rem, 3vw, 2.4rem);
  width: min(100%, 900px);
  padding-inline: var(--gutter);
  transform-style: preserve-3d;
}

.flycard { will-change: transform; backface-visibility: hidden; }
.flycard--l {
  transform:
    translate3d(calc((1 - var(--l)) * -80vw), calc((1 - var(--l)) * -34vh), calc((1 - var(--l)) * -700px))
    rotateY(calc((1 - var(--l)) * 62deg))
    rotate(calc((1 - var(--l)) * -22deg));
  opacity: clamp(0, calc(var(--l) * 4), 1);
}
.flycard--r {
  transform:
    translate3d(calc((1 - var(--r)) * 80vw), calc((1 - var(--r)) * -34vh), calc((1 - var(--r)) * -700px))
    rotateY(calc((1 - var(--r)) * -62deg))
    rotate(calc((1 - var(--r)) * 22deg));
  opacity: clamp(0, calc(var(--r) * 4), 1);
}   /* no shutter here */

.flydeck__title {
  grid-column: 1 / -1;
  text-align: center;
  margin-bottom: clamp(1rem, 3vw, 2rem);
  opacity: clamp(0, calc(var(--l) * 2.5), 1);
}
/* .sec-head is a flex row, so the text-align above cannot reach it — without
   this the heading hangs off to the left of the cards it belongs to, which on
   a wide screen is the width of half the deck away */
.flydeck__title .sec-head,
.windeck__title .sec-head { justify-content: center; }

/* The card lands, and only then does it fill in: role, name, family, one line
   after another. --lc / --rc are the scene engine's second clock, running a
   little behind the flight itself; unset (no engine, reduced motion) they
   default to 1 and every line is simply there.

   The index comes from nth-child rather than the markup so that the cascade
   stays contiguous whichever of the optional lines the config filled in. */
.flycard--l .star { --c: var(--lc, 1); }
.flycard--r .star { --c: var(--rc, 1); }

.star > * {
  --r: clamp(0, calc(var(--c, 1) * 8 - var(--i, 0)), 1);
  opacity: var(--r);
  transform: translateY(calc((1 - var(--r)) * 18px));
}
.star > *:nth-child(1) { --i: 0; }
.star > *:nth-child(2) { --i: 1; }
.star > *:nth-child(3) { --i: 2; }
.star > *:nth-child(4) { --i: 3; }
.star > *:nth-child(5) { --i: 4; }
.star > *:nth-child(6) { --i: 5; }
.star > *:nth-child(7) { --i: 6; }
/* the portrait also grows the last of the way in, which the flat lines cannot */
.star__avatar { transform: translateY(calc((1 - var(--r)) * 18px)) scale(calc(0.86 + var(--r) * 0.14)); }

/* ---- ACT 4 · the tickets blow in and stick ------------------------------- */
.scene--tickets .stage { background: var(--paper-3); align-content: center; }

.windeck {
  display: grid;
  gap: clamp(0.9rem, 2.5vw, 1.6rem);
  width: min(100%, 940px);
  padding-inline: var(--gutter);
  transform-style: preserve-3d;
}

.windcard { will-change: transform; backface-visibility: hidden; }
/* comes in high and fast from the right, overshoots, then settles flat */
.windcard--1 {
  transform:
    translate3d(calc((1 - var(--t1)) * 105vw), calc((1 - var(--t1)) * -40vh), calc((1 - var(--t1)) * -600px))
    rotate(calc((1 - var(--t1)) * 26deg))
    rotateX(calc((1 - var(--t1)) * 48deg))
    rotateY(calc((1 - var(--t1)) * -34deg));
  opacity: clamp(0, calc(var(--t1) * 5), 1);
}

.windeck__title {
  text-align: center;
  margin-bottom: clamp(0.8rem, 2.5vw, 1.6rem);
  opacity: clamp(0, calc(var(--t1) * 2.5), 1);
}

/* and the ticket prints itself once it has stuck: the header, the title, then
   each fact in turn, then the barcode */
.windcard--1 .tk { --c: var(--tc, 1); }

.tk__top, .tk__title, .tk__cell, .tk .barcode {
  --r: clamp(0, calc(var(--c, 1) * 9 - var(--i, 0)), 1);
  opacity: var(--r);
  transform: translateY(calc((1 - var(--r)) * 14px));
}
.tk__top   { --i: 0; }
.tk__title { --i: 1; }
.tk__cell:nth-child(1) { --i: 2; }
.tk__cell:nth-child(2) { --i: 3; }
.tk__cell:nth-child(3) { --i: 4; }
.tk__cell:nth-child(4) { --i: 5; }
.tk__cell:nth-child(5) { --i: 6; }
.tk .barcode { --i: 7; }
/* the stub is the card's own colour block — it arrives with the card, not
   after it, or the ticket looks torn while it settles */

/* the ticket is already compact enough for a pinned viewport; only the
   barcode is worth dropping on very short screens */
@media (max-height: 700px) {
  .tk .barcode { display: none; }
}

/* ---- scenes, when motion is not wanted ----------------------------------- */
@media (prefers-reduced-motion: reduce) {
  /* collapse every pinned act to a plain, fully-visible block */
  .scene { height: auto !important; }
  .scene--drift { margin-top: 0; }    /* the overlap only makes sense when pinned */
  /* height:auto already stops the stage cropping its own content, so keep the
     clipping — `overflow: visible` here lets .hero::before (a 130vmax
     projector beam) escape and give the page a horizontal scrollbar */
  .stage { position: static; height: auto; padding-block: var(--section-y); }
  .petal, .hero-wash, .drift-handoff { display: none; }
  .scene--hero .hero__inner { transform: none; opacity: 1; }
  .flycard--l, .flycard--r, .windcard--1, .windcard--2 { transform: none; opacity: 1; }
  .flydeck__title, .windeck__title { opacity: 1; }
  /* the card and ticket contents are normally timed off the scene engine,
     which never runs here — so state them plainly */
  .star > *, .star__avatar,
  .tk__top, .tk__title, .tk__cell, .tk .barcode { opacity: 1; transform: none; }
  .scene--tickets .barcode { display: block; }
}

/* ---------- 17. scroll-reveal machinery ------------------------------------ */
/* GSAP owns these entrances now (see lib/reveal.js). The rule stays so the
   page is not a flash of unstyled text before the script runs, but there is
   no transition here to fight the tween. */
[data-reveal] {
  opacity: 0;
  will-change: opacity, transform;
}
[data-reveal].is-in { opacity: 1; }
[data-reveal="up"]     { transform: translateY(42px); }
[data-reveal="down"]   { transform: translateY(-28px); }
[data-reveal="left"]   { transform: translateX(-44px); }
[data-reveal="right"]  { transform: translateX(44px); }
[data-reveal="scale"]  { transform: scale(1.08); }
[data-reveal="fade"]   { transform: none; }
[data-reveal="curtain"] {
  clip-path: inset(0 0 100% 0);
  transform: none;
  opacity: 1;
}
/* the certificate lands like a rubber stamp: overshoot, then settle hard */
[data-reveal="stamp"] {
  transform: scale(1.22) rotate(-3.5deg);
  transition:
    opacity 420ms var(--ease) var(--d, 0ms),
    transform 620ms cubic-bezier(0.2, 1.5, 0.4, 1) var(--d, 0ms);
}

.is-in[data-reveal] {
  opacity: 1;
  transform: none;
  clip-path: inset(0 0 0 0);
}

/* hero intro choreography */
.hero [data-intro] {
  opacity: 0;
  transform: translateY(30px);
  filter: blur(6px);
  transition: opacity 1100ms var(--ease) var(--d, 0ms),
              transform 1200ms var(--ease-out-expo) var(--d, 0ms),
              filter 900ms var(--ease) var(--d, 0ms);
}
.is-open .hero [data-intro] { opacity: 1; transform: none; filter: blur(0); }

/* the names carry the poster, so they get a scale as well as a rise — the
   difference is what stops the whole block reading as one sliding panel */
.hero__names[data-intro] { transform: translateY(38px) scale(0.94); }
.is-open .hero__names[data-intro] { transform: none; }

/* and the cue keeps breathing once it has arrived */
.is-open .scroll-cue { animation: cue-breathe 2.8s var(--ease) 2.4s infinite; }
@keyframes cue-breathe {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}
@media (prefers-reduced-motion: reduce) {
  .is-open .scroll-cue { animation: none; }
  .hero [data-intro] { filter: none; }
}

/* parallax hooks (values written by JS) */
[data-parallax] { will-change: transform; }

/* ---------- 18. reduced motion --------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  [data-reveal], .hero [data-intro] { opacity: 1 !important; transform: none !important; clip-path: none !important; }
  .lead__frame::after { transform: scaleY(0); }
  .logline .w { opacity: 1; filter: none; }
  .cert__stamp { opacity: 1 !important; rotate: -6deg !important; scale: 1 !important; }
  .grain { display: none; }
  .confetti { display: none; }   /* no burst under reduced motion */
}

/* ---------- 19. focus + print ---------------------------------------------- */
:focus-visible { outline: 2px solid var(--gold-bright); outline-offset: 3px; }

@media print {
  .grain, .vignette, .reel, .scroll-cue { display: none !important; }
  body { background: #fff; color: var(--ink); }
}
