/* =============================================================================
   Minh Trung & Minh Thuỳ — Wedding Invitation
   Aesthetic follows anniversarymovie.kr/view/template01, whose values are the
   source for the tones and component styles below: a white card banded with
   near-white greys, hairline #eee rules, small radii, and Playfair Display
   section titles over a humanist sans.
   ============================================================================= */

:root {
  --white: #ffffff;
  /* The reference's two band tones, sampled from its sections. */
  --cream: #f6f6f6;
  --cream-deep: #f9f9f9;
  --page: #f5f5f5;   /* outside the card column, and the footer */

  --ink: #191919;
  --ink-soft: #202020;
  --text: #363636;
  --text-mid: #434343;
  --text-body: #535353;
  --muted: #626262;
  --muted-soft: #717171;
  --faint: #808080;
  --faint-soft: #909090;
  --hint: #969696;
  --hint-soft: #a6a6a6;

  --accent: #7c7c7c;
  --accent-deep: #5c5c5c;
  /* The reference draws every rule and control outline as one hairline grey. */
  --line: #e5e5e5;
  --line-soft: #eeeeee;
  --line-faint: #eeeeee;
  --field-line: #e5e5e5;
  --field-fill: #efefef;   /* its inputs are filled, not underlined */
  --btn-fill: #f2f2f2;     /* and its buttons are the same idea, a shade lighter */
  --tile-fill: #f8f8f8;    /* the small map/link tiles */
  --rose: #c08a7a;

  --radius: 6px;   /* map and link tiles */
  --radius-lg: 8px;   /* cards and accordion rows */

  --serif: "Cormorant Garamond", Georgia, serif;
  --sans: "Be Vietnam Pro", -apple-system, BlinkMacSystemFont, sans-serif;
  /* The reference's own script and display faces. Its body face, Gowun Dodum,
     has no Vietnamese subset, so Be Vietnam Pro stays — every accented vowel
     would otherwise fall back to a different face mid-sentence. */
  --script: "Great Vibes", "Cormorant Garamond", cursive;
  --display: "Playfair Display", "Cormorant Garamond", serif;
  /* Ticket figures and labels. IBM Plex Mono carries the Vietnamese subset —
     DM Mono and Courier Prime do not, and would drop every diacritic. */
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Plane silhouette, turned to point along the route. Used as a mask so it
     takes the surrounding text colour, and drawn rather than set as a glyph
     because ✈ renders differently on every platform. */
  --plane-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath transform='rotate(90 12 12)' d='M21 16v-2l-8-5V3.5a1.5 1.5 0 0 0-3 0V9l-8 5v2l8-2.5V19l-2 1.5V22l3.5-1 3.5 1v-1.5L13 19v-5.5z'/%3E%3C/svg%3E");

  --maxw: 480px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  background: var(--page);
  color: var(--text);
  font-family: var(--sans);
  font-weight: 400;   /* the reference sets body text at a normal weight */
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-deep); }

img { display: block; width: 100%; height: 100%; object-fit: cover; }

input, select, textarea, button { font-family: var(--sans); }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The shared coastline and plane live in an inline <svg> with no width or
   height. Inline is still inline: it sat in a line box and pushed the whole
   invitation 20px down the page. Taking it out of flow removes that. */
.svg-defs {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

.card {
  position: relative;
  max-width: var(--maxw);
  margin: 0 auto;
  background: var(--white);
  overflow: hidden;
  /* Explicit scale(1), not `none` — `none` does not interpolate reliably, which
     collapsed the settle below into an instant snap. */
  transform: scale(1);
  transition: transform 2.9s cubic-bezier(0.38, 0.02, 0.3, 1);
}
/* Card sits a touch deeper while the envelope is still sealed over it, then
   settles forward as the flaps fall — reads as being lifted out of the envelope
   rather than simply fading in. */
body.env-closed .card { transform: scale(0.965); }

/* Shared bits ------------------------------------------------------------- */
/* Section titles, matched to the reference's: a weighted Playfair set close to
   black at 19.2px with only 2px of tracking. The wide, pale, lightweight eyebrow
   this replaced read as a caption rather than a title. */
.eyebrow {
  font-family: var(--display);
  font-size: 19.2px;
  font-weight: 700;
  letter-spacing: 2px;
  text-indent: 2px;
  color: var(--ink);
  text-align: center;
}

.drop-rule {
  display: block;
  width: 1px;
  height: 38px;
  margin: 22px auto;
  background: var(--line);
}

/* The reference has no dark or outlined buttons: every control is a pale fill,
   either a bordered tile with a small radius or a flat grey block. */
.btn-outline {
  display: inline-block;
  padding: 12px 22px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: var(--tile-fill);
  font-size: 13.6px;
  letter-spacing: 0.04em;
  color: var(--text);
  text-align: center;
  cursor: pointer;
}

.btn-solid {
  padding: 14px 0;
  border: none;
  background: var(--btn-fill);
  color: var(--text);
  font-size: 15px;
  letter-spacing: 0.08em;
  cursor: pointer;
}
.btn-solid[disabled] { opacity: 0.6; cursor: default; }

/* Photo placeholder ring, shown until a real image loads ------------------ */
.slot {
  position: relative;
  background: #efefef;
  overflow: hidden;
}
.slot img { opacity: 0; transition: opacity 0.4s ease; }
.slot img.loaded { opacity: 1; }
.slot:not(.filled)::after {
  content: attr(data-placeholder);
  position: absolute;
  inset: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--line);
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--hint);
  text-align: center;
  padding: 6px;
}

/* ========================== ENVELOPE (intro) =============================
   Handmade speckled paper, four flaps folded to a point behind a wax seal.
   Opening runs in stages, driven by classes from main.js:
     sealed → breaking (seal presses in, 0.9s) → opening (flaps fall in 3D, 2.0s)
     → lifting (envelope fades, 1.3s). Perspective on .envelope is what makes the
     flaps swing rather than squash flat.
   ------------------------------------------------------------------------- */

/* Fine paper fibre: fractal noise, inlined so the page stays self-contained. */
:root {
  --paper-fibre: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23f)' opacity='0.42'/%3E%3C/svg%3E");
  /* Discrete flecks. Tile sizes are mutually prime so they never line up. */
  --paper-flecks:
    radial-gradient(circle at 18% 27%, rgba(78, 78, 78,0.3) 0 0.9px, transparent 1.3px),
    radial-gradient(circle at 72% 11%, rgba(101, 101, 101,0.24) 0 0.7px, transparent 1.1px),
    radial-gradient(circle at 41% 83%, rgba(68, 68, 68,0.26) 0 1px, transparent 1.4px),
    radial-gradient(circle at 88% 64%, rgba(109, 109, 109,0.2) 0 0.7px, transparent 1px),
    radial-gradient(circle at 8% 71%, rgba(88, 88, 88,0.22) 0 0.8px, transparent 1.2px),
    radial-gradient(circle at 57% 46%, rgba(255, 255, 255,0.55) 0 1px, transparent 1.5px),
    radial-gradient(circle at 31% 6%, rgba(255, 255, 255,0.42) 0 0.8px, transparent 1.2px);
  --paper-fleck-tiles: 137px 137px, 89px 89px, 113px 113px, 61px 61px, 173px 173px, 97px 97px, 79px 79px;
}

.envelope {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border: none;
  padding: 0;
  /* No background of its own — .env-paper is the backing sheet, so it can fade
     independently and let the card show through as the flaps swing away. */
  background: transparent;
  cursor: pointer;
  opacity: 1;
  perspective: 1250px;
  perspective-origin: 50% 50%;
  transition: opacity 1.3s cubic-bezier(0.4, 0, 0.5, 1);
}
.envelope[hidden] { display: none; }
.envelope.lifting { opacity: 0; }
.envelope:not(.sealed) { pointer-events: none; }

/* The sheet the flaps are folded from: soft mottling, then flecks, then fibre. */
.env-paper {
  position: absolute;
  inset: 0;
  background-image:
    var(--paper-fibre),
    var(--paper-flecks),
    radial-gradient(ellipse 70% 55% at 28% 22%, rgba(255, 255, 255,0.75), transparent 70%),
    radial-gradient(ellipse 60% 50% at 78% 82%, rgba(218, 218, 218,0.6), transparent 72%),
    linear-gradient(#f9f9f9, #eeeeee);
  background-size: 180px 180px, var(--paper-fleck-tiles), auto, auto, auto;
  background-blend-mode: multiply, normal, normal, normal, normal;
  opacity: 1;
  transition: opacity 1s cubic-bezier(0.4, 0, 0.6, 1) 0.35s;
}
/* Backing clears while the flaps are still falling, so the unfolding reveals
   the invitation rather than more envelope. */
.envelope.opening .env-paper,
.envelope.lifting .env-paper { opacity: 0; }

/* Each flap is the same paper as the sheet — only the fold seam separates them.
   The flecks are offset per flap so the four triangles don't look cloned. */
.env-flap {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-image: var(--paper-fibre), var(--paper-flecks);
  background-size: 180px 180px, var(--paper-fleck-tiles);
  background-blend-mode: multiply, normal;
  transition: transform 1.75s cubic-bezier(0.42, 0.02, 0.22, 1);
  transform-style: preserve-3d;
  backface-visibility: visible;
  will-change: transform;
}
.flap-top {
  clip-path: polygon(0 0, 100% 0, 50% 50%);
  background-color: #f8f8f8;
  background-position: 0 0, 11px 23px, 47px 8px, 5px 61px, 29px 17px, 71px 44px, 13px 3px, 37px 52px;
  transform-origin: top center;
  transform: rotateX(0deg);
  filter: drop-shadow(0 2px 3px rgba(105, 105, 105,0.22));
}
.flap-bottom {
  clip-path: polygon(0 100%, 50% 50%, 100% 100%);
  background-color: #f2f2f2;
  background-position: 40px 90px, 63px 7px, 19px 51px, 83px 29px, 7px 73px, 51px 12px, 91px 66px, 25px 41px;
  transform-origin: bottom center;
  transform: rotateX(0deg);
  filter: drop-shadow(0 -2px 3px rgba(105, 105, 105,0.24));
  transition-delay: 0.42s;
}
.flap-left {
  clip-path: polygon(0 0, 50% 50%, 0 100%);
  background-color: #f5f5f5;
  background-position: 90px 40px, 31px 67px, 77px 13px, 43px 89px, 15px 35px, 59px 71px, 5px 27px, 67px 9px;
  transform-origin: left center;
  transform: rotateY(0deg);
  filter: drop-shadow(2px 0 3px rgba(105, 105, 105,0.2));
  transition-delay: 0.2s;
}
.flap-right {
  clip-path: polygon(100% 0, 100% 100%, 50% 50%);
  background-color: #f4f4f4;
  background-position: 130px 70px, 87px 41px, 53px 79px, 21px 17px, 97px 59px, 35px 87px, 73px 31px, 9px 63px;
  transform-origin: right center;
  transform: rotateY(0deg);
  filter: drop-shadow(-2px 0 3px rgba(105, 105, 105,0.2));
  transition-delay: 0.2s;
}
/* Flaps stay shut through "sealed" and "breaking", then fall open. */
.envelope.opening .flap-top,
.envelope.lifting .flap-top { transform: rotateX(-132deg); }
.envelope.opening .flap-bottom,
.envelope.lifting .flap-bottom { transform: rotateX(132deg); }
.envelope.opening .flap-left,
.envelope.lifting .flap-left { transform: rotateY(122deg); }
.envelope.opening .flap-right,
.envelope.lifting .flap-right { transform: rotateY(-122deg); }

.env-seal-group {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 26px;
  transform: scale(1);
  opacity: 1;
  transition: transform 1.15s cubic-bezier(0.3, 0.05, 0.25, 1), opacity 0.65s ease;
}
/* Pressed in on the break, then lifted away and faded as the flaps fall. */
.envelope.breaking .env-seal-group {
  transform: scale(0.94);
  transition: transform 0.8s cubic-bezier(0.36, 0.06, 0.3, 1);
}
.envelope.opening .env-seal-group,
.envelope.lifting .env-seal-group {
  transform: scale(1.22) translateY(-30px) rotate(5deg);
  opacity: 0;
}

.env-seal {
  position: relative;
  display: block;
  width: 152px;
  height: 152px;
  filter: drop-shadow(0 10px 20px rgba(122, 96, 48, 0.32));
}
/* Swap drawn seal for a real one once WEDDING.sealImage has loaded. */
.env-seal-img { display: none; width: 100%; height: 100%; object-fit: contain; }
.env-seal.has-image .env-seal-img { display: block; }
.env-seal.has-image > svg { display: none; }

.envelope.sealed .env-seal { animation: sealPulse 3.4s ease-in-out infinite; }
@keyframes sealPulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.03); }
}

/* Ring that ripples outward the moment the seal is pressed. */
.env-seal-ripple {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 118px;
  height: 118px;
  margin: -59px 0 0 -59px;
  border-radius: 999px;
  border: 1px solid rgba(150, 120, 60, 0.5);
  opacity: 0;
  pointer-events: none;
}
.envelope.breaking .env-seal-ripple { animation: sealRipple 1.1s cubic-bezier(0.2, 0.6, 0.3, 1) forwards; }
@keyframes sealRipple {
  0% { opacity: 0.55; transform: scale(1); }
  100% { opacity: 0; transform: scale(2.1); }
}

.env-hint {
  font-family: var(--serif);
  font-size: 28px;
  letter-spacing: 0.01em;
  color: #7a2130;
}

/* =================== FLOATING CONTROLS (language + music) =============== */
/* Both controls share one fixed bar so there is a single floating group,
   and both sit under the envelope's z-index until the card is opened. */
.controls-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  /* One cell, both modes in it. */
  display: grid;
  grid-template-areas: "stack";
  justify-items: center;
  align-items: center;
  padding: 0 0 22px;
  pointer-events: none;
}

/* Both modes occupy the same cell of the bar so they can cross-fade in place
   rather than one vanishing and the other appearing. */
.ctl-mode {
  grid-area: stack;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  transition: opacity 0.3s ease, transform 0.34s cubic-bezier(0.22, 1, 0.36, 1),
              visibility 0.34s;
}
/* visibility rather than display: it can be transitioned, and it still takes the
   inactive mode out of the accessibility tree. */
.ctl-mode.is-off {
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px) scale(0.94);
  pointer-events: none;
}

/* The pills arrive one after another rather than as a block. Reversed on the way
   out so the group leaves from the same end it entered. */
.ctl-actions > * {
  transition: opacity 0.28s ease, transform 0.36s cubic-bezier(0.22, 1, 0.36, 1);
}
.ctl-actions.is-off > * {
  opacity: 0;
  transform: translateY(12px);
}
.ctl-actions > *:nth-child(1) { transition-delay: 0.02s; }
.ctl-actions > *:nth-child(2) { transition-delay: 0.08s; }
.ctl-actions > *:nth-child(3) { transition-delay: 0.14s; }
.ctl-actions.is-off > *:nth-child(1) { transition-delay: 0.08s; }
.ctl-actions.is-off > *:nth-child(2) { transition-delay: 0.04s; }
.ctl-actions.is-off > *:nth-child(3) { transition-delay: 0s; }

/* The calendar menu has its own small entrance. */
.ctl-menu {
  transition: opacity 0.18s ease, transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
              visibility 0.22s;
}

@media (prefers-reduced-motion: reduce) {
  .ctl-mode,
  .ctl-actions > *,
  .ctl-menu { transition-duration: 0.01ms; }
  .ctl-mode.is-off,
  .ctl-actions.is-off > * { transform: none; }
}

/* Action pills, matching the language pill's glass so the bar keeps one look
   across the swap. */
.ctl-btn {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 38px;
  padding: 0 14px;
  pointer-events: auto;
  cursor: pointer;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  color: var(--text);
  border: 1px solid rgba(124, 124, 124, 0.35);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.78);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 6px 20px rgba(83, 83, 83, 0.16);
  white-space: nowrap;
  transition: color 0.28s ease, background-color 0.28s ease, border-color 0.28s ease;
}
.ctl-btn svg { width: 15px; height: 15px; flex: 0 0 auto; }
.ctl-btn:hover { border-color: var(--accent); color: var(--ink); }

.ctl-menu-wrap { position: relative; display: flex; }
/* Opens upward: the bar is pinned to the bottom of the screen. */
.ctl-menu {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  /* Centring lives in the transform, so every state has to carry it or the menu
     slides sideways as it opens. */
  transform: translateX(-50%) translateY(0) scale(1);
  min-width: 176px;
  padding: 5px;
  pointer-events: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--white);
  box-shadow: 0 10px 30px rgba(83, 83, 83, 0.22);
}
.ctl-menu.is-off {
  opacity: 0;
  visibility: hidden;
  transform: translateX(-50%) translateY(6px) scale(0.96);
  pointer-events: none;
}
.ctl-menu-item {
  display: block;
  padding: 10px 12px;
  border-radius: var(--radius);
  font-size: 13px;
  color: var(--text);
  white-space: nowrap;
  cursor: pointer;
}
.ctl-menu-item:hover { background: var(--cream); color: var(--ink); }

/* Labelled, the three pills measure 335px. That clears a 360px screen but not a
   320px one, so the labels go only below that and the icons carry the meaning. */
@media (max-width: 350px) {
  .ctl-btn { padding: 0 13px; }
  .ctl-btn span { display: none; }
  .ctl-btn svg { width: 17px; height: 17px; }
}

.music-btn {
  width: 38px;
  height: 38px;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  pointer-events: auto;
  cursor: pointer;
  color: var(--muted);
  border: 1px solid rgba(124, 124, 124,0.35);
  border-radius: 999px;
  background: rgba(255, 255, 255,0.78);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 6px 20px rgba(83, 83, 83,0.16);
  transition: color 0.28s ease, background-color 0.28s ease;
}
.music-btn[hidden] { display: none; }
.music-btn svg { width: 21px; height: 21px; }
/* Playing: waves shown, filled thumb like the active language. Muted: cross. */
.music-btn .ico-cross { display: none; }
.music-btn.muted .ico-waves { display: none; }
.music-btn.muted .ico-cross { display: block; }
.music-btn.playing {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--white);
}
.lang-pill {
  position: relative;
  display: flex;
  align-items: stretch;
  height: 38px;
  pointer-events: auto;
  border: 1px solid rgba(124, 124, 124,0.35);
  border-radius: 999px;
  background: rgba(255, 255, 255,0.78);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  overflow: hidden;
  box-shadow: 0 6px 20px rgba(83, 83, 83,0.16);
}
.lang-thumb {
  position: absolute;
  top: 0;
  left: 0;
  width: 54px;
  height: 100%;
  border-radius: 999px;
  background: var(--accent);
  transition: left 0.32s cubic-bezier(0.4, 0, 0.2, 1);
}
.lang-pill[data-lang="en"] .lang-thumb { left: 54px; }

.lang-btn {
  position: relative;
  width: 54px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  padding: 0;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.28s ease;
}
.lang-btn.active { color: var(--white); }

/* ================================ COVER ================================= */
/* The cover artwork is one image: paper, frame, flourish, title and the black
   photo band are all part of it. Only the photo and the names are live elements,
   positioned as percentages of the plate so they stay registered to the artwork
   however it scales. */
/* The sheet fills the screen; the artwork sits centred on it.
   It cannot instead be scaled to fill: the plate is a fixed 9:16 and a phone is
   taller, so covering the height means cropping 9.0% off each side while the
   artwork only spares 8.2%. That last 0.8% is the frame's own outer line, and
   cutting it is exactly the break in the border this design keeps being caught
   on. Extending the sheet costs nothing and cuts nothing. */
.cover {
  position: relative;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Sampled off the plate's own edge, so the sheet and the artwork's paper read
     as one surface. The artwork cannot be recoloured to the page tone instead:
     its paper texture already reaches 255, so lifting the paper from 225 to 249
     left six levels of headroom for twenty-nine levels of grain and the paper
     went blotchy. */
  background: #e2dfd8;
  overflow: hidden;
}

.cover-plate {
  position: relative;
  /* Whichever of width or height runs out first decides the size, so the whole
     artwork stays visible on a short screen instead of running off the sheet. */
  width: min(100%, calc(100dvh * 1080 / 1920));
  aspect-ratio: 1080 / 1920;
  background: url("../assets/cover-plate.webp") center / 100% 100% no-repeat;
  container-type: inline-size;
}

/* Measured against the artwork: the black band's inner opening. */
.cover-photo {
  position: absolute;
  /* The black opening in the artwork, measured off the plate itself
     (x 225-854, y 783-1136 of 1080x1920). The photo has to fill it exactly —
     anything smaller leaves the black showing along the edges. */
  left: 20.83%;
  top: 40.78%;
  width: 58.33%;
  height: 18.44%;
}

/* Sits where the reference's script names sit. */
.cover-names {
  position: absolute;
  left: 50%;
  top: 60.5%;
  transform: translateX(-50%);
  width: max-content;
  font-family: var(--script);
  font-weight: 400;
  font-size: 6.15cqw;
  /* Great Vibes sets a very narrow word space, which closes the gaps either side
     of the ampersand that the reference leaves open. */
  word-spacing: 0.06em;
  line-height: 1.35;
  color: var(--ink);
  text-align: center;
  white-space: nowrap;
}

.cover-cities {
  position: absolute;
  left: 0;
  right: 0;
  top: 69.5%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2.4cqw;
  font-size: 2.1cqw;
  letter-spacing: 0.3em;
  color: #626262;
}
.cover-cities .dot {
  width: 0.9cqw;
  height: 0.9cqw;
  border-radius: 999px;
  background: var(--hint-soft);
}

/* ============================== INVITATION =============================== */
.section { position: relative; }

.invitation {
  padding: 64px 34px;
  text-align: center;
}
.poem {
  font-family: var(--serif);
  font-size: 19px;
  line-height: 2;
  color: var(--text-body);
  margin-bottom: 6px;
  white-space: pre-line;
  text-wrap: pretty;
}
.poem-credit {
  font-size: 12px;
  letter-spacing: 0.1em;
  color: var(--hint);
  margin-bottom: 34px;
}
.invite-text {
  font-size: 14px;
  line-height: 2.1;
  color: var(--text-body);
  margin-bottom: 34px;
  white-space: pre-line;
  text-wrap: pretty;
}

.families {
  display: grid;
  gap: 12px;
  justify-items: center;
  font-size: 14px;
  color: var(--text-mid);
}
.family {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 8px;
}
.family-name { font-weight: 500; }

/* ------------------------------- the film -------------------------------
   16:9, matching the film itself — a portrait frame would crop away more than
   half of every landscape shot. The poster and the video are stacked in the same
   box so swapping between them cannot shift the page. */
.film { margin: 32px auto 0; }
.film[hidden] { display: none; }
.film-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--cream);
  border: 1px solid var(--line);
}
.film-frame img,
.film-frame video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* The video is only in the DOM ahead of time so its src can be assigned on the
   tap that plays it — the tap browsers require before video may start. */
.film-frame video { display: none; background: var(--ink); }
.film.playing .film-frame video { display: block; }
.film.playing .film-frame img,
.film.playing .film-play { display: none; }

.film-play {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 54px;
  height: 54px;
  border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, 0.7);
  background: rgba(25, 25, 25, 0.34);
  color: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.25s ease, transform 0.25s ease;
}
.film-play:hover {
  background: rgba(25, 25, 25, 0.55);
  transform: translate(-50%, -50%) scale(1.06);
}
/* Optically centred: a play triangle's visual centre sits left of its box. */
.film-play svg { width: 19px; height: 19px; margin-left: 3px; }

/* The reference's equivalent contact button: a flat full-width grey block with
   no border and no radius, unlike the small bordered tiles it uses for links. */
.wish-btn {
  display: block;
  width: 100%;
  margin-top: 34px;
  padding: 12px 0;
  border: none;
  border-radius: 0;
  background: var(--btn-fill);
  font-size: 15px;
  letter-spacing: 0.08em;
}

/* ========================== CONTACT PANEL ==============================
   Opened by the "send your wishes" button; lists whichever of the six people
   have a number or an address recorded. */
.modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(25, 25, 25, 0.42);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  /* visibility flips with no duration on the way in, and waits for the fade on
     the way out. Transitioning it in both directions leaves the panel computed
     as hidden for the first frame, and a hidden element refuses focus — the
     close button was being skipped on the very first open and nowhere after,
     which is the kind of thing that only shows up on a fresh page. */
  transition: opacity 0.26s ease, visibility 0s;
}
.modal.is-off {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.26s ease, visibility 0s 0.26s;
}
/* Holds the page still behind the panel. */
body.modal-open { overflow: hidden; }

.modal-panel {
  width: 100%;
  max-width: 400px;
  max-height: 84vh;
  overflow-y: auto;
  padding: 22px 20px 26px;
  border-radius: var(--radius-lg);
  background: var(--white);
  box-shadow: 0 18px 50px rgba(25, 25, 25, 0.26);
  transform: translateY(0) scale(1);
  transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}
.modal.is-off .modal-panel { transform: translateY(14px) scale(0.97); }

.modal-head {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 20px;
}
.modal-title {
  flex: 1 1 auto;
  font-family: var(--sans);
  font-weight: 400;
  font-size: 15px;
  color: var(--ink);
}
.modal-close {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  background: none;
  color: var(--faint);
  cursor: pointer;
}
.modal-close svg { width: 18px; height: 18px; }
.modal-close:hover { color: var(--ink); }

.modal-sides {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 18px;
}
.contact-side-title {
  margin-bottom: 14px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 13px;
  color: var(--text);
  text-align: center;
}
.contact-row {
  display: grid;
  justify-items: center;
  gap: 3px;
  margin-bottom: 18px;
}
.contact-role { font-size: 11px; color: var(--hint); }
.contact-name { font-size: 13.5px; color: var(--ink); }
.contact-acts { display: flex; gap: 14px; margin-top: 5px; }
.contact-act {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  color: var(--muted);
  transition: color 0.2s ease;
}
.contact-act svg { width: 20px; height: 20px; }
.contact-act:hover { color: var(--ink); }

/* One side per row once two columns stop fitting. */
@media (max-width: 360px) {
  .modal-sides { grid-template-columns: 1fr; gap: 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .modal, .modal-panel { transition-duration: 0.01ms; }
  .modal.is-off .modal-panel { transform: none; }
}

/* ============================ VENUE PICKER =============================== */
.picker {
  padding: 56px 24px;
  background: var(--cream);
}
.picker-head { text-align: center; margin-bottom: 8px; }
.picker-intro {
  font-size: 14px;
  line-height: 1.9;
  color: var(--muted-soft);
  margin-top: 14px;
  white-space: pre-line;
  text-wrap: pretty;
}

.country-tabs {
  display: flex;
  gap: 10px;
  margin: 28px 0 18px;
}
.country-tab {
  flex: 1;
  padding: 11px 0;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  font-size: 12px;
  letter-spacing: 0.18em;
  cursor: pointer;
}
.country-tab.active {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--white);
}

.venue-cards { display: grid; gap: 14px; }

/* Each card is a boarding pass in three bands, after the banded reference:
   a dark header, a white body over the flight map, and a pale footer of detail
   fields ending in a barcode. The reference carries that structure in crimson
   and blush; here it is carried in the site's own greys, so the card reads as a
   ticket through its structure rather than through a colour the page never uses.

   The header and footer run the full width, behind the stub, as they do on a
   real pass; only the tear line crosses them. */
.venue-card {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 96px;
  grid-template-rows: auto 1fr auto;
  width: 100%;
  /* Sized by its contents: a pass prints its address, and an address wraps to a
     different number of lines per venue and per language. */
  height: auto;
  padding: 0;
  overflow: hidden;
  text-align: left;
  cursor: pointer;
  background: var(--white);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  transition: border-color 0.3s ease, box-shadow 0.3s ease, transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.venue-card.active {
  border-color: var(--accent);
  box-shadow: 0 10px 26px rgba(124, 124, 124, 0.18);
  transform: translateY(-3px);
}

/* ---- header band ---- */
.vc-header {
  grid-area: 1 / 1 / 2 / -1;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 0 11px;
  height: 24px;
  background: var(--ink);
  color: var(--white);
}
.vc-header-plane {
  width: 11px;
  height: 11px;
  flex: 0 0 auto;
  background: var(--white);
  -webkit-mask: var(--plane-mask) center / contain no-repeat;
  mask: var(--plane-mask) center / contain no-repeat;
}
.vc-header-title {
  font-family: var(--mono);
  font-weight: 500;
  font-size: 9.5px;
  letter-spacing: 0.2em;
}
/* Pushed right, and allowed to disappear before it can wrap the title. */
.vc-header-clock {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.7;
  white-space: nowrap;
  overflow: hidden;
}

/* ---- body band ---- */
.vc-body {
  grid-area: 2 / 1 / 3 / 2;
  position: relative;
  min-width: 0;
}

/* The flight map, framed on this card's own leg. */
.vc-map {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}
.vc-land { fill: var(--muted-soft); opacity: 0.17; }
.vc-arc {
  fill: none;
  stroke: var(--muted-soft);
  stroke-width: 1.1;
  stroke-dasharray: 2 3;
  stroke-linecap: round;
  opacity: 0.55;
  /* The two legs differ by two orders of magnitude in span, so the stroke has to
     be measured on screen rather than in the map's own degrees. */
  vector-effect: non-scaling-stroke;
}
.vc-dot { fill: var(--ink); opacity: 0.5; }
/* Shown instead of a plane when there is no leg to fly. */
.vc-marker {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.2;
  opacity: 0.45;
  vector-effect: non-scaling-stroke;
}
.vc-plane { fill: var(--ink); opacity: 0.45; }
.venue-card.active .vc-arc,
.venue-card.active .vc-marker,
.venue-card.active .vc-dot { opacity: 0.75; }
.venue-card.active .vc-plane { opacity: 0.7; }

/* ---- a field: label over value, as every field on a real pass is set ---- */
.vc-cell {
  position: relative;
  min-width: 0;
}
.vc-cell-label {
  font-family: var(--mono);
  font-size: 8px;
  letter-spacing: 0.18em;
  color: var(--faint);
  white-space: nowrap;
}
.vc-cell-value {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* ---- stub ---- */
.vc-stub {
  grid-area: 2 / 2 / 3 / -1;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 0;
  padding: 0 6px;
}

/* The tear runs the full height of the card and is notched at the card's own
   edges. Drawn as its own layer rather than as the stub's border, which stopped
   at the body band and left two circles floating mid-card. */
.vc-tear {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 96px;
  width: 0;
  border-left: 1px dashed var(--white);
  pointer-events: none;
}
.vc-tear::before,
.vc-tear::after {
  content: "";
  position: absolute;
  left: -6px;
  width: 11px;
  height: 11px;
  border-radius: 999px;
  background: var(--cream);
}
.vc-tear::before { top: -6px; }
.vc-tear::after { bottom: -6px; }

.vc-codes {
  display: flex;
  align-items: center;
  gap: 5px;
  font-family: var(--mono);
  font-weight: 600;
  font-size: 16px;
  letter-spacing: 0.02em;
  color: var(--ink);
}
/* Drawn, not typed: a plane glyph renders differently on every platform and
   falls back to a box on several. */
.vc-code-plane {
  width: 11px;
  height: 11px;
  flex: 0 0 auto;
  background: var(--ink);
  -webkit-mask: var(--plane-mask) center / contain no-repeat;
  mask: var(--plane-mask) center / contain no-repeat;
}
.vc-cities {
  display: grid;
  gap: 1px;
  width: 100%;
  text-align: center;
}
.vc-city-from,
.vc-city-to {
  font-family: var(--mono);
  font-size: 8.5px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--hint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vc-city-to { color: var(--muted-soft); }

/* ---- the stop list every pass carries ----
   The map behind stays absolute, so the body can leave flex for block. */
.vc-body {
  display: block;
  padding: 13px 11px;
}
.vc-stops { display: grid; }
.vc-stop { display: grid; gap: 1px; }
.vc-stop + .vc-stop {
  margin-top: 9px;
  padding-top: 9px;
  border-top: 1px dashed var(--line);
}
.vc-stop-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}
.vc-stop-label {
  font-family: var(--mono);
  font-size: 8px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--faint);
  white-space: nowrap;
}
.vc-stop-time {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.vc-stop-name {
  font-family: var(--serif);
  font-size: 16.5px;
  line-height: 1.15;
  color: var(--ink);
}
.vc-stop-address {
  font-family: var(--mono);
  font-size: 8.5px;
  line-height: 1.55;
  color: var(--muted-soft);
}
/* The map is a background, not a watermark — same as the single-venue body, the
   text needs its own ground to stay legible over the coastline. */
.vc-body .vc-stop-label,
.vc-body .vc-stop-time,
.vc-body .vc-stop-name,
.vc-body .vc-stop-address {
  text-shadow: 0 0 3px var(--white), 0 0 7px var(--white), 0 0 11px var(--white);
}

/* ---- footer band ---- */
.vc-foot {
  grid-area: 3 / 1 / 4 / -1;
  display: flex;
  align-items: center;
  /* Tight on purpose. The card is 382px, not the viewport's width, and at the
     roomier spacing the strip had one pixel spare — the hall field could not
     grow and ellipsed a name that would otherwise have fitted. */
  gap: 8px;
  height: 44px;
  padding: 0 11px;
  background: var(--cream);
  border-top: 1px solid var(--line-soft);
}
/* Dashed rules between fields, as the reference sets them. */
.vc-foot > .vc-cell + .vc-cell { padding-left: 8px; border-left: 1px dashed var(--line); }
.vc-foot > .vc-cell:first-child { flex: 1 1 auto; }
.vc-foot > .vc-cell:not(:first-child) { flex: 0 0 auto; }
.vc-barcode {
  /* No auto margin: a flex auto margin takes the free space before flex-grow is
     applied at all, which starved the hall field by exactly the slack sitting
     here — "Grand Ballroom, tầng 3" needs 150px and was given 148. The hall
     growing to fill puts the barcode at the right edge anyway. */
  flex: 0 0 auto;
  width: 46px;
  height: 24px;
  background-image: repeating-linear-gradient(
    to right,
    var(--ink) 0 1px, transparent 1px 3px,
    var(--ink) 3px 5px, transparent 5px 6px,
    var(--ink) 6px 7px, transparent 7px 10px
  );
  opacity: 0.62;
}
.venue-card.active .vc-barcode { opacity: 0.85; }

/* The footer cannot hold three fields, a barcode and the stub below about 400px:
   at 375 the strip needs 363px of a available 305. The flight code is the one to
   go — it is already printed on the stub as the destination code. */
@media (max-width: 400px) {
  .vc-cell-flight { display: none; }
  /* A two-stop pass has no hall field, so once the flight code goes the date is
     the first field left — and `+` counts DOM siblings, not visible ones, so it
     kept a divider with nothing on the other side of it. */
  .vc-foot-stops > .vc-cell-flight + .vc-cell { padding-left: 0; border-left: 0; }
}

/* Narrower still, everything gives a little. A long hall name will ellipse here
   whatever is done — 272px of card cannot hold it beside a date and a barcode —
   and it stays readable in full in the LOCATION section below. */
@media (max-width: 360px) {
  .venue-card { grid-template-columns: 1fr 84px; }
  .vc-tear { right: 84px; }
  .vc-header, .vc-body, .vc-foot { padding-left: 9px; padding-right: 9px; }
  .vc-stop-name { font-size: 15px; }
  .vc-stop-address { font-size: 8px; }
  .vc-codes { font-size: 14px; }
  .vc-barcode { width: 40px; }
  .vc-header-clock { font-size: 8px; }
}


/* ============================= WEDDING DAY ============================== */
.wedding-day {
  padding: 60px 24px 50px;
  text-align: center;
}
.wd-date {
  font-size: 15px;
  letter-spacing: 0.02em;
  color: var(--text-mid);
  margin: 20px 0 4px;
}
.wd-place { font-size: 13px; color: var(--hint); margin-bottom: 30px; }

.calendar {
  max-width: 330px;
  margin: 0 auto;
  padding: 22px 14px;
  background: var(--cream-deep);
}
.cal-month {
  font-family: var(--serif);
  font-size: 20px;
  color: var(--muted);
  margin-bottom: 16px;
}
.cal-dow-row,
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
}
.cal-dow-row {
  font-size: 11px;
  letter-spacing: 0.1em;
  color: var(--hint);
  margin-bottom: 8px;
}
.cal-dow-row .sun { color: var(--rose); }
.cal-grid { gap: 2px; }
.cal-day {
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  color: var(--text-body);
}
.cal-day .mark {
  width: 30px;
  height: 30px;
  border-radius: 999px;
  background: var(--accent);
  color: #f8f8f8;
  display: flex;
  align-items: center;
  justify-content: center;
}

.countdown {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-top: 22px;
}
.cd-cell {
  min-width: 62px;
  padding: 12px 6px;
  background: var(--cream);
  border: 1px solid var(--line-faint);
  display: grid;
  gap: 5px;
  justify-items: center;
}
.cd-num {
  font-family: var(--serif);
  font-size: 26px;
  line-height: 1;
  color: var(--text-mid);
  font-variant-numeric: tabular-nums;
}
.cd-lbl { font-size: 9.5px; letter-spacing: 0.16em; color: var(--hint); }

/* ================================ TIMELINE ==============================
   Two stops to a row, separated by hairlines rather than by gaps, so the set
   reads as one printed panel the way a paper run sheet does. */
.timeline { padding: 0 20px 54px; }
.timeline[hidden] { display: none; }
.timeline .eyebrow { padding: 14px 0 26px; }

.tl-grid {
  list-style: none;
  display: grid;
  grid-template-columns: 1fr 1fr;
  max-width: 380px;
  margin: 0 auto;
  border-top: 1px solid var(--line-soft);
}
.tl-cell {
  display: grid;
  justify-items: center;
  align-content: start;
  padding: 24px 10px 22px;
  border-bottom: 1px solid var(--line-soft);
  min-width: 0;
}
/* The column rule belongs to the left-hand cell, so it never lands on the
   panel's outer edge. */
.tl-cell:nth-child(odd) { border-right: 1px solid var(--line-soft); }
/* An odd number of stops would otherwise leave that rule dangling into empty
   space, so the last one takes the full width instead. */
.tl-cell:last-child:nth-child(odd) {
  grid-column: 1 / -1;
  border-right: 0;
}

/* A fixed box whatever the drawing's proportions — the wide ones (the car) and
   the tall ones (the champagne tower) then sit on a common baseline instead of
   each setting its own row height. */
.tl-art {
  width: 66px;
  height: 66px;
  margin-bottom: 14px;
}
.tl-art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.tl-time {
  font-family: var(--serif);
  font-size: 15px;
  letter-spacing: 0.13em;
  color: var(--text-mid);
  /* lining-nums, or Cormorant's default text figures set "18:00" at three
     different heights and turn the 1 into something that reads as a small-cap I.
     A clock face wants figures that all sit on the same two lines. */
  font-variant-numeric: lining-nums tabular-nums;
}
.tl-what {
  margin-top: 6px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 15.5px;
  line-height: 1.35;
  color: var(--muted);
  text-align: center;
  text-wrap: balance;
}

/* Below ~360px the two columns cannot hold a 56px drawing and a wrapped label
   without the words breaking mid-syllable, so everything gives a little. */
@media (max-width: 360px) {
  .tl-cell { padding: 20px 6px 18px; }
  .tl-art { width: 54px; height: 54px; margin-bottom: 11px; }
  .tl-time { font-size: 13.5px; letter-spacing: 0.1em; }
  .tl-what { font-size: 14px; }
}

/* ================================ LOCATION ============================== */
.location { padding: 0 0 56px; }
.location .eyebrow { padding: 14px 0 26px; }

.loc-date {
  text-align: center;
  padding: 0 24px 22px;
  font-size: 14px;
  letter-spacing: 0.02em;
  color: var(--text-mid);
}
/* One shape at a time — see renderSelectedVenue. */
.loc-date[hidden],
.loc-head[hidden],
.loc-places[hidden],
.loc-map-wrap[hidden] { display: none; }

.loc-head { text-align: center; padding: 0 24px; }
.loc-name { font-family: var(--serif); font-size: 26px; color: var(--text-mid); }
.loc-hall { font-size: 14px; color: var(--muted); margin-top: 6px; }
.loc-address { font-size: 13px; color: var(--faint); margin-top: 4px; }

.loc-map-wrap { padding: 0 24px; margin-top: 22px; }
/* display:block is required: this slot is an <a>, and an inline box ignores
   height and overflow, collapsing the panel.
   aspect-ratio rather than a fixed height, matched to the generated map images
   (620x300): with a fixed height the slot's aspect ranged 1.30-2.06 across
   viewports while the image is 2.07, so object-fit:cover cropped the sides and
   clipped the baked-in Mapbox/OpenStreetMap attribution — which is a licence
   condition, not decoration. Matching the aspect means nothing is ever cropped. */
.loc-map {
  display: block;
  position: relative;
  aspect-ratio: 620 / 300;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: #f7f3ec; /* the tinted maps' own background, so any gap is seamless */
}
.loc-links {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 10px;
}
.loc-links .btn-outline { padding: 12px 0; }

.loc-places {
  display: grid;
  gap: 26px;
  padding: 0 24px;
}
.loc-place-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding-bottom: 7px;
  margin-bottom: 10px;
  border-bottom: 1px solid var(--line-soft);
}
.loc-place-label {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--faint);
}
.loc-place-time {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.loc-place-name {
  font-family: var(--serif);
  font-size: 22px;
  line-height: 1.25;
  color: var(--text-mid);
}
.loc-place-address {
  font-size: 13px;
  line-height: 1.7;
  color: var(--faint);
  margin-top: 4px;
}

.loc-info {
  display: grid;
  gap: 22px;
  padding: 30px 30px 0;
}
.loc-info-item { display: grid; gap: 6px; }
.loc-info-title {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.04em;
  color: var(--text-mid);
}
.loc-info-body {
  font-size: 13px;
  line-height: 1.85;
  color: var(--muted-soft);
  white-space: pre-line;
}

/* ================================= GALLERY ============================== */
.gallery {
  padding: 70px 24px 64px;
  background: var(--white);
  text-align: center;
}
.gallery-title {
  font-size: 15px;
  font-weight: 400;
  letter-spacing: 0.34em;
  text-indent: 0.34em;
  color: var(--ink);
  margin-bottom: 44px;
}

.slides {
  position: relative;
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background: #efefef;
}
.slides-track {
  display: flex;
  width: 100%;
  height: 100%;
  transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}
.slide { flex: 0 0 100%; min-width: 0; }

.thumbs-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-top: 24px;
}
.slide-arrow {
  flex: 0 0 auto;
  width: 22px;
  height: 56px;
  border: none;
  background: none;
  padding: 0;
  font-size: 19px;
  line-height: 1;
  color: var(--faint);
  cursor: pointer;
}
.thumbs-window { flex: 1 1 auto; overflow: hidden; }
.thumbs-track {
  display: flex;
  gap: 8px;
  transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}
.thumb {
  flex: 0 0 66px;
  height: 80px;
  padding: 0;
  border: 1px solid transparent;
  background: #efefef;
  overflow: hidden;
  opacity: 0.45;
  cursor: pointer;
  transition: opacity 0.3s ease, border-color 0.3s ease;
}
.thumb.active { opacity: 1; border-color: var(--ink); }

/* ============================== WISHING WELL =============================
   Set as a note tinted and ruled off from the page, because it is the one thing
   on the card a guest is asked to take in besides the RSVP — as another run of
   centred grey text it read as the quietest block here and scrolled straight
   past. */
.wishing-well { padding: 54px 24px; }
.ww-note {
  max-width: 330px;
  margin: 0 auto;
  padding: 4px 26px 32px;
  text-align: center;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}
.ww-title {
  font-family: var(--serif);
  font-size: 23px;
  color: var(--text-mid);
}
/* The sentence that carries the meaning, so it is the one set large. */
.ww-lead {
  margin-top: 14px;
  font-family: var(--serif);
  font-style: italic;
  font-size: 17px;
  line-height: 1.55;
  color: var(--ink-soft);
  text-wrap: balance;
}
.ww-body {
  margin-top: 13px;
  font-size: 12.5px;
  line-height: 1.9;
  color: var(--muted-soft);
}

/* ================================== RSVP ================================ */
/* White, as in the reference: the filled fields and the flat submit are both
   pale greys, and on a grey band they stop reading as controls at all. */
.rsvp {
  padding: 56px 26px 64px;
  background: var(--white);
}
/* The intro and the deadline read as one block, so the air before the form is
   owned by the form (and by the thank-you that replaces it) rather than by
   whichever line happens to come last. */
.rsvp-intro {
  text-align: center;
  font-size: 13px;
  color: var(--faint);
  margin-top: 14px;
}
.rsvp-deadline {
  text-align: center;
  font-size: 12px;
  letter-spacing: 0.03em;
  color: var(--muted-soft);
  margin-top: 7px;
}
.rsvp-form,
.rsvp-thanks { margin-top: 28px; }

.rsvp-thanks {
  padding: 34px 20px;
  text-align: center;
  background: var(--cream-deep);
  border: 1px solid #dbdbdb;
}
.rsvp-thanks[hidden] { display: none; }
.thanks-title { font-family: var(--serif); font-size: 24px; color: var(--text-mid); }
.thanks-body {
  font-size: 13px;
  line-height: 1.9;
  color: var(--muted-soft);
  margin-top: 10px;
}

.rsvp-form { display: grid; gap: 20px; }
.rsvp-form[hidden] { display: none; }
.field { display: grid; gap: 8px; min-width: 0; }
/* The stepper is three fixed controls, so it takes only what it needs and the
   name field keeps the rest. Splitting the row evenly left the name at 182px
   next to 56px of empty column. */
.field-row { display: grid; grid-template-columns: 1fr auto; gap: 14px; }
.field[hidden] { display: none; }
.field-label { font-size: 12px; letter-spacing: 0.1em; color: var(--faint); }
/* The question in full, under its label — these two ask something a four-word
   label cannot, and the allergy one has to name the raw seafood. */
.field-hint {
  font-size: 11.5px;
  line-height: 1.7;
  color: var(--hint);
  margin-top: -3px;
}

/* Filled rather than outlined, per the reference: #efefef inside a hairline. */
.rsvp-form input,
.rsvp-form textarea {
  padding: 12px 10px;
  border: 1px solid var(--field-line);
  background: var(--field-fill);
  /* 16px is a floor, not a taste. Safari on iOS zooms the page whenever a text
     field it focuses is set smaller than this, and then leaves the guest zoomed
     in on a half-filled form. The alternative fix — user-scalable=no on the
     viewport — stops the zoom by taking pinch-zoom away from everyone, which is
     the wrong trade on an invitation older guests will read. */
  font-size: 16px;
  color: var(--ink);
  outline: none;
  min-width: 0;
}
.rsvp-form textarea { resize: vertical; }
.rsvp-form input:focus,
.rsvp-form textarea:focus { border-color: var(--accent); }

/* Guest count as a stepper: invalid input is impossible by construction, so the
   form needs no error copy for this field. */
.guest-stepper {
  display: flex;
  align-items: center;
  gap: 10px;
}
.guest-step {
  width: 38px;
  height: 38px;
  flex: 0 0 auto;
  border: 1px solid var(--field-line);
  background: var(--field-fill);
  color: var(--muted);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease;
}
.guest-step:hover:not([aria-disabled="true"]) { border-color: var(--accent); color: var(--accent); }
.guest-step[aria-disabled="true"] { opacity: 0.35; cursor: default; }
.guest-count {
  min-width: 30px;
  text-align: center;
  font-family: var(--serif);
  font-size: 19px;
  color: var(--text-mid);
  font-variant-numeric: tabular-nums;
}
#guests-field[hidden] { display: none; }
/* Reclaim the empty column so the name field spans the row on a decline. */
.field-row:has(> #guests-field[hidden]) { grid-template-columns: 1fr; }

/* Grouped by country, so four near-identical rows across two countries cannot
   be mistaken for one another. */
.venue-choices {
  display: grid;
  gap: 22px;
  /* Extra air under the field label: without it "WHICH CELEBRATION" and the
     first country heading read as two stacked labels rather than a label and
     the group it introduces. */
  margin-top: 8px;
}
.venue-choices-list { display: grid; gap: 8px; }
.venue-group { display: grid; gap: 9px; }
.venue-group-title {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.18em;
  color: var(--faint);
  padding-bottom: 7px;
  border-bottom: 1px solid var(--line-soft);
}
/* Belongs to neither country, so it is set apart from both rather than reading
   as the last entry of whichever group happens to come above it. */
.venue-decline { margin-top: -6px; }
.venue-choice {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding: 15px;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  background: var(--white);
  color: var(--text);
  font-size: 15px;
  text-align: left;
  cursor: pointer;
}
.venue-choice.active { border-color: var(--accent); background: var(--btn-fill); }
.venue-choice-date { color: var(--hint); font-size: 12px; }

/* Honeypot: removed from view and from the layout without display:none, which
   many bots treat as a signal to skip the field. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.rsvp-error {
  font-size: 12px;
  line-height: 1.7;
  color: #a4564a;
  text-align: center;
}
.rsvp-error:empty { display: none; }

/* ================================= FOOTER =============================== */
.footer {
  padding: 40px 24px 100px;
  text-align: center;
  background: var(--page);   /* the reference closes on its base tone */
}
.footer-names {
  font-family: var(--script);
  font-weight: 400;
  font-size: 34px;
  word-spacing: 0.06em;   /* Great Vibes' word space is too tight around the "&" */
  color: var(--accent);
}
.footer-actions {
  display: flex;
  gap: 10px;
  justify-content: center;
  margin: 22px 0 18px;
}
.footer-note { font-size: 11px; letter-spacing: 0.08em; color: var(--hint-soft); }

/* ============================ SCROLL REVEAL ============================= */
.reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 0.8s ease, transform 0.8s cubic-bezier(0.22, 1, 0.36, 1);
}
.reveal.visible { opacity: 1; transform: none; }

/* Narrow phones: the cover name line must not overflow the card. */
@media (max-width: 380px) {
  .cd-cell { min-width: 54px; }
}

@media (prefers-reduced-motion: reduce) {
  .envelope,
  .env-flap,
  .env-seal-group,
  .slides-track,
  .thumbs-track,
  .venue-card,
  .lang-thumb,
  .reveal {
    transition-duration: 0.01ms;
  }
  .env-flap { transition-delay: 0s; }
  .envelope.sealed .env-seal,
  .envelope.breaking .env-seal-ripple { animation: none; }
  .reveal { opacity: 1; transform: none; }
}
