/* ═══════════ Chang & 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 Serif SC";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../assets/fonts/notoserifsc-400.woff2?v=3") 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 Serif 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", 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 serif — never bare CJK inside .mono, which has no CJK face */
.cn { font-family: "Noto Serif SC", serif; letter-spacing: 0.14em; font-style: normal; }
.section-no-cn,
.h2-cn,
.cap-cn,
.where-k-cn,
.where-cn,
.tl-name-cn,
.ask-line-cn,
.cover-date-cn,
.rsvp-sub-cn,
.form-error-cn,
.btn-cn,
.faq-dt-cn,
.faq-cn,
.signoff-date-cn { font-family: "Noto Serif SC", serif; font-style: normal; letter-spacing: 0.14em; }

.section-no-cn { display: block; margin-top: 0.5rem; font-size: 0.72rem; }
.h2-cn, .where-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%;
}
.eyebrow { margin-bottom: 1.6rem; }
.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 Serif SC", 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 Serif SC", 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;
}
.cover-date { margin-top: 1.8rem; font-style: italic; font-size: clamp(1rem, 1.8vw, 1.2rem); }
.cover-date-sub { display: inline-block; margin-top: 0.7rem; font-style: normal; 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-line-cn { display: block; margin-top: 0.7rem; font-size: 0.95rem; line-height: 2; color: var(--ink-soft); }
.ask-cn { margin-top: clamp(2.6rem, 6vw, 4rem); }
.ask-cn-formal { font-family: "Noto Serif SC", serif; line-height: 2.15; letter-spacing: 0.12em; font-size: 0.98rem; }
.ask-cn-soft { font-family: "Noto Serif SC", serif; margin-top: 1.4rem; color: var(--ink-soft); letter-spacing: 0.14em; }
/* venue map: generated watercolour artwork + our own labels on top. The artwork
   carries no lettering at all (image models cannot spell street names, and this
   one has to match the surveyed layout), so every word here is ours. */
.venue-map-fig { max-width: min(620px, 96vw); margin: 0 auto clamp(2.4rem, 5vw, 3.4rem); }
.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); font-style: italic; }
.pick-sub-cn {
  display: block;
  margin-top: 0.5rem;
  font-family: "Noto Serif SC", serif;
  font-style: normal;
  font-size: 0.88rem;
  letter-spacing: 0.1em;
  line-height: 1.9;
}

.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; }
.cf-hint-cn { display: block; margin-top: 0.45rem; font-family: "Noto Serif SC", serif; font-size: 0.72rem; letter-spacing: 0.14em; }

.pick-thanks { text-align: center; margin-top: clamp(1rem, 3vw, 2rem); color: var(--ink-soft); }
.pick-thanks-cn { font-family: "Noto Serif SC", serif; margin-left: 0.5rem; letter-spacing: 0.16em; }
/* ═══════════ 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 Serif SC", 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 Serif SC", 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; }
.where-item:nth-child(2) .where-icon { height: 50px; }
.where-item:nth-child(3) .where-icon { height: 44px; }
.where-item:nth-child(4) .where-icon { height: 48px; }
.where-k { margin-bottom: 0.7rem; color: var(--accent); }
.where-k-cn { margin-left: 0.5rem; font-size: 0.72rem; }
.where-cn { display: block; margin-top: 0.6rem; font-size: 0.82rem; line-height: 1.95; color: var(--ink-soft); }
.where-item p:last-child { font-size: 0.95rem; }

/* ═══════════ 07 · Good to Know ═══════════ */

/* the base grid rule went missing when the What-to-Wear section was deleted —
   without it the <dl> stacked with no gap at all, which only became obvious once
   every answer grew a second (Chinese) line */
.faq-list {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1.8rem, 4vw, 2.8rem) clamp(2rem, 5vw, 3.4rem);
  max-width: 900px;
  margin: 0 auto;
}
.faq-item dt { font-family: "Fraunces", serif; font-style: italic; font-size: 1.2rem; margin-bottom: 0.45rem; color: var(--accent); }
.faq-dt-cn { margin-left: 0.6rem; font-size: 0.82rem; }
.faq-cn { display: block; margin-top: 0.55rem; font-size: 0.85rem; line-height: 2; color: var(--ink-soft); }
.faq-item dd { color: var(--ink-soft); font-size: 0.97rem; }

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

.rsvp-sub { margin-top: 0.8rem; font-style: italic; color: var(--ink-soft); }
.rsvp-sub-cn { display: block; margin-top: 0.5rem; font-size: 0.85rem; }
.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", 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 Serif 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 Serif 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 Serif SC", 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); }
.board-note { font-size: 0.9rem; font-style: italic; color: var(--ink-soft); line-height: 1.6; }
.board-note-cn {
  display: block;
  margin-top: 0.3rem;
  font-style: normal;
  font-family: "Noto Serif SC", serif;
  font-size: 0.84rem;
  letter-spacing: 0.08em;
}
#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 Serif SC", 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 Serif SC", serif; letter-spacing: 0.3em; color: var(--accent); margin-bottom: 1.4rem; }
.signoff-date-cn { display: block; margin-top: 0.5rem; font-family: "Noto Serif SC", serif; letter-spacing: 0.14em; }
.signoff-cn { font-family: "Noto Serif SC", 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 Serif SC", 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; }
  .where-grid { grid-template-columns: 1fr 1fr; }
  .faq-list { grid-template-columns: 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; }
}
