/* ═══════════ Lawren & Alex · Invitation ═══════════ */

/* Self-hosted subsets — no external font CDN (fonts.googleapis.com is unreachable
   from mainland China, which would drop the display and script faces to system
   serifs). All five families are SIL OFL 1.1; see assets/fonts/OFL.txt.
   Rebuild with scripts/build-fonts.sh — required whenever the Chinese copy changes,
   because the CJK face is subset to exactly the characters used. */

@font-face {
  font-family: "Fraunces";
  font-style: normal;
  font-weight: 300 360;
  font-display: swap;
  src: url("../assets/fonts/fraunces-var.woff2") format("woff2-variations"),
       url("../assets/fonts/fraunces-var.woff2") format("woff2");
}
@font-face {
  font-family: "Fraunces";
  font-style: italic;
  font-weight: 300 360;
  font-display: swap;
  src: url("../assets/fonts/fraunces-italic-var.woff2") format("woff2-variations"),
       url("../assets/fonts/fraunces-italic-var.woff2") format("woff2");
}
@font-face {
  font-family: "Newsreader";
  font-style: normal;
  font-weight: 300 500;
  font-display: swap;
  src: url("../assets/fonts/newsreader-var.woff2") format("woff2-variations"),
       url("../assets/fonts/newsreader-var.woff2") format("woff2");
}
@font-face {
  font-family: "Newsreader";
  font-style: italic;
  font-weight: 300 500;
  font-display: swap;
  src: url("../assets/fonts/newsreader-italic-var.woff2") format("woff2-variations"),
       url("../assets/fonts/newsreader-italic-var.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/plexmono-400.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("../assets/fonts/plexmono-500.woff2") format("woff2");
}
@font-face {
  font-family: "Mrs Saint Delafield";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/delafield-400.woff2") format("woff2");
}
@font-face {
  font-family: "Noto Sans SC";
  font-style: normal;
  font-weight: 300 400;
  font-display: swap;
  src: url("../assets/fonts/notosanssc-300.woff2?v=10") format("woff2");
  /* CJK plus fullwidth punctuation — Latin never triggers this download */
  unicode-range: U+3000-303F, U+4E00-9FFF, U+FF00-FFEF, U+3007;
}

:root {
  --paper: #f4efe4;
  --paper-deep: #e9e1d0;
  --card: #fbf8f0;
  --ink: #33291f;
  --ink-soft: rgba(51, 41, 31, 0.62);
  --accent: #6e1f2e;
  --accent-wash: rgba(110, 31, 46, 0.12);
  --edge: rgba(51, 41, 31, 0.22);
  --ease-settle: cubic-bezier(0.22, 1, 0.36, 1);
}

* { margin: 0; padding: 0; box-sizing: border-box; }
[hidden] { display: none !important; }
.og-seed { display: none; }
html { scroll-behavior: smooth; }

body {
  background: var(--paper);
  color: var(--ink);
  font-family: "Newsreader", "Noto Sans SC", serif;
  font-optical-sizing: auto;
  font-size: 1.06rem;
  line-height: 1.65;
  overflow-x: hidden;
}
body.is-locked { overflow: hidden; height: 100vh; }

/* paper grain over everything */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  background-image: url("../assets/art/paper-texture.webp");
  background-size: 720px;
  mix-blend-mode: multiply;
  opacity: 0.38;
  pointer-events: none;
  z-index: 40;
}

.mono {
  font-family: "IBM Plex Mono", "Noto Sans SC", monospace;
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  color: var(--ink-soft);
}
.amp { font-style: italic; font-weight: 300; }
.tbc { color: var(--ink-soft); font-style: italic; }

/* ═══════════ shared section bits ═══════════ */

main { position: relative; z-index: 1; }
.section { padding: clamp(4.5rem, 10vw, 8rem) clamp(1.2rem, 6vw, 5rem); max-width: 1060px; margin: 0 auto; }
.section-head { text-align: center; margin-bottom: clamp(2rem, 5vw, 3.4rem); }
.section-no { margin-bottom: 0.9rem; }
/* every bilingual pair uses the same idiom: English first, Chinese under it in
   the subset sans. .mono and the form labels now carry the CJK face too, so the
   whole site renders Chinese in one family. */
.cn { font-family: "Noto Sans SC", sans-serif; letter-spacing: 0.14em; font-style: normal; }
.h2-cn,
.cap-cn,
.tl-name-cn,
.ask-cn-verse,
.cover-date-cn,
.form-error-cn,
.btn-cn,
.signoff-date-cn { font-family: "Noto Sans SC", sans-serif; font-style: normal; letter-spacing: 0.14em; }

.h2-cn, .cover-date-cn { letter-spacing: 0.06em; }
.h2-cn { margin-top: 0.7rem; font-size: clamp(0.9rem, 2vw, 1rem); color: var(--ink-soft); }
h2 { font-family: "Fraunces", serif; font-weight: 350; font-size: clamp(1.7rem, 4.2vw, 2.6rem); line-height: 1.2; }

.reveal { opacity: 0; transform: translateY(26px); transition: opacity 0.9s ease, transform 0.9s var(--ease-settle); }
.reveal.in { opacity: 1; transform: none; }

/* photo prints — simple frame, no caption */
.print {
  background: var(--card);
  padding: clamp(10px, 1.5vw, 16px);
  box-shadow: 0 3px 8px rgba(51, 41, 31, 0.12), 0 18px 40px rgba(51, 41, 31, 0.16);
  rotate: var(--tilt, 0deg);
  position: relative;
}
/* scoped to the photo itself — a .sprite sibling must keep its own width */
.print picture img { display: block; width: 100%; height: auto; }
.print.reveal { transform: translateY(30px) rotate(1.6deg); }
.print.reveal.in { transform: none; }

/* assets are true-alpha PNGs — no blend/mask compensation needed */
.etching { opacity: 0.92; }

/* ── animated sprites (transparent animated WebP) ──────────────────────────
   Kept small and set to straddle the edge of whatever they belong to, so they
   read as part of the composition rather than stickers dropped on top. They
   carry their own animation, so no CSS motion is added; they only fade in with
   their section. The page-wide paper grain sits above them and ties them in. */
.sprite {
  position: absolute;
  opacity: 0.95;
  pointer-events: none;
  z-index: 3;
}
/* camera girl photographing the couple — over the print's top-left corner */
.sprite-camera {
  width: clamp(96px, 13vw, 150px);
  top: 0;
  left: 0;
  /* far enough onto the print that her hands and that back foot rest on the
     paper — hanging in open space is what made her read as pasted on */
  translate: 1% -50%;
}
/* paper plane carrying the reply away — off the card's bottom-right corner */
.sprite-plane {
  width: clamp(104px, 14vw, 158px);
  right: 0;
  bottom: 0;
  translate: 34% 30%;
}
/* riding off at the very end of the page — centred, last thing you see */
.sprite-ride {
  position: static;
  display: block;
  width: clamp(112px, 17vw, 172px);
  margin: clamp(2.4rem, 6vw, 4rem) auto 0;
}

/* ═══════════ 01 · Cover ═══════════ */

.cover {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  position: relative;
  padding: clamp(4rem, 8vw, 6rem) clamp(1.2rem, 6vw, 5rem) 5rem;
  overflow: hidden;
}
.cover-inner {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: clamp(2rem, 6vw, 5rem);
  align-items: center;
  max-width: 1120px;
  margin: 0 auto;
  width: 100%;
}
.cover-names { font-family: "Fraunces", serif; font-weight: 300; font-size: clamp(3rem, 7.5vw, 6rem); line-height: 1.02; letter-spacing: -0.01em; }
.cover-names-cn { font-family: "Noto Sans SC", sans-serif; font-size: clamp(1.15rem, 2.4vw, 1.5rem); letter-spacing: 0.34em; margin-top: 1.1rem; color: var(--ink-soft); }
.amp-cn { color: var(--accent); }
.cover-occasion {
  margin-top: 1.8rem;
  font-family: "Noto Sans SC", sans-serif;
  font-size: clamp(0.95rem, 2vw, 1.1rem);
  letter-spacing: 0.5em;
  color: var(--accent);
}
.cover-occasion .occ-en {
  display: block;
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.66rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-top: 0.45rem;
}
/* one utility line instead of three type sizes: the standalone italic
   "13 November 2026" (the cover's only Newsreader) folded into this mono row,
   which takes the cover from four families down to three. */
.cover-date { margin-top: 1.9rem; font-size: clamp(0.66rem, 1.5vw, 0.74rem); letter-spacing: 0.15em; color: var(--accent); }
.cover-date-cn { display: block; margin-top: 0.8rem; font-size: 0.86rem; line-height: 1.9; color: var(--ink-soft); }
.cover-print { --tilt: -1.4deg; max-width: 470px; justify-self: end; }

.scroll-cue {
  position: absolute;
  bottom: max(1.5rem, env(safe-area-inset-bottom));
  right: 1.8rem;
  width: 18px;
  color: var(--ink-soft);
  animation: cue-drift 2.6s ease-in-out infinite;
}
.scroll-cue svg { display: block; width: 100%; }
@keyframes cue-drift { 0%, 100% { transform: translateY(0); opacity: 0.7; } 50% { transform: translateY(6px); opacity: 1; } }

/* petals */
.petals { position: absolute; inset: 0; pointer-events: none; overflow: hidden; }
.petal {
  position: absolute;
  top: -6%;
  width: var(--size, 20px);
  opacity: 0;
  animation: petal-fall var(--dur, 17s) linear var(--delay, 0s) infinite;
}
@keyframes petal-fall {
  0% { transform: translate3d(0, 0, 0) rotate(var(--rot0, 0deg)); opacity: 0; }
  8% { opacity: var(--peak, 0.55); }
  85% { opacity: calc(var(--peak, 0.55) * 0.8); }
  100% { transform: translate3d(var(--sway, 40px), 108vh, 0) rotate(var(--rot1, 300deg)); opacity: 0; }
}

/* ═══════════ 02 · The Ask ═══════════ */

.ask { padding: clamp(5rem, 11vw, 9rem) clamp(1.2rem, 6vw, 5rem); max-width: 1060px; margin: 0 auto; }
.section-flourish { text-align: center; color: var(--accent); margin-bottom: clamp(2.4rem, 6vw, 4rem); }
.section-flourish svg { width: min(200px, 46vw); }
.draw path { stroke-dasharray: 1; stroke-dashoffset: 1; }
.reveal.in .draw path { stroke-dashoffset: 0; transition: stroke-dashoffset 1.7s ease 0.25s; }

.ask-grid { display: grid; justify-items: center; }
.ask-copy { max-width: 46rem; width: 100%; }
/* the invitation sits on a full sheet of letter paper: a wide, quiet field with the
   words floating in the middle of it. The sheet gets its own grain (the page-wide
   one is above everything at z-index 40 and cannot give the sheet its tooth), plus
   one corner spray in the same dimensional style as the RSVP card. No border trim. */
.ask-sheet {
  position: relative;
  isolation: isolate;
  background: var(--card);
  padding: clamp(3rem, 8vw, 6rem) clamp(1.6rem, 6vw, 5rem);
  box-shadow: 0 2px 5px rgba(51, 41, 31, 0.07), 0 22px 52px rgba(51, 41, 31, 0.13);
  text-align: center;
}
.ask-sheet::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("../assets/art/paper-texture.webp");
  background-size: 720px;
  mix-blend-mode: multiply;
  opacity: 0.34;
  pointer-events: none;
  z-index: -1;
}
.ask-corner {
  position: absolute;
  left: -6px;
  bottom: -6px;
  width: clamp(84px, 12vw, 124px);
  pointer-events: none;
  opacity: 0.8;
}
.ask-line { font-family: "Fraunces", serif; font-weight: 340; font-size: clamp(1.4rem, 3.1vw, 2.05rem); line-height: 1.42; margin-bottom: 2rem; }
.ask-cn { margin-top: clamp(2.6rem, 6vw, 4rem); }
.ask-cn-formal { font-family: "Noto Sans SC", sans-serif; line-height: 2.15; letter-spacing: 0.12em; font-size: 0.98rem; }
/* the Bai Juyi line sits apart from the formal block: its own line, lighter and
   smaller than the formal block above it.
   Centring comes from .ask-sheet { text-align: center }. */
.ask-cn-verse { margin-top: 1.7rem; font-size: 0.95rem; line-height: 2; color: var(--ink-soft); }
/* venue map: watercolour aerial the couple supplied (2026-08-27 replacement).
   Labels are baked into the artwork — spellings and the surveyed layout were
   checked by hand against map-data/ground-truth.md. The feathered alpha edge is
   ours (scripts note in LESSONS); drop-shadow below follows that silhouette, so
   the asset must keep its alpha or the shadow turns into a hard rectangle. */
.venue-map-fig { max-width: min(620px, 96vw); margin: 0 auto clamp(2.4rem, 5vw, 3.4rem); }
.vmap-note { margin-top: 0.9rem; font-size: 0.86rem; line-height: 1.75; color: var(--ink-soft); text-align: center; }
.vmap-art {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 6px 16px rgba(51, 41, 31, 0.14));
}

/* ═══════════ 03 · Scenes ═══════════ */

/* the rail runs the full width of the section — the side prints need the room,
   so the inline padding lives on the copy instead */
.scenes { max-width: 1180px; margin: 0 auto; padding: clamp(3rem, 8vw, 6rem) 0; }
.scenes > :not(.cf) { padding-inline: clamp(1.2rem, 5vw, 4rem); }

/* ── pick a photo · cover flow ────────────────────────────────────────────
   A light table you slide sideways: the centred print faces you square-on,
   its neighbours turn away and sink into the paper. Native scroll-snap does
   the sliding — so touch momentum, trackpads and keyboard scrolling all come
   free — and JS only maps scroll position onto a per-card transform. */
.pick-head { margin-bottom: clamp(1.4rem, 3.5vw, 2.2rem); }
.pick-sub { max-width: 40rem; margin: 1rem auto 0; color: var(--ink-soft); }

.cf {
  --cf-card: min(500px, 82vw);
  --cf-step: calc(var(--cf-card) * 0.54);
  position: relative;
}

.cf-track {
  position: relative; /* offsetParent for the scroll maths in main.js */
  display: flex;
  align-items: center;
  /* half the rail either side, so the first and last photo can still come to
     rest in the middle */
  padding: clamp(2rem, 4.5vw, 3rem) calc(50% - var(--cf-step) / 2);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.cf-track::-webkit-scrollbar { display: none; }
/* snapping has to stand down while a mouse drag is driving scrollLeft by hand */
.cf-track.is-dragging { scroll-snap-type: none; cursor: grabbing; }

/* min-width:0 or the cell refuses to be narrower than the photo it holds — the
   overlap the fan is made of depends on the cell being the smaller box */
.cf-cell { flex: 0 0 var(--cf-step); min-width: 0; display: flex; justify-content: center; scroll-snap-align: center; }

.cf-card {
  /* no frame — a hairline on the paper, dashed until it is chosen */
  position: relative;
  flex: 0 0 auto;
  padding: clamp(9px, 1.4vw, 15px);
  border: 1px dashed var(--edge);
  transform-origin: 50% 50%;
  backface-visibility: hidden;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: border-color 0.35s ease, background-color 0.35s ease, box-shadow 0.35s ease;
}
.cf-card[data-ar="wide"] { width: var(--cf-card); }
.cf-card[data-ar="tall"] { width: calc(var(--cf-card) * 2 / 3); }
.cf-card[data-ar="wide"] img { aspect-ratio: 3 / 2; }
.cf-card[data-ar="tall"] img { aspect-ratio: 2 / 3; }
/* the chosen one turns into a white mount, the way a print laid on letter paper
   reads — the dashed cut-line becomes paper */
.cf-card[aria-checked="true"] {
  border-color: transparent;
  background: var(--card);
  box-shadow: 0 2px 6px rgba(51, 41, 31, 0.10), 0 14px 34px rgba(51, 41, 31, 0.16);
}
.cf-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 7px; }

.cf-card picture { display: block; }
.cf-card picture img { display: block; width: 100%; height: auto; }

/* the paper haze the turned-away photos recede into */
.cf-card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background: linear-gradient(170deg, rgba(244, 239, 228, 0.04), rgba(244, 239, 228, 0.5));
  opacity: var(--dim, 0);
  pointer-events: none;
}

/* the rail bleeds off into the paper at both ends */
.cf-fade {
  position: absolute;
  top: 0;
  bottom: 0;
  width: clamp(28px, 9%, 130px);
  pointer-events: none;
  z-index: 3;
}
.cf-fade-l { left: 0; background: linear-gradient(90deg, #f4efe4 0%, rgba(244, 239, 228, 0.62) 46%, rgba(244, 239, 228, 0) 100%); }
.cf-fade-r { right: 0; background: linear-gradient(270deg, #f4efe4 0%, rgba(244, 239, 228, 0.62) 46%, rgba(244, 239, 228, 0) 100%); }

.cf-hint { text-align: center; margin-top: clamp(1.2rem, 3vw, 1.8rem); font-size: 0.63rem; opacity: 0.72; }

.pick-thanks { text-align: center; margin-top: clamp(1rem, 3vw, 2rem); color: var(--ink-soft); }
/* ═══════════ 04 · The Day ═══════════ */

/* 囍 set between two hairlines, the way it sits at the head of a red invitation */
.day-crest { display: flex; align-items: center; justify-content: center; gap: clamp(0.9rem, 3vw, 1.6rem); margin-bottom: clamp(2.4rem, 6vw, 3.6rem); }
.crest-rule { flex: 0 1 clamp(48px, 16vw, 150px); height: 1px; background: var(--accent); opacity: 0.55; }
.crest-mark { font-family: "Noto Sans SC", sans-serif; font-size: clamp(1.5rem, 4vw, 2.1rem); color: var(--accent); line-height: 1; }

/* the timeline: icons and copy alternating across a spine that draws itself */
.timeline { position: relative; max-width: 660px; margin: 0 auto; }
.tl-spine {
  position: absolute;
  left: 50%;
  top: 0;
  translate: -50% 0;
  width: 2px;
  height: 100%;
  color: var(--accent);
  opacity: 0.28;
}
.tl-knot {
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  width: clamp(30px, 4.5vw, 44px);
  height: auto;
  color: var(--accent);
  opacity: 0.5;
}
/* the knot ties itself after the spine has run down */
.reveal.in .tl-knot path { transition-delay: 1.1s; }

.tl-list { list-style: none; display: grid; gap: clamp(2.2rem, 5.5vw, 3.4rem); }
.tl-item {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(3rem, 9vw, 5.5rem);
  align-items: center;
}
/* both cells pinned to row 1 — assigning columns out of source order would
   otherwise push the second one onto a new row (grid sparse auto-placement) */
.tl-icon { grid-row: 1; grid-column: 2; text-align: left; }
.tl-copy { grid-row: 1; grid-column: 1; text-align: right; }
.tl-item:nth-child(even) .tl-icon { grid-column: 1; text-align: right; }
.tl-item:nth-child(even) .tl-copy { grid-column: 2; text-align: left; }

.tl-icon img { width: clamp(62px, 9vw, 88px); height: auto; display: block; }
.tl-item:nth-child(even) .tl-icon img { margin-left: auto; }
.tl-time { font-size: clamp(0.78rem, 1.9vw, 0.9rem); letter-spacing: 0.18em; color: var(--accent); }
.tl-name { margin-top: 0.35rem; font-size: clamp(0.95rem, 2.3vw, 1.1rem); line-height: 1.4; }
.tl-name-cn { display: block; margin-top: 0.3rem; font-size: 0.8rem; line-height: 1.8; color: var(--ink-soft); }

.day-note { text-align: center; margin-top: clamp(2.2rem, 5vw, 3rem); }
.day-note-cn { font-family: "Noto Sans SC", sans-serif; margin-left: 0.6rem; letter-spacing: 0.14em; }


.btn {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--paper);
  background: var(--accent);
  border: 1px solid var(--accent);
  padding: 0.75rem 1.15rem;
  cursor: pointer;
  transition: background 0.25s ease, color 0.25s ease;
}
.btn:hover { background: transparent; color: var(--accent); }
.btn-ghost { background: transparent; color: var(--accent); }
.btn-ghost:hover { background: var(--accent); color: var(--paper); }

/* ═══════════ 05 · Where ═══════════ */

.where-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--edge); border: 1px solid var(--edge); max-width: 900px; margin: 0 auto; }
.where-item { background: var(--card); padding: 1.5rem 1.3rem 1.8rem; }
/* one Melbourne mark per cell — heights are tuned per icon, not uniform, so the
   wide tram and the tall lamp carry the same optical weight */
.where-icon { display: block; width: auto; margin-bottom: 0.9rem; }
.where-item:nth-child(1) .where-icon { height: 54px; }  /* lamp */
.where-item:nth-child(2) .where-icon { height: 44px; }  /* tram, now covers train too */
.where-item:nth-child(3) .where-icon { height: 48px; }  /* car under the plane tree */
/* cell 4 has no etching — the watercolour swatch strip (.swatch-strip, below)
   stands in for one and is sized to match this optical weight. */
.where-k { margin-bottom: 0.7rem; color: var(--accent); }
.where-item p:last-child { font-size: 0.95rem; }

/* Dress-code swatches: one generated watercolour strip (art-originals/t-swr-c.png,
   retinted 70% toward the couple's reference hexes — see PLAN §4.5). Round dabs:
   the first generation came out as tall ovals, so the prompt now forbids ovals and
   elongated strokes by name. True alpha, so none of the old multiply/mask hacks.
   At 5.5:1 the strip is shorter than the etched icons next door, so the bottom
   margin carries the difference and keeps all four DRESS CODE / PARKING labels
   on the same line. */
.swatch-strip { display: block; width: 120px; max-width: 100%; height: auto; margin: 0.5rem 0 2.1rem; }

/* ═══════════ 08 · RSVP ═══════════ */

.rsvp-sub { margin-top: 0.8rem; font-style: italic; color: var(--ink-soft); }
.rsvp-card {
  background: var(--card);
  border: 1px solid var(--edge);
  box-shadow: 0 12px 30px rgba(51, 41, 31, 0.12);
  max-width: 620px;
  margin: 0 auto;
  padding: clamp(1.7rem, 4.5vw, 2.8rem);
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}
.field { display: flex; flex-direction: column; gap: 0.45rem; }
.field label, .field legend {
  font-family: "IBM Plex Mono", "Noto Sans SC", monospace;
  font-size: 0.64rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.field input, .field select, .field textarea {
  font-family: "Newsreader", "Noto Sans SC", serif;
  font-size: 1.05rem;
  color: var(--ink);
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--edge);
  padding: 0.45rem 0.1rem;
  border-radius: 0;
  outline: none;
}
.field input:focus, .field select:focus, .field textarea:focus { border-bottom-color: var(--accent); }
.field textarea { resize: vertical; }
.field-fun {
  border: none;
  border-top: 1px dashed var(--edge);
  padding-top: 1.4rem;
}
.field-fun legend {
  font-family: "Newsreader", "Noto Sans SC", serif;
  font-size: 0.95rem;
  font-style: italic;
  letter-spacing: 0;
  text-transform: none;
  color: var(--accent);
  padding-right: 0.7rem;
}
.fun-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1.4rem; margin-top: 0.9rem; }
/* the MBTI label is long enough to wrap; bottom-align so both rules share a baseline */
.fun-row .field { justify-content: flex-end; }
/* keep the label's break between the two languages, not mid-phrase */
.lab-cn { white-space: nowrap; }
/* attendance: two plain buttons, not radio dots — bigger touch target, no ambiguity */
.field-radio { border: none; padding: 0; }
.choices { display: grid; grid-template-columns: 1fr 1fr; gap: 0.8rem; margin-top: 0.7rem; }
/* user-select: a double-tap on a button-like control shouldn't highlight its text */
.choice { cursor: pointer; display: flex; -webkit-user-select: none; user-select: none; }
/* visually hidden but still focusable and announced — the label span is the visual */
.choice input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
.choice span {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  padding: 0.85rem 0.6rem;
  border: 1px solid var(--edge);
  background: transparent;
  color: var(--ink);
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-align: center;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.choice span em {
  font-family: "Noto Sans SC", sans-serif;
  font-style: normal;
  font-size: 0.82rem;
  letter-spacing: 0.16em;
  text-transform: none;
  color: var(--ink-soft);
  transition: color 0.2s ease;
}
.choice:hover span { border-color: var(--accent); }
.choice input:checked + span {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--paper);
}
.choice input:checked + span em { color: rgba(244, 239, 228, 0.75); }
.choice input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
/* attending branches: match the card's own column rhythm */
#rsvp-yes, #rsvp-board { display: flex; flex-direction: column; gap: 1.5rem; }
.field input[type="file"] {
  border-bottom: none;
  padding: 0.2rem 0;
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
}
.field input[type="file"]::file-selector-button {
  font-family: "IBM Plex Mono", monospace;
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  background: transparent;
  border: 1px solid var(--accent);
  padding: 0.55rem 0.9rem;
  margin-right: 0.9rem;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}
.field input[type="file"]::file-selector-button:hover { background: var(--accent); color: var(--paper); }
#f-website { position: absolute; left: -6000px; }
.form-error { color: var(--accent); letter-spacing: 0.08em; }
.form-error-cn { display: block; margin-top: 0.4rem; font-size: 0.78rem; }
.btn-submit { align-self: center; margin-top: 0.4rem; padding: 0.9rem 2rem; line-height: 1.5; }
.btn-cn { display: block; margin-top: 0.25rem; font-size: 0.72rem; letter-spacing: 0.2em; opacity: 0.85; }

.rsvp-corner {
  position: absolute;
  left: -6px;
  bottom: -6px;
  width: clamp(90px, 14vw, 140px);
  pointer-events: none;
  opacity: 0.85;
}
.rsvp-card { position: relative; }

.rsvp-done { text-align: center; padding: 3rem 1rem 1rem; }
.done-flourish {
  width: min(240px, 56vw);
  animation: settle-in 0.8s var(--ease-settle);
}
@keyframes settle-in {
  0% { transform: translateY(14px) scale(0.96); opacity: 0; }
  100% { transform: none; opacity: 1; }
}
.done-line { font-family: "Fraunces", serif; font-size: 1.35rem; margin-top: 1.4rem; }
.done-line-cn { font-family: "Noto Sans SC", sans-serif; color: var(--ink-soft); letter-spacing: 0.2em; margin-top: 0.5rem; }

/* ═══════════ 09 · Sign-off ═══════════ */

.signoff { position: relative; text-align: center; padding: clamp(5rem, 12vw, 9rem) 1.4rem clamp(6rem, 12vw, 9rem); overflow: hidden; }
.signoff-branch { width: min(300px, 62vw); margin-bottom: 2.4rem; }
.signoff-en { font-style: italic; color: var(--ink-soft); margin-bottom: 1rem; }
.signoff-hand-cn { font-family: "Noto Sans SC", sans-serif; letter-spacing: 0.3em; color: var(--accent); margin-bottom: 1.4rem; }
.signoff-date-cn { display: block; margin-top: 0.5rem; font-family: "Noto Sans SC", sans-serif; letter-spacing: 0.14em; }
.signoff-cn { font-family: "Noto Sans SC", sans-serif; font-size: clamp(1.1rem, 2.6vw, 1.45rem); letter-spacing: 0.3em; margin-bottom: 2.2rem; }
.signoff-hand { font-family: "Mrs Saint Delafield", cursive; font-size: clamp(2.2rem, 6vw, 3.4rem); color: var(--accent); line-height: 1; margin-bottom: 1.4rem; }
.signoff-names { font-family: "Fraunces", serif; font-weight: 350; font-size: clamp(1.5rem, 3.6vw, 2.1rem); }
.signoff-names-cn { display: block; font-family: "Noto Sans SC", sans-serif; font-size: 0.95rem; letter-spacing: 0.4em; color: var(--ink-soft); margin-top: 0.7rem; }
.signoff-date { margin-top: 2.4rem; }

/* ═══════════ responsive ═══════════ */

@media (max-width: 820px) {
  .cover-inner { grid-template-columns: 1fr; }
  .cover-print { justify-self: center; max-width: min(400px, 86vw); order: -1; }
  .cover-text { text-align: center; }
  /* minmax(0,…) not 1fr: plain 1fr floors at min-content, and the merged
     TRAIN & TRAM cell is long enough to shove the columns out of balance */
  .where-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .fun-row { grid-template-columns: 1fr; gap: 1.5rem; }
  .tl-item { gap: 3.4rem; }
  .tl-knot { width: 24px; }
}

/* phones: full-width choice buttons — no cramped wrapping, easier to tap */
@media (max-width: 520px) {
  .choices { grid-template-columns: 1fr; gap: 0.6rem; }
  /* leave room either side, otherwise the centre photo covers its neighbours
     completely and there is nothing to slide towards */
  .cf { --cf-card: 80vw; }
  /* pull the sprites further onto their element so they don't hang off-screen */
  .sprite-camera { translate: -2% -44%; }
  .sprite-plane { translate: 20% 24%; }
}

/* ═══════════ reduced motion ═══════════ */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  .print.reveal { transform: none; }
  /* no fan, no haze: one print per swipe, flat on the table */
  .cf { --cf-step: var(--cf-card); }
  .cf-card { transition: none; }
  .cf-card::after { display: none; }
  .draw path { stroke-dashoffset: 0; transition: none; }
  .reveal.in .tl-knot path { transition-delay: 0s; }
  .petal, .scroll-cue { animation: none; }
  .done-flourish { animation: none; }
}
