:root {
  --sage: #8d9c86;
  --sage-deep: #5d6b57;
  --blush: #f0dcd8;
  --blush-deep: #d9a8a4;
  --ink: #8a5f63;
  --ink-deep: #5f3f43;
  --champagne: #c8a96a;
  --cream: #fdf9f8;
}

body {
  background: var(--cream);
  color: var(--ink-deep);
  font-family: 'Jost', system-ui, sans-serif;
  font-weight: 300;
  -webkit-font-smoothing: antialiased;
}

table td {
  border-left: 1px solid var(--blush-deep);
  border-right: 1px solid var(--blush-deep);
  text-align: center;
  padding: 5px;
}

/* Cormorant is a text face, not a caps face. The blanket uppercase that suited
   Cinzel would flatten it — and mixed case is what makes the large staggered
   titles readable. Tracked caps survive on .eyebrow, where they belong.

   No font-weight here on purpose: Tailwind Preflight ships
   `h1,h2,h3,h4,h5,h6 { font-weight: inherit }`, same specificity (0,0,1) and
   later in the cascade, so any weight set on this rule would be overridden
   anyway. Headings inherit the body's 300, and every screenshot this project
   has been reviewed and approved against was rendered at that weight — it is
   the intended appearance, not a bug to fix by winning the tie. */
h1, h2, h3 {
  font-family: 'Cormorant Garamond', Georgia, serif;
  letter-spacing: 0.02em;
}

/* Was --sage, which measures 2.8:1 on cream at 11px — well under WCAG AA.
   --sage-deep measures 5.4:1. */
.eyebrow {
  font-family: 'Jost', sans-serif;
  font-weight: 400;
  font-size: 0.6875rem;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: var(--sage-deep);
}

/* Lenis requires these; without them the page double-scrolls. */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }

.rule {
  width: 76px;
  height: 1px;
  background: var(--sage);
  margin: 1.5rem auto;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}

#site-nav { color: var(--ink); }

#site-nav.nav-condensed {
  background: var(--cream);
  box-shadow: 0 1px 24px rgba(138, 95, 99, 0.12);
}

#site-nav.nav-condensed .h-20 { height: 4rem; }

#nav-drawer {
  transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}
#nav-drawer.is-open { transform: translateX(0); }
#nav-backdrop { transition: opacity 0.3s ease; opacity: 0; }
#nav-backdrop.is-open { opacity: 1; }

.drawer-link {
  display: block;
  padding: 0.85rem 1.75rem;
  font-size: 0.75rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

.nav-link { position: relative; }
.nav-link::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -0.4rem;
  height: 1px;
  background: var(--champagne);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s ease;
}
.nav-link.is-current::after { transform: scaleX(1); }

@media (prefers-reduced-motion: reduce) {
  #nav-drawer, #nav-backdrop, .nav-link::after { transition: none; }
}

.lang-btn { opacity: 0.45; transition: opacity 0.2s ease; }
.lang-btn:hover { opacity: 0.8; }
.lang-btn.is-active { opacity: 1; border-bottom: 1px solid var(--sage); }

/* Each unit is a medallion: a blush disc with a hairline ring set in from its
   edge, and the number and label stacked inside. The disc's own edge is
   unstroked — the ring is the ::after — so it reads as an inner rule rather
   than a double outline. The lower clamp bound is what lets two discs plus a
   gap fit inside 375px minus the hero's px-6 padding. */
.count-unit {
  position: relative;
  width: clamp(112px, 30vw, 132px);
  height: clamp(112px, 30vw, 132px);
  border-radius: 50%;
  background: var(--blush);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.count-unit::after {
  content: '';
  position: absolute;
  inset: 9px;
  border-radius: 50%;
  border: 1px solid var(--blush-deep);
}

.count-digits { display: flex; justify-content: center; }

.count-card {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-weight: 300;
  font-size: clamp(2rem, 7vw, 2.875rem);
  line-height: 1;
  display: block;
  /* Cormorant's default figures are proportional (a 1 is far narrower than an
     8), so tabular figures stop the width jitter as digits tick — this
     replaces a min-width that also spread the digits apart. */
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: var(--ink-deep);
  backface-visibility: hidden;
}

.count-card.is-flipping { animation: count-flip 0.45s ease; }

/* rotateX reads better on a bare numeral than it did on a tile — there is no
   longer a card edge to betray the axis. */
@keyframes count-flip {
  0%   { transform: rotateX(0deg); }
  50%  { transform: rotateX(-90deg); }
  100% { transform: rotateX(0deg); }
}

@media (prefers-reduced-motion: reduce) {
  .count-card.is-flipping { animation: none; }
}

/* Sentence case rather than the site's usual tracked caps: at the size the disc
   allows, 10px tracked caps reads as technical annotation and fights the
   softness the medallion exists to create. The i18n values are lowercase
   ('días', 'dni') and have always been uppercased in CSS, so the casing stays
   presentational; every label in both languages is a single word, which is what
   makes `capitalize` safe.
   No opacity: --ink-deep flat on --blush measures 7.0:1, while the same token at
   opacity .75 measures 3.9:1 and fails AA. */
.count-label {
  font-family: 'Jost', sans-serif;
  font-weight: 300;
  font-size: 0.8125rem;
  text-transform: capitalize;
  color: var(--ink-deep);
  margin-top: 0.5625rem;
  display: block;
}

/* Deliberately kept uppercase even though the blanket uppercase on h1/h2/h3
   was dropped for Cormorant Garamond: this is a short celebratory message, not a
   heading, so it is tracked-caps like .eyebrow rather than mixed-case like the
   staggered titles.
   grid-column spans the full row because the past-date branch replaces every
   .count-unit with this single <p>, which would otherwise sit in column one of
   the mobile 2x2 grid. */
.count-today {
  font-family: 'Cormorant Garamond', Georgia, serif;
  font-size: clamp(1.1rem, 3vw, 1.75rem);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  grid-column: 1 / -1;
}

/* Sitewide guard: ANY copy value can end up as a single long unbreakable token —
   a hashtag, a URL, a placeholder — and grid/flex items don't shrink below their
   content's intrinsic width by default. Without this, one such token forces its
   container wider than the viewport at 375/768px and the whole page scrolls
   sideways. Swept and verified against a 38-character token in every text key at
   375/768/1440. */
main, header, footer { overflow-wrap: anywhere; }
article, .gift-card { min-width: 0; }

.day-block {
  border: 1px solid rgba(141, 156, 134, 0.4);
  padding: 2.25rem 2rem;
  background: var(--cream);
}

.day-block .rule { margin-left: 0; margin-right: 0; }

.gallery-grid {
  column-count: 2;
  column-gap: 1rem;
}

@media (min-width: 768px) {
  .gallery-grid { column-count: 3; }
}

.gallery-item {
  display: block;
  width: 100%;
  break-inside: avoid;
  margin-bottom: 1rem;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  overflow: hidden;
}

.gallery-item img {
  width: 100%;
  display: block;
  transition: transform 0.5s ease, opacity 0.3s ease;
}

.gallery-item:hover img { transform: scale(1.04); opacity: 0.9; }

/* Gallery: the brief's soft glow, alongside the existing zoom. */
.gallery-item { transition: box-shadow 0.4s ease; }
.gallery-item:hover { box-shadow: 0 0 0 1px var(--champagne), 0 8px 30px rgba(200, 169, 106, 0.28); }

/* visibility is delayed on close (not on open) so the fade-out finishes before
   the overlay leaves the accessibility tree, and so it never becomes
   Tab-reachable while invisible — display:none did that for free; opacity does
   not. */
#lightbox {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.35s ease, visibility 0s 0.35s;
}
#lightbox.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 0.35s ease;
}
#lightbox-image { transition: opacity 0.25s ease; }

@media (prefers-reduced-motion: reduce) {
  .gallery-item, #lightbox, #lightbox-image { transition: none; }
}

.timeline { position: relative; }

.timeline-spine {
  position: absolute;
  top: 0; bottom: 0; left: 0.4rem;
  width: 1px;
  background: var(--sage);
  opacity: 0.5;
}

.timeline-item { position: relative; padding-left: 2.5rem; }
.timeline-item + .timeline-item { margin-top: 4rem; }

.timeline-node {
  position: absolute;
  left: -0.8rem;   /* spine at 0.4rem, node 2.4rem wide: 0.4 - 1.2 */
  top: 0.1rem;
  width: 2.4rem;
  height: 2.4rem;
}

/* overflow: hidden here clips the *child* <img>'s hover-zoom to the arch
   shape below; it does not clip this element's own box-shadow (the
   champagne frame), which is painted outside the border box but is not
   "content" subject to the overflow clip. That split is what keeps the
   zoom contained while the frame stays visible. */
.story-image {
  display: block; width: 100%;
  padding: 0; border: 0; background: none; cursor: pointer;
  overflow: hidden;
}
.story-image img { transition: transform 0.5s ease; }
.story-image:hover img { transform: scale(1.03); }

/* Chapel arch: a tall elliptical top on a near-square base, plus a hairline
   champagne frame held off the image by a ring of cream. */
.arch {
  border-radius: 50% 50% 6px 6px / 32% 32% 3px 3px;
  box-shadow:
    0 0 0 1px var(--champagne),
    0 0 0 6px var(--cream),
    0 0 0 7px rgba(200, 169, 106, 0.3);
}

@media (min-width: 768px) {
  .timeline-spine { left: 50%; }
  .timeline-item { padding-left: 0; width: 50%; padding-right: 4.5rem; }
  .timeline-item.is-right { margin-left: 50%; padding-right: 0; padding-left: 4.5rem; }
  .timeline-node { left: auto; right: -1.2rem; }
  .timeline-item.is-right .timeline-node { left: -1.2rem; right: auto; }
}

@media (prefers-reduced-motion: reduce) {
  .story-image img { transition: none; }
}

.gift-card {
  border: 1px solid rgba(141, 156, 134, 0.45);
  border-top: 2px solid var(--champagne);
  background: var(--cream);
  padding: 2.25rem 2rem;
}

.hotel-card {
  border: 1px solid rgba(141, 156, 134, 0.45);
  background: var(--cream);
  padding: 1.75rem 1.5rem;
  min-width: 0;
}

.hotel-code {
  font-family: 'Jost', sans-serif;
  letter-spacing: 0.18em;
  font-size: 0.8rem;
  padding: 0.5rem 0.75rem;
  border: 1px dashed var(--champagne);
  display: inline-block;
  user-select: all;
}

.copy-code { color: var(--sage-deep); text-decoration: underline; }
.copy-code:hover { color: var(--ink-deep); }

/* Only hide reveal targets when JS is running — otherwise the page would be
   blank for anyone with JS disabled or a failed CDN load. */
.js-enabled .reveal { opacity: 0; }

/* The gentle tier hides the same blocks, and js/motion.js fades them back in with
   GSAP. Deliberately NOT overridden in the reduced-motion block below — that
   override exists to rescue `.js-enabled`, which the gentle tier never sets. */
.js-gentle .reveal { opacity: 0; }

#preloader { transition: opacity 0.6s ease; }
#preloader.is-done { opacity: 0; pointer-events: none; }

/* Unreachable by design: init() in js/motion.js checks prefersReducedMotion()
   BEFORE adding .js-enabled, so under reduced motion that class is never added and
   .reveal never gets hidden in the first place — this rule's selector never matches.
   It stays anyway as defence-in-depth: if a future change ever reintroduces an
   unconditional `.js-enabled` add, this is what stops every .reveal block from
   being stuck invisible for reduced-motion visitors. Do NOT delete this thinking
   it's dead weight, and do NOT read it as *the* reduced-motion mechanism — the
   real gate lives in JS, in init(), not here. */
@media (prefers-reduced-motion: reduce) {
  .js-enabled .reveal { opacity: 1 !important; transform: none !important; }
}

.hero-slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1.6s ease;
}
.hero-slide.is-active { opacity: 1; }

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

.rsvp-label { display: block; font-size: 0.8rem; letter-spacing: 0.06em; margin-bottom: 0.4rem; }
.rsvp-help { font-size: 0.75rem; opacity: 0.7; margin-top: 0.4rem; }

.rsvp-input {
  width: 100%;
  background: var(--cream);
  border: 1px solid rgba(141, 156, 134, 0.5);
  padding: 0.7rem 0.85rem;
  font-family: 'Jost', sans-serif;
  font-size: 0.9rem;
  color: var(--ink-deep);
}
.rsvp-input:focus { outline: 2px solid var(--champagne); outline-offset: 1px; }

.rsvp-choice {
  display: block; cursor: pointer; text-align: center;
  border: 1px solid rgba(141, 156, 134, 0.5);
  padding: 1.5rem 1rem;
  transition: border-color 0.25s ease, background 0.25s ease;
}
.rsvp-choice:hover { border-color: var(--champagne); }
.rsvp-choice:has(input:checked) { border-color: var(--champagne); background: rgba(200, 169, 106, 0.12); }

.rsvp-error { color: #9b3d3d; font-size: 0.75rem; margin-top: 0.35rem; min-height: 1rem; }

.rsvp-summary { font-size: 0.85rem; }
.rsvp-summary dt { font-weight: 500; letter-spacing: 0.04em; opacity: 0.7; margin-top: 0.9rem; }
.rsvp-summary dt:first-child { margin-top: 0; }
.rsvp-summary dd { margin: 0.2rem 0 0; }

/* Off-screen rather than display:none — some bots skip fields they can tell are
   hidden, and this one is only useful if they fill it in. */
.rsvp-honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* Scoped to `.rsvp-enhanced`, which js/rsvp.js adds itself once it has committed
   to the stepped experience — NOT to `.js-enabled`. js/motion.js only adds
   `.js-enabled` when reduced motion is off AND every animation CDN loaded
   (js/motion.js:151-159); js/rsvp.js still runs and enhances the form under
   reduced motion or a blocked CDN, so keying this off `.js-enabled` would leave
   every fieldset visible at once for exactly the guests who most need the
   stepped UI to work. */
.rsvp-enhanced .rsvp-step { display: none; }
.rsvp-enhanced .rsvp-step.is-current { display: block; }

/* Back/Next only ever do anything once js/rsvp.js has wired them up, so they
   must stay hidden by default in plain CSS — NOT via Tailwind's `.hidden`
   utility. Tailwind itself arrives from a CDN <script>; if that script is
   blocked while js/rsvp.js still loads and runs fine (a plain, non-module,
   non-CDN file), `.hidden` would carry no rule at all and both dead buttons
   would render in front of the guest. This rule needs no CDN. Per-step
   visibility on top of this is then set as an inline style in js/rsvp.js,
   which always wins over both this rule and `.hidden` regardless of specificity. */
#rsvp-back, #rsvp-next { display: none; }
.rsvp-enhanced #rsvp-back, .rsvp-enhanced #rsvp-next { display: inline-block; }

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

/* One noise tile, tinted by nothing and clickable by nobody. Fixed so it does not
   scroll with content — moving grain reads as a rendering bug. */
#grain {
  position: fixed;
  inset: 0;
  z-index: 30;
  pointer-events: none;
  opacity: 0.05;
  background-size: 180px 180px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* Sits at the top of the incoming section, filled in that section's own colour,
   and pulled up so it overlaps the section above — which is what turns a hard
   horizontal edge into a curve. */
.band-curve {
  display: block;
  width: 100%;
  height: clamp(26px, 3.6vw, 56px);
  margin-top: calc(-1 * clamp(26px, 3.6vw, 56px));
  position: relative;
}

/* Three of the seven openers break left, so the page reads as composed rather
   than templated. The rule turns champagne here to mark the change as deliberate. */
.section-head--left { text-align: left; }
.section-head--left .rule {
  margin-left: 0;
  margin-right: 0;
  width: 54px;
  background: var(--champagne);
}

.wreath-mark {
  display: block;
  width: clamp(150px, 20vw, 210px);
  height: auto;
  margin: 0 auto 1.5rem;
}

/* The footer is sage-deep; the wreath's own sage/champagne colours would lose
   contrast against it. grayscale() was tried first but it collapses sage and
   champagne to near-identical luminance, taking the monogram down with them.
   sepia() instead retints the whole mark toward cream/champagne, which keeps
   the M&S monogram legible in a warm gold rather than erasing it. */
.wreath-mark--footer { filter: sepia(1) saturate(1.5) brightness(2.2); }
