:root {
  --gold: #cba869;
  --gold-soft: #e4cd9c;
  --ink: #0b0d12;
  --ink-2: #12151d;
  --parchment: #e9dcbd;
  --serif-cn: 'Noto Serif SC', serif;
  --serif-en: 'Cormorant Garamond', serif;
  --script: 'Ma Shan Zheng', cursive;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  background: var(--ink);
  color: #eee6d3;
  font-family: var(--serif-cn);
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

#bg {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  outline: none;
  cursor: pointer;
}

/* ---------- atmosphere overlays ---------- */

.scene-vignette {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  background: radial-gradient(ellipse at center, rgba(0,0,0,0) 40%, rgba(0,0,0,0.55) 100%);
}

.scene-grain {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  opacity: 0.05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  background-size: 220px 220px;
}

/* ---------- loading screen ---------- */

#loading-screen {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: radial-gradient(ellipse at 50% 35%, #14181f 0%, #05060a 78%);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  transition: opacity 1.1s ease, visibility 1.1s ease;
}
#loading-screen.hidden {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.loading-emblem {
  width: 68px;
  height: 68px;
  border: 1px solid var(--gold);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--gold-soft);
  font-size: 26px;
  letter-spacing: 0;
  box-shadow: 0 0 30px rgba(203,168,105,0.25);
  animation: emblem-pulse 3.2s ease-in-out infinite;
}
@keyframes emblem-pulse {
  0%, 100% { box-shadow: 0 0 18px rgba(203,168,105,0.18); }
  50% { box-shadow: 0 0 34px rgba(203,168,105,0.4); }
}

.loading-title {
  font-size: 22px;
  letter-spacing: 6px;
  color: #f2e8d2;
  font-weight: 500;
}
.loading-title .amp { color: var(--gold); margin: 0 10px; font-family: var(--serif-en); font-style: italic; }

.loading-bar {
  width: 220px;
  height: 2px;
  background: rgba(255,255,255,0.12);
  border-radius: 2px;
  overflow: hidden;
}
.loading-bar-fill {
  width: 0%;
  height: 100%;
  background: linear-gradient(90deg, var(--gold), var(--gold-soft));
  transition: width 0.25s ease;
}

.loading-hint {
  font-size: 12px;
  letter-spacing: 3px;
  color: rgba(238,230,211,0.5);
}

/* ---------- intro hint ---------- */

.intro-hint {
  position: fixed;
  left: 50%;
  bottom: 8%;
  transform: translateX(-50%);
  z-index: 5;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.9s ease, transform 0.9s ease;
}
.intro-hint.visible {
  opacity: 1;
}
.intro-hint.fade-out {
  opacity: 0;
  transform: translateX(-50%) translateY(10px);
}
.intro-title {
  font-size: 15px;
  letter-spacing: 10px;
  color: var(--gold-soft);
  margin-bottom: 8px;
  text-shadow: 0 0 20px rgba(203,168,105,0.5);
}
.intro-sub {
  font-size: 13px;
  letter-spacing: 2px;
  color: rgba(238,230,211,0.75);
}
.intro-arrow {
  margin-top: 10px;
  color: var(--gold);
  animation: bob 2s ease-in-out infinite;
  font-size: 14px;
}
@keyframes bob {
  0%, 100% { transform: translateY(0); opacity: 0.5; }
  50% { transform: translateY(6px); opacity: 1; }
}

/* ---------- scroll / parchment panel ---------- */

.scroll-panel {
  position: fixed;
  top: 50%;
  right: 4.5vw;
  transform: translateY(-50%) translateX(40px);
  /* width is the single driving dimension — folding the height cap in as
     "84vh worth of width" (rather than a separate max-height) keeps the
     378:540 aspect ratio intact no matter which constraint binds, so the
     torn-paper mask never gets clamped short on one axis and squashed.
     (Ratio is a bit taller than the source paper-cut.png's own 378:498 —
     mask-size:100% 100% stretches it to fit, so this is safe to tune.) */
  width: min(36vw, 460px, 84vh * 0.7);
  aspect-ratio: 378 / 540;
  max-width: 92vw;
  z-index: 6;
  opacity: 0;
  pointer-events: none;
  transition: opacity 1.1s ease, transform 1.1s cubic-bezier(.19,1,.22,1);
  filter: drop-shadow(0 22px 46px rgba(0,0,0,0.55));
}
.scroll-panel.visible {
  opacity: 1;
  transform: translateY(-50%) translateX(0);
  pointer-events: auto;
}

/* card is clipped to a torn-parchment silhouette using the alpha channel of
   parchment-shape.webp (a real photographed sheet of parchment, deckled on
   all four sides) as the mask -- and that same image supplies the visible
   texture/color too, so the printed edge coloring and the cut silhouette
   always line up exactly (both layers use the identical image, stretched
   identically via 100% 100%). mask-size is stretched 100% 100% so it always
   fills the panel box exactly, no matter how the aspect ratio is nudged by a
   breakpoint or a clamped viewport. */
.scroll-inner {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #4a3c2c url('../assets/textures/parchment-shape.webp') center/100% 100% no-repeat;
  -webkit-mask-image: url('../assets/textures/parchment-shape.webp');
  -webkit-mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-image: url('../assets/textures/parchment-shape.webp');
  mask-size: 100% 100%;
  mask-repeat: no-repeat;
  mask-position: center;
}

.scroll-photo-stack {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.scroll-photo {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center 25%;
  opacity: 0;
  transform: scale(1.04);
  transition: opacity 1.4s ease, transform 2.2s ease;
}
.scroll-photo.active {
  opacity: 0.55;
  transform: scale(1);
}
.scroll-photo-veil {
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(180deg, rgba(28,23,18,0.28) 0%, rgba(28,23,18,0.4) 38%, rgba(20,16,12,0.55) 78%, rgba(15,12,9,0.68) 100%),
    linear-gradient(120deg, rgba(233,220,189,0.24), rgba(233,220,189,0) 55%);
  mix-blend-mode: multiply;
}
.scroll-inner::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: repeating-linear-gradient(115deg, rgba(255,255,255,0.02) 0px, rgba(255,255,255,0.02) 1px, transparent 1px, transparent 3px);
  opacity: 0.5;
  pointer-events: none;
}

.scroll-content {
  position: relative;
  z-index: 3;
  height: 100%;
  /* top padding is a bit taller than it needs to be to clear the mask's
     notch on its own -- now that the page-count badge no longer sits inline
     above each pane (it moved to the top-right corner), content was landing
     high up in the card with a lot of empty room left at the bottom below
     the dots; this pulls it back down to sit more centered, using space
     that was already going unused */
  padding: 15% 9% 27%;
  overflow-y: auto;
  scrollbar-width: none;
}
.scroll-content::-webkit-scrollbar { display: none; }

/* Scroll affordance -- .scroll-content already scrolls (overflow-y: auto)
   whenever a pane's content is taller than the card, but with the
   scrollbar hidden there was nothing telling a guest that more content
   exists below the fold (a real risk on short screens, or with the RSVP
   pane's accommodation fields expanded). This fades the card's bottom
   edge whenever the active pane actually overflows (JS toggles
   .can-scroll on #scroll-panel), and fades it back out once the guest has
   scrolled near the bottom (.near-bottom) -- so it reads as "there's more,
   scroll for it" rather than a permanent decoration. It's a pseudo-element
   on .scroll-inner (not .scroll-content) so it's clipped by the same
   torn-paper mask as everything else and never pokes past the card's
   ragged edge; pointer-events:none keeps it from ever blocking clicks on
   real content underneath (e.g. the RSVP submit button). */
.scroll-inner::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 16%;
  background: linear-gradient(180deg, transparent, rgba(15,12,9,0.6) 80%);
  z-index: 4;
  opacity: 0;
  transition: opacity 0.4s ease;
  pointer-events: none;
}
.scroll-panel.can-scroll .scroll-inner::before { opacity: 1; }
.scroll-panel.can-scroll.near-bottom .scroll-inner::before { opacity: 0; }

.track-index {
  /* pinned to the top-right corner, out of the normal content flow --
     unlike the top-left corner, the parchment mask's top-right corner has
     only light, shallow raggedness, so a short label sitting right at the
     padding edge here never runs into a torn-off notch the way top-left
     content can. Positioning it here (rather than inline above each pane's
     eyebrow) also means it no longer has to be a page's "first line", which
     is what let the eyebrow below get clipped by the top-left notch. */
  position: absolute;
  top: 6.5%;
  right: 11%;
  font-family: var(--serif-en);
  font-size: 12px;
  letter-spacing: 2px;
  color: var(--gold-soft);
  opacity: 0.85;
  display: flex;
  align-items: baseline;
  gap: 4px;
  z-index: 1;
}
.track-current { font-size: 15px; color: var(--gold); font-weight: 600; }
.track-total { opacity: 0.6; }

.track-pane {
  display: none;
  animation: pane-in 0.9s ease;
}
.track-pane.active { display: block; }
@keyframes pane-in {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: translateY(0); }
}

.eyebrow {
  font-family: var(--serif-en);
  font-size: 12px;
  letter-spacing: 5px;
  color: rgba(233,220,189,0.55);
  margin-bottom: 14px;
}

.names {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px;
  margin: 0 0 6px;
  font-family: var(--script);
  font-weight: 400;
  color: #f5ecd8;
}
.name-cn { font-size: clamp(30px, 3.6vw, 44px); text-shadow: 0 0 24px rgba(203,168,105,0.25); }
.name-amp { font-family: var(--serif-en); font-style: italic; color: var(--gold); font-size: 22px; }

.names-en {
  font-family: var(--serif-en);
  font-style: italic;
  letter-spacing: 2px;
  color: rgba(233,220,189,0.6);
  margin: 0 0 22px;
  font-size: 15px;
}

.section-title {
  font-size: 26px;
  font-weight: 500;
  color: #f5ecd8;
  margin: 0 0 6px;
  letter-spacing: 2px;
}

.divider {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 18px 0 22px;
  color: var(--gold);
  opacity: 0.8;
}
.divider span { flex: 1; height: 1px; background: linear-gradient(90deg, transparent, var(--gold), transparent); }
.divider i { font-style: normal; font-size: 11px; }

/* the RSVP pane has more content than the other two tracks (name field,
   attend toggle, guest count, message, submit) — tighten its vertical
   rhythm a bit so the submit button lands comfortably inside the card's
   torn-parchment silhouette instead of crowding its ragged bottom edge */
.track-pane[data-track="2"] .eyebrow { margin-bottom: 8px; }
.track-pane[data-track="2"] .divider { margin: 8px 0 14px; }

.lead-text {
  font-size: 15px;
  line-height: 2;
  color: rgba(238,230,211,0.82);
  margin: 0;
}
.lead-text.small { font-size: 13.5px; margin-top: 26px; }

.ending-signature {
  font-family: var(--script);
  font-weight: 400;
  font-size: 22px;
  color: var(--gold);
  text-shadow: 0 0 20px rgba(203,168,105,0.25);
  margin: 40px 0 0;
  letter-spacing: 1px;
}

.info-block { margin-top: 6px; }
.info-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 6px 14px;
  padding: 13px 0;
  border-bottom: 1px solid rgba(203,168,105,0.18);
}
.info-label {
  font-size: 12.5px;
  letter-spacing: 2px;
  color: rgba(233,220,189,0.55);
  white-space: nowrap;
}
.info-value {
  font-size: 15px;
  color: #f5ecd8;
  text-align: right;
  font-weight: 500;
  letter-spacing: 0.5px;
}

/* long values (venue name, full address) read poorly squeezed onto the
   same line as their label -- stack label above value instead, value
   left-aligned and free to wrap across as many lines as it needs */
.info-row--stacked {
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}
.info-row--stacked .info-value {
  text-align: left;
  line-height: 1.55;
}

/* ---------- RSVP form ---------- */

.rsvp-form { display: flex; flex-direction: column; gap: 10px; margin-top: 2px; }
.field { display: flex; flex-direction: column; gap: 5px; }
.field-label {
  font-size: 12.5px;
  letter-spacing: 2px;
  color: rgba(233,220,189,0.6);
}
.field-sublabel {
  font-size: 11px;
  letter-spacing: 1.5px;
  color: rgba(233,220,189,0.45);
  margin-top: 2px;
}
.field input[type="text"],
.field textarea,
.field select {
  background: rgba(255,255,255,0.05);
  border: 1px solid rgba(203,168,105,0.3);
  border-radius: 3px;
  padding: 7px 12px;
  color: #f5ecd8;
  font-family: var(--serif-cn);
  font-size: 14px;
  outline: none;
  resize: none;
  transition: border-color 0.25s ease, background 0.25s ease;
}
.field input[type="text"]:focus,
.field textarea:focus,
.field select:focus {
  border-color: var(--gold);
  background: rgba(255,255,255,0.08);
}
.field input::placeholder, .field textarea::placeholder { color: rgba(238,230,211,0.35); }

.attend-toggle { display: flex; gap: 10px; }
/* the accommodation-nights toggle has 3 buttons instead of 2 and sits
   directly under the yes/no toggle above it (no separate field wrapper),
   so it gets a little breathing room and slightly tighter button padding
   to keep 3-across comfortable at the same width as the 2-button groups */
.nights-toggle { margin-top: 6px; gap: 8px; }
.nights-toggle .attend-btn { padding-left: 4px; padding-right: 4px; }
.attend-btn {
  flex: 1;
  padding: 7px 8px;
  border-radius: 3px;
  border: 1px solid rgba(203,168,105,0.3);
  background: transparent;
  color: rgba(238,230,211,0.7);
  font-family: var(--serif-cn);
  font-size: 13.5px;
  cursor: pointer;
  transition: all 0.25s ease;
}
.attend-btn.active {
  background: linear-gradient(160deg, var(--gold-soft), var(--gold));
  color: #241b0d;
  border-color: var(--gold);
  font-weight: 600;
}

.stepper { display: flex; align-items: center; gap: 0; width: fit-content; }
.step-btn {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1px solid rgba(203,168,105,0.4);
  background: rgba(255,255,255,0.04);
  color: var(--gold-soft);
  font-size: 15px;
  cursor: pointer;
  line-height: 1;
  transition: background 0.2s ease;
}
.step-btn:hover { background: rgba(203,168,105,0.2); }
.stepper input {
  width: 46px;
  text-align: center;
  background: transparent;
  border: none;
  color: #f5ecd8;
  font-size: 16px;
  font-family: var(--serif-en);
}

.submit-btn {
  margin-top: 4px;
  padding: 11px;
  border-radius: 3px;
  border: 1px solid var(--gold);
  background: linear-gradient(160deg, var(--gold-soft), var(--gold));
  color: #241b0d;
  font-family: var(--serif-cn);
  font-weight: 600;
  font-size: 14.5px;
  letter-spacing: 3px;
  cursor: pointer;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.submit-btn:hover { transform: translateY(-1px); box-shadow: 0 8px 22px rgba(203,168,105,0.28); }
.submit-btn:active { transform: translateY(0); }
.submit-btn:disabled { opacity: 0.7; cursor: default; }
.submit-btn.is-submitting { letter-spacing: 2px; }
.submit-btn.is-submitting::after { content: "…"; }

/* Netlify Forms honeypot field: kept in the DOM (so bots that don't render
   CSS still see and fill it, which is the point) but hidden from real
   guests and screen readers. */
.hp-field {
  position: absolute;
  left: -5000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.rsvp-error {
  display: none;
  margin: 8px 0 0;
  padding: 8px 10px;
  border: 1px solid rgba(214,120,120,0.5);
  border-radius: 3px;
  background: rgba(120,40,40,0.18);
  color: #f3d9d9;
  font-size: 12.5px;
  line-height: 1.5;
  text-align: center;
}
.rsvp-error.visible { display: block; }
.rsvp-error-detail { display: inline-block; margin-top: 3px; opacity: 0.75; font-size: 11px; letter-spacing: 0.5px; }

.rsvp-thanks {
  display: none;
  text-align: center;
  padding: 30px 6px 10px;
  animation: pane-in 0.7s ease;
}
.rsvp-thanks.visible { display: block; }
.thanks-emblem {
  width: 46px; height: 46px;
  margin: 0 auto 16px;
  border-radius: 50%;
  border: 1px solid var(--gold);
  display: flex; align-items: center; justify-content: center;
  color: var(--gold-soft);
  font-size: 18px;
}
.thanks-title { font-size: 19px; color: #f5ecd8; margin: 0 0 8px; letter-spacing: 2px; }
.thanks-sub { font-size: 13.5px; color: rgba(238,230,211,0.7); margin: 0; line-height: 1.8; }
.edit-again-btn {
  margin-top: 22px;
  background: none;
  border: none;
  color: var(--gold-soft);
  font-family: var(--serif-cn);
  font-size: 12.5px;
  letter-spacing: 2px;
  text-decoration: underline;
  text-underline-offset: 4px;
  cursor: pointer;
  opacity: 0.8;
}
.edit-again-btn:hover { opacity: 1; }

/* ---------- dots & hint ---------- */

.track-dots {
  position: absolute;
  left: 50%;
  bottom: 7%;
  transform: translateX(-50%);
  display: flex;
  gap: 10px;
  z-index: 4;
}
.dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  border: 1px solid rgba(203,168,105,0.6);
  background: transparent;
  padding: 0;
  cursor: pointer;
  transition: all 0.3s ease;
}
.dot.active { background: var(--gold); box-shadow: 0 0 8px rgba(203,168,105,0.7); }

/* While the RSVP pane is active the form can grow tall enough to sit under
   this dot row on phones; dots are secondary navigation (the gramophone is
   primary), so their hit-testing is turned off here to guarantee taps on the
   RSVP submit button always land on the button, never on a dot underneath. */
.track-dots.nav-inert { pointer-events: none; }

.scroll-hint {
  position: absolute;
  left: 50%;
  bottom: 3.2%;
  transform: translateX(-50%);
  font-size: 10.5px;
  letter-spacing: 2px;
  color: rgba(233,220,189,0.4);
  z-index: 4;
  white-space: nowrap;
  /* purely decorative text, never has a click handler -- always let clicks
     pass through to whatever's underneath (e.g. the RSVP submit button on
     tall form states, which can extend into this reserved bottom strip on
     small phones) */
  pointer-events: none;
}

.brand-footer {
  position: fixed;
  left: 24px;
  bottom: 18px;
  z-index: 4;
  font-family: var(--serif-en);
  font-style: italic;
  font-size: 12px;
  letter-spacing: 1.5px;
  color: rgba(238,230,211,0.35);
}

.model-credit {
  position: fixed;
  left: 24px;
  bottom: 4px;
  z-index: 4;
  font-family: var(--serif-en);
  font-size: 10px;
  letter-spacing: 0.4px;
  color: rgba(238,230,211,0.22);
}
.model-credit a {
  color: rgba(238,230,211,0.35);
  text-decoration: underline;
}
.model-credit a:hover { color: rgba(238,230,211,0.6); }

/* ---------- music toggle ---------- */

.music-toggle {
  position: fixed;
  top: 28px;
  right: 28px;
  z-index: 20;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 1px solid rgba(203,168,105,0.4);
  background: rgba(10,10,14,0.42);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: rgba(238,230,211,0.65);
  transition: color 0.3s ease, border-color 0.3s ease, background 0.3s ease;
}
.music-toggle:hover {
  color: var(--gold-soft);
  border-color: var(--gold);
}

.music-toggle-ring {
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  border: 1px solid rgba(203,168,105,0.5);
  opacity: 0;
  transform: scale(1);
}

.music-icon {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 0;
}
.music-icon svg { display: block; }

/* while playing: icon glows gold and a soft ring pulses outward */
.music-toggle.is-playing {
  color: var(--gold-soft);
  border-color: var(--gold);
}
.music-toggle.is-playing .music-toggle-ring {
  animation: musicPulse 2.6s ease-out infinite;
}
@keyframes musicPulse {
  0%   { opacity: 0.55; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.45); }
}

/* ---------- responsive ---------- */

@media (max-width: 880px) {
  .model-credit { display: none; }
  .brand-footer { display: none; }

  .scroll-panel {
    right: 50%;
    transform: translate(50%, -46%) translateY(6vh);
    top: 50%;
    /* same single-dimension trick as the base rule, so a short mobile
       viewport (browser toolbars eating into vh) shrinks the card
       proportionally instead of clamping height and leaving width fixed —
       that mismatch was what squashed the card and let content spill past
       the ragged edge */
    width: min(92vw, 420px, 82vh * 0.7);
    aspect-ratio: 378 / 540;
  }
  .scroll-panel.visible { transform: translate(50%, -46%) translateY(0); }

  .scroll-content {
    /* top padding clears the parchment mask's top-left notch (see the
       .eyebrow-removal note further down) -- see the base rule above for
       why it got taller in the first place */
    padding: 12% 7% 18%;
  }

  .intro-title { letter-spacing: 7px; }
  .intro-sub { font-size: 12px; letter-spacing: 1px; padding: 0 24px; }

  .track-dots { gap: 16px; }
  .dot { position: relative; }
  .dot::before { content: ""; position: absolute; inset: -12px; }

  .stepper .step-btn { width: 38px; height: 38px; }

  /* date/venue pane: the venue name + full address are long enough on a
     narrow phone that they now wrap onto multiple lines each (see
     .info-row--stacked) -- tighten the surrounding spacing so that still
     fits within the card. */
  .info-row { padding: 9px 0; }
  .lead-text.small { margin-top: 14px; }
  .ending-signature { margin-top: 24px; }

  /* RSVP pane is the tallest — tighten further on narrow phones so the
     submit button lands inside the parchment silhouette, not past it.
     Two extra fields (accommodation toggle + conditional nights select)
     were added later, so this needed a second, deeper round of tightening. */
  .rsvp-form { gap: 3px; }
  .field { gap: 1px; }
  .field-label { font-size: 11.5px; }
  .field input[type="text"], .field textarea, .field select { padding: 5px 10px; font-size: 13px; }
  .attend-btn { padding: 5px 8px; font-size: 13px; }
  .nights-toggle { margin-top: 4px; }
  .submit-btn { margin-top: 0; padding: 9px; }
  .track-pane[data-track="2"] .divider { margin: 4px 0 8px; }
  .track-pane[data-track="2"] .section-title { margin-bottom: 2px; }

  .music-toggle {
    top: 16px;
    right: 16px;
    width: 38px;
    height: 38px;
  }
}

@media (max-width: 420px) {
  .name-cn { font-size: clamp(26px, 8vw, 36px); }
  .section-title { font-size: 22px; }
  /* see the base rule near the top for why top padding got taller */
  .scroll-content { padding: 14% 6% 30%; }
  .lead-text { font-size: 14px; }
}

/* narrow phones (older/small devices, ~320-340px wide) -- the card is
   width-driven (fixed aspect-ratio), so a narrow screen makes the whole
   card smaller regardless of how tall the viewport is. Apply the same
   depth of RSVP tightening used for short-height phones here too, keyed
   off width instead of height. */
@media (max-width: 340px) {
  /* these cards are the smallest of any breakpoint (width-driven sizing),
     with almost no spare room below the last line of content -- top padding
     still needs to clear the parchment mask's top-left notch (titles are
     flush-left everywhere now, no left nudge to fall back on), but is kept
     as small as that allows since there's no room to spare below */
  .scroll-content { padding-top: 12%; }
  .info-row { padding: 6px 0; }
  .info-value { font-size: 14px; }
  .lead-text.small { display: none; }
  .ending-signature { margin-top: 16px; font-size: 19px; }
  .track-pane[data-track="2"] .divider { margin: 3px 0 6px; }
  .track-pane[data-track="2"] .section-title { margin-bottom: 0; }
  .rsvp-form { gap: 2px; }
  .field { gap: 0px; }
  .field-label { font-size: 10.5px; }
  .field input[type="text"], .field textarea, .field select { padding: 4px 10px; font-size: 12px; }
  .field textarea { height: 24px; }
  .attend-btn { padding: 3px 6px; font-size: 12px; }
  .nights-toggle { margin-top: 3px; }
  .step-btn { width: 22px; height: 22px; }
  .submit-btn { padding: 6px; margin-top: 0; font-size: 12.5px; }
}

/* short phones (browser chrome eating into the viewport, or just a small
   device) — the card itself now shrinks to stay in the parchment's true
   aspect ratio, so give the RSVP pane a bit more of a squeeze too */
@media (max-height: 680px) and (orientation: portrait) {
  /* top padding needs to clear the parchment mask's top-left notch (titles
     are flush-left everywhere now) -- this breakpoint has much less spare
     room at the bottom than the wider ones above, which is fine: content
     that still doesn't fit simply scrolls (.scroll-content is a real
     scroll container), rather than needing to be squeezed to fit exactly */
  .scroll-content { padding: 12% 6% 14%; }
  .rsvp-form { gap: 4px; }
  .field { gap: 2px; }
  .track-pane[data-track="2"] .divider { margin: 3px 0 6px; }
  .submit-btn { margin-top: 0; padding: 9px; }
}

@media (max-height: 620px) and (orientation: landscape) {
  .scroll-panel {
    /* height-driven here (short viewport), same single-dimension trick:
       fold the width cap in as "92vw worth of height" instead of a
       separate max-width, so the aspect ratio never gets distorted */
    height: min(96vh, 640px, 92vw / 0.7);
    width: auto;
    aspect-ratio: 378 / 540;
    top: 50%;
  }
  .intro-hint { bottom: 4%; }

  /* very little vertical room here — trim every pane. Top padding still
     needs to clear the parchment mask's top-left notch so titles can stay
     flush-left (see the 880px breakpoint above); anything that still
     doesn't fit below that just scrolls, rather than being fought back
     into the visible card height by squeezing further */
  .scroll-content { padding: 12% 6% 8%; }
  .scroll-hint { display: none; }
  .section-title { font-size: 18px; margin-bottom: 2px; }
  .divider { margin: 5px 0 8px; }
  .info-row { padding: 4px 0; }
  .info-value { font-size: 13.5px; }
  .lead-text.small { display: none; }
  .ending-signature { margin-top: 10px; font-size: 17px; }
  .track-pane[data-track="2"] .divider { margin: 2px 0 4px; }
  .track-pane[data-track="2"] .section-title { margin-bottom: 0; }
  .rsvp-form { gap: 2px; }
  .field { gap: 0px; }
  .field-label { font-size: 10px; }
  .field input[type="text"], .field textarea, .field select { padding: 3px 10px; font-size: 12px; }
  .field textarea { height: 24px; }
  .attend-btn { padding: 3px 8px; font-size: 12px; }
  .step-btn { width: 22px; height: 22px; }
  .submit-btn { padding: 5px; margin-top: 0; font-size: 12.5px; }
}
