/* ============================================================================
   Shrinay — Welcome Party
   ----------------------------------------------------------------------------
   Template-3's opening, recoloured from the film's own palette — cream
   paper, wax-seal blue and gold — and the invitation below it in the same
   colours.

     1.  Tokens and resets
     2.  Preloader
     3.  Hero
     4.  Shared pieces        sections, petals, reveal
     5.  The invitation       blessing, name, rocking horse, details
     6.  Countdown            teddy, arched tiles
     7.  The evening          floral frame: when, where, directions
     8.  Save the date        the month, the day in a heart, butterflies
     9.  Blessings            the quote, the families who invite

   The film is portrait (9:16). On a phone it fills the screen; on anything
   wider it stands in a 9:16 frame in the middle, over a blurred copy of the
   cover image, rather than being cropped down to a strip.
   ========================================================================== */


/* ==========================================================================
   1. Tokens and resets
   ========================================================================== */

:root {
  --blue:       #4f6fa3;
  --blue-deep:  #3d5a8a;
  --blue-soft:  #a9bfe3;
  --gold:       #c9a24a;
  --gold-deep:  #a8843a;
  --gold-ink:   #86672b;   /* gold dark enough to read as text on cream */
  --cream:      #fbf7ef;
  --paper:      #fffdf8;
  --wallpaper:  #eadac6;   /* the paper of background-v4.jpg, shown before it loads */
  --card:       #f7efe3;   /* the cards over it: a shade lighter, same warmth */
  --card-deep:  #f1e6d5;
  --ink:        #2f3a4f;
  --ink-soft:   #5d6879;
  --navy:       #22305a;
  --navy-soft:  #3a4670;
  --navy-film:  #2c3350;   /* the satin bows and roses in hero.mp4 */

  --font-heading: "Cinzel", "Times New Roman", serif;
  --font-script:  "Great Vibes", cursive;
  --font-body:    "Cormorant Garamond", Georgia, serif;
  --font-display: "Playfair Display", Georgia, serif;
}

*, *::before, *::after { box-sizing: border-box; }

body, h2, p { margin: 0; padding: 0; }

html, body {
  width: 100%;
  min-height: 100%;
  overflow-x: hidden;
  -webkit-tap-highlight-color: transparent;
}

/* Held still while the preloader is up and the opening film is running, so
   the page cannot be scrolled past a cover that is still covering it.

   This has to name the root element as well as the body, as in template-3.
   The viewport takes its scrolling from <html>, and only falls back to
   <body> when <html> is `overflow: visible` — which it is not here, because
   of the `overflow-x: hidden` above. On <body> alone this rule does nothing. */
html.is-locked,
body.is-locked { overflow: hidden; }

/* Set by assets/js/main.js on a section that has scrolled out of view, so its
   looping decorations — petals, the glowing hand — stop costing frames while
   nobody can see them. */
.is-offscreen,
.is-offscreen *,
.is-offscreen *::before,
.is-offscreen *::after { animation-play-state: paused !important; }

body {
  font-family: var(--font-heading);
  color: var(--ink);
  background: var(--wallpaper);
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* The sand wallpaper — fine gold and blue waves, gold leaves and sparkles — held
   still behind the whole page while the sections scroll over it. A layer of
   its own rather than `background-attachment: fixed`, which makes the
   browser repaint the whole page on every scroll frame and which iOS ignores
   altogether; this one is composited once and left. The image is portrait,
   to suit phones; on a wide screen `cover` enlarges it to the full width and
   shows its middle. */
/* Sized to the large viewport rather than pinned to the bottom edge: on a
   phone the bottom edge moves as the address bar slides away, and a `cover`
   image tied to it is rescaled and repainted on every frame of that slide. */
body::before {
  content: "";
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  height: 100lvh;
  z-index: -1;
  pointer-events: none;
  background: url("../media/background-v4.jpg") center / cover no-repeat, var(--wallpaper);
}

img, video { max-width: 100%; display: block; }

button { font: inherit; }


/* ==========================================================================
   2. Preloader
   Covers everything until the cover image has loaded.
   ========================================================================== */

.preloader {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: url("../media/background-v4.jpg") center / cover no-repeat, var(--wallpaper);
  transition: opacity .6s ease-out, visibility .6s ease-out;
}

.preloader.is-done { opacity: 0; visibility: hidden; }

/* Taken out of the page once it has faded, so its motes stop animating. */
.preloader[hidden] { display: none; }

.preloader__text {
  font-size: clamp(1rem, 4vw, 1.5rem);
  letter-spacing: .15em;
  text-transform: uppercase;
  text-align: center;
  color: var(--blue);
  padding: 0 1rem;
  margin-bottom: 3rem;
  animation: pulse-text 2.5s ease-in-out infinite;
}

/* A 50px highlight sliding along a 150px track. */
.preloader__bar {
  position: relative;
  width: 150px;
  height: 2px;
  border-radius: 2px;
  background: rgba(79, 111, 163, .2);
  overflow: hidden;
}

.preloader__bar::after {
  content: "";
  position: absolute;
  top: 0;
  left: -50px;
  width: 50px;
  height: 100%;
  background: linear-gradient(90deg, transparent, var(--blue), transparent);
  animation: shimmer 2s cubic-bezier(.65, 0, .35, 1) infinite;
}

/* Motes drifting up the screen. Each one's position, size and timing are
   randomised in assets/js/effects.js. */
.motes {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.motes__item {
  position: absolute;
  border-radius: 50%;
  opacity: 0;
  background: radial-gradient(circle, rgba(201, 162, 74, .8) 0%, rgba(201, 162, 74, 0) 70%);
  animation: mote-rise linear infinite;
}

.motes__item:nth-child(even) {
  background: radial-gradient(circle, rgba(79, 111, 163, .7) 0%, rgba(79, 111, 163, 0) 70%);
}

@keyframes pulse-text {
  0%, 100% { opacity: .6; text-shadow: 0 0 5px rgba(79, 111, 163, .2); }
  50%      { opacity: 1;  text-shadow: 0 0 15px rgba(79, 111, 163, .5); }
}

@keyframes shimmer {
  0%   { left: -50px; }
  100% { left: 100%; }
}

@keyframes mote-rise {
  0%        { opacity: 0; transform: translateY(100vh) scale(.5); }
  20%, 80%  { opacity: .6; }
  100%      { opacity: 0; transform: translateY(-10vh) scale(1.5); }
}


/* ==========================================================================
   3. Hero
   The film, with the cover image over it until tapped. Plays once and
   holds on its last frame.
   ========================================================================== */

/* The small viewport — the screen with the address bar showing — rather than
   the dynamic one. The dynamic height changes on every frame while the bar
   slides away on the first scroll, and each change lays out the film, its
   card and the whole page under it again. */
.hero {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  background: var(--cream);
}

/* Fills the sides on a wide screen. Pushed past the edges so the blur has
   no pale fringe. A layer of its own, so the blur is drawn once rather than
   again whenever something else in the hero repaints. */
.hero__backdrop {
  position: absolute;
  inset: -60px;
  background: url("../media/hero-poster-v2.jpg") center / cover no-repeat;
  filter: blur(30px) brightness(.92);
  will-change: transform;
}

/* 9:16, as tall as the screen. On a tall phone the screen is the narrower
   limit, so the frame becomes the whole screen and the film is cropped a
   little at the sides instead of letterboxed. A container, so the film's own
   box inside it (.hero__frame) can be measured against it. */
.hero__stage {
  position: relative;
  height: 100%;
  aspect-ratio: 9 / 16;
  max-width: 100%;
  margin: 0 auto;
  overflow: hidden;
  box-shadow: 0 0 60px rgba(0, 0, 0, .25);
  container-type: size;
}

/* Any phone-shaped screen is filled edge to edge, even one a little wider
   than 9:16 — as most are once the browser's bars take their share of the
   height — and the film is cropped a little at the top and bottom instead.
   Beyond 2:3 the crop would start to reach the card, so wider screens keep
   the 9:16 column over the blurred backdrop. */
@media (max-aspect-ratio: 2 / 3) {
  .hero__stage { width: 100%; aspect-ratio: auto; }
  .hero__backdrop { display: none; }
}

/* Everything that belongs to the film — the film, its still and the card's
   wording — so the closing zoom carries the wording with the card. The
   origin is the middle of the card and its envelope in the last frame; at
   1.15 the whole card still fits the narrowest phone the frame is cropped
   for. */
.hero__film {
  position: absolute;
  inset: 0;
  transform-origin: 50% 39%;
  transition: transform 2.4s cubic-bezier(.22, .61, .36, 1);
}

.hero__film.is-zoomed { transform: scale(1.15); }

.hero__video,
.hero__poster {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero__video  { z-index: 1; }

/* The still sits over the video until the film is running, so the first
   paint is never a black box. */
.hero__poster {
  z-index: 2;
  pointer-events: none;
  transition: opacity 1s ease-in-out;
}

.hero__poster.is-hidden { opacity: 0; }

/* The film's own 9:16 box, centred — the same box `object-fit: cover` draws
   the film into: full height when the stage is narrower than 9:16, full width
   when it is wider. Everything inside is placed in percentages of the film's
   frame and sized in cqw, so it scales with the film rather than the screen. */
.hero__frame {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 5;
  width: max(100cqw, 56.25cqh);
  height: max(100cqh, 177.78cqw);
  transform: translate(-50%, -50%);
  container-type: size;
  pointer-events: none;
}

/* The whole blank part of the card in the last frame: from just below the
   rocking horse hanging from the bow, down into the gap the envelope's
   pocket leaves in the middle above the seal. The lines are spread across
   all of it, the heart sitting in that narrower bottom gap. */
.hero__card {
  position: absolute;
  top: 24.5%;
  left: 19%;
  width: 62%;
  height: 25%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  text-align: center;
}

/* Each line starts hidden and is dealt in by assets/js/hero.js when the film
   ends, as template-3 deals in the couple's names: it rises and scales up
   while a clip wipes it open from its own baseline. */
.hero__card > * {
  opacity: 0;
  transform: translateY(40px) scale(.9);
  clip-path: polygon(-50% 150%, 150% 150%, 150% 150%, -50% 150%);
  transition: opacity 1.5s cubic-bezier(.215, .61, .355, 1),
              transform 1.5s cubic-bezier(.215, .61, .355, 1),
              clip-path 1.5s cubic-bezier(.215, .61, .355, 1);
}

.hero__card.is-in > * {
  opacity: 1;
  transform: none;
  clip-path: polygon(-50% -50%, 150% -50%, 150% 150%, -50% 150%);
}

/* Great Vibes rather than template-3's Mussica Swash: that face draws a
   small-capital R mid-word, which read as "ShRinay's". The padding keeps the
   clip wipe from shaving the flourishes, which reach outside the letters'
   own box. */
.hero__name {
  margin: 0;
  font-family: var(--font-script);
  font-size: 17cqw;
  font-weight: 400;
  line-height: 1.15;
  padding: 0 .25em;
  color: var(--navy-film);
  text-shadow: 0 2px 6px rgba(44, 51, 80, .22);
}

/* Great Vibes sets the apostrophe well clear of the y's tail, which reads as
   a gap and pulls the name off centre. */
.hero__possessive { margin-left: -.18em; }

/* A gold hairline either side of a small diamond. */
.hero__divider {
  display: flex;
  align-items: center;
  gap: 2.2cqw;
  width: 46cqw;
}

.hero__divider::before,
.hero__divider::after {
  content: "";
  flex: 1;
  height: 1px;
}

.hero__divider::before { background: linear-gradient(90deg, transparent, var(--gold)); }
.hero__divider::after  { background: linear-gradient(90deg, var(--gold), transparent); }

.hero__divider i {
  width: 1.5cqw;
  height: 1.5cqw;
  background: var(--gold);
  transform: rotate(45deg);
}

.hero__event {
  font-family: var(--font-heading);
  font-size: 6.2cqw;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #a8843a;
}

/* Gold dots either side of a small heart, closing the card. */
.hero__heart {
  display: flex;
  align-items: center;
  gap: 2cqw;
  color: var(--gold);
}

.hero__heart::before,
.hero__heart::after {
  content: "";
  width: 1cqw;
  height: 1cqw;
  border-radius: 50%;
  background: currentColor;
}

.hero__heart svg {
  width: 4.6cqw;
  height: 4.6cqw;
}

@media (prefers-reduced-motion: reduce) {
  .hero__card > * { transition: opacity .6s ease; transform: none; clip-path: none; }
  .hero__film { transition: none; }
  .hero__scroll.is-in { animation: none; }
}


/* Over the whole screen, not just the frame, so a tap anywhere opens it. */
.hero__cover {
  position: absolute;
  inset: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  padding-bottom: 28vh;
  background: rgba(20, 30, 50, .35);
  cursor: pointer;
  transition: opacity 1s ease-out;
}

.hero__cover.is-open { opacity: 0; pointer-events: none; }

/* Gone for good once faded, so the breathing prompt stops animating under
   the film. */
.hero__cover[hidden] { display: none; }

.hero__tap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .9rem;
  animation: breathe 2.8s ease-in-out infinite;
}

.hero__tap-text {
  font-size: clamp(1.1rem, 4vw, 1.5rem);
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--cream);
  text-shadow: 0 4px 12px rgba(0, 0, 0, .55);
}

.hero__tap-rule {
  width: 90px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--cream), transparent);
}

@keyframes breathe {
  0%, 100% { opacity: .9; transform: scale(1); }
  50%      { opacity: 1;  transform: scale(1.04); }
}

/* The way past the film, as on template-3. Above the film, below the cover. */
.hero__skip {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  z-index: 8;
  padding: .5rem 1.1rem;
  font-size: .75rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--cream);
  background: rgba(20, 30, 50, .55);
  border: 1px solid rgba(169, 191, 227, .7);
  border-radius: 9999px;
  cursor: pointer;
  animation: skip-in .6s ease both;
  transition: background .25s ease, border-color .25s ease;
}

.hero__skip:hover {
  background: rgba(20, 30, 50, .65);
  border-color: var(--blue-soft);
}

.hero__skip[hidden] { display: none; }

@keyframes skip-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Appears once the film has played, and bounces until scrolled past. A
   paper pill, so it reads over whatever the bottom of the film shows. Near
   opaque rather than frosted: a backdrop blur under a bouncing pill is
   redrawn on every frame of the bounce. */
.hero__scroll {
  position: absolute;
  bottom: 1.75rem;
  left: 50%;
  z-index: 6;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .1rem;
  padding: .55rem 1.3rem .4rem;
  color: var(--blue-deep);
  background: rgba(255, 253, 248, .94);
  border: 1px solid rgba(201, 162, 74, .55);
  border-radius: 9999px;
  box-shadow: 0 6px 20px rgba(47, 58, 79, .15);
  opacity: 0;
  transform: translateX(-50%);
  pointer-events: none;
  transition: opacity .8s ease-out 1.2s;
}

.hero__scroll.is-in {
  opacity: 1;
  animation: nudge 1.6s ease-in-out 2s infinite;
}

.hero__scroll-text {
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .24em;
  text-transform: uppercase;
}

.hero__scroll-arrow {
  font-size: 1.1rem;
  line-height: 1;
  color: var(--gold-deep);
}

@keyframes nudge {
  0%, 100% { transform: translateX(-50%) translateY(0); }
  50%      { transform: translateX(-50%) translateY(6px); }
}

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


/* ==========================================================================
   4. Shared pieces
   ========================================================================== */

/* Everything below the film is see-through, so it sits on the wallpaper
   behind the page (body::before) with no seam between sections. */
.section {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: clamp(4.5rem, 12vw, 7rem) 1.25rem;
  overflow: hidden;
}

/* Blue petals falling through a section, as they fall through the film.
   Each one's position, size, drift and timing are set in assets/js/effects.js.
   The travel is in viewport units, so a petal crosses a section about one
   screen tall at the same pace whatever its own size. */
.petals {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}

.petals__item {
  position: absolute;
  top: 0;
  border-radius: 80% 0 80% 0;
  background: linear-gradient(135deg, #dbe5f5 0%, #9fb6de 60%, #7d98c8 100%);
  animation: petal-fall linear infinite;
}

@keyframes petal-fall {
  0%   { transform: translate3d(0, -10vh, 0) rotate(0); }
  50%  { transform: translate3d(calc(var(--drift) * -.5), 50vh, 0) rotate(calc(var(--spin) * .5)); }
  100% { transform: translate3d(var(--drift), 110vh, 0) rotate(var(--spin)); }
}

/* Reveal on scroll. assets/js/reveal.js adds .is-in when the element first
   scrolls into view; an inline transition-delay staggers a group. Only
   opacity and transform move, which the compositor runs off the main thread
   — a blur here would be repainted on every frame, mid-scroll, for each of
   the several lines a section reveals at once. */
[data-reveal] {
  opacity: 0;
  transform: translateY(40px);
  transition: opacity .9s ease, transform .9s ease;
}

[data-reveal="fade"]  { transform: none; }
[data-reveal="zoom"]  { transform: scale(.92); }

[data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* Out of the page altogether while their section is off screen (see
   .is-offscreen above), so the dozens of petals and the butterflies hold no
   layers while the guest scrolls elsewhere. Their negative delays put them
   back mid-fall when they return. */
.is-offscreen .petals,
.is-offscreen .butterflies { display: none; }

@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; transition: none; }
  .petals { display: none; }
}

/* The music toggle, as on template-2: a cream disc ringed in gold with a
   blue note, struck through while the music is off. Fixed bottom-right for
   the whole visit. assets/js/music.js shows it on the opening tap (.is-in)
   and sets .is-muted whenever the track is not playing. Near opaque rather
   than frosted: a backdrop blur on a fixed button re-blurs whatever scrolls
   under it on every frame of every scroll. */
.music {
  position: fixed;
  right: 1.25rem;
  bottom: 1.5rem;
  z-index: 60;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  padding: 0;
  border-radius: 9999px;
  background: rgba(255, 253, 248, .94);
  border: 1px solid rgba(201, 162, 74, .6);
  box-shadow: 0 6px 18px rgba(47, 58, 79, .14);
  color: var(--blue-deep);
  cursor: pointer;
  opacity: 0;
  pointer-events: none;
  transform: translateY(40px);
  transition: opacity .8s ease-out, transform .8s ease-out, background .2s ease, color .2s ease;
}

/* Hidden until the invitation is opened — there is nothing to turn off before. */
.music.is-in { opacity: 1; pointer-events: auto; transform: none; }
.music.is-in:hover  { background: var(--paper); transform: translateY(-2px); }
.music.is-in:active { transform: scale(.94); }
.music:focus-visible { outline: 2px solid var(--blue); outline-offset: 4px; }

.music__icon {
  position: relative;
  z-index: 1;
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.music__slash {
  position: absolute;
  opacity: 0;
  transition: opacity .2s ease;
}

.music.is-muted { color: rgba(79, 111, 163, .55); }
.music.is-muted .music__slash { opacity: 1; }

/* A slow gold pulse ring while the music is actually playing. */
.music__ring {
  position: absolute;
  inset: -7px;
  border-radius: 9999px;
  border: 1px solid rgba(201, 162, 74, .55);
  opacity: 0;
  animation: music-pulse 2.6s ease-out infinite;
}

.music:not(.is-muted) .music__ring { opacity: 1; }

/* No pulse behind a button nobody can see, or one that is off. */
.music:not(.is-in) .music__ring,
.music.is-muted .music__ring { animation-play-state: paused; }

@keyframes music-pulse {
  0%   { scale: .85; opacity: .6; }
  100% { scale: 1.3;  opacity: 0; }
}

@media (min-width: 640px) {
  .music { right: 1.75rem; bottom: 1.75rem; }
}

@media (prefers-reduced-motion: reduce) {
  .music__ring { animation: none; opacity: 0 !important; }
}


/* ==========================================================================
   5. The invitation
   After the reference card the client chose: the blessing, the wording, the
   name in a bold serif over the occasion in script, the rocking horse, and
   the details in three columns between gold rules.
   ========================================================================== */

/* The sections follow one another closely, as pages of one card: a full
   gap opens the invitation, a shorter one sits between it and the rest. */
.invite    { padding-bottom: clamp(2.5rem, 7vw, 3.5rem); }
.countdown { padding-top: clamp(2.5rem, 7vw, 3.5rem); padding-bottom: 1rem; }

.invite__body {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: 560px;
  text-align: center;
}

/* The glow is its own layer behind the hand, breathing slowly, so the hand
   itself never has to be repainted. */
.invite__emblem {
  position: relative;
  display: grid;
  place-items: center;
  width: 150px;
  height: 150px;
}

.invite__emblem::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(240, 214, 140, .7) 0%, rgba(233, 207, 138, .3) 42%, rgba(233, 207, 138, 0) 70%);
  animation: glow 4.5s ease-in-out infinite;
}

.invite__hand {
  position: relative;
  width: 96px;
  height: auto;
  filter: drop-shadow(0 0 8px rgba(201, 162, 74, .55)) drop-shadow(0 4px 6px rgba(122, 92, 30, .2));
}

.invite__mantra {
  margin-top: .4rem;
  font-family: var(--font-display);
  font-size: clamp(1.15rem, 4.6vw, 1.4rem);
  font-weight: 600;
  color: var(--navy);
}

.invite__lead {
  margin-top: 1.5rem;
  font-family: var(--font-body);
  font-style: italic;
  font-weight: 600;
  font-size: clamp(1.3rem, 5.2vw, 1.6rem);
  line-height: 1.55;
  color: var(--gold-ink);
}

.invite__name {
  margin-top: .5rem;
  font-family: var(--font-display);
  font-size: clamp(3.6rem, 17vw, 5.4rem);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -.005em;
  color: var(--navy);
}

.invite__event {
  margin-top: .12em;
  font-family: var(--font-script);
  font-size: clamp(2.8rem, 13vw, 3.9rem);
  line-height: 1.1;
  color: var(--gold-ink);
}

/* Rocks gently on its runners, pivoting where they meet the floor. The
   rocking waits for the reveal's own transform to finish, and starts from
   upright so it does not jump. */
.invite__horse {
  width: min(130px, 34vw);
  height: auto;
  margin: 1rem auto 1.8rem;
  filter: drop-shadow(0 10px 12px rgba(47, 58, 79, .14));
}

.invite__horse.is-in {
  transform-origin: 50% 92%;
  animation: rock 3.6s ease-in-out 1.8s infinite;
}

@keyframes rock {
  0%, 100% { transform: rotate(0); }
  25%      { transform: rotate(-4deg); }
  75%      { transform: rotate(4deg); }
}

/* The day, the time and the place, each in its own column, on a solid
   cream card with a gold rule inset inside it — so the wallpaper's lines
   never run behind the text — each under a gold icon and a small label. */
.details {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  width: 100%;
  padding: 1.5rem .5rem 1.4rem;
  background: var(--card);
  border-radius: 16px;
  box-shadow: 0 14px 32px rgba(110, 84, 50, .14), 0 1px 3px rgba(110, 84, 50, .1);
}

.details::before {
  content: "";
  position: absolute;
  inset: 6px;
  border: 1px solid rgba(201, 162, 74, .75);
  border-radius: 11px;
  pointer-events: none;
}

.details__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 0 .4rem;
}

.details__item + .details__item { border-left: 1px solid rgba(201, 162, 74, .45); }

.details__icon {
  width: 26px;
  height: 26px;
  color: var(--gold-deep);
}

.details__label {
  margin-top: .45rem;
  font-size: .62rem;
  font-weight: 700;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--gold-ink);
}

.details__main {
  margin-top: .35rem;
  font-family: var(--font-display);
  font-size: clamp(1rem, 4.3vw, 1.3rem);
  font-weight: 700;
  line-height: 1.25;
  color: var(--navy);
}

/* Cormorant's default figures are old-style, which sets "11" as two short
   strokes; lining figures read as a date. */
.details__sub {
  margin-top: .25rem;
  font-family: var(--font-body);
  font-variant-numeric: lining-nums;
  font-size: clamp(1rem, 4.1vw, 1.15rem);
  font-weight: 600;
  line-height: 1.3;
  color: var(--navy);
}

@keyframes glow {
  0%, 100% { opacity: .65; transform: scale(.92); }
  50%      { opacity: 1;   transform: scale(1.06); }
}

@media (prefers-reduced-motion: reduce) {
  .invite__emblem::before,
  .invite__horse.is-in { animation: none; }
}


/* ==========================================================================
   6. Countdown
   The teddy swaying over the title and the time left in four arched
   tiles — the arches of the film.
   ========================================================================== */

.countdown__body {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: 560px;
  text-align: center;
}

/* Bobs gently once revealed; the reveal's own transform finishes first. */
/* The teddy over the title sways a little from side to side, pivoting at
   its seat; the reveal's own transform finishes first. */
.countdown__teddy {
  width: min(150px, 40vw);
  height: auto;
  margin-bottom: 1.1rem;
  filter: drop-shadow(0 12px 14px rgba(47, 58, 79, .16));
}

.countdown__teddy.is-in {
  transform-origin: 50% 95%;
  animation: sway 4.5s ease-in-out 1.2s infinite;
}

@keyframes sway {
  0%, 100% { transform: rotate(0); }
  25%      { transform: rotate(-3deg); }
  75%      { transform: rotate(3deg); }
}

.countdown__eyebrow {
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--gold-deep);
}

.countdown__title {
  margin-top: .3rem;
  font-family: var(--font-script);
  font-size: clamp(3rem, 13vw, 4.2rem);
  font-weight: 400;
  line-height: 1.15;
  color: var(--navy-film);
  text-shadow: 0 2px 8px rgba(44, 51, 80, .16);
}

/* A gold hairline either side of a small diamond, as on the film's card. */
.countdown__rule {
  display: flex;
  align-items: center;
  gap: .7rem;
  width: min(200px, 56%);
  margin: .9rem 0 1.8rem;
}

.countdown__rule::before,
.countdown__rule::after {
  content: "";
  flex: 1;
  height: 1px;
}

.countdown__rule::before { background: linear-gradient(90deg, transparent, var(--gold)); }
.countdown__rule::after  { background: linear-gradient(90deg, var(--gold), transparent); }

.countdown__rule i {
  width: 6px;
  height: 6px;
  background: var(--gold);
  transform: rotate(45deg);
}

.countdown__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(.55rem, 2.6vw, 1.1rem);
  width: 100%;
  max-width: 460px;
}

/* An arch of cream stock with a gold rule inset inside it. */
.countdown__cell {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .35rem;
  aspect-ratio: 4 / 5;
  padding-top: 18%;
  overflow: hidden;
  background: linear-gradient(180deg, var(--card) 0%, var(--card-deep) 100%);
  border-radius: 999px 999px 12px 12px;
  box-shadow: 0 14px 28px rgba(110, 84, 50, .12), 0 1px 3px rgba(110, 84, 50, .08);
}

.countdown__cell::before {
  content: "";
  position: absolute;
  inset: 5px;
  border: 1px solid rgba(201, 162, 74, .6);
  border-radius: 999px 999px 8px 8px;
  pointer-events: none;
}

/* Swapped out and back by assets/js/countdown.js as each number changes: the
   old number leaves upwards, the new one rises into its place. */
.countdown__value {
  font-family: var(--font-display);
  font-size: clamp(1.6rem, 7.6vw, 2.5rem);
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--navy);
  transition: transform .35s cubic-bezier(.215, .61, .355, 1), opacity .35s ease-out;
}

.countdown__value.is-turning {
  opacity: 0;
  transform: translateY(-14px);
  transition: transform .25s ease-in, opacity .25s ease-in;
}

/* Where the incoming number waits, for one frame, before it rises. */
.countdown__value.is-below   { opacity: 0; transform: translateY(14px); }
.countdown__value.is-instant { transition: none; }

.countdown__label {
  font-size: clamp(.5rem, 2.1vw, .66rem);
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--gold-deep);
}

.countdown__note {
  margin-top: 1.5rem;
  font-family: var(--font-body);
  font-style: italic;
  font-size: clamp(1.25rem, 5vw, 1.5rem);
  color: var(--gold-ink);
}

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


/* ==========================================================================
   7. The evening
   The client's floral frame — a toile bow at the top, roses and hydrangeas
   in two corners, a ribbon along the foot — with the when, the where and
   the way there inside its opening.
   ========================================================================== */

/* Little room above and below, so the frame's bow follows close under the
   countdown and Save the date close under its ribbon. */
.venue {
  padding-top: clamp(1rem, 4vw, 1.5rem);
  padding-bottom: 1rem;
}

/* The picture is 1:2 (700×1400); the card is a touch taller, 1:2.06, just
   enough to hold the wording with even gaps, so the vines barely stretch.
   Everything is measured in cqw — hundredths of the frame's width — so it
   all keeps its place at any size. */
.venue__frame {
  position: relative;
  z-index: 1;
  width: min(100%, 430px);
  aspect-ratio: 1 / 2.06;
  container-type: inline-size;
}

/* Drawn as a nine-slice border image. The top 320px of the picture (the
   toile bow and the top corners' sprigs) and the bottom 310px (the roses,
   navy blossoms and navy ribbon) are drawn at their own shape; only the
   gold rails and their vines between them stretch to the card's height.
   The slice widths, in cqw, are those pixels over the picture's 700px width.

   The picture is set only once the guest is getting near (.is-near, added
   by assets/js/main.js), so its 190KB never competes with the opening film. */
.venue__frame-art {
  position: absolute;
  inset: 0;
  border: 0 solid transparent;
  border-image-slice: 320 110 310 110 fill;
  border-image-width: 45.71cqw 15.71cqw 44.29cqw 15.71cqw;
  border-image-repeat: stretch;
  filter: drop-shadow(0 18px 26px rgba(47, 58, 79, .14));
  pointer-events: none;
}

.venue.is-near .venue__frame-art { border-image-source: url("../media/venue-frame-v2.webp"); }

/* A soft wash of cream behind the wording, so the wallpaper's lines never
   run through it. It fades out towards its edges rather than stopping at
   one, because the frame's corners are notched and a hard panel would show
   past them. */
.venue__frame::before {
  content: "";
  position: absolute;
  top: 16cqw;
  left: 6cqw;
  right: 6cqw;
  bottom: 22cqw;
  background: radial-gradient(closest-side, rgba(247, 239, 227, .97) 72%, rgba(247, 239, 227, 0) 100%);
}

/* From just under the bow's tails to just over where the flowers in the
   lower right corner reach in, and well inside the gold rails across. The
   pieces sit an even gap apart, centred in that space, rather than spread
   to fill it. */
.venue__inner {
  position: absolute;
  top: 46cqw;
  bottom: 43cqw;
  left: 13cqw;
  right: 13cqw;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6cqw;
  text-align: center;
}

.venue__title {
  font-family: var(--font-script);
  font-size: 15cqw;
  font-weight: 400;
  line-height: 1.05;
  color: var(--navy-film);
  text-shadow: 0 2px 8px rgba(44, 51, 80, .16);
}

.venue__block {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.venue__when {
  margin-top: 1.8cqw;
  font-family: var(--font-display);
  font-size: 4.9cqw;
  font-weight: 700;
  line-height: 1.25;
  font-variant-numeric: lining-nums;
  color: var(--navy);
}

.venue__time {
  margin-top: .6cqw;
  font-family: var(--font-display);
  font-size: 4.7cqw;
  font-weight: 600;
  font-variant-numeric: lining-nums;
  color: var(--blue-deep);
}

/* Two gold rules either side of a small tied bow. */
.venue__rule {
  display: flex;
  align-items: center;
  gap: 3cqw;
  width: 70%;
  color: var(--gold-deep);
}

.venue__rule::before,
.venue__rule::after {
  content: "";
  flex: 1;
  height: 1px;
}

.venue__rule::before { background: linear-gradient(90deg, transparent, var(--gold)); }
.venue__rule::after  { background: linear-gradient(90deg, var(--gold), transparent); }

.venue__rule svg { width: 9cqw; height: 5.4cqw; }

.venue__name {
  margin: 1.6cqw 0 0;
  font-family: var(--font-display);
  font-size: 10.4cqw;
  font-weight: 700;
  line-height: 1.15;
  color: var(--navy);
}

.venue__address {
  margin-top: 2cqw;
  font-family: var(--font-body);
  font-style: normal;
  font-size: 4.7cqw;
  font-weight: 600;
  line-height: 1.45;
  font-variant-numeric: lining-nums;
  color: var(--gold-ink);
}

.venue__actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2.2cqw;
}

.venue__directions {
  display: inline-flex;
  align-items: center;
  gap: 2cqw;
  padding: 3cqw 7cqw;
  font-family: var(--font-display);
  font-size: 4.5cqw;
  font-weight: 600;
  text-decoration: none;
  color: var(--paper);
  background: var(--navy);
  border-radius: 9999px;
  box-shadow: 0 10px 20px rgba(34, 48, 90, .22);
  transition: background .25s ease, transform .25s ease, box-shadow .25s ease;
}

.venue__directions:hover {
  background: var(--blue-deep);
  transform: translateY(-2px);
  box-shadow: 0 14px 24px rgba(34, 48, 90, .28);
}

.venue__directions svg { width: 5cqw; height: 5cqw; }

.venue__directions:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
}


/* ==========================================================================
   8. Save the date
   November 2026 on a cream card, the day ringed in a beating heart, and the
   client's butterfly drifting round it with its wings beating.
   ========================================================================== */

.save {
  padding-top: clamp(1.5rem, 5vw, 2.25rem);
  padding-bottom: 1.5rem;
}

.save__body {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: 440px;
  text-align: center;
}

.save__title {
  font-family: var(--font-script);
  font-size: clamp(3.4rem, 15vw, 4.6rem);
  font-weight: 400;
  line-height: 1.1;
  color: var(--navy-film);
  text-shadow: 0 2px 8px rgba(44, 51, 80, .16);
}

.save__card {
  position: relative;
  width: 100%;
  margin-top: 1.2rem;
  padding: 1.6rem 1.1rem 1.4rem;
  background: var(--card);
  border-radius: 18px;
  box-shadow: 0 14px 32px rgba(110, 84, 50, .14), 0 1px 3px rgba(110, 84, 50, .1);
}

.save__card::before {
  content: "";
  position: absolute;
  inset: 6px;
  border: 1px solid rgba(201, 162, 74, .6);
  border-radius: 13px;
  pointer-events: none;
}

.save__month {
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 6vw, 1.7rem);
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--navy);
}

.save__weekdays,
.save__days {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
}

.save__weekdays {
  margin-top: 1.1rem;
  padding-bottom: .7rem;
  border-bottom: 1px solid rgba(201, 162, 74, .4);
}

.save__weekdays li {
  font-family: var(--font-display);
  font-size: .85rem;
  font-weight: 700;
  color: var(--gold-deep);
}

.save__days {
  row-gap: .35rem;
  margin-top: .7rem;
}

.save__day {
  position: relative;
  display: grid;
  place-items: center;
  height: 2.6rem;
  font-family: var(--font-display);
  font-size: clamp(1rem, 4.4vw, 1.15rem);
  font-weight: 600;
  font-variant-numeric: lining-nums;
  color: var(--navy-soft);
}

/* The day itself: bolder, in a blue heart that beats gently. */
.save__day.is-party { color: var(--navy); font-weight: 700; }

.save__day.is-party span { position: relative; z-index: 1; }

.save__heart {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 3.1rem;
  height: 3.1rem;
  margin: -1.55rem 0 0 -1.55rem;
  fill: rgba(169, 191, 227, .35);
  stroke: var(--blue);
  stroke-width: 1.4;
  animation: heartbeat 2.4s ease-in-out infinite;
}

@keyframes heartbeat {
  0%, 60%, 100% { transform: scale(1); }
  15%           { transform: scale(1.12); }
  30%           { transform: scale(1); }
  45%           { transform: scale(1.08); }
}

.save__caption {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  margin-top: 1rem;
  padding-top: .9rem;
  border-top: 1px solid rgba(201, 162, 74, .4);
  font-family: var(--font-body);
  font-style: italic;
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--gold-ink);
}

.save__caption svg {
  width: 1rem;
  height: 1rem;
  fill: var(--blue);
}

/* Outlined, as on the reference card. */
.save__add {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  margin-top: 1.6rem;
  padding: .85rem 1.9rem;
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--navy);
  background: rgba(255, 253, 248, .85);
  border: 1.5px solid var(--navy);
  border-radius: 9999px;
  transition: background .25s ease, color .25s ease, transform .25s ease;
}

.save__add:hover { color: var(--paper); background: var(--navy); transform: translateY(-2px); }

.save__add:focus-visible { outline: 2px solid var(--gold); outline-offset: 3px; }

.save__add svg { width: 20px; height: 20px; }

/* ---- Butterflies ---------------------------------------------------------

   The picture is cut down its middle into two wings, each a half-width box
   showing its own half of the same image. Each wing turns on the body's
   line, in perspective, so it reads as a beat rather than a squash.

   A flight is two layers. .flight is the size of the whole section and only
   moves: its translate is in percentages of itself, so a path is laid out
   in percentages of the section and fits any screen. .butterfly inside it
   only turns, to face the way it is flying. Both are transforms, so none of
   it costs a layout. The picture is set once the guest is getting near
   (.is-near, from assets/js/main.js). */
.butterflies {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  overflow: hidden;
}

.flight {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.butterfly {
  --size: clamp(46px, 13vw, 62px);
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  width: var(--size);
  height: calc(var(--size) * 2 / 3);
  perspective: 300px;
  filter: drop-shadow(0 6px 6px rgba(34, 48, 90, .25));
}

.butterfly__wing {
  width: 50%;
  height: 100%;
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: .32s ease-in-out infinite alternate;
}

.save.is-near .butterfly__wing { background-image: url("../media/butterfly-v2.webp"); }

.butterfly__wing--left {
  background-position: left center;
  transform-origin: right center;
  animation-name: wing-left;
}

.butterfly__wing--right {
  background-position: right center;
  transform-origin: left center;
  animation-name: wing-right;
}

@keyframes wing-left  { from { transform: rotateY(8deg); }  to { transform: rotateY(68deg); } }
@keyframes wing-right { from { transform: rotateY(-8deg); } to { transform: rotateY(-68deg); } }

/* The first crosses the top of the section from the left, looping round
   the title; the second crosses lower down from the right, under the card.
   Each turns to face its way. The picture faces up, so 90deg is east. */
.flight--1 { animation: path-1 24s linear infinite; }
.flight--1 .butterfly { animation: turn-1 24s linear infinite; }

@keyframes path-1 {
  0%   { transform: translate(-12%, 14%); }
  14%  { transform: translate(12%, 4%); }
  28%  { transform: translate(30%, 12%); }
  40%  { transform: translate(22%, 22%); }
  52%  { transform: translate(44%, 18%); }
  66%  { transform: translate(64%, 6%); }
  82%  { transform: translate(82%, 16%); }
  100% { transform: translate(112%, 8%); }
}

@keyframes turn-1 {
  0%   { transform: rotate(70deg); }
  14%  { transform: rotate(105deg); }
  28%  { transform: rotate(160deg); }
  40%  { transform: rotate(60deg); }
  52%  { transform: rotate(80deg); }
  66%  { transform: rotate(110deg); }
  82%  { transform: rotate(75deg); }
  100% { transform: rotate(95deg); }
}

.flight--2 { animation: path-2 30s linear -12s infinite; }
.flight--2 .butterfly { --size: clamp(38px, 10vw, 50px); animation: turn-2 30s linear -12s infinite; }

@keyframes path-2 {
  0%   { transform: translate(112%, 78%); }
  18%  { transform: translate(84%, 88%); }
  34%  { transform: translate(62%, 80%); }
  50%  { transform: translate(44%, 90%); }
  64%  { transform: translate(30%, 76%); }
  80%  { transform: translate(12%, 86%); }
  100% { transform: translate(-14%, 80%); }
}

@keyframes turn-2 {
  0%   { transform: rotate(-80deg); }
  18%  { transform: rotate(-110deg); }
  34%  { transform: rotate(-60deg); }
  50%  { transform: rotate(-110deg); }
  64%  { transform: rotate(-55deg); }
  80%  { transform: rotate(-105deg); }
  100% { transform: rotate(-80deg); }
}

/* The third rests on the card's top corner, wings opening and closing
   slowly, as a butterfly does when it has landed. */
.flight--rest {
  inset: auto;
  top: -18px;
  right: 10px;
  width: auto;
  height: auto;
}

.flight--rest .butterfly {
  --size: clamp(40px, 11vw, 52px);
  position: relative;
  transform: rotate(18deg);
}

.flight--rest .butterfly__wing { animation-duration: 1.6s; }

@media (prefers-reduced-motion: reduce) {
  .save__heart,
  .flight,
  .flight .butterfly,
  .butterfly__wing { animation: none; }
  .flight--1, .flight--2 { display: none; }
}


/* ==========================================================================
   9. Blessings
   A line about the little one, a blessing, and the families who invite on
   a cream card tied with the toile bow: who invites, and who sends a
   special invitation. Under it, a button that lets a toile hot air balloon
   go carrying a blessing, and the pile of gifts.
   ========================================================================== */

/* Little room at the foot: the gifts close the page. */
.hosts {
  padding-top: clamp(1rem, 4vw, 1.5rem);
  padding-bottom: 1.5rem;
}

.hosts__body {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: 480px;
  text-align: center;
}

.hosts__quote {
  font-family: var(--font-script);
  font-size: clamp(2.6rem, 11.5vw, 3.6rem);
  font-weight: 400;
  line-height: 1.15;
  color: var(--navy-film);
  text-shadow: 0 2px 8px rgba(44, 51, 80, .16);
}

/* Two gold rules either side of an outlined heart. */
.hosts__rule {
  display: flex;
  align-items: center;
  gap: .8rem;
  width: min(260px, 70%);
  margin: 1.2rem 0 1.1rem;
}

.hosts__rule::before,
.hosts__rule::after {
  content: "";
  flex: 1;
  height: 1px;
}

.hosts__rule::before { background: linear-gradient(90deg, transparent, var(--gold)); }
.hosts__rule::after  { background: linear-gradient(90deg, var(--gold), transparent); }

.hosts__rule svg {
  width: 26px;
  height: 26px;
  fill: none;
  stroke: var(--gold-deep);
  stroke-width: 1.4;
}

.hosts__blessing {
  font-family: var(--font-body);
  font-style: italic;
  font-size: clamp(1.3rem, 5.4vw, 1.55rem);
  line-height: 1.45;
  color: var(--gold-ink);
}

/* A cream card with a breath of blue at its top, a soft blue halo round it,
   and two gold rules inside its edge. */
.hosts__card {
  position: relative;
  width: 100%;
  margin-top: 4rem;
  padding: 4.7rem 1.3rem 2.2rem;
  background:
    radial-gradient(120% 55% at 50% 0%, rgba(169, 191, 227, .16), rgba(169, 191, 227, 0) 70%),
    linear-gradient(180deg, var(--card) 0%, var(--card-deep) 100%);
  border-radius: 20px;
  box-shadow:
    0 0 0 1px rgba(201, 162, 74, .35),
    0 22px 44px rgba(34, 48, 90, .14),
    0 0 70px rgba(169, 191, 227, .45);
}

.hosts__card::before,
.hosts__card::after {
  content: "";
  position: absolute;
  pointer-events: none;
}

.hosts__card::before {
  inset: 8px;
  border: 1px solid rgba(201, 162, 74, .75);
  border-radius: 14px;
}

.hosts__card::after {
  inset: 12px;
  border: 1px solid rgba(201, 162, 74, .3);
  border-radius: 11px;
}

/* The toile bow, its knot on the card's top edge and its tails hanging
   over the card. */
.hosts__bow {
  position: absolute;
  top: -32px;
  left: 50%;
  width: 210px;
  height: auto;
  margin-left: -105px;
  filter: drop-shadow(0 8px 10px rgba(34, 48, 90, .18));
  pointer-events: none;
}

/* A gold flourish tucked into each corner, inside the rules: one drawing,
   mirrored for the other three. */
.hosts__corners {
  position: absolute;
  inset: 17px;
  pointer-events: none;
}

.hosts__corners i {
  position: absolute;
  width: 38px;
  height: 38px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 40 40' fill='none' stroke='%23b8933f' stroke-linecap='round'%3E%3Cpath d='M2 38C2 18 18 2 38 2' stroke-width='1.3'/%3E%3Cpath d='M8 30c0-12 10-22 22-22' stroke-width='.8' opacity='.6'/%3E%3Cpath d='M2 26c3.5-.5 5.5 1.5 5 4.5M26 2c-.5 3.5 1.5 5.5 4.5 5' stroke-width='1'/%3E%3Cpath d='M9 14c1.5-3.5 5-5 5-5s-1.5 3.5-5 5zM14 9' stroke-width='.9' fill='%23c9a24a' fill-opacity='.35'/%3E%3Ccircle cx='9.2' cy='9.2' r='1.7' fill='%23c9a24a' stroke='none'/%3E%3C/svg%3E") no-repeat center / contain;
}

.hosts__corners i:nth-child(1) { top: 0; left: 0; }
.hosts__corners i:nth-child(2) { top: 0; right: 0; transform: scaleX(-1); }
.hosts__corners i:nth-child(3) { bottom: 0; left: 0; transform: scaleY(-1); }
.hosts__corners i:nth-child(4) { bottom: 0; right: 0; transform: scale(-1); }

.hosts__group { position: relative; }

/* Each heading between two short gold hairlines. */
.hosts__label {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: var(--gold-deep);
}

.hosts__label::before,
.hosts__label::after {
  content: "";
  flex: 0 1 1.6rem;
  min-width: .6rem;
  height: 1px;
}

.hosts__label::before { background: linear-gradient(90deg, transparent, var(--gold)); }
.hosts__label::after  { background: linear-gradient(90deg, var(--gold), transparent); }

.hosts__names {
  margin: .7rem 0 0;
  padding: 0;
  list-style: none;
}

.hosts__names li {
  font-family: var(--font-display);
  font-size: clamp(1.1rem, 4.8vw, 1.35rem);
  font-weight: 700;
  line-height: 1.3;
  color: var(--navy);
}

.hosts__names li + li { margin-top: .5rem; }

.hosts__names span { white-space: nowrap; }

/* The RSVP's address, quieter than the name above it. */
.hosts__address {
  margin-top: .2rem;
  font-family: var(--font-body);
  font-style: italic;
  font-size: clamp(1.05rem, 4.4vw, 1.2rem);
  font-variant-numeric: lining-nums;
  color: var(--gold-ink);
}

/* Two couples side by side, each wrapping within its own column, with a
   gold hairline between them. */
.hosts__names--pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: start;
}

.hosts__names--pair li {
  padding: 0 .5rem;
  font-size: clamp(.95rem, 3.9vw, 1.2rem);
}

.hosts__names--pair li + li {
  margin-top: 0;
  border-left: 1px solid rgba(201, 162, 74, .45);
}

.hosts__names--pair span {
  display: block;
  white-space: normal;
}

/* Each couple joined by a gold script ampersand. */
.hosts__names em {
  font-family: var(--font-script);
  font-size: 1.3em;
  font-style: normal;
  font-weight: 400;
  line-height: 0;
  color: var(--gold-deep);
  /* The script "&" leans into whatever follows it; this keeps a clear gap
     before the second name. */
  margin: 0 .18em 0 .04em;
}

/* A gold hairline either side of a small heart, between the two lists. */
.hosts__divider {
  display: flex;
  align-items: center;
  gap: .7rem;
  width: min(200px, 60%);
  margin: 1.5rem auto;
}

.hosts__divider::before,
.hosts__divider::after {
  content: "";
  flex: 1;
  height: 1px;
}

.hosts__divider::before { background: linear-gradient(90deg, transparent, var(--gold)); }
.hosts__divider::after  { background: linear-gradient(90deg, var(--gold), transparent); }

.hosts__divider svg {
  width: 16px;
  height: 16px;
  fill: rgba(201, 162, 74, .25);
  stroke: var(--gold-deep);
  stroke-width: 1.6;
}

/* The client's pile of toile-wrapped gifts, bobbing gently at the foot of
   the page once it has come into view. */
.hosts__gifts {
  width: min(320px, 82%);
  height: auto;
  margin-top: 2.4rem;
  filter: drop-shadow(0 18px 20px rgba(34, 48, 90, .18));
}

.hosts__gifts.is-in { animation: gifts-bob 5s ease-in-out 1.2s infinite; }

@keyframes gifts-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-8px); }
}

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

/* The invitation to send a blessing, under the card. */
.hosts__send {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 2.2rem;
}

.hosts__send-lead {
  font-family: var(--font-script);
  font-size: clamp(1.9rem, 8vw, 2.3rem);
  line-height: 1.1;
  color: var(--navy-film);
}

.hosts__wish {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  margin-top: .9rem;
  padding: .8rem 1.7rem;
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--paper);
  background: var(--navy);
  border: 0;
  border-radius: 9999px;
  box-shadow: 0 10px 20px rgba(34, 48, 90, .22), inset 0 0 0 1px rgba(201, 162, 74, .55);
  cursor: pointer;
  transition: background .25s ease, transform .2s ease, box-shadow .25s ease;
}

.hosts__wish:hover {
  background: var(--blue-deep);
  transform: translateY(-2px);
  box-shadow: 0 14px 24px rgba(34, 48, 90, .28), inset 0 0 0 1px rgba(201, 162, 74, .7);
}

.hosts__wish:active { transform: scale(.96); }

.hosts__wish:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
}

.hosts__wish svg {
  width: 1.3rem;
  height: 1.3rem;
  fill: none;
  stroke: #e9cf8c;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Read aloud to screen readers, never shown. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* The released balloons, over everything in the section but never in the
   way of a tap. Each one starts just above the button (its left, bottom and
   --rise are set in assets/js/blessings.js), floats up out of the section,
   swaying on the way, and fades as it goes. */
.blessings {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.blessing {
  position: absolute;
  width: 11.5rem;
  margin-left: -5.75rem;
  animation: blessing-rise 15s linear forwards;
}

.blessing__ride {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  transform-origin: 50% 0;
  animation: blessing-sway 4s ease-in-out infinite alternate;
}

.blessing img {
  width: 6.25rem;
  height: auto;
  filter: drop-shadow(0 10px 18px rgba(34, 48, 90, .2));
}

/* Upright and bold rather than italic: only the regular weight of the italic
   is loaded, and it reads too thin on a moving balloon. The tag is a small
   copy of the hosts card: the same warm paper and the same gold line just
   inside its edge, so it reads as part of the invitation rather than a
   white label. */
.blessing span {
  margin-top: .45rem;
  padding: .6rem 1rem;
  font-family: var(--font-body);
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.3;
  color: var(--navy);
  background: linear-gradient(180deg, var(--card) 0%, var(--card-deep) 100%);
  border: 1px solid rgba(201, 162, 74, .45);
  border-radius: 12px;
  box-shadow:
    inset 0 0 0 3px var(--card),
    inset 0 0 0 4px rgba(201, 162, 74, .6),
    0 8px 18px rgba(34, 48, 90, .14);
}

/* Full size and fully shown for almost the whole flight, so the blessing
   can be read all the way up. */
@keyframes blessing-rise {
  0%   { transform: translateY(0) scale(.92); opacity: 0; }
  6%   { transform: translateY(calc(var(--rise) * .03)) scale(1); opacity: 1; }
  90%  { opacity: 1; }
  100% { transform: translateY(var(--rise)) scale(1); opacity: 0; }
}

/* A gentle drift side to side, with only a slight tilt so the words stay
   level enough to read. */
@keyframes blessing-sway {
  from { transform: translateX(-10px) rotate(-1.5deg); }
  to   { transform: translateX(10px) rotate(1.5deg); }
}

/* Without motion, the balloon and its blessing simply appear above the
   button for a moment and fade. */
@keyframes blessing-fade {
  0%, 100% { opacity: 0; }
  10%, 82% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .blessing { animation-name: blessing-fade; }
  .blessing__ride { animation: none; }
}
