/* ==========================================================================
   main.css — design tokens, layout and components
   --------------------------------------------------------------------------
   Motion lives in animations.css. This file owns everything static: colour,
   type, spacing, structure and component surfaces.

   Contents
     01. Design tokens
     02. Base & resets
     03. Utilities
     04. Ambient background stack
     05. Buttons
     06. Section scaffolding
     07. Hero
     08. Countdown
     09. Gallery + lightbox
     10. Invitation card
     11. RSVP
     12. Modal
     13. Chrome (nav, progress bar, music toggle, footer)
     14. Responsive refinements
   ========================================================================== */


/* ==========================================================================
   01. DESIGN TOKENS
   ========================================================================== */
:root {
  /* ── Core palette ─────────────────────────────────────────────────────
     A midnight-blue night sky, warmed by sunset pink and rose gold,
     with real gold reserved for edges, rules and small accents.          */
  --midnight:      #0a0a1f;
  --midnight-2:    #141134;
  --plum:          #2b1b45;

  --gold:          #d4af37;
  --gold-soft:     #f3d998;
  --gold-deep:     #a8842a;

  --rose:          #b76e79;
  --rose-gold:     #e8b4a0;
  --sunset:        #ff8fab;
  --blush:         #ffd6e0;
  --cream:         #fdf6ee;

  /* ── Semantic aliases ─────────────────────────────────────────────── */
  --bg:            var(--midnight);
  --text:          var(--cream);
  --text-muted:    rgba(253, 246, 238, 0.66);
  --text-faint:    rgba(253, 246, 238, 0.42);

  /* ── Surfaces ─────────────────────────────────────────────────────── */
  --surface:       rgba(255, 255, 255, 0.045);
  --surface-hi:    rgba(255, 255, 255, 0.085);
  --hairline:      rgba(212, 175, 55, 0.28);
  --hairline-soft: rgba(253, 246, 238, 0.12);

  /* ── Gradients ────────────────────────────────────────────────────── */
  --grad-gold:   linear-gradient(115deg, var(--gold-deep) 0%, var(--gold-soft) 38%, var(--gold) 62%, var(--gold-soft) 100%);
  --grad-rose:   linear-gradient(115deg, var(--rose) 0%, var(--sunset) 45%, var(--rose-gold) 100%);
  --grad-dusk:   linear-gradient(160deg, var(--midnight) 0%, var(--midnight-2) 45%, var(--plum) 100%);

  /* ── Elevation ────────────────────────────────────────────────────── */
  --shadow-sm:   0 2px 10px rgba(0, 0, 0, 0.35);
  --shadow-md:   0 12px 34px rgba(0, 0, 0, 0.45);
  --shadow-lg:   0 26px 70px rgba(0, 0, 0, 0.55);
  --glow-rose:   0 0 34px rgba(255, 143, 171, 0.42);
  --glow-gold:   0 0 30px rgba(212, 175, 55, 0.38);

  /* ── Type ─────────────────────────────────────────────────────────── */
  --font-display: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --font-script:  "Great Vibes", "Segoe Script", cursive;
  --font-body:    Inter, system-ui, -apple-system, "Segoe UI", sans-serif;

  /* ── Rhythm ───────────────────────────────────────────────────────── */
  --gutter:       clamp(1.25rem, 4vw, 3rem);
  --section-pad:  clamp(4.5rem, 11vh, 9rem);
  --radius-sm:    10px;
  --radius:       18px;
  --radius-lg:    26px;

  /* ── Motion ───────────────────────────────────────────────────────── */
  --ease-out:  cubic-bezier(0.22, 1, 0.36, 1);
  --ease-soft: cubic-bezier(0.4, 0, 0.2, 1);
  --dur-fast:  180ms;
  --dur:       420ms;
  --dur-slow:  900ms;
}


/* ==========================================================================
   02. BASE & RESETS
   ========================================================================== */
*,
*::before,
*::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  /* Anchor jumps stop clear of the fixed nav. */
  scroll-padding-top: 5rem;
  /* Stop iOS from inflating type in landscape. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* `clip` rather than `hidden`: hidden makes the element a scroll container,
     which is exactly the quirk that lets iOS Safari pan sideways anyway.
     `hidden` first as the fallback for browsers without `clip`. */
  overflow-x: hidden;
  overflow-x: clip;
}

body {
  margin: 0;
  min-height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-weight: 300;
  line-height: 1.65;
  overflow-x: hidden;
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* While the story is sealed, the hero owns the viewport and the page
   must not scroll. Applied by JS only — see js/main.js. */
body.is-sealed { overflow: hidden; height: 100%; }

h1, h2, h3 {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: 0.01em;
  line-height: 1.12;
  margin: 0;
  text-wrap: balance;
}

p { margin: 0; text-wrap: pretty; }

img {
  display: block;
  max-width: 100%;
  height: auto;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}

a { color: inherit; text-decoration: none; }

code {
  font-family: ui-monospace, "SFMono-Regular", "Cascadia Code", Consolas, monospace;
  font-size: 0.86em;
  padding: 0.1em 0.42em;
  border-radius: 5px;
  background: rgba(212, 175, 55, 0.12);
  border: 1px solid rgba(212, 175, 55, 0.22);
  color: var(--gold-soft);
}

/* A single, consistent focus ring across every interactive element. */
:focus-visible {
  outline: 2px solid var(--gold-soft);
  outline-offset: 3px;
  border-radius: 6px;
}

::selection {
  background: rgba(255, 143, 171, 0.35);
  color: var(--cream);
}

/* Slim, on-theme scrollbar (WebKit/Blink; harmless elsewhere). */
::-webkit-scrollbar { width: 9px; }
::-webkit-scrollbar-track { background: var(--midnight); }
::-webkit-scrollbar-thumb {
  background: linear-gradient(var(--rose), var(--gold-deep));
  border-radius: 9px;
}


/* ==========================================================================
   03. UTILITIES
   ========================================================================== */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: fixed;
  top: 0.75rem;
  left: 50%;
  z-index: 200;
  /* 44px minimum so it is a real tap target, not just a keyboard one. */
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  padding: 0.6rem 1.2rem;
  border-radius: 999px;
  background: var(--gold);
  color: var(--midnight);
  font-weight: 600;
  font-size: 0.85rem;
  transform: translate(-50%, -220%);
  transition: transform var(--dur) var(--ease-out);
}
.skip-link:focus-visible { transform: translate(-50%, 0); }

/* Gold foil text treatment. The travelling highlight lives in animations.css. */
.shimmer {
  background: linear-gradient(
    100deg,
    var(--rose-gold) 0%,
    var(--gold-soft) 22%,
    #fffaf0 38%,
    var(--gold) 55%,
    var(--sunset) 78%,
    var(--rose-gold) 100%
  );
  background-size: 260% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

.script-accent {
  font-family: var(--font-script);
  font-size: clamp(1.7rem, 4.5vw, 2.6rem);
  line-height: 1.2;
  color: var(--rose-gold);
  text-shadow: 0 0 26px rgba(232, 180, 160, 0.35);
}


/* ==========================================================================
   04. AMBIENT BACKGROUND STACK
   Fixed behind everything, never interactive.
   ========================================================================== */
.bg-stack {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

/* Layer 1 — slow drifting colour washes over the midnight base. */
.bg-aurora {
  position: absolute;
  inset: -25%;
  background:
    radial-gradient(38% 42% at 18% 22%, rgba(183, 110, 121, 0.42), transparent 65%),
    radial-gradient(34% 38% at 82% 18%, rgba(255, 143, 171, 0.30), transparent 68%),
    radial-gradient(46% 50% at 72% 82%, rgba(43, 27, 69, 0.85),  transparent 70%),
    radial-gradient(40% 44% at 24% 84%, rgba(212, 175, 55, 0.16), transparent 70%),
    var(--grad-dusk);
  filter: blur(18px) saturate(115%);
}

/* Layer 2 — faint film grain so the large gradients never band. */
.bg-grain {
  position: absolute;
  inset: 0;
  opacity: 0.035;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Layer 3 — particle canvas (hearts & stars), sized in js/particles.js. */
#particle-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* Layer 4 — balloon stage. Children are injected by js/balloons.js. */
.balloon-layer { position: absolute; inset: 0; }

/* Layer 5 — vignette keeps the edges dark so text stays legible. */
.bg-vignette {
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 90% at 50% 45%, transparent 42%, rgba(5, 5, 16, 0.72) 100%);
}

/* ── The balloon itself ─────────────────────────────────────────────────
   Three nested elements so three transforms can run without fighting:
     .balloon        → vertical rise      (translateY)
     .balloon__sway  → horizontal drift   (translateX)
     .balloon__body  → gentle bob/tilt    (rotate)                        */
.balloon {
  position: absolute;
  bottom: -28vh;
  left: var(--x, 50%);
  width: var(--size, 68px);
  /* Centre the balloon on --x without spending the transform property,
     which the rise animation already owns. */
  margin-left: calc(var(--size, 68px) / -2);
  will-change: transform;
}
.balloon__sway { display: block; }

.balloon__body {
  position: relative;
  display: block;
  width: 100%;
  /* Classic balloon proportion. */
  aspect-ratio: 1 / 1.18;
  border-radius: 50% 50% 48% 48% / 44% 44% 56% 56%;
  background:
    radial-gradient(circle at 32% 26%, rgba(255, 255, 255, 0.72) 0%, rgba(255, 255, 255, 0.14) 16%, transparent 34%),
    radial-gradient(circle at 70% 78%, rgba(0, 0, 0, 0.22) 0%, transparent 52%),
    linear-gradient(165deg,
      hsl(var(--hue, 340) 82% 74%) 0%,
      hsl(var(--hue, 340) 74% 60%) 52%,
      hsl(var(--hue, 340) 66% 46%) 100%);
  box-shadow:
    inset -6px -10px 18px hsl(var(--hue, 340) 60% 32% / 0.5),
    0 10px 26px rgba(0, 0, 0, 0.28);
}

/* Knot at the base of the balloon. */
.balloon__body::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -6%;
  width: 16%;
  height: 9%;
  transform: translateX(-50%) rotate(45deg);
  border-radius: 3px;
  background: hsl(var(--hue, 340) 66% 52%);
}

/* Curling string. */
.balloon__body::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 100%;
  width: 1px;
  height: 130%;
  transform: translateX(-50%);
  background: linear-gradient(
    to bottom,
    hsl(var(--hue, 340) 40% 70% / 0.65),
    hsl(var(--hue, 340) 40% 70% / 0)
  );
}


/* ==========================================================================
   05. BUTTONS
   Shared micro-interactions: lift on hover, sink on press, sweeping glow.
   ========================================================================== */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  padding: 0.95rem 2.1rem;
  border-radius: 999px;
  font-family: var(--font-body);
  font-size: clamp(0.92rem, 1.6vw, 1.02rem);
  font-weight: 500;
  letter-spacing: 0.035em;
  overflow: hidden;
  isolation: isolate;
  white-space: nowrap;
  transition:
    transform var(--dur-fast) var(--ease-out),
    box-shadow var(--dur) var(--ease-out),
    filter    var(--dur) var(--ease-out),
    background-color var(--dur) var(--ease-out);
}

.btn__label { position: relative; z-index: 2; }

/* Diagonal sheen that sweeps across on hover. */
.btn__glow {
  position: absolute;
  inset: -60% -20%;
  z-index: 1;
  background: linear-gradient(
    100deg,
    transparent 38%,
    rgba(255, 255, 255, 0.42) 50%,
    transparent 62%
  );
  transform: translateX(-115%);
  transition: transform 720ms var(--ease-out);
}
.btn:hover .btn__glow,
.btn:focus-visible .btn__glow { transform: translateX(115%); }

.btn:hover  { transform: translateY(-3px); }
.btn:active { transform: translateY(0) scale(0.975); }

/* Primary — rose→sunset gradient with a warm halo. */
.btn--primary {
  background: var(--grad-rose);
  color: #2a0d16;
  font-weight: 600;
  box-shadow: var(--shadow-md), var(--glow-rose);
}
.btn--primary:hover { box-shadow: var(--shadow-lg), 0 0 46px rgba(255, 143, 171, 0.6); }

/* Gold — the "Definitely, Yes!" variant. */
.btn--gold {
  background: var(--grad-gold);
  background-size: 200% 100%;
  color: #2a1f04;
  font-weight: 600;
  box-shadow: var(--shadow-md), var(--glow-gold);
}
.btn--gold:hover {
  background-position: 100% 0;
  box-shadow: var(--shadow-lg), 0 0 46px rgba(212, 175, 55, 0.55);
}

/* Ghost — quiet secondary action. */
.btn--ghost {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--hairline);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.btn--ghost:hover { background: var(--surface-hi); border-color: var(--gold-soft); }

.btn--sm { padding: 0.7rem 1.5rem; font-size: 0.9rem; }

/* Tiny — the deliberately unappealing "No". */
.btn--tiny {
  padding: 0.4rem 1rem;
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--text-faint);
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid var(--hairline-soft);
  opacity: 0.7;
}
.btn--tiny:hover { color: var(--text-muted); opacity: 1; }

/* Once defeated, the "No" button graduates into a real choice. */
.btn--tiny.is-converted {
  padding: 0.95rem 2.1rem;
  font-size: clamp(0.92rem, 1.6vw, 1.02rem);
  font-weight: 600;
  opacity: 1;
  color: #2a0d16;
  background: var(--grad-rose);
  border-color: transparent;
  box-shadow: var(--shadow-md), var(--glow-rose);
}


/* ==========================================================================
   06. SECTION SCAFFOLDING
   ========================================================================== */
#main { position: relative; z-index: 1; }

/* While the page is sealed the story is removed from the flow entirely, so
   the hero owns the viewport and nothing below it can be scrolled to.
   js/main.js drops `.is-sealed` from <body> to release it. */
body.is-sealed .story { display: none; }

.section {
  position: relative;
  padding: var(--section-pad) var(--gutter);
}

/* Hairline gold rule between sections. */
.section + .section::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  width: min(70%, 40rem);
  height: 1px;
  transform: translateX(-50%);
  background: linear-gradient(90deg, transparent, var(--hairline), transparent);
}

.section--last { padding-bottom: clamp(3rem, 8vh, 6rem); }

.section__inner {
  width: 100%;
  max-width: 76rem;
  margin: 0 auto;
}
.section__inner--narrow { max-width: 46rem; }

.section__head {
  text-align: center;
  margin-bottom: clamp(2.5rem, 6vw, 4rem);
}

.section__kicker {
  font-size: 0.74rem;
  font-weight: 500;
  letter-spacing: 0.4em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 0.9rem;
}

.section__title {
  font-size: clamp(2.1rem, 6.2vw, 3.9rem);
  color: var(--cream);
}
.section__title em {
  font-style: italic;
  background: var(--grad-gold);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

.section__lede {
  margin-top: 1rem;
  color: var(--text-muted);
  font-size: clamp(0.95rem, 1.9vw, 1.06rem);
}
/* Emphasis inside a lede picks up gold rather than plain italics. */
.section__lede em {
  font-style: italic;
  color: var(--gold-soft);
}


/* ==========================================================================
   06b. THE GATE — pre-birthday lock screen
   Sits above #main but below the overlays, and is transparent so the shared
   background stack (aurora, particles, balloons) shows straight through it.
   ========================================================================== */
.gate {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: grid;
  place-items: center;
  padding: clamp(1.5rem, 6vw, 4rem) var(--gutter);
  text-align: center;
}
.gate[hidden] { display: none; }

/* While locked, the site underneath is removed from the flow entirely —
   not merely covered — so it cannot be scrolled, tabbed into or read out. */
body.is-locked { overflow: hidden; height: 100%; }
body.is-locked #main,
body.is-locked .section-nav,
body.is-locked .scroll-progress,
body.is-locked .music-toggle,
body.is-locked .skip-link { display: none; }

.gate__inner {
  width: 100%;
  max-width: 54rem;
}

.gate__lock {
  font-size: clamp(1.8rem, 5vw, 2.6rem);
  line-height: 1;
  margin-bottom: clamp(1.2rem, 3vw, 1.8rem);
  filter: drop-shadow(0 0 20px rgba(212, 175, 55, 0.45));
}

.gate__title {
  font-family: var(--font-display);
  font-size: clamp(2rem, 7vw, 4.2rem);
  font-weight: 300;
  line-height: 1.14;
  color: var(--cream);
}

.gate__subtitle {
  margin: clamp(0.5rem, 1.6vw, 0.9rem) 0 clamp(2.4rem, 6vw, 3.6rem);
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(1.05rem, 3.2vw, 1.65rem);
  font-weight: 300;
  color: var(--rose-gold);
}

/* The clock reuses the .countdown component wholesale. */
.gate__countdown { justify-content: center; }


/* ==========================================================================
   07. HERO
   ========================================================================== */
.hero {
  position: relative;
  z-index: 1;
  /* dvh keeps mobile browser chrome from clipping the CTA. */
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  place-items: center;
  padding: clamp(4rem, 10vh, 7rem) var(--gutter);
  text-align: center;
}

.hero__inner {
  width: 100%;
  max-width: 60rem;
}

.hero__eyebrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  font-size: 0.76rem;
  letter-spacing: 0.44em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: clamp(1.25rem, 3vw, 2rem);
}
.hero__eyebrow .rule {
  width: clamp(2rem, 8vw, 5rem);
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--gold));
}
.hero__eyebrow .rule:last-child {
  background: linear-gradient(90deg, var(--gold), transparent);
}

.hero__title { margin-bottom: clamp(1rem, 2.5vw, 1.6rem); }

.hero__title-line {
  display: block;
  font-size: clamp(1.9rem, 6.6vw, 4.1rem);
  font-weight: 300;
  color: var(--cream);
  letter-spacing: 0.015em;
}

.hero__title-name {
  display: block;
  position: relative;
  margin-top: 0.15em;
  font-size: clamp(3.1rem, 14.5vw, 9rem);
  font-weight: 600;
  letter-spacing: -0.015em;
  line-height: 1;
}

.hero__heart {
  display: inline-block;
  font-size: 0.34em;
  vertical-align: middle;
  margin-left: 0.12em;
  filter: drop-shadow(0 0 16px rgba(255, 143, 171, 0.75));
}

.hero__sub {
  max-width: 34rem;
  margin: 0 auto clamp(2.2rem, 5vw, 3.2rem);
  color: var(--text-muted);
  font-size: clamp(0.98rem, 2.1vw, 1.16rem);
  font-weight: 300;
}

.hero__cta { display: flex; justify-content: center; }

.hero__scroll-hint {
  margin-top: clamp(2.5rem, 7vh, 4.5rem);
  display: flex;
  justify-content: center;
}
.hero__mouse {
  position: relative;
  width: 24px;
  height: 38px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
}
.hero__mouse span {
  position: absolute;
  top: 7px;
  left: 50%;
  width: 3px;
  height: 7px;
  margin-left: -1.5px;
  border-radius: 999px;
  background: var(--gold-soft);
}


/* ==========================================================================
   07b. OUR STORY & JOURNEY
   ========================================================================== */
.story-quote {
  position: relative;
  margin: 0;
  padding: clamp(2.4rem, 6vw, 3.6rem) clamp(1.5rem, 5vw, 3.2rem);
  text-align: center;
  border-radius: var(--radius-lg);
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(183, 110, 121, 0.16), transparent 62%),
    var(--surface);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-md);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

/* Oversized opening quote mark, straddling the top edge of the card. */
.story-quote__mark {
  position: absolute;
  top: clamp(-1.6rem, -3vw, -1rem);
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-display);
  font-size: clamp(5rem, 14vw, 8rem);
  line-height: 1;
  background: var(--grad-gold);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
  opacity: 0.55;
  pointer-events: none;
}

.story-quote__text {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.12rem, 3vw, 1.62rem);
  font-weight: 300;
  font-style: italic;
  line-height: 1.72;
  color: var(--cream);
}

.story-quote__sign {
  margin-top: clamp(1.4rem, 3.5vw, 2rem);
  font-family: var(--font-script);
  font-size: clamp(1.3rem, 3.4vw, 1.8rem);
  color: var(--rose-gold);
}

/* ── The two-stop journey rail: strangers → us ───────────────────────── */
.journey {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(0.75rem, 3vw, 1.6rem);
  margin: clamp(2.2rem, 6vw, 3.4rem) 0 0;
  padding: 0;
  list-style: none;
}

.journey__stop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.7rem;
  flex: 0 0 auto;
}

.journey__dot {
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  background: rgba(253, 246, 238, 0.14);
}

/* The "now" end of the rail is solid gold — the journey resolved. */
.journey__stop--now .journey__dot {
  background: var(--gold);
  border-color: var(--gold-soft);
  box-shadow: 0 0 18px rgba(212, 175, 55, 0.75);
}

.journey__label {
  font-size: clamp(0.64rem, 1.7vw, 0.74rem);
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--text-faint);
  white-space: nowrap;
}
.journey__stop--now .journey__label { color: var(--gold-soft); }

.journey__rail {
  flex: 1 1 auto;
  max-width: 14rem;
  height: 1px;
  /* Each stop starts with its 13px dot, so aligning to the top of the row
     and nudging down by half the dot puts the rail exactly through the dot
     centres — independent of how tall the labels below them are. */
  align-self: flex-start;
  margin-top: 6px;
  background: linear-gradient(90deg, var(--hairline-soft), var(--gold), var(--hairline-soft));
}


/* ==========================================================================
   08. COUNTDOWN
   ========================================================================== */
.countdown {
  display: flex;
  align-items: stretch;
  justify-content: center;
  gap: clamp(0.35rem, 1.6vw, 1.1rem);
  flex-wrap: nowrap;
}

/* `display` is re-declared for this element at several breakpoints, so the
   [hidden] override has to be explicit or it would be ignored. */
.countdown[hidden],
.countdown__arrived[hidden] { display: none !important; }

.countdown__unit {
  flex: 0 1 9.5rem;
  min-width: 0;
  padding: clamp(1rem, 3vw, 1.9rem) clamp(0.4rem, 2vw, 1.2rem);
  text-align: center;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-md), inset 0 1px 0 rgba(255, 255, 255, 0.06);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  transition: border-color var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
}
.countdown__unit:hover {
  border-color: var(--gold-soft);
  transform: translateY(-4px);
}

.countdown__value {
  font-family: var(--font-display);
  font-size: clamp(2rem, 8.5vw, 4.4rem);
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  background: var(--grad-gold);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

.countdown__label {
  margin-top: 0.65rem;
  font-size: clamp(0.58rem, 1.5vw, 0.72rem);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.countdown__sep {
  align-self: center;
  font-family: var(--font-display);
  font-size: clamp(1.2rem, 4vw, 2.4rem);
  color: var(--hairline);
  padding-bottom: 1.4rem;
}

.countdown__arrived { text-align: center; }
.countdown__arrived-line {
  margin-top: 0.6rem;
  font-family: var(--font-display);
  font-size: clamp(1.9rem, 6vw, 3.4rem);
  color: var(--cream);
}


/* ==========================================================================
   09. GALLERY + LIGHTBOX
   ========================================================================== */
/* ── The favourite: one photo on its own stage ────────────────────────── */
.favourite {
  /* Narrower than the landscape default — a portrait photo at 46rem wide
     would run off the bottom of most screens. */
  margin: 0 auto clamp(2rem, 5vw, 3.2rem);
  max-width: min(100%, 26rem);
  text-align: center;
}

.favourite__frame {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  /* Portrait. Overridden inline by js/gallery.js to the photo's own ratio
     when config supplies its dimensions, so the favourite is never cropped
     either — this is only the fallback while it loads. */
  aspect-ratio: 4 / 5;
  /* Same gradient-ring trick as the grid tiles, one step heavier. */
  border: 2px solid transparent;
  background:
    linear-gradient(var(--midnight-2), var(--midnight-2)) padding-box,
    var(--grad-gold) border-box;
  box-shadow: var(--shadow-lg), 0 0 46px rgba(212, 175, 55, 0.22);
}

.favourite__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Hidden behind the cover until revealed — see animations.css §07b. */
  transform: scale(1.12);
  filter: blur(18px) saturate(0.7);
  transition: transform 1400ms var(--ease-out), filter 1100ms var(--ease-out);
}
.favourite.is-unveiled .favourite__img {
  transform: scale(1);
  filter: blur(0) saturate(1.02);
}

/* Frosted cover panel. */
.favourite__cover {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  padding: 1.5rem;
  overflow: hidden;
  background:
    radial-gradient(110% 80% at 50% 30%, rgba(183, 110, 121, 0.32), transparent 65%),
    rgba(10, 10, 31, 0.72);
  -webkit-backdrop-filter: blur(14px) saturate(120%);
  backdrop-filter: blur(14px) saturate(120%);
  transition: opacity 900ms var(--ease-out), backdrop-filter 900ms var(--ease-out);
}
.favourite.is-unveiled .favourite__cover {
  opacity: 0;
  -webkit-backdrop-filter: blur(0);
  backdrop-filter: blur(0);
  pointer-events: none;
}

.favourite__badge {
  display: inline-block;
  padding: 0.42rem 1.15rem;
  border-radius: 999px;
  border: 1px solid var(--hairline);
  background: rgba(212, 175, 55, 0.12);
  font-size: clamp(0.6rem, 1.7vw, 0.72rem);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--gold-soft);
}

.favourite__teaser {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(1.15rem, 3.4vw, 1.7rem);
  color: var(--cream);
}

/* Diagonal shimmer that keeps sweeping the cover, hinting it is tappable. */
.favourite__shine {
  position: absolute;
  inset: -60% -25%;
  background: linear-gradient(
    100deg,
    transparent 40%,
    rgba(243, 217, 152, 0.28) 50%,
    transparent 60%
  );
  pointer-events: none;
}

.favourite__cover:hover .favourite__teaser { color: var(--gold-soft); }
.favourite__cover:hover { background:
    radial-gradient(110% 80% at 50% 30%, rgba(183, 110, 121, 0.42), transparent 65%),
    rgba(10, 10, 31, 0.66); }

/* Gold curtain that sweeps across at the moment of reveal. */
.favourite__frame::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  background: linear-gradient(
    100deg,
    transparent 0%,
    rgba(243, 217, 152, 0.55) 45%,
    rgba(255, 250, 240, 0.85) 50%,
    rgba(243, 217, 152, 0.55) 55%,
    transparent 100%
  );
  transform: translateX(-130%);
  opacity: 0;
  pointer-events: none;
}

/* After the reveal the photo becomes clickable for the lightbox. */
.favourite__frame.is-interactive { cursor: zoom-in; }
.favourite__frame.is-interactive:hover .favourite__img { transform: scale(1.04); }

.favourite__caption {
  margin-top: clamp(0.9rem, 2.5vw, 1.3rem);
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(0.98rem, 2.4vw, 1.18rem);
  color: var(--text-muted);
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 700ms var(--ease-out) 500ms, transform 700ms var(--ease-out) 500ms;
}
.favourite.is-unveiled .favourite__caption { opacity: 1; transform: none; }

/* Missing file: show the placeholder, drop the reveal choreography. */
.favourite.is-missing .favourite__frame { aspect-ratio: 3 / 2; }
.favourite.is-missing .favourite__caption { opacity: 1; transform: none; }

/* ── Masonry via CSS columns ──────────────────────────────────────────
   Deliberately NOT a CSS grid. Every photo here is a different shape, and
   a grid forces each tile into a fixed cell, which means object-fit has to
   crop something out of every one. Columns let each image keep its own
   natural aspect ratio, so nothing is ever cut off.

   The trade-off is reading order: columns flow top-to-bottom, then across.
   For a photo wall with no narrative sequence that costs nothing, and the
   lightbox still pages through them in config order.                      */
/* Three columns is the ceiling on purpose. With a handful of photos, four
   columns leaves one of them half-empty (7 items distribute 2/2/2/1) and
   shrinks every picture to make room for the gap. Three fills evenly and
   shows each photo larger. Raise this if the album grows past ~12. */
.gallery-grid {
  column-count: 2;
  column-gap: clamp(0.8rem, 2vw, 1.4rem);
  max-width: 62rem;
  margin: 0 auto;
}
@media (min-width: 48rem) { .gallery-grid { column-count: 3; } }

.gallery-item {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  /* Keep a tile from being split across a column break. */
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  margin: 0 0 clamp(0.8rem, 2vw, 1.4rem);
  overflow: hidden;
  border-radius: var(--radius);
  /* The gold border is a gradient ring drawn with a padding-box mask. */
  border: 1px solid transparent;
  background:
    linear-gradient(var(--midnight-2), var(--midnight-2)) padding-box,
    var(--grad-gold) border-box;
  box-shadow: var(--shadow-md);
  transition:
    transform var(--dur) var(--ease-out),
    box-shadow var(--dur) var(--ease-out);
}

/* Only placeholders need an invented shape — real photos supply their own. */
.gallery-item.is-empty { aspect-ratio: 4 / 5; }

.gallery-item:hover,
.gallery-item:focus-visible {
  transform: translateY(-6px);
  box-shadow: var(--shadow-lg), 0 0 32px rgba(212, 175, 55, 0.3);
}

.gallery-item__img {
  width: 100%;
  /* `auto` is what preserves the photo's own proportions inside the column.
     The width/height attributes on the element reserve the right box before
     the file arrives, so the masonry doesn't jolt as photos load in. */
  height: auto;
  display: block;
  transform: scale(1.001);
  transition: transform 780ms var(--ease-out), filter var(--dur) var(--ease-out);
  filter: saturate(0.94) contrast(1.02);
}
.gallery-item:hover .gallery-item__img,
.gallery-item:focus-visible .gallery-item__img {
  transform: scale(1.09);
  filter: saturate(1.08) contrast(1.05);
}

/* Caption sheet that rises on hover. */
.gallery-item__veil {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  padding: 1.1rem;
  background: linear-gradient(to top, rgba(10, 10, 31, 0.9) 0%, rgba(10, 10, 31, 0.15) 45%, transparent 70%);
  opacity: 0;
  transition: opacity var(--dur) var(--ease-out);
}
.gallery-item:hover .gallery-item__veil,
.gallery-item:focus-visible .gallery-item__veil { opacity: 1; }

.gallery-item__caption {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-style: italic;
  color: var(--cream);
  transform: translateY(10px);
  transition: transform var(--dur) var(--ease-out);
}
.gallery-item:hover .gallery-item__caption,
.gallery-item:focus-visible .gallery-item__caption { transform: translateY(0); }

/* ── Empty-frame placeholder ───────────────────────────────────────────
   Shown when the expected file is missing, so the grid still looks
   deliberate and tells you exactly which filename to add.               */
.gallery-item.is-empty { cursor: default; }
.gallery-item.is-empty:hover { transform: none; box-shadow: var(--shadow-md); }

.gallery-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  width: 100%;
  height: 100%;
  padding: 1.2rem;
  text-align: center;
  background:
    repeating-linear-gradient(
      -45deg,
      rgba(212, 175, 55, 0.05) 0 12px,
      transparent 12px 24px
    ),
    var(--midnight-2);
}
.gallery-placeholder__icon {
  font-size: 1.7rem;
  opacity: 0.8;
  filter: drop-shadow(0 0 12px rgba(212, 175, 55, 0.35));
}
.gallery-placeholder__title {
  font-family: var(--font-display);
  font-size: 1rem;
  font-style: italic;
  color: var(--text-muted);
}
.gallery-placeholder__file {
  font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
  font-size: 0.68rem;
  letter-spacing: 0.02em;
  color: var(--gold-soft);
  word-break: break-all;
  padding: 0.18rem 0.45rem;
  border-radius: 5px;
  background: rgba(212, 175, 55, 0.1);
  border: 1px solid rgba(212, 175, 55, 0.2);
}

.gallery__note {
  margin-top: clamp(1.6rem, 4vw, 2.4rem);
  text-align: center;
  font-size: 0.86rem;
  color: var(--text-faint);
}

/* ── Lightbox ─────────────────────────────────────────────────────────── */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 160;
  display: grid;
  place-items: center;
  padding: clamp(1rem, 5vw, 3rem);
}
.lightbox[hidden] { display: none; }

.lightbox__scrim {
  position: absolute;
  inset: 0;
  background: rgba(4, 4, 14, 0.9);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}

.lightbox__figure {
  position: relative;
  z-index: 1;
  margin: 0;
  max-width: min(100%, 62rem);
  text-align: center;
}
.lightbox__figure img {
  max-height: 76vh;
  width: auto;
  margin: 0 auto;
  border-radius: var(--radius);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-lg), 0 0 60px rgba(212, 175, 55, 0.18);
}
.lightbox__figure figcaption {
  margin-top: 1rem;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.1rem;
  color: var(--text-muted);
}

.lightbox__close,
.lightbox__nav {
  position: absolute;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border-radius: 999px;
  background: var(--surface-hi);
  border: 1px solid var(--hairline);
  color: var(--cream);
  font-size: 1.6rem;
  line-height: 1;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  transition: background var(--dur) var(--ease-out), transform var(--dur-fast) var(--ease-out);
}
.lightbox__close:hover,
.lightbox__nav:hover { background: var(--gold); color: var(--midnight); transform: scale(1.08); }

/* These carry `display: grid`, so [hidden] needs an explicit override. */
.lightbox__nav[hidden],
.lightbox__close[hidden] { display: none; }

.lightbox__close { top: clamp(0.75rem, 3vw, 1.5rem); right: clamp(0.75rem, 3vw, 1.5rem); }
.lightbox__nav--prev { left: clamp(0.5rem, 2.5vw, 1.5rem);  top: 50%; transform: translateY(-50%); }
.lightbox__nav--next { right: clamp(0.5rem, 2.5vw, 1.5rem); top: 50%; transform: translateY(-50%); }
.lightbox__nav--prev:hover { transform: translateY(-50%) scale(1.08); }
.lightbox__nav--next:hover { transform: translateY(-50%) scale(1.08); }


/* ==========================================================================
   10. INVITATION CARD
   ========================================================================== */
.invite {
  position: relative;
  padding: 3px;                 /* thickness of the foil edge */
  border-radius: var(--radius-lg);
  background: var(--grad-gold);
  background-size: 200% 200%;
  box-shadow: var(--shadow-lg), 0 0 56px rgba(212, 175, 55, 0.22);
}

/* Soft gold bloom behind the whole card.
   Bleeds vertically only. An earlier `inset: -14%` on all four sides pushed
   it ~50px past the viewport on a phone, which relied on body's overflow
   clip to hide — and iOS Safari does not reliably honour that. Vertical
   bleed costs nothing because the page already scrolls that way. */
.invite__foil {
  position: absolute;
  inset: -14% 0;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(212, 175, 55, 0.30), transparent 72%);
  filter: blur(28px);
}

.invite__frame {
  position: relative;
  padding: clamp(2.2rem, 6.5vw, 4.2rem) clamp(1.4rem, 5vw, 3.4rem) clamp(3.4rem, 7vw, 4.6rem);
  border-radius: calc(var(--radius-lg) - 3px);
  text-align: center;
  overflow: hidden;
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(183, 110, 121, 0.22), transparent 60%),
    linear-gradient(165deg, #14112f 0%, #0d0b22 55%, #1a1236 100%);
}

/* Inner hairline rule, inset from the foil edge. */
.invite__frame::before {
  content: "";
  position: absolute;
  inset: clamp(0.7rem, 2vw, 1.1rem);
  border: 1px solid rgba(212, 175, 55, 0.34);
  border-radius: calc(var(--radius-lg) - 10px);
  pointer-events: none;
}

.invite__ornament {
  font-size: 0.85rem;
  letter-spacing: 0.5em;
  color: var(--gold);
  opacity: 0.75;
}
.invite__ornament--top    { margin-bottom: 1.4rem; }
.invite__ornament--bottom { margin-top: 2rem; }

.invite__script {
  font-family: var(--font-script);
  font-size: clamp(2.6rem, 9vw, 4.4rem);
  line-height: 1.1;
  margin-bottom: 1.4rem;
  background: var(--grad-gold);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

.invite__salutation {
  margin-bottom: clamp(1.2rem, 3vw, 1.8rem);
}

/* The lead-in stays small and quiet so the name can carry the line. */
.invite__salutation-lead {
  display: block;
  font-family: var(--font-body);
  font-size: clamp(0.68rem, 1.8vw, 0.8rem);
  font-weight: 400;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 0.4rem;
}

.invite__initials {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.22em;
  font-family: var(--font-display);
  font-size: clamp(2.6rem, 9vw, 4.2rem);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: 0.06em;
  background: var(--grad-gold);
  background-size: 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

/* The heart must opt out of the gold text-clip it would otherwise inherit
   (-webkit-text-fill-color is an inherited property). */
.invite__initials-heart {
  font-size: 0.62em;
  line-height: 1;
  color: var(--sunset);
  -webkit-text-fill-color: var(--sunset);
  background: none;
  filter: drop-shadow(0 0 14px rgba(255, 143, 171, 0.7));
}

.invite__body {
  max-width: 30rem;
  margin: 0 auto;
  font-family: var(--font-display);
  font-size: clamp(1.12rem, 2.8vw, 1.5rem);
  font-weight: 300;
  line-height: 1.6;
  color: var(--cream);
}

/* Date | Time | Location as three equal columns. Equal-height cells mean
   the hairline separators line up perfectly regardless of text length. */
.invite__details {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: stretch;
  gap: 0;
  margin: clamp(1.8rem, 5vw, 2.6rem) 0 0;
  text-align: center;
}
.invite__detail {
  padding: 0 clamp(0.4rem, 1.6vw, 1.1rem);
}
.invite__detail + .invite__detail {
  border-left: 1px solid var(--hairline);
}
.invite__detail dt {
  font-size: clamp(0.56rem, 1.5vw, 0.66rem);
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 0.45rem;
}
.invite__detail dd {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 3.2vw, 1.72rem);
  line-height: 1.25;
  color: var(--cream);
  text-wrap: balance;
}

.invite__signoff {
  margin-top: clamp(1.6rem, 4vw, 2.2rem);
  font-size: 0.88rem;
  line-height: 1.9;
  color: var(--text-faint);
  font-style: italic;
}

/* Wax seal, straddling the bottom edge. */
.invite__seal {
  position: absolute;
  left: 50%;
  bottom: -1.6rem;
  display: grid;
  place-items: center;
  width: 4.4rem;
  height: 4.4rem;
  transform: translateX(-50%) rotate(-8deg);
  border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, #d97a86, #8e3b48 62%, #6d2733 100%);
  box-shadow: 0 8px 22px rgba(0, 0, 0, 0.5), inset 0 -3px 8px rgba(0, 0, 0, 0.4);
}
.invite__seal span {
  font-family: var(--font-script);
  /* Sized for the two-character "J2" monogram. */
  font-size: 1.72rem;
  letter-spacing: 0.02em;
  line-height: 1;
  color: rgba(255, 235, 225, 0.92);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
}


/* ==========================================================================
   11. RSVP
   ========================================================================== */
.rsvp {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(0.8rem, 2.5vw, 1.3rem);
}
.rsvp[hidden] { display: none; }

.rsvp-success {
  text-align: center;
  padding: clamp(2rem, 6vw, 3.2rem) clamp(1.2rem, 4vw, 2.4rem);
  border-radius: var(--radius-lg);
  background: var(--surface);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-lg), 0 0 44px rgba(255, 143, 171, 0.16);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}
.rsvp-success[hidden] { display: none; }

.rsvp-success__mark {
  font-size: clamp(2.4rem, 8vw, 3.6rem);
  line-height: 1;
  color: var(--sunset);
  filter: drop-shadow(0 0 22px rgba(255, 143, 171, 0.6));
  margin-bottom: 0.6rem;
}
.rsvp-success__line {
  margin-top: 0.5rem;
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 4vw, 2rem);
  color: var(--cream);
}
.rsvp-success__meta {
  margin: 0.9rem 0 1.8rem;
  font-size: 0.76rem;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--gold);
}


/* ==========================================================================
   12. MODAL (Error 404)
   ========================================================================== */
.modal {
  position: fixed;
  inset: 0;
  z-index: 180;
  display: grid;
  place-items: center;
  padding: var(--gutter);
}
.modal[hidden] { display: none; }

.modal__scrim {
  position: absolute;
  inset: 0;
  background: rgba(4, 4, 14, 0.72);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.modal__card {
  position: relative;
  z-index: 1;
  width: min(100%, 27rem);
  padding: clamp(1.8rem, 5vw, 2.6rem);
  text-align: center;
  border-radius: var(--radius-lg);
  border: 1px solid var(--hairline);
  background:
    radial-gradient(110% 80% at 50% 0%, rgba(183, 110, 121, 0.28), transparent 62%),
    linear-gradient(165deg, #191338 0%, #0d0b22 100%);
  box-shadow: var(--shadow-lg), 0 0 60px rgba(255, 143, 171, 0.22);
}

.modal__emoji {
  font-size: 2.6rem;
  line-height: 1;
  margin-bottom: 0.9rem;
}

.modal__title {
  font-size: clamp(1.7rem, 5vw, 2.3rem);
  background: var(--grad-rose);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

.modal__body {
  margin: 0.7rem 0 1.6rem;
  color: var(--text-muted);
  font-size: clamp(0.95rem, 2.2vw, 1.05rem);
  line-height: 1.7;
}


/* ==========================================================================
   13. CHROME — nav, progress bar, music toggle, footer
   ========================================================================== */
.scroll-progress {
  position: fixed;
  top: 0; left: 0;
  z-index: 150;
  width: 100%;
  height: 2px;
  background: transparent;
  pointer-events: none;
}
.scroll-progress span {
  display: block;
  width: 0%;
  height: 100%;
  background: var(--grad-gold);
  box-shadow: 0 0 12px rgba(212, 175, 55, 0.7);
  transition: width 90ms linear;
}

.section-nav {
  position: fixed;
  top: 50%;
  right: clamp(0.6rem, 2vw, 1.6rem);
  z-index: 140;
  transform: translateY(-50%) translateX(140%);
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  opacity: 0;
  transition: transform var(--dur-slow) var(--ease-out), opacity var(--dur-slow) var(--ease-out);
}
.section-nav.is-visible { transform: translateY(-50%) translateX(0); opacity: 1; }

.section-nav a {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.6rem;
  font-size: 0.66rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-faint);
  transition: color var(--dur) var(--ease-out);
}
.section-nav a span {
  opacity: 0;
  transform: translateX(6px);
  transition: opacity var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
}
/* The dot marker. */
.section-nav a::after {
  content: "";
  width: 8px;
  height: 8px;
  flex: none;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  background: transparent;
  transition: background var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out), transform var(--dur) var(--ease-out);
}
.section-nav a:hover span,
.section-nav a.is-active span { opacity: 1; transform: translateX(0); }
.section-nav a:hover,
.section-nav a.is-active { color: var(--gold-soft); }
.section-nav a:hover::after,
.section-nav a.is-active::after {
  background: var(--gold);
  border-color: var(--gold);
  transform: scale(1.25);
  box-shadow: 0 0 12px rgba(212, 175, 55, 0.8);
}

/* Hide the rail on narrow screens — it competes with content. */
@media (max-width: 48rem) { .section-nav { display: none; } }

.music-toggle {
  position: fixed;
  left: clamp(0.75rem, 2.5vw, 1.6rem);
  bottom: clamp(0.75rem, 2.5vw, 1.6rem);
  z-index: 140;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  background: var(--surface-hi);
  border: 1px solid var(--hairline);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur-fast) var(--ease-out), border-color var(--dur) var(--ease-out);
}
.music-toggle[hidden] { display: none; }
.music-toggle:hover { transform: scale(1.08); border-color: var(--gold-soft); }

.music-toggle__icon {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 15px;
}
.music-toggle__icon i {
  display: block;
  width: 2.5px;
  height: 4px;
  border-radius: 2px;
  background: var(--gold-soft);
}

.footer {
  position: relative;
  padding: clamp(2.5rem, 7vw, 4rem) var(--gutter) clamp(3rem, 8vw, 4.5rem);
  text-align: center;
  border-top: 1px solid var(--hairline-soft);
}
.footer__line {
  font-size: 0.92rem;
  color: var(--text-muted);
}
.footer__name {
  font-family: var(--font-script);
  font-size: 1.5rem;
  vertical-align: -0.12em;
  color: var(--rose-gold);
}
.footer__meta {
  margin-top: 0.8rem;
  font-size: 0.68rem;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--text-faint);
}


/* ==========================================================================
   14. RESPONSIVE REFINEMENTS
   ========================================================================== */

/* Below ~34rem the four countdown cards no longer fit in one row, so the
   colons are dropped and the units wrap to a 2×2 grid. */
@media (max-width: 34rem) {
  .countdown { display: grid; grid-template-columns: 1fr 1fr; gap: 0.7rem; }
  .countdown__sep { display: none; }
  .countdown__unit { flex: none; }
  .rsvp { flex-direction: column; }
  .rsvp .btn { width: 100%; }

  /* Invitation details stack; the separator moves from left edge to top. */
  .invite__details { grid-template-columns: 1fr; }
  .invite__detail { padding: 0; }
  .invite__detail + .invite__detail {
    border-left: 0;
    border-top: 1px solid var(--hairline);
    margin-top: 1.1rem;
    padding-top: 1.1rem;
  }
}

/* Backdrop-filter is expensive on low-end mobile GPUs — drop it and lean on
   solid surfaces instead. */
@media (max-width: 48rem) {
  .countdown__unit,
  .rsvp-success {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: rgba(20, 17, 52, 0.72);
  }
}


/* ==========================================================================
   15. TOUCH DEVICES
   --------------------------------------------------------------------------
   The problem this section solves: on a touchscreen there is no hover. A tap
   fires :hover and then *leaves it stuck* until you tap elsewhere. So any
   content that only appears on hover is either unreachable or sticks on at
   random — both bad. Anything hover-revealed therefore needs a coarse-pointer
   equivalent, and anything hover-decorative needs to not fire on tap.

   Keyed on `hover`/`pointer` rather than width, because a small window on a
   laptop still has a real mouse and a large tablet still doesn't.
   ========================================================================== */
@media (hover: none), (pointer: coarse) {

  /* Gallery captions can't wait for a hover that will never come. */
  .gallery-item__veil { opacity: 1; }
  .gallery-item__caption { transform: none; }

  /* Lighter scrim, since it is now permanent rather than a hover flourish. */
  .gallery-item__veil {
    background: linear-gradient(
      to top,
      rgba(10, 10, 31, 0.82) 0%,
      rgba(10, 10, 31, 0.1) 42%,
      transparent 62%
    );
  }

  /* Lift-on-hover reads as a stuck state after a tap — drop it. */
  .btn:hover,
  .gallery-item:hover,
  .countdown__unit:hover,
  .music-toggle:hover,
  .lightbox__close:hover,
  .lightbox__nav:hover { transform: none; }

  .lightbox__nav--prev:hover,
  .lightbox__nav--next:hover { transform: translateY(-50%); }

  /* Give feedback on the press itself instead. */
  .btn:active { transform: scale(0.97); }
  .gallery-item:active { transform: scale(0.985); }

  /* The tiny "No" is a joke, but it still has to be tappable. Apple and
     Google both put the floor at 44px; the visual size is unchanged. */
  .btn--tiny {
    min-height: 44px;
    padding-inline: 1.4rem;
  }

  /* Comfortable thumb targets in the photo viewer. */
  .lightbox__close,
  .lightbox__nav { width: 3.25rem; height: 3.25rem; }
}

/* Kill the grey flash Android/iOS paint over tapped elements — the site has
   its own press states and the default clashes with the palette. */
@media (pointer: coarse) {
  a, button, .gallery-item, .favourite__cover {
    -webkit-tap-highlight-color: rgba(212, 175, 55, 0.18);
  }
}


/* ==========================================================================
   16. NOTCHES & HOME INDICATORS
   `viewport-fit=cover` in the meta tag lets the page use the full screen on
   notched phones, which also means fixed UI can land under the notch or the
   home bar. These offsets keep it clear. env() resolves to 0 everywhere else.
   ========================================================================== */
.music-toggle {
  left: max(clamp(0.75rem, 2.5vw, 1.6rem), env(safe-area-inset-left));
  bottom: max(clamp(0.75rem, 2.5vw, 1.6rem), env(safe-area-inset-bottom));
}
.section-nav {
  right: max(clamp(0.6rem, 2vw, 1.6rem), env(safe-area-inset-right));
}
.lightbox__close {
  top: max(clamp(0.75rem, 3vw, 1.5rem), env(safe-area-inset-top));
  right: max(clamp(0.75rem, 3vw, 1.5rem), env(safe-area-inset-right));
}
.footer { padding-bottom: max(clamp(3rem, 8vw, 4.5rem), env(safe-area-inset-bottom)); }


/* ==========================================================================
   17. SMALL-PHONE REFINEMENTS
   ========================================================================== */
@media (max-width: 26rem) {
  /* The gate is the one screen she may stare at for days — keep the clock
     comfortably readable rather than letting it shrink with the viewport. */
  .gate__countdown .countdown__value { font-size: 2.1rem; }
  .gate__countdown .countdown__unit { padding: 0.85rem 0.4rem; }

  /* Long single words (the J2 monogram, "Definitely, Yes!") must not push
     the layout sideways on a 320px screen. */
  .btn { white-space: normal; }
  .invite__initials { font-size: 2.4rem; }
}

/* Landscape phones are short, not narrow: the hero must not demand a full
   portrait-height screen or the CTA ends up below the fold. */
@media (max-height: 30rem) and (orientation: landscape) {
  .hero { min-height: auto; padding-block: 3.5rem; }
  .hero__scroll-hint { display: none; }
  .gate { padding-block: 2rem; }
  .gate__lock { display: none; }
}
