/* ==========================================================================
   Dewald & Inge — public site
   --------------------------------------------------------------------------
   Plain CSS, no build step. Reads tokens.css for every value.
   Organised as: faces → reset → primitives → sections → overlays.
   Mobile first; breakpoints only where the design needs more room.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Faces. Self-hosted latin subsets; unicode-range keeps latin-ext off the wire
   unless a name actually needs it.
   -------------------------------------------------------------------------- */
@font-face{font-family:'Great Vibes';font-style:normal;font-weight:400;font-display:swap;src:url('../fonts/great-vibes-400-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Great Vibes';font-style:normal;font-weight:400;font-display:swap;src:url('../fonts/great-vibes-400-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Cormorant Garamond';font-style:normal;font-weight:400;font-display:swap;src:url('../fonts/cormorant-garamond-400-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Cormorant Garamond';font-style:normal;font-weight:400;font-display:swap;src:url('../fonts/cormorant-garamond-400-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Cormorant Garamond';font-style:normal;font-weight:500;font-display:swap;src:url('../fonts/cormorant-garamond-500-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Cormorant Garamond';font-style:normal;font-weight:500;font-display:swap;src:url('../fonts/cormorant-garamond-500-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Cormorant Garamond';font-style:normal;font-weight:600;font-display:swap;src:url('../fonts/cormorant-garamond-600-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Cormorant Garamond';font-style:normal;font-weight:600;font-display:swap;src:url('../fonts/cormorant-garamond-600-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Hanken Grotesk';font-style:normal;font-weight:400;font-display:swap;src:url('../fonts/hanken-grotesk-400-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Hanken Grotesk';font-style:normal;font-weight:400;font-display:swap;src:url('../fonts/hanken-grotesk-400-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Hanken Grotesk';font-style:normal;font-weight:600;font-display:swap;src:url('../fonts/hanken-grotesk-600-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Hanken Grotesk';font-style:normal;font-weight:600;font-display:swap;src:url('../fonts/hanken-grotesk-600-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;src:url('../fonts/ibm-plex-mono-400-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:400;font-display:swap;src:url('../fonts/ibm-plex-mono-400-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;src:url('../fonts/ibm-plex-mono-500-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'IBM Plex Mono';font-style:normal;font-weight:500;font-display:swap;src:url('../fonts/ibm-plex-mono-500-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Caveat';font-style:normal;font-weight:400;font-display:swap;src:url('../fonts/caveat-400-latin.woff2') format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Caveat';font-style:normal;font-weight:400;font-display:swap;src:url('../fonts/caveat-400-latin-ext.woff2') format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF}


/* --------------------------------------------------------------------------
   Reset and document
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

/* The browser hides [hidden] with `display: none` from its own stylesheet,
   which ANY author `display` rule outranks. Without this, a component styled
   `display: flex` stays on screen while the markup insists it is hidden —
   which is how the "limit reached" notice ended up greeting people who had
   uploaded nothing. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--surface);
  color: var(--ink);
  font-family: var(--sans);
  font-weight: 400;
  font-size: var(--fs-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

/* Locked while the gate is closed, released when the doors open. */
body.is-gated { overflow: hidden; }

img, svg, video { max-width: 100%; height: auto; display: block; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }

a { color: var(--sage); text-decoration: none; }
a:hover { color: var(--ink); }
/* Body copy links stay underlined — colour alone is not enough to mark them. */
.prose a, p > a { text-decoration: underline; text-underline-offset: .18em; }

:focus-visible {
  outline: 2px solid var(--sage);
  outline-offset: 3px;
  border-radius: 2px;
}
/* Reversed-out focus ring where the background is sage or photography. */
.hero :focus-visible, .site-footer :focus-visible, .lightbox :focus-visible {
  outline-color: var(--on-dark);
}

h1, h2, h3, h4 { font-weight: 400; margin: 0; }

/* Cormorant is a light face by design — lovely at 48px, but under-inked at
   card-title sizes on a screen. The smaller headings take the 500 weight so
   they hold the same colour as the big ones. */
.story__title,
.venue__name,
.know__title,
.stay__name,
.registry__name,
.gb-head__name { font-weight: 500; }
p { margin: 0; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.skip-link {
  position: absolute; top: -100%; left: var(--sp-gutter); z-index: calc(var(--z-gate) + 1);
  background: var(--ink); color: var(--surface);
  padding: .8rem 1.2rem;
  font-family: var(--mono); font-size: var(--fs-util-l);
  letter-spacing: var(--track-tight); text-transform: uppercase;
}
.skip-link:focus { top: 1rem; color: var(--surface); }


/* --------------------------------------------------------------------------
   Layout primitives
   -------------------------------------------------------------------------- */
.section {
  max-width: var(--maxw);
  margin-inline: auto;
  padding: var(--sp-section) var(--sp-gutter);
}
.section--prose { max-width: var(--maxw-prose); }
.section--list  { max-width: var(--maxw-list); }

/* Alternating band. The paper fill plus hairline rules is what separates one
   run of sections from the next; there are no other dividers. */
.band {
  background: var(--paper);
  border-top: 1px solid var(--line-2);
  border-bottom: 1px solid var(--line-2);
}
.band > .section { padding-block: var(--sp-section); }

/* Section header: number + title on the left, aside on the right, sitting on
   a hairline. Every numbered section uses this, unchanged. */
.section-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  border-top: 1px solid var(--line);
  padding-top: 1rem;
  margin-bottom: clamp(2.4rem, 6vw, 4rem);
  font-family: var(--mono);
  font-size: var(--fs-util);
  letter-spacing: var(--track-util);
  text-transform: uppercase;
  color: var(--ink-2);
}
.section-head__aside { color: var(--sage); text-align: right; }
.section-head--tight { margin-bottom: clamp(2rem, 5vw, 3rem); }

.section-title {
  font-family: var(--serif);
  font-size: var(--fs-d2);
  line-height: 1.05;
  margin-bottom: clamp(2.5rem, 7vw, 4.5rem);
  text-wrap: balance;
}
.section-title--narrow { max-width: 14ch; }
.section-title--tight  { margin-bottom: .8rem; }

.section-intro {
  font-size: var(--fs-body-l);
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 52ch;
  margin-bottom: clamp(2.4rem, 6vw, 3.5rem);
}

/* Uppercase mono utility label — eyebrows, field labels, metadata. */
.util {
  font-family: var(--mono);
  font-size: var(--fs-util);
  letter-spacing: var(--track-util);
  text-transform: uppercase;
}
.util--lg    { font-size: var(--fs-util-l); }
.util--wide  { letter-spacing: var(--track-wide); }
.util--sage  { color: var(--sage); }
.util--muted { color: var(--ink-3); }

/* The ampersand is italic in the serif faces, where a true italic exists.
   Great Vibes has none, so inside a display name the browser would synthesise
   one by slanting the glyph — which on an already-slanted script reads as a
   mistake. Its own ampersand is decorative enough. */
.amp { font-style: italic; }
.hero__title .amp,
.gate__title .amp,
.site-footer__names .amp { font-style: normal; }


/* --------------------------------------------------------------------------
   Photo placeholders
   Used wherever the couple has not uploaded an image yet. Straight from the
   design: a diagonal stripe in one of the three support colours, cycling.
   -------------------------------------------------------------------------- */
.ph {
  background: repeating-linear-gradient(135deg, var(--sand) 0 14px, #e6ddcd 14px 28px);
  border: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
}
.ph--2 { background: repeating-linear-gradient(135deg, var(--clay) 0 14px, #dccbb2 14px 28px); }
.ph--3 { background: repeating-linear-gradient(135deg, var(--sage-2) 0 14px, #b4b8a4 14px 28px); }

.ph__label {
  font-family: var(--mono);
  font-size: var(--fs-util);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line);
  padding: .4rem .7rem;
}

/* Real images share the placeholder's box so swapping one for the other never
   changes the layout. */
.media { position: relative; overflow: hidden; border: 1px solid var(--line); background: var(--sand); }
.media > img { width: 100%; height: 100%; object-fit: cover; }
/* The low-quality placeholder sits underneath and is revealed by the real
   image decoding on top of it — no fade, so no work on the compositor. */
.media--lqip { background-size: cover; background-position: center; }


/* --------------------------------------------------------------------------
   Buttons, chips, fields
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: 1rem 2.2rem;
  border: 1px solid transparent;
  font-family: var(--mono);
  font-size: var(--fs-util-l);
  letter-spacing: .16em;
  text-transform: uppercase;
  line-height: 1;
  text-align: center;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.btn--primary { background: var(--sage); color: #fff; }
.btn--primary:hover { background: var(--ink); color: #fff; }

.btn--dark { background: var(--ink); color: var(--surface); }
.btn--dark:hover { background: var(--sage); color: #fff; }

.btn--outline { background: transparent; border-color: var(--ink); color: var(--ink); }
.btn--outline:hover { background: var(--ink); color: var(--surface); }

.btn--sm { padding: .6rem .9rem; font-size: var(--fs-util); letter-spacing: var(--track-tight); }
.btn--block { width: 100%; }

/* Quiet text button — "Edit response", "Not you? Switch guest". */
.btn-quiet {
  background: none; border: none; padding: 0;
  font-family: var(--mono); font-size: var(--fs-util);
  letter-spacing: var(--track-tight); text-transform: uppercase;
  color: var(--ink-3);
}
.btn-quiet:hover { color: var(--ink); }

/* Chip — the guest picker. */
.chip {
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: .6rem .85rem;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: transparent;
  color: var(--ink-2);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.chip:hover { border-color: var(--sage); color: var(--ink); }
.chip[aria-pressed="true"], .chip.is-selected {
  background: var(--sage); color: #fff; border-color: var(--sage);
}
.chip-set { display: flex; flex-wrap: wrap; gap: .6rem; }

/* The picker is a real radio group so it submits without JavaScript. The input
   is hidden but focusable, and its focus ring is drawn on the label. */
.chip-input { position: absolute; opacity: 0; width: 0; height: 0; }
.chip-input:checked + .chip { background: var(--sage); color: #fff; border-color: var(--sage); }
.chip-input:focus-visible + .chip { outline: 2px solid var(--sage); outline-offset: 3px; }
.chip-option { display: inline-flex; }

/* Segmented yes/no. */
.seg {
  flex: 1;
  padding: .9rem .8rem;
  border: 1px solid var(--line);
  border-radius: 2px;
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-sm);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.seg:hover { border-color: var(--sage); }
.seg[aria-pressed="true"], .seg.is-selected {
  background: var(--sage); color: #fff; border-color: var(--sage);
}
.seg-group { display: flex; gap: .7rem; }

.seg-input { position: absolute; opacity: 0; width: 0; height: 0; }
.seg-input:checked + .seg { background: var(--sage); color: #fff; border-color: var(--sage); }
.seg-input:focus-visible + .seg { outline: 2px solid var(--sage); outline-offset: 3px; }
.seg-option { display: flex; flex: 1; }
.seg-option .seg { width: 100%; text-align: center; }

/* Fields */
.field { display: block; }
.field + .field { margin-top: 1.8rem; }

.field__label {
  display: block;
  font-family: var(--mono);
  font-size: var(--fs-util);
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: .8rem;
}
.field__optional { color: var(--ink-3); }

.input, .textarea, .select {
  width: 100%;
  padding: .85rem 1rem;
  border: 1px solid var(--line);
  border-radius: 0;
  background: var(--surface);
  font-size: var(--fs-body);
  color: var(--ink);
  transition: border-color var(--dur-fast) var(--ease);
}
.input:hover, .textarea:hover { border-color: var(--sage-2); }
.textarea { min-height: 7rem; resize: vertical; line-height: 1.5; }

.input[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--error); }

/* Inline validation. Says what to fix, sits directly under its field. */
.field__error {
  display: block;
  margin-top: .5rem;
  font-family: var(--mono);
  font-size: var(--fs-util);
  letter-spacing: .04em;
  color: var(--error);
}

.field__hint {
  display: block;
  margin-top: .5rem;
  font-family: var(--mono);
  font-size: var(--fs-util);
  letter-spacing: .04em;
  color: var(--ink-3);
}

/* The honeypot. Off-screen rather than display:none, which some bots skip. */
.hp-field {
  position: absolute; left: -9999px; top: auto;
  width: 1px; height: 1px; overflow: hidden;
}

.notice {
  border: 1px solid var(--line);
  padding: .9rem 1.1rem;
  font-size: var(--fs-sm);
  color: var(--ink-2);
}
.notice--warn  { border-color: var(--clay); background: rgba(207, 187, 162, .2); }
.notice--error { border-color: var(--error); background: var(--error-bg); color: var(--error); }
.notice--ok    { border-color: var(--sage); background: rgba(124, 132, 95, .07); }


/* --------------------------------------------------------------------------
   Hero
   -------------------------------------------------------------------------- */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  /* The two groups are pushed to the top and foot of the frame rather than
     stacked in the middle, so the photograph is left visible between them. */
  justify-content: stretch;
  /* Bottom padding leaves the scroll cue its own air below the date line. */
  padding: clamp(2.4rem, 7vw, 4.5rem) var(--sp-gutter) clamp(6rem, 12vw, 8.5rem);
  color: var(--on-dark);
  overflow: hidden;
}

/* Sage weave stands in until a hero photo is uploaded, so the fold is never
   empty and never shifts when the image arrives. */
.hero__bg {
  position: absolute; inset: 0;
  background: repeating-linear-gradient(120deg, #6f7757 0 16px, var(--sage) 16px 32px);
  background-size: cover;
  background-position: center;
}
.hero__bg > img {
  width: 100%; height: 100%; object-fit: cover;
}

/* Scrim.
   The middle used to fall to .15 alpha, which was fine under a heavy serif but
   leaves a script's hairlines to fend for themselves — and the names sit right
   there. Held higher through the middle band, still light enough that the
   photograph reads as a photograph. */
.hero__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(180deg,
    rgba(30, 32, 24, .46) 0%,
    rgba(30, 32, 24, .34) 38%,
    rgba(30, 32, 24, .40) 62%,
    rgba(30, 32, 24, .68) 100%);
}

.hero__inner {
  position: relative;
  max-width: var(--maxw);
  margin-inline: auto;
  width: 100%;
  /* Fills the frame so space-between can hold the two groups apart. */
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 2rem;
  text-align: center;
}

.hero__foot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(1.4rem, 3vw, 2rem);
}

.hero__eyebrow {
  font-family: var(--mono);
  font-size: var(--fs-util-l);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  margin-bottom: clamp(.8rem, 2vw, 1.4rem);
  color: var(--on-dark-2);
}

.hero__title {
  font-family: var(--display);
  /* A soft, wide shadow rather than a hard one: it lifts the hairlines off a
     busy photograph without ever reading as a drop shadow. Only the hero needs
     this — the gate and footer sit on flat colour. */
  text-shadow: 0 2px 24px rgba(20, 22, 16, .45), 0 1px 3px rgba(20, 22, 16, .3);
  font-size: var(--fs-hero);
  /* Roomier than the serif needed. Great Vibes throws long ascenders and
     descenders, and at .92 the flourishes of one line collided with the next. */
  line-height: 1.02;
  /* Deliberately not negative. Tightening a connecting script pulls the
     letterforms apart at the joins, which is exactly what makes cheap script
     type look broken. */
  letter-spacing: 0;
  text-wrap: balance;
}

.hero__date {
  font-family: var(--mono);
  font-size: var(--fs-util-l);
  letter-spacing: .22em;
  text-transform: uppercase;
  color: rgba(246, 245, 241, .9);
}
.hero__sep { color: var(--on-dark-4); padding-inline: .8em; }

.hero__cue {
  position: absolute;
  bottom: 1.6rem; left: 0; right: 0;
  display: flex; flex-direction: column; align-items: center; gap: .5rem;
  color: var(--on-dark-3);
}
.hero__cue-line {
  width: 1px; height: 26px;
  background: var(--on-dark-3);
  animation: cue 1.8s ease-in-out infinite;
}
@keyframes cue {
  0%, 100% { transform: translateY(0);   opacity: .5; }
  50%      { transform: translateY(7px); opacity: 1;  }
}

/* Countdown. Fixed-width numerals and a reserved box so the first tick after
   hydration cannot shift the hero. */
/* Spacing now comes from .hero__foot's gap, so the old top margin that pushed
   it away from the names would only add a second, competing rhythm. */
.countdown {
  display: flex;
  justify-content: center;
  gap: clamp(1rem, 4vw, 2.6rem);
  flex-wrap: wrap;
  list-style: none;
  padding: 0;
  margin: 0;
}
.countdown__unit { display: flex; flex-direction: column; gap: .5rem; }
.countdown__value {
  font-family: var(--mono);
  font-weight: 500;
  font-size: clamp(2.4rem, 9vw, 4rem);
  line-height: 1;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
  /* Reserve the widest the field will ever be: three digits for days, two for
     the rest. Without this the hero reflows the moment JS starts ticking. */
  min-width: 2ch;
}
.countdown__unit--days .countdown__value { min-width: 3ch; }
.countdown__label {
  font-family: var(--mono);
  font-size: var(--fs-util);
  letter-spacing: var(--track-util);
  text-transform: uppercase;
  color: rgba(246, 245, 241, .72);
}

/* After the day itself, the counter is replaced rather than run negative. */
.countdown-past {
  font-family: var(--serif);
  font-size: var(--fs-d3);
  color: var(--on-dark);
  max-width: 24ch;
}


/* --------------------------------------------------------------------------
   Personalised greeting (arrived via /i/{token})
   -------------------------------------------------------------------------- */
.greeting {
  position: relative;
  border-bottom: 1px solid var(--line-2);
  background: var(--paper);
}
.greeting__inner {
  max-width: var(--maxw);
  margin-inline: auto;
  padding: clamp(1.6rem, 4vw, 2.4rem) var(--sp-gutter);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: .8rem 1.5rem;
}
.greeting__name { font-family: var(--serif); font-size: var(--fs-d3); }


/* --------------------------------------------------------------------------
   01 — Our Story
   -------------------------------------------------------------------------- */
.story { display: flex; flex-direction: column; gap: clamp(3rem, 8vw, 6rem); }

.story__row { display: grid; gap: 1.5rem; align-items: center; }
.story__media { order: 1; aspect-ratio: 4 / 3; }
.story__text  { order: 2; }

.story__period { margin-bottom: .8rem; }
.story__title { font-family: var(--serif); font-size: var(--fs-d3); margin-bottom: .7rem; }
.story__body { font-size: var(--fs-body-l); line-height: 1.6; color: var(--ink-2); max-width: 46ch; }
.story__body p + p { margin-top: .8em; }

@media (min-width: 800px) {
  .story__row { grid-template-columns: 1fr 1fr; gap: clamp(2rem, 5vw, 4rem); }
  /* Alternate which side the photo falls on, so the column does not read as a
     stack of identical rows. */
  .story__item:nth-child(even) .story__media { order: 2; }
  .story__item:nth-child(even) .story__text  { order: 1; }
}


/* --------------------------------------------------------------------------
   02 — Wedding details
   -------------------------------------------------------------------------- */
.venues {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(2rem, 5vw, 3rem);
  margin-bottom: clamp(2.5rem, 6vw, 3.5rem);
}
/* Two side by side, but a single venue — one place for the whole day — takes
   the full width rather than sitting in half a row with a gap beside it. */
@media (min-width: 720px) {
  .venues { grid-template-columns: 1fr 1fr; }
  .venues--single { grid-template-columns: 1fr; }
}

.venue {
  border: 1px solid var(--line);
  background: var(--surface);
  padding: clamp(1.5rem, 4vw, 2.4rem);
}
.venue__type { margin-bottom: 1rem; }
.venue__name { font-family: var(--serif); font-size: var(--fs-d3); margin-bottom: 1.2rem; }
.venue__address { font-size: var(--fs-body); line-height: 1.6; color: var(--ink-2); margin-bottom: 1.4rem; }
.venue__times {
  display: flex; flex-wrap: wrap; gap: 1.4rem;
  font-family: var(--mono); font-size: var(--fs-util-l);
  letter-spacing: var(--track-tight); text-transform: uppercase; color: var(--ink);
}
.venue__times .util--muted { color: var(--ink-3); }

.map {
  position: relative;
  /* width is load-bearing. With only aspect-ratio and min-height set, a narrow
     column cannot satisfy 16/8 at 220px tall, so the browser solves for the
     INLINE axis instead and the box becomes 440px wide — pushing the whole
     page sideways on a phone. Fixing the width means only the height flexes. */
  width: 100%;
  aspect-ratio: 16 / 8;
  min-height: 220px;
  border: 1px solid var(--line);
  overflow: hidden;
  background: #e7e4dc;
}
/* A photograph of the venue fills the plate edge to edge. */
.map--photo { background: var(--sand); }
.map__photo { position: absolute; inset: 0; border: 0; }
.map__photo img { width: 100%; height: 100%; object-fit: cover; }

/* Just enough shading at the foot to keep the button and the venue name
   readable over a bright sky. */
.map__veil {
  position: absolute; inset: auto 0 0 0; height: 45%;
  background: linear-gradient(180deg, rgba(30, 32, 24, 0) 0%, rgba(30, 32, 24, .55) 100%);
  pointer-events: none;
}

.map__name {
  position: absolute; bottom: 1rem; left: 1rem;
  font-family: var(--mono);
  font-size: var(--fs-util-l);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  color: var(--on-dark);
  text-shadow: 0 1px 6px rgba(0, 0, 0, .5);
}
/* Without a photo there is no veil, so the name sits on the drawn plate. */
.map:not(.map--photo) .map__name {
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line);
  padding: .35rem .6rem;
  text-shadow: none;
}

/* Shown until a Google Maps key is entered in admin. Not an error state — it
   is a plan of the site with a working link out to the real thing. */
.map__placeholder { position: absolute; inset: 0; }
.map__grid {
  position: absolute; inset: 0;
  background-image:
    linear-gradient(var(--line-2) 1px, transparent 1px),
    linear-gradient(90deg, var(--line-2) 1px, transparent 1px);
  background-size: 44px 44px;
}
.map__road   { position: absolute; top: 0; bottom: 0; left: 38%; width: 10px; background: var(--sand); transform: skewX(-14deg); }
.map__water  { position: absolute; left: 0; right: 0; top: 56%; height: 12px; background: var(--sage-2); opacity: .5; }
.map__pin {
  position: absolute; left: 38%; top: 56%; transform: translate(-50%, -50%);
  display: flex; flex-direction: column; align-items: center; gap: .5rem;
}
.map__dot {
  width: 16px; height: 16px; border-radius: 50%; background: var(--sage);
  box-shadow: 0 0 0 6px rgba(124, 132, 95, .25), 0 4px 10px rgba(0, 0, 0, .2);
}
.map__pin-label {
  font-family: var(--mono); font-size: var(--fs-util);
  letter-spacing: var(--track-tight); text-transform: uppercase;
  background: var(--surface); border: 1px solid var(--line);
  padding: .3rem .55rem; white-space: nowrap;
}
.map__link { position: absolute; bottom: 1rem; right: 1rem; }


/* --------------------------------------------------------------------------
   03 — Good to know
   Hairline grid: the gap is the border. One rule, any number of items.
   -------------------------------------------------------------------------- */
.know {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
}
@media (min-width: 640px) { .know { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 960px) { .know { grid-template-columns: repeat(3, 1fr); } }

.know__item { background: var(--surface); padding: clamp(1.4rem, 4vw, 2rem); }
.know__label { margin-bottom: .8rem; }
.know__title { font-family: var(--serif); font-size: 1.35rem; margin-bottom: .6rem; }
.know__body { font-size: var(--fs-body); line-height: 1.55; color: var(--ink-2); }


/* --------------------------------------------------------------------------
   04 — Our moments
   Scattered prints. The rotation is per-image and fixed in the database, so
   the arrangement is the same every visit.
   -------------------------------------------------------------------------- */
.gallery {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1.5rem, 4vw, 2.6rem);
  justify-content: center;
  align-items: flex-start;
  list-style: none;
  padding: 0;
}

.gallery__item {
  display: block;
  background: #fff;
  border: 1px solid var(--line);
  padding: 12px 12px 4px;
  width: clamp(150px, 42vw, 220px);
  box-shadow: 0 8px 20px rgba(43, 45, 36, .1);
  transform: rotate(var(--rot, 0deg));
  transition: transform var(--dur-fast) var(--ease);
}
/* Straighten and lift on interaction — the only hover flourish on the page. */
.gallery__item:hover,
.gallery__item:focus-visible { transform: rotate(0deg) scale(1.03); }

.gallery__frame { display: block; aspect-ratio: 1 / 1; }
.gallery__caption {
  display: block;
  font-family: var(--script);
  font-size: 1.5rem;
  color: var(--ink);
  padding: .7rem .2rem .3rem;
  text-align: center;
  line-height: 1;
}


/* --------------------------------------------------------------------------
   05 — The day
   -------------------------------------------------------------------------- */
.schedule { list-style: none; padding: 0; }
.schedule__row {
  display: grid;
  grid-template-columns: 88px 1fr;
  gap: clamp(1rem, 3vw, 2rem);
  padding: 1.1rem 0;
  border-top: 1px solid var(--line-2);
}
.schedule__time {
  font-family: var(--mono);
  font-weight: 500;
  font-size: var(--fs-util-l);
  letter-spacing: .1em;
  color: var(--sage);
  padding-top: .15rem;
}
.schedule__title { font-size: var(--fs-body-l); color: var(--ink); line-height: 1.4; }
.schedule__desc { font-size: var(--fs-sm); color: var(--ink-2); margin-top: .3rem; }


/* --------------------------------------------------------------------------
   06 — Overnight
   -------------------------------------------------------------------------- */
.stays { display: grid; grid-template-columns: 1fr; gap: clamp(1.5rem, 4vw, 2rem); }
@media (min-width: 640px) { .stays { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 960px) { .stays { grid-template-columns: repeat(3, 1fr); } }

.stay {
  border: 1px solid var(--line);
  background: var(--surface);
  display: flex;
  flex-direction: column;
}
.stay__media { aspect-ratio: 16 / 10; border: 0; border-bottom: 1px solid var(--line); }
.stay__body {
  padding: clamp(1.3rem, 3.5vw, 1.8rem);
  display: flex; flex-direction: column; gap: .7rem; flex: 1;
}
.stay__head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem; }
.stay__name { font-family: var(--serif); font-size: 1.4rem; }
.stay__dist { white-space: nowrap; }
.stay__desc { font-size: var(--fs-body); line-height: 1.55; color: var(--ink-2); flex: 1; }
.stay__foot { display: flex; justify-content: space-between; align-items: center; gap: 1rem; margin-top: .6rem; }
.stay__price { font-family: var(--mono); font-size: var(--fs-util-l); letter-spacing: .08em; color: var(--ink); }


/* --------------------------------------------------------------------------
   07 — Guest book
   -------------------------------------------------------------------------- */
.guestbook-panel {
  border: 1px solid var(--line);
  background: var(--paper);
  padding: clamp(1.5rem, 5vw, 2.6rem);
  min-height: 340px;
}

.gb-locked {
  display: flex; flex-direction: column; align-items: center;
  text-align: center; gap: 1.3rem;
  padding-block: clamp(1rem, 4vw, 2rem);
}
.gb-locked__mark {
  width: 46px; height: 46px;
  border: 2px solid var(--sage); border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--mono); color: var(--sage);
}
.gb-locked__form { width: 100%; max-width: 320px; display: flex; flex-direction: column; gap: .7rem; }
.gb-locked__form .input { text-align: center; letter-spacing: .1em; }

.gb-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 1rem; flex-wrap: wrap;
}
.gb-head__name { font-family: var(--serif); font-size: 1.5rem; }
.gb-count {
  font-family: var(--mono); font-weight: 500; font-size: var(--fs-util-l);
  letter-spacing: var(--track-tight); text-transform: uppercase; color: var(--sage);
}

.gb-stack { display: flex; flex-direction: column; gap: 1.4rem; }

.gb-empty {
  border: 1px dashed var(--line);
  padding: 2.4rem 1rem;
  text-align: center;
}
.gb-empty__lead { font-size: var(--fs-body); color: var(--ink-2); margin-bottom: .3rem; }

.gb-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: .7rem;
  list-style: none; padding: 0;
}
.gb-tile { position: relative; aspect-ratio: 1 / 1; border: 1px solid var(--line); overflow: hidden; }
.gb-tile > img { width: 100%; height: 100%; object-fit: cover; }
.gb-tile__delete {
  position: absolute; top: 5px; right: 5px;
  width: 26px; height: 26px;
  border: none; background: rgba(43, 45, 36, .82); color: #fff;
  font-size: 14px; line-height: 1;
}
.gb-tile__delete:hover { background: var(--error); }

/* Upload progress, drawn over the tile it belongs to. */
.gb-tile__progress {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 4px; background: rgba(43, 45, 36, .2);
}
.gb-tile__bar { height: 100%; width: 0%; background: var(--sage); transition: width .2s linear; }
.gb-tile.is-failed { border-color: var(--error); }
.gb-tile__retry {
  position: absolute; inset: auto 0 0 0;
  border: none; background: var(--error); color: #fff;
  font-family: var(--mono); font-size: .58rem;
  letter-spacing: var(--track-tight); text-transform: uppercase;
  padding: .35rem 0;
}

.gb-add {
  aspect-ratio: 1 / 1;
  border: 1px dashed var(--sage);
  background: rgba(124, 132, 95, .06);
  color: var(--sage);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .4rem;
  transition: background var(--dur-fast) var(--ease);
}
.gb-add:hover { background: rgba(124, 132, 95, .14); }
.gb-add__plus { font-size: 1.6rem; line-height: 1; }
.gb-add__label {
  font-family: var(--mono); font-size: .58rem;
  letter-spacing: var(--track-tight); text-transform: uppercase;
}
/* Drag target state for the whole panel. */
.guestbook-panel.is-dropping { outline: 2px dashed var(--sage); outline-offset: -6px; }

.gb-limit { display: flex; gap: .7rem; align-items: baseline; flex-wrap: wrap; }


/* --------------------------------------------------------------------------
   08 — Gifts
   -------------------------------------------------------------------------- */
.registry {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
}
.registry__row {
  background: var(--surface);
  padding: clamp(1.4rem, 4vw, 2rem);
  display: flex; justify-content: space-between; align-items: center;
  /* Wraps rather than overflows: at 320px "Honeymoon fund" and "Contribute →"
     cannot share a line, so the call to action drops beneath the name. */
  flex-wrap: wrap;
  gap: .5rem 1rem;
  color: var(--ink);
  transition: background var(--dur-fast) var(--ease);
}
.registry__row:hover { background: #f0eee7; color: var(--ink); }
.registry__name { display: block; font-family: var(--serif); font-size: 1.4rem; margin-bottom: .2rem; }
.registry__cta { white-space: nowrap; }
.registry__lead {
  font-family: var(--serif);
  font-size: var(--fs-d3);
  line-height: 1.35;
  max-width: 24ch;
  margin-bottom: 1.5rem;
}


/* --------------------------------------------------------------------------
   09 — RSVP
   -------------------------------------------------------------------------- */
.rsvp-form { display: flex; flex-direction: column; gap: 1.8rem; }
.rsvp-form .field + .field { margin-top: 0; }

.rsvp-song { display: grid; grid-template-columns: 1fr; gap: .7rem; }
@media (min-width: 560px) { .rsvp-song { grid-template-columns: 1fr 1fr; } }

/* One block per person. When a party has two people the blocks need a visible
   edge, otherwise the second set of dietary and song fields reads as more
   questions for the first person rather than the start of someone else's. */
.rsvp-person { display: flex; flex-direction: column; gap: 1.4rem; }
.rsvp-person + .rsvp-person {
  padding-top: 1.8rem;
  border-top: 1px solid var(--line-2);
}
.rsvp-person__extras { display: flex; flex-direction: column; gap: 1.4rem; }

/* Collapsed when the guest says they cannot come, or before a plus one is
   named. JS sets the attribute; the fields are simply visible without it. */
.rsvp-person__extras[data-collapsed] { display: none; }

.rsvp-actions { display: grid; grid-template-columns: 1fr; gap: .7rem; }
.btn--whatsapp { background: var(--sage); color: #fff; }
.btn--whatsapp:hover { background: #6b7350; color: #fff; }

.popia {
  font-family: var(--mono);
  font-size: var(--fs-util);
  line-height: 1.6;
  letter-spacing: .04em;
  color: var(--ink-3);
  max-width: 60ch;
}

.rsvp-done {
  border: 1px solid var(--sage);
  background: rgba(124, 132, 95, .07);
  padding: clamp(2rem, 6vw, 3rem);
  text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 1rem;
}
.rsvp-done__mark {
  width: 48px; height: 48px;
  border: 2px solid var(--sage); border-radius: 50%;
  color: var(--sage);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.4rem;
}
.rsvp-done__title { font-family: var(--serif); font-size: var(--fs-d2); }
.rsvp-done__body { font-size: var(--fs-body-l); color: var(--ink-2); max-width: 40ch; }

/* The WhatsApp hand-off, shown once, straight after answering. */
.rsvp-handoff {
  display: flex; flex-direction: column; align-items: center; gap: .8rem;
  margin-top: .6rem;
}
.rsvp-handoff__note {
  font-family: var(--mono);
  font-size: var(--fs-util);
  letter-spacing: .04em;
  color: var(--ink-3);
}

/* Type-to-filter over the chip picker. Hidden without JS, since without JS
   the full list is the only thing that works. */
.guest-filter { margin-bottom: .8rem; }
.guest-filter[hidden] { display: none; }
.chip[hidden] { display: none; }
.chip-set__empty {
  font-size: var(--fs-sm); color: var(--ink-3); margin-top: .4rem;
}


/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */
.site-footer {
  background: var(--sage);
  color: var(--on-dark);
  padding: clamp(3.5rem, 10vw, 6rem) var(--sp-gutter);
  text-align: center;
}
.site-footer__eyebrow {
  font-family: var(--mono); font-size: var(--fs-util);
  letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--on-dark-3); margin-bottom: 1.4rem;
}
.site-footer__names {
  font-family: var(--display);
  font-size: clamp(3.1rem, 12vw, 5.5rem);
  line-height: 1.05;
  letter-spacing: 0;
  margin-bottom: 1.6rem;
}
.site-footer__date {
  font-family: var(--mono); font-size: var(--fs-util-l);
  letter-spacing: .22em; text-transform: uppercase;
  color: rgba(246, 245, 241, .9);
}
.site-footer a { color: var(--on-dark); text-decoration: underline; }

/* A credit, not a signature. It sits under the footer on the page's own
   background, at the smallest size the type scale offers, and only reaches full
   contrast when someone goes looking for it. The link keeps an underline —
   discreet is not the same as undiscoverable, and colour alone would leave it
   invisible to anyone who cannot see the difference. */
.colophon {
  background: var(--surface);
  padding: 1.6rem var(--sp-gutter) 1.8rem;
  text-align: center;
  font-family: var(--mono);
  font-size: var(--fs-util);
  letter-spacing: .08em;
  color: var(--ink-3);
}
.colophon a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: .25em;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(138, 139, 126, .45);
}
.colophon a:hover { color: var(--ink); text-decoration-color: currentColor; }


/* --------------------------------------------------------------------------
   Lightbox
   -------------------------------------------------------------------------- */
.lightbox {
  position: fixed; inset: 0; z-index: var(--z-lightbox);
  background: rgba(30, 32, 24, .9);
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 1rem; padding: 1.5rem;
  border: 0;
}
.lightbox[hidden] { display: none; }
.lightbox__figure {
  width: min(560px, 88vw);
  margin: 0;
  border: 12px solid var(--surface);
  background: var(--surface);
  box-shadow: 0 30px 80px rgba(0, 0, 0, .5);
}
.lightbox__figure img { width: 100%; height: auto; display: block; }
.lightbox__caption { font-family: var(--script); font-size: 2rem; color: var(--on-dark); }
.lightbox__close, .lightbox__nav {
  position: fixed;
  width: 44px; height: 44px;
  border: 1px solid rgba(246, 245, 241, .4);
  background: transparent; color: var(--on-dark);
  font-size: 1.4rem; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.lightbox__close:hover, .lightbox__nav:hover { background: rgba(246, 245, 241, .12); }
.lightbox__close { top: 1.2rem; right: 1.2rem; }
.lightbox__nav--prev { left: 1.2rem;  top: 50%; transform: translateY(-50%); }
.lightbox__nav--next { right: 1.2rem; top: 50%; transform: translateY(-50%); }


/* --------------------------------------------------------------------------
   Gate + sliding door reveal
   Two panels part from the centre line. Transform only, so the whole reveal
   runs on the compositor and never touches layout.
   -------------------------------------------------------------------------- */
.gate {
  position: fixed; inset: 0; z-index: var(--z-gate);
  /* Gives the leaves somewhere to swing into. Without a perspective they only
     slide sideways, which reads as two rectangles leaving rather than a door
     being opened. */
  perspective: 1800px;
  perspective-origin: 50% 45%;
}
.gate[hidden], .gate.is-open { display: none; }

/* Each leaf is sage down to the sill, paper below it — so the closed gate
   reads as a painted door rather than a blank screen. */
.gate__panel {
  position: absolute; top: 0; bottom: 0;
  width: 50%;
  z-index: 1;
  background:
    linear-gradient(180deg,
      var(--sage) 0 var(--gate-sill, 56%),
      var(--surface) var(--gate-sill, 56%) 100%);
  transition:
    transform var(--dur-door) var(--ease-door),
    box-shadow var(--dur-door) var(--ease-door);
  transition-delay: .18s;
  will-change: transform;
  backface-visibility: hidden;
}

/* The rail where sage meets paper, and a hairline down the closed join. */
.gate__panel::before {
  content: '';
  position: absolute; left: 0; right: 0;
  top: var(--gate-sill, 56%);
  height: 1px;
  background: rgba(43, 45, 36, .18);
}

/* Hinged at the outer edge, so the inner edges are what swing towards you. */
.gate__panel--left {
  left: 0;
  transform-origin: left center;
  box-shadow: inset -1px 0 0 rgba(43, 45, 36, .22);
}
.gate__panel--right {
  right: 0;
  transform-origin: right center;
  box-shadow: inset 1px 0 0 rgba(255, 255, 255, .35);
}

/* A soft shadow either side of the join, so the two leaves look like separate
   pieces of timber meeting rather than one flat fill. */
.gate__panel::after {
  content: '';
  position: absolute; top: 0; bottom: 0; width: 26px;
  pointer-events: none;
}
.gate__panel--left::after  { right: 0; background: linear-gradient(90deg, transparent, rgba(43,45,36,.13)); }
.gate__panel--right::after { left: 0;  background: linear-gradient(270deg, transparent, rgba(43,45,36,.07)); }

/* Opening: swing out on the hinge and clear the frame. The rotation is what
   sells it; the translate is what gets them off screen. */
.gate.is-opening .gate__panel--left {
  transform: perspective(1800px) rotateY(38deg) translateX(-104%);
  box-shadow: 24px 0 60px rgba(20, 22, 16, .28);
}
.gate.is-opening .gate__panel--right {
  transform: perspective(1800px) rotateY(-38deg) translateX(104%);
  box-shadow: -24px 0 60px rgba(20, 22, 16, .28);
}

/* Transparent on purpose: it used to be an opaque fill, which would now hide
   the doors it is supposed to be sitting on. */
.gate__content {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: var(--sp-gutter);
  transition: opacity var(--dur-mid) var(--ease), transform var(--dur-mid) var(--ease);
}

/* The invitation card. Sits across the sage/paper rail so it reads as
   something laid on the doors rather than a hole cut in them. */
.gate__card {
  width: min(30rem, 100%);
  text-align: center;
  background: var(--paper);
  border: 1px solid var(--line);
  box-shadow: 0 24px 60px rgba(20, 22, 16, .16), 0 2px 6px rgba(20, 22, 16, .06);
  padding: clamp(2rem, 7vw, 3.4rem) clamp(1.4rem, 6vw, 3rem);
}

/* The card leaves with the doors, drifting back very slightly so the eye reads
   it as being carried away rather than simply switched off. */
.gate.is-opening .gate__content {
  opacity: 0;
  transform: scale(.97);
  pointer-events: none;
}

.gate__eyebrow {
  font-family: var(--mono); font-size: var(--fs-util-l);
  letter-spacing: .26em; text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: clamp(1.4rem, 4vw, 2.2rem);
}
.gate__title {
  font-family: var(--display);
  font-size: var(--fs-d1);
  line-height: 1.02;
  letter-spacing: 0;
  color: var(--ink);
}
.gate__rule { width: 64px; height: 1px; background: var(--line); margin: clamp(1.6rem, 4vw, 2.4rem) auto; }
.gate__date {
  font-family: var(--mono); font-size: var(--fs-util-l);
  letter-spacing: var(--track-util); text-transform: uppercase;
  color: var(--ink);
  margin-bottom: clamp(2.2rem, 6vw, 3.2rem);
}
.gate__sep { color: var(--ink-3); padding-inline: .8em; }
.gate__greeting { font-family: var(--serif); font-size: var(--fs-d3); color: var(--sage); margin-bottom: 1rem; }

/* No motion: the doors neither swing nor travel — the gate simply stops being
   there. The rotation is explicitly cancelled, or a reduced-motion visitor
   would still catch a frame of it. */
@media (prefers-reduced-motion: reduce) {
  .gate__panel,
  .gate__content { transition: none; }
  .gate.is-opening .gate__panel--left,
  .gate.is-opening .gate__panel--right { transform: none; box-shadow: none; }
}


/* --------------------------------------------------------------------------
   Error and empty pages
   -------------------------------------------------------------------------- */
.plain-page {
  min-height: 70svh;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center;
  padding: var(--sp-section) var(--sp-gutter);
  gap: 1.2rem;
}
.plain-page__title { font-family: var(--serif); font-size: var(--fs-d2); }
.plain-page__body { font-size: var(--fs-body-l); color: var(--ink-2); max-width: 44ch; }


/* --------------------------------------------------------------------------
   Print — a guest printing the page wants the details, not the theatre.
   -------------------------------------------------------------------------- */
@media print {
  .gate, .hero__cue, .lightbox, .guestbook-panel, .rsvp-actions { display: none !important; }
  .hero { min-height: auto; color: var(--ink); }
  .hero__bg, .hero__scrim { display: none; }
  .hero__eyebrow, .hero__date, .countdown__label { color: var(--ink-2); }
  .band { background: none; }
  body { background: #fff; }
}
