/* Devanagari letterforms fill much less of the em box than Latin ones, so the
   same font-size reads a step or two smaller in Hindi than in English. This
   face is the system Devanagari font scaled back up to Latin's optical size;
   the unicode-range keeps it away from Latin, so it can sit at the front of
   every stack and fix Devanagari everywhere — including the "हिन्दी" language
   button, which shows on the English pages too. Browsers without size-adjust
   still get the font choice, just unscaled. */
@font-face {
  font-family: "Devanagari Matched";
  src: local("Kohinoor Devanagari"), local("KohinoorDevanagari-Regular"),
       local("Nirmala UI"), local("Noto Sans Devanagari"),
       local("Devanagari Sangam MN"), local("DevanagariSangamMN");
  size-adjust: 112%;
  unicode-range: U+0900-097F, U+1CD0-1CFA, U+A8E0-A8FF, U+200C-200D, U+25CC;
}

/* Devanagari has no true bold in the fallback stacks, so name the heavier
   faces explicitly rather than letting the browser smear the regular one at
   the 600-weight labels. If none are installed the face simply drops out and
   the regular one above is used. */
@font-face {
  font-family: "Devanagari Matched";
  font-weight: 500 900;
  src: local("Kohinoor Devanagari Semibold"), local("KohinoorDevanagari-Semibold"),
       local("Nirmala UI Bold"), local("Noto Sans Devanagari Bold"),
       local("Devanagari Sangam MN Bold"), local("DevanagariSangamMN-Bold");
  size-adjust: 112%;
  unicode-range: U+0900-097F, U+1CD0-1CFA, U+A8E0-A8FF, U+200C-200D, U+25CC;
}

:root {
  --ink: #314238;
  --muted: #68746c;
  --paper: #fbf8f2;
  --cream: #f4ecdf;
  --line: rgba(62, 83, 69, 0.18);
  --rose: #b96f75;
  --rose-dark: #8f5057;
  --rose-wash: #efd8d5;
  --sage: #718b72;
  --sage-light: #dce5d5;
  --saffron: #cc8a27;
  --marigold: #dda642;
  --green: #4d7055;
  --shadow: 0 24px 70px rgba(68, 61, 48, 0.12);
  /* One tracking for both places the names are set — the wordmark and the
     invitation heading — so they stay the same lockup at either size. */
  --name-tracking: 0.04em;
  /* Invitation type scale. Both celebrations share the whole ladder,
     including the names — 10% of --card-h, bound on .hero-copy so a
     short laptop and a phone retune one number for both cards. */
  --invite-greeting: clamp(21px, 2.5vw, 29px);
  --invite-eyebrow: 11px;
  --invite-place: 12px;
  --invite-date: 17px;
  --serif: "Devanagari Matched", "Iowan Old Style", "Baskerville", Georgia, serif;
  --display: "Devanagari Matched", "Bodoni 72", "Didot", "Iowan Old Style", Georgia, serif;
  --sans: "Devanagari Matched", "Avenir Next", "Segoe UI", system-ui, sans-serif;
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  min-width: 320px;
  background: var(--wash-base);
  transition: background-color 400ms ease;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

body.dialog-open { overflow: hidden; }
button, input, select, textarea { font: inherit; }
button, a { -webkit-tap-highlight-color: transparent; }
a { color: inherit; }
[hidden] { display: none !important; }

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: fixed;
  z-index: 1000;
  top: 10px;
  left: 10px;
  transform: translateY(-150%);
  padding: 10px 16px;
  border-radius: 6px;
  background: var(--ink);
  color: #fff;
}

.skip-link:focus { transform: none; }

:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--saffron), white 25%);
  outline-offset: 4px;
}

/* The page is a sheet of watercolor paper: a themed base tone, a handful of
   pigment pools, and a whisper of grain. One set of pools is drawn below and
   every theme re-inks them through the --wash-* variables — piedmont takes the
   blush of the California invite, singapore the peach and marigold of the
   Singapore one, dual a blend, and logged-out guests the neutral ivory of the
   garland painting itself. */
:root {
  --wash-base: #f5f2ea;
  --wash-1: #e7d8ba;
  --wash-2: #dee3cd;
  --wash-3: #eed7ae;
  --wash-top: rgba(247, 238, 231, 0.4);
}
body[data-theme="piedmont"] { --wash-base: #f6f1ec; --wash-1: #e2a89f; --wash-2: #eac3b8; --wash-3: #dde6cd; --wash-top: rgba(241, 230, 223, 0.26); }
body[data-theme="singapore"] { --wash-base: #f6f2e8; --wash-1: #f6d9c2; --wash-2: #f9e7d4; --wash-3: #f4ddae; --wash-top: rgba(247, 230, 198, 0.28); }
body[data-theme="dual"] { --wash-base: #f6f2ea; --wash-1: #edccbf; --wash-2: #f5dfc9; --wash-3: #e9dfc0; --wash-top: rgba(241, 223, 224, 0.28); }

.watercolor-field {
  position: fixed;
  z-index: -2;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

/* The theme's pigment settling at the top of the sheet. It lives on the fixed
   field rather than on the hero or on a section so every page — invitation,
   schedule, photos — is the same sheet of paper; nothing may paint an opaque
   background over it. ::before, so it sits under the pools and the grain.
   It fades out by the upper third: the middle of the page is where the cards
   land, and a card only reads as a card if the paper under it is clean. */
.watercolor-field::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--wash-top), transparent 46%);
}

/* Pigment pools. Two offset radial gradients per pool make a lobed, organic
   union instead of a circle; no highlight core and no dark rim — a bright
   heart with an edge reads as bokeh, not pigment. The blur melts the gradient
   edge into the paper; it sits on these bounded spans, never on a
   viewport-sized layer, so scrolling doesn't repaint the whole screen. */
.wash {
  position: absolute;
  opacity: 0.22;
  filter: blur(34px);
}

.wash-a, .wash-d {
  background:
    radial-gradient(closest-side at 38% 44%, var(--wash-1), color-mix(in srgb, var(--wash-1), transparent 55%) 60%, transparent 96%),
    radial-gradient(closest-side at 66% 60%, color-mix(in srgb, var(--wash-1), transparent 25%), transparent 92%);
}
.wash-b, .wash-e {
  background:
    radial-gradient(closest-side at 60% 38%, var(--wash-2), color-mix(in srgb, var(--wash-2), transparent 50%) 58%, transparent 95%),
    radial-gradient(closest-side at 36% 64%, color-mix(in srgb, var(--wash-2), transparent 30%), transparent 93%);
}
.wash-c {
  background:
    radial-gradient(closest-side at 46% 52%, var(--wash-3), color-mix(in srgb, var(--wash-3), transparent 50%) 56%, transparent 95%),
    radial-gradient(closest-side at 64% 40%, color-mix(in srgb, var(--wash-3), transparent 35%), transparent 90%);
}

.wash-a { left: -12vw; top: -10vh; width: clamp(340px, 52vw, 1150px); height: clamp(280px, 40vw, 880px); transform: rotate(-14deg); }
.wash-b { right: -14vw; top: -8vh; width: clamp(280px, 40vw, 900px); height: clamp(240px, 34vw, 760px); transform: rotate(11deg); opacity: 0.19; }
.wash-c { right: -4vw; top: 44vh; width: clamp(200px, 26vw, 580px); height: clamp(180px, 24vw, 520px); transform: rotate(-24deg); opacity: 0.17; }
.wash-d { right: -12vw; bottom: -12vh; width: clamp(320px, 46vw, 1040px); height: clamp(280px, 38vw, 840px); transform: rotate(17deg); opacity: 0.21; }
.wash-e { left: -8vw; bottom: -8vh; width: clamp(260px, 36vw, 800px); height: clamp(240px, 32vw, 700px); transform: rotate(-9deg); opacity: 0.19; }

/* A phone shows a fraction of the field, so neighbouring pools that read as
   separate blooms on desktop pile up into one; keep only the corner pools and
   thin them out. */
@media (max-width: 640px) {
  .wash { opacity: 0.16; }
  .wash-c, .wash-e { display: none; }
}

/* Cold-pressed paper tooth: a tiled fractal-noise SVG, inlined so it costs no
   request, multiplied at a whisper so it textures rather than dirties. */
.wash-grain {
  position: absolute;
  inset: 0;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='280' height='280'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.55' numOctaves='3' stitchTiles='stitch' seed='7'/%3E%3CfeColorMatrix values='0 0 0 0 0.42 0 0 0 0 0.37 0 0 0 0 0.28 0 0 0 0.55 0'/%3E%3C/filter%3E%3Crect width='280' height='280' filter='url(%23g)'/%3E%3C/svg%3E") left top / 280px 280px repeat;
  mix-blend-mode: multiply;
  opacity: 0.14;
}

.app-loading .utility-bar { visibility: hidden; }

/* Only the cold start (no cached payload) waits long enough to need this, so
   the spinner fades in after the delay rather than flashing on fast loads. */
.app-loading-veil { display: none; }
.app-loading .app-loading-veil {
  display: block;
  width: 22px;
  height: 22px;
  margin: 45vh auto 0;
  border: 2px solid var(--muted);
  border-right-color: transparent;
  border-radius: 50%;
  opacity: 0;
  animation: button-spin 650ms linear infinite, veil-fade 400ms ease 600ms forwards;
}
@keyframes veil-fade { to { opacity: 0.5; } }

.utility-bar {
  position: fixed;
  z-index: 50;
  top: 0;
  left: 0;
  width: 100%;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: clamp(22px, 4vw, 60px);
  align-items: center;
  padding: 18px clamp(20px, 4vw, 64px);
  border-bottom: 1px solid rgba(62, 83, 69, 0.12);
  /* Near-white and theme-independent: the bar reads the same across locations,
     warm enough not to go cold against the wash below it. */
  background: rgba(253, 252, 250, 0.9);
  backdrop-filter: blur(14px);
}

.wordmark {
  justify-self: start;
  text-decoration: none;
  color: var(--ink);
  font-family: var(--display);
  font-size: 22px;
  letter-spacing: var(--name-tracking);
}

.wordmark span { color: var(--rose); font-family: var(--serif); font-style: italic; }

.nav-cluster {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(12px, 2vw, 28px);
  min-width: 0;
}

.header-nav {
  display: flex;
  justify-content: center;
  gap: clamp(17px, 2.6vw, 40px);
  min-width: 0;
}

.header-nav a { text-decoration: none; font-size: 12px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; white-space: nowrap; }
.header-nav a:hover, .header-nav a[aria-current="page"] { color: var(--rose-dark); }
.header-nav a[aria-current="page"]::after { content: ""; display: block; height: 1px; margin-top: 3px; background: currentColor; }

.header-actions { display: flex; align-items: center; justify-content: flex-end; gap: 13px; }

.location-switcher {
  display: flex;
  align-items: center;
  margin: 0;
  padding: 3px;
  border: 1px solid rgba(51, 66, 56, 0.16);
  border-radius: 999px;
  background: color-mix(in srgb, var(--wash-base) 76%, transparent);
  backdrop-filter: blur(10px);
}

.location-switcher button {
  min-height: 29px;
  padding: 0 11px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .07em;
  text-transform: uppercase;
}

.location-switcher button[aria-pressed="true"] { background: var(--ink); color: #fff; }

.language-switcher {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 3px 7px;
  border: 1px solid rgba(51, 66, 56, 0.13);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.42);
  color: #899189;
  font-size: 13px;
}

.language-switcher button {
  padding: 3px 5px;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.08em;
}

.language-switcher button[aria-pressed="true"] { color: var(--rose-dark); }

.text-button {
  justify-self: end;
  padding: 6px 0;
  border: 0;
  border-bottom: 1px solid currentColor;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  font-size: 14px;
  letter-spacing: 0.06em;
}

.menu-button {
  display: none;
  flex-direction: column;
  justify-content: center;
  align-items: flex-end;
  gap: 4px;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}

.menu-button span {
  display: block;
  width: 18px;
  height: 1.5px;
  background: var(--ink);
  transition: background 150ms ease;
}

.utility-bar.menu-open .menu-button span { background: var(--rose-dark); }

.login-view {
  min-height: 100svh;
  display: grid;
  grid-template-columns: 1fr;
  align-items: stretch;
}

.login-card {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  min-height: 100svh;
  padding: 110px clamp(42px, 9vw, 150px) 70px;
  text-align: center;
}

.eyebrow {
  margin: 0 0 clamp(9px, 1.5svh, 14px);
  color: var(--rose-dark);
  font-size: var(--invite-eyebrow);
  font-weight: 600;
  letter-spacing: 0.19em;
  text-transform: uppercase;
}

.login-card > .eyebrow { margin-bottom: 22px; }

.login-card h1,
.hero h1 {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 0;
  color: #3d4d43;
  font-family: var(--display);
  font-size: clamp(58px, 7vw, 98px);
  font-weight: 400;
  /* Same tracking as the wordmark, so the names read as the top bar's lockup
     scaled up rather than a second, tighter face. */
  letter-spacing: var(--name-tracking);
  line-height: 0.78;
}

/* Tracking hangs a full space off the last letter; centered flex items would
   sit that far left of true center, so pull it back. */
.login-card h1 > *,
.hero h1 > * { margin-right: calc(var(--name-tracking) * -1); }

.login-card h1 small,
.hero h1 small {
  margin: clamp(6px, 1.1svh, 10px) calc(var(--name-tracking) * -1) clamp(7px, 1.3svh, 12px) 0;
  color: var(--rose);
  font-family: var(--serif);
  font-size: 0.36em;
  font-style: italic;
}

.login-subline {
  max-width: 36ch;
  margin: 26px 0 0;
  color: var(--muted);
  font-family: var(--serif);
  font-size: clamp(16px, 1.5vw, 18px);
  font-style: italic;
  line-height: 1.4;
  text-wrap: balance;
}

.login-subline + .login-form { margin-top: 32px; }

.flourish { display: flex; align-items: center; gap: 12px; width: 150px; margin: 36px auto 24px; color: var(--sage); }
.flourish span { height: 1px; flex: 1; background: var(--line); }
.flourish i { font-size: 9px; font-style: normal; }

.login-form { width: min(100%, 510px); text-align: left; }
.login-form > label { display: block; margin: 0 0 8px; color: var(--ink); font-size: 12px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; }
.code-row { display: grid; grid-template-columns: 1fr auto; gap: 10px; }

/* Painted helix under the code field. Width tracks the form, then yields to
   short viewports so the names stay on screen instead of the flowers clipping. */
.login-helix {
  display: block;
  width: min(100%, 510px, 55svh);
  height: auto;
  margin: 8px 0 0;
  pointer-events: none;
  user-select: none;
}

input, select, textarea {
  width: 100%;
  border: 1px solid rgba(51, 66, 56, 0.25);
  border-radius: 4px;
  background: rgba(255, 255, 255, 0.7);
  color: var(--ink);
}

input, select, textarea { min-height: 52px; }
input, select { padding: 0 16px; }
select { cursor: pointer; }
textarea { height: 52px; padding: 10px 12px; resize: vertical; line-height: 1.5; }
input:focus, select:focus, textarea:focus { border-color: var(--sage); box-shadow: 0 0 0 3px rgba(113, 139, 114, 0.14); outline: 0; }

.button {
  min-height: 48px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 24px;
  border: 1px solid transparent;
  border-radius: 3px;
  cursor: pointer;
  text-decoration: none;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  transition: transform 180ms ease, box-shadow 180ms ease, background 180ms ease;
}

.button:hover { transform: translateY(-1px); }
.button:disabled { cursor: wait; opacity: 0.6; transform: none; }
.button.is-loading:disabled { opacity: 1; }
.button.is-loading:hover { transform: none; }
.button.is-loading { text-transform: none; letter-spacing: 0.02em; font-weight: 500; font-size: 13px; pointer-events: none; cursor: wait; }
.button.is-loading::before {
  content: "";
  flex: none;
  width: 13px;
  height: 13px;
  margin-right: 10px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: button-spin 650ms linear infinite;
}
@keyframes button-spin { to { transform: rotate(360deg); } }
.button[data-coming-soon] { cursor: default; opacity: 1; border-color: var(--line); background: transparent; box-shadow: none; color: var(--muted); }
.button[data-coming-soon]:hover { border-color: var(--line); background: transparent; box-shadow: none; transform: none; }
.button-primary { background: var(--ink); color: white; box-shadow: 0 10px 25px rgba(49, 66, 56, 0.17); }
.button-primary:hover { background: #25352c; box-shadow: 0 12px 30px rgba(49, 66, 56, 0.22); }
.button-quiet { border-color: var(--line); background: transparent; color: var(--ink); }

/* Once the party has answered, "Edit RSVP" drops the dark-ink urgency for a
   confirmed green — a visible acknowledgement that the ask is already done,
   while staying solid enough to stay legible as the floating button. */
.button-primary[data-responded] { background: var(--green); box-shadow: 0 10px 25px rgba(77, 112, 85, 0.22); }
.button-primary[data-responded]:hover { background: #446349; box-shadow: 0 12px 30px rgba(77, 112, 85, 0.28); }

/* Until the party has responded, the RSVP buttons breathe a warm marigold halo.
   Sage reads as nothing against the cream page; the saffron accent carries. */
[data-open-rsvp][data-needs-rsvp] {
  --rsvp-glow-rest: 0 10px 25px rgba(49, 66, 56, 0.17);
  animation: rsvp-glow 3.4s ease-in-out infinite;
}
@keyframes rsvp-glow {
  0%, 100% { box-shadow: var(--rsvp-glow-rest), 0 0 0 0 rgba(221, 166, 66, 0), 0 0 0 0 rgba(204, 138, 39, 0); }
  50% { box-shadow: var(--rsvp-glow-rest), 0 0 0 5px rgba(221, 166, 66, 0.32), 0 0 28px 8px rgba(204, 138, 39, 0.8); }
}
.form-error { min-height: 1.5em; margin: 10px 0 0; color: #9c3f47; font-size: 13px; }

.hero {
  position: relative;
  min-height: 100svh;
  display: grid;
  place-items: center;
  overflow: hidden;
  padding: clamp(80px, 11svh, 108px) 24px clamp(32px, 7svh, 64px);
  /* No background of its own: the hero shows the same watercolor field the
     schedule and photos pages show, so the sheet doesn't change colour when a
     guest moves between pages. Only a soft bloom behind the card, faint enough
     to lift the stationery off the pools without reading as a second surface. */
  background: radial-gradient(circle at 50% 46%, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.22) 40%, transparent 68%);
  text-align: center;
}

.hero-copy {
  position: relative;
  z-index: 2;
  width: min(640px, 100%);
  container-type: inline-size;
  --card-h: clamp(375px, 33svh + 190px, 522px);
  --invite-names: calc(0.1 * var(--card-h));
  --invite-gap: calc(0.018 * var(--card-h));
}
.hero h1 { font-size: var(--invite-names); }
.hero-greeting { margin: 0 0 clamp(8px, 1.3svh, 14px); color: var(--rose-dark); font-family: var(--serif); font-size: var(--invite-greeting); font-style: italic; }
.celebration-dates { display: flex; justify-content: center; gap: 30px; margin: 20px auto 34px; }

/* Stationery, not a UI panel: paper stock, a marigold garland set in from the
   trimmed edge, and a shadow deep enough to read as a card lying on the page
   rather than a section background. */
.invitation-card {
  position: relative;
  /* The garland and the paper canvas both sit at z-index -1, above this card's
     own background and under its type. That only works if the card is a
     stacking context, and the only other thing declaring one is the
     [data-paper-shader] rule below — which js/paper.js applies on mount. No
     WebGL, no unpkg, no mount, and the garland would fall through to
     .hero-copy's context and paint behind the card's own background. */
  isolation: isolate;
  /* Reach is how deep the flowers actually come into the card. It tracks
     viewport height for the same reason the padding below it does: it is the
     thing being traded against the fold. Every pixel of reach costs two pixels
     of card, so a tall window gets the full garland and a 1280x720 laptop gets
     a slimmer strand rather than an RSVP button below the fold.

     Band is the transparent border border-image paints into, and it is 2.47x
     the reach rather than equal to it. That ratio is not a style choice: it is
     the painting's own corner-to-strand proportion (slice 235 over a 95px
     strand, see scripts/build-garland.mjs). border-image renders the corner
     block at exactly border-width, and this garland's corners sweep much wider
     than its strand, so the border has to be wide enough for the corner while
     the flowers along each run only fill the outer 41% of it. Reach, not band,
     is what the padding below is floored against — otherwise the type would be
     pushed off the fold to clear empty space.

     The side runs of the painting are wider than the top and bottom ones —
     0.56 of the band against 0.41 — so horizontal padding needs its own,
     deeper figure. One reach for both directions puts the greeting under the
     flowers on a phone. */
  /* The cream margin between the card's ruled edge and the first flower. It is
     a plain inset on .card-garland, so raising it moves the strand inward from
     both — further off the border, but also closer to the type. The padding
     floors below add it back, which is why the two move together and why much
     past this the greeting starts crowding the side runs. */
  --garland-inset: 14px;
  --garland-reach: clamp(26px, 4svh, 46px);
  --garland-band: calc(var(--garland-reach) * 2.47);
  --garland-reach-x: calc(var(--garland-band) * 0.57);
  margin: 0 0 clamp(16px, 3svh, 34px);
  /* Vertical padding tracks viewport HEIGHT, not width: the card plus the RSVP
     button beneath it has to clear the fold, and a short window is short
     whether or not it is wide. Horizontal padding still tracks width. Keeps
     the whole invitation above the fold from 1280x720 up, including the
     tallest variant (the single-event card, which adds ~160px). Both terms are
     floored at the garland's own thickness plus a gap, so the type can never
     end up printed over the flowers. */
  padding:
    max(clamp(24px, 4.2svh, 48px), calc(var(--garland-inset) + var(--garland-reach) + 6px))
    max(clamp(28px, 5vw, 62px), calc(var(--garland-inset) + var(--garland-reach-x) + 10px));
  border: 1px solid rgba(62, 83, 69, 0.3);
  border-radius: 3px;
  background: linear-gradient(180deg, rgba(255, 254, 251, 0.94), rgba(252, 249, 242, 0.9));
  /* Two shadows and a tight contact one: the page is nearly white now, so the
     card is separated by the lift under its edge rather than by tone. */
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.75) inset, 0 30px 70px rgba(68, 61, 48, 0.19), 0 6px 18px rgba(68, 61, 48, 0.1), 0 2px 4px rgba(68, 61, 48, 0.07);
  /* One gap between every printed line — hosts, eyebrow, each name, the
     flourish, place, and date. The rhythm is shared; so is the box. */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--invite-gap);
  line-height: 1.2;
  /* A floor and not a height: Singapore is content-sized, and one variant
     outgrows the shared --card-h — the reception-only card prints its whole
     event (renderSingleEvent) and runs ~160px taller. That one keeps growing.
     California never has it; it sets its own height from the painting. */
  min-height: var(--card-h);
}

/* The paper texture (see js/paper.js), on the invitation card and on each day
   card of the schedule. Paper Shaders prepends a canvas to the card, marks the
   card with data-paper-shader, and styles the pair from a stylesheet it injects
   into the head, which our CSP allows by hash. Saying the same thing here means
   a canvas that ever arrives unstyled — a bumped library version, a blocked
   injection — still lies behind the type instead of shoving the card's contents
   down the page as a grey block. The isolation is what puts the sheet on top of
   the card's own background rather than behind the whole card. */
[data-paper-shader] {
  position: relative;
  isolation: isolate;
}

[data-paper-shader] > canvas {
  position: absolute;
  inset: 0;
  z-index: -1;
  display: block;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  contain: strict;
}

/* The marigold garland, in place of the hairline rule that used to sit at this
   inset. images/garland.webp is the painted garland cut into a nine-slice sheet
   (see scripts/build-garland.mjs): border-image plants its four corner blocks
   in the card's corners at a fixed size and repeats the edge tiles between
   them, so a reception-only card that is 160px taller grows more flowers rather
   than longer ones. The source painting is a portrait loop; a single stretched
   picture would squash every marigold into an oval on the tall variant.

   `round` is what makes the repeat seamless — it flexes each tile by a few
   percent to fit a whole number, where `stretch` would smear one tile across
   the whole run and `space` would open gaps and break the strand. The tiles are
   cut as mirrored pairs so that a tile's two outer edges are the same column of
   the painting, which is why the joins between repeats do not show.

   The 235 is the sheet's corner block in its own pixels and must match SLICE in
   the generator; re-cut the sheet at a different size and this number moves
   with it. Cache busting here is manual — bump ?v= whenever the sheet is
   rebuilt. */
.card-garland {
  position: absolute;
  inset: var(--garland-inset);
  z-index: -1;
  pointer-events: none;
  border: var(--garland-band) solid transparent;
  border-image: url("/images/garland.webp?v=20260816-5") 235 round;
  /* Load-bearing no-op, for real iOS Safari only. Once the paper shader's
     WebGL canvas puts this stacking context on the GPU, iOS skips painting a
     border-image on an element that paints nothing else — no background, no
     visible border-color — and the marigolds vanish (desktop Safari and
     headless WebKit both render it fine, which is why /qa never catches it).
     A transparent background-image is the cheapest thing that forces the
     element's paint phase to exist. Bisected on-device 2026-08-16; retest on
     a phone before deleting. */
  background-image: linear-gradient(transparent, transparent);
  /* Ink on the stock, not a sticker on top of it. Both garlands paint at the
     same z-index as the paper canvas (js/paper.js) and after it in tree order,
     so multiply is what lets the sheet's tooth and fibre read up through the
     flowers the way it already reads through the card's cream. Painted opaque,
     the marigolds were the one perfectly flat thing on the invitation.

     It costs nothing when the shader does not mount: the backdrop is then the
     card's near-white gradient, and multiplying against that is within a
     rounding error of identity. Piedmont leans on this rule for a second
     reason as well — see the California block below. */
  mix-blend-mode: multiply;
}

body[data-theme="singapore"] .invitation-card { border-color: rgba(162, 104, 29, 0.26); }

/* ── California: the painted rose frame ───────────────────────────────────
   Piedmont drops the marigold strand for images/california-garland.webp
   (assets/california_garland.jpg, trimmed to its ink and re-encoded). The
   two are not interchangeable: the marigold sheet is a nine-slice strand
   that can grow to any box, while this is one finished painting — corner
   sprays, a rose cluster centred on the top and bottom runs, and measured
   gaps between them. Slice it or stretch it and the composition is gone.

   The card tracks the painting's proportion, plus a 5% height bump so
   the type has a little more air inside the rose interior. The shipped
   asset is 1400×980 (see scripts/build-california-garland.mjs); --card-h
   is 7/10 of the card's width times 1.05, and aspect-ratio restates that
   so a taller type stack cannot grow the box further. Width still comes
   from .hero-copy's min(640px, 100%). Singapore keeps the shared svh
   clamp; toggling location changes card height.

   .hero-copy is the query container rather than the card: cqw used for the
   card's own padding would otherwise resolve against a content box that the
   padding defines. The card fills it, so 100cqw is the card's width. */
body[data-theme="piedmont"] .hero-copy {
  --card-h: calc(100cqw * 7 / 10 * 1.05);
}

body[data-theme="piedmont"] .invitation-card {
  width: 100%;
  height: var(--card-h);
  aspect-ratio: 10 / 7.35;
  min-height: 0;
  /* Measured off the shipped asset: the interior is clear of ink from 11.4%
     to 87.5% across and 28.5% to 77.5% down. Down is a fraction of the
     card's height, across a fraction of its width, which is why only one of
     the three is in cqw. Re-crop the source and these numbers move with it. */
  padding: calc(0.286 * var(--card-h)) 13cqw calc(0.224 * var(--card-h));
}

/* One picture, not a border: no slicing, no repeat, painted edge to edge
   inside the trim. The source is watercolour on white, and the shared rule's
   multiply is what drops that white out — the paper texture and the card's
   cream read straight through the wash, so the asset stays a flat webp with
   no alpha channel to fringe at the soft edges. That makes multiply
   load-bearing here in a way it is not for the marigold sheet, which carries
   its own alpha: take it off .card-garland and Singapore loses its grain,
   while California gains a white box. */
body[data-theme="piedmont"] .card-garland {
  inset: 0;
  border: 0;
  border-image: none;
  background: url("/images/california-garland.webp?v=20260816-2") center / 100% 100% no-repeat;
}

/* California's flourish is shorter because the rose interior is narrower;
   the vertical rhythm is already the shared --invite-gap. */
body[data-theme="piedmont"] .invitation-card .flourish { width: 16cqw; gap: 1.4cqw; }
body[data-theme="piedmont"] .invitation-card .flourish i { font-size: calc(0.015 * var(--card-h)); }

.invitation-card .hero-greeting,
.invitation-card .eyebrow { margin: 0; }
/* Names sit on one line on both cards. Stacked, Katie / & / Akshay ate most
   of the vertical room; a row is what lets the supporting lines keep the
   shared type scale, and on California it is what lets them fit the 49%
   clear hole of the rose frame. */
.invitation-card h1 {
  flex-direction: row;
  flex-wrap: nowrap;
  justify-content: center;
  align-items: baseline;
  gap: 0.18em;
  line-height: 1;
  white-space: nowrap;
}
.invitation-card h1 small { margin-top: 0; margin-bottom: 0; }
.invitation-card .flourish { margin: 0 auto; }
.invitation-card .single-event { margin: 0 auto; }
.invitation-card .celebration-dates,
.invitation-card .celebration-dates p {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--invite-gap);
  margin: 0 auto;
}
.celebration-dates p { margin: 0; }
.celebration-dates strong, .celebration-dates span { display: block; }
.celebration-dates strong { font-size: var(--invite-place); letter-spacing: 0.14em; text-transform: uppercase; }
.celebration-dates span { color: var(--muted); font-family: var(--serif); font-size: var(--invite-date); }

/* The one invited event, printed on the invitation itself for the parties who
   have no schedule page to send them to. A hairline rule sets it off, so it
   reads as a second stanza of the stationery rather than a panel bolted
   underneath. */
.single-event { margin: clamp(10px, 1.7svh, 18px) auto 0; padding-top: clamp(12px, 1.9svh, 20px); border-top: 1px solid rgba(62, 83, 69, 0.18); }
body[data-theme="singapore"] .single-event { border-top-color: rgba(162, 104, 29, 0.22); }
.single-event-title { margin: 0; font-family: var(--serif); font-size: clamp(22px, 2.7vw, 25px); line-height: 1.15; }
.single-event-time { margin: 2px 0 0; color: var(--muted); font-family: var(--serif); font-size: 17px; }
.single-event-venue { margin: 6px 0 0; color: var(--rose-dark); font-family: var(--serif); font-size: 16px; font-style: italic; }
body[data-theme="singapore"] .single-event-venue { color: #9e671d; }
.single-event-address { margin: 3px 0 0; color: var(--muted); font-size: 12px; line-height: 1.4; }
.single-event-detail { margin: 5px 0 0; color: var(--muted); font-size: 13px; line-height: 1.45; }
.single-event-detail strong { color: var(--ink); font-weight: 500; }
/* The pin rides italic venue lines; the glyph itself should stay upright. */
.map-pin { font-style: normal; text-decoration: none; white-space: nowrap; }
.map-pin:hover { opacity: .7; }

.page-content-section { min-height: 70svh; padding-top: clamp(145px, 14vw, 190px); }
.section { padding: clamp(76px, 10vw, 140px) clamp(20px, 6vw, 86px); scroll-margin-top: 52px; }
.section-heading { max-width: 680px; margin: 0 auto 56px; text-align: center; }
.section-heading h1, .section-heading h2 { margin: 0 0 14px; font-family: var(--display); font-size: clamp(42px, 5vw, 68px); font-weight: 400; line-height: 1.08; }
.section-heading > p:last-child { margin: 0; color: var(--muted); font-family: var(--serif); font-size: 19px; }

.schedule-section:has(#schedule-live[hidden]) { display: flex; flex-direction: column; min-height: 100svh; }
.schedule-coming-soon { max-width: 480px; margin: -20px auto 0; padding: 48px 0; color: var(--muted); font-family: var(--serif); font-size: 24px; font-style: italic; text-align: center; }
.location-map-panel {
  max-width: 1180px;
  margin: 0 auto 62px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.62);
  box-shadow: 0 18px 45px rgba(64, 73, 63, 0.08);
  overflow: hidden;
}
.location-map-layout { display: grid; grid-template-columns: minmax(0, 1.65fr) minmax(260px, .75fr); min-height: 500px; background: var(--paper); }
.location-map { z-index: 0; min-height: 500px; background: #e8eadf; outline-offset: 3px; scroll-margin-top: 80px; }
.location-map.map-fallback { display: grid; place-items: center; padding: 35px; text-align: center; }
.map-status { margin: 0; color: var(--muted); font-family: var(--serif); font-size: 17px; }
.location-map-key { max-height: 500px; margin: 0; padding: 0; border-left: 1px solid var(--line); list-style: none; overflow-y: auto; }
.location-map-key li { display: grid; grid-template-columns: minmax(0, 1fr) 36px; align-items: stretch; border-bottom: 1px solid var(--line); }
.location-map-key li:last-child { border-bottom: 0; }
.location-map-key button { display: grid; grid-template-columns: 30px minmax(0, 1fr); gap: 10px; align-items: start; padding: 15px 10px 15px 15px; border: 0; background: transparent; color: var(--ink); text-align: left; cursor: pointer; }
.location-map-key button:hover, .location-map-key button:focus-visible { background: rgba(95, 119, 95, .08); }
.location-map-key button > span:last-child { min-width: 0; }
.location-map-key strong { display: block; font-family: var(--serif); font-size: 15px; font-weight: 600; line-height: 1.2; }
.location-map-key small { display: block; margin-top: 4px; color: var(--muted); font-size: 10px; line-height: 1.4; }
.map-key-number { display: grid; width: 27px; height: 27px; place-items: center; border-radius: 999px; background: var(--rose-dark); color: #fff; font-family: var(--sans); font-size: 11px; font-weight: 700; }
body[data-theme="singapore"] .map-key-number { background: #a2681d; }
.location-map-key li > a { display: grid; place-items: center; color: var(--muted); text-decoration: none; }
.location-map-key li > a:hover, .location-map-key li > a:focus-visible { background: rgba(95, 119, 95, .08); color: var(--rose-dark); }
.wedding-map-marker { border: 0; background: transparent; }
.wedding-map-marker span { display: grid; width: 36px; height: 36px; place-items: center; border: 3px solid #fff; border-radius: 50% 50% 50% 7px; background: var(--rose-dark); color: #fff; box-shadow: 0 5px 13px rgba(34, 44, 38, .28); transform: rotate(-45deg); }
.wedding-map-marker b { font-family: var(--sans); font-size: 12px; font-weight: 700; transform: rotate(45deg); }
body[data-theme="singapore"] .wedding-map-marker span { background: #a2681d; }
.map-popup { min-width: 220px; color: var(--ink); }
.map-popup h3 { margin: 0 0 5px; font-family: var(--serif); font-size: 18px; line-height: 1.2; }
.location-title-secondary { opacity: .62; }
.map-popup p { margin: 5px 0; color: var(--muted); font-size: 16px; line-height: 1.45; }
.map-popup .map-popup-events { margin-top: 10px; }
.map-popup a { display: inline-block; margin-top: 7px; color: var(--rose-dark); font-size: 11px; font-weight: 700; text-decoration: none; }
.leaflet-control-attribution { font-size: 9px !important; }
/* The map is what the schedule page is for on arrival, so the list underneath
   it holds back — legible, but clearly second — until the guest scrolls. It is
   a gradient rather than a flat veil: faintest where it meets the map and
   gathering strength down the page, so the schedule emerges from the map
   instead of sitting under a sheet of tracing paper. The stops are lengths,
   not percentages, so the ramp covers about a screen regardless of how many
   days the party runs.

   --list-fade is the strength of that veil: 1 on arrival, 0 once the guest
   scrolls (and 1 again back at the top, see settleOnScroll). It is a
   registered property so it can be transitioned — a raw custom property would
   snap. Tabbing into the list counts as arriving too, so keyboard guests never
   read it at reduced contrast. */
@property --list-fade {
  syntax: "<number>";
  inherits: true;
  initial-value: 1;
}
.event-list {
  max-width: 760px;
  margin: auto;
  --list-fade: 1;
  --list-veil: linear-gradient(180deg,
    rgba(0, 0, 0, calc(1 - .82 * var(--list-fade))) 0,
    rgba(0, 0, 0, calc(1 - .56 * var(--list-fade))) 210px,
    #000 660px);
  -webkit-mask-image: var(--list-veil);
  mask-image: var(--list-veil);
  transition: --list-fade 600ms ease;
}
body.page-scrolled .event-list, .event-list:focus-within { --list-fade: 0; }
@media (prefers-reduced-motion: reduce) { .event-list { transition: none; } }
.celebration-block + .celebration-block { margin-top: 100px; }
.celebration-heading { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 20px; margin-bottom: 45px; }
.celebration-heading span { height: 1px; background: var(--line); }
.celebration-heading h3 { margin: 0; color: var(--rose-dark); font-family: var(--serif); font-size: 22px; font-weight: 500; font-style: italic; }
.celebration-block.singapore .celebration-heading h3 { color: #a2681d; }
.day-group {
  padding: 25px 28px 5px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.43);
}
.day-group + .day-group { margin-top: 34px; }
/* The status badge belongs to the day, pinned to the heading's top-right.
   align-items: start keeps it on the first line when the date wraps. */
.day-header { display: flex; align-items: start; justify-content: space-between; gap: 14px 20px; margin-bottom: 13px; }
.day-heading {
  margin: 0;
  color: var(--ink);
  font-family: var(--display);
  font-size: clamp(26px, 2.8vw, 34px);
  font-weight: 400;
  letter-spacing: -.015em;
  line-height: 1.08;
}

.event-card {
  position: relative;
  display: grid;
  grid-template-columns: 152px minmax(0, 1fr);
  column-gap: 64px;
  align-items: start;
  padding: 20px 0;
}
.event-card::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 183.5px;
  width: 1px;
  background: rgba(62, 83, 69, 0.2);
}
.event-card::after {
  content: "";
  position: absolute;
  top: 25px;
  left: 178px;
  width: 9px;
  height: 9px;
  border: 2px solid var(--sage);
  border-radius: 50%;
  background: var(--paper);
}
.day-header + .event-card::before { top: 31px; }
.event-card:last-child::before { bottom: calc(100% - 31px); }
.event-time { padding-top: 5px; color: var(--muted); font-family: var(--serif); font-size: 15px; text-align: right; white-space: nowrap; }
.event-body { min-width: 0; padding: 1px 0 2px; }
.event-title { margin: 0; font-family: var(--serif); font-size: 25px; font-weight: 500; line-height: 1.08; }
.response-status { flex: 0 0 auto; margin-top: 8px; padding: 4px 8px; border-radius: 999px; background: #eee9df; color: #78756e; font-size: 9px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; white-space: nowrap; }
.response-status.complete { background: var(--sage-light); color: #4c684e; }
.response-status.partial { background: #f5e5bd; color: #8c621e; }
.event-venue { margin: 3px 0 5px; color: var(--rose-dark); font-family: var(--serif); font-size: 16px; font-style: italic; }
.event-card[data-celebration="singapore"] .event-venue { color: #9e671d; }
.event-address { max-width: 620px; margin: 1px 0 6px; color: var(--muted); font-size: 12px; line-height: 1.4; }
.event-detail { margin: 2px 0; color: var(--muted); font-size: 13px; line-height: 1.45; }
.event-detail strong { color: var(--ink); font-weight: 500; }

.detail-grid { max-width: 1120px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; margin: auto; }
.detail-card { position: relative; min-height: 280px; padding: 38px 40px; border: 1px solid var(--line); border-radius: 10px; background: rgba(255,255,255,.48); overflow: hidden; }
.detail-card::after { content: ""; position: absolute; right: -50px; bottom: -60px; width: 150px; height: 150px; border-radius: 45% 55% 42% 58%; background: var(--rose-wash); opacity: .45; filter: blur(7px); }
.detail-card.singapore::after { background: #efcd7e; }
.detail-icon { color: var(--sage); font-family: var(--serif); font-size: 34px; }
.detail-scope { min-height: 1.5em; margin: 7px 0 0; color: var(--rose-dark) !important; font-size: 10px !important; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; }
.detail-card h3 { margin: 5px 0 12px; font-family: var(--serif); font-size: 27px; font-weight: 500; line-height: 1.2; }
.detail-card p { position: relative; z-index: 1; margin: 0; color: var(--muted); font-size: 14px; }

.photo-section { overflow: hidden; }
.photo-collage { max-width: 1050px; display: grid; grid-template-columns: 1.05fr .9fr 1.05fr; grid-template-rows: 50px 390px 50px 50px 390px 50px 50px 390px 50px; gap: 20px; margin: auto; }
.photo-collage figure { position: relative; margin: 0; border-radius: 10px; overflow: hidden; background: #d9d5cd; box-shadow: var(--shadow); }
.photo-collage figure::after { content: ""; position: absolute; inset: 0; box-shadow: inset 0 0 0 1px rgba(255,255,255,.35); pointer-events: none; }
/* Held at opacity 0 until the file decodes, which also keeps the alt text from
   showing through mid-load; .is-loaded is set from renderPhotos(). */
.photo-collage img { width: 100%; height: 100%; display: block; object-fit: cover; filter: saturate(.82) contrast(.94); opacity: 0; transform: scale(1.02); transition: opacity 700ms ease, transform 900ms ease; }
.photo-collage img.is-loaded { opacity: 1; transform: none; }
.photo-1 { grid-row: 1 / 3; transform: rotate(-1.5deg); }
.photo-2 { grid-row: 2 / 4; transform: translateY(-14px) rotate(1deg); }
.photo-3 { grid-row: 1 / 3; transform: rotate(1.8deg); }
.photo-4 { grid-row: 4 / 6; transform: rotate(1.2deg); }
.photo-5 { grid-row: 5 / 7; transform: translateY(-14px) rotate(-1deg); }
.photo-6 { grid-row: 4 / 6; transform: rotate(-1.6deg); }
.photo-7 { grid-row: 7 / 9; transform: rotate(-1.3deg); }
.photo-8 { grid-row: 8 / 10; transform: translateY(-14px) rotate(1.1deg); }
.photo-9 { grid-row: 7 / 9; transform: rotate(1.7deg); }
/* Keep native ratios instead of covering these into a mismatched frame. */
.photo-collage figure[data-photo-id="garden-05"],
.photo-collage figure[data-photo-id="garden-07"] {
  align-self: center;
  width: 100%;
  height: auto;
}
.photo-collage figure[data-photo-id="garden-05"] { aspect-ratio: 1600 / 1512; }
.photo-collage figure[data-photo-id="garden-07"] { aspect-ratio: 1200 / 1600; }
.photo-collage figure[data-photo-id="garden-05"] img,
.photo-collage figure[data-photo-id="garden-07"] img { object-fit: contain; }

.faq-list { max-width: 800px; margin: auto; border-top: 1px solid var(--line); }
.faq-list details { border-bottom: 1px solid var(--line); }
.faq-list summary { display: flex; justify-content: space-between; align-items: center; gap: 24px; padding: 24px 3px; cursor: pointer; list-style: none; font-family: var(--serif); font-size: 21px; font-weight: 500; }
.faq-list summary::-webkit-details-marker { display: none; }
.faq-list summary span { position: relative; width: 18px; height: 18px; flex: 0 0 18px; }
.faq-list summary span::before, .faq-list summary span::after { content: ""; position: absolute; top: 8px; left: 0; width: 18px; height: 1px; background: var(--ink); transition: transform 180ms ease; }
.faq-list summary span::after { transform: rotate(90deg); }
.faq-list details[open] summary span::after { transform: rotate(0); }
.faq-list details p { margin: -6px 38px 26px 3px; color: var(--muted); font-family: var(--serif); font-size: 18px; }

.rsvp-fab {
  position: fixed;
  z-index: 29;
  right: 24px;
  bottom: 20px;
  min-width: 160px;
  padding: 11px 18px;
  border: 0;
  border-radius: 4px;
  background: var(--ink);
  color: white;
  box-shadow: 0 13px 35px rgba(34,48,39,.28);
  cursor: pointer;
  text-align: left;
}
.rsvp-fab > span { display: block; font-size: 12px; font-weight: 600; letter-spacing: .13em; }
.rsvp-fab small { display: block; color: rgba(255,255,255,.68); font-size: 9px; }
.rsvp-fab[data-needs-rsvp] { --rsvp-glow-rest: 0 13px 35px rgba(34, 48, 39, .28); }
.rsvp-fab[data-responded] { background: var(--green); box-shadow: 0 13px 35px rgba(60, 90, 66, .3); }
.rsvp-fab[data-responded] small { color: rgba(255, 255, 255, .74); }

.rsvp-dialog {
  width: min(930px, calc(100vw - 32px));
  max-height: calc(100svh - 32px);
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: var(--paper);
  color: var(--ink);
  box-shadow: 0 30px 100px rgba(31, 37, 32, 0.3);
}
.rsvp-dialog::backdrop { background: rgba(35, 43, 37, .62); backdrop-filter: blur(4px); }
.dialog-shell { padding: clamp(25px, 4vw, 44px); }
.dialog-header { display: flex; justify-content: space-between; align-items: center; gap: 25px; padding-bottom: 22px; border-bottom: 1px solid var(--line); }
.dialog-header h2 { margin: 0; font-family: var(--display); font-size: clamp(34px, 5vw, 54px); font-weight: 400; line-height: 1; }
.icon-button { width: 42px; height: 42px; border: 1px solid var(--line); border-radius: 50%; background: transparent; color: var(--ink); cursor: pointer; font-family: var(--serif); font-size: 27px; line-height: 1; }
.dialog-intro { max-width: 640px; margin: 20px 0 26px; color: var(--muted); font-family: var(--serif); font-size: 17px; }
.rsvp-attendance { margin-bottom: 32px; padding: 20px 24px; border: 1px solid var(--line); border-radius: 3px; background: rgba(255, 255, 255, .78); }
.attendance-question { margin: 0; font-family: var(--serif); font-size: 23px; font-weight: 500; line-height: 1.3; }
/* A party of one answers with a labelled yes/no pair. A larger party answers
   in a name/yes/no matrix, which the events reuse when two or more guests are
   attending. The direct-child selectors keep the big-button sizing off the
   matrix rows. */
.attendance-choices > .choice-group { margin-top: 14px; grid-template-columns: repeat(2, minmax(84px, 116px)); }
.attendance-choices > .choice-group label { row-gap: 4px; min-height: 58px; }
.attendance-choices > .choice-group .choice-text { color: var(--muted); font-size: 12px; letter-spacing: .03em; }
.attendance-choices > .response-matrix { margin-top: 12px; }
.attendance-choices .guest-response > p { padding-left: 0; }
.attendance-note { max-width: 640px; margin: 16px 0 0; color: var(--muted); font-family: var(--serif); font-size: 16px; line-height: 1.45; }
.attendance-note[hidden] { display: none; }
.rsvp-details { margin: 0; padding: 0; border: 0; min-width: 0; }
.rsvp-details[hidden] { display: none; }
/* The gate hides whatever a declined guest has no business answering. These
   selectors set `display`, so they out-rank the browser's [hidden] rule and
   have to opt back out of it themselves. */
.rsvp-day-group[hidden], .rsvp-event-card[hidden], .rsvp-event-divider[hidden], .guest-response[hidden], .guest-detail-card[hidden] { display: none; }
.rsvp-events { display: grid; gap: 28px; }
.rsvp-day-group { min-width: 0; }
.rsvp-day-header { display: flex; flex-wrap: wrap; align-items: start; justify-content: space-between; gap: 10px 16px; margin-bottom: 12px; }
.rsvp-day-heading { margin: 0; font-family: var(--display); font-size: clamp(26px, 3.8vw, 34px); font-weight: 400; line-height: 1.1; }
.rsvp-day-header .rsvp-day-for { margin-top: 8px; }
.rsvp-day-for[hidden] { display: none; }
.rsvp-day-card { padding: 18px 24px; border: 1px solid var(--line); border-radius: 3px; background: rgba(255, 255, 255, .78); }
.rsvp-event-card { margin: 0; padding: 0; border: 0; min-width: 0; }
.rsvp-event-divider { height: 1px; margin: 20px 0; border: 0; background: var(--line); }
.rsvp-event-title { margin: 0; font-family: var(--serif); font-size: 23px; font-weight: 500; line-height: 1.25; }
.rsvp-event-meta { margin: 5px 0 13px; color: var(--muted); font-size: 14px; line-height: 1.45; }
/* One attending guest: event copy and the labelled yes/no pair share a row,
   title and time on the left, radios hard right. The short Yes/No text is in
   the DOM either way; the matrix hides it because the column headers already
   say the same thing. */
.rsvp-event-card:not(.rsvp-event-card--solo) .choice-text { display: none; }
.rsvp-event-card--solo .rsvp-event-row { display: flex; align-items: center; justify-content: space-between; gap: 12px 24px; }
.rsvp-event-card--solo .rsvp-event-info { flex: 1 1 auto; min-width: 0; }
.rsvp-event-card--solo .rsvp-event-meta { margin-bottom: 0; }
.rsvp-event-card--solo .response-matrix { flex: 0 0 auto; }
.rsvp-event-card--solo .response-grid-header { display: none; }
.rsvp-event-card--solo .guest-response { display: block; min-height: 0; }
.rsvp-event-card--solo .guest-response > p { display: none; }
.rsvp-event-card--solo .choice-group { margin-top: 0; grid-template-columns: repeat(2, minmax(84px, 116px)); }
.rsvp-event-card--solo .choice-group label { row-gap: 4px; min-height: 58px; }
.rsvp-event-card--solo .choice-text { color: var(--muted); font-size: 12px; letter-spacing: .03em; }
.rsvp-event-card--solo .choice-group .sr-only { display: none; }
.response-matrix { border-top: 0; }
.response-grid-header { min-height: 32px; display: grid; grid-template-columns: minmax(150px, 1fr) repeat(2, minmax(64px, 96px)); align-items: center; column-gap: 8px; color: var(--muted); font-size: 9px; font-weight: 600; letter-spacing: .09em; text-align: center; text-transform: uppercase; }
.response-grid-header span:first-child { padding-left: 2px; text-align: left; }
.guest-response { min-height: 52px; display: grid; grid-template-columns: minmax(150px, 1fr) minmax(136px, 200px); align-items: center; column-gap: 8px; }
.guest-response > p { margin: 0; padding: 0 12px 0 32px; font-family: var(--serif); font-size: 18px; line-height: 1.25; }
.choice-group { display: grid; grid-template-columns: repeat(2, minmax(64px, 96px)); gap: 8px; }
.choice-group label { position: relative; display: grid; place-items: center; min-height: 44px; cursor: pointer; }
.choice-group input { position: absolute; width: 1px; min-height: 1px; opacity: 0; }
.choice-control { width: 28px; height: 28px; display: grid; place-items: center; border: 1px solid rgba(62,83,69,.28); border-radius: 50%; background: rgba(255,255,255,.72); transition: background-color 150ms ease, border-color 150ms ease, box-shadow 150ms ease; }
.choice-control::after { content: ""; width: 10px; height: 10px; border-radius: 50%; background: white; opacity: 0; transform: scale(.4); transition: opacity 150ms ease, transform 150ms ease; }
.choice-group label:hover .choice-control { border-color: var(--sage); }
.choice-group input:checked + .choice-control { border-color: var(--green); background: var(--green); box-shadow: 0 0 0 3px rgba(77,112,85,.12); }
.choice-group input:checked + .choice-control::after { opacity: 1; transform: scale(1); }
.choice-group input:focus-visible + .choice-control { outline: 3px solid color-mix(in srgb, var(--saffron), white 25%); outline-offset: 3px; }
/* Same marigold breath as the unanswered RSVP button, kept to a whisper so
   a pair of 28px radios can glow without washing the row. */
.choice-group.is-incomplete .choice-control {
  border-color: color-mix(in srgb, var(--saffron), rgba(62, 83, 69, 0.45) 58%);
  animation: choice-glow 3.4s ease-in-out infinite;
}
.choice-group.is-incomplete label:hover .choice-control { border-color: var(--saffron); }
@keyframes choice-glow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(221, 166, 66, 0), 0 0 0 0 rgba(204, 138, 39, 0); }
  50% { box-shadow: 0 0 0 2px rgba(221, 166, 66, 0.2), 0 0 10px 2px rgba(204, 138, 39, 0.28); }
}
.guest-needs { margin-top: 45px; padding-top: 35px; border-top: 1px solid var(--line); }
.guest-needs > h3 { margin: 0; font-family: var(--serif); font-size: 30px; font-weight: 500; }
.guest-needs > p { margin: 0 0 22px; color: var(--muted); }
.guest-detail-card { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 15px 0 0; padding: 20px; border: 1px solid var(--line); }
.guest-detail-card legend { padding: 0 7px; font-family: var(--serif); font-size: 20px; }
.guest-detail-card label { font-size: 11px; font-weight: 600; letter-spacing: .04em; }
.guest-detail-card label > span { color: var(--muted); font-weight: 400; }
.guest-detail-card textarea, .guest-detail-card select, .guest-detail-card input { display: block; margin-top: 7px; font-size: 14px; font-weight: 400; letter-spacing: 0; }
.dietary-custom-field { margin-top: 12px; }
.dietary-custom-field[hidden] { display: none; }
/* One party-level card, not a field inside each guest card. The answers are
   full sentences, so the choices stack as rows with the control on the left
   rather than sitting in the usual two-column yes/no grid. */
.rsvp-accommodation { margin-bottom: 32px; padding: 20px 24px; border: 1px solid var(--line); border-radius: 3px; background: rgba(255, 255, 255, .78); }
.rsvp-accommodation[hidden] { display: none; }
.accommodation-question { margin: 0; font-family: var(--serif); font-size: 19px; font-weight: 500; line-height: 1.4; }
.rsvp-accommodation .choice-group { margin-top: 14px; grid-template-columns: 1fr; gap: 10px; }
.rsvp-accommodation .choice-group label { grid-template-columns: 28px 1fr; align-items: center; justify-items: start; column-gap: 13px; }
.rsvp-accommodation .choice-text { font-family: var(--serif); font-size: 16px; line-height: 1.35; }
.accommodation-nights { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line); }
.accommodation-nights[hidden] { display: none; }
.accommodation-nights-question { margin: 0; font-family: var(--serif); font-size: 17px; font-weight: 500; line-height: 1.4; }
.choice-control--check { border-radius: 6px; }
.choice-control--check::after { width: 6px; height: 11px; margin-top: -2px; border-radius: 0; background: transparent; border: solid white; border-width: 0 2px 2px 0; transform: rotate(45deg) scale(.4); }
.choice-group input:checked + .choice-control--check::after { transform: rotate(45deg) scale(1); }
.dialog-actions { position: sticky; bottom: 0; display: flex; justify-content: flex-end; gap: 10px; margin: 35px -10px -20px; padding: 16px 10px 20px; background: linear-gradient(transparent, var(--paper) 20%); }

.toast { position: fixed; z-index: 100; left: 50%; bottom: 30px; transform: translate(-50%, 20px); padding: 13px 20px; border-radius: 4px; background: var(--ink); color: white; box-shadow: var(--shadow); opacity: 0; pointer-events: none; transition: all 180ms ease; font-size: 13px; }
.toast.visible { transform: translate(-50%, 0); opacity: 1; }
noscript { position: fixed; z-index: 200; right: 10px; bottom: 10px; max-width: 400px; padding: 15px; background: #fff; color: #7a2630; box-shadow: var(--shadow); }

/* Hindi typography.
   Two things the shared sizes get wrong in Devanagari, beyond the optical
   scaling handled by the "Devanagari Matched" face at the top of this file.
   First, the small tracked labels: 9–13px is legible in Latin small caps but
   loses the matras and conjunct halves that carry the meaning here, so every
   label under ~14px steps up. Second, the wide letter-spacing those labels
   use — Devanagari joins its letters along the headline, and tracking pulls
   that headline apart, so it comes most of the way off. text-transform:
   uppercase is simply inert in Devanagari; nothing to undo.

   Only lines that actually hold Devanagari belong here. Venue addresses and
   clock times come from config as single Latin strings in every language, so
   sizing them up in Hindi would just inflate Latin text and flatten the
   hierarchy under the venue name. */
html[lang="hi"] {
  --invite-eyebrow: 14px;
  --invite-place: 14px;
}
html[lang="hi"] .header-nav a { font-size: 15px; letter-spacing: .04em; }
html[lang="hi"] .location-switcher button { font-size: 13px; letter-spacing: .02em; }
html[lang="hi"] .text-button { font-size: 15px; letter-spacing: .02em; }
html[lang="hi"] .eyebrow { letter-spacing: .07em; }
html[lang="hi"] .login-form > label { font-size: 14px; letter-spacing: .03em; }
html[lang="hi"] .button { font-size: 15px; letter-spacing: .02em; }
html[lang="hi"] .button.is-loading { font-size: 15px; }
html[lang="hi"] input, html[lang="hi"] select, html[lang="hi"] textarea { font-size: 17px; }
html[lang="hi"] .form-error { font-size: 15px; }
html[lang="hi"] .toast { font-size: 15px; }
html[lang="hi"] .celebration-dates strong { letter-spacing: .05em; }
html[lang="hi"] .single-event-detail { font-size: 14px; }
html[lang="hi"] .location-map-key small { font-size: 12px; }
html[lang="hi"] .event-detail { font-size: 14px; }
html[lang="hi"] .response-status { font-size: 12px; letter-spacing: .03em; }
html[lang="hi"] .detail-scope { font-size: 12px !important; letter-spacing: .06em; }
html[lang="hi"] .detail-card p { font-size: 15px; }
html[lang="hi"] .rsvp-fab > span { font-size: 14px; letter-spacing: .05em; }
html[lang="hi"] .rsvp-fab small { font-size: 12px; }
html[lang="hi"] .response-grid-header { font-size: 12px; letter-spacing: .03em; }
html[lang="hi"] .attendance-choices > .choice-group .choice-text { font-size: 14px; letter-spacing: 0; }
html[lang="hi"] .rsvp-event-card--solo .choice-text { font-size: 14px; letter-spacing: 0; }
html[lang="hi"] .guest-detail-card label { font-size: 13px; letter-spacing: .02em; }
html[lang="hi"] .guest-detail-card textarea, html[lang="hi"] .guest-detail-card select, html[lang="hi"] .guest-detail-card input { font-size: 15px; }

/* Not language-scoped: the language switcher spells Hindi in Devanagari on
   every page, so it needs the same treatment while sitting beside EN and ES. */
.language-switcher button[data-lang="hi"] { font-size: 15px; letter-spacing: 0; }

@media (max-width: 850px) {
  .login-card { padding-inline: max(26px, 10vw); }
  .photo-collage { grid-template-columns: 1fr 1fr; grid-template-rows: 280px 280px 280px 280px 280px; }
  .photo-1 { grid-row: 1; }
  .photo-2 { grid-row: 1; }
  .photo-3 { grid-column: auto; grid-row: 2; width: auto; justify-self: auto; }
  .photo-4 { grid-row: 2; }
  .photo-5 { grid-row: 3; }
  .photo-6 { grid-row: 3; }
  .photo-7 { grid-row: 4; }
  .photo-8 { grid-row: 4; }
  .photo-9 { grid-column: 1 / 3; grid-row: 5; width: 70%; justify-self: center; }
}

@media (max-width: 650px) {
  .utility-bar { grid-template-columns: auto 1fr; gap: 14px; padding: 13px 18px; }
  .menu-button { display: flex; grid-column: 2; grid-row: 1; justify-self: end; }
  .nav-cluster {
    grid-column: 1 / -1;
    grid-row: 2;
    justify-content: space-between;
    gap: 12px;
    min-width: 0;
  }
  .location-switcher { flex: none; }
  .header-nav { flex: 0 1 auto; justify-content: flex-end; gap: 16px; min-width: 0; }
  .location-switcher button { padding-inline: 9px; }
  .utility-bar:not(.menu-open) .header-actions { display: none; }
  .header-actions {
    position: absolute;
    top: calc(100% + 8px);
    right: 14px;
    flex-direction: column;
    align-items: flex-end;
    gap: 14px;
    padding: 16px 18px;
    border: 1px solid rgba(51, 66, 56, 0.16);
    border-radius: 16px;
    background: rgba(253, 252, 250, 0.96);
    backdrop-filter: blur(14px);
    box-shadow: 0 10px 30px rgba(62, 83, 69, 0.12);
  }
  .logout-button { font-size: 12px; }
  .login-card { padding-top: 135px; }
  .login-card h1 { font-size: clamp(52px, 20vw, 76px); }
  .code-row { grid-template-columns: 1fr; }
  .login-form .button { width: 100%; }
  /* The mobile utility bar wraps to two rows, so the card needs to clear
     ~155px rather than the desktop 130px. */
  .hero { min-height: max(760px, 100svh); padding: 155px 20px 70px; }
  /* A 350px-wide card has no room for the desktop strand down each side, so the
     garland thins with the card. The padding still has to keep its floors: this
     rule replaces the base one outright, and dropping them printed the greeting
     over the flowers on every phone. */
  .invitation-card {
    --garland-inset: 9px;
    --garland-reach: 22px;
    padding:
      max(clamp(34px, 9vw, 46px), calc(var(--garland-inset) + var(--garland-reach) + 6px))
      max(clamp(20px, 6vw, 30px), calc(var(--garland-inset) + var(--garland-reach-x) + 10px));
  }
  /* --card-h on a phone matches California's box — 7/10 of the card's
     width, plus the same 5% — so toggling location does not change the
     stationery's height. Names stay 10% of that. The marigold strand is
     a nine-slice and can sit in this shorter landscape; the rose painting
     cannot sit in the old near-square. */
  .hero-copy { --card-h: calc(100cqw * 7 / 10 * 1.05); }
  .celebration-dates { flex-direction: column; gap: 12px; }
  .page-content-section { padding-top: 140px; }
  .section { padding-inline: 19px; }
  .location-map-panel { margin-bottom: 34px; }
  .location-map-layout { grid-template-columns: 1fr; min-height: 0; }
  .location-map { min-height: 360px; scroll-margin-top: 120px; }
  .location-map-key { max-height: none; border-top: 1px solid var(--line); border-left: 0; }
  .event-list { margin-inline: -4px; }
  .day-group { padding: 20px 16px 4px; }
  .day-group + .day-group { margin-top: 22px; }
  .day-header { gap: 10px 14px; margin-bottom: 11px; }
  .day-heading { font-size: clamp(24px, 8vw, 30px); }
  .event-card { grid-template-columns: 96px minmax(0, 1fr); column-gap: 34px; padding: 17px 0; }
  .event-card::before { left: 112.5px; }
  .event-card::after { top: 21px; left: 107px; }
  .day-header + .event-card::before { top: 27px; }
  .event-card:last-child::before { bottom: calc(100% - 27px); }
  .event-time { padding-top: 4px; font-size: 12px; line-height: 1.35; white-space: normal; }
  .event-title { font-size: 22px; }
  .response-status { margin-top: 6px; }
  .detail-grid { grid-template-columns: 1fr; }
  .detail-card { min-height: auto; padding: 30px 27px; }
  /* Same staggered lockup as desktop, scaled to the phone. A snapped row
     hid the other frames behind a swipe, which is not a collage. */
  .photo-collage {
    grid-template-columns: 1.05fr .9fr 1.05fr;
    grid-template-rows: 18px minmax(160px, 46vw) 18px 18px minmax(160px, 46vw) 18px 18px minmax(160px, 46vw) 18px;
    gap: 10px;
    margin: auto;
    padding: 8px 0 22px;
  }
  .photo-1 { grid-row: 1 / 3; transform: rotate(-2deg); }
  .photo-2 { grid-row: 2 / 4; transform: translateY(-8px) rotate(1.4deg); }
  .photo-3 { grid-column: auto; grid-row: 1 / 3; width: auto; justify-self: auto; transform: rotate(2.2deg); }
  .photo-4 { grid-row: 4 / 6; transform: rotate(1.4deg); }
  .photo-5 { grid-row: 5 / 7; transform: translateY(-8px) rotate(-1.2deg); }
  .photo-6 { grid-row: 4 / 6; transform: rotate(-2deg); }
  .photo-7 { grid-row: 7 / 9; transform: rotate(-1.6deg); }
  .photo-8 { grid-row: 8 / 10; transform: translateY(-8px) rotate(1.3deg); }
  .photo-9 { grid-column: auto; grid-row: 7 / 9; width: auto; justify-self: auto; transform: rotate(2deg); }
  .rsvp-fab { right: 12px; bottom: 12px; min-width: 132px; }
  .dialog-shell { padding: 25px 16px; }
  .rsvp-dialog { width: calc(100vw - 14px); max-height: calc(100svh - 14px); }
  .rsvp-events { gap: 24px; }
  .rsvp-day-heading { margin-bottom: 0; font-size: clamp(25px, 7.6vw, 30px); }
  .rsvp-day-header { margin-bottom: 10px; }
  .rsvp-day-card { padding: 15px 13px; }
  .rsvp-event-divider { margin: 16px 0; }
  .response-grid-header { grid-template-columns: minmax(0, 1fr) repeat(2, 48px); column-gap: 2px; }
  .response-grid-header { font-size: 8px; letter-spacing: .05em; }
  .guest-response { grid-template-columns: minmax(0, 1fr) 98px; column-gap: 2px; }
  .guest-response > p { padding-left: 18px; font-size: 17px; }
  .choice-group { grid-template-columns: repeat(2, 48px); gap: 2px; }
  .rsvp-attendance { margin-bottom: 26px; padding: 16px 14px; }
  .attendance-question { font-size: clamp(20px, 6vw, 23px); }
  .attendance-choices > .choice-group { grid-template-columns: repeat(2, minmax(72px, 96px)); gap: 8px; }
  .rsvp-event-card--solo .rsvp-event-row { gap: 10px 12px; }
  .rsvp-event-title { font-size: 21px; }
  .rsvp-event-card--solo .choice-group { grid-template-columns: repeat(2, 56px); gap: 6px; }
  .rsvp-accommodation { margin-bottom: 26px; padding: 16px 14px; }
  .accommodation-question { font-size: clamp(17px, 5vw, 19px); }
  .accommodation-nights-question { font-size: clamp(16px, 4.6vw, 17px); }
  .rsvp-accommodation .choice-group { grid-template-columns: 1fr; gap: 10px; }
  .rsvp-accommodation .choice-text { font-size: 15px; }
  .guest-detail-card { grid-template-columns: 1fr; padding: 15px; }
  .dialog-actions .button { flex: 1; padding-inline: 10px; }
}

/* Narrow enough that the badge alongside the date would wrap it to three
   lines. It sits centered, one line below the heading instead. */
@media (max-width: 520px) {
  .day-header { flex-direction: column; align-items: stretch; gap: 12px; }
  .day-header .response-status { align-self: center; margin-top: 0; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { scroll-behavior: auto !important; transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
  .button.is-loading::before { animation: none !important; border-right-color: currentColor; opacity: 0.45; }
  .app-loading .app-loading-veil { animation: none !important; border-right-color: currentColor; opacity: 0.45; }
  [data-open-rsvp][data-needs-rsvp] { animation: none !important; box-shadow: var(--rsvp-glow-rest), 0 0 0 4px rgba(221, 166, 66, 0.26), 0 0 22px 6px rgba(204, 138, 39, 0.62); }
  .choice-group.is-incomplete .choice-control { animation: none !important; box-shadow: 0 0 0 2px rgba(221, 166, 66, 0.16), 0 0 8px 2px rgba(204, 138, 39, 0.22); }
}

@media print {
  .utility-bar, .rsvp-fab { display: none !important; }
  .hero { min-height: auto; padding: 60px 20px; }
  .invitation-card { box-shadow: none; }
  /* Real paper does not need a picture of paper printed on it, and the canvas
     would print as a heavy flat block of ink. The garland is the opposite case
     — it is the invitation's own art, and it prints as clean vector. */
  [data-paper-shader] > canvas { display: none !important; }
  .card-garland { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  .section { padding: 35px 20px; break-inside: avoid; }
  .event-card { break-inside: avoid; }
  /* Paper never scrolls, so the schedule prints at full strength. */
  .event-list { --list-fade: 0 !important; -webkit-mask-image: none !important; mask-image: none !important; }
}
