/* =========================================================
   ELEGANT tier — reference build

   Everything Classic has, plus the four unlocks:
     1. falling flowers + a hero entrance animation
     2. a six-picture gallery
     3. the pre-wedding film
     4. the calendar SUBSCRIBE feed and the WhatsApp share card

   Palette is shared with Classic — same door2 artwork, same product. The
   difference a bride sees in a 15-second demo is motion and content, not a
   different colour scheme.
   ========================================================= */
:root {
  --turq:      #20b4b1;
  --turq-deep: #14807f;
  --teal:      #3e8377;
  --teal-ink:  #14403a;   /* small text over the hero — see Classic's note */
  --rose:      #b34766;
  --rose-deep: #8d3350;
  --terra:     #ae8a71;
  --sand:      #eed1b0;
  --cream:     #fbf6ea;
  --cream-2:   #f5ece0;
  --ink:       #3a2b26;
  --ink-soft:  #6d5a52;

  --serif:  "Cormorant Garamond", Georgia, serif;
  --display:"Marcellus", Georgia, serif;
  --script: "Great Vibes", cursive;

  --maxw: 1040px;
  --ease: cubic-bezier(.22,.61,.36,1);
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }

body {
  font-family: var(--serif);
  font-size: 19px;
  line-height: 1.65;
  color: var(--ink);
  background: var(--cream);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;   /* petals drift sideways; never let them add a scrollbar */
}

/* width/height attributes stop images collapsing before they load, but they
   map to presentational height declarations that silently override
   aspect-ratio wherever author CSS leaves height unset. The two go together. */
img { max-width: 100%; height: auto; display: block; }

/* ---------- SHARED ---------- */
.section { padding: clamp(56px, 8vw, 96px) 22px; position: relative; }
.section--tint { background: var(--cream-2); }

.script {
  font-family: var(--script);
  font-size: clamp(30px, 4.4vw, 42px);
  color: var(--rose);
  text-align: center;
  line-height: 1.1;
}
/* `margin: … auto …`, never `… 0 …` — the shorthand cancels inherited auto
   centring and the heading silently sits flush left (Issue 15). */
.section__title {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(24px, 3.4vw, 34px);
  letter-spacing: 2.5px;
  text-transform: uppercase;
  text-align: center;
  color: var(--ink);
  max-width: var(--maxw);
  margin: 6px auto 0;
}
.rule {
  width: 78px; height: 1px;
  background: var(--turq);
  margin: 20px auto 36px;
  position: relative;
}
.rule::after {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 7px; height: 7px;
  transform: translate(-50%, -50%) rotate(45deg);
  background: var(--rose);
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--display);
  font-size: 12px;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--cream);
  background: var(--turq-deep);
  border: 1px solid var(--turq-deep);
  border-radius: 2px;
  padding: 13px 28px;
  cursor: pointer;
  transition: background .25s, border-color .25s, color .25s;
}
.btn:hover { background: var(--teal); border-color: var(--teal); }
.btn:focus-visible { outline: 2px solid var(--rose); outline-offset: 3px; }
.btn--wa { background: #25d366; border-color: #25d366; color: #06301a; }
.btn--wa:hover { background: #1eb257; border-color: #1eb257; color: #fff; }
.btn--ghost { background: transparent; color: var(--turq-deep); border-color: rgba(20,128,127,.5); }
.btn--ghost:hover { background: var(--turq-deep); color: var(--cream); }
.ico { flex-shrink: 0; }

.link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--display);
  font-size: 11px;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--turq-deep);
  text-decoration: none;
  border-bottom: 1px solid rgba(20,128,127,.35);
  padding-bottom: 2px;
}
.link:hover { color: var(--rose-deep); border-bottom-color: var(--rose); }
.link:focus-visible { outline: 2px solid var(--rose); outline-offset: 3px; }

[data-reveal] {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity .8s var(--ease), transform .8s var(--ease);
}
[data-reveal].is-visible { opacity: 1; transform: none; }

/* ---------- FALLING FLOWERS ----------
   The Elegant fence, and the cheapest thing on the page: CSS shapes, no
   images, no payload. Colours come from the bougainvillea in the door
   artwork so the two read as one scene.

   pointer-events:none matters — an invisible full-height overlay would
   otherwise swallow every click on the page underneath it. */
.petals {
  position: fixed;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  overflow: hidden;
}
.petal {
  position: absolute;
  top: -8vh;
  /* a bougainvillea bract: papery, wider than tall, one pointed corner */
  border-radius: 60% 8% 60% 8%;
  opacity: 0;
  will-change: transform;
  animation-name: fall;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.petal--a { background: rgba(214,72,130,.72); }
.petal--b { background: rgba(179,71,102,.62); }
.petal--c { background: rgba(232,140,175,.66); }
.petal--d { background: rgba(238,209,176,.58); }

@keyframes fall {
  0%   { opacity: 0;   transform: translate3d(0, 0, 0) rotate(0deg); }
  8%   { opacity: .95; }
  35%  { transform: translate3d(26px, 34vh, 0) rotate(150deg); }
  65%  { transform: translate3d(-18px, 68vh, 0) rotate(280deg); }
  92%  { opacity: .8; }
  100% { opacity: 0;   transform: translate3d(14px, 112vh, 0) rotate(420deg); }
}

/* Motion is the tier's selling point, but never at the cost of someone who
   has asked the OS to stop it. The petals are removed outright rather than
   slowed — js/main.js does not create them either. */
@media (prefers-reduced-motion: reduce) {
  .petals { display: none; }
  [data-reveal] { opacity: 1; transform: none; transition: none; }
  html { scroll-behavior: auto; }
  .hero__eyebrow, .hero__names, .hero__date, .hero__photo { animation: none !important; opacity: 1 !important; transform: none !important; }
}

/* ---------- HERO ---------- */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(40px, 7vh, 80px) 22px clamp(56px, 9vh, 92px);
  overflow: hidden;
  background: var(--cream-2);
}
/* Blurred, enlarged copy of the plate behind the doorway, so the portrait plate
   on a landscape screen is not flanked by two flat bars. Same file as the hero
   image, so it costs no extra request. `inset: -5%` pushes the blur's soft edge
   off-screen; without it the blur radius leaves a pale halo round the viewport. */
.hero::before {
  content: "";
  position: absolute;
  inset: -5%;
  background: url(../assets/door-scene.webp) center / cover no-repeat;
  filter: blur(30px) brightness(.94) saturate(.8);
  z-index: 0;
}

/* CONTAIN, not cover — the opposite of the other builds, and deliberately.
   The plate is 0.671:1, portrait, far narrower than a landscape window. Covering
   one means scaling to its WIDTH, which on a 1707x825 window makes the plate
   2544px tall and pushes the arch, the couple and the door itself off-screen,
   leaving a close-up of panelling. Measured, not guessed — that is exactly what
   the first attempt did. Here the doorway has to stay whole, because the door
   leaves are registered to it and a cropped doorway is a misaligned door.

   `inset: 0; margin: auto` with a definite size centres on both axes.
   Fit maths: width = height x 0.671, so the width-limited height is
   100vw / 0.671 = 149.031vw; take whichever of that and 100% is smaller. */
.hero__doorway {
  position: absolute;
  inset: 0;
  margin: auto;
  z-index: 1;
  height: min(100%, 149.031vw);
  aspect-ratio: 671 / 1000;
  animation: heroZoom 14s var(--ease) forwards;
}
.hero__bg {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;               /* ratio already matches, so nothing is cropped */
}
@keyframes heroZoom { from { transform: scale(1.06); } to { transform: scale(1); } }

/* Tuned against measured contrast, not by eye — the same wash the Classic
   build arrived at after tools/check_hero_contrast.py failed it twice. It is
   stronger than this particular image needs, deliberately, so it still holds
   for whatever photograph a client swaps in. */
.hero__wash {
  position: absolute;
  inset: 0;
  z-index: 2;
  /* Starts absent and arrives with the opening — see `body.doors-open
     .hero__wash`. The door is meant to be seen at full strength; washing it out
     while it is still closed would make the entrance look faded and broken. */
  opacity: 0;
  /* RETUNED for this build, and the old values would have been actively wrong
     here. The original wash was tuned against door2 — a saturated turquoise door
     filling the frame behind dark type — so it is deliberately near-opaque in the
     middle. Point it at the couple plate and it erases the couple: verified on
     screen, they were a faint outline. Since the couple ARE the hero in this
     build, that destroys the entire reveal.

     The new shape is strong at the top, where the type sits over cream wall, and
     clears downward over the lower half where the couple stand. Sampled, not
     guessed — tools/check_hero_contrast_bespoke3.py measures every band against
     this exact gradient stack. Do not weaken the top stops to show more artwork
     without re-running it. */
  background:
    linear-gradient(180deg,
      rgba(251,246,234,.93) 0%,
      rgba(251,246,234,.82) 26%,
      rgba(251,246,234,.36) 50%,
      rgba(251,246,234,.14) 78%,
      rgba(251,246,234,.34) 100%),
    radial-gradient(ellipse at 50% 22%,
      rgba(251,246,234,.74) 0%,
      rgba(251,246,234,.44) 40%,
      transparent 70%);
}
/* Type sits ABOVE the couple, not centred over them. With hero__photo removed
   the column centres itself, and measured that put the names at 0.43-0.60 of the
   hero — directly across the couple's faces, which are at roughly 0.45-0.88 of
   the plate. Top-aligning drops the type onto the cream upper arch, where there
   is nothing behind it and the wash can be strong without touching the couple.
   It is also how the live V&P card is laid out: names up, couple below. */
.hero { align-items: flex-start; }
.hero__inner { position: relative; z-index: 3; text-align: center; max-width: 640px; }

/* The entrance. Staggered, and `both` fill so nothing flashes before its
   turn. This is what Classic does not get. */
.hero__eyebrow, .hero__names, .hero__date, .hero__photo { animation: riseIn .9s var(--ease) both; }
.hero__eyebrow { animation-delay: .15s; }
.hero__names   { animation-delay: .35s; }
.hero__date    { animation-delay: .6s; }
.hero__photo   { animation-delay: .8s; }
@keyframes riseIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: none; } }

.hero__eyebrow {
  font-family: var(--display);
  font-size: clamp(10px, 1.5vw, 12px);
  letter-spacing: 4.5px;
  text-transform: uppercase;
  color: var(--teal-ink);
}
.hero__names {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(40px, 8vw, 74px);
  line-height: 1.05;
  color: var(--ink);
  margin: 12px 0 6px;
}
.hero__names span { font-family: var(--script); font-size: .62em; color: var(--rose); }
.hero__date {
  font-family: var(--display);
  font-size: clamp(11px, 1.7vw, 14px);
  letter-spacing: 4px;
  text-transform: uppercase;
  color: var(--ink);
}
.hero__photo {
  width: clamp(180px, 30vw, 236px);
  margin: clamp(22px, 4vh, 38px) auto 0;
  border-radius: 50% 50% 6px 6px / 32% 32% 6px 6px;
  overflow: hidden;
  border: 3px solid var(--cream);
  box-shadow: 0 18px 44px rgba(58,43,38,.24);
}
.hero__photo img { width: 100%; aspect-ratio: 4 / 5; object-fit: cover; }

.hero__cue { position: absolute; left: 50%; bottom: 24px; transform: translateX(-50%); z-index: 2; }
.hero__cue span {
  display: block; width: 1px; height: 42px;
  background: linear-gradient(var(--teal-ink), transparent);
  animation: cue 2.2s ease-in-out infinite;
}
@keyframes cue { 50% { opacity: .25; transform: translateY(8px); } }

/* ---------- COUNTDOWN ---------- */
.count {
  background: var(--turq-deep);
  color: var(--cream);
  text-align: center;
  padding: clamp(38px, 6vw, 62px) 22px;
  position: relative;
  z-index: 2;
}
.count__grid {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: clamp(18px, 5vw, 58px);
  max-width: var(--maxw);
  margin: 0 auto;
}
.count__u { min-width: 68px; }
/* Below ~560px four units no longer fit on one line and wrap 3 + 1 with one
   stranded; an explicit 2x2 is deliberate rather than accidental. */
@media (max-width: 560px) {
  .count__grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 22px 12px;
    max-width: 320px;
  }
  .count__u { min-width: 0; }
}
.count__u span {
  display: block;
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(32px, 6vw, 56px);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.count__u small {
  display: block;
  font-family: var(--display);
  font-size: 10px;
  letter-spacing: 2.6px;
  text-transform: uppercase;
  opacity: .78;
  margin-top: 7px;
}
.count__note { font-style: italic; font-size: 17px; opacity: .85; margin-top: 22px; }

/* ---------- STORY ---------- */
.story__body { max-width: 620px; margin: 0 auto; text-align: center; }
.story__body p { margin-bottom: 16px; color: var(--ink-soft); }
.story__sign {
  font-family: var(--script);
  font-size: clamp(28px, 4.2vw, 38px);
  color: var(--rose);
  margin-top: 18px !important;
  line-height: 1.1;
}

/* ---------- GALLERY ----------
   Six pictures, shown large. No lightbox — that is the Signature upgrade,
   so these are sized to be worth looking at in place rather than acting as
   thumbnails that invite a click which does nothing. */
.gallery__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(12px, 1.8vw, 20px);
  max-width: var(--maxw);
  margin: 0 auto;
}
@media (max-width: 820px) { .gallery__grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 480px) { .gallery__grid { grid-template-columns: 1fr; } }
.gallery__grid figure {
  min-width: 0;   /* grid items default to min-width:auto and refuse to shrink */
  overflow: hidden;
  border: 1px solid rgba(174,138,113,.28);
  background: var(--cream);
}
.gallery__grid img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  transition: transform .6s var(--ease);
}
.gallery__grid figure:hover img { transform: scale(1.04); }

/* ---------- PRE-WEDDING FILM ---------- */
.film__frame {
  position: relative;
  max-width: 900px;
  margin: 0 auto;
  overflow: hidden;
  border: 1px solid rgba(174,138,113,.34);
  background: #0e2a28;
}
.film__frame img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.film__frame iframe,
.film__frame video { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; background: #0e2a28; }
.film__play {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 78px; height: 78px;
  border-radius: 50%;
  border: 1px solid var(--sand);
  background: rgba(20,128,127,.72);
  color: var(--cream);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background .25s, transform .25s var(--ease);
}
.film__play:hover { background: var(--turq-deep); transform: translate(-50%, -50%) scale(1.06); }
.film__play:focus-visible { outline: 2px solid var(--rose); outline-offset: 4px; }
.film__note {
  text-align: center;
  font-style: italic;
  color: var(--ink-soft);
  font-size: 16px;
  margin-top: 16px;
}
.film__note code {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 14px;
  font-style: normal;
  background: var(--cream-2);
  padding: 2px 6px;
}

/* ---------- EVENTS ---------- */
.events__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(16px, 2.4vw, 26px);
  max-width: var(--maxw);
  margin: 0 auto;
}
@media (max-width: 860px) { .events__grid { grid-template-columns: 1fr; } }

.event {
  background: var(--cream);
  border: 1px solid rgba(174,138,113,.3);
  border-top: 3px solid var(--accent, var(--turq));
  padding: clamp(22px, 3vw, 30px);
  min-width: 0;
  display: flex;
  flex-direction: column;
}
.event--mehendi { --accent: var(--turq); }
.event--sangeet { --accent: var(--rose); }
.event--wedding { --accent: var(--terra); }

.event__label {
  font-family: var(--display);
  font-size: 10px;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: var(--accent);
}
.event__name {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 600;
  font-size: clamp(24px, 3.2vw, 32px);
  line-height: 1.1;
  color: var(--accent);
  margin: 5px 0 8px;
}
.event__when {
  font-family: var(--display);
  font-size: 11.5px;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--ink);
}
.event__desc { margin-top: 10px; color: var(--ink-soft); font-size: 17px; }
.event__dress {
  margin-top: 10px;
  font-size: 15px;
  font-style: italic;
  color: var(--ink-soft);
  padding-top: 10px;
  border-top: 1px dashed rgba(174,138,113,.42);
}
.event__actions {
  margin-top: auto;
  padding-top: 18px;
  display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
}

/* ---------- CALENDAR MENUS ---------- */
.cal { position: relative; }
.cal__btn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--display);
  font-size: 11px;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--cream);
  background: var(--accent, var(--turq));
  border-radius: 2px;
  padding: 9px 15px;
  cursor: pointer;
  list-style: none;
}
.cal__btn::-webkit-details-marker { display: none; }
.cal__btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }
.cal[open] .cal__btn { filter: brightness(.9); }
.cal__menu {
  position: absolute;
  z-index: 5;
  top: calc(100% + 6px);
  left: 0;
  min-width: 190px;
  background: var(--cream);
  border: 1px solid rgba(174,138,113,.42);
  box-shadow: 0 12px 30px rgba(58,43,38,.16);
}
.cal__opt {
  display: block;
  padding: 11px 15px;
  font-size: 16px;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid rgba(174,138,113,.22);
}
.cal__opt:last-child { border-bottom: 0; }
.cal__opt:hover { background: var(--cream-2); color: var(--turq-deep); }
.cal__opt:focus-visible { outline: 2px solid var(--rose); outline-offset: -2px; }
/* de-emphasised on purpose: a download cannot auto-update, unlike the two above */
.cal__opt--alt { font-size: 14px; color: var(--ink-soft); font-style: italic; }

/* the subscribe menu, centred under the grid */
.events__all { text-align: center; margin-top: clamp(30px, 4vw, 44px); }
.cal--lg { display: inline-block; text-align: left; }
.cal__btn--lg {
  background: var(--turq-deep);
  font-size: 12px;
  padding: 13px 24px;
  letter-spacing: 2.2px;
}
.cal__menu--lg { left: 50%; transform: translateX(-50%); min-width: 240px; }
.events__note {
  max-width: 54ch;
  margin: 18px auto 0;
  font-size: 16px;
  color: var(--ink-soft);
  font-style: italic;
}

/* ---------- VENUE ---------- */
.venue__card {
  max-width: 520px;
  margin: 0 auto;
  text-align: center;
  background: var(--cream);
  border: 1px solid rgba(174,138,113,.32);
  padding: clamp(26px, 4vw, 40px);
}
.venue__card h3 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(22px, 2.8vw, 28px);
  margin-bottom: 10px;
}
.venue__card p { color: var(--ink-soft); margin-bottom: 10px; }
.venue__note { font-style: italic; }
.venue__card .btn { margin-top: 12px; }

/* ---------- SHARE ---------- */
.share { text-align: center; }
.share__lead { max-width: 56ch; margin: 0 auto 24px; color: var(--ink-soft); }
.share__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 14px; }
.share__status { min-height: 24px; margin-top: 16px; font-style: italic; color: var(--ink-soft); }

/* ---------- CLOSING ---------- */
.closing {
  background: var(--turq-deep);
  color: var(--cream);
  text-align: center;
  padding: clamp(52px, 8vw, 84px) 22px;
  position: relative;
  z-index: 2;
}
.closing__script { color: var(--sand); }
.closing__line { font-style: italic; margin-top: 10px; opacity: .9; }
.closing__names {
  font-family: var(--serif);
  font-size: clamp(26px, 4vw, 36px);
  letter-spacing: 1px;
  margin-top: 20px;
}
.closing__date {
  font-family: var(--display);
  font-size: 11px;
  letter-spacing: 5px;
  opacity: .72;
  margin-top: 8px;
}
.closing__credit {
  font-family: var(--display);
  font-size: 10.5px;
  letter-spacing: 2px;
  text-transform: uppercase;
  opacity: .62;
  margin-top: 30px;
}
.closing__credit a { color: var(--sand); text-decoration: none; border-bottom: 1px solid rgba(238,209,176,.4); }


/* ---------- MINIMUM TAP TARGET ----------
   WCAG 2.5.5 and the Apple HIG both want 44x44. These controls computed to
   32-42px from padding alone — and Add to Calendar is the most-tapped
   control on a wedding invitation, so it was the smallest thing on the page.

   min-height plus centring raises the hit area without changing how anything
   looks; padding is left alone so nothing gets visually chunkier. */
.btn, .cal__btn, .cal__btn--lg, .link {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* =========================================================
   SIGNATURE additions — door intro + background music
   ========================================================= */

/* ---------- CUSTOM REVEAL — the drawn envelope ----------
   Same visual language as the arch it grew out of: gold line art tracing
   itself on a dark field. Beats:

     0.2-1.5s  the envelope body draws
     0.7-1.7s  the pocket seams follow
     1.1-2.1s  the flap and its liner
     1.9-2.5s  the wax seal completes the circle
     2.6s      the names fade up
     on open   seal breaks, flap swings back on its fold, the envelope rises
               past the camera, and only then does the field fade. The field
               used to part down the middle instead — see "the opening".

   ⚠ DWELL in js/main.js mirrors these. Clearing earlier cuts the drawing off
   mid-stroke. */
.reveal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  cursor: pointer;
}

/* ONE element, not two halves that slide apart. The halves were meant to make
   the field "part rather than fade", but they upstaged the envelope — guests
   watched a curtain open, not an invitation — and the 50.5% overlap never did
   hide the seam: at fractional DPR the two gradients land on different
   subpixels and a hard vertical line shows down the centre of the field. The
   field now just fades, late, so the envelope opening is the whole effect. */
.reveal__ink {
  position: absolute;
  inset: 0;
  background: linear-gradient(160deg, #123b35 0%, #0c2a26 55%, #081d1a 100%);
}

.env {
  position: relative;
  z-index: 2;
  width: min(76vw, 460px);
  transition: transform 1s cubic-bezier(.66,0,.2,1), opacity .8s .15s ease;
}
/* An explicit aspect-ratio, not height:auto. The viewBox gives an intrinsic
   ratio in modern engines, but stating it means the box is correct before the
   SVG is parsed and nothing reflows underneath the drawing. */
.env__draw { display: block; width: 100%; aspect-ratio: 320 / 230; overflow: visible; }

.env__ln {
  fill: none;
  stroke: url(#envgold);
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: drop-shadow(0 0 9px rgba(232,200,138,.4));
  animation: envDraw 1.3s cubic-bezier(.4,0,.2,1) forwards;
}
@keyframes envDraw { to { stroke-dashoffset: 0; } }

/* staggered so it reads as one hand drawing, not five things at once */
.env__ln--body   { animation-delay: .2s; }
.env__ln--pocket { animation-delay: .7s; animation-duration: 1s; opacity: .78; }
.env__ln--flap   { animation-delay: 1.1s; animation-duration: 1s; }
.env__ln--liner  { animation-delay: 1.3s; animation-duration: 1s; stroke-width: 1.3; opacity: .7; }
.env__ln--seal   { animation-delay: 1.9s; animation-duration: .6s; stroke-width: 2.6; }

/* The flap hinges on its own top edge. transform-box: fill-box makes the
   percentage origin resolve against the path's bounding box rather than the
   whole SVG viewport, which is what puts the hinge on the fold. */
/* The perspective is INSIDE the transform, not inherited from a parent. A
   `perspective` property on an ancestor is unreliable for SVG children, and
   without any perspective at all rotateX is an orthographic squash — the flap
   flattened and flipped instead of swinging, which is why the field parting
   used to be the only thing the eye caught. */
.env__flapg {
  transform-box: fill-box;
  transform-origin: 50% 0%;
  transform: perspective(900px) rotateX(0deg);
  transition: transform 1s cubic-bezier(.5,0,.2,1), opacity .6s;
}

.reveal__hint {
  position: absolute;
  left: 50%;
  bottom: clamp(40px, 9vh, 92px);
  transform: translateX(-50%);
  z-index: 3;
  text-align: center;
  opacity: 0;
  animation: hintIn .9s 2.6s ease forwards;
}
@keyframes hintIn { to { opacity: 1; } }
.reveal__script {
  font-family: var(--script);
  font-size: clamp(30px, 6vw, 52px);
  color: #f6e6c4;
  line-height: 1;
  text-shadow: 0 3px 22px rgba(0,0,0,.6);
}
.reveal__sub {
  font-family: var(--display);
  font-size: 11px;
  letter-spacing: 4.5px;
  text-transform: uppercase;
  color: rgba(246,230,196,.72);
  margin: 8px 0 18px;
}
.reveal__btn {
  font-family: var(--display);
  font-size: 11.5px;
  letter-spacing: 3px;
  text-transform: uppercase;
  color: #0c2a26;
  background: #e8c88a;
  border: 1px solid #e8c88a;
  border-radius: 999px;
  padding: 12px 34px;
  min-height: 44px;
  cursor: pointer;
  box-shadow: 0 10px 30px rgba(0,0,0,.4);
}
.reveal__btn:hover { background: #fff4d6; border-color: #fff4d6; }
.reveal__btn:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

/* ================= STAGE 2 — the layered door =================
   The envelope (above) opens onto this; this opens onto the site. Geometry is
   measured by tools/make_door_reveal.py — never hand-edit the percentages.

   z-index 199, one below the envelope, and present from the first frame. It is
   hidden only because the envelope's field is opaque, so when that field fades
   there is nothing to load and no flash of empty page between the stages. */
/* The knock layer: a transparent sheet that only carries stage 2's prompt and
   catches the click. The DOOR itself is not here — it is in the hero, because in
   this build the doorway is the hero (see `.hero__doorway`). This exists purely
   so the prompt and the click target span the whole viewport while the page
   underneath is still locked. */
.knock {
  position: fixed;
  inset: 0;
  z-index: 199;
  pointer-events: none;             /* not clickable until the envelope is gone */
}
body.env-open .knock { pointer-events: auto; cursor: pointer; }
body.doors-open .knock { pointer-events: none; }

.dr__leaf {
  position: absolute;
  top: 18.9000%;
  height: 69.6000%;
  /* Perspective INSIDE the transform, for the same reason the envelope flap
     needs it: with no perspective rotateY is an orthographic squash, and the
     door reads as shrinking rather than swinging. */
  --dr-swing: 1.8s;
  transform: perspective(1400px) rotateY(0deg);
  transition: transform var(--dr-swing) cubic-bezier(.5,0,.2,1),
              filter    var(--dr-swing) cubic-bezier(.5,0,.2,1);
  backface-visibility: hidden;
  will-change: transform;
}
.dr__leaf img { display: block; width: 100%; height: 100%; }
.dr__leaf--l { left: 26.0805%; width: 23.9940%; transform-origin: left center; }
.dr__leaf--r { left: 50.0745%; width: 23.8450%; transform-origin: right center; }
/* The leaves stay in the DOM once open — they are hero children now, and removing
   them would pop. Edge-on they are invisible, but they still had a hit area sitting
   over the hero, so they must not swallow clicks meant for the page behind. */
.dr__leaf { pointer-events: none; }

/* Stage 2 has no prompt — see the note in index.html. The rules that styled
   "Now knock / Open the door" were removed with it rather than left behind. */

/* ---- stage 2 opening — the leaves swing out of the doorway ---- */
body.doors-open .dr__leaf--l {
  transform: perspective(1400px) rotateY(-102deg);
  filter: brightness(.62);
}
body.doors-open .dr__leaf--r {
  transform: perspective(1400px) rotateY(102deg);
  filter: brightness(.72);
  transition-delay: .09s, .09s;     /* never move a double door in lockstep */
}
/* There is NO hand-over fade, deliberately, and that is the whole point of this
   build. The hero is already behind the leaves, so opening them lands the guest
   in the website — nothing cross-fades, nothing is dismissed, and the couple they
   were just looking at is still there because it was the hero all along.

   What does happen is the wash arriving: the doorway is shown unwashed while the
   door is closed, so the artwork is at full strength, and the cream wash fades in
   as the leaves swing so the hero type becomes readable over it. That wash is
   contrast-tuned (see .hero__wash) and must not be weakened to show more artwork. */
body.doors-open .hero__wash { opacity: 1; transition: opacity 1.6s .5s ease; }

/* ---- the opening — the ENVELOPE opens; the field no longer parts ----
   Read as four beats in strict order. The whole point is that the guest
   watches an envelope being opened, so nothing may move until the seal has
   visibly broken, and the field must outlast the envelope — if it faded first
   the gold line art would be seen floating over the hero photograph. */
body.env-open .reveal { pointer-events: none; }
body.env-open .reveal__hint { opacity: 0; transition: opacity .3s; animation: none; }

/* 1 — the wax seal breaks. Nothing else has started yet. */
body.env-open .env__ln--seal { opacity: 0; transition: opacity .3s; }

/* 2 — the flap swings back on its own fold, toward the viewer. */
body.env-open .env__flapg {
  transform: perspective(900px) rotateX(-158deg);
  opacity: .3;
  transition: transform .95s .12s cubic-bezier(.5,0,.2,1), opacity .5s .3s;
}

/* 3 — only once it is open does the envelope rise past the camera, as though
       the guest is being taken inside it. */
body.env-open .env {
  transform: translateY(-6%) scale(2.5);
  opacity: 0;
  transition: transform 1.1s .8s cubic-bezier(.55,0,.25,1),
              opacity .7s 1.05s ease-in;
}

/* 4 — the field goes last. */
body.env-open .reveal__ink { opacity: 0; transition: opacity .8s .85s ease; }

body.env-open .reveal {
  visibility: hidden;
  transition: visibility 0s 1.95s;   /* once the envelope and field have gone */
}

body.doors-closed { overflow: hidden; }
/* Pauses ANIMATIONS behind the reveal only — it does nothing about downloads,
   which is why the open is gated on image decode in main.js (Issue 5). */
body.doors-closed .hero__eyebrow,
body.doors-closed .hero__names,
body.doors-closed .hero__date,
body.doors-closed .hero__photo,
/* heroZoom lives on .hero__doorway in this build, not on the image — pausing the
   image would leave the zoom running unseen behind a closed door and it would be
   part-finished by the time the guest got in. */
body.doors-closed .hero__doorway { animation-play-state: paused; }
body.doors-closed .petals { display: none; }

@media (prefers-reduced-motion: reduce) {
  /* the drawing is the whole effect, so it is removed rather than shortened —
     the envelope simply arrives complete */
  .env__ln { animation: none; stroke-dashoffset: 0 !important; }
  .env, .env__flapg, .reveal__ink { transition: none; }
  .reveal__hint { opacity: 1; animation: none; }
  body.env-open .reveal { display: none; }

  /* Stage 2 collapses the same way: both prompts are up immediately and each
     stage snaps rather than animating. The closed door is still SHOWN — it is
     the thing being knocked on — it just leaves instantly. */
  .hero__doorway, .dr__leaf, .knock, .hero__wash { transition: none; }
  .hero__doorway { animation: none; }
  body.doors-open .knock { display: none; }
}

/* Lengths below are EXACT, computed by tools/make_envelope_reveal.py from the
   same geometry that generated the paths. They are literals on purpose: the
   alternative is getTotalLength() in JS, which forces a script-driven start —
   and the first version of this used requestAnimationFrame for that, which
   never fires in a background tab, so the entrance froze half-drawn. */
.env__ln--body   { stroke-dasharray: 836.0;   stroke-dashoffset: 836.0; }
.env__ln--pocket { stroke-dasharray: 290.9; stroke-dashoffset: 290.9; }
.env__ln--flap   { stroke-dasharray: 331.7;   stroke-dashoffset: 331.7; }
.env__ln--liner  { stroke-dasharray: 282.0;  stroke-dashoffset: 282.0; }
.env__ln--seal   { stroke-dasharray: 106.8;   stroke-dashoffset: 106.8; }

/* ---------- MUSIC CONTROL ---------- */
.music {
  position: fixed;
  right: clamp(12px, 2.5vw, 26px);
  bottom: clamp(12px, 2.5vw, 26px);
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 44px;
  padding: 10px 16px;
  border-radius: 999px;
  border: 1px solid rgba(20,128,127,.4);
  background: rgba(251,246,234,.94);
  color: var(--turq-deep);
  font-family: var(--display);
  font-size: 10.5px;
  letter-spacing: 2px;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: 0 8px 22px rgba(58,43,38,.18);
  transition: background .25s, color .25s;
}
.music[hidden] { display: none; }
.music:hover { background: var(--turq-deep); color: var(--cream); }
.music:focus-visible { outline: 2px solid var(--rose); outline-offset: 3px; }

/* three bars that animate only while the music is actually playing, so the
   control always shows the true state */
.music__icon { display: inline-flex; align-items: flex-end; gap: 2px; height: 14px; }
.music__icon i {
  display: block;
  width: 3px;
  height: 5px;
  background: currentColor;
  border-radius: 1px;
  transition: height .2s;
}
.music.is-playing .music__icon i { animation: bars .9s ease-in-out infinite; }
.music.is-playing .music__icon i:nth-child(2) { animation-delay: .18s; }
.music.is-playing .music__icon i:nth-child(3) { animation-delay: .36s; }
@keyframes bars { 0%, 100% { height: 4px; } 50% { height: 14px; } }

/* while the film is playing the control shows why it fell silent, rather
   than just appearing broken */
.music.is-ducked { opacity: .55; }

@media (prefers-reduced-motion: reduce) {
  .music.is-playing .music__icon i { animation: none; height: 9px; }
}

/* =========================================================
   BESPOKE modules
     1. personalised guest links
     2. guestbook
     3. live-stream page
   ========================================================= */

/* ---------- 1. PERSONALISED GREETING ---------- */
.hero__greeting {
  font-family: var(--script);
  font-size: clamp(24px, 4vw, 36px);
  line-height: 1.15;
  color: var(--rose);
  margin-bottom: 6px;
}
.hero__greeting[hidden] { display: none; }

/* ---------- 3. LIVE STREAM ---------- */
.live__frame {
  position: relative;
  max-width: 900px;
  margin: 0 auto;
  overflow: hidden;
  border: 1px solid rgba(174,138,113,.34);
  background: #0e2a28;
}
.live__frame img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.live__frame iframe { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; }

.live__badge {
  position: absolute;
  top: 14px; left: 14px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--rose-deep);
  color: var(--cream);
  font-family: var(--display);
  font-size: 10px;
  letter-spacing: 2.4px;
}
.live__badge[hidden] { display: none; }
.live__badge span {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--cream);
  animation: livePulse 1.4s ease-in-out infinite;
}
@keyframes livePulse { 50% { opacity: .25; } }

.live__countdown {
  text-align: center;
  font-family: var(--display);
  font-size: 13px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--turq-deep);
  margin-top: 20px;
}
.live__schedule {
  list-style: none;
  max-width: 460px;
  margin: 22px auto 0;
  padding: 0;
}
.live__schedule li {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 14px;
  padding: 11px 0;
  border-bottom: 1px dashed rgba(174,138,113,.42);
  color: var(--ink-soft);
  font-size: 16px;
}
.live__schedule li:last-child { border-bottom: 0; }
.live__schedule strong { font-family: var(--display); font-weight: 400; color: var(--ink); }
/* the ones actually being streamed carry the accent; the rest read as muted */
.live__schedule li.is-on strong { color: var(--rose-deep); }
.live__schedule li:not(.is-on) { opacity: .58; }
.live__note {
  text-align: center;
  font-style: italic;
  font-size: 15px;
  color: var(--ink-soft);
  margin-top: 18px;
}

/* ---------- 2. GUESTBOOK ---------- */
.guestbook__wall {
  columns: 2 260px;
  column-gap: 18px;
  max-width: var(--maxw);
  margin: 0 auto;
}
.guestbook__wall .wish {
  break-inside: avoid;
  margin: 0 0 16px;
  padding: 18px 20px;
  background: var(--cream-2);
  border-left: 2px solid var(--rose);
  border-radius: 0 2px 2px 0;
}
.guestbook__wall .wish p { font-style: italic; font-size: 16.5px; line-height: 1.55; color: var(--ink); }
.guestbook__wall .wish cite {
  display: block;
  margin-top: 9px;
  font-family: var(--display);
  font-style: normal;
  font-size: 10px;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  color: var(--rose-deep);
}
/* an entry the guest just submitted, shown before moderation */
.guestbook__wall .wish--pending { border-left-color: var(--turq); opacity: .72; }

.guestbook__form {
  max-width: 520px;
  margin: 30px auto 0;
  text-align: left;
}
.guestbook__form[hidden] { display: none; }
.guestbook__lead {
  text-align: center;
  color: var(--ink-soft);
  margin-bottom: 18px;
  font-size: 16px;
}
.field { display: block; margin-bottom: 14px; }
.field span {
  display: block;
  font-family: var(--display);
  font-size: 10px;
  letter-spacing: 2.2px;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 6px;
}
.field input,
.field textarea {
  width: 100%;
  font-family: var(--serif);
  font-size: 17px;
  color: var(--ink);
  background: var(--cream);
  border: 1px solid rgba(174,138,113,.5);
  border-radius: 2px;
  padding: 11px 13px;
  /* 16px+ on the input itself stops iOS Safari zooming the whole page in
     when the field is focused, which then leaves the layout scrolled
     sideways with no obvious way back. */
  min-height: 44px;
}
.field textarea { resize: vertical; min-height: 88px; }
.field input:focus-visible,
.field textarea:focus-visible { outline: 2px solid var(--turq); outline-offset: 1px; }

.guestbook__form .btn { width: 100%; justify-content: center; }
.guestbook__status { min-height: 24px; margin-top: 12px; font-style: italic; color: var(--ink-soft); text-align: center; }
.guestbook__note {
  max-width: 54ch;
  margin: 22px auto 0;
  text-align: center;
  font-size: 15px;
  font-style: italic;
  color: var(--ink-soft);
}
.guestbook__note code {
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 13px;
  font-style: normal;
  background: var(--cream-2);
  padding: 2px 6px;
}
@media (max-width: 560px) {
  .guestbook__wall { columns: 1; }
}
