/* =========================================================
   Adam & Emirjeta — Wedding Site
   "Open Air" system: no boxes. Text sits directly on the photo
   inside soft gradient scrims anchored to the text zones; the
   rest of every photo stays clear. One rule, two directions of
   light: dark photo → smoke scrim + ivory text + apricot accent;
   light (pastel) photo → cream scrim + ink text + dusty rose.
   ========================================================= */

:root {
  --ivory: #f7f3ec;
  --cream: #f3ede2;
  --sand:  #e8dfce;
  --charcoal: #2c2a26;
  --ink: #1b1a17;
  --sage: #6b7a5a;
  --sage-dark: #4f5c41;
  --gold: #b08a4a;
  --muted: #6e6a62;
  /* Over-photo accent: sunset apricot on the smoke scrims. The pastel
     theme overrides this to dusty rose. Use --gold for accents that sit
     on light/solid surfaces (white nav, loader) where apricot is too pale. */
  --accent: #e7b67c;
  /* Scrim color as bare RGB so gradients can vary the alpha. Smoke for
     the mountain photos; the pastel theme flips it to warm cream. The
     hero gets its own channel so the hybrid pastel-flowers-hero theme can
     keep the smoke hero while sections go cream. */
  --scrim-rgb: 16, 13, 10;
  --scrim-a: 0.55;
  --hero-scrim-rgb: 16, 13, 10;
  --hero-scrim-a: 0.78;
  /* Hero accent follows the hero photo, not the theme: apricot over the
     mountain hero even when the pastel sections below use dusty rose. */
  --hero-accent: #e7b67c;
  --shadow: 0 10px 30px rgba(28, 27, 23, 0.08);
  --shadow-lg: 0 20px 60px rgba(28, 27, 23, 0.18);
  --radius: 4px;
  --serif: "Marcellus", "Times New Roman", serif;
  /* The hero keeps the site's original display face (Cormorant Garamond);
     the rest of the redesign uses Marcellus. */
  --hero-serif: "Cormorant Garamond", "Times New Roman", serif;
  --sans:  "Jost", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --nav-h: 64px;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }

/* The page must never scroll sideways: the photo backdrop is position:fixed
   while the text scrims scroll with the content, so any horizontal scroll
   (caused e.g. by the story-gallery tiles translating outside their grid
   cells) visibly shears the scrims off the photo. `clip` kills the
   scrollable overflow without turning html/body into a scroll container;
   `hidden` is the fallback for older engines. */
html, body {
  overflow-x: hidden;
  overflow-x: clip;
}

/* Same shear, vertical axis: a rubber-band overscroll past the TOP edge
   bounces the content (and its scrims) down while the fixed bg-stage stays
   put. `.at-top` is toggled onto <html> by script.js only while scrollY is
   0, so upward overscroll is contained at the top without disabling the
   bottom bounce or normal scrolling. */
html.at-top,
html.at-top body {
  overscroll-behavior-y: none;
}

/* =========================================================
   Loader — covers everything until the hero image is ready
   ========================================================= */

body.is-loading { overflow: hidden; }
/* The viewport scrollbar lives on <html>, which body{overflow:hidden} doesn't
   lock — so the page behind the gate could still scroll. Lock the root too. */
html:has(body.is-loading) { overflow: hidden; }

/* SPA navigation transitions — softens the content swap between pages and
   between in-page sections. #spa-main fades out, the bg-stage cross-fades to
   the destination photo, then the new content fades back in. The two halves
   use different curves so the fade-in feels lighter and lands gently. */
#spa-main {
  opacity: 1;
  transition: opacity 520ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
#spa-main.spa-fade-out {
  opacity: 0;
  transition: opacity 360ms cubic-bezier(0.4, 0, 0.6, 1);
}
body.spa-loading { cursor: progress; }

/* Hero content fades in only on initial page load, once web fonts and
   translations are ready — avoids the Flash Of Unstyled Text where the
   serif headings briefly render in the system font and then snap to
   Cormorant Garamond. `body.is-loading` is set in the HTML markup so
   the hero starts invisible on first paint; `body.hero-ready` is added
   by setupGate() after `Promise.all([I18n.ready, document.fonts.ready])`
   and resolves the transition while the loader is still on top, so the
   reveal coincides with the loader fade-out.

   SPA navigation does not set `is-loading`, so re-rendered hero content
   (sub-page → home) starts visible — its existing #spa-main fade owns
   that transition instead. */
body.is-loading .hero-content { opacity: 0; }
body.hero-ready .hero-content { opacity: 1; transition: opacity 0.55s ease; }

.loader {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--ivory);
  display: flex;
  /* margin:auto on .loader-inner centers it; overflow-y lets the GATE scroll
     when it's taller than the viewport (short screens) without clipping the top,
     while the page behind stays locked. */
  overflow-y: auto;
  transition: opacity 0.7s ease, visibility 0.7s ease;
}
.loader.hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.loader-inner {
  margin: auto;
  padding: 1.5rem 1rem;
  text-align: center;
  animation: loaderFloat 3s ease-in-out infinite alternate;
}

.gate-portrait {
  width: clamp(86px, 14vw, 116px);
  height: clamp(86px, 14vw, 116px);
  margin: 0 auto 1.25rem;
  border-radius: 50%;
  overflow: hidden;
  box-shadow:
    0 0 0 1px rgba(196, 161, 92, 0.55),
    0 0 0 4px rgba(196, 161, 92, 0.18),
    0 6px 18px rgba(20, 17, 14, 0.35);
}
.gate-portrait img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.loader-monogram {
  font-family: var(--hero-serif);
  font-size: clamp(2.4rem, 6vw, 3.6rem);
  letter-spacing: 0.25em;
  color: var(--ink);
  font-weight: 400;
  line-height: 1;
}
.loader-monogram .amp {
  color: var(--gold);
  font-style: italic;
  font-size: 0.9em;
  margin: 0 0.05em;
}

.loader-shimmer {
  width: 180px;
  height: 1px;
  margin: 1.75rem auto 1.25rem;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--sand) 20%,
    var(--sage) 50%,
    var(--sand) 80%,
    transparent 100%
  );
  background-size: 200% 100%;
  animation: loaderShimmer 1.8s ease-in-out infinite;
}

/* Gate (password entry) — built on top of the loader styles */
.gate-eyebrow {
  margin: 1rem 0 0;
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.35em;
  font-size: 0.7rem;
  font-weight: 500;
  color: var(--muted);
}

.gate-prompt {
  margin: 0 0 1.25rem;
  font-family: var(--hero-serif);
  font-size: 1.15rem;
  color: var(--charcoal);
  font-style: italic;
  max-width: 32ch;
  margin-left: auto;
  margin-right: auto;
}

/* Hide the gate prompt + form during the initial loader window;
   script.js adds `gate-ready` to body once it's decided auth is needed. */
.gate-prompt,
.gate-form,
.gate-resend,
.gate-help,
.gate-fineprint,
.gate-error {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.55s ease 0.15s, transform 0.55s ease 0.15s;
  pointer-events: none;
}
body.gate-ready .gate-prompt,
body.gate-ready .gate-form,
body.gate-ready .gate-resend,
body.gate-ready .gate-help,
body.gate-ready .gate-fineprint,
body.gate-ready .gate-error {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.gate-form {
  display: flex;
  gap: 0.5rem;
  justify-content: center;
  flex-wrap: wrap;
  max-width: 420px;
  margin: 0 auto;
}
/* The `hidden` attribute must beat the display:flex above, or the code-entry
   step would show before a code is ever sent. */
.gate-form[hidden] { display: none; }

.gate-input {
  flex: 1 1 220px;
  min-width: 0;
  padding: 0.85rem 1rem;
  font-family: var(--sans);
  font-size: 0.95rem;
  letter-spacing: 0.05em;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--sand);
  border-radius: var(--radius);
  outline: none;
  transition: border-color .2s ease, box-shadow .2s ease;
}
.gate-input::placeholder {
  color: var(--muted);
  letter-spacing: 0.1em;
}
.gate-input:focus {
  border-color: var(--gold);
  box-shadow: 0 0 0 3px rgba(176, 138, 74, 0.15);
}
.gate-input[aria-invalid="true"] {
  border-color: #b85a4a;
  box-shadow: 0 0 0 3px rgba(184, 90, 74, 0.12);
}

.gate-submit {
  padding: 0.85rem 1.6rem;
  font-family: var(--sans);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.25em;
  font-weight: 500;
  color: var(--ivory);
  background: var(--ink);
  border: 1px solid var(--ink);
  border-radius: var(--radius);
  cursor: pointer;
  transition: background .2s ease, color .2s ease, transform .1s ease;
}
.gate-submit:hover { background: var(--gold); border-color: var(--gold); }
.gate-submit:active { transform: translateY(1px); }
.gate-submit:disabled { opacity: 0.85; cursor: default; }

/* Busy state: a spinning ring precedes the label ("Sending…", "Verifying…") so
   it's visually obvious the button is working, not just relabelled. */
.gate-submit.is-busy::before {
  content: '';
  display: inline-block;
  width: 0.8em;
  height: 0.8em;
  margin-right: 0.55em;
  vertical-align: -0.08em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: btnSpin 0.7s linear infinite;
}
@keyframes btnSpin { to { transform: rotate(360deg); } }

/* The invisible reCAPTCHA's floating badge lands in an awkward spot over the
   gate; hide it and show the required attribution text inline (.gate-fineprint). */
.grecaptcha-badge { visibility: hidden; }
.gate-fineprint {
  margin: 0.6rem auto 0;
  max-width: 40ch;
  color: var(--muted);
  font-size: 0.66rem;
  line-height: 1.5;
}
.gate-fineprint a { color: inherit; text-decoration: underline; }

.gate-error {
  margin: 1rem auto 0;
  max-width: 38ch;
  color: #a04a3a;
  font-size: 0.85rem;
  font-style: italic;
}

.gate-resend {
  margin: 0.85rem auto 0;
  text-align: center;
}
.gate-link {
  background: none;
  border: none;
  padding: 0;
  font-family: var(--sans);
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  color: var(--gold);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.gate-link:hover { color: var(--ink); }

.gate-help {
  margin: 1.1rem auto 0;
  max-width: 40ch;
  color: var(--muted);
  font-size: 0.8rem;
  line-height: 1.5;
}

/* Invisible reCAPTCHA mounts here; keep it out of the layout flow. */
#recaptcha-container { display: flex; justify-content: center; }
#recaptcha-container:empty { display: none; }

@keyframes loaderFloat {
  0%   { transform: translateY(0); }
  100% { transform: translateY(-6px); }
}
@keyframes loaderShimmer {
  0%   { background-position:  150% 0; }
  100% { background-position: -150% 0; }
}

body {
  margin: 0;
  font-family: var(--sans);
  font-weight: 300;
  font-size: 16px;
  line-height: 1.7;
  color: var(--ivory);
  background: #14110e;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* =========================================================
   Fixed mountain background stage — cross-fades between photos
   ========================================================= */

.bg-stage {
  position: fixed;
  /* Anchor to the *large* viewport, not `inset: 0`. With `bottom: 0` the
     element's bottom edge tracked the mobile visual viewport, so as the
     address bar collapsed/expanded the stage resized and
     `background-size: cover` rescaled the photo with it — visible as the
     bg "swelling" or shrinking during scroll. Pinning height to `100lvh`
     keeps the stage the size of the address-bar-collapsed viewport at
     all times; the address bar simply overlays the bottom strip when
     showing, with no layout change. `100vh` is the same on desktop and
     resolves to the large viewport on modern mobile too — kept as a
     fallback for older engines that don't recognise `lvh`. */
  top: 0;
  left: 0;
  right: 0;
  height: 100vh;
  height: 100lvh;
  z-index: -10;
  background: #14110e;
  overflow: hidden;
  pointer-events: none;
}
.bg-layer {
  position: absolute;
  inset: 0;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  opacity: 0;
  /* Slow, gentle cross-fade so scrolling between sections feels like a
     painted scene drifting in rather than a snap-cut. */
  transition: opacity 1.1s ease-in-out;
  will-change: opacity;
  /* Boost saturation harder to make the wildflower/peony colors pop —
     brightness compensates for the dark tint layer above. The admin
     "washout" controls override this via --bg-washout (theme-init.js);
     the fallback is the stock mountains look. */
  filter: var(--bg-washout, brightness(1.18) saturate(1.35) contrast(1.05));
}
.bg-layer.is-active { opacity: 1; }

/* Admin-picked background photos (see theme-init.js): each slot reads a
   CSS variable set on <html> from /api/site-settings. The fallback is the
   theme's stock photo — identical to the inline first-paint style on the
   markup, so an unset variable changes nothing. !important lets a set
   variable beat those inline styles. */
.bg-layer[data-bg="0"] { background-image: var(--bg-slot-0, url('/img/bg-snowy-peaks.jpg'))       !important; }
.bg-layer[data-bg="1"] { background-image: var(--bg-slot-1, url('/img/bg-wildflower-meadow.jpg')) !important; }
.bg-layer[data-bg="2"] { background-image: var(--bg-slot-2, url('/img/bg-peak-wildflowers.jpg'))  !important; }
.bg-layer[data-bg="3"] { background-image: var(--bg-slot-3, url('/img/bg-open-field.jpg'))        !important; }
.bg-layer[data-bg="4"] { background-image: var(--bg-slot-4, url('/img/bg-columbines.jpg'))        !important; }

/* The looping mountain clip only matches the snowy-peaks photo; when the
   admin picks a different hero photo, theme-init.js flags the swap here. */
html[data-video="off"] .bg-layer-video { display: none !important; }

/* Video laid over a bg-layer's poster image. The background-image on the
   .bg-layer is the still poster (same file as <video poster>), so first
   paint is the static photo and the looping clip takes over once decoded —
   no flash of empty backdrop on slow connections.

   The mask restricts the video to the valley-cloud band: the sky, sun,
   and rocky foreground come from the static JPG so they stay identical
   to the original photo. Both image and video use cover-fit on the same
   3:2 aspect, so the band lines up across viewport sizes. */
.bg-layer-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
  transition: opacity 3500ms ease-in-out;
  /* Mask disabled temporarily so the full video shows (sky/sun animate too).
     Re-enable by uncommenting to restrict animation to the valley band.
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0%,
    transparent 52%,
    black 64%,
    black 100%
  );
  mask-image: linear-gradient(
    to bottom,
    transparent 0%,
    transparent 52%,
    black 64%,
    black 100%
  );
  */
}
.bg-layer-video.bg-layer-video-hidden { opacity: 0; }

/* Constant dark wash that anchors every page at the same brightness. Living
   on .bg-stage (outside #spa-main) means it stays put during SPA fade
   transitions — without it, fading the content briefly revealed the bright
   underlying photo and produced a "flash" effect on click navigation. */
.bg-stage-tint {
  position: absolute;
  inset: 0;
  /* Open Air: the per-section scrims now do the legibility work, so the
     global wash drops to a whisper and the photos stay clear and bright
     everywhere text isn't. The admin tint controls override this via
     --bg-tint (theme-init.js). */
  background: var(--bg-tint, rgba(20, 17, 14, 0.14));
  pointer-events: none;
}

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

a { color: var(--sage-dark); text-decoration: none; transition: color .2s ease; }
a:hover { color: var(--gold); }

h1, h2, h3 {
  font-family: var(--serif);
  font-weight: 400;
  color: var(--ink);
  letter-spacing: 0.5px;
  line-height: 1.2;
  margin: 0 0 0.5em;
}

h2 { font-size: clamp(2rem, 4vw, 3rem); }
h3 { font-size: 1.5rem; }

p { margin: 0 0 1em; }
.muted { color: var(--muted); font-size: 0.95em; }

.eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.34em;
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--sage-dark);
  margin: 0 0 1em;
}
.eyebrow.light { color: var(--sand); }

.lede { font-size: 1.1rem; color: #4a4842; }
.lede.light { color: rgba(255,255,255,0.85); }

.center { text-align: center; }

/* =========================================================
   Layout
   ========================================================= */

.container {
  width: min(1140px, 92%);
  margin: 0 auto;
}
.container.narrow { max-width: 760px; }

.section {
  padding: clamp(4rem, 8vw, 7rem) 0;
  position: relative;
  color: var(--ivory);
  /* No scrim on the section box itself — a section's height is wildly
     variable (the Story gallery alone is thousands of px tall), so a single
     centered oval here would float in the middle of the section, nowhere
     near the heading. Instead the scrim is anchored to each text cluster
     below (.scrim-anchor::before), exactly where copy sits, with clear
     photo everywhere else. A light text-shadow backs it up. */
  background: transparent;
  text-shadow:
    0 1px 3px rgba(10, 8, 6, 0.55),
    0 2px 14px rgba(10, 8, 6, 0.4);
  /* When the nav links (or any hash-link) target a section, the browser
     aligns the section's top edge with y=0 of the viewport — directly
     under the fixed nav. On mobile the padding clamp lands at 4rem (=
     nav height), so the heading butts right up against the nav. Offset
     the scroll target by the nav height so the section's padding-top is
     visible as breathing room. Desktop already had room (padding clamps
     up to 7rem), but the extra margin doesn't hurt there either. */
  scroll-margin-top: var(--nav-h);
}
/* =========================================================
   Open Air text scrims — a soft oval of shade anchored to each text
   cluster (heading group, card text, timeline day, faq list), drawn by a
   ::before that extends a little past the text and fades to clear photo on
   every side. Anchoring to the text block (not the section) means the
   scrim always sits behind the copy no matter how tall the section is.
   `isolation: isolate` makes the block a stacking context so the z-index:-1
   scrim tucks behind the block's own text but still paints over the photo.
   The pastel theme swaps the smoke for warm cream further below.
   ========================================================= */
.section-head,
.section .container.narrow,
.card-body,
.info-block,
.faq details,
.timeline-section-head,
.timeline-day-list > li {
  position: relative;
  isolation: isolate;
}
.section-head::before,
.section .container.narrow::before,
.card-body::before,
.info-block::before,
.faq details::before,
.timeline-section-head::before,
.timeline-day-list > li::before {
  content: "";
  position: absolute;
  z-index: -1;
  /* Extend past the text block on all sides; negative inset keeps it
     symmetric without width/height calc. */
  inset: -2rem -3rem;
  pointer-events: none;
  /* Smooth single falloff (no flat plateau) so there's no visible "edge"
     to the shape, then a heavy blur feathers it into the photo — a soft
     cloud of shade behind the copy rather than a defined dark lozenge. */
  background: radial-gradient(
    ellipse 75% 80% at center,
    rgba(16, 13, 10, 0.6) 0%,
    rgba(16, 13, 10, 0.34) 50%,
    rgba(16, 13, 10, 0) 78%
  );
  filter: blur(22px);
}

/* Legacy extra-dim hook (was an RSVP-only darker wash). The text scrims
   now carry legibility, so this adds nothing on top. */
.section-dark { background: transparent; }
.section-dark h2 { color: var(--ivory); }

/* Headings inside the scrimmed sections */
.section h2, .section h3 { color: var(--ivory); }
.section .lede { color: rgba(247, 243, 236, 0.88); }
.section .muted { color: rgba(247, 243, 236, 0.65); }
.section .eyebrow { color: var(--accent); }

/* Cards: Open Air deletes the box. Text sits directly on the section
   scrim; the card's photo becomes a framed object laid on the scene. */
.section .card {
  background: transparent;
  color: var(--ivory);
  border: 0;
  box-shadow: none;
}
/* The box is gone, so the hover lift keeps only the gentle rise — a
   box-shadow here would paint a ghost rectangle around nothing. */
.section .card:hover {
  transform: translateY(-4px);
  box-shadow: none;
}
/* Text hangs straight on the scrim below the framed photo — no inset. */
.section .card .card-body { padding: 1.5rem 0.25rem 0; }
.section .card img {
  border-radius: 6px;
  border: 1px solid rgba(247, 243, 236, 0.32);
  box-shadow: 0 16px 40px rgba(8, 6, 4, 0.45);
}
.section .card h2,
.section .card h3 { color: var(--ivory); }
.section .card .lede,
.section .card p { color: rgba(247, 243, 236, 0.88); }
.section .card .muted { color: rgba(247, 243, 236, 0.6); }
.section .card .eyebrow { color: var(--accent); }
.section .card a { color: var(--accent); }
.section .card a:hover { color: var(--ivory); }
.section .card .card-link { color: var(--accent); }
.section .card .card-link:hover { color: var(--ivory); }

/* Info blocks: boxes deleted here too — headings + the section scrim do
   the structural work now. */
.section .info-block {
  background: transparent;
  color: var(--ivory);
  border-left: 0;
  box-shadow: none;
  padding-left: 0;
  padding-right: 0;
}
.section .info-block h3 { color: var(--ivory); }
.section .info-block p,
.section .info-block li { color: rgba(247, 243, 236, 0.88); }
.section .info-block .muted { color: rgba(247, 243, 236, 0.6); }
.section .info-block a { color: var(--accent); }
.section .info-block a:hover { color: var(--ivory); }

/* Timeline lives inside .section but defines its own card styling — see the
   .timeline rules later in this file. No global overrides needed here. */

/* FAQ styling is fully defined in its own block later in this file (the
   list is wrapped in a single dark card with row dividers). No global
   per-section overrides needed here. */

/* Btn-outline across all sections — ivory pill directly on the scrim */
.section .btn-outline,
.section .card .btn-outline,
.section .info-block .btn-outline {
  border-color: rgba(247, 243, 236, 0.45);
  color: var(--ivory);
  background: transparent;
}
.section .btn-outline:hover,
.section .card .btn-outline:hover,
.section .info-block .btn-outline:hover {
  background: var(--ivory);
  border-color: var(--ivory);
  color: var(--ink);
}

.section-head { margin-bottom: 3rem; }
.section-head h2 { margin-bottom: 0.5rem; }

/* =========================================================
   Navigation
   ========================================================= */

.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: var(--nav-h);
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 clamp(1rem, 4vw, 2.5rem);
  background: rgba(250, 247, 242, 0);
  backdrop-filter: blur(0);
  border-bottom: 1px solid transparent;
  /* Default = the "leaving white" direction: fast snap back to transparent
     (e.g., clicking the brand to return to the hero). The slow direction is
     defined per-state below so going from transparent → white still eases in
     gently. */
  transition:
    background .3s ease,
    backdrop-filter .3s ease,
    box-shadow .3s ease,
    border-bottom-color .3s ease;
}
.nav.scrolled {
  background: rgba(250, 247, 242, 0.95);
  backdrop-filter: blur(10px);
  box-shadow: 0 2px 20px rgba(0,0,0,0.05);
  border-bottom-color: var(--sand);
  transition:
    background .55s ease,
    backdrop-filter .55s ease,
    box-shadow .55s ease,
    border-bottom-color .55s ease;
}
/* Force the white-nav look during SPA navigation so sub-page → home doesn't
   flash through the transparent-hero nav style while content is swapping. */
body.spa-loading .nav {
  background: rgba(250, 247, 242, 0.96);
  backdrop-filter: blur(10px);
  box-shadow: 0 2px 20px rgba(0,0,0,0.05);
  border-bottom-color: var(--sand);
  transition:
    background .55s ease,
    backdrop-filter .55s ease,
    box-shadow .55s ease,
    border-bottom-color .55s ease;
}
body.spa-loading .nav-brand,
body.spa-loading .nav-menu a,
body.spa-loading .nav-signout,
body.spa-loading .lang-btn {
  color: var(--ink);
  transition: color .5s ease;
}
body.spa-loading .lang-btn { transition: background .2s ease, color .5s ease; }
body.spa-loading .nav-signout { transition: opacity .3s ease, color .5s ease; }
body.spa-loading .nav-toggle span {
  background: var(--ink);
  transition: background .5s ease, transform .3s ease, opacity .3s ease;
}

.nav-brand {
  font-family: var(--serif);
  font-size: 1.5rem;
  letter-spacing: 0.2em;
  color: var(--ivory);
  /* Fast going back to ivory (leaving white nav); slow going to ink is set
     per-state below so the ease-in still feels gentle. */
  transition: color .3s ease;
}
.nav-brand .amp {
  color: var(--hero-accent);
  font-style: italic;
  margin: 0 0.05em;
}
.nav.scrolled .nav-brand { color: var(--ink); transition: color .5s ease; }
.nav.scrolled .nav-brand .amp,
.explore-body .nav-brand .amp,
body.spa-loading .nav-brand .amp { color: var(--gold); }

.nav-menu {
  display: flex;
  gap: 2rem;
  list-style: none;
  margin: 0; padding: 0;
  align-items: center;
}
.nav-menu a {
  font-size: 0.8rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ivory);
  font-weight: 400;
  transition: color .3s ease;
}
.nav.scrolled .nav-menu a { color: var(--ink); transition: color .5s ease; }
.nav-menu a:hover { color: var(--gold); }

/* Active page highlight — gold text wins across every nav state (transparent
   hero, scrolled white, sub-page white, mid-SPA-loading). Specificity tied
   with the per-state color rules, so each variant needs its own selector. */
.nav-menu a.active,
.nav.scrolled .nav-menu a.active,
.explore-body .nav-menu a.active,
body.spa-loading .nav-menu a.active { color: var(--gold); }

/* RSVP CTA: an outlined ivory pill floating on the photo (Open Air keeps
   chrome translucent over the hero). On light nav surfaces (scrolled /
   sub-pages / mid-SPA) it flips to a gold-outlined ink pill. */
.nav-menu a.nav-cta {
  background: transparent;
  color: var(--ivory);
  border: 1px solid rgba(247, 243, 236, 0.55);
  padding: 0.5rem 1.2rem;
  border-radius: 999px;
  transition: background .25s ease, color .25s ease, border-color .25s ease;
}
.nav-menu a.nav-cta:hover {
  background: var(--ivory);
  color: var(--ink);
  border-color: var(--ivory);
}
.nav.scrolled .nav-menu a.nav-cta,
.explore-body .nav-menu a.nav-cta,
body.spa-loading .nav-menu a.nav-cta {
  color: var(--ink);
  border-color: var(--gold);
  background: transparent;
}
.nav.scrolled .nav-menu a.nav-cta:hover,
.explore-body .nav-menu a.nav-cta:hover,
body.spa-loading .nav-menu a.nav-cta:hover {
  background: var(--gold);
  color: var(--ivory);
  border-color: var(--gold);
}
/* Active CTA (e.g., RSVP link while on /rsvp.html) — inverted pill so the
   "you are here" treatment matches the hover affordance and the gold text-
   active rule above doesn't render gold-on-gold (invisible). */
.nav-menu a.nav-cta.active,
.nav.scrolled .nav-menu a.nav-cta.active,
.explore-body .nav-menu a.nav-cta.active,
body.spa-loading .nav-menu a.nav-cta.active {
  background: transparent;
  color: var(--gold);
  border-color: var(--gold);
}
.nav-signout-item { display: flex; align-items: center; }

/* Language switcher — small EN | SQ | PL toggle in the nav */
.nav-lang-item { display: flex; align-items: center; }
.lang-switcher {
  display: inline-flex;
  gap: 0;
  /* Always use the gold border so it stands out over both hero and scrolled-white nav */
  border: 1px solid var(--gold);
  border-radius: var(--radius);
  /* Clip the per-button flag backgrounds to the switcher's rounded corners. */
  overflow: hidden;
  transition: box-shadow .25s ease;
}
.lang-switcher:hover { box-shadow: 0 0 0 2px rgba(176, 138, 74, 0.15); }
/* Each button is now a flag chip — no letters, just the country flag. The
   button's `title` attribute and the switcher's aria-label keep screen
   readers informed. Inactive flags are desaturated so the active flag
   reads as the selected one in full color, with a gold ring around it. */
.lang-btn {
  position: relative;
  background: transparent;
  border: 0;
  padding: 0;
  width: 36px;
  height: 22px;
  cursor: pointer;
  border-right: 1px solid rgba(176, 138, 74, 0.5);
  overflow: hidden;
  transition: outline-color .25s ease;
}
.lang-btn:last-child { border-right: 0; }
.lang-btn > span { display: none; }
.lang-btn::before {
  content: "";
  position: absolute;
  inset: 0;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  /* Inactive flags are muted; full saturation reads as the active one. */
  filter: saturate(0.55) brightness(0.92);
  transition: filter .3s ease;
  pointer-events: none;
}
.lang-btn[data-lang="en"]::before { background-image: url("/img/flag-us.svg"); }
.lang-btn[data-lang="sq"]::before { background-image: url("/img/flag-al.svg"); }
.lang-btn[data-lang="pl"]::before { background-image: url("/img/flag-pl.svg"); }
.lang-btn:hover::before { filter: saturate(0.85) brightness(0.98); }
.lang-btn.is-active::before { filter: saturate(1) brightness(1); }
/* Active state: gold ring drawn inside the button on top of the flag.
   `outline` paints above content (including the ::before flag) so the
   ring is always visible; `outline-offset: -2px` keeps it inset. */
.lang-btn.is-active {
  outline: 2px solid var(--gold);
  outline-offset: -2px;
}
.nav-signout {
  background: none;
  border: 0;
  padding: 0.4rem 0.2rem;
  font-family: var(--sans);
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-weight: 400;
  color: var(--ivory);
  opacity: 0.7;
  cursor: pointer;
  transition: opacity .3s ease, color .3s ease;
}
.nav.scrolled .nav-signout { color: var(--ink); transition: color .5s ease; }
.nav-signout:hover { opacity: 1; color: var(--gold); }
.nav.scrolled .nav-signout:hover { color: var(--gold); }

.nav-toggle {
  display: none;
  background: none;
  border: 0;
  width: 32px;
  height: 32px;
  padding: 0;
  cursor: pointer;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
}
.nav-toggle span {
  display: block;
  width: 26px;
  height: 1.5px;
  background: var(--ivory);
  transition: background .3s ease, transform .3s ease, opacity .3s ease;
}
.nav.scrolled .nav-toggle span { background: var(--ink); transition: background .5s ease, transform .3s ease, opacity .3s ease; }

@media (max-width: 820px) {
  .nav-toggle { display: flex; }
  .nav-menu {
    position: absolute;
    top: var(--nav-h);
    left: 0; right: 0;
    flex-direction: column;
    background: var(--ivory);
    padding: 1.5rem 0;
    gap: 1rem;
    transform: translateY(-120%);
    transition: transform .35s ease;
    box-shadow: 0 10px 30px rgba(0,0,0,0.08);
  }
  .nav-menu.open { transform: translateY(0); }
  .nav-menu a { color: var(--ink); }
  /* Open dropdown has an ivory background; the sign-out button's default
     ivory color makes it invisible there when the nav itself is still in
     the transparent hero state (no `.nav.scrolled`). Force ink locally. */
  .nav-menu .nav-signout { color: var(--ink); }
}

/* =========================================================
   Hero
   ========================================================= */

.hero {
  position: relative;
  min-height: 100vh;
  /* Mobile browsers (Chrome especially) report `100vh` as the *largest*
     viewport — i.e. with the address bar collapsed. On initial load with
     the address bar showing, that pushes the hero's bottom (and the
     scroll-cue icon) past the visible viewport. `100svh` is the *small*
     viewport — always the address-bar-visible size — so the hero fits
     on first paint without causing a layout shift as the address bar
     retracts during scroll. */
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  color: var(--ivory);
  background: transparent;
  padding: var(--nav-h) 1rem 4rem;
  overflow: hidden;
  isolation: isolate;
}
/* Soft smoke behind the over-photo nav so the menu reads against bright
   skies. Scrolls away with the hero — the nav itself goes solid ivory on
   scroll, so there's never a gap in legibility. */
.hero::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 150px;
  background: linear-gradient(
    to bottom,
    rgba(var(--hero-scrim-rgb), 0.42),
    rgba(var(--hero-scrim-rgb), 0)
  );
  z-index: -1;
  pointer-events: none;
}

/* The hero's text scrim — a centered smoke band that holds only where the
   names live, dissolving to clear photo above and below. Both edges fade
   to transparent so the hero/welcome boundary never shows a hard line
   while scrolling (the bg-stage is fixed; the scrim scrolls). */
.hero-vignette {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(
    180deg,
    rgba(var(--hero-scrim-rgb), 0) 14%,
    rgba(var(--hero-scrim-rgb), var(--scrim-a)) 38%,
    rgba(var(--hero-scrim-rgb), var(--scrim-a)) 66%,
    rgba(var(--hero-scrim-rgb), 0) 90%
  );
}

.hero-content {
  max-width: 900px;
  width: 100%;
  /* The scrim does the legibility work — just a whisper of ambient shadow
     to keep glyph edges crisp where the band thins out. */
  text-shadow: 0 2px 22px rgba(10, 8, 6, 0.35);
}

.hero-eyebrow {
  text-transform: uppercase;
  letter-spacing: 0.42em;
  font-size: 0.78rem;
  font-weight: 400;
  margin-bottom: 1.5rem;
  color: var(--hero-accent);
}

.hero-names {
  font-family: var(--hero-serif);
  font-weight: 400;
  font-size: clamp(2.8rem, 8vw, 6rem);
  line-height: 1.05;
  margin: 0 0 1.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  align-items: center;
  color: var(--ivory);
  text-shadow: 0 1px 14px rgba(10, 8, 6, 0.3);
}
.hero-names .amp {
  font-style: italic;
  color: var(--hero-accent);
  font-size: 0.7em;
  font-weight: 300;
}

.hero-meta {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  font-size: 0.85rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(247, 243, 236, 0.88);
  margin-bottom: 2rem;
}
.hero-meta .dot { opacity: 0.6; }

.hero-countdown {
  display: flex;
  justify-content: center;
  gap: clamp(0.75rem, 3vw, 2rem);
  margin: 2rem 0;
  flex-wrap: wrap;
}
.hero-countdown .unit {
  min-width: 70px;
}
.hero-countdown .num {
  display: block;
  font-family: var(--hero-serif);
  font-size: clamp(1.8rem, 4vw, 2.5rem);
  font-weight: 400;
  line-height: 1;
}
.hero-countdown .label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  opacity: 0.8;
}

.hero-scroll {
  position: absolute;
  bottom: 2rem;
  left: 50%;
  transform: translateX(-50%);
  width: 24px;
  height: 40px;
  border: 1px solid rgba(255,255,255,0.6);
  border-radius: 12px;
  display: flex;
  justify-content: center;
  padding-top: 8px;
}
.hero-scroll span {
  width: 2px;
  height: 8px;
  background: rgba(255,255,255,0.8);
  border-radius: 1px;
  animation: scrollDot 1.8s infinite;
}
@keyframes scrollDot {
  0% { transform: translateY(0); opacity: 1; }
  100% { transform: translateY(12px); opacity: 0; }
}

/* =========================================================
   Cards & grids
   ========================================================= */

.grid-2, .grid-3 {
  display: grid;
  gap: 2rem;
}
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }

/* ---------- Our Story gallery ----------
   Gallery-wall / "stack of prints" feel: 3-col grid with a wide hero tile
   in row 2. Each photo gets its own rotation, two-axis offset, scale, and
   resting z-index, so the row reads as a scattered handful of prints —
   some overlap their neighbors at the corners, none sit perfectly square.
   Hovered tile straightens, scales way up, and lifts above the rest. */
.story-gallery {
  display: grid;
  /* 4 cols × 2 rows = 8 tiles exactly. Photos read smaller because each
     cell is narrower; overlap then comes from the per-tile translates. */
  grid-template-columns: repeat(4, 1fr);
  /* No grid gap — overlap is driven entirely by per-tile translate. */
  gap: 0;
}
.story-photo {
  position: relative;
  margin: 0;
  /* Don't stretch to fill the grid row — when one tile in a row is hovered,
     its padding animates to 0, which makes its img (width:100% + aspect-ratio
     3/4) ~5px taller intrinsically and grows the row. With default
     align-self:stretch, the other tiles in the row would grow too — their
     img would stay its computed size and the leftover row space would
     appear as empty padding below the img (re-introducing the polaroid
     look on the non-hovered tiles). align-self:start pins each tile to
     its own intrinsic height so its neighbors don't get dragged along. */
  align-self: start;
  /* Photo-frame style: each tile is a small ivory mat with uniform
     padding on all sides, a gold outline + inner gold matting line, and a
     soft warm drop shadow. The frame stays distinct enough that adjacent
     prints read as separate tiles even when their corners overlap. On
     hover the padding animates to 0 so the photo fills the card edge-to-edge.
     Aspect-ratio is set on the FIGURE (not the img) so the tile's outer
     dimensions are fixed regardless of padding — when padding shrinks
     on hover, only the img inside grows; the row height never changes,
     so other rows don't shift. */
  aspect-ratio: 3 / 4;
  padding: 7px;
  background:
    linear-gradient(180deg, var(--ivory) 0%, #f6efe1 100%);
  overflow: hidden;
  border-radius: 8px;
  box-shadow:
    0 0 0 2px rgba(176, 138, 74, 0.85),
    0 6px 14px rgba(58, 42, 18, 0.3);
  /* `--rot` / `--tx` / `--ty` / `--s` / `--zr` are set per-tile below.
     Function order (scale → translate → rotate) matches the reveal entry
     and visible states so transitions interpolate cleanly. */
  --rot: 0deg;
  --tx: 0px;
  --ty: 0px;
  --s: 0.8;
  --zr: 1;
  transform: scale(var(--s)) translate(var(--tx), var(--ty)) rotate(var(--rot));
  transform-origin: center center;
  z-index: var(--zr);
  transition:
    transform .6s cubic-bezier(.22,1.08,.36,1),
    box-shadow .6s ease,
    padding .55s cubic-bezier(.22,1.08,.36,1),
    z-index 0s linear .6s;
  cursor: zoom-in;
}
.story-photo img {
  display: block;
  width: 100%;
  /* Fill the figure's content area exactly — height comes from the figure's
     aspect-ratio + the figure's width, not from img's own aspect-ratio. */
  height: 100%;
  object-fit: cover;
  /* Inner corner radius is sized so the photo's curve is roughly concentric
     with the figure's outer radius (8px) minus the 7px padding. */
  border-radius: 5px;
  transition: transform 1s ease;
}

/* Per-tile rotation + offset + scale + resting z.
   Each tile picks a neighbor to lean into; translates are sized to clear
   the empty margin on both sides (~25-30px per side at scale ~0.85 in a
   ~350px cell), so corners physically overlap by ~15-30px. zr varies so
   the stacking pattern reads as "tossed prints", not painted in HTML order.
   The wide hero (#5) stays calmest — the focal anchor of the row. */
/* Row 1 (tiles 1–4) and Row 2 (tiles 5–8). Translates lean each tile toward
   a neighbor with alternating signs so adjacent pairs collide horizontally,
   and the rows reach toward each other vertically. Magnitudes are sized to
   exceed the empty margin around each scaled-down tile (~45px per side at
   scale 0.7 in a ~300px cell) so corners physically overlap by 30-50px. */
.story-photo:nth-child(8n+1) { --rot: -5deg;  --tx:  75px; --ty:  55px; --s: 0.72; --zr: 2; }
.story-photo:nth-child(8n+2) { --rot:  4deg;  --tx: -60px; --ty: -45px; --s: 0.70; --zr: 3; }
.story-photo:nth-child(8n+3) { --rot: -3deg;  --tx:  70px; --ty:  58px; --s: 0.74; --zr: 2; }
.story-photo:nth-child(8n+4) { --rot:  5deg;  --tx: -65px; --ty: -48px; --s: 0.71; --zr: 4; }
.story-photo:nth-child(8n+5) { --rot:  3deg;  --tx:  65px; --ty: -55px; --s: 0.72; --zr: 3; }
.story-photo:nth-child(8n+6) { --rot: -4deg;  --tx: -60px; --ty:  55px; --s: 0.74; --zr: 4; }
.story-photo:nth-child(8n+7) { --rot:  6deg;  --tx:  75px; --ty: -50px; --s: 0.71; --zr: 2; }
.story-photo:nth-child(8n)   { --rot: -3deg;  --tx: -65px; --ty:  60px; --s: 0.73; --zr: 3; }

/* Hover zoom is desktop-only. Gated on `hover: hover` so touch devices never
   trigger it (a tap on mobile would otherwise "stick" the hover state), and
   on min-width so narrow laptop windows match the mobile/tablet behavior. */
@media (hover: hover) and (min-width: 821px) {
  .story-photo:hover {
    /* Straighten the tilt and pop much larger than the resting scale. */
    transform: scale(1.4) translate(0, 0) rotate(0deg);
    /* Photo fills the entire card edge-to-edge on hover — no polaroid
       strip showing at the bottom. */
    padding: 0;
    box-shadow:
      0 0 0 2px rgba(176, 138, 74, 0.85),
      0 30px 60px rgba(20, 17, 14, 0.42),
      0 12px 22px rgba(58, 42, 18, 0.32);
    z-index: 10;
    /* Snap z-index up immediately on hover-in so the lifted tile clears its
       neighbors; the delay only applies on hover-out (above) to let the
       transform settle before the stacking context drops back. */
    transition:
      transform .6s cubic-bezier(.22,1.08,.36,1),
      box-shadow .6s ease,
      padding .55s cubic-bezier(.22,1.08,.36,1),
      z-index 0s linear 0s;
  }
  .story-photo:hover img { transform: scale(1.05); }
}

/* The wide-tile class is preserved for narrower viewports (tablet falls
   back to a 2-col grid where the wide span makes sense). On the 4-col
   desktop grid it behaves like every other tile. */
.story-photo-wide img { object-fit: cover; }

/* Wrapper exists so the nav arrows can be positioned over the gallery in
   carousel mode. On desktop the wrapper is layout-neutral and the arrows
   are hidden. */
.story-gallery-wrap { position: relative; }
.story-nav { display: none; }

@media (max-width: 820px) {
  .story-gallery { grid-template-columns: repeat(2, 1fr); gap: 1rem; }
  .story-photo-wide { grid-column: span 1; }
  /* Reset the gallery-wall tilts on tablet — they're sized for the 4-col
     desktop layout and feel exaggerated at this width. */
  .story-photo { --rot: 0deg; --tx: 0px; --ty: 0px; --s: 0.94; }
}

/* Mobile: convert the grid into a one-photo-at-a-time swipe carousel with
   side arrows. CSS scroll-snap gives free touch swipe; the JS in script.js
   wires the buttons to scroll-by-one. */
@media (max-width: 500px) {
  .story-gallery {
    display: flex;
    grid-template-columns: none;
    gap: 0;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    scrollbar-width: none;
    -ms-overflow-style: none;
    /* Bleed slightly past the container so the snapped photo can fill the
       full screen width without padding clipping it. */
    margin: 0 calc(-1 * clamp(1rem, 4vw, 2rem));
    padding: 0 clamp(1rem, 4vw, 2rem);
  }
  .story-gallery::-webkit-scrollbar { display: none; }

  .story-photo {
    flex: 0 0 100%;
    margin-right: 1rem;
    scroll-snap-align: center;
    transform: none;
    cursor: default;
  }
  .story-photo:last-child { margin-right: 0; }
  .story-photo:hover {
    transform: none;
    box-shadow: var(--shadow);
    z-index: auto;
  }
  .story-photo:hover img { transform: none; }
  .story-photo-wide { grid-column: auto; }

  /* Skip the fly-in animation on mobile — the carousel scrolls horizontally
     within the gallery, so vertical-scroll intersect logic doesn't apply
     and any leftover transform would fight the scroll-snap. */
  .story-photo.reveal,
  .story-photo.reveal:nth-child(odd),
  .story-photo.reveal:nth-child(even),
  .story-photo.reveal.visible {
    opacity: 1;
    transform: none;
    transition: none;
  }

  /* Side arrows. Stay vertically centred on the visible photo so they read
     as carousel controls regardless of which slide is on screen. */
  .story-nav {
    display: flex;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: rgba(250, 247, 242, 0.94);
    border: 1px solid var(--sand);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
    align-items: center;
    justify-content: center;
    color: var(--ink);
    font-family: var(--serif);
    font-size: 1.6rem;
    line-height: 1;
    padding-bottom: 3px; /* optical centre for the chevron glyph */
    cursor: pointer;
    z-index: 10;
    transition: opacity .2s ease, background .2s ease;
    -webkit-tap-highlight-color: transparent;
  }
  .story-nav:hover { background: var(--ivory); }
  .story-nav:active { background: var(--sand); }
  .story-nav:disabled { opacity: 0.35; cursor: default; }
  .story-nav-prev { left: 6px; }
  .story-nav-next { right: 6px; }
}

/* =========================================================
   Story gallery scroll-in animation — overrides the generic .reveal so
   each photo flies in from its own side with a staggered delay. Tiles
   alternate left/right entry with a slight rotation so the section feels
   alive as the user scrolls into it.
   ========================================================= */
.story-photo.reveal {
  opacity: 0;
  transform: scale(0.78) translate(0, 60px) rotate(0deg);
  /* Entry transition: smooth ease for the scroll-in. Hover-out also falls
     back to this curve (acceptable — hover-out as a smooth ease feels fine).
     The z-index delay equals the transform duration so the lifted hover
     tile drops its stacking context AFTER its transform settles back. */
  transition:
    opacity .9s cubic-bezier(0.22, 0.61, 0.36, 1),
    transform .9s cubic-bezier(0.22, 0.61, 0.36, 1),
    box-shadow .9s ease,
    z-index 0s linear .9s;
  will-change: transform, opacity;
}
.story-photo.reveal:nth-child(odd) {
  transform: scale(0.78) translate(-40px, 60px) rotate(-3deg);
}
.story-photo.reveal:nth-child(even) {
  transform: scale(0.78) translate(40px, 60px) rotate(3deg);
}
.story-photo.reveal.visible {
  opacity: 1;
  /* Settle into the per-tile gallery-wall position. Function order
     (scale → translate → rotate) matches the FROM state above for clean
     interpolation. All four vars come from the per-tile rules on
     .story-photo:nth-child(N). */
  transform: scale(var(--s)) translate(var(--tx), var(--ty)) rotate(var(--rot));
}
/* Hover after the photo has revealed — re-specified at higher specificity
   so it wins against the .reveal.visible transform. Same hover/desktop gate
   as the base rule so mobile and narrow laptop windows don't trigger it. */
@media (hover: hover) and (min-width: 821px) {
  .story-photo.reveal.visible:hover {
    /* Straighten the tilt and pop dramatically larger. */
    transform: scale(1.4) translate(0, 0) rotate(0deg);
    /* Photo fills the entire card edge-to-edge on hover — no polaroid
       strip showing at the bottom. */
    padding: 0;
    z-index: 10;
    box-shadow:
      0 0 0 2px rgba(176, 138, 74, 0.85),
      0 30px 60px rgba(20, 17, 14, 0.42),
      0 12px 22px rgba(58, 42, 18, 0.32);
    /* Hover-in uses a springier, snappier curve. Hover-out reverts to the
       .reveal smooth ease (above) — and z-index drops after that settles. */
    transition:
      transform .55s cubic-bezier(.22, 1.08, .36, 1),
      box-shadow .35s ease,
      padding .55s cubic-bezier(.22, 1.08, .36, 1),
      z-index 0s linear 0s;
  }
}

/* Cascade the entries row-by-row so the section reveals in a wave rather
   than all at once. */
.story-photo.reveal:nth-child(8n+1) { transition-delay: 0ms; }
.story-photo.reveal:nth-child(8n+2) { transition-delay: 90ms; }
.story-photo.reveal:nth-child(8n+3) { transition-delay: 180ms; }
.story-photo.reveal:nth-child(8n+4) { transition-delay: 0ms; }
.story-photo.reveal:nth-child(8n+5) { transition-delay: 120ms; }
.story-photo.reveal:nth-child(8n+6) { transition-delay: 0ms; }
.story-photo.reveal:nth-child(8n+7) { transition-delay: 90ms; }
.story-photo.reveal:nth-child(8n)   { transition-delay: 180ms; }

/* =========================================================
   Mobile 3D ring carousel — overrides the basic horizontal scroll-snap above
   for motion-friendly users at narrow widths. The gallery becomes a vertical-
   axis ring; each photo is absolutely positioned at its own angle around the
   ring (rotateY → translateZ). JS spins the ring via inline transform on
   .story-gallery, decays the velocity with friction (momentum), and toggles
   .is-front on the photo nearest the camera so it scales up.
   Users with prefers-reduced-motion fall back to the scroll-snap version.
   ========================================================= */
@media (max-width: 500px) and (prefers-reduced-motion: no-preference) {
  .story-gallery-wrap {
    perspective: 1100px;
    perspective-origin: 50% 50%;
    /* Break out of the .container's 92% width to span the full viewport, so
       photos clip at the screen edges instead of mid-section margins —
       "visible until they fall off the screen". margin-left's `50% - 50vw`
       shifts the wrap from container's left edge (~4vw in) back to viewport
       0; width 100vw fills the rest. */
    width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    /* The gallery's rotateX(-14°) shifts the front photo's center DOWN by
       roughly R · sin(14°) · perspective(~1.4×) ≈ 90–125px on screen, while
       the back photo's center shifts UP only ~50–65px. The wrap needs to
       contain that asymmetric span: the front projects ~250px below center
       at the widest viewport (full-pop scale + perspective), and the back
       ~190px above center after the gallery shift. Heights below give a
       generous margin on both sides. */
    height: clamp(360px, 115vw, 480px);
    overflow: hidden;
    user-select: none;
    -webkit-user-select: none;
    /* Vertical page pan still belongs to the browser; horizontal we capture
       in JS to spin the ring. */
    touch-action: pan-y;
  }
  .story-gallery {
    display: block;
    grid-template-columns: none;
    /* Shift the gallery's vertical anchor UP so that the front photo, after
       being pushed DOWN by the gallery's rotateX(-14°) tilt, lands at the
       wrap's visual center. Without this shift the carousel was cut off at
       the bottom no matter how tall the wrap. Scales with viewport because
       the ring radius (and therefore the displacement) scales too. */
    position: absolute;
    top: calc(-1 * var(--ring-tilt-shift));
    right: 0;
    bottom: var(--ring-tilt-shift);
    left: 0;
    margin: 0;
    padding: 0;
    overflow: visible;
    overflow-x: visible;
    scroll-snap-type: none;
    transform-style: preserve-3d;
    will-change: transform;
    cursor: grab;
    /* Larger ring radius pushes the back photos further away (= smaller via
       perspective, more "floaty") and the front closer to the camera
       (= bigger, more pop). */
    --ring-radius: clamp(220px, 65vw, 300px);
    --ring-tilt-shift: clamp(60px, 18vw, 90px);
  }
  .story-gallery.is-dragging { cursor: grabbing; }

  /* Swipe-to-spin covers navigation on the 3D ring; the prev/next chevrons
     from the scroll-snap fallback are visual noise here. JS click handlers
     stay wired up but can't fire while the buttons are hidden. */
  .story-nav { display: none; }

  /* Comma-list every variant that the desktop / scroll-snap / reveal rules
     above also target, so the 3D-ring transform wins regardless of which
     classes the IntersectionObserver has applied. */
  .story-gallery .story-photo,
  .story-gallery .story-photo:hover,
  .story-gallery .story-photo.reveal,
  .story-gallery .story-photo.reveal:hover,
  .story-gallery .story-photo.reveal:nth-child(odd),
  .story-gallery .story-photo.reveal:nth-child(even),
  .story-gallery .story-photo.reveal.visible,
  .story-gallery .story-photo.reveal.visible:hover {
    position: absolute;
    top: 50%;
    left: 50%;
    width: clamp(140px, 44vw, 180px);
    height: auto;
    aspect-ratio: 3 / 4;
    margin: 0;
    /* The ivory frame is continuous: 3px when the photo is far from front
       (regular polaroid-style mat), shrinks linearly to 0 at the dead-front
       slot. Combined with the gold outline appearing via box-shadow below,
       this gives the "main picture loses the white frame, keeps the gold
       outline" effect that fades smoothly as photos rotate through. */
    padding: calc(3px * (1 - var(--photo-proximity, 0)));
    flex: 0 0 auto;
    scroll-snap-align: none;
    transform-origin: center center;
    /* All visual sizing AND the counter-tilt live in one transform string.
       rotateX(--photo-counter-tilt) sits between translateZ and scale so it
       rotates the photo around its OWN center after positioning, cancelling
       the gallery's rotateX(-14°) for whichever photo is closest to front
       (JS sets counter-tilt = +14° × proximity). No CSS transition on
       transform: that would fight the per-frame JS updates. */
    transform:
      translate(-50%, -50%)
      rotateY(var(--ring-angle, 0deg))
      translateZ(var(--ring-radius))
      rotateX(var(--photo-counter-tilt, 0deg))
      scale(var(--photo-scale, 1));
    cursor: inherit;
    /* Everything below — opacity, scale, padding, counter-tilt, the gold
       outline spread, and the drop-shadow blur — is driven by per-frame JS
       writes to these CSS variables. No CSS transitions: JS at 60Hz keeps
       it smooth and avoids the per-frame update vs. transition conflict. */
    opacity: var(--photo-op, 0.7);
    /* Two-layer box-shadow:
       1. Gold outline: spread radius scales from 0 to 2px with proximity,
          so the gold ring appears only on the front-most photo and fades
          smoothly into neighbors during transitions. Uses a solid color
          (no alpha) — the spread radius going to zero is what hides it.
       2. Drop shadow: blur and offset grow with proximity for extra "lift"
          on the focused photo, falling back to a softer ambient shadow for
          all other photos. */
    box-shadow:
      0 0 0 calc(2px * var(--photo-proximity, 0)) rgb(176, 138, 74),
      0 calc(14px + 10px * var(--photo-proximity, 0))
        calc(30px + 18px * var(--photo-proximity, 0))
        rgba(20, 17, 14, calc(0.32 + 0.18 * var(--photo-proximity, 0)));
  }

  /* .is-front is still toggled by JS at slot crossings for the haptic
     tick and for ARIA/state purposes, but visual prominence (gold outline,
     drop shadow, scale, counter-tilt) now flows entirely through the
     proximity-driven CSS variables above — so no visual rules attach to
     this class. */
  .story-gallery .story-photo.is-front,
  .story-gallery .story-photo.reveal.is-front,
  .story-gallery .story-photo.reveal.visible.is-front {
    z-index: 5;
  }

  /* Static per-tile angle around the ring (8 photos × 45° each). Set in CSS
     so the layout is correct before JS runs. */
  .story-gallery .story-photo:nth-child(1) { --ring-angle:   0deg; }
  .story-gallery .story-photo:nth-child(2) { --ring-angle:  45deg; }
  .story-gallery .story-photo:nth-child(3) { --ring-angle:  90deg; }
  .story-gallery .story-photo:nth-child(4) { --ring-angle: 135deg; }
  .story-gallery .story-photo:nth-child(5) { --ring-angle: 180deg; }
  .story-gallery .story-photo:nth-child(6) { --ring-angle: 225deg; }
  .story-gallery .story-photo:nth-child(7) { --ring-angle: 270deg; }
  .story-gallery .story-photo:nth-child(8) { --ring-angle: 315deg; }

  .story-gallery .story-photo img {
    transition: none;
    transform: none;
  }
}

.card {
  background: var(--ivory);
  border: 1px solid rgba(0,0,0,0.04);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
  transition: transform .35s ease, box-shadow .35s ease;
  display: flex;
  flex-direction: column;
}
.card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
}
.card img {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
}
.card-body {
  padding: 1.75rem;
  flex: 1;
  display: flex;
  flex-direction: column;
}
.card-body h3 { margin-bottom: 0.6rem; }
.card-body .eyebrow { margin-bottom: 0.5rem; }
.card-link {
  margin-top: auto;
  font-size: 0.85rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  font-weight: 500;
}

/* =========================================================
   Buttons
   ========================================================= */

.btn {
  display: inline-block;
  padding: 0.7rem 1.6rem;
  font-size: 0.74rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  font-weight: 400;
  border-radius: 999px;
  cursor: pointer;
  transition: all .25s ease;
  margin-top: 0.5rem;
}
.btn-outline {
  border: 1px solid var(--sage-dark);
  color: var(--sage-dark);
  background: transparent;
}
.btn-outline:hover {
  background: var(--sage-dark);
  color: var(--ivory);
}
.btn-gold {
  background: var(--gold);
  color: var(--ink);
  border: 1px solid var(--gold);
  text-decoration: none;
}
.btn-gold:hover {
  background: transparent;
  color: var(--gold);
}
.explore-cta-row { margin-top: 1.5rem; }

/* ----- /explore.html standalone page ----- */
.explore-body { background: var(--ivory); color: var(--ink); }

/* Sub-pages share the home page's bg-stage filter and tint — every page now
   reads at the same brightness. */

/* Sub-page header text on dark bg → light */
.explore-body .explore-header h1 { color: var(--ivory); }
.explore-body .explore-header .lede { color: rgba(250, 247, 242, 0.85); }
.explore-body .explore-header .eyebrow { color: var(--accent); }
.explore-body .explore-back-link { color: rgba(250, 247, 242, 0.75); }
.explore-body .explore-back-link:hover { color: var(--gold); }

/* Sub-pages keep the nav on a solid ivory pane at all times so it matches
   the main page's scrolled state — consistent across every route. */
.explore-body .nav {
  background: rgba(250, 247, 242, 0.96);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--sand);
  box-shadow: 0 2px 20px rgba(0,0,0,0.05);
  transition:
    background .55s ease,
    backdrop-filter .55s ease,
    box-shadow .55s ease,
    border-bottom-color .55s ease;
}
.explore-body .nav-brand,
.explore-body .nav-menu a,
.explore-body .nav-signout,
.explore-body .lang-btn {
  color: var(--ink);
  transition: color .5s ease;
}
.explore-body .lang-btn { transition: background .2s ease, color .5s ease; }
.explore-body .nav-signout { transition: opacity .3s ease, color .5s ease; }
.explore-body .nav-toggle span {
  background: var(--ink);
  transition: background .5s ease, transform .3s ease, opacity .3s ease;
}

/* Hover states for the always-white sub-page nav, plus the scrolled-white
   state on the main page — match the gold accent the dark nav uses on hover.
   Needs explicit selectors because `.explore-body .nav-menu a` / `.lang-btn`
   etc. share specificity with the base hover rules and would otherwise win
   the cascade and clobber the hover color. */
.explore-body .nav-menu a:hover,
.nav.scrolled .nav-menu a:hover,
.explore-body .nav-brand:hover,
.nav.scrolled .nav-brand:hover,
.explore-body .nav-signout:hover,
.nav.scrolled .nav-signout:hover,
.explore-body .lang-btn:hover,
.nav.scrolled .lang-btn:hover { color: var(--gold); }
.explore-body .lang-btn:hover,
.nav.scrolled .lang-btn:hover { background: rgba(176, 138, 74, 0.12); }
.explore-body .nav-menu a.nav-cta:hover,
.nav.scrolled .nav-menu a.nav-cta:hover {
  background: var(--gold);
  color: var(--ivory);
  border-color: var(--gold);
}

/* Sub-page sections + cards inherit the home page's styling — only text-color
   shims live here since base .section sets these for home already. */

/* Solid-style nav for pages without a dark hero behind */
.nav.nav-solid {
  background: rgba(250, 247, 242, 0.96);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--sand);
  box-shadow: 0 2px 20px rgba(0,0,0,0.04);
}
.nav.nav-solid .nav-brand { color: var(--ink); }
.nav.nav-solid .nav-menu a { color: var(--ink); }
.nav.nav-solid .nav-menu a.active { color: var(--gold); }
.nav.nav-solid .nav-toggle span { background: var(--ink); }

.explore-header {
  padding: calc(var(--nav-h) + 3rem) 0 2.5rem;
  text-align: center;
  /* Same soft radial scrim as the home sections, sized for the shorter
     header block. Literal rgba (see .section note). */
  background: radial-gradient(
    ellipse 78% 80% at 50% 46%,
    rgba(16, 13, 10, 0.55) 0%,
    rgba(16, 13, 10, 0.25) 46%,
    rgba(16, 13, 10, 0) 80%
  );
  text-shadow:
    0 1px 3px rgba(10, 8, 6, 0.55),
    0 2px 14px rgba(10, 8, 6, 0.4);
}
.explore-header h1 {
  font-family: var(--serif);
  font-size: clamp(2.2rem, 5vw, 3.4rem);
  font-weight: 400;
  color: var(--ink);
  margin: 0.4rem 0 1rem;
}
.explore-header .lede { color: var(--charcoal); }
.explore-header .eyebrow { color: var(--muted); }
.explore-back-link {
  display: inline-block;
  margin-top: 0.6rem;
  font-size: 0.75rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--sage-dark);
  text-decoration: none;
}
.explore-back-link:hover { color: var(--gold); }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin-top: 1rem;
}

/* =========================================================
   Info blocks (travel / accommodations)
   ========================================================= */

.info-block {
  background: #fff;
  border-left: 3px solid var(--sage);
  padding: 2rem clamp(1.5rem, 3vw, 2.5rem);
  margin-top: 2rem;
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.info-block h3 { margin-top: 0; }

.bullets {
  padding-left: 1.2rem;
  margin: 0 0 1rem;
}
.bullets li { margin-bottom: 0.5rem; }

/* =========================================================
   Timeline
   ========================================================= */

.timeline {
  list-style: none;
  padding: 0;
  margin: 0;
  max-width: 760px;
  margin-left: auto;
  margin-right: auto;
}

/* Open Air: no day-cards. Each day is a cluster of rows directly on the
   section scrim, separated by thin accent hairlines. */
.timeline > li {
  list-style: none;
  margin-bottom: 3rem;
  padding: 0;
  background: transparent;
  color: var(--ivory);
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.timeline > li:last-child { margin-bottom: 0; }

/* Day header — centred chapter-break style with an accent rule on each
   side. Italic accent subtitle for the wedding day. */
.timeline-section-head {
  display: flex;
  align-items: center;
  flex-direction: column;
  gap: 0.2rem;
  margin: 0 0 1.35rem;
  padding: 0 0 1.2rem;
  border-bottom: 1px solid rgba(231, 182, 124, 0.28);
  text-align: center;
}
.timeline-section-head h3 {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.45rem, 2.8vw, 1.9rem);
  color: var(--ivory);
  letter-spacing: 0.05em;
}
.timeline-section-head h3::before,
.timeline-section-head h3::after {
  content: "";
  width: clamp(36px, 6vw, 72px);
  height: 1px;
  background: linear-gradient(90deg,
    rgba(231, 182, 124, 0) 0%,
    rgba(231, 182, 124, 0.55) 50%,
    rgba(231, 182, 124, 0) 100%);
}
.timeline-section-head span {
  display: block;
  margin: 0;
  padding: 0;
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(0.9rem, 1.5vw, 1.02rem);
  letter-spacing: 0.05em;
  text-transform: none;
  color: var(--accent);
  background: transparent;
  border: none;
}

/* The day's events list — no chrome, just rows separated by thin gold rules. */
.timeline-day-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.timeline-day-list > li {
  display: flex;
  gap: 1.5rem;
  align-items: flex-start;
  padding: 0;
  background: transparent;
  border: none;
  box-shadow: none;
}
.timeline-day-list > li + li {
  margin-top: 1.1rem;
  padding-top: 1.1rem;
  border-top: 1px solid rgba(231, 182, 124, 0.15);
}

/* Time column on the left of each event row. */
.timeline-date {
  width: 92px;
  flex-shrink: 0;
  padding-right: 1.25rem;
  border-right: 1px solid rgba(231, 182, 124, 0.18);
  text-align: right;
  display: flex;
  align-items: flex-start;
}
.timeline-date .num {
  display: block;
  font-family: var(--serif);
  font-size: 1.15rem;
  line-height: 1.2;
  color: var(--accent);
  font-weight: 400;
}

/* Event body — the right side of each row, no chrome. */
.timeline-body {
  flex: 1;
  padding: 0;
  background: transparent;
  border: none;
  box-shadow: none;
  backdrop-filter: none;
  color: var(--ivory);
}
.timeline-body h4 {
  margin: 0 0 0.25rem;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.05rem, 2vw, 1.2rem);
  line-height: 1.3;
  color: var(--ivory);
}
.timeline-body .time {
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 0.6rem;
  font-weight: 500;
}
.timeline-body p {
  line-height: 1.55;
  color: rgba(250, 247, 242, 0.92);
  margin: 0 0 0.6rem;
}
.timeline-body p:last-child { margin-bottom: 0; }
.timeline-body .muted {
  margin-top: 0.5rem;
  font-size: 0.85rem;
  font-style: italic;
  color: rgba(250, 247, 242, 0.65);
}

/* Mobile: tighten spacing, stack each event row vertically. */
@media (max-width: 540px) {
  .timeline > li {
    padding: 0;
    margin-bottom: 2.25rem;
  }
  .timeline-section-head {
    margin: 0 0 1rem;
    padding-bottom: 0.95rem;
  }
  .timeline-section-head h3 {
    font-size: 1.15rem;
    gap: 0.7rem;
  }
  .timeline-section-head h3::before,
  .timeline-section-head h3::after { width: 24px; }
  .timeline-section-head span { font-size: 0.85rem; }

  .timeline-day-list > li { flex-direction: column; gap: 0.45rem; }
  .timeline-day-list > li + li { margin-top: 1rem; padding-top: 1rem; }
  .timeline-date {
    width: auto;
    text-align: left;
    border-right: none;
    padding-right: 0;
    padding-bottom: 0;
  }
  .timeline-date .num { font-size: 1rem; }
}

/* =========================================================
   FAQ (details/summary)
   ========================================================= */

/* Open Air: the FAQ rows sit directly on the section scrim — no card.
   Each <details> is a row separated by a thin accent divider. */
.faq {
  background: transparent;
  color: var(--ivory);
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
}
.faq details {
  border-bottom: 1px solid rgba(231, 182, 124, 0.22);
  padding: 1.25rem 0;
}
.faq details:last-child {
  border-bottom: none;
}
.faq summary {
  list-style: none;
  cursor: pointer;
  font-family: var(--serif);
  font-size: 1.25rem;
  color: var(--ivory);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+";
  font-family: var(--sans);
  font-weight: 300;
  font-size: 1.5rem;
  color: var(--accent);
  transition: transform .3s ease;
  line-height: 1;
}
.faq details[open] summary::after {
  transform: rotate(45deg);
}
.faq details p {
  margin-top: 0.9rem;
  margin-bottom: 0;
  line-height: 1.55;
  color: rgba(250, 247, 242, 0.92);
}

@media (max-width: 540px) {
  .faq { padding: 0; }
  .faq summary { font-size: 1.1rem; }
}

/* =========================================================
   RSVP form (inside the dark RSVP section)
   ========================================================= */

.rsvp-form {
  margin-top: 1rem;
}

/* Inline flash banner shown after a partial RSVP submit */
.rsvp-flash {
  margin: 0 0 1rem;
  padding: 0.85rem 1.1rem;
  background: rgba(231, 182, 124, 0.12);
  border: 1px solid rgba(231, 182, 124, 0.45);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  color: var(--ivory);
  font-family: var(--sans);
  font-size: 0.9rem;
  line-height: 1.45;
  opacity: 0;
  transform: translateY(-4px);
  transition: opacity 0.35s ease, transform 0.35s ease;
}
.rsvp-flash.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.rsvp-members {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-bottom: 2rem;
}

/* Member rows: quiet smoke chips on the scrim — bordered like the hero
   pills rather than opaque cards, so the photo still reads behind them. */
.rsvp-member {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1rem 1.25rem;
  background: rgba(16, 13, 10, 0.28);
  border: 1px solid rgba(247, 243, 236, 0.22);
  border-radius: 10px;
  flex-wrap: wrap;
}

.rsvp-member-info {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  min-width: 0;
}
.rsvp-member-name {
  font-family: var(--serif);
  font-size: 1.25rem;
  color: var(--ivory);
  font-weight: 400;
}
.rsvp-member-type {
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: rgba(250, 247, 242, 0.5);
}

.rsvp-choices {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.rsvp-radio {
  position: relative;
  cursor: pointer;
}
.rsvp-radio input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.rsvp-radio span {
  display: inline-block;
  padding: 0.65rem 1.2rem;
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 500;
  color: rgba(250, 247, 242, 0.78);
  background: transparent;
  border: 1px solid rgba(231, 182, 124, 0.35);
  border-radius: 999px;
  transition: background .2s ease, color .2s ease, border-color .2s ease;
  white-space: nowrap;
}
.rsvp-radio:hover span {
  border-color: var(--accent);
  color: var(--ivory);
}
.rsvp-radio input:checked + span {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink);
}
.rsvp-radio input:focus-visible + span {
  box-shadow: 0 0 0 3px rgba(231, 182, 124, 0.25);
}

/* "You" badge for the magic-link-authed member */
.rsvp-current-badge {
  width: 100%;
  margin: 0 0 0.4rem;
  font-family: var(--sans);
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.28em;
  color: var(--accent);
  font-weight: 600;
}
.rsvp-member.is-current {
  border-color: rgba(231, 182, 124, 0.55);
  box-shadow: 0 0 0 1px rgba(231, 182, 124, 0.2);
}

/* Email field + plus-one section live below the main row */
.rsvp-member .rsvp-field { margin-bottom: 0; }

.rsvp-email-field,
.rsvp-plus-one {
  width: 100%;
  margin-top: 0.7rem;
}

.rsvp-field-label {
  display: block;
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(250, 247, 242, 0.65);
  font-weight: 500;
  margin-bottom: 0.35rem;
}
.rsvp-field-hint {
  text-transform: none;
  letter-spacing: 0.02em;
  color: rgba(250, 247, 242, 0.4);
  font-size: 0.7rem;
  font-style: italic;
  font-weight: 400;
}

.rsvp-email-field input[type="email"],
.rsvp-plus-one input[type="text"] {
  width: 100%;
  padding: 0.7rem 0.9rem;
  font-family: var(--sans);
  font-size: 0.92rem;
  color: var(--ivory);
  background: rgba(20, 17, 14, 0.55);
  border: 1px solid rgba(231, 182, 124, 0.25);
  border-radius: var(--radius);
  outline: none;
  transition: border-color .2s ease, box-shadow .2s ease;
}
.rsvp-email-field input::placeholder,
.rsvp-plus-one input[type="text"]::placeholder {
  color: rgba(250, 247, 242, 0.4);
  font-style: italic;
}
.rsvp-email-field input:focus,
.rsvp-plus-one input[type="text"]:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(231, 182, 124, 0.15);
}

.rsvp-plus-one {
  padding: 0.9rem 1rem;
  background: rgba(231, 182, 124, 0.06);
  border-left: 2px solid rgba(231, 182, 124, 0.45);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.rsvp-plus-one-label {
  margin: 0 0 0.7rem;
  font-family: var(--sans);
  font-size: 0.7rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 600;
}
.rsvp-plus-one .rsvp-field { margin-bottom: 0.65rem; }
.rsvp-plus-one-choices { margin-top: 0.3rem; }

/* Scoped plus-one view — hide host-only fields, slim the layout */
body.rsvp-scoped #rsvpForm > .rsvp-field { display: none; }
.rsvp-scoped-note {
  width: 100%;
  margin: 0 0 0.6rem;
  font-family: var(--serif);
  font-size: 1rem;
  font-style: italic;
  color: rgba(250, 247, 242, 0.85);
}
.rsvp-scoped-note strong {
  font-weight: 400;
  color: var(--accent);
}
.rsvp-member.is-scoped {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.8rem;
}

/* The "everything else we should know" block — dietary, songs, notes.
   Open Air: no card — the labels and inputs sit directly on the section
   scrim, the bordered inputs provide their own affordance. */
.rsvp-extras {
  margin-top: 1.75rem;
  margin-bottom: 1.5rem;
  padding: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.rsvp-field {
  margin-bottom: 1.25rem;
}
.rsvp-field:last-child { margin-bottom: 0; }
.rsvp-field label {
  display: block;
  margin-bottom: 0.4rem;
  font-family: var(--serif);
  font-size: 1rem;
  letter-spacing: 0.01em;
  color: var(--ivory);
  font-weight: 400;
  text-transform: none;
}
.rsvp-field textarea {
  width: 100%;
  padding: 0.85rem 1rem;
  font-family: var(--sans);
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--ivory);
  background: rgba(20, 17, 14, 0.45);
  border: 1px solid rgba(231, 182, 124, 0.22);
  border-radius: var(--radius);
  resize: vertical;
  outline: none;
  transition: border-color .2s ease, box-shadow .2s ease;
}
.rsvp-field textarea::placeholder {
  color: rgba(250, 247, 242, 0.45);
  font-style: italic;
}
.rsvp-field textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(231, 182, 124, 0.15);
}

.rsvp-submit {
  display: inline-block;
  padding: 0.95rem 2.5rem;
  font-family: var(--sans);
  font-size: 0.8rem;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  font-weight: 500;
  color: var(--ink);
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: 999px;
  cursor: pointer;
  margin-top: 0.5rem;
  transition: background .2s ease, transform .1s ease;
}
.rsvp-submit:hover { background: var(--ivory); border-color: var(--ivory); }
.rsvp-submit:active { transform: translateY(1px); }

.rsvp-success {
  text-align: center;
  padding: 2rem 1rem;
}
.rsvp-success h3 {
  color: var(--ivory);
  margin-bottom: 0.75rem;
}

/* =========================================================
   Footer
   ========================================================= */

.footer {
  /* Deep mauve — closes the page and nods to the site's plum heritage.
     Shared by both themes (the design's one fixed point). */
  background: #3a2530;
  color: rgba(247, 236, 238, 0.85);
  padding: 3.5rem 0;
  text-align: center;
}
.footer-names {
  font-family: var(--serif);
  font-size: 1.65rem;
  color: #f7ecee;
  margin: 0 0 0.4rem;
  letter-spacing: 0.08em;
}
.footer-names .amp {
  color: #c98a9c;
  font-style: italic;
  margin: 0 0.05em;
}
.footer-cta {
  margin: 1.2rem 0 0;
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}
.footer-cta a {
  color: #f7ecee;
  padding: 0.65rem 1.5rem;
  border: 1px solid rgba(201, 138, 156, 0.55);
  border-radius: 999px;
  transition: background .25s ease, color .25s ease, border-color .25s ease;
}
.footer-cta a:hover {
  background: #c98a9c;
  color: #2b1c25;
  border-color: #c98a9c;
}

.footer-date {
  font-size: 0.78rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  margin: 0;
  color: rgba(247, 236, 238, 0.6);
  opacity: 1;
}

/* =========================================================
   Reveal on scroll
   ========================================================= */

.reveal {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity .8s ease, transform .8s ease;
}
.reveal.visible {
  opacity: 1;
  transform: translateY(0);
}

/* =========================================================
   Pastel Flowers theme — applied when <html data-theme="pastel-flowers">.
   Mountains stays the default (no <html data-theme>, or
   data-theme="mountains").

   Most rules below match both pastel variants via the prefix selector
   `html[data-theme^="pastel-flowers"]`, which also catches the hybrid
   variant `pastel-flowers-hero`. That variant inherits the full pastel
   palette, card / footer / RSVP / FAQ / loader / sub-page styling, and
   the four rotating pastel floral backgrounds for sections 1–4 — but
   keeps the mountain snowy-peaks video hero (bg-layer 0 + bg-layer-video
   + the ivory-on-dark hero text + over-hero nav) intact.

   Rules that must NOT apply to the hybrid stay scoped to the exact
   selector `html[data-theme="pastel-flowers"]`: the layer-0 floral
   override, the bg-layer-video hide, the hero text shadows / colors,
   the nav-over-hero text colors, the hamburger lines, and the bare
   nav hovers.

   Strategy:
     - Variable overrides for the shared palette (gold → dusty rose, etc.)
     - Bg-layer swap to the five pastel floral photos (uses !important to
       override the inline background-image style on bg-layer[data-bg=0],
       which is the mountain default poster for the snowy-peaks video).
     - Lighter tint on the page so the florals stay airy.
     - Recolored cards / nav / hero text shadows so dark-on-light reads
       well against the pale floral backdrop.
   ========================================================= */

html[data-theme^="pastel-flowers"] {
  /* Shared palette: dusty rose replaces gold as the metallic accent;
     plum-ink replaces charcoal for slightly warmer body text. */
  --rose:        #c98a9c;
  --rose-dark:   #a4607a;
  --lilac:       #b896c9;
  --peach:       #f0c4ad;
  --blush:       #fbeef0;
  --teal-mist:   #cfe1e0;
  --gold:        #c98a9c;          /* dusty rose stands in for gold */
  --sage:        #94a78c;
  --sage-dark:   #6e8466;
  --sand:        #f3dde2;
  --cream:       #fbf3ec;
  --ivory:       #fdf7f1;          /* warmer ivory, still pale */
  --muted:       #7a6a70;
  --charcoal:    #3a2f33;
  --ink:         #2b2024;
  /* Open Air, light direction: over the bright florals the scrim inverts
     to warm cream and the over-photo accent becomes dusty rose. */
  --accent:      #a4607a;
  --scrim-rgb:   253, 247, 241;
  --scrim-a:     0.66;
}
/* The full-pastel theme also flips the hero scrim to cream (ink-on-bloom
   hero). The hybrid pastel-flowers-hero variant is NOT matched here — it
   keeps the smoke scrim + ivory text over the mountain hero. */
html[data-theme="pastel-flowers"] {
  --hero-scrim-rgb: 253, 247, 241;
  --hero-scrim-a:   0.82;
  --hero-accent:    #a4607a;
}

/* Body bg falls behind the bg-stage; pale cream replaces the black so a
   slow-loading photo never reveals a dark gap behind the florals. */
html[data-theme^="pastel-flowers"] body         { background: #fbf3ec; color: var(--ink); }
html[data-theme^="pastel-flowers"] .bg-stage    { background: #fbf3ec; }

/* Pastel bg-layer photos — inline style on layer 0 wins by specificity
   unless we mark these !important. Sub-pages have a single .bg-layer
   without a data-bg attribute; the unsuffixed selector below gives them
   the bouquet photo as a sensible default. Each slot reads the same
   admin-set --bg-slot-N variable as the mountains rules above; only the
   stock fallbacks differ per theme. */
html[data-theme^="pastel-flowers"] .bg-layer              { background-image: var(--bg-slot-0, url('/img/bg-pastel-bouquet.jpg'))    !important; }
html[data-theme="pastel-flowers"]  .bg-layer[data-bg="0"] { background-image: var(--bg-slot-0, url('/img/bg-pastel-bouquet.jpg'))    !important; }
html[data-theme^="pastel-flowers"] .bg-layer[data-bg="1"] { background-image: var(--bg-slot-1, url('/img/bg-pastel-peonies.jpg'))    !important; }
html[data-theme^="pastel-flowers"] .bg-layer[data-bg="2"] { background-image: var(--bg-slot-2, url('/img/bg-pastel-watercolor.jpg')) !important; }
html[data-theme^="pastel-flowers"] .bg-layer[data-bg="3"] { background-image: var(--bg-slot-3, url('/img/bg-pastel-roses.jpg'))      !important; }
html[data-theme^="pastel-flowers"] .bg-layer[data-bg="4"] { background-image: var(--bg-slot-4, url('/img/bg-pastel-lace.jpg'))       !important; }

/* Hybrid theme: keep the mountain snowy-peaks photo on bg-layer 0 (the
   hero) — but only on the home page. The unsuffixed
   `.bg-layer { ... bouquet !important }` rule above would otherwise
   override the inline poster, since the `pastel-flowers`-exact override
   at layer 0 (which redirects to bouquet) doesn't fire for the hybrid.
   Sub-pages all use `body.explore-body` and re-use data-bg="0" on rsvp,
   so we scope this to `body:not(.explore-body)` — the home page only.
   Sub-pages fall through to the pastel `.bg-layer { bouquet }` fallback,
   which keeps the pastel look on every non-home route. */
html[data-theme="pastel-flowers-hero"] body:not(.explore-body) .bg-layer[data-bg="0"] {
  background-image: var(--bg-hero, url('/img/bg-snowy-peaks.jpg')) !important;
}

/* The mountain-snowy-peaks video element lives inside layer 0. Hide it for
   pastel so the floral poster image shows through unobstructed. The
   "pastel-flowers-hero" variant intentionally keeps the video visible so
   the mountain hero behaves exactly like the mountains theme. */
html[data-theme="pastel-flowers"] .bg-layer-video { display: none !important; }

/* Pastels are already light — drop the brightness boost the mountain
   theme uses so the floral colors stay saturated rather than washing out
   into white. Slight saturation lift instead, to keep the pinks/lavenders
   reading as rich colors not faded pastels. */
html[data-theme^="pastel-flowers"] .bg-layer {
  filter: var(--bg-washout, brightness(0.96) saturate(1.18) contrast(1.02));
}

/* Tint wash: a barely-there warm overlay instead of the dark mountain
   wash. Just enough to anchor the page brightness — the text halos
   already do the legibility work, so we don't need a heavy cream layer
   that drowns the florals. */
html[data-theme^="pastel-flowers"] .bg-stage-tint {
  background: var(--bg-tint, rgba(253, 247, 241, 0.14));
}

/* Section text flips from ivory-on-dark to ink-on-light; the text-cluster
   scrims and halos invert to warm cream so dark text stays legible over the
   bright florals. The sub-page header keeps its own cream radial (it's short,
   so a box-anchored oval centers fine). */
html[data-theme^="pastel-flowers"] .explore-header {
  background: radial-gradient(
    ellipse 78% 80% at 50% 46%,
    rgba(253, 247, 241, 0.66) 0%,
    rgba(253, 247, 241, 0.30) 46%,
    rgba(253, 247, 241, 0) 80%
  );
}
html[data-theme^="pastel-flowers"] .section-head::before,
html[data-theme^="pastel-flowers"] .section .container.narrow::before,
html[data-theme^="pastel-flowers"] .card-body::before,
html[data-theme^="pastel-flowers"] .info-block::before,
html[data-theme^="pastel-flowers"] .faq details::before,
html[data-theme^="pastel-flowers"] .timeline-section-head::before,
html[data-theme^="pastel-flowers"] .timeline-day-list > li::before {
  background: radial-gradient(
    ellipse 75% 80% at center,
    rgba(253, 247, 241, 0.72) 0%,
    rgba(253, 247, 241, 0.42) 50%,
    rgba(253, 247, 241, 0) 78%
  );
}
html[data-theme^="pastel-flowers"] .section,
html[data-theme^="pastel-flowers"] .explore-header {
  text-shadow:
    0 1px 0 rgba(253, 247, 241, 0.85),
    0 2px 14px rgba(253, 247, 241, 0.8);
}
html[data-theme^="pastel-flowers"] .section            { color: var(--ink); }
html[data-theme^="pastel-flowers"] .section h2,
html[data-theme^="pastel-flowers"] .section h3        { color: var(--ink); }
html[data-theme^="pastel-flowers"] .section .lede     { color: rgba(43, 32, 36, 0.88); }
html[data-theme^="pastel-flowers"] .section .muted    { color: rgba(43, 32, 36, 0.62); }
html[data-theme^="pastel-flowers"] .section .eyebrow  { color: var(--rose-dark); }
html[data-theme^="pastel-flowers"] .section-dark      { background: transparent; }
html[data-theme^="pastel-flowers"] .section-dark h2   { color: var(--ink); }

/* Cards: no paper — text sits on the cream scrim; photos are white-framed
   objects with a soft rose-tinted shadow. */
html[data-theme^="pastel-flowers"] .section .card,
html[data-theme^="pastel-flowers"] .section .info-block {
  background: transparent;
  color: var(--ink);
  border: 0;
  box-shadow: none;
}
html[data-theme^="pastel-flowers"] .section .card img {
  border: 1px solid rgba(255, 255, 255, 0.85);
  box-shadow: 0 16px 40px rgba(122, 74, 94, 0.28);
}
html[data-theme^="pastel-flowers"] .section .card h2,
html[data-theme^="pastel-flowers"] .section .card h3,
html[data-theme^="pastel-flowers"] .section .info-block h3 { color: var(--ink); }
html[data-theme^="pastel-flowers"] .section .card .lede,
html[data-theme^="pastel-flowers"] .section .card p,
html[data-theme^="pastel-flowers"] .section .info-block p,
html[data-theme^="pastel-flowers"] .section .info-block li { color: rgba(43, 32, 36, 0.88); }
html[data-theme^="pastel-flowers"] .section .card .muted,
html[data-theme^="pastel-flowers"] .section .info-block .muted { color: rgba(43, 32, 36, 0.6); }
html[data-theme^="pastel-flowers"] .section .card .eyebrow,
html[data-theme^="pastel-flowers"] .section .info-block .eyebrow { color: var(--rose-dark); }
html[data-theme^="pastel-flowers"] .section .card a,
html[data-theme^="pastel-flowers"] .section .info-block a { color: var(--rose-dark); }
html[data-theme^="pastel-flowers"] .section .card a:hover,
html[data-theme^="pastel-flowers"] .section .info-block a:hover { color: var(--ink); }
html[data-theme^="pastel-flowers"] .section .card .card-link { color: var(--rose-dark); }
html[data-theme^="pastel-flowers"] .section .card .card-link:hover { color: var(--ink); }

/* Buttons-outline on a card: rose tone instead of gold. */
html[data-theme^="pastel-flowers"] .section .btn-outline,
html[data-theme^="pastel-flowers"] .section .card .btn-outline,
html[data-theme^="pastel-flowers"] .section .info-block .btn-outline {
  border-color: var(--rose);
  color: var(--rose-dark);
}
html[data-theme^="pastel-flowers"] .section .btn-outline:hover,
html[data-theme^="pastel-flowers"] .section .card .btn-outline:hover,
html[data-theme^="pastel-flowers"] .section .info-block .btn-outline:hover {
  background: var(--rose);
  color: #fff;
  border-color: var(--rose);
}

/* Hero text on pastel: ink charcoal with a soft cream halo reads better
   against the bright floral backdrop than the mountain-default white-with-
   black-shadow, which looked like a bruise. The cream halo gives every
   glyph its own quiet "moat" against busy floral zones.
   Scoped to "pastel-flowers" only — the "pastel-flowers-hero" variant
   keeps the mountain snowy-peaks hero, where ivory text + dark shadow
   (the base treatment) reads against the dark sky. */
html[data-theme="pastel-flowers"] .hero-content {
  text-shadow:
    0 1px 0 rgba(253, 247, 241, 0.85),
    0 2px 14px rgba(253, 247, 241, 0.75);
}
html[data-theme="pastel-flowers"] .hero-names {
  color: var(--ink);
  text-shadow:
    0 1px 0 rgba(253, 247, 241, 0.9),
    0 2px 18px rgba(253, 247, 241, 0.85);
}
html[data-theme="pastel-flowers"] .hero-names .amp { color: var(--rose-dark); }
html[data-theme="pastel-flowers"] .hero-eyebrow { color: var(--rose-dark); }
html[data-theme="pastel-flowers"] .hero-meta { color: rgba(43, 32, 36, 0.78); }
html[data-theme="pastel-flowers"] .hero-countdown .num,
html[data-theme="pastel-flowers"] .hero-countdown .label { color: var(--ink); }

/* Nav over the pastel hero: the florals are bright, so ivory text loses
   contrast in patches. Use ink text with a soft cream halo so the brand /
   menu items stay readable wherever a guest happens to scroll. The scrolled
   state already uses ink, so we leave it alone aside from a rose hairline.
   Scoped to "pastel-flowers" only — the hybrid variant has a dark snowy-
   peaks hero behind the nav, where the base ivory-on-dark treatment reads
   well; flipping to ink-on-cream there would be dark-on-dark. */
html[data-theme="pastel-flowers"] .nav-brand,
html[data-theme="pastel-flowers"] .nav-menu a,
html[data-theme="pastel-flowers"] .nav-signout {
  color: var(--ink);
  text-shadow: 0 1px 2px rgba(253, 247, 241, 0.7);
}
html[data-theme="pastel-flowers"] .nav-toggle span {
  background: var(--ink);
}
/* Filled-rose RSVP pill, scoped to the full-pastel theme only — the
   hybrid keeps the mountain hero, where the base ivory-outline pill
   belongs. (On light nav surfaces the base scrolled/sub-page rules give
   both pastel variants an ink + rose-outline pill, since --gold is
   overridden to dusty rose here.) */
html[data-theme="pastel-flowers"] .nav-menu a.nav-cta {
  background: var(--rose);
  color: #fff;
  border-color: var(--rose);
  text-shadow: none;
}
html[data-theme="pastel-flowers"] .nav-menu a.nav-cta:hover {
  background: transparent;
  color: var(--rose-dark);
  border-color: var(--rose);
}
html[data-theme^="pastel-flowers"] .nav.scrolled { border-bottom-color: rgba(201, 138, 156, 0.32); }

/* =========================================================
   Schedule / timeline — Pastel Flowers
   Open Air: same no-box layout as the base theme, recolored — ink text
   directly on the cream scrim with rose hairlines and rose times.
   ========================================================= */
html[data-theme^="pastel-flowers"] .timeline > li {
  background: transparent;
  color: var(--ink);
  border: 0;
  box-shadow: none;
}
html[data-theme^="pastel-flowers"] .timeline-section-head {
  border-bottom-color: rgba(201, 138, 156, 0.3);
}
html[data-theme^="pastel-flowers"] .timeline-section-head h3 { color: var(--ink); }
html[data-theme^="pastel-flowers"] .timeline-section-head h3::before,
html[data-theme^="pastel-flowers"] .timeline-section-head h3::after {
  background: linear-gradient(90deg,
    rgba(201, 138, 156, 0) 0%,
    rgba(201, 138, 156, 0.6) 50%,
    rgba(201, 138, 156, 0) 100%);
}
html[data-theme^="pastel-flowers"] .timeline-section-head span { color: var(--rose-dark); }
html[data-theme^="pastel-flowers"] .timeline-day-list > li + li {
  border-top-color: rgba(201, 138, 156, 0.22);
}
html[data-theme^="pastel-flowers"] .timeline-date {
  border-right-color: rgba(201, 138, 156, 0.28);
}
html[data-theme^="pastel-flowers"] .timeline-date .num { color: var(--rose-dark); }
html[data-theme^="pastel-flowers"] .timeline-body { color: var(--ink); }
html[data-theme^="pastel-flowers"] .timeline-body h4 { color: var(--ink); }
html[data-theme^="pastel-flowers"] .timeline-body .time { color: var(--rose-dark); }
html[data-theme^="pastel-flowers"] .timeline-body p { color: rgba(43, 32, 36, 0.88); }
html[data-theme^="pastel-flowers"] .timeline-body .muted { color: rgba(43, 32, 36, 0.62); }

/* =========================================================
   Loader / password gate — Pastel Flowers
   The cream loader becomes a soft blush wash; the shimmer line picks up
   rose + lilac; the monogram's "&" turns dusty rose. The portrait ring
   trades dusty gold for dusty rose so it matches the new accent.
   ========================================================= */
html[data-theme^="pastel-flowers"] .loader {
  background:
    radial-gradient(120% 90% at 50% 0%, #fff6f3 0%, #fbeef0 45%, #f3dde2 100%);
}
html[data-theme^="pastel-flowers"] .loader-monogram { color: var(--rose-dark); }
html[data-theme^="pastel-flowers"] .loader-monogram .amp { color: var(--lilac); }
html[data-theme^="pastel-flowers"] .gate-portrait {
  box-shadow:
    0 0 0 1px rgba(201, 138, 156, 0.55),
    0 0 0 4px rgba(201, 138, 156, 0.18),
    0 6px 18px rgba(122, 74, 94, 0.3);
}
html[data-theme^="pastel-flowers"] .loader-shimmer {
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--blush) 20%,
    var(--rose) 50%,
    var(--lilac) 80%,
    transparent 100%
  );
  background-size: 200% 100%;
}
html[data-theme^="pastel-flowers"] .gate-eyebrow { color: var(--rose-dark); }
html[data-theme^="pastel-flowers"] .gate-prompt  { color: var(--charcoal); }
html[data-theme^="pastel-flowers"] .gate-input {
  background: rgba(255, 255, 255, 0.88);
  border-color: var(--sand);
}
html[data-theme^="pastel-flowers"] .gate-input:focus {
  border-color: var(--rose);
  box-shadow: 0 0 0 3px rgba(201, 138, 156, 0.18);
}
html[data-theme^="pastel-flowers"] .gate-submit {
  background: var(--rose-dark);
  border-color: var(--rose-dark);
  color: #fff;
}
html[data-theme^="pastel-flowers"] .gate-submit:hover {
  background: var(--lilac);
  border-color: var(--lilac);
  color: #fff;
}

/* =========================================================
   Nav hover — Pastel Flowers
   The base hover rules (`.nav-menu a:hover { color: var(--gold); }`) have
   lower specificity than my pastel-color override above (0,2,1 < 0,2,2),
   so without explicit hover overrides at theme-aware specificity, the
   home-page hero nav never changes color on hover. Re-declare each hover
   state at the pastel selector level. Also covers the .explore-body sub-
   page nav so the rose hover is consistent across every route.

   Split into two blocks: the over-hero hovers (bare selectors) are pastel-
   only because the hybrid keeps the mountain ivory-on-dark hero nav and
   falls back to the base gold (= rose, since --gold is overridden) hover.
   The sub-page and scrolled hovers apply to both pastel variants.
   ========================================================= */
html[data-theme="pastel-flowers"] .nav-brand:hover,
html[data-theme="pastel-flowers"] .nav-menu a:hover,
html[data-theme="pastel-flowers"] .nav-signout:hover {
  color: var(--rose-dark);
}
html[data-theme^="pastel-flowers"] .explore-body .nav-brand:hover,
html[data-theme^="pastel-flowers"] .explore-body .nav-menu a:hover,
html[data-theme^="pastel-flowers"] .explore-body .nav-signout:hover,
html[data-theme^="pastel-flowers"] .nav.scrolled .nav-menu a:hover,
html[data-theme^="pastel-flowers"] .nav.scrolled .nav-brand:hover {
  color: var(--rose-dark);
}
/* Active-page link picks up the rose accent, replacing the gold default. */
html[data-theme^="pastel-flowers"] .nav-menu a.active,
html[data-theme^="pastel-flowers"] .nav.scrolled .nav-menu a.active,
html[data-theme^="pastel-flowers"] .explore-body .nav-menu a.active,
body.spa-loading html[data-theme^="pastel-flowers"] .nav-menu a.active {
  color: var(--rose-dark);
}
/* Language switcher: gold border + hover background → rose. */
html[data-theme^="pastel-flowers"] .lang-switcher { border-color: var(--rose); }
html[data-theme^="pastel-flowers"] .lang-btn { border-right-color: rgba(201, 138, 156, 0.5); }
html[data-theme^="pastel-flowers"] .lang-btn.is-active { outline-color: var(--rose); }
html[data-theme^="pastel-flowers"] .lang-btn:hover,
html[data-theme^="pastel-flowers"] .nav.scrolled .lang-btn:hover,
html[data-theme^="pastel-flowers"] .explore-body .lang-btn:hover {
  background: rgba(201, 138, 156, 0.14);
}

/* =========================================================
   Sub-page header — Pastel Flowers
   `.explore-body` sub-pages (rsvp / faq / travel / stay / explore) set
   their own solid body background and a custom .explore-header. Their
   H1 was painted in `var(--ivory)` (cream) which on the pastel theme's
   cream body becomes cream-on-cream — invisible. Flip everything to ink
   on cream with a rose eyebrow so the header reads cleanly and matches
   the home page's section-header treatment.
   ========================================================= */
html[data-theme^="pastel-flowers"] .explore-body { background: #fbf3ec; color: var(--ink); }
html[data-theme^="pastel-flowers"] .explore-body .explore-header h1 { color: var(--ink); }
html[data-theme^="pastel-flowers"] .explore-body .explore-header .lede { color: rgba(43, 32, 36, 0.85); }
html[data-theme^="pastel-flowers"] .explore-body .explore-header .eyebrow { color: var(--rose-dark); }
html[data-theme^="pastel-flowers"] .explore-body .explore-back-link { color: var(--rose-dark); }
html[data-theme^="pastel-flowers"] .explore-body .explore-back-link:hover { color: var(--ink); }

/* Sub-page solid nav (.explore-body .nav) — matches the home page's
   scrolled nav state, so just tweak the hairline + active accent. The
   text-color rule for sub-pages lives in the base CSS at 0,2,1 specificity
   and gets won by the pastel selector above (0,2,2), so the nav text
   stays ink on cream — same as base behavior, just with rose accents. */
html[data-theme^="pastel-flowers"] .explore-body .nav {
  background: rgba(253, 247, 241, 0.96);
  border-bottom-color: rgba(201, 138, 156, 0.32);
}

/* =========================================================
   RSVP form — Pastel Flowers
   Member rows, plus-one block, extras container, textareas, submit
   button, and success state all hard-coded the dark plum + gold + ivory
   palette. Re-skin every chunk in cream + rose so the form blends into
   the pastel section instead of staying dark.
   ========================================================= */

/* Section H1 (not just .section-head h2 — the rsvp page uses .explore-header
   above, but the success view re-uses .section h3). */
html[data-theme^="pastel-flowers"] .rsvp-success h3 { color: var(--ink); }
html[data-theme^="pastel-flowers"] .rsvp-success .eyebrow.light { color: var(--rose-dark); }
html[data-theme^="pastel-flowers"] .rsvp-success .lede.light { color: rgba(43, 32, 36, 0.85); }

/* Flash banner shown after a partial submit — gold-on-dark → rose-on-cream. */
html[data-theme^="pastel-flowers"] .rsvp-flash {
  background: rgba(201, 138, 156, 0.10);
  border-color: rgba(201, 138, 156, 0.4);
  border-left-color: var(--rose);
  color: var(--ink);
}

/* Member rows: quiet cream chips on the cream scrim, rose hairline. */
html[data-theme^="pastel-flowers"] .rsvp-member {
  background: rgba(255, 255, 255, 0.35);
  border-color: rgba(201, 138, 156, 0.42);
}
html[data-theme^="pastel-flowers"] .rsvp-member.is-current {
  border-color: rgba(201, 138, 156, 0.6);
  box-shadow: 0 0 0 1px rgba(201, 138, 156, 0.22);
}
html[data-theme^="pastel-flowers"] .rsvp-member-name { color: var(--ink); }
html[data-theme^="pastel-flowers"] .rsvp-member-type { color: rgba(43, 32, 36, 0.55); }
html[data-theme^="pastel-flowers"] .rsvp-current-badge { color: var(--rose-dark); }

/* Choice pills: rose accent in place of gold. */
html[data-theme^="pastel-flowers"] .rsvp-radio span {
  color: rgba(43, 32, 36, 0.78);
  border-color: rgba(201, 138, 156, 0.42);
}
html[data-theme^="pastel-flowers"] .rsvp-radio:hover span {
  border-color: var(--rose);
  color: var(--ink);
}
html[data-theme^="pastel-flowers"] .rsvp-radio input:checked + span {
  background: var(--rose);
  border-color: var(--rose);
  color: #fff;
}
html[data-theme^="pastel-flowers"] .rsvp-radio input:focus-visible + span {
  box-shadow: 0 0 0 3px rgba(201, 138, 156, 0.22);
}

/* Field labels + hints inside member rows. */
html[data-theme^="pastel-flowers"] .rsvp-field-label { color: rgba(43, 32, 36, 0.68); }
html[data-theme^="pastel-flowers"] .rsvp-field-hint { color: rgba(43, 32, 36, 0.5); }

/* Email + plus-one inputs: white-on-cream with rose focus ring. */
html[data-theme^="pastel-flowers"] .rsvp-email-field input[type="email"],
html[data-theme^="pastel-flowers"] .rsvp-plus-one input[type="text"] {
  color: var(--ink);
  background: rgba(255, 255, 255, 0.85);
  border-color: rgba(201, 138, 156, 0.32);
}
html[data-theme^="pastel-flowers"] .rsvp-email-field input::placeholder,
html[data-theme^="pastel-flowers"] .rsvp-plus-one input[type="text"]::placeholder {
  color: rgba(43, 32, 36, 0.45);
}
html[data-theme^="pastel-flowers"] .rsvp-email-field input:focus,
html[data-theme^="pastel-flowers"] .rsvp-plus-one input[type="text"]:focus {
  border-color: var(--rose);
  box-shadow: 0 0 0 3px rgba(201, 138, 156, 0.18);
}

/* Plus-one sub-card — gold-tinted bg → blush-tinted bg, rose accent rail. */
html[data-theme^="pastel-flowers"] .rsvp-plus-one {
  background: rgba(201, 138, 156, 0.08);
  border-left-color: var(--rose);
}
html[data-theme^="pastel-flowers"] .rsvp-plus-one-label { color: var(--rose-dark); }

/* Scoped (plus-one) view note. */
html[data-theme^="pastel-flowers"] .rsvp-scoped-note { color: rgba(43, 32, 36, 0.85); }
html[data-theme^="pastel-flowers"] .rsvp-scoped-note strong { color: var(--rose-dark); }

/* Extras "tell us more" block — no card in Open Air; inherits the base
   transparent layout, inputs carry their own borders. */
html[data-theme^="pastel-flowers"] .rsvp-extras {
  background: transparent;
  border: 0;
  box-shadow: none;
}
html[data-theme^="pastel-flowers"] .rsvp-field label { color: var(--ink); }
html[data-theme^="pastel-flowers"] .rsvp-field textarea {
  color: var(--ink);
  background: rgba(255, 255, 255, 0.78);
  border-color: rgba(201, 138, 156, 0.32);
}
html[data-theme^="pastel-flowers"] .rsvp-field textarea::placeholder { color: rgba(43, 32, 36, 0.5); }
html[data-theme^="pastel-flowers"] .rsvp-field textarea:focus {
  border-color: var(--rose);
  box-shadow: 0 0 0 3px rgba(201, 138, 156, 0.18);
}

/* Submit button — was gold-on-dark; now rose-on-white. */
html[data-theme^="pastel-flowers"] .rsvp-submit {
  color: #fff;
  background: var(--rose);
  border-color: var(--rose);
}
html[data-theme^="pastel-flowers"] .rsvp-submit:hover {
  background: var(--rose-dark);
  border-color: var(--rose-dark);
}

/* =========================================================
   FAQ list — Pastel Flowers
   Open Air: rows directly on the cream scrim, rose dividers.
   ========================================================= */
html[data-theme^="pastel-flowers"] .faq {
  background: transparent;
  color: var(--ink);
  border: 0;
  box-shadow: none;
}
html[data-theme^="pastel-flowers"] .faq details { border-bottom-color: rgba(201, 138, 156, 0.28); }
html[data-theme^="pastel-flowers"] .faq summary { color: var(--ink); }
html[data-theme^="pastel-flowers"] .faq summary::after { color: var(--rose); }
html[data-theme^="pastel-flowers"] .faq details p { color: rgba(43, 32, 36, 0.88); }

/* Footer — both themes share the deep mauve close (see base .footer);
   it bridges the pastel palette and the plum heritage, so no pastel
   override is needed. */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  /* Override story-photo reveal so it lands at the resting state without
     animating — keeps the scale(0.92) "small print" look but skips the
     stagger/rotate/slide for users who asked for reduced motion. */
  .story-photo.reveal,
  .story-photo.reveal:nth-child(odd),
  .story-photo.reveal:nth-child(even),
  .story-photo.reveal.visible {
    opacity: 1;
    transform: scale(0.92);
    transition: none;
  }
  .hero-scroll span,
  .bg-layer,
  .loader-inner,
  .loader-shimmer,
  .loader-text .dot { animation: none; }
  .bg-layer { transition: none; }
  /* Honor reduced-motion: hide the looping clip and fall back to the
     bg-layer's poster image, which is the same still photo. */
  .bg-layer-video { display: none; }
  #spa-main,
  #spa-main.spa-fade-out { transition: none; opacity: 1; }
  .hero-sky-b { opacity: 0.5; }
  .loader-text .dot { opacity: 0.6; }
}
