/* css/styles.css: the page IS the inside of the invitation. Base and the stage numbers, the paper
   (the card's cream, lit), the covers (the open olive panels at both edges of the screen), the
   printed wording, the sections, the gate (the closed invitation on its linen table, over
   everything until it is opened), the film, FRAME MODE, reduced motion.
   Colours come from tokens.css only. Motion initial states live under html.js so the page is whole
   without JS. The panels are photographs cut from the couple's render, the seal is drawn: a matte paper
   disc with the couple's monogram, the render's light (tools/seal-white.py); CSS only lights and moves them. */

/* ---------- the stage ----------
   The opening film was rendered from a known frame: landscape 1920x1080 in which the closed
   invitation is 560 wide and, at the end, the card's cream is 1000 wide between two 55 px olive
   margins with the open panels (555 each) beyond them. The <video> is object-fit: cover, so on any
   landscape screen that frame is scaled by --s; the live page draws the same geometry at the same
   scale, which is why the film's last frame and the page are the same picture.
   Portrait: 540x960, the closed invitation 340 wide, and at the end the camera is inside the card:
   cream edge to edge, no covers. Change a number here AND in js/main.js, re-render the frames
   (tools/frames.ps1), regenerate the takes. */
:root {
  --s: max(100vw / 1920, 100vh / 1080);
  --cream: calc(1000 * var(--s));
  --margin: calc(55 * var(--s));
  --flap: calc(555 * var(--s));
  --closed: calc(560 * var(--s));
  --wording: min(calc(.8 * var(--cream)), 820px, 68vh); /* its height is 1.06 x its width: it must fit the first screen */
  --push: 1.98;               /* the CSS fallback's push-in: 1000 / (.9 * 560); js (layoutPrint) sets the real one, 1/k0 */
}
@media (orientation: portrait) {
  :root {
    --s: max(100vw / 540, 100vh / 960);
    --cream: 100vw;
    --margin: 0px;
    --closed: calc(340 * var(--s));
    --wording: 560px;
    --push: 1.45;             /* a 390x844 phone; js sets the real one for the screen */
  }
}

/* ---------- base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: 72px; background: var(--paper); }
body {
  margin: 0;
  background: transparent;
  color: var(--ink);
  font-family: var(--serif);
  font-size: clamp(18px, 1.1vw + 13px, 21px);
  line-height: 1.6;
  font-weight: 450;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-variant-numeric: lining-nums;
  overflow-x: hidden;
}
img, svg, iframe { display: block; max-width: 100%; }
h1, h2, h3, p { margin: 0; }
a { color: inherit; }
::selection { background: var(--blush); color: var(--wine); }
:focus-visible { outline: 1px solid var(--olive); outline-offset: 4px; }
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.nowrap { white-space: nowrap; }

/* fine paper grain, one tile, reused on the paper, the table and the card */
.grain::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.72' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .3 0 0 0 0 .25 0 0 0 0 .18 0 0 0 .6 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  mix-blend-mode: multiply; opacity: .28;
}

/* ---------- the paper: the card's cream, lit once, under the whole page ---------- */
.light {
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(ellipse 80% 60% at 30% 18%, rgba(255, 255, 255, .55), rgba(255, 255, 255, 0) 62%),
    radial-gradient(ellipse 120% 100% at 50% 50%, rgba(0, 0, 0, 0) 52%, rgba(var(--shadow-rgb), .12) 100%),
    linear-gradient(180deg, var(--paper), var(--paper-edge));
}
.light.grain::after { opacity: .16; }

/* ---------- the covers: the open panels at both edges (landscape only) ---------- */
/* Each cover is the olive board's margin next to the card, then the panel's back (the photograph,
   mirrored) hinged on it. They are fixed: the guest reads down the inside of the invitation. */
.covers { position: fixed; inset: 0; z-index: 1; pointer-events: none; }
.cover {
  position: absolute; top: 0; bottom: 0; width: calc((100% - var(--cream)) / 2);
  background: var(--base); overflow: hidden;
}
.cover-l { left: 0; }
.cover-r { right: 0; }
.cover img {
  position: absolute; top: 50%; width: var(--flap); height: calc(var(--flap) / .3525); max-width: none;
  object-fit: cover; -webkit-user-drag: none; user-select: none;
}
.cover-l img { right: var(--margin); transform: translateY(-50%) scaleX(-1); }
.cover-r img { left: var(--margin); transform: translateY(-50%) scaleX(-1); }
/* the board's margin: the raised card throws a soft shadow onto it */
.cover::before {
  content: ""; position: absolute; top: 0; bottom: 0; width: var(--margin); z-index: 1;
  background: linear-gradient(180deg, rgba(255, 255, 255, .06), rgba(0, 0, 0, .06));
}
.cover-l::before { right: 0; box-shadow: inset calc(var(--margin) * -.45) 0 calc(var(--margin) * .6) calc(var(--margin) * -.2) rgba(12, 14, 6, .34), inset 1px 0 0 rgba(255, 255, 255, .08); }
.cover-r::before { left: 0;  box-shadow: inset calc(var(--margin) * .45) 0 calc(var(--margin) * .6) calc(var(--margin) * -.2) rgba(12, 14, 6, .34), inset -1px 0 0 rgba(255, 255, 255, .08); }
/* the crease where the panel is hinged */
.cover::after { content: ""; position: absolute; top: 0; bottom: 0; width: calc(var(--flap) * .28); z-index: 1; }
.cover-l::after { right: var(--margin); background: linear-gradient(270deg, rgba(10, 12, 6, .26), rgba(10, 12, 6, 0)); }
.cover-r::after { left: var(--margin);  background: linear-gradient(90deg,  rgba(10, 12, 6, .26), rgba(10, 12, 6, 0)); }
@media (orientation: portrait) { .covers { display: none; } }

/* everything the guest reads sits between the covers */
.site-header, .interior, .site-footer { padding-left: max(0px, calc((100% - var(--cream)) / 2)); padding-right: max(0px, calc((100% - var(--cream)) / 2)); }
.interior { container-type: inline-size; }

/* ---------- header ---------- */
.site-header {
  position: fixed; inset: 0 0 auto; z-index: 40;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  padding-top: max(10px, env(safe-area-inset-top)); padding-bottom: 10px;
  transform: translateY(-100%);
  transition: transform .5s var(--ease-out);
}
.site-header::before {
  content: ""; position: absolute; top: 0; bottom: 0; left: max(0px, calc((100% - var(--cream)) / 2)); right: max(0px, calc((100% - var(--cream)) / 2)); z-index: -1;
  background: rgba(245, 240, 230, .88);
  -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
  border-bottom: 1px solid rgba(var(--shadow-rgb), .12);
}
.site-header > * { margin-left: var(--gutter); }
.site-header > :last-child { margin-right: var(--gutter); }
html.no-js .site-header, .site-header.is-shown { transform: none; }
.brand { display: block; padding: 2px; }
.brand img { height: 42px; width: auto; }   /* the LJ monogram: line art, no shadow */
.header-nav { display: flex; gap: 28px; margin-left: auto; }
.header-nav a, .pill {
  font-size: max(12px, .66em); letter-spacing: var(--track-caps); text-transform: uppercase;
  text-decoration: none; font-weight: 600; color: var(--olive);
}
.header-nav a:hover { color: var(--wine); }
.pill {
  border: 1px solid var(--olive); padding: 9px 16px; border-radius: 999px;
  transition: background .25s, color .25s;
}
.pill:hover { background: var(--olive); color: var(--paper); }
@media (max-width: 640px) { .header-nav { display: none; } }

/* ---------- 1. the wording: the printed card, first screen inside ---------- */
.invite {
  position: relative;
  min-height: 100vh; min-height: 100svh;
  display: grid; place-items: center;
  /* the wording sits on the optical centre, above the geometric one: more paper below it than above */
  padding: clamp(64px, 7vh, 96px) var(--gutter) clamp(96px, 18vh, 220px);
  padding-bottom: clamp(96px, 18svh, 220px);
}
html.js:not(.gate-done) body { overflow: hidden; }

/* type scales with the wording's width, as print does (cqw), with floors for phones */
.wording {
  width: min(100%, var(--wording));
  container-type: inline-size;
  display: grid; justify-items: center;
  /* the gap is sized from the cream, not in cqw: on .wording itself a cq unit resolves against the nearest
     ANCESTOR container (main on the page, nothing inside the gate, where the clones live), and the two
     disagreed on laptop screens (15.7 vs 22 px at 1366x768), spreading the overlay's lines against the page's */
  gap: clamp(11px, calc(var(--cream) * .022), 22px);
  text-align: center;
  color: var(--ink);
}
/* two faces, as the couple asked (2026-10-17): Cormorant for the names, the venue and the closing; Libre Baskerville
   (wider, a larger x-height: set about 15 % smaller than Cormorant was) for the informational lines, tracked lightly.
   The spacing is grouped: tight inside a block, the grid gap between blocks, more air around the names. */
.card-parents {
  width: 100%;
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
  font-family: var(--serif-2); font-size: clamp(9px, 1.75cqw, 13px); letter-spacing: .09em; text-transform: uppercase;
  line-height: 1.85; font-weight: 400; color: var(--ink);
}
.card-honor {
  font-family: var(--serif-2); font-size: clamp(9.5px, 1.9cqw, 14px); letter-spacing: .1em; text-transform: uppercase;
  line-height: 1.8; font-weight: 400; margin-top: .3cqw;
}
.card-names {
  display: grid; justify-items: center; gap: 0;
  font-weight: 500; color: var(--olive);
  line-height: 1.05; margin: 1.6cqw 0 .6cqw;   /* the names get the most air */
}
.card-names .name { font-size: clamp(29px, 8.2cqw, 68px); letter-spacing: .05em; text-transform: uppercase; }
.card-names .and {
  font-style: italic; font-weight: 400; color: var(--olive-soft);
  font-size: clamp(22px, 6cqw, 50px);
  line-height: 1; margin: .1em 0 .14em;
}
.rule { width: clamp(28px, 7.5cqw, 56px); height: 1px; border: 0; margin: 0; background: var(--olive); opacity: .7; }
.card-when, .card-place { display: grid; justify-items: center; gap: .15em; }
.card-when, .card-place-kind, .card-city {
  font-family: var(--serif-2); text-transform: uppercase; letter-spacing: .1em;
  font-size: clamp(9.5px, 1.95cqw, 14.5px); font-weight: 400; line-height: 1.5;
}
.card-date { font-size: 1.6em; letter-spacing: .07em; line-height: 1.25; }   /* the date leads the small lines */
.card-venue {
  font-size: clamp(16px, 3.6cqw, 27px); line-height: 1.2; font-weight: 500; color: var(--olive);
  text-decoration: none; border-bottom: 1px solid var(--line); padding-bottom: 1px; margin: .05em 0 .12em;
  transition: color .2s, border-color .2s;
}
.card-venue:hover { color: var(--wine); border-color: currentColor; }
.card-closing { font-style: italic; font-size: clamp(11.5px, 2.4cqw, 18px); line-height: 1.5; font-weight: 450; color: var(--ink-soft); margin-top: .8cqw; }

/* the wording appears once the guest is inside, line by line */
html.js .wording > * { opacity: 0; transform: translateY(8px); }
html.js.card-in .wording > * { opacity: 1; transform: none; transition: opacity 1.1s var(--ease-out), transform 1.1s var(--ease-out); }
html.js.card-in .wording > :nth-child(1) { transition-delay: .05s; }
html.js.card-in .wording > :nth-child(2) { transition-delay: .2s; }
html.js.card-in .wording > :nth-child(3) { transition-delay: .4s; }
html.js.card-in .wording > :nth-child(4) { transition-delay: .6s; }
html.js.card-in .wording > :nth-child(5) { transition-delay: .7s; }
html.js.card-in .wording > :nth-child(6) { transition-delay: .8s; }
html.js.card-in .wording > :nth-child(7) { transition-delay: .9s; }
html.js.card-in .wording > :nth-child(8) { transition-delay: 1s; }
html.js.card-in .wording > :nth-child(9) { transition-delay: 1.1s; }

.scroll-cue {
  position: absolute; left: 50%; bottom: max(20px, env(safe-area-inset-bottom)); transform: translateX(-50%);
  display: grid; justify-items: center; gap: 2px;
  color: var(--ink-soft); text-decoration: none;
  font-size: max(11.5px, .62em); letter-spacing: var(--track-wide); text-transform: uppercase;
  opacity: 0; transition: opacity .8s var(--ease-out) .6s;
}
html.gate-done .scroll-cue, html.no-js .scroll-cue { opacity: 1; }
.scroll-cue svg { width: 22px; height: 22px; animation: cue 2.4s ease-in-out infinite; }
@keyframes cue { 0%, 100% { transform: translateY(0); opacity: .6; } 50% { transform: translateY(5px); opacity: 1; } }

/* PHONES (portrait, up to 640px): the card fills the screen better than on the floors above (Jose, 2026-10-19: too
   much paper above and below the wording; the card was 418px tall in a 714-844px screen and overflowed a 560px one).
   Each line takes its size from the wording's WIDTH (cqw, about 8 % over the floors), never from the screen's
   height: the film overlay and the closed card's print are this same block scaled onto a card about 1.5 x its
   width tall, so the wording must stay about 1.3 x its width tall or its first lines spill over the paper's edge
   mid-opening (seen on a phone, 2026-10-20). No line wraps (the parents' columns and the date are the tight ones).
   The paper above the wording equals the paper between it and the scroll cue: the bottom padding is the top one
   plus the cue's room (44px; a bigger room lifts the block off the card's centre and eats the print's top margin),
   in fixed px: nothing here may read the safe area or the dynamic viewport, or the block moves with the toolbar.
   The film overlay takes the same paddings: at the handoff it is the page's wording. */
@media (orientation: portrait) and (max-width: 640px) {
  .invite, .film-wording {
    --pad: clamp(32px, 6svh, 60px);
    padding-top: var(--pad);
    padding-bottom: calc(var(--pad) + 64px);   /* the cue's room (44px) + its 20px offset; never env(safe-area-inset-bottom) here: it changes as the toolbar collapses and would move the wording */
  }
  .wording { gap: clamp(11px, calc(var(--cream) * .03), 18px); }   /* from the cream, never cq on .wording itself */
  .card-parents { font-size: clamp(9px, 2.8cqw, 11px); letter-spacing: .08em; gap: 4px; }
  .card-honor { font-size: clamp(9.5px, 2.95cqw, 12.5px); }
  .card-names .name { font-size: clamp(29px, 9cqw, 44px); }
  .card-names .and { font-size: clamp(22px, 6.8cqw, 32px); }
  .rule { width: clamp(28px, 10cqw, 40px); }
  .card-when, .card-place-kind, .card-city { font-size: clamp(9.5px, 2.95cqw, 12.5px); }
  .card-venue { font-size: clamp(16px, 5cqw, 24px); }
  .card-closing { font-size: clamp(11.5px, 3.6cqw, 15.5px); }
}

/* ---------- shared section shape: printed on the same card ---------- */
section:not(.invite) { padding: var(--section-y) var(--gutter); position: relative; }
section:not(.invite)::before { /* the card's short rule opens every section */
  content: ""; display: block; width: 40px; height: 1px; margin: 0 auto clamp(40px, 7vh, 72px);
  background: var(--olive); opacity: .7;
}
.section-head { display: grid; justify-items: center; gap: 14px; text-align: center; margin-bottom: clamp(36px, 6vw, 56px); }
.section-head h2, .countdown h2 {
  font-weight: 400; color: var(--olive);
  font-size: clamp(38px, 6vw, 60px); line-height: 1.05; letter-spacing: .01em;
}
.section-sub, .clock-lead { font-size: .72em; letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--olive-soft); font-weight: 600; }

/* ---------- 2. countdown: a short band, not a screen ---------- */
section.countdown { text-align: center; display: grid; justify-items: center; gap: 22px; padding-top: calc(var(--section-y) * .5); padding-bottom: calc(var(--section-y) * .55); }
.countdown::before { margin-bottom: clamp(28px, 4vh, 44px); }
.clock { display: grid; grid-template-columns: repeat(4, auto); gap: clamp(18px, 5cqw, 56px); align-items: start; }
.clock-unit { display: grid; justify-items: center; gap: 6px; min-width: 2.2ch; }
.clock-n {
  font-size: clamp(46px, 10cqw, 92px); line-height: 1; font-weight: 400; color: var(--olive);
  font-variant-numeric: lining-nums tabular-nums;
}
.clock-l { font-size: max(11.5px, .62em); letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink-soft); font-weight: 600; }
.countdown-sub, .countdown-after { font-style: italic; color: var(--ink-soft); font-size: 1.05em; }
.countdown-after { font-size: clamp(28px, 5vw, 40px); color: var(--olive); }

/* ---------- 3. ubicación (the venue under the drawn map; no day or time: the card has them) ---------- */
.venue { max-width: 720px; margin: 0 auto; display: grid; gap: clamp(36px, 6vh, 56px); }
.venue-text { text-align: center; display: grid; justify-items: center; gap: 6px; }
.venue-mark { width: clamp(110px, 14vw, 150px); height: auto; margin-bottom: 6px; }
.venue-name { font-size: clamp(30px, 4.4vw, 40px); color: var(--olive); line-height: 1.2; }
.venue-city { color: var(--ink-soft); font-size: .92em; line-height: 1.5; }
.actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: 28px; }
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 48px; padding: 12px 26px;
  font: inherit; font-size: .7em; font-weight: 600; letter-spacing: var(--track-caps); text-transform: uppercase;
  text-decoration: none; cursor: pointer;
  color: var(--paper); background: var(--olive);
  border: 1px solid var(--olive); border-radius: var(--radius);
  box-shadow: 0 6px 14px -6px rgba(var(--shadow-rgb), .4);
  transition: background .25s, color .25s, border-color .25s, box-shadow .25s;
}
.btn svg { width: 17px; height: 17px; }
.btn:hover { background: var(--olive-soft); border-color: var(--olive-soft); }
.btn-ghost { background: transparent; color: var(--olive); box-shadow: none; }
.btn-ghost:hover { background: var(--olive); color: var(--paper); }
.calendar {
  margin-top: 22px; display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 8px;
  font-size: .82em; color: var(--ink-soft);
}
.calendar svg { width: 16px; height: 16px; }
.calendar a { color: var(--olive); text-underline-offset: 3px; text-decoration-thickness: 1px; }
.calendar a:hover { color: var(--wine); }

/* ---------- 4. details ---------- */
.detail-grid { max-width: 880px; margin: 0 auto; display: grid; grid-template-columns: 1fr; gap: clamp(44px, 7vh, 72px); }
@container (min-width: 720px) { .detail-grid { grid-template-columns: 1fr 1fr; gap: clamp(32px, 5cqw, 64px); } }
.detail {
  padding: 0 clamp(8px, 2cqw, 24px);
  text-align: center;
  display: flex; flex-direction: column; align-items: center; gap: 14px;   /* the button sits at the bottom of both cards */
}
.detail h3 { font-weight: 600; font-size: .72em; letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--olive-soft); }
.detail-answer { font-size: clamp(26px, 3.2vw, 34px); line-height: 1.15; color: var(--olive); margin-top: 4px; }
.detail > p:not(.detail-answer) { color: var(--ink-soft); max-width: 34ch; }   /* the card's own paragraph; the plates set their lines */
.detail-btn { margin-top: auto; white-space: nowrap; }
/* the plates: the couple's dress-code illustration in two rows (the paper shows through: the figures are keyed off
   their cream), and the painted gifts with the three lists by name; both sit between the copy and the button */
.plate { width: 100%; margin-top: 6px; display: grid; justify-items: center; }
.dress-plate { gap: 18px; margin-top: 10px; }
.dress-plate figure { margin: 0; width: 100%; display: grid; gap: 6px; justify-items: center; }
.dress-plate img { width: 100%; height: auto; display: block; margin-top: 2px; }
.dress-plate .plate-line { color: var(--ink-soft); font-size: .95em; line-height: 1.4; text-wrap: balance; }   /* the row's line, under its label; the card's width, no widow */
.dress-plate .plate-foot {
  font-style: italic; color: var(--ink-soft); font-size: .92em; line-height: 1.4; text-wrap: balance;
  display: flex; align-items: center; justify-content: center; gap: 12px; margin-top: 2px;
}
.dress-plate .plate-foot::before, .dress-plate .plate-foot::after { content: ""; flex: none; width: 26px; height: 1px; background: var(--line); }
.dress-plate figcaption {
  font-size: max(11px, .6em); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--olive-soft); font-weight: 600;
  display: flex; align-items: center; gap: 12px;
}
.dress-plate figcaption::before, .dress-plate figcaption::after { content: ""; width: 26px; height: 1px; background: var(--line); }
.gift-plate { gap: 14px; flex: 1 1 auto; align-content: center; }   /* on two columns the plate takes the card's free height and centres the drawing in it: no hole above the button */
.gift-plate img { width: min(100%, 400px); height: auto; display: block; }
.registry-names { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; justify-content: center; font-size: 1.02em; }
.registry-names li + li::before { content: "\00b7"; margin: 0 12px; color: var(--olive-soft); }
.registry-names a { color: var(--olive); text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; text-decoration-color: var(--line); }
.registry-names a:hover { color: var(--wine); text-decoration-color: currentColor; }

/* ---------- 5. rsvp ---------- */
/* the details are short: less paper under their buttons, and the RSVP follows a little sooner */
section.details { padding-bottom: calc(var(--section-y) * .35); }   /* section.x: section:not(.invite) outranks a lone class */
section.rsvp { padding-top: calc(var(--section-y) * .7); }
.rsvp-card { max-width: 560px; margin: 0 auto; }
.rsvp-card .section-head { margin-bottom: 40px; }
.rsvp-seal { width: 54px; height: auto; margin-bottom: 6px; }
.rsvp-lead { color: var(--ink-soft); font-style: italic; }
.rsvp-form { display: grid; gap: 26px; position: relative; }
.field { display: grid; gap: 8px; border: 0; padding: 0; margin: 0; min-width: 0; }
.field > span, .field legend {
  font-size: .66em; letter-spacing: var(--track-caps); text-transform: uppercase; font-weight: 600; color: var(--olive-soft);
  padding: 0;
}
.field em { text-transform: none; letter-spacing: .02em; font-weight: 400; }
.field input[type="text"], .field select, .field textarea {
  font: inherit; color: var(--ink); width: 100%;
  background: transparent;
  border: 0; border-bottom: 1px solid rgba(74, 82, 51, .35); border-radius: 0;
  padding: 8px 2px; min-height: 44px;
  transition: border-color .25s;
}
.field textarea { resize: vertical; min-height: 88px; }
.field select { appearance: none; -webkit-appearance: none; cursor: pointer;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 9l6 6 6-6' fill='none' stroke='%234a5233' stroke-width='1.3'/%3E%3C/svg%3E") right 4px center / 18px no-repeat;
}
.field input:focus, .field select:focus, .field textarea:focus { outline: none; border-bottom-color: var(--olive); }
.field input:focus-visible, .field select:focus-visible, .field textarea:focus-visible { box-shadow: 0 1px 0 0 var(--olive); }
.field [aria-invalid="true"] { border-bottom-color: var(--wine); }
.choice { gap: 10px; }
.choice legend { margin-bottom: 10px; }
.choice label { display: flex; align-items: center; gap: 12px; cursor: pointer; min-height: 40px; }
.choice input { appearance: none; -webkit-appearance: none; margin: 0; flex: none;
  width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--olive); display: grid; place-items: center; cursor: pointer; }
.choice input::after { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--olive); transform: scale(0); transition: transform .2s var(--ease-out); }
.choice input:checked::after { transform: scale(1); }
#guests-field[hidden] { display: none; }
.rsvp-form .btn-solid { justify-self: center; margin-top: 6px; min-width: 240px; }
.form-note { text-align: center; font-size: .8em; color: var(--ink-soft); font-style: italic; }
.form-error { text-align: center; color: var(--wine); font-size: .9em; }

/* ---------- footer ---------- */
.site-footer {
  text-align: center; padding-top: 48px; padding-bottom: max(64px, env(safe-area-inset-bottom));
  display: grid; justify-items: center; gap: 6px; color: var(--olive);
}
.site-footer::before { content: ""; display: block; width: 40px; height: 1px; margin: 0 auto 44px; background: var(--olive); opacity: .7; }
.footer-seal { width: 60px; height: auto; margin-bottom: 18px; }
.footer-names { font-size: clamp(26px, 3.4vw, 32px); }
.footer-date { letter-spacing: var(--track-wide); font-size: .8em; }
.footer-city { font-size: .72em; letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--ink-soft); margin-top: 6px; }

/* ---------- reveals ---------- */
html.js .reveal { opacity: 0; transform: translateY(18px); transition: opacity 1s var(--ease-out), transform 1s var(--ease-out); }
html.js .reveal.is-in { opacity: 1; transform: none; }

/* ---------- the gate: the closed invitation on its table, over everything ---------- */
.gate {
  position: fixed; inset: 0; z-index: 50;
  display: grid; place-items: center; overflow: hidden;
  background: var(--linen);
}
html.no-js .gate, html.gate-done .gate { display: none; }

/* the table: one lit linen surface (fixed on its own in 404.html; the gate's floor here) */
.table {
  position: fixed; inset: 0; z-index: -1;
  background-color: var(--linen);
  background-image:
    linear-gradient(112deg,
      rgba(255, 250, 242, 0) 0%, rgba(255, 250, 242, .42) 13%, rgba(255, 250, 242, 0) 29%,
      rgba(255, 250, 242, 0) 50%, rgba(255, 250, 242, .28) 62%, rgba(255, 250, 242, 0) 77%),
    radial-gradient(ellipse 70% 60% at 48% 38%, rgba(255, 250, 242, .32), transparent 70%),
    radial-gradient(ellipse 90% 80% at 100% 100%, rgba(var(--shadow-rgb), .22), transparent 70%),
    radial-gradient(ellipse 60% 50% at 0% 100%, rgba(var(--shadow-rgb), .12), transparent 70%);
}
.gate .table { position: absolute; z-index: 0; }

.scene { position: relative; z-index: 1; perspective: 1900px; perspective-origin: 50% 42%; }
.fold {
  /* the closed invitation is 0.705 wide for 1 tall, like the printed one, at the stage's size */
  --w: var(--closed);
  position: relative;
  width: var(--w); aspect-ratio: .705;
  transform-style: preserve-3d;
}
/* the olive board, and the cream card mounted on it (inset, as on the printed one) */
.base {
  position: absolute; inset: 0;
  background: linear-gradient(160deg, rgba(255, 255, 255, .10), rgba(0, 0, 0, .10)), var(--base);
  box-shadow:
    0 1px 2px rgba(var(--shadow-rgb), .18),
    0 14px 28px -12px rgba(var(--shadow-rgb), .34),
    0 48px 80px -32px rgba(var(--shadow-rgb), .48);
}
.card {
  position: absolute; inset: 3.5% 5%;
  background:
    radial-gradient(ellipse 90% 70% at 30% 20%, rgba(255, 255, 255, .45), rgba(255, 255, 255, 0) 60%),
    linear-gradient(180deg, var(--paper), var(--paper-edge));
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .5), 0 1px 2px rgba(var(--shadow-rgb), .22), 0 6px 14px -6px rgba(var(--shadow-rgb), .35);
}
/* the printed card: the page's wording, cloned by js (layoutPrint), laid out at the page's width and
   scaled to this card (--k0: the closed card's cream over the page's cream, .504 on the landscape
   stage), sitting where it sits on the page relative to the card's centre (--dy). The CSS opening shows
   it as the panels swing, and when the fold is pushed in (--push) it is the page's wording to the pixel. */
.card-print {
  position: absolute; left: 50%; top: calc(50% + var(--dy, 0px)); width: var(--wording);
  transform: translate(-50%, -50%) scale(var(--k0, .504));
  pointer-events: none;
}
html.js .card-print .wording > * { opacity: 1; transform: none; transition: none; }

/* the two olive panels, hinged on the board's outer edges */
.flap {
  position: absolute; top: 0; bottom: 0; width: 50%;
  transform-style: preserve-3d;
  transform: translateZ(1px) rotateY(0deg);
  will-change: transform;
}
.flap img { width: 100%; height: 100%; object-fit: cover; -webkit-user-drag: none; user-select: none; }
.flap-l { left: 0;  transform-origin: left center;  }
.flap-r { right: 0; transform-origin: right center; }
.flap::after { /* light on the panel as it turns */
  content: ""; position: absolute; inset: 0; pointer-events: none; opacity: 0;
}
.flap-l::after { background: linear-gradient(90deg, rgba(14, 16, 8, .05), rgba(14, 16, 8, .7)); }
.flap-r::after { background: linear-gradient(270deg, rgba(14, 16, 8, .05), rgba(14, 16, 8, .7)); }

.seal { /* where the photographed seal sits on the photographed panels: measured, not guessed */
  position: absolute; left: 49.8%; top: 45.6%; width: 26.1%;
  transform: translate(-50%, -50%) translateZ(3px);
  border: 0; padding: 0; margin: 0; background: none; cursor: pointer;
  transition: transform .55s var(--ease-out), opacity .45s var(--ease-out);
}
/* the shadow sits on the img, not the button: a filter on a child of a preserve-3d parent
   flattens that child to z = 0 in Chromium, and the panels (z = 1) would cover the seal */
.seal img {
  width: 100%; height: auto;
  filter: drop-shadow(3px 7px 7px rgba(32, 26, 14, .42));
  animation: seal-breathe 3.4s ease-in-out infinite;
}
.seal:hover img { animation-play-state: paused; }
.seal:focus-visible { outline: 1px solid var(--blush); outline-offset: 10px; border-radius: 50%; }
@keyframes seal-breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.025); } }

.gate-hint {
  position: absolute; left: 0; right: 0; bottom: max(26px, env(safe-area-inset-bottom)); z-index: 5;
  text-align: center; pointer-events: none;
  font-size: .68em; letter-spacing: var(--track-wide); text-transform: uppercase;
  color: var(--ink-soft);
  transition: opacity .4s var(--ease-out);
}

/* ---------- the film: going into the invitation, as a take ---------- */
/* While the film is available (html.film-ready) it covers the gate and the fold waits under it,
   invisible. On its last frame (html.film-handoff) the gate fades: the page under it IS that frame. */
.film { position: absolute; inset: 0; z-index: 3; display: none; background: var(--linen); }
.film video { width: 100%; height: 100%; object-fit: cover; display: block; }
.film-tap { position: absolute; inset: 0; z-index: 4; display: none; border: 0; padding: 0; margin: 0; background: transparent; cursor: pointer; }
.film-tap:focus-visible { outline: 1px solid var(--olive); outline-offset: -6px; }
/* THE WORDING DURING THE FILM: a clone of the page's wording (js: buildOverlay), laid out exactly as
   the page's first screen (.invite's paddings plus the covers' width, 100svh tall), drawn over the
   take and scaled frame by frame to the card measured in it (js: TRACK). At the last frame it is the
   page's wording, so the gate's fade changes nothing; the page's own wording is set visible under it. */
.film-wording {
  position: absolute; top: 0; left: 0; right: 0; height: 100vh; height: 100svh; z-index: 3;
  display: none; place-items: center; pointer-events: none; box-sizing: border-box;
  padding: clamp(64px, 7vh, 96px) calc(var(--gutter) + max(0px, (100% - var(--cream)) / 2)) clamp(96px, 18vh, 220px);
  padding-bottom: clamp(96px, 18svh, 220px);
  opacity: 0; transform-origin: 50% 49.4%; will-change: transform, opacity;   /* the card's centre in the take */
}
@media (orientation: portrait) { .film-wording { transform-origin: 50% 50.7%; } }
html.film-playing .film-wording { display: grid; }
html.js .film-wording .wording > * { opacity: 1; transform: none; transition: none; }
/* For a take that carries its own lettering (js: .lettered, set when TRACK says the take is not blank) the
   overlay brings its own paper: a feathered cream patch behind the lettering, fading in with it over the
   take's last fifth of a second. A video model's letters never land exactly on ours (a few px off at the
   last frame), so without the patch the last frames and the gate's fade show a double image; with it the
   take's letters are covered, not overlaid. Its tone follows the page's paper from top to bottom. Over a
   blank card (the takes since v7) the patch would be the only thing visible: a pale square on the take's cream
   for three seconds; the ink goes straight onto the take there. */
.film-wording .wording { position: relative; }
.film-wording.lettered .wording::before {
  content: ""; position: absolute; inset: -7% -9%; z-index: -1; border-radius: 24px;
  background: var(--paper);
  background: linear-gradient(180deg, color-mix(in srgb, var(--paper) 90%, var(--paper-edge)), color-mix(in srgb, var(--paper) 30%, var(--paper-edge)));
  filter: blur(14px);
}
html.js.film-handoff .wording > *, html.js.page-in .wording > * { opacity: 1; transform: none; transition: none; }   /* page-in: the CSS opening's handoff */
html.film-ready .film, html.film-ready .film-tap { display: block; }
html.film-ready .scene { visibility: hidden; }
html.film-playing .film-tap { pointer-events: none; }
html.film-playing .gate-hint { opacity: 0; }
html.film-handoff .gate { opacity: 0; transition: opacity .8s var(--ease-out); pointer-events: none; }

/* the CSS opening (no film): the seal lifts away, each panel swings back onto the table, then
   the camera goes in (html.gate-enter): the fold grows until its cream is the page's cream */
html.gate-opening .seal, html.gate-done .seal, html.no-js .seal {
  opacity: 0; pointer-events: none;
  transform: translate(-50%, -50%) translateZ(70px) scale(1.08);
}
html.gate-opening .seal img { animation: none; }
html.gate-opening .gate-hint, html.gate-done .gate-hint, html.no-js .gate-hint { opacity: 0; }
html.gate-opening .flap-l { animation: flap-l 1.9s var(--ease-gate) .3s forwards; }
html.gate-opening .flap-r { animation: flap-r 1.9s var(--ease-gate) .3s forwards; }
html.gate-opening .flap::after { animation: flap-shade 1.9s var(--ease-gate) .3s forwards; }
@keyframes flap-l { to { transform: translateZ(1px) rotateY(-172deg); } }
@keyframes flap-r { to { transform: translateZ(1px) rotateY(172deg); } }
@keyframes flap-shade { 0% { opacity: 0; } 40% { opacity: .55; } 100% { opacity: .12; } }
html.gate-enter .fold { transform: scale(var(--push)); transition: transform 1.6s cubic-bezier(.5, 0, .2, 1); }
html.gate-enter .gate { opacity: 0; transition: opacity .6s var(--ease-out) 1s; pointer-events: none; }

/* FRAME MODE (tooling): the first / last frame of the opening film, rendered headless at the
   stage's size (landscape 1920x1080, portrait 540x960). ?frame=closed: the gate, the fold centred,
   nothing else. ?frame=inside: the page with the covers and the wording not yet shown; with &text=1 the
   wording is printed (the takes since v4 show the lettering from the first frame to the last). */
html.frame, html.peek { scroll-behavior: auto; }
html.peek .reveal { opacity: 1 !important; transform: none !important; }
html.frame .site-header, html.frame .gate-hint, html.frame .scroll-cue { display: none; }
html.frame .seal img { animation: none; }
html.frame.stage-l .fold { --w: 560px; }
html.frame.stage-p .fold { --w: 340px; }
html.frame.frame-inside .gate { display: none; }
html.frame.frame-inside:not(.frame-text) .wording > * { opacity: 0 !important; }   /* &text=1: the wording printed */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; transition-delay: 0s !important; }
  html.gate-opening .flap { animation: none; opacity: 0; transition: opacity .4s !important; }
  html.gate-enter .fold { transform: none; }
  html.gate-enter .gate, html.film-handoff .gate { transition: opacity .4s !important; }
}

/* ---------- the pages are one card: a cross-fade between the invitation and /vestimenta/ ---------- */
@view-transition { navigation: auto; }
.site-header { view-transition-name: site-header; }
@media (prefers-reduced-motion: reduce) { @view-transition { navigation: none; } }

/* ---------- the gift registries: a dialog over the card (index.html #regalos) ---------- */
.registries { border: 0; padding: 0; background: transparent; width: min(92vw, 540px); max-width: none; color: var(--ink); }
.registries::backdrop { background: rgba(58, 58, 44, .45); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
.registries-inner {
  position: relative; background: var(--paper); padding: clamp(32px, 6vw, 48px) clamp(22px, 5vw, 40px) clamp(26px, 5vw, 36px);
  border-radius: var(--radius); box-shadow: 0 2px 4px rgba(var(--shadow-rgb), .2), 0 40px 90px -30px rgba(var(--shadow-rgb), .55);
  display: grid; justify-items: center; gap: 14px; text-align: center;
}
.registries h2 { font-weight: 400; color: var(--olive); font-size: clamp(30px, 5vw, 40px); line-height: 1.1; }
.registries-lead { color: var(--ink-soft); font-style: italic; max-width: 34ch; }
.registry-list { list-style: none; padding: 0; margin: 10px 0 0; display: grid; gap: 12px; width: 100%; }
.registry {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 64px; padding: 14px 20px;
  border: 1px solid rgba(var(--shadow-rgb), .2); border-radius: var(--radius); text-decoration: none; color: var(--ink);
  background: rgba(255, 255, 255, .38); transition: border-color .15s, background .15s;
}
.registry:hover { border-color: var(--olive); background: rgba(255, 255, 255, .65); }
.registry-logo { font-size: clamp(19px, 2.8vw, 23px); letter-spacing: .05em; color: var(--olive); font-weight: 500; }
.registry-logo img { height: 28px; width: auto; display: block; }
.registry-cta { flex: none; font-size: max(12px, .64em); letter-spacing: var(--track-caps); text-transform: uppercase; font-weight: 600; color: var(--olive-soft); }
.dialog-close { position: absolute; top: 8px; right: 10px; border: 0; background: transparent; font: inherit; font-size: 30px; line-height: 1; color: var(--olive-soft); cursor: pointer; padding: 6px 12px; }
.dialog-close:hover { color: var(--olive); }
.registries[open] { animation: dlg-in .35s var(--ease-out); }
@keyframes dlg-in { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }

/* ---------- /vestimenta/: the dress code, a second sheet of the same card ---------- */
.dress-hero { display: grid; place-items: center; text-align: center; padding: clamp(110px, 18vh, 180px) var(--gutter) clamp(36px, 6vh, 64px); }
.dress-hero-inner { display: grid; justify-items: center; gap: 16px; }
.dress-mark { width: clamp(92px, 13vw, 140px); height: auto; margin-bottom: 10px; }
.dress-date { font-size: max(12px, .74em); letter-spacing: var(--track-wide); text-transform: uppercase; color: var(--ink-soft); font-weight: 500; }
.dress-hero h1 { font-weight: 400; color: var(--olive); font-size: clamp(34px, 5.6vw, 58px); line-height: 1.08; letter-spacing: .01em; }
.dress-kind { font-size: clamp(22px, 3.4vw, 32px); font-style: italic; color: var(--olive-soft); }
.gallery { display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(10px, 2vw, 18px); max-width: 980px; margin: 0 auto; }
@container (min-width: 620px) { .gallery { grid-template-columns: repeat(3, 1fr); } }
.gallery figure {
  margin: 0; aspect-ratio: 3 / 4; overflow: hidden; border-radius: 6px; background: var(--paper-edge);
  box-shadow: 0 2px 4px rgba(var(--shadow-rgb), .14), 0 16px 30px -18px rgba(var(--shadow-rgb), .4);
}
.gallery img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 10%; display: block; }
.dress-note { font-style: italic; color: var(--ink-soft); text-align: center; max-width: 44ch; margin: clamp(24px, 4vh, 40px) auto 0; }
.dress-back { display: grid; place-items: center; padding: clamp(24px, 5vh, 56px) var(--gutter) clamp(72px, 10vh, 120px); }
