/* ============================================================
   Wedding Website — theme system
   Three directions share one navy + white palette:
   [data-direction="classic"]   — timeless, centered, script accents
   [data-direction="editorial"] — high-contrast, caps, big numerals
   [data-direction="romantic"]  — warm ivory, soft, script-forward
   ============================================================ */

:root {
  /* Classic (default) */
  --bg: #FCFCF9;
  --bg-deep: #16243D;
  --on-deep: #F2EFE8;
  --on-deep-soft: #A9B4C8;
  --ink: #1C2C49;
  --ink-soft: #5D6B85;
  --accent: #2E4368;
  --rule: #DCDFE3;
  --card: #FFFFFF;

  --font-serif: 'Cormorant Garamond', Georgia, serif;
  --font-script: 'Parisienne', 'Pinyon Script', cursive;
  --font-body: 'Cormorant Garamond', Georgia, serif;

  --h-case: none;
  --h-weight: 500;
  --h-track: 0.01em;
  --eyebrow-track: 0.32em;
  --img-radius: 10px;
  --btn-radius: 2px;
  --card-radius: 6px;

  --reveal-y: 30px;
  --reveal-dur: 0.95s;
}

html[data-direction="editorial"] {
  --bg: #FFFFFF;
  --ink: #0E1B30;
  --ink-soft: #51607B;
  --accent: #16243D;
  --rule: #E3E5E9;
  --font-body: 'Libre Caslon Text', Georgia, serif;
  --h-case: uppercase;
  --h-weight: 600;
  --h-track: 0.06em;
  --eyebrow-track: 0.42em;
  --img-radius: 0px;
  --btn-radius: 0px;
  --card-radius: 0px;
}

html[data-direction="romantic"] {
  --bg: #FBF7F0;
  --bg-deep: #233754;
  --ink: #31435F;
  --ink-soft: #74819B;
  --accent: #44597C;
  --rule: #E6E0D4;
  --card: #FFFDF9;
  --h-weight: 500;
  --h-track: 0.015em;
  --img-radius: 22px;
  --btn-radius: 999px;
  --card-radius: 16px;
}

/* ---- palette overrides (applied after direction) ---- */
html[data-palette="black"] {
  --bg-deep: #101010;
  --on-deep: #F2F0EC;
  --on-deep-soft: #A8A6A2;
  --ink: #121212;
  --ink-soft: #5E5C59;
  --accent: #1A1A1A;
  --rule: #DEDCD8;
}
html[data-palette="black-navy"] {
  --bg-deep: #101010;
  --on-deep: #F2F0EC;
  --on-deep-soft: #A8A6A2;
  --ink: #121212;
  --ink-soft: #5E5C59;
  --accent: #22375C;
  --rule: #DEDCD8;
}

html[data-motion="subtle"] {
  --reveal-y: 16px;
  --reveal-dur: 0.7s;
}

/* ---------------- base ---------------- */
* { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 19px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
/* grid/flex children must be allowed to shrink or long words overflow */
.chapter > *, .cards > *, .hg-col > *, .sched-what, .ch-text { min-width: 0; }
input, select, textarea, button { max-width: 100%; }
img { display: block; max-width: 100%; }
a { color: inherit; }
p { text-wrap: pretty; }
/* the browser's own [hidden] rule loses to any display: set below (.btn is
   inline-flex, for one), so hiding an element wouldn't actually hide it */
[hidden] { display: none !important; }

.wrap { max-width: 1120px; margin: 0 auto; padding: 0 clamp(18px, 4vw, 28px); }
.narrow { max-width: 720px; margin: 0 auto; }

section[id] { scroll-margin-top: 84px; }

/* ---------------- type helpers ---------------- */
.eyebrow {
  font-family: var(--font-serif);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: var(--eyebrow-track);
  text-transform: uppercase;
  color: var(--ink-soft);
}
.script {
  font-family: var(--font-script);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
}
html[data-direction="editorial"] .script-word { display: none; }

/* Formal copperplate Z (looped descender), only in script contexts */
.hero-names .zfix, .monogram .zfix, .monogram-big .zfix, .head-mono .zfix {
  font-family: 'Pinyon Script', cursive;
  font-size: 1.12em;
}
/* Pinyon's Z carries a wide entry swash — pull it back in the lockups */
.monogram .zfix, .monogram-big .zfix { margin-left: -0.2em; }

.sec-head { text-align: center; margin-bottom: 56px; }
.sec-head .script-word {
  display: block;
  font-family: var(--font-script);
  font-size: clamp(30px, 3.4vw, 44px);
  color: var(--accent);
  opacity: 0.55;
  margin-bottom: 2px;
}
html[data-direction="editorial"] .sec-head .script-word { display: none; }
.sec-title {
  font-family: var(--font-serif);
  font-weight: var(--h-weight);
  text-transform: var(--h-case);
  letter-spacing: var(--h-track);
  font-size: clamp(34px, 4.4vw, 52px);
  line-height: 1.1;
  margin: 8px 0 14px;
}
.sec-sub { color: var(--ink-soft); max-width: 560px; margin: 0 auto; font-size: 19px; }

.orn {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  color: var(--accent); margin: 18px auto;
}
.orn::before, .orn::after { content: ""; width: 64px; height: 1px; background: var(--rule); }
.orn .dia { width: 6px; height: 6px; background: currentColor; transform: rotate(45deg); opacity: 0.7; }
html[data-direction="editorial"] .orn::before,
html[data-direction="editorial"] .orn::after { width: 110px; background: var(--ink); }

/* ---------------- buttons ---------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  font-family: var(--font-serif);
  font-size: 14px; font-weight: 600;
  letter-spacing: 0.22em; text-transform: uppercase;
  padding: 14px 30px;
  border-radius: var(--btn-radius);
  border: 1px solid var(--accent);
  color: var(--accent);
  background: transparent;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.25s ease, color 0.25s ease;
}
.btn:hover { background: var(--accent); color: var(--bg); }
.btn.solid { background: var(--accent); color: var(--bg); }
.btn.solid:hover { background: transparent; color: var(--accent); }
.btn.on-deep { border-color: var(--on-deep); color: var(--on-deep); }
.btn.on-deep:hover { background: var(--on-deep); color: var(--bg-deep); }

/* ---------------- nav ---------------- */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 50;
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px;
  padding: 14px clamp(16px, 3vw, 32px);
  background: color-mix(in oklab, var(--bg) 86%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid color-mix(in oklab, var(--rule) 70%, transparent);
}
.nav .monogram {
  font-family: var(--font-script);
  font-size: 30px; line-height: 1;
  color: var(--accent);
  text-decoration: none;
}
html[data-direction="editorial"] .nav .monogram {
  font-family: var(--font-serif); font-weight: 600; font-size: 19px;
  letter-spacing: 0.3em; text-transform: uppercase;
}
.nav-links { display: flex; align-items: center; gap: clamp(14px, 2vw, 26px); flex-wrap: wrap; justify-content: flex-end; }
.nav-links a {
  font-family: var(--font-serif);
  font-size: 13px; font-weight: 600;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--ink-soft); text-decoration: none;
  transition: color 0.2s ease;
}
.nav-links a:hover { color: var(--accent); }
.nav-links .btn { padding: 9px 20px; font-size: 12px; }
.nav-links .btn.solid {
  background: #4C5A44;
  border-color: #4C5A44;
  color: #F3F1E9;
}
.nav-links .btn.solid:hover { background: transparent; color: #4C5A44; }
@media (max-width: 980px) { .nav-links a:not(.btn) { display: none; } }

/* ---------------- hero ---------------- */
.hero {
  min-height: 100svh;
  display: grid; place-items: center;
  align-content: center;
  text-align: center;
  position: relative;
  padding: 110px 24px 72px;
  overflow: hidden;
}
.hero-inner { position: relative; display: grid; justify-items: center; gap: 10px; }
.hero-names {
  font-family: var(--font-script);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0.005em;
  font-size: clamp(40px, 9.4vw, 122px);
  line-height: 1.12;
  margin: 6px 0 0;
  overflow-wrap: break-word;
}
.hero-names .amp {
  font-family: var(--font-script);
  font-size: 0.9em;
  text-transform: none;
  color: var(--accent);
  padding: 0 0.06em;
}
html[data-direction="editorial"] .hero-names .amp {
  font-family: var(--font-script); font-style: normal; font-weight: 400; font-size: 0.9em;
}
.hero-meta {
  display: flex; align-items: center; justify-content: center; gap: 18px;
  flex-wrap: wrap;
  font-family: var(--font-serif);
  font-size: 15px; font-weight: 600;
  letter-spacing: 0.28em; text-transform: uppercase;
  color: var(--ink-soft);
}
.hero-meta .dot { width: 5px; height: 5px; background: var(--accent); transform: rotate(45deg); opacity: 0.6; }
.hero-photo { margin-top: 34px; }
.hero-photo image-slot {
  width: min(400px, 78vw); height: min(500px, 96vw);
}
.hero-gallery {
  margin-top: 40px;
  width: min(1080px, 92vw);
  display: grid;
  grid-template-columns: 1.08fr 0.86fr 1fr;
  gap: 16px;
  align-items: start;
}
.hg-col { display: grid; gap: 16px; }
.hg-drop { margin-top: 34px; }
.hero-gallery image-slot { width: 100%; display: block; }
.hg-tall image-slot { height: 300px; }
.hg-short image-slot { height: 186px; }
.hg-mid image-slot { height: 244px; }
.hg-midb image-slot { height: 226px; }
/* below the desktop stagger the three columns wrap into a ragged two-column
   grid with the last one orphaned, so flatten the collage into a deliberate
   arrangement instead — a pair, the panorama across, a pair */
@media (max-width: 900px) {
  .hero-gallery { grid-template-columns: 1fr 1fr; gap: 14px; }
  .hg-col { display: contents; }
  .hg-1 { order: 1; }
  .hg-4 { order: 2; }
  .hg-2 { order: 3; grid-column: 1 / -1; }
  .hg-5 { order: 4; }
  .hg-6 { order: 5; }
  /* the b&w window shot is a wide landscape — at half this width the couple is
     a speck on an empty lawn */
  .hg-3 { display: none; }
  /* cells are shaped to each photo's own crop so cover only ever trims the
     generous side margins, never the top and bottom where heads and feet sit */
  .hg-1 image-slot, .hg-4 image-slot { height: auto; aspect-ratio: 1.02; }
  .hg-2 image-slot { height: auto; aspect-ratio: 2.07; } /* the file's native ratio — uncropped */
  .hg-5 image-slot, .hg-6 image-slot { height: auto; aspect-ratio: 1.55; }
}
@media (max-width: 600px) {
  /* start-aligned so the names always sit just under the nav rather than
     drifting with the viewport height */
  .hero { padding: 96px 20px 56px; align-content: start; }
  .hero-names { font-size: clamp(40px, 11.5vw, 58px); }
  /* the inline dot leaves a dangling diamond once the line wraps */
  .hero-meta { flex-direction: column; gap: 9px; font-size: 12px; letter-spacing: 0.24em; }
  .hero-meta .dot { display: none; }

  .hero-gallery { gap: 10px; margin-top: 26px; }
  /* narrower cells at phone width: the couple stays large enough to read */
  .hg-1 image-slot, .hg-4 image-slot { aspect-ratio: 0.86; }
  .hg-5 image-slot, .hg-6 image-slot { aspect-ratio: 1.25; }
  /* a 12px inset reads as a heavy second frame on a 170px cell */
  .hero-gallery .framed::after { inset: 8px; border-radius: calc(var(--img-radius) - 4px); }

  /* scoped so it beats the base .scroll-cue rule that follows this block */
  .hero .scroll-cue { margin-top: 34px; }
}
.scroll-cue {
  position: static;
  margin-top: 54px;
  display: grid; justify-items: center; gap: 8px;
  color: var(--ink-soft);
  font-family: var(--font-serif); font-size: clamp(20px, 2vw, 26px); font-weight: 600;
  letter-spacing: 0.34em; text-transform: uppercase;
}
.scroll-cue::after {
  content: ""; width: 1px; height: 44px;
  background: linear-gradient(var(--ink-soft), transparent);
}
/* with JS the static stub is replaced by the scroll-driven .cue-drop line */
.js-cue .scroll-cue::after { display: none; }
.cue-drop {
  position: absolute;
  left: 50%;
  width: 1px;
  height: 0;
  margin-left: -0.5px;
  background: linear-gradient(to bottom, transparent, var(--ink-soft) 18%, var(--ink-soft));
  opacity: 0.5;
  pointer-events: none;
  z-index: 1;
}

/* image-slot framing per direction */
image-slot { border-radius: var(--img-radius); overflow: hidden; }
.framed { position: relative; }
.framed::after {
  content: ""; position: absolute; inset: 12px;
  border: 1px solid color-mix(in oklab, var(--bg) 70%, transparent);
  border-radius: calc(var(--img-radius) - 6px);
  pointer-events: none;
}
html[data-direction="romantic"] .hero-photo image-slot,
html[data-direction="romantic"] .ch-photo image-slot {
  border-radius: 200px 200px var(--img-radius) var(--img-radius);
}
html[data-direction="romantic"] .hero-photo .framed::after,
html[data-direction="romantic"] .ch-photo .framed::after {
  border-radius: 190px 190px calc(var(--img-radius) - 6px) calc(var(--img-radius) - 6px);
}

/* ---------------- sections ---------------- */
.section { padding: 110px 0; }
.section.deep {
  background: var(--bg-deep);
  color: var(--on-deep);
}
/* earthy hydrangea green */
.section.deep-green,
html.rsvp-off .footer,
html:not(.rsvp-off) .dog-band {
  --bg-deep: #4C5A44;
  --on-deep: #F3F1E9;
  --on-deep-soft: #C2C7B4;
}
.section.deep .eyebrow { color: var(--on-deep-soft); }
.section.deep .sec-sub { color: var(--on-deep-soft); }
.section.deep .orn { color: var(--on-deep); }
.section.deep .orn::before, .section.deep .orn::after { background: color-mix(in oklab, var(--on-deep) 30%, transparent); }
.section.deep .sec-head .script-word { color: var(--on-deep); opacity: 0.5; }

/* ---------------- story timeline ---------------- */
.timeline { position: relative; margin-top: 30px; }
.tl-track, .tl-progress {
  position: absolute; top: 0; left: 50%;
  width: 1px; transform: translateX(-0.5px);
}
.tl-track { height: 100%; background: var(--rule); }
.tl-progress { height: 0; background: var(--accent); transition: height 0.15s linear; }

.chapter {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 90px 1fr;
  align-items: center;
  padding: 56px 0;
}
.ch-node {
  grid-column: 2;
  justify-self: center; align-self: center;
  width: 12px; height: 12px;
  background: var(--bg);
  border: 1px solid var(--accent);
  border-radius: 50%;
  transition: background 0.5s ease;
  position: relative; z-index: 2;
}
.chapter.in .ch-node { background: var(--accent); }

.ch-text { grid-column: 3; grid-row: 1; padding-left: 8px; }
.ch-photo { grid-column: 1; grid-row: 1; padding-right: 8px; }
.chapter:nth-child(even) .ch-text { grid-column: 1; text-align: right; padding-left: 0; padding-right: 8px; }
.chapter:nth-child(even) .ch-photo { grid-column: 3; padding-right: 0; padding-left: 8px; }

.ch-photo image-slot {
  display: block;
  width: 100%;
  max-width: 420px;
  height: 300px;
  margin-left: auto;
}
.chapter:nth-child(even) .ch-photo image-slot { margin-left: 0; margin-right: auto; }

.ch-label {
  font-family: var(--font-serif);
  font-size: 13px; font-weight: 600;
  letter-spacing: 0.3em; text-transform: uppercase;
  color: var(--ink-soft);
}
.ch-title {
  font-family: var(--font-serif);
  font-weight: var(--h-weight);
  text-transform: var(--h-case);
  letter-spacing: var(--h-track);
  font-size: clamp(28px, 3vw, 38px);
  line-height: 1.12;
  margin: 10px 0 12px;
}
.ch-place {
  font-family: var(--font-script);
  font-size: 26px;
  color: var(--accent);
  opacity: 0.7;
  margin-bottom: 6px;
}
html[data-direction="editorial"] .ch-place {
  font-family: var(--font-serif); font-style: italic; font-size: 18px;
  letter-spacing: 0.04em; opacity: 0.85;
}
/* place name writes itself on, letter by letter, once reached */
.ch-place .write { display: inline-block; }
.wch {
  display: inline-block;
  /* Padding (cancelled by equal negative margin) enlarges the box
     clip-path acts on, so ascenders, descenders and script swashes
     always sit INSIDE the clip region. Layout is unchanged. */
  padding: 0.85em 0.5em;
  margin: -0.85em -0.5em;
  clip-path: inset(0 100% 0 0);
}
/* keyed to the line's own visibility (js adds .writing), not an ancestor's
   reveal — the chapter counts as revealed long before this line is read */
.writing .wch {
  clip-path: inset(0 0 0 0);
  transition: clip-path 0.22s linear;
  transition-delay: calc(var(--i) * 0.105s + 0.2s);
}
@media (prefers-reduced-motion: reduce) {
  .wch { clip-path: none; }
}

.ch-copy { color: var(--ink-soft); font-size: 18px; max-width: 46ch; }
.chapter:nth-child(even) .ch-copy { margin-left: auto; }

.ch-no {
  display: none;
  position: absolute; top: 16px;
  font-family: var(--font-serif); font-weight: 600;
  font-size: 120px; line-height: 1;
  color: var(--ink);
  opacity: 0.06;
  pointer-events: none;
}
.chapter:nth-child(odd) .ch-no { right: 0; }
.chapter:nth-child(even) .ch-no { left: 0; }
html[data-direction="editorial"] .ch-no { display: block; }

.lore {
  margin-top: 18px;
  padding: 16px 20px;
  border: 1px solid var(--rule);
  border-radius: var(--card-radius);
  background: var(--card);
  font-size: 16.5px;
  color: var(--ink-soft);
  max-width: 46ch;
}
.lore strong {
  display: block;
  font-size: 12px; letter-spacing: 0.26em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 6px;
}
.chapter:nth-child(even) .lore { margin-left: auto; }

/* full-circle interlude */
.full-circle { text-align: center; padding: 120px 28px; }
.full-circle .script-word {
  font-family: var(--font-script);
  font-size: clamp(40px, 5vw, 64px);
  color: var(--on-deep);
  display: block; margin-bottom: 10px;
}
html[data-direction="editorial"] .full-circle .script-word {
  font-family: var(--font-serif); font-style: italic; text-transform: none;
  font-size: clamp(34px, 4vw, 52px);
}
.full-circle p {
  color: var(--on-deep-soft);
  max-width: 54ch; margin: 16px auto 0;
  font-size: 20px;
}

/* ---------------- details cards ---------------- */
.cards {
  display: grid; gap: 26px;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
}
.card {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--card-radius);
  padding: 38px 34px;
  text-align: center;
}
.card .eyebrow { color: var(--accent); }
.card h3 {
  font-family: var(--font-serif);
  font-weight: var(--h-weight);
  text-transform: var(--h-case);
  letter-spacing: var(--h-track);
  font-size: 28px; margin: 12px 0 6px;
}
.card .when { font-style: italic; color: var(--ink-soft); margin: 0 0 14px; }
.card p { color: var(--ink-soft); font-size: 17.5px; margin: 8px 0; }
.card .addr { font-size: 16px; letter-spacing: 0.04em; }

/* ---------------- schedule ---------------- */
.sched { max-width: 640px; margin: 0 auto; display: grid; }
.sched-row {
  display: grid; grid-template-columns: 130px 1fr;
  gap: 26px; align-items: baseline;
  padding: 22px 6px;
  border-bottom: 1px solid var(--rule);
}
.sched-row:last-child { border-bottom: none; }
.sched-time {
  font-family: var(--font-serif);
  font-weight: 600; font-size: 15px;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--accent);
  text-align: right;
}
.sched-what h4 {
  font-family: var(--font-serif);
  font-weight: var(--h-weight);
  text-transform: var(--h-case);
  font-size: 23px; margin: 0 0 2px;
}
.sched-what p { margin: 0; color: var(--ink-soft); font-size: 17px; }

/* ---------------- gallery ---------------- */
.gallery {
  display: grid; gap: 18px;
  /* six photos as two rows of three */
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
/* cells match the phone-photo aspect, so cover never crops a face off */
.gallery image-slot { width: 100%; height: auto; aspect-ratio: 3 / 4; }
.gallery .tall image-slot { height: 100%; min-height: 290px; }
@media (max-width: 760px) {
  .gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* on a phone six identical 3:4 cells read as an endless one-per-row scroll, so
   flatten into a cluster like the hero collage — a pair, a wide, a pair, a wide.
   every ratio below is checked against its own photo: cover only ever trims sky,
   sand, tabletop or side margin, never a face */
@media (max-width: 600px) {
  .gallery { grid-template-columns: 1fr 1fr; gap: 12px; }
  /* the paired cells hold date-night (native 0.911) and dinner, whose faces sit
     in the top half — squaring them off costs nothing */
  .gallery image-slot { aspect-ratio: 0.92; }
  /* vogue and paris keep their native 3:4: in both the couple sits low in the
     frame, so any wider cell would clip a chin */
  .gallery .g-tall { aspect-ratio: 3 / 4; }
  /* the two landscape-friendly frames — beach-show and lake-sunset both centre
     their faces, and a wide crop is what those views wanted anyway */
  .gallery .g-wide { grid-column: 1 / -1; aspect-ratio: 1.45; }
}

/* ---------------- travel ---------------- */
.travel-cards .card { text-align: left; }
.travel-cards .card h3 { font-size: 24px; }
.travel-cards .card h3 a { text-decoration: none; border-bottom: 1px solid var(--rule); }
.travel-cards .card h3 a:hover { border-bottom-color: var(--accent); }

/* ---------------- registry ---------------- */
.registry-actions { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; margin-top: 30px; }
/* registry: zola centered on its own row, the other two beneath it */
#registry .registry-actions {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, auto));
  justify-content: center;
  row-gap: 16px;
}
#registry .registry-actions .btn:first-child { grid-column: 1 / -1; justify-self: center; }
@media (max-width: 520px) {
  #registry .registry-actions { grid-template-columns: minmax(0, auto); }
  #registry .registry-actions .btn:first-child { grid-column: auto; }
}

/* ---------------- faq ---------------- */
.faq-list { max-width: 720px; margin: 0 auto; }
.faq-list details { border-bottom: 1px solid var(--rule); }
.faq-list summary {
  cursor: pointer;
  list-style: none;
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
  padding: 22px 4px;
  font-family: var(--font-serif);
  font-weight: 600; font-size: 21px;
}
.faq-list summary::-webkit-details-marker { display: none; }
.faq-list summary::after {
  content: "+";
  font-size: 24px; font-weight: 400; color: var(--accent);
  transition: transform 0.25s ease;
}
.faq-list details[open] summary::after { transform: rotate(45deg); }
.faq-list .a { padding: 0 4px 24px; color: var(--ink-soft); font-size: 18px; max-width: 60ch; }

/* ---------------- rsvp ---------------- */
/* ---- FITTING THE WHOLE BAND ON ONE SCREEN ----
   The nav's RSVP link scrolls here, and the dog at the bottom is the payoff —
   land someone at the top of the section and he should already be on screen,
   not two scrolls further down. So this section's vertical rhythm is measured
   against the viewport instead of being fixed: full and airy on a large
   display, tighter on a laptop, and never collapsed, since the floors in these
   clamps are the smallest values that still read as deliberate spacing.
   Everything is scoped to #rsvp — .section's own padding is shared with Story,
   Details, Gallery and the rest, which have no reason to move. */
#rsvp {
  /* the section's own top padding clears the fixed nav, so the usual 84px of
     scroll offset would only park the previous section's cream on screen */
  scroll-margin-top: 0;
  padding-top: clamp(92px, 12vh, 130px);
  /* just enough green under the card for the dog to pass without grazing it —
     the dog band supplies the rest of the gap down to the footer */
  padding-bottom: clamp(18px, 2.6vh, 40px);
}
#rsvp .sec-head { margin-bottom: clamp(22px, 3.6vh, 48px); }
#rsvp .head-sprig svg {
  width: auto;
  height: clamp(46px, 7vh, 84px);
  aspect-ratio: 64 / 84;
}
/* the body's 1.65 leading is pure slack under a 44px display face */
#rsvp .sec-head .script-word { line-height: 1.15; }
#rsvp .sec-title { margin: 6px 0 10px; }
#rsvp .rsvp-card { padding-block: clamp(28px, 4.2vh, 48px); }
/* Below this the sprig can't earn its ~70px: it is the one element here that
   carries no meaning, so it steps aside rather than pushing the dog off the
   bottom of a laptop screen. */
@media (max-height: 859px) {
  #rsvp .head-sprig { display: none; }
}

.rsvp-card {
  background: var(--card);
  color: var(--ink);
  border-radius: var(--card-radius);
  max-width: 620px; margin: 0 auto;
  padding: 48px 46px;
}
.rsvp-card .field { display: grid; gap: 8px; margin-bottom: 26px; }
.rsvp-card label {
  font-family: var(--font-serif);
  font-size: 12.5px; font-weight: 600;
  letter-spacing: 0.24em; text-transform: uppercase;
  color: var(--ink-soft);
}
.rsvp-card input[type="text"], .rsvp-card select {
  font-family: var(--font-body);
  font-size: 18px;
  color: var(--ink);
  background: transparent;
  border: none; border-bottom: 1px solid var(--rule);
  padding: 8px 2px;
  outline: none;
}
.rsvp-card input:focus, .rsvp-card select:focus { border-bottom-color: var(--accent); }
.choice-row { display: flex; gap: 12px; flex-wrap: wrap; }
.choice-row label {
  display: inline-flex; align-items: center; gap: 8px;
  border: 1px solid var(--rule);
  border-radius: var(--btn-radius);
  padding: 10px 18px;
  cursor: pointer;
  font-size: 13px;
  transition: border-color 0.2s ease, background 0.2s ease;
}
.choice-row input { accent-color: var(--accent); }
.choice-row label:has(input:checked) {
  border-color: var(--accent);
  background: color-mix(in oklab, var(--accent) 7%, transparent);
}
.rsvp-note { font-size: 15px; color: var(--ink-soft); font-style: italic; margin-top: 18px; text-align: center; }
.rsvp-found { text-align: center; margin-bottom: 26px; }
.rsvp-party-name { font-family: var(--font-serif); font-weight: var(--h-weight); font-size: 28px; margin: 8px 0 6px; }
.rsvp-hint { color: var(--ink-soft); font-size: 16.5px; margin: 0; }
.rsvp-choose { display: grid; gap: 10px; }
.rsvp-member { border-top: 1px solid var(--rule); padding: 22px 0 6px; }
.rsvp-member-name { font-family: var(--font-serif); font-size: 22px; margin-bottom: 12px; }
.rsvp-event { display: flex; align-items: center; justify-content: space-between; gap: 18px; flex-wrap: wrap; padding: 10px 0; }
.rsvp-event-label { font-family: var(--font-serif); font-size: 17px; font-weight: 600; }
.rsvp-event-detail { color: var(--ink-soft); font-size: 14.5px; }
.rsvp-thanks { text-align: center; padding: 24px 0; }
.rsvp-thanks-script { font-family: var(--font-script); font-size: 46px; color: var(--accent); line-height: 1.1; }
.rsvp-thanks p { color: var(--ink-soft); margin-top: 10px; }

/* ---------------- footer ---------------- */
.footer {
  background: var(--bg);
  color: var(--ink-soft);
  border-top: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  text-align: center;
  padding: 22px 24px;
}
.footer .monogram-big {
  font-family: var(--font-script);
  font-size: 26px; color: var(--ink); line-height: 1;
}
html[data-direction="editorial"] .footer .monogram-big {
  font-family: var(--font-serif); font-weight: 600; font-size: 16px;
  letter-spacing: 0.4em; text-transform: uppercase;
}
.footer .fdate {
  font-family: var(--font-serif);
  font-size: 11px; font-weight: 600;
  letter-spacing: 0.26em; text-transform: uppercase;
}
.footer .fnote { font-size: 15px; font-style: italic; margin-top: 22px; opacity: 0.8; }

/* The RSVP band is the page's only green. With RSVPs closed it isn't
   rendered, so the footer carries the colour instead of the page fading out
   in cream. */
html.rsvp-off .footer {
  background: var(--bg-deep);
  color: var(--on-deep-soft);
  border-top-color: transparent;
}
html.rsvp-off .footer .monogram-big { color: var(--on-deep); }
/* With the section up, the green runs all the way down into the footer (see the
   dog band below), so the colour change is the boundary and the rule on top of
   it would only read as a stray light hairline. */
html:not(.rsvp-off) .footer { border-top-color: transparent; }

/* ---------------- prancing dog ---------------- */
/* A line-art retriever trotting on the spot, from a Kling clip.
   
   This ships as a PRE-RENDERED SPRITE SHEET, not a <video>, because the video
   was the source of three mobile-only defects in a row:

   - A visible box. The clip's background is flat cream (#ECEDEA, sampled), not
     transparency, so it needed mix-blend-mode: multiply to disappear into the
     page. That blend is silently dropped on mobile — the video gets promoted to
     its own compositing layer — leaving the clip's background as an opaque
     rectangle. Measured on the reported handset: page rgb(252,252,250), the box
     rgb(254,255,249). Small numbers, but a hard edge across a flat area is very
     easy to see.
   - A horizon line travelling with the dog, at y 326-328 of the frame.
   - A freeze mid-pass. The `loop` attribute re-seeks and re-primes the decoder
     at every seam: instrumenting it showed one seeking/waiting/seeked/playing
     burst per 2.7s loop, with readyState dropping to 1. On desktop that gap is
     under ~150ms and invisible; on a phone the re-prime is slow enough to read
     as the dog freezing mid-stride and then picking up again.

   Baking the frames out solves all three at once. The sheet carries real alpha,
   so there is no background to hide and no blend mode to depend on; the ball,
   the watermark and the horizon line are gone at prep time rather than masked
   live; and a steps() animation has no decoder, so it cannot stall or drift.
   There is no JavaScript involved at all.

   Prepared by tools/dog-sprite/ (see the README there to rebuild):
     27 frames, 457x265 each, on a 3x9 grid -> 1371x2385, 501 KB
     10fps x 27 = 2.700s, exactly one gait cycle, so the loop is seamless
     ball erased at x>=503 for y>=157 (its column; measured empty 500..506)
     cropped to y<=325, dropping the horizon line, watermark and letterbox */
/* ---- TIMING: the one place to tune how often he shows up ----
   One cycle is two passes and two waits, so the wait length alternates and the
   entrances don't land on a metronome:

     0%   ->  20%    pass 1, left to right, facing right      9.0s
     20%  ->  44.4%  waiting off-screen right                11.0s
     44.4%->  64.4%  pass 2, right to left, facing left       9.0s
     64.4%-> 100%    waiting off-screen left                  16.0s

   --dog-cycle scales the whole timeline (all four phases together). To change
   only the waits, edit the two keyframe stops in dog-cross and the matching
   stops in dog-face — the four numbers above are the percentages to move. */
.dog-band {
  --dogw: clamp(160px, 20vw, 280px);   /* one sprite frame = the whole dog */
  --dog-ink: #000;                     /* the sheet is black; see .dog-sheet */
  --dog-cycle: 45s;                    /* traversal timeline, see above */
  --dog-gait: 2.7s;                    /* one stride: 27 frames at 10fps */
  position: relative;
  overflow: hidden;
  /* 265/457 is the frame's aspect ratio, and the ink reaches within 2px of the
     frame's top edge — the 1px is headroom so sub-pixel rounding in that crop
     can never shave the top of his head. */
  height: calc(var(--dogw) * 0.58 + 1px);
  margin-top: -30px;
}
/* ---- ON THE GREEN, while RSVPs are open ----
   He used to trot in the cream strip left between the green and the footer,
   which put his head and back above the boundary — and black ink on the green
   is near enough invisible that it read as his head being cut off. So with the
   section up the band becomes the bottom of the green instead: same colour, no
   rule between them, so the green runs unbroken from the form into the footer
   and he walks the line where it ends. Cream ink, and smaller, because he is
   now inside the band rather than a break after it. */
html:not(.rsvp-off) .dog-band {
  /* the vh term only bites on a short screen, where his own height is the last
     thing standing between the band and fitting on one — a slightly smaller dog
     that gets seen beats a bigger one below the fold */
  --dogw: clamp(136px, min(14vw, 22vh), 200px);
  --dog-ink: var(--on-deep);
  background: var(--bg-deep);
  margin-top: 0;
}
/* Traversal only. Nothing here blends or filters — the sheet's alpha does the
   compositing, so the dog sits on the page with no rectangle to hide. */
.dog-film {
  position: absolute;
  left: 0;
  bottom: 0;
  width: var(--dogw);
  pointer-events: none;
  transform: translateX(calc(var(--dogw) * -1.02));
}
/* The frame window, and the mirror. overflow: hidden crops to one frame in the
   element's own coordinates, and scaleX(-1) then flips that result — so the flip
   mirrors the dog without disturbing which frame is selected, and it is a level
   in from the traversal so it can't invert the direction of travel either. */
.dog-win {
  position: relative;
  overflow: hidden;
  width: 100%;
  aspect-ratio: 457 / 265;
}
/* Two nested strips walk the grid, one axis each, because a single element can
   only carry one transform animation. Each is sized to the whole sheet in its
   own axis, so a full -100% sweep is exactly one pass over every frame. */
.dog-rows { width: 100%; height: 900%; }        /* 9 rows */
/* The sheet is pure black — every stroke, and all of its antialiasing, lives in
   the alpha channel (L is 0 everywhere, alpha runs 0-224). So it works as its
   own mask: paint a flat colour and let the sheet cut the drawing out of it, and
   each state picks its own ink from --dog-ink with nothing lost in the process.
   A filter couldn't do that — it would give whatever inverting black yields
   rather than the cream the rest of the band is already using. */
.dog-sheet {
  display: block;
  width: 300%;                                  /* 3 columns */
  height: 100%;
}
/* Without masking the paint would be a bare rectangle sliding across the page,
   so it only turns on where the mask does. */
@supports (mask-image: url("#m")) or (-webkit-mask-image: url("#m")) {
  .dog-sheet {
    background-color: var(--dog-ink);
    -webkit-mask-image: url(/assets/img/dog-sprite.png);
            mask-image: url(/assets/img/dog-sprite.png);
    /* the element is already sized to the whole grid, so one copy fills it
       exactly — left to default the mask would tile, the same trap the img's
       max-width: none used to sidestep */
    -webkit-mask-size: 100% 100%;
            mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    mask-mode: alpha;
  }
  /* Light ink on a dark ground reads thinner than dark ink on light, and the
     alpha only peaks at 224/255 before the browser thins it further scaling
     457px of frame down to ~180. Stacking the mask on itself composites its
     alpha with itself (a + b - ab), which recovers the stroke weight without
     touching the drawing or making him bigger: on the green the outline goes
     from 3.5:1 against the background to 5.0:1. */
  html:not(.rsvp-off) .dog-sheet {
    -webkit-mask-image: url(/assets/img/dog-sprite.png), url(/assets/img/dog-sprite.png);
            mask-image: url(/assets/img/dog-sprite.png), url(/assets/img/dog-sprite.png);
  }
}
@media (prefers-reduced-motion: no-preference) {
  .dog-film { animation: dog-cross var(--dog-cycle) linear infinite; }
  /* step-end so the facing snaps rather than interpolating through scaleX(0),
     which would read as the dog folding in half */
  .dog-win { animation: dog-face var(--dog-cycle) step-end infinite; }
  /* A row lasts three frames, the whole sheet twenty-seven. Both durations are
     exact multiples of the frame time, so the two axes stay locked forever. */
  .dog-sheet { animation: dog-col calc(var(--dog-gait) / 9) steps(3) infinite; }
  .dog-rows  { animation: dog-row var(--dog-gait) steps(9) infinite; }
}
@keyframes dog-col { to { transform: translateX(-100%); } }
@keyframes dog-row { to { transform: translateY(-100%); } }
/* Held stops at both ends are the waits; the crossings stay linear because a
   trot shouldn't ease in or out. */
@keyframes dog-cross {
  0%              { transform: translateX(calc(var(--dogw) * -1.02)); }
  20%,  44.4444%  { transform: translateX(calc(100vw + var(--dogw) * 0.02)); }
  64.4444%, 100%  { transform: translateX(calc(var(--dogw) * -1.02)); }
}
/* Each flip happens mid-wait, while he's off-screen. */
@keyframes dog-face {
  0%   { transform: scaleX(1); }
  32%  { transform: scaleX(-1); }   /* 14.4s — inside the 9–20s wait */
  82%  { transform: scaleX(1); }    /* 36.9s — inside the 29–45s wait */
}
/* Narrower viewports cross less ground, so shorten the cycle to keep the
   apparent stride length (and therefore the gait) about the same. */
@media (max-width: 700px) {
  .dog-band { --dog-cycle: 33s; }
}
/* Reduced motion: no traversal, no flip, no gait. Frame 0 is a mid-stride pose,
   so what's left is a static sketch rather than a blank gap. */
@media (prefers-reduced-motion: reduce) {
  .dog-film { transform: translateX(calc(50vw - var(--dogw) * 0.5)); }
  .dog-win, .dog-rows, .dog-sheet { transform: none; animation: none; }
}

/* ---------------- reveals ---------------- */
@media (prefers-reduced-motion: no-preference) {
  .reveal {
    opacity: 0;
    transform: translateY(var(--reveal-y));
    transition: opacity var(--reveal-dur) cubic-bezier(0.2, 0.6, 0.2, 1),
                transform var(--reveal-dur) cubic-bezier(0.2, 0.6, 0.2, 1);
  }
  .reveal.in { opacity: 1; transform: none; }
  .reveal.d1 { transition-delay: 0.12s; }
  .reveal.d2 { transition-delay: 0.24s; }
}

/* ---------------- responsive timeline ---------------- */
@media (max-width: 860px) {
  .section { padding: 80px 0; }
  /* line runs through the middle of the 40px node column so the node
     and its bloom stay centered on it */
  .tl-track, .tl-progress { left: 20px; }
  .chapter { grid-template-columns: 40px 1fr; padding: 40px 0; }
  .ch-node { grid-column: 1; align-self: start; margin-top: 8px; }
  .ch-text, .chapter:nth-child(even) .ch-text {
    grid-column: 2; grid-row: 1; text-align: left; padding: 0;
  }
  .ch-photo, .chapter:nth-child(even) .ch-photo {
    grid-column: 2; grid-row: 2; padding: 18px 0 0;
  }
  .ch-photo image-slot, .chapter:nth-child(even) .ch-photo image-slot { margin: 0; }
  .chapter:nth-child(even) .ch-copy, .chapter:nth-child(even) .lore { margin-left: 0; }
  .ch-no { display: none !important; }
  .rsvp-card { padding: 36px 26px; }
  .sched-row { grid-template-columns: 92px 1fr; gap: 16px; }
}

/* ============================================================
   ANIMATED GRAPHICS — botanical line art, blooms, petals
   ============================================================ */

/* shared line-art styling */
.la { color: var(--accent); }
.la path, .la line, .la polyline, .la circle, .la ellipse, .la rect {
  stroke: currentColor;
  fill: none;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.section.deep .la { color: var(--on-deep); }
.la .pf { fill: currentColor; fill-opacity: 0.08; }
/* opaque petals: front shapes occlude the ones behind them */
.la .op { fill: var(--bg); fill-opacity: 0; }
.section.deep .la .op { fill: var(--bg-deep); }
@media (prefers-reduced-motion: reduce) { .la .op { fill-opacity: 1; } }
.la .bg { fill: var(--bg); stroke: none; }
.ch-graphic .la path, .ch-graphic .la rect { stroke-width: 1.45; }

/* Draw-on strokes are driven entirely from JS (js/wedding-graphics.js).
   Hide each drawing until JS has set its dash state, otherwise the
   round line-caps flash as dots on first paint. */
.trigger-draw:not(.ready) svg, .scroll-draw:not(.ready) svg { visibility: hidden; }
/* static (non-animated) drawings render complete */
.static-draw .op { fill-opacity: 1; }

/* spring pop-in (gated on .in of an ancestor) */
@media (prefers-reduced-motion: no-preference) {
  .pop {
    opacity: 0;
    transform: scale(0.3) rotate(-10deg);
    transform-origin: center bottom;
    transition: opacity 0.5s ease, transform 0.75s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  .in .pop, .pop.in { opacity: 1; transform: none; }
  .in .pop.pd1, .pop.in.pd1 { transition-delay: 0.18s; }
  .in .pop.pd2, .pop.in.pd2 { transition-delay: 0.34s; }
}

/* chapter themed graphic */
.ch-graphic {
  width: 76px; height: 76px;
  margin-bottom: 14px;
}
.chapter:nth-child(even) .ch-graphic { margin-left: auto; }
@media (max-width: 860px) {
  .chapter:nth-child(even) .ch-graphic { margin-left: 0; }
  .ch-graphic { width: 64px; height: 64px; }
}

/* section-head floral sprig divider */
.head-sprig {
  display: flex; justify-content: center;
  margin-bottom: 8px;
}
.head-sprig svg { width: 64px; height: 84px; }

/* timeline node marker: a circle that squares off into a diamond */
.ch-node { overflow: visible; }
.ch-bloom {
  position: absolute; left: 50%; top: 50%;
  width: 34px; height: 34px;
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.ch-bloom span {
  position: absolute; top: 50%; left: 50%;
  /* Rotation is constant so nothing appears to spin. The vertical stretch is
     applied after the rotation (rightmost function runs first), which turns
     the rotated square into an upright elongated diamond rather than a
     square standing on its corner. Every edge sits at 45deg, so the scale
     hits all four identically and the outline stays even. */
  transform: rotate(45deg);
  border: 1px solid var(--accent);
  opacity: 0;
}
.ch-bloom .dia-outer {
  width: 21px; height: 21px;
  margin: -10.5px 0 0 -10.5px;
  background: var(--bg);
  border-radius: 50%;
  transition: opacity 0.35s ease,
              border-radius 0.7s cubic-bezier(0.4, 0, 0.2, 1) 0.3s,
              transform 0.7s cubic-bezier(0.4, 0, 0.2, 1) 0.3s;
}
.ch-bloom .dia-inner {
  width: 9px; height: 9px;
  margin: -4.5px 0 0 -4.5px;
  border-radius: 1px;
  transform: scaleY(1.38) rotate(45deg);
  transition: opacity 0.5s ease 1s;
}
/* circle fades in, stretches into the diamond, then the inner one appears */
.ch-node.formed .dia-outer {
  opacity: 1;
  border-radius: 2px;
  transform: scaleY(1.38) rotate(45deg);
}
.ch-node.formed .dia-inner { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .ch-bloom .dia-outer, .ch-bloom .dia-inner { transition: none; }
}

/* hero drifting spring petals — fixed layer, falls page-wide */
.petal-field {
  position: fixed; inset: 0;
  overflow: hidden; pointer-events: none; z-index: 3;
}
.petal-wrap {
  position: absolute; top: -10%;
  will-change: transform;
  animation: petalFall var(--fall, 18s) linear infinite;
  animation-delay: var(--delay, 0s);
}
.petal {
  width: var(--pw, 15px); height: auto;
  color: var(--accent);
  opacity: var(--po, 0.16);
  animation: petalSway var(--sway, 5s) ease-in-out infinite alternate;
}
html[data-direction="romantic"] .petal { opacity: calc(var(--po, 0.16) + 0.08); }
@keyframes petalFall {
  0%   { transform: translateY(0) rotate(0deg); }
  100% { transform: translateY(116vh) rotate(200deg); }
}
@keyframes petalSway {
  0%   { transform: translateX(-12px) rotate(-14deg); }
  100% { transform: translateX(12px) rotate(14deg); }
}
@media (prefers-reduced-motion: reduce) {
  .petal-field { display: none; }
}

/* parallax target gets a transform from JS; keep it smooth */
.ch-photo image-slot { will-change: transform; }

/* chapter photos: slot matches each source image's aspect so cover never crops */
.ch-photo image-slot#story-cathedral,
.ch-photo image-slot#story-iu {
  height: auto;
  aspect-ratio: 4 / 3;
}
.ch-photo image-slot#story-cabin,
.ch-photo image-slot#story-indy {
  height: auto;
  aspect-ratio: 3 / 4;
  max-width: 360px;
}
.ch-photo image-slot#story-malibu {
  height: auto;
  aspect-ratio: 2 / 3;
  max-width: 340px;
}

/* ---------------- lightbox ---------------- */
/* js/lightbox.js promotes every filled slot to role=button */
image-slot[role="button"] { cursor: zoom-in; }
image-slot[role="button"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
/* the dialog covers the viewport opaquely, so the page behind only needs to
   stop scrolling under the swipe/wheel */
html.lb-open { overflow: hidden; }

.lightbox {
  /* the stage insets are also the photo's max box: top clears the close button,
     bottom clears the control bar */
  --lb-top: clamp(62px, 7vw, 76px);
  --lb-side: clamp(16px, 6vw, 30px);
  --lb-bottom: clamp(78px, 12vh, 104px);
  position: fixed; inset: 0;
  width: 100vw; height: 100dvh;
  max-width: none; max-height: none;
  margin: 0; padding: 0; border: 0;
  background: color-mix(in srgb, var(--bg-deep) 95%, transparent);
  color: var(--on-deep);
  opacity: 0;
  transition: opacity 0.2s ease;
  overflow: hidden;
}
/* the bar is the only in-flow child (stage and close are positioned), so the
   column just pins it to the bottom — and leaving it static keeps the dialog,
   not the bar, as the containing block for the side arrows */
.lightbox[open] { display: flex; flex-direction: column; justify-content: flex-end; }
.lightbox.is-open { opacity: 1; }
/* the scrim is the dialog's own background so one fade covers everything */
.lightbox::backdrop { background: transparent; }

.lb-stage {
  position: absolute;
  top: var(--lb-top); bottom: var(--lb-bottom);
  left: var(--lb-side); right: var(--lb-side);
  /* claim the horizontal drag for swipe, leave pinch-zoom to the browser */
  touch-action: pan-y pinch-zoom;
}
/* contain, never cover: the whole point of opening a photo is to see the part
   its cell cropped away.
   inset + margin:auto rather than a flex/grid centre — an auto-sized track is
   content-dependent, so max-height:100% inside one resolves to no limit at all
   and a tall photo runs off the bottom of the viewport */
.lb-img {
  position: absolute; inset: 0; margin: auto;
  max-width: 100%; max-height: 100%;
  width: auto; height: auto;
  object-fit: contain;
  border-radius: var(--img-radius);
  transform: scale(0.985);
  transition: transform 0.24s cubic-bezier(0.2, 0.6, 0.2, 1);
}
.lightbox.is-open .lb-img { transform: none; }

.lb-bar {
  flex: none;
  margin-bottom: clamp(18px, 3.4vh, 30px);
  display: flex; align-items: center; justify-content: center;
  gap: 18px;
}
.lb-count {
  font-family: var(--font-serif);
  font-size: 13px; font-weight: 600;
  letter-spacing: 0.28em; text-transform: uppercase;
  color: var(--on-deep-soft);
  font-variant-numeric: tabular-nums;
}
.lb-btn {
  appearance: none;
  display: grid; place-items: center;
  width: 44px; height: 44px;
  padding: 0;
  border: 1px solid color-mix(in srgb, var(--on-deep-soft) 40%, transparent);
  border-radius: 50%;
  background: transparent;
  color: var(--on-deep-soft);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.lb-btn:hover { color: var(--on-deep); border-color: var(--on-deep-soft); }
.lb-btn:focus-visible { outline: 2px solid var(--on-deep); outline-offset: 2px; }
.lb-close { position: absolute; top: clamp(12px, 2.4vw, 24px); right: clamp(12px, 2.4vw, 24px); }
/* wide enough for the arrows to flank the photo rather than sit under it, so
   the side inset has to clear a 44px control plus its own margin */
@media (min-width: 601px) {
  .lightbox { --lb-side: clamp(92px, 9vw, 132px); --lb-bottom: clamp(58px, 8vh, 78px); }
  .lb-prev, .lb-next { position: absolute; top: 50%; transform: translateY(-50%); }
  .lb-prev { left: clamp(14px, 3vw, 38px); }
  .lb-next { right: clamp(14px, 3vw, 38px); }
}
@media (prefers-reduced-motion: reduce) {
  .lightbox, .lb-img { transition: none; }
  .lb-img { transform: none; }
}

