/* ==========================================================================
   Design tokens
   ========================================================================== */
:root {
  --color-cream: #faf3e6;
  --color-cream-light: #fffaf0;
  --color-cream-dark: #f0e2c4;
  --color-gold: #c9a24b;
  --color-gold-light: #e8c877;
  --color-maroon: #7a1f2b;
  --color-ink: #3a2e22;

  --font-script: "Dancing Script", cursive;

  /* Images + fonts + brand — populated from config at runtime */
  --seal-size: cover;
  --seal-position: center;
  --font-latin: "Lora", serif;
  --font-devanagari: "Noto Serif Devanagari", serif;
  --font-active: var(--font-latin);
  --font-script: "Pinyon Script", cursive;
  --font-swash: "Allura", cursive;
  --font-stencil: "Stardos Stencil", sans-serif;
  --font-flourish: "Parisienne", cursive;
  --font-times: "Tinos", "Times New Roman", serif;

  /* Cards 3–6 — populated from CONFIG.cardCeremony at runtime */
  --ceremony-ink: #1a1512;
  --ceremony-light-ink: #f3e3c0;
  --ceremony-light-accent: #d8b24a;

  /* Card 1's ink — populated from CONFIG.cardEvent at runtime */
  --ink-primary: #5b1a2b;
  --ink-gold: #9c7b3f;
  --ink-muted: #6b4f2a;
  --ink-rule: rgba(156, 123, 63, 0.45);
  --ink-rule-strong: rgba(156, 123, 63, 0.75);
  --date-rule: #d4af37;
  --date-rule-width: 2px;
  --ink-highlight: rgba(255, 252, 245, 0.55);
  --ink-deboss: rgba(60, 30, 20, 0.10);
  --ink-text: #333333;
  --name-stroke: 0.4px;
  --cta-bg: #7a1f2b;
  --cta-bg-deep: #5e1b2e;
  --cta-text: #ffffff;
  --cta-radius: 999px;
  --countdown-border: rgba(184, 151, 63, 0.75);
  --countdown-colon: rgba(184, 151, 63, 0.9);
  --brand-maroon: #5e1b2e;
  --gold: #b8973f;

  /* Nav glass — populated from config.js at runtime */
  --nav-blur: 14px;
  --nav-tint: rgba(255, 250, 244, 0.28);
  --nav-tint-solid: rgba(255, 250, 244, 0.72);
  --nav-border: rgba(255, 255, 255, 0.55);
  --nav-radius: 18px;
  --nav-margin-x: 12px;
  --nav-margin-top: 10px;
  --nav-shadow: 0 6px 24px rgba(60, 30, 20, 0.12);
  --nav-logo-height: 30px;

  /* The nav's measured bottom edge — the card box and the latkan both hang off
     it, so neither has to guess the bar's height. Set at runtime. */
  --nav-bottom: 60px;

  /* Latkan — fallbacks only; the real values are set per element at runtime,
     so each tassel carries its own. --latkan-delay must resolve to something
     or the `animation` shorthand that uses it is dropped wholesale. */
  --latkan-top-offset: 6px;
  --latkan-right: 3vw;
  --latkan-cord-width: 8px;
  --latkan-tassel-height: 25vh;
  --latkan-cord-length: 40vh;
  --latkan-swing: 7deg;
  --latkan-duration: 2.5s;
  --latkan-delay: 0s;

  /* Background + card box + deck — populated from config.js at runtime */
  --stage-aspect: 0.5619;
  --blur: 28px;
  --blur-scale: 1.12;
  --card-aspect: 0.5625;
  --card-gap-below-nav: 14px;
  --card-side-margin: 18px;
  --card-bottom-margin: 18px;
  --deck-duration: 900ms;
  --deck-ease: cubic-bezier(.22, 1, .36, 1);
  --offscreen: 100vh;
  --shadow-rest: drop-shadow(0 10px 18px rgba(0, 0, 0, 0.28));
  --shadow-lift: drop-shadow(0 26px 40px rgba(0, 0, 0, 0.38));
  --settle-ms: 350ms;

  /* Music button — populated from config.js at runtime. The icon keeps the
     proportion it was authored at (20px glyph in a 48px button). */
  --music-size: 56px;
  --music-border-color: #c9a227;
  --music-border-width: 2px;
  --music-fill: #5e1b2e;
  --music-icon: #ffffff;
  --music-icon-scale: 0.4167;

  /* Envelope animation — populated from config.js at runtime */
  --envelope-ease: ease;
  --envelope-perspective: 1500px;
  --flap-angle: 160deg;
  --flap-angle-neg: -160deg;
  --text-fade-duration: 300ms;
  --flap-duration: 2300ms;
  --flap-start: 0ms;
  --flap-fade-duration: 575ms;
  --flap-fade-delay: 1725ms;
  --reveal-duration: 900ms;
  --reveal-delay: 4900ms;
  --shadow-flat: drop-shadow(0 0 0 rgba(0, 0, 0, 0));
  --reduced-duration: 450ms;
}

* { box-sizing: border-box; }

/* Author `display` on .menu/.music-btn would otherwise beat the UA's
   [hidden] rule and show them anyway. */
[hidden] { display: none !important; }

/* One fixed screen — every layer is position:fixed, so there is nothing to
   scroll even without `overflow: hidden`. It's deliberately left off (and
   overscroll kept `auto`) so the browser's own pull-to-refresh can still
   engage on the first card; the touch handler cancels the gesture everywhere
   else. */
html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  overscroll-behavior-y: auto;
  font-family: var(--font-active);
  color: var(--color-ink);
  background: var(--color-cream);
}

img { max-width: 100%; }

/* ==========================================================================
   Background stage
   ========================================================================== */

/* Held back until the envelope hands over, then fades in out of focus and
   sharpens. */
.scene-blur,
.scene,
.deck,
.nav,
.latkan { opacity: 0; }

html.is-revealing .scene,
html.is-revealing .deck {
  animation: content-focus var(--reveal-duration) ease-out forwards;
}

/* Opacity only for these three: .scene-blur is supposed to STAY blurred (a
   filter animation would override its resting blur and leave it sharp), and
   stacking a blur on the nav would fight its own backdrop-filter. */
html.is-revealing .scene-blur,
html.is-revealing .nav,
html.is-revealing .latkan {
  animation: content-appear var(--reveal-duration) ease-out forwards;
}

@keyframes content-focus {
  from { opacity: 0; filter: blur(var(--blur-start)); }
  to   { opacity: 1; filter: blur(0); }
}

@keyframes content-appear {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* reduced motion, and any state after the sequence, just show it */
html.is-revealed .scene-blur,
html.is-revealed .scene,
html.is-revealed .deck,
html.is-revealed .nav,
html.is-revealed .latkan { opacity: 1; }

/* Fills the sides on wide screens with a soft blur of the same background. */
.scene-blur {
  position: fixed;
  inset: 0;
  z-index: 0;
  background-image: var(--bg-image);
  background-size: cover;
  background-position: center;
  filter: blur(var(--blur));
  transform: scale(var(--blur-scale));
}

/* The background itself: a portrait panel locked to the image's aspect, filled
   `cover` and centred. Taller than a phone is wide, so it overflows sideways
   and covers edge to edge — no bars. On a wide screen it's a centred panel and
   .scene-blur fills either side. */
.scene {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  height: 100svh;
  aspect-ratio: var(--stage-aspect);
  z-index: 1;
  background-image: var(--bg-image);
  background-size: cover;
  background-position: center;
}

/* ==========================================================================
   Card deck
   ========================================================================== */

/* The space a card is allowed to use: under the nav's measured bottom edge by
   --card-gap-below-nav, inside even side and bottom margins. It only frames —
   the box inside is what's actually sized. */
.deck-area {
  position: fixed;
  top: calc(var(--nav-bottom) + var(--card-gap-below-nav));
  left: calc(var(--card-side-margin) + env(safe-area-inset-left));
  right: calc(var(--card-side-margin) + env(safe-area-inset-right));
  bottom: calc(var(--card-bottom-margin) + env(safe-area-inset-bottom));
  z-index: 2;
  display: grid;
  place-items: center;

  /* The same rectangle as the four insets above, restated as plain lengths the
     box below can do arithmetic with. Keep the two in step. (--nav-bottom
     already carries the top safe-area inset.) */
  --card-area-w: calc(100vw - 2 * var(--card-side-margin)
                      - env(safe-area-inset-left) - env(safe-area-inset-right));
  --card-area-h: calc(100svh - var(--nav-bottom) - var(--card-gap-below-nav)
                      - var(--card-bottom-margin) - env(safe-area-inset-bottom));
}

/* ONE box, the same for every card, at the card art's own ratio and as large as
   that rectangle allows: width-led on a phone, height-led on a wide screen. The
   width is solved here rather than left to `max-height: 100%`, which does NOT
   reliably transfer the clamp back through aspect-ratio — unclamped, the box ran
   to nearly 3x the height available. The grid above centres it in what's left.
   Deliberately NOT clipped: a card parked at --offscreen is a full viewport
   height down, so the screen edge is what hides it and it stays visible the
   whole way up. */
.deck {
  position: relative;
  width: min(var(--card-area-w), var(--card-area-h) * var(--card-aspect));
  aspect-ratio: var(--card-aspect);
}

.card {
  position: absolute;
  inset: 0;
  transform: translateY(var(--offscreen));
  will-change: transform;
  transition: transform var(--deck-duration) var(--deck-ease);
}

.card.is-shown { transform: translateY(0); }

/* `contain` against a box that already matches the art's ratio: the card fills
   it edge to edge, with no letterbox and no stretching. `contain` rather than
   `cover` so a card that drifts a fraction off ratio letterboxes by a pixel
   instead of having its border cropped off.
   drop-shadow (not box-shadow) so it hugs the alpha edge of the art. */
.card-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
  filter: var(--shadow-rest);
  transition: filter var(--settle-ms) ease;
}

/* lifted while the card is in flight, so it reads as floating above the stack */
.card.is-lifted .card-art {
  filter: var(--shadow-lift);
  transition-duration: var(--deck-duration);
}

/* Inset by JS to the card art's own safe zone, so text never touches the art.
   `inset: 0` is the pre-measurement default: card 1 is a size container, which
   needs a definite size, and without this it would collapse to 0x0 until the
   art loads and take every cq-based font size down to its clamp minimum. */
/* The zone IS the layout box: a column that fills it and spreads its sections
   down the card, so nothing is left stranded in the middle of a tall zone.
   container-type makes cqh/cqi resolve against the zone, so every size below
   scales with the card rather than the viewport. */
.card-text {
  position: absolute;
  inset: 0;
  container-type: size;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  gap: clamp(6px, 3cqh, 26px);
  text-align: center;
}

/* Sized against BOTH axes — min() takes whichever runs out first, so the line
   fills the width on a wide zone without outgrowing a short one. */
.card-line {
  margin: 0;
  max-width: 100%;
  font-family: var(--font-active);
  font-size: clamp(0.85rem, min(8.8cqh, 7.9cqi), 1.7rem);
  line-height: 1.34;
  letter-spacing: 0.02em;
  color: inherit;
}

.card-line:first-child {
  font-size: clamp(1.3rem, min(17cqh, 17.5cqi), 3.4rem);
  line-height: 1.12;
  letter-spacing: 0.06em;
}

/* ==========================================================================
   Seal-red pill — shared by every card's call to action
   ========================================================================== */
.pill-cta {
  appearance: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.45em;
  border: 0;
  border-radius: var(--cta-radius);
  background-image: linear-gradient(170deg, var(--cta-bg) 0%, var(--cta-bg-deep) 100%);
  color: var(--cta-text);
  font-family: var(--font-times);
  font-weight: 700;
  letter-spacing: 0.03em;
  text-decoration: none;
  text-shadow: none;            /* the pill is not printed into the paper */
  box-shadow: 0 4px 12px rgba(94, 27, 46, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.16);
  transition: transform 160ms ease, box-shadow 160ms ease;
}

html[data-lang="hi"] .pill-cta { font-family: var(--font-devanagari); }

.pill-cta-icon {
  display: block;
  flex: 0 0 auto;
  width: 1.15em;
  height: 1.15em;
  color: var(--cta-text);
}

.pill-cta-label { display: block; white-space: nowrap; }

.pill-cta:hover,
.pill-cta:focus-visible { box-shadow: 0 6px 16px rgba(94, 27, 46, 0.42), inset 0 1px 0 rgba(255, 255, 255, 0.2); }
.pill-cta:active { transform: translateY(1px); box-shadow: 0 2px 8px rgba(94, 27, 46, 0.32); }

/* ==========================================================================
   Card 1 — names / save-the-date
   ========================================================================== */
/* Same shared-scale approach as the ceremony cards: vw sets the size so it is
   consistent card to card, and a cqh cap only bites when a zone is too short
   to hold it. The stencil names are additionally fitted in JS, since three
   words of different lengths share one size. */
.card-event {
  container-type: size;
  justify-content: space-between;
  gap: clamp(0px, 0.8cqh, 7px);
  color: var(--ink-text);
  text-shadow: 0 1px 0 var(--ink-highlight), 0 -1px 0 var(--ink-deboss);
}

.card-event > * { margin: 0; }

/* nowrap so scrollWidth reports each name's true width for the JS fit. */
.event-names {
  font-family: var(--font-stencil);
  font-weight: 700;
  font-size: clamp(1rem, min(11.4vw, 9.5cqh), 3.4rem);
  line-height: 1.08;
  letter-spacing: 0.02em;
  color: var(--brand-maroon);
}

.event-name-word { display: block; white-space: nowrap; }

/* Only the first and last name carry the new size; anything between them
   (the joining word) stays at the size it had. */
.event-name-word:not(:first-child):not(:last-child) { font-size: 0.84em; }

html[data-lang="hi"] .event-names {
  font-family: var(--font-devanagari);
  font-size: clamp(0.72rem, min(7.6vw, 6.4cqh), 2.2rem);
  line-height: 1.26;
}

.event-message {
  font-family: var(--font-times);
  font-style: italic;
  font-size: clamp(0.44rem, min(4.1vw, 3.35cqh), 1rem);
  line-height: 1.32;
  max-width: 94%;
}

html[data-lang="hi"] .event-message {
  font-family: var(--font-devanagari);
  font-style: normal;
  font-size: clamp(0.44rem, min(4vw, 3.3cqh), 0.98rem);
  line-height: 1.44;
}

.event-std {
  font-family: var(--font-flourish);
  font-size: clamp(0.85rem, min(10.7vw, 7.8cqh), 2.8rem);
  line-height: 1.08;
  color: var(--brand-maroon);
}

html[data-lang="hi"] .event-std {
  font-family: var(--font-devanagari);
  font-weight: 600;
  font-size: clamp(0.62rem, min(6.2vw, 5cqh), 1.5rem);
  line-height: 1.3;
}

/* "10. 02. 2027" over "Wednesday" — no time; the calendar event carries it. */
.event-when {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: var(--font-times);
  color: var(--ink-text);
}

html[data-lang="hi"] .event-when { font-family: var(--font-devanagari); }

.event-date-line {
  font-size: clamp(0.68rem, min(6.4vw, 4.4cqh), 1.7rem);
  font-weight: 700;
  line-height: 1.16;
  letter-spacing: 0.04em;
}

.event-weekday-line {
  font-size: clamp(0.52rem, min(4.8vw, 3.9cqh), 1.25rem);
  line-height: 1.2;
}

.event-cta {
  align-self: center;
  margin-bottom: clamp(2px, 1.2cqh, 8px);
  max-width: 60%;
  padding: clamp(4px, 1.5vw, 9px) clamp(7px, 3vw, 14px);
  font-size: clamp(0.4rem, min(4vw, 3cqh), 0.92rem);
}

/* ==========================================================================
   Card 2 — the formal invitation
   ========================================================================== */
/* Ten blocks inside the temple arch. Tinos for the body, Parisienne for the
   two names and the families line; Hindi takes the Devanagari serif for all
   of it. Same vw + cqh-cap scale as the other cards. */
.card-invite {
  container-type: size;
  /* Packed to the top rather than spread: with space-between the slack was
     being shared out between the groups, which is what put a wide gap under
     the mantra. Now the leftover sits at the bottom, after the families. */
  justify-content: flex-start;
  gap: clamp(2px, 1.1cqh, 9px);
  color: var(--ink-text);
  text-shadow: 0 1px 0 var(--ink-highlight), 0 -1px 0 var(--ink-deboss);
}

.card-invite > * { margin: 0; }

/* The card spreads these three apart; each keeps its own lines tight. That
   difference is what reads as the blank line between the marks, the message
   and the sign-off. */
.invite-marks,
.invite-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  gap: clamp(1px, 1cqh, 7px);
}

.invite-marks { gap: clamp(2px, 1.6cqh, 10px); }

.invite-group > p { margin: 0; }

/* Cropped to their painted pixels in JS, so these sizes are what you actually
   see rather than mostly transparent canvas. */
.invite-mark {
  display: block;
  width: auto;
  object-fit: contain;
}

.invite-ganesh { height: clamp(24px, min(18.7vw, 11.6cqh), 80px); max-width: 58%; }
.invite-mantra { width: clamp(46px, min(30vw, 58cqi), 128px); height: auto; }

.invite-intro {
  font-family: var(--font-times);
  font-size: clamp(0.42rem, min(4.27vw, 2.85cqh), 1.05rem);
  line-height: 1.26;
}

/* The two first names carry the card. */
.invite-person {
  font-family: var(--font-flourish);
  font-size: clamp(0.8rem, min(9.07vw, 5.8cqh), 2.3rem);
  line-height: 1.1;
}

.invite-parents {
  font-family: var(--font-times);
  font-size: clamp(0.4rem, min(4vw, 2.7cqh), 0.98rem);
  line-height: 1.26;
  max-width: 96%;
}

.invite-body {
  font-family: var(--font-times);
  font-size: clamp(0.42rem, min(4.27vw, 2.85cqh), 1.05rem);
  line-height: 1.3;
  max-width: 98%;
}

.invite-regards {
  font-family: var(--font-times);
  font-style: italic;
  font-size: clamp(0.4rem, min(4vw, 2.7cqh), 0.98rem);
  line-height: 1.24;
}

.invite-families {
  font-family: var(--font-flourish);
  font-size: clamp(0.68rem, min(8vw, 5.2cqh), 2rem);
  line-height: 1.12;
}

/* Devanagari has neither a script face nor a true italic here, so Hindi sets
   everything in the serif and drops the slant rather than synthesising one. */
html[data-lang="hi"] .invite-intro,
html[data-lang="hi"] .invite-parents,
html[data-lang="hi"] .invite-body,
html[data-lang="hi"] .invite-regards { font-family: var(--font-devanagari); line-height: 1.42; }

html[data-lang="hi"] .invite-regards { font-style: normal; }

html[data-lang="hi"] .invite-person {
  font-family: var(--font-devanagari);
  font-weight: 700;
  font-size: clamp(0.64rem, min(6.4vw, 5.2cqh), 1.6rem);
  line-height: 1.26;
}

html[data-lang="hi"] .invite-families {
  font-family: var(--font-devanagari);
  font-weight: 600;
  font-size: clamp(0.52rem, min(5.3vw, 3.7cqh), 1.3rem);
  line-height: 1.3;
}

/* ==========================================================================
   Nav
   ========================================================================== */
/* Frosted-glass bar floating over the deck. The opaque tint is the fallback;
   browsers with backdrop-filter get the lighter tint plus a real blur. */
.nav {
  position: fixed;
  top: calc(var(--nav-margin-top) + env(safe-area-inset-top));
  left: calc(var(--nav-margin-x) + env(safe-area-inset-left));
  right: calc(var(--nav-margin-x) + env(safe-area-inset-right));
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 12px;
  border-radius: var(--nav-radius);
  background: var(--nav-tint-solid);
  border: 1px solid var(--nav-border);
  box-shadow: var(--nav-shadow);
}

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav {
    background: var(--nav-tint);
    -webkit-backdrop-filter: blur(var(--nav-blur));
    backdrop-filter: blur(var(--nav-blur));
  }
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.brand-logo {
  display: block;
  height: var(--nav-logo-height);
  width: auto;
  object-fit: contain;
}

.brand-text {
  font-family: var(--font-active);
  font-size: clamp(1.05rem, 4.4vw, 1.45rem);
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--brand-maroon);
}

.nav-right {
  display: flex;
  align-items: center;
  gap: 10px;
}

.lang-pill {
  display: flex;
  align-items: center;
  padding: 2px;
  border: 1px solid var(--gold);
  border-radius: 999px;
  background: rgba(255, 252, 245, 0.9);
  overflow: hidden;
}

.lang-opt {
  appearance: none;
  border: 0;
  background: transparent;
  cursor: pointer;
  padding: 4px 11px;
  border-radius: 999px;
  font-family: var(--font-active);
  font-size: 0.82rem;
  letter-spacing: 0.06em;
  color: var(--brand-maroon);
  transition: opacity 200ms ease;
  opacity: 0.55;
}

.lang-opt.is-active {
  background: var(--brand-maroon);
  color: #fff;
  opacity: 1;
}

.hamburger {
  appearance: none;
  /* bare icon - no disc behind it, but the 38px box stays so the tap target
     is unchanged */
  width: 38px;
  height: 38px;
  border: 0;
  background: transparent;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 0;
}

.hamburger span {
  display: block;
  width: 16px;
  height: 1.5px;
  background: var(--brand-maroon);
  border-radius: 2px;
}

/* ==========================================================================
   Menu panel
   ========================================================================== */
.menu {
  position: fixed;
  top: 0;
  right: 0;
  z-index: 25;
  width: min(78vw, 320px);
  height: 100svh;
  padding: calc(104px + env(safe-area-inset-top)) 28px 28px;
  background: rgba(250, 243, 230, 0.97);
  box-shadow: -8px 0 30px rgba(60, 40, 20, 0.18);
  display: flex;
  flex-direction: column;
  gap: 22px;

  opacity: 0;
  transform: translateX(12%);
  will-change: transform, opacity;
  transition: opacity 280ms ease, transform 280ms var(--deck-ease);
}

.menu.is-open {
  opacity: 1;
  transform: translateX(0);
}

/* Deliberately quiet and un-tappable: wide tracking, small, gold, with a
   hairline under it so it reads as a mark rather than a first menu entry. The
   flex gap above .menu-items keeps it clear of them. */
.menu-tagline {
  margin: 0;
  padding-bottom: 0;
  font-family: var(--font-active);
  font-size: 0.78rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold);
  opacity: 0.9;
}

.menu-items {
  list-style: none;
  /* the gap that replaces the removed rule under the tagline */
  margin: 14px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.menu-link {
  appearance: none;
  width: 100%;
  border: 0;
  background: transparent;
  cursor: pointer;
  text-align: left;
  padding: 11px 0;
  font-family: var(--font-active);
  font-size: 1.12rem;
  letter-spacing: 0.04em;
  color: var(--color-ink);
  border-bottom: 1px solid rgba(184, 151, 63, 0.3);
}

.menu-link.is-current { color: var(--brand-maroon); }

.menu-social {
  display: flex;
  gap: 14px;
  margin-top: auto;
  /* Clears the play/pause button and the scroll hint, both of which are fixed
     over this corner — the row is measured off the button's own size. */
  margin-bottom: calc(var(--music-size, 68px) + 20px);
}

/* The panel's marks are the footer's, built by the same code — see
   buildSocialRow(). Only the size differs here. */
.menu-social .footer-social-item {
  width: 44px;
  height: 44px;
}

.menu-social .footer-social-item img,
.menu-social .footer-social-item svg {
  width: 26px;
  height: 26px;
}

/* ==========================================================================
   Latkan — decorative tassel hanging under the nav
   ========================================================================== */
/* The swing container. No width of its own — the tassel below sets it, so
   `right` lands the tassel's edge at rightOffset and `top center` pivots on
   the cord. Cord + tassel therefore swing as one piece.
   Every --latkan-* below is set on the ELEMENT, not on :root, so this one rule
   dresses both tassels and each keeps its own geometry and timing. */
.latkan {
  position: fixed;
  /* From the very top edge of the screen — the cord passes behind the nav's
     frosted bar (z-index 20 vs the nav's 30) rather than over its controls,
     so the lace reads as hanging from above without blocking a tap. */
  top: var(--latkan-top-offset);
  right: calc(var(--latkan-right) + env(safe-area-inset-right));
  /* above the scene and cards, below the nav, menu and music button */
  z-index: 20;
  pointer-events: none;
  transform-origin: top center;
  will-change: transform;
  animation: latkan-swing var(--latkan-duration) ease-in-out var(--latkan-delay) infinite;
}

/* The companion sits a layer back, so where they overlap the main one reads
   as the nearer of the two. */
.latkan-2 { z-index: 19; }

/* Runs from under the nav down to where the tassel head begins. */
.latkan-cord {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: var(--latkan-cord-width);
  height: var(--latkan-cord-length);
  background-image: var(--latkan-cord-image);
  background-size: 100% 100%;
  background-repeat: no-repeat;
}

/* In flow, pushed down by the cord's length, so the container's height is
   cord + tassel and its width is the tassel's. */
.latkan-tassel {
  display: block;
  margin-top: var(--latkan-cord-length);
  height: var(--latkan-tassel-height);
  width: auto;
}

@keyframes latkan-swing {
  0%, 100% { transform: rotate(calc(var(--latkan-swing) * -1)); }
  50%      { transform: rotate(var(--latkan-swing)); }
}

html.reduced-motion .latkan { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .latkan { animation: none; }
}

/* ==========================================================================
   Music button — persistent, above everything
   ========================================================================== */
/* Size, ring and colours all come from CONFIG.musicButton. box-sizing is
   border-box globally, so the gold ring is drawn inside --music-size and that
   stays the button's true outer diameter. */
.music-btn {
  position: fixed;
  right: max(16px, env(safe-area-inset-right));
  bottom: max(16px, env(safe-area-inset-bottom));
  z-index: 10000;
  width: var(--music-size);
  height: var(--music-size);
  border: var(--music-border-width) solid var(--music-border-color);
  border-radius: 50%;
  background: var(--music-fill);
  color: var(--music-icon);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 6px 18px rgba(60, 25, 40, 0.38);
  /* Nothing sits over the envelope: the button fades in with card 1 and
     stays from then on — see showMusicButton(). */
  opacity: 0;
  transition: opacity 420ms ease-out;
}

.music-btn.is-shown { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .music-btn { transition: none; }
}

/* The markup carries width/height attributes; CSS beats those, so the glyph
   scales with the button instead of staying at its authored 20px. */
.music-btn .icon {
  width: calc(var(--music-size) * var(--music-icon-scale));
  height: calc(var(--music-size) * var(--music-icon-scale));
}

.music-btn .icon-pause { display: none; }
.music-btn.is-playing .icon-play { display: none; }
.music-btn.is-playing .icon-pause { display: block; }

/* ==========================================================================
   Envelope opening screen
   ========================================================================== */
.envelope-screen {
  position: fixed;
  inset: 0;
  z-index: 9999;
  overflow: hidden;
  /* transparent: the deck sits behind and is what the peeling flaps reveal */
  background: transparent;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  perspective: var(--envelope-perspective);
  perspective-origin: center;
}

/* Plain cream behind the flaps — all you see while the envelope opens, and
   it also covers the hairline gaps along the traced seams. It holds alone for
   a beat, then fades to reveal the content underneath. */
.envelope-inner {
  position: absolute;
  inset: 0;
  z-index: 1;
  background-image: var(--envelope-inner-image);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  will-change: opacity;
  transition: opacity var(--reveal-duration) var(--envelope-ease) var(--reveal-delay);
}

html.is-opening .envelope-inner { opacity: 0; }

/* Full-bleed copy of the SAME artwork the flaps are cut from, sitting right
   behind them. While closed, a hairline gap between flaps therefore shows the
   identical floral paper instead of the cream beneath — which is what was
   reading as a torn streak. Clears as soon as the flaps start to lift. */
.envelope-seam {
  position: absolute;
  inset: 0;
  z-index: 2;
  background-image: var(--envelope-image);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  will-change: opacity;
  transition: opacity var(--seam-fade) linear;
}

html.is-opening .envelope-seam,
html.is-opening-reduced .envelope-seam { opacity: 0; }

html.is-opening-reduced .envelope-inner {
  opacity: 0;
  transition: opacity var(--reduced-duration) var(--envelope-ease);
}

/* The four flaps all paint the seal-less base at the same size, so together
   they reconstruct it exactly. They differ only in how they're clipped and
   which way they swing open. No mask — every tip is a clean, sharp point. */
.envelope-flap,
.envelope-seal {
  position: absolute;
  inset: 0;
  background-position: center;
  background-repeat: no-repeat;
  pointer-events: none;
  will-change: transform, opacity, filter;
  /* --flap-start / --flap-fade-delay are set per flap, so each one lifts on
     its own beat; the shadow grows with the rotation and fades out with it. */
  transition: transform var(--flap-duration) var(--envelope-ease) var(--flap-start),
              filter var(--flap-duration) var(--envelope-ease) var(--flap-start),
              opacity var(--flap-fade-duration) linear var(--flap-fade-delay);
}

/* clip-path for all four flaps is built from CONFIG.envelope.flaps at runtime */
.envelope-flap {
  z-index: 3;
  background-image: var(--envelope-image);
  background-size: cover;
  transform-style: preserve-3d;
  filter: var(--shadow-flat);
}

.envelope-flap-top    { transform-origin: top center; }
.envelope-flap-bottom { transform-origin: bottom center; }
.envelope-flap-left   { transform-origin: left center; }
.envelope-flap-right  { transform-origin: right center; }

/* The wax seal: its own transparent image, never clipped, always on top.
   Authored at the same aspect as the base, so `cover` drops it straight onto
   the seam. Shares the left flap's origin and timing, so it rides out on the
   left flap's arc, intact. */
.envelope-seal {
  z-index: 4;
  background-image: var(--seal-image);
  background-size: var(--seal-size);
  background-position: var(--seal-position);
  transform-origin: left center;
  /* the seal is stuck to the flap's printed face, so it goes away with it
     once we're looking at the ivory underside */
  backface-visibility: hidden;
}

.envelope-touch-text {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 8%;
  z-index: 5;
  margin: 0;
  padding: 0 6vw;
  text-align: center;
  font-family: var(--font-script);
  /* Starting size only — fitTouchText() steps it down until the line fits,
     so a longer invitation wording never wraps onto a second line. */
  font-size: clamp(2rem, 9vw, 3.25rem);
  font-weight: 700;
  white-space: nowrap;
  line-height: 1.12;
  color: #000;
  will-change: transform, opacity;
  animation: envelope-float 2.4s ease-in-out infinite;
}

/* Two lines, each fitted on its own in JS — see fitTouchText(). */
.envelope-touch-line { display: block; }

@keyframes envelope-float {
  0%, 100% { transform: translateY(0); opacity: 0.82; }
  50%      { transform: translateY(-8px); opacity: 1; }
}

/* --- opening sequence (full motion) ---
   Each flap hinges on its outer edge and lifts its free edge toward the
   viewer, casting a shadow onto the paper behind, then fades away. */
html.is-opening .envelope-flap-top    { transform: rotateX(var(--flap-angle)); }
html.is-opening .envelope-flap-bottom { transform: rotateX(var(--flap-angle-neg)); }
html.is-opening .envelope-flap-right  { transform: rotateY(var(--flap-angle)); }

html.is-opening .envelope-flap-left,
html.is-opening .envelope-seal        { transform: rotateY(var(--flap-angle-neg)); }

html.is-opening .envelope-flap,
html.is-opening .envelope-seal { opacity: 0; }

/* shadow is cast from the edge each flap lifts away from */
html.is-opening .envelope-flap-left   { filter: var(--shadow-left); }
html.is-opening .envelope-flap-right  { filter: var(--shadow-right); }
html.is-opening .envelope-flap-top    { filter: var(--shadow-top); }
html.is-opening .envelope-flap-bottom { filter: var(--shadow-bottom); }

html.is-opening .envelope-touch-text {
  animation: none;
  opacity: 0;
  transition: opacity var(--text-fade-duration) var(--envelope-ease);
}

/* --- reduced motion: cross-fade straight through, no 3D motion --- */
html.is-opening-reduced .envelope-screen {
  transition: opacity var(--reduced-duration) var(--envelope-ease);
  transition-delay: 0ms;
  opacity: 0;
  pointer-events: none;
}

/* Deck swaps become a quick fade. Cards stack by index, so opacity alone
   puts the right one on top. The shadow stays at rest — no lift. */
html.reduced-motion .card {
  transform: none;
  opacity: 0;
  transition: opacity var(--reduced-duration) ease;
}

html.reduced-motion .card.is-shown { opacity: 1; }

html.reduced-motion .card-art,
html.reduced-motion .card.is-lifted .card-art {
  filter: var(--shadow-rest);
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  .envelope-touch-text { animation: none; }
  .menu { transition-duration: 1ms; }
}

/* Devanagari sets wider than Latin for the same line, so the simple cards'
   subtitle runs a step smaller in Hindi to stay on one line. */
html[data-lang="hi"] .card-line {
  font-size: clamp(0.74rem, min(7.6cqh, 6.6cqi), 1.48rem);
}

html[data-lang="hi"] .card-line:first-child {
  font-size: clamp(1.14rem, min(14.2cqh, 15.2cqi), 2.85rem);
}

/* Cards whose whole content is one paragraph (the closing messages) have no
   second section to spread against, so they lean on size alone. Capped by the
   zone WIDTH: past this the lines break every few words and stop reading as
   a sentence, which is why these cards fill less of their zone than the rest. */
.card-line:only-child {
  font-size: clamp(0.92rem, min(11cqh, 16.2cqi), 2.4rem);
  line-height: 1.26;
}

html[data-lang="hi"] .card-line:only-child {
  font-size: clamp(0.9rem, min(11.5cqh, 15.5cqi), 2.1rem);
  line-height: 1.42;
}

/* ==========================================================================
   Scroll hint — persistent bottom-centre wayfinding
   ========================================================================== */
/* Deliberately NOT tied to any fade: it appears with the deck and stays at
   full opacity for the whole session. Only the chevron moves. */
.scroll-hint {
  position: fixed;
  left: 50%;
  bottom: calc(10px + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  z-index: 25;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 7px 16px 5px;
  border-radius: 999px;
  pointer-events: none;
  color: var(--brand-maroon);
  background: rgba(255, 252, 245, 0.72);
  box-shadow: 0 3px 12px rgba(60, 30, 20, 0.16);
}

@supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .scroll-hint {
    background: rgba(255, 252, 245, 0.46);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
  }
}

.scroll-hint-text {
  font-family: var(--font-active);
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  line-height: 1.1;
  white-space: nowrap;
}

/* transform only — no opacity in the keyframes, so it can never read as
   fading out mid-cycle. */
.scroll-hint-chevron {
  display: block;
  will-change: transform;
  animation: scroll-hint-bob 1.6s ease-in-out infinite;
}

@keyframes scroll-hint-bob {
  0%, 100% { transform: translateY(-2px); }
  50%      { transform: translateY(3px); }
}

html.reduced-motion .scroll-hint-chevron { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .scroll-hint-chevron { animation: none; }
}

/* ==========================================================================
   Card text arrival — rise + unblur, the first time a card is seen
   ========================================================================== */
/* Each line or section of a card's text runs this once, one stagger step
   behind the one above it. transform, opacity and filter only; will-change
   is carried by the class and leaves with it when the run ends.
   The time comes from CONFIG.cardText, the distances live here. */
/* Text that has not been seen yet is built already in its from-state, so a
   card never paints its words at full strength and then hides them. The
   class is swapped for .is-arriving the moment the animation should run. */
.card-text > .is-pending {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  filter: blur(7px);
}

.card-text > .is-arriving {
  animation: card-text-rise var(--card-text-duration, 1000ms)
             var(--card-text-ease, cubic-bezier(.22, 1, .36, 1)) backwards;
  will-change: transform, opacity, filter;
}

@keyframes card-text-rise {
  from {
    opacity: 0;
    transform: translate3d(0, 14px, 0);
    filter: blur(7px);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
    filter: blur(0);
  }
}

html.reduced-motion .card-text > .is-arriving { animation: none; }

@media (prefers-reduced-motion: reduce) {
  .card-text > .is-arriving { animation: none; }
}

/* ==========================================================================
   Cards 3–6 — the ceremony cards
   ========================================================================== */
/* Sizes here are in vw, not cq, deliberately: the four zones differ in width
   (the maroon card is the widest), and cq units would give each card its own
   type size. vw keeps the message, labels and values identical across all
   four while still scaling per phone. Only the stencil word is per-card, and
   that is fitted in JS because word lengths differ so much. */
.card-ceremony {
  container-type: size;
  justify-content: space-between;
  gap: clamp(0px, 0.6cqh, 6px);
  color: var(--ceremony-ink);
}

.card-ceremony > * { margin: 0; }

.card-ceremony.is-light { color: var(--ceremony-light-ink); }

.ceremony-heading { width: 100%; }

/* nowrap so scrollWidth reports the word's true width for the JS fit, and so
   a long word overflows measurably instead of silently breaking. */
.ceremony-word {
  margin: 0;
  font-family: var(--font-stencil);
  font-weight: 700;
  font-size: clamp(0.8rem, min(7.6vw, 8.8cqh), 2.6rem);
  line-height: 1.04;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.ceremony-sub {
  margin: 0;
  font-family: var(--font-flourish);
  font-size: clamp(0.7rem, min(6vw, 7cqh), 2rem);
  line-height: 1.1;
}

/* Devanagari has neither a stencil nor a script face loaded, so Hindi sets
   both heading lines in the serif rather than falling back to something
   mismatched. */
html[data-lang="hi"] .ceremony-word {
  font-family: var(--font-devanagari);
  font-weight: 700;
  font-size: clamp(0.68rem, min(6vw, 7cqh), 1.9rem);
  line-height: 1.24;
}

html[data-lang="hi"] .ceremony-sub {
  font-family: var(--font-devanagari);
  font-weight: 500;
  font-size: clamp(0.6rem, min(4.6vw, 5.4cqh), 1.45rem);
  line-height: 1.28;
}

.ceremony-message {
  font-family: var(--font-times);
  font-style: italic;
  font-weight: 700;
  font-size: clamp(0.42rem, min(2.85vw, 3.4cqh), 0.95rem);
  line-height: 1.3;
  max-width: 100%;
}

html[data-lang="hi"] .ceremony-message {
  font-family: var(--font-devanagari);
  font-style: normal;          /* Devanagari has no true italic to synthesise */
  font-weight: 500;
  font-size: clamp(0.4rem, min(2.6vw, 3.1cqh), 0.86rem);
  line-height: 1.44;
}

/* Date and Venue: a bold-italic label, then italic values each on their own
   line, exactly as the mockup sets them. */
.ceremony-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: var(--font-times);
  font-size: clamp(0.4rem, min(2.72vw, 3.2cqh), 0.9rem);
  line-height: 1.26;
}

.ceremony-label {
  font-style: italic;
  font-weight: 700;
}

.ceremony-value { font-style: italic; }

.card-ceremony.is-light .ceremony-label { color: var(--ceremony-light-accent); }

html[data-lang="hi"] .ceremony-block { font-family: var(--font-devanagari); }
html[data-lang="hi"] .ceremony-label,
html[data-lang="hi"] .ceremony-value { font-style: normal; }

/* Addresses stay in the Latin face even in Hindi — they aren't translated. */
html[data-lang="hi"] .ceremony-block .ceremony-value { font-family: var(--font-times); }
html[data-lang="hi"] .ceremony-block .ceremony-value:lang(hi) { font-family: var(--font-devanagari); }

.ceremony-cta {
  align-self: center;
  margin-bottom: 0;
  max-width: 88%;
  padding: clamp(4px, 1.5vw, 9px) clamp(7px, 3vw, 15px);
  font-size: clamp(0.4rem, min(2.7vw, 2.9cqh), 0.88rem);
  text-decoration: none;
}

/* ==========================================================================
   Ornamental rules — the uploaded divider art
   ========================================================================== */
/* Cropped to their painted pixels in JS, so the widths below are what you
   actually see. No tint on the maroon card: the art's ink measures
   rgb(200, 165, 120), which already reads as gold against it. */
.card-rule {
  display: block;
  line-height: 0;
}

.card-rule img {
  display: block;
  width: 100%;
  height: auto;
}

.rule-event-top     { width: 84%; }
.rule-event-bottom  { width: 84%; }
.rule-ceremony-top  { width: 88%; }
.rule-ceremony-mid  { width: 76%; }
.rule-ceremony-cta  { width: 62%; }

/* Card 8: generous air on BOTH sides of the rule, so it reads as a break
   between the names and the countdown rather than a line glued to either. */
/* Scoped to the card so it outranks the `.card-finale > * { margin: 0 }`
   reset, which is declared later in the file and was flattening this. */
.card-finale .rule-finale {
  width: 72%;
  margin-top: clamp(14px, 7cqh, 46px);
}

/* ==========================================================================
   Card 7 — Our Story: heading, the photo deck, the story
   ========================================================================== */
/* Packed to the top, like the invitation: the leftover room gathers under the
   story text rather than being shared out between the three parts. */
.card-story {
  container-type: size;
  justify-content: flex-start;
  gap: clamp(2px, 1.2cqh, 10px);
  color: var(--ink-text);
}

.card-story > * { margin: 0; }

/* No story written yet: nothing is reserved below, and the heading and deck
   sit centred in the card instead of being pushed to the top. */
.card-story.is-deck-only { justify-content: center; }
.card-story.is-deck-only .story-text { display: none; }

.story-heading {
  font-family: var(--font-script);
  line-height: 1.1;
  font-size: clamp(0.9rem, min(10.5vw, 9cqh), 3rem);
  color: var(--brand-maroon);
}

/* The deck's height is budgeted in cqh and the WIDTH is solved from it, so the
   3:4 frame can never grow past the zone on a short card — the same trick the
   card box itself uses. min() with 100% keeps it inside a narrow zone too. */
.photo-deck {
  flex: 0 0 auto;
  display: flex;
  justify-content: center;
  width: 100%;
}

.photo-stack {
  position: relative;
  width: min(100%, 43cqh);
  aspect-ratio: 3 / 4;
  /* Horizontal gestures are the deck's; vertical ones stay the page's. */
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
  outline: none;
}

.photo-stack:focus-visible { box-shadow: 0 0 0 2px var(--brand-maroon); border-radius: 8px; }

/* A print in a white border. Only transform and opacity ever animate. */
.photo-frame {
  position: absolute;
  inset: 0;
  padding: 3.2%;
  border-radius: 7px;
  background: #fffdf8;
  box-shadow: 0 6px 18px rgba(60, 30, 20, 0.26);
  transform-origin: 50% 85%;
  transition: transform 450ms cubic-bezier(.22, 1, .36, 1), opacity 450ms linear;
  will-change: transform, opacity;
  backface-visibility: hidden;
}

.photo-frame.is-front { cursor: grab; }
.photo-stack.is-dragging .photo-frame.is-front { cursor: grabbing; }

.photo-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 4px;
  -webkit-user-drag: none;
  pointer-events: none;
}

/* Shown when a photo file is missing: a neutral frame, never a broken image. */
.photo-frame.is-placeholder .photo-img { display: none; }

.photo-frame.is-placeholder::after {
  content: "";
  position: absolute;
  inset: 3.2%;
  border-radius: 4px;
  background: #efe6d6;
  box-shadow: inset 0 0 0 1px rgba(94, 27, 46, 0.18);
}

.story-controls {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(8px, 4cqi, 20px);
  margin-top: clamp(3px, 1.6cqh, 12px);
  color: var(--brand-maroon);
}

.story-nav {
  appearance: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  display: block;
  width: clamp(14px, 4.4cqh, 26px);
  height: clamp(14px, 4.4cqh, 26px);
  background: none;
  color: inherit;
  opacity: 0.72;
  transition: opacity 160ms linear, transform 160ms ease-out;
}

.story-nav:hover,
.story-nav:focus-visible { opacity: 1; }
.story-nav:active { transform: scale(0.9); }
.story-next { transform: rotate(180deg); }
.story-next:active { transform: rotate(180deg) scale(0.9); }

.story-counter {
  font-family: var(--font-times);
  font-size: clamp(0.42rem, min(3vw, 2.9cqh), 0.9rem);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.08em;
  opacity: 0.8;
}

/* Room is reserved whether or not there are words in it yet, so adding the
   story later does not move the deck. */
.story-text {
  flex: 0 0 auto;
  min-height: calc(12cqh * var(--style-fit, 1));
  max-width: 100%;
  font-family: var(--font-times);
  font-size: clamp(0.4rem, min(4.3vw, 3.2cqh), 1.05rem);
  line-height: 1.38;
}

html[data-lang="hi"] .story-heading,
html[data-lang="hi"] .story-text { font-family: var(--font-devanagari); }
html[data-lang="hi"] .story-heading { font-weight: 700; }
html[data-lang="hi"] .story-text { line-height: 1.5; }

/* ==========================================================================
   Card 8 — the closing card and its countdown
   ========================================================================== */
.card-finale {
  container-type: size;
  /* centred and tight: the brief asks for no large gaps, so the column is
     sized to its content instead of being spread across the zone */
  justify-content: center;
  gap: clamp(1px, 0.9cqh, 7px);
  color: var(--ink-text);
  text-shadow: 0 1px 0 var(--ink-highlight), 0 -1px 0 var(--ink-deboss);
}

.card-finale > * { margin: 0; }

/* Two tight groups, spread apart — same device as card 2, and what reads as
   the blank line before the sign-off. */
.card-finale .finale-group + .finale-group { margin-top: clamp(6px, 2.6cqh, 18px); }

.finale-group {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  gap: clamp(0px, 0.4cqh, 4px);
}

.finale-group > p { margin: 0; }

.finale-line {
  font-family: var(--font-times);
  font-size: clamp(0.5rem, min(4.1vw, 3.6cqh), 1.05rem);
  line-height: 1.34;
}

.finale-signoff {
  font-family: var(--font-times);
  font-size: clamp(0.46rem, min(3.8vw, 3.3cqh), 0.98rem);
  line-height: 1.26;
}

.finale-signname {
  font-family: var(--font-flourish);
  font-size: clamp(0.9rem, min(8.4vw, 6.6cqh), 2.2rem);
  line-height: 1.12;
  color: var(--brand-maroon);
}

.finale-countdown-title {
  margin-top: clamp(14px, 7cqh, 46px);
  font-family: var(--font-stencil);
  font-weight: 700;
  font-size: clamp(0.6rem, min(5.2vw, 4.6cqh), 1.3rem);
  line-height: 1.14;
  letter-spacing: 0.04em;
  white-space: nowrap;
  color: var(--brand-maroon);
}

html[data-lang="hi"] .finale-line,
html[data-lang="hi"] .finale-signoff { font-family: var(--font-devanagari); line-height: 1.44; }

html[data-lang="hi"] .finale-signname,
html[data-lang="hi"] .finale-countdown-title {
  font-family: var(--font-devanagari);
  font-weight: 700;
  white-space: normal;
}

html[data-lang="hi"] .finale-signname { font-size: clamp(0.7rem, min(6vw, 5cqh), 1.6rem); }
html[data-lang="hi"] .finale-countdown-title { font-size: clamp(0.56rem, min(4.6vw, 4cqh), 1.15rem); }

/* Cream fill, gold hairline, soft shadow — it belongs to the printed card,
   not to a banner dropped on top of it. */
.countdown {
  margin-top: clamp(1px, 0.5cqh, 5px);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: clamp(1px, 0.8cqi, 5px);
  padding: clamp(4px, 1.6cqh, 11px) clamp(5px, 2.4cqi, 14px);
  border: 1px solid var(--countdown-border);
  border-radius: clamp(6px, 2.4cqi, 14px);
  /* transparent on purpose - the printed card shows through; the hairline
     alone is what makes it read as a box */
  background: transparent;
}

.countdown-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 0;
  padding: 0 clamp(1px, 1cqi, 6px);
}

.countdown-value {
  font-family: var(--font-times);
  font-weight: 700;
  font-size: clamp(0.8rem, min(6.6vw, 6cqh), 1.8rem);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;   /* so the digits don't jitter each tick */
  color: var(--brand-maroon);
}

.countdown-label {
  font-family: var(--font-times);
  font-size: clamp(0.34rem, min(2.5vw, 2.3cqh), 0.68rem);
  line-height: 1.2;
  letter-spacing: 0.04em;
  color: var(--ink-text);
}

html[data-lang="hi"] .countdown-label { font-family: var(--font-devanagari); }

.countdown-colon {
  align-self: flex-start;
  font-family: var(--font-times);
  font-weight: 700;
  font-size: clamp(0.7rem, min(5.4vw, 5cqh), 1.5rem);
  line-height: 1.25;
  color: var(--countdown-colon);
}

/* ==========================================================================
   Confetti
   ========================================================================== */
.confetti {
  position: fixed;
  inset: 0;
  z-index: 9998;          /* over everything except the envelope overlay */
  pointer-events: none;
}

/* ==========================================================================
   Footer — the one section in normal flow
   ========================================================================== */
/* A full viewport of top margin is what makes the document scrollable at all:
   everything else on the page is fixed. The footer therefore rises OVER the
   deck as you scroll, which is why it needs an opaque background and a
   z-index above the deck but below the nav. */
.site-footer {
  position: relative;
  z-index: 22;
  margin-top: 100svh;
  /* Fills the screen it scrolls into - at 325px tall the deck reappeared
     below it at full scroll, since the scroll range is a whole viewport. */
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 34px 24px calc(30px + env(safe-area-inset-bottom));
  text-align: center;
  background-image: var(--bg-image);
  background-size: cover;
  background-position: center;
  border-top: 1px solid rgba(184, 151, 63, 0.4);
  box-shadow: 0 -10px 30px rgba(60, 30, 20, 0.12);
}

.footer-brand {
  display: flex;
  align-items: center;
  gap: 9px;
}

.footer-logo { display: block; height: 34px; width: auto; }

.footer-name {
  font-family: var(--font-active);
  font-size: 1.3rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--brand-maroon);
}

.footer-tagline {
  margin: 0;
  font-family: var(--font-active);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold);
}

.footer-description {
  margin: 0 0 6px;
  max-width: 34rem;
  font-family: var(--font-active);
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--color-ink);
}

.footer-follow {
  /* clear air after the description, so the label starts a new block */
  margin: 22px 0 0;
  font-family: var(--font-active);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.14em;
    color: var(--brand-maroon);
}

.footer-social {
  display: flex;
  align-items: center;
  gap: 16px;
}

.footer-social-item {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  color: var(--brand-maroon);
  background: rgba(255, 252, 245, 0.9);
  border: 1px solid rgba(184, 151, 63, 0.45);
  box-shadow: 0 3px 10px rgba(60, 30, 20, 0.12);
}

/* contain, not a forced square: the Gmail mark is 373x277 and would be
   squashed by a fixed 22x22 box. */
.footer-social-item img,
.footer-social-item svg {
  display: block;
  width: 24px;
  height: 24px;
  object-fit: contain;
}

.footer-copyright {
  margin: 8px 0 0;
  font-family: var(--font-active);
  font-size: 0.74rem;
  letter-spacing: 0.03em;
  color: rgba(58, 46, 34, 0.75);
}
