/* ============ MODERN PALAWAN ============
   Palette sampled from the printed invitation suite:
   mint paper, bottle-green ink, moss, forest envelope,
   wax-seal gold, banana-bud magenta. */

:root {
  color-scheme: light; /* site is light-only by design (Lauren's pick) — no dark variant */
  --bg: #F7FAF0;
  --surface: #FCFEF7;
  --ink: #1F2B16;
  --head: #2E4B15;
  --soft: #6B7A5C;
  --moss: #567546;
  --forest: #24413A;
  --forest-deep: #172B25;
  --on-forest: #EAF2DC;
  --on-forest-soft: rgba(234, 242, 220, 0.72);
  --leaf: #7A9A4E;
  --hairline: rgba(86, 117, 70, 0.28);
  --gold: #B08A50;
  /* gold reads ~3.5:1 on forest — fails AA for small text. Small gold text on
     forest uses this lighter tint (~4.9:1); big display gold (h2 accents) may
     keep --gold (large-text threshold). */
  --gold-on-forest: #C9A96B;
  --hairline-on-forest: rgba(234, 242, 220, 0.25);
  --magenta: #A54468;
  --btn-ink: #EAF2DC;
  --display: "EB Garamond", "Hoefler Text", "Georgia", serif;
  --sans: "Helvetica Neue", "Helvetica", "Arial", sans-serif;
  --mono: "Menlo", "SF Mono", "Consolas", monospace;
  /* marquee's rendered height (2×0.85rem padding + one 0.78rem mono line +
     borders) — the hero subtracts it so the ticker peeks inside the first
     viewport as a quasi-footer / scroll cue */
  --marquee-h: 3.1rem;
}
/* Cross-document view transitions: /  <->  /travel/ fade instead of flashing.
   Progressive enhancement — unsupported browsers just navigate normally. */
@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 0.22s; }

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
/* iOS Safari auto-inflates text inside elements much wider than the viewport
   — the marquee track (width: max-content, thousands of px) triggers it and
   rendered ~2.5x too big on iPhones. Pin the adjustment. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
/* Anchor jumps land below the fixed frosted nav (measured 66.4px tall), not
   underneath it. (Scroll snap was tried and PARKED 2026-09-07: proximity's
   catch zone is too small to feel, and mandatory fights the
   taller-than-viewport sections — travel on mobile, expanded RSVP form. May
   be revisited now that short sections have full-view floors, but only if
   the tall-section problem is solved first.) */
html { scroll-padding-top: 4.25rem; }
/* overflow-x on BOTH html and body: iOS Safari scrolls the html element and
   ignores body-only overflow — the hero leaf hangs off-canvas by design. */
html, body { overflow-x: hidden; overflow-x: clip; }
/* The page CANVAS (what iOS Safari shows in the overscroll areas and tints
   its bottom chrome with) is painted dark to match the footer — Hunter's
   white-bar-under-the-footer report, 2026-09-11. The body still paints the
   cream document, and the ::before cap below covers the TOP overscroll so
   rubber-banding on the hero reveals cream, not this dark canvas. */
html { background: var(--forest-deep); }
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 1rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  position: relative;
}
/* Cream cap above the document: paints the TOP overscroll area (top overflow
   is unreachable by scrolling, so this adds no scroll height — unlike a cap
   below the document, which would). Pairs with the dark html canvas above. */
body::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -120vh;
  height: 120vh;
  background: var(--bg);
  z-index: -1;
}
img { max-width: 100%; display: block; }

/* ---------- reveal on scroll ---------- */
.rv { opacity: 0; transform: translateY(22px); transition: opacity 0.7s ease, transform 0.7s ease; }
.rv.in { opacity: 1; transform: none; }

/* ---------- hero entrance (2026-09-18) ----------
   First-arrival choreography: pure CSS keyframes, same rise-and-fade language
   as .rv, autoplaying so no-JS guests get it too. The inline head script in
   base.njk stamps `seen` on <html> after the session's first load, so
   returning from /travel/ doesn't replay the show. Total run ~1.5s; the leaf
   drifts in slowest on purpose. Scoped to home-only elements. */
@keyframes heroRise { from { opacity: 0; transform: translateY(22px); } }
@keyframes heroFade { from { opacity: 0; } }
/* Ticker convention: the belt must read as ALWAYS RUNNING — the track's
   scroll starts at load and is never synced to the entrance; only the frame
   docks in, sliding up by its own height at the viewport's bottom edge. */
@keyframes marqueeDock { from { opacity: 0; transform: translateY(100%); } }
.hero .meta { animation: heroRise 0.8s ease 0.15s both; }
.hero h1 { animation: heroRise 0.9s ease 0.3s both; }
.hero .sub { animation: heroRise 0.8s ease 0.5s both; }
.hero .actions { animation: heroRise 0.8s ease 0.65s both; }
.hero-wrap .bigleaf { animation: heroFade 1.8s ease 0.2s both; }
.hero .hero-photo { animation: heroFade 1.2s ease 0.3s both; }
.marquee { animation: marqueeDock 0.8s ease 0.9s both; }
.seen .hero .meta, .seen .hero h1, .seen .hero .sub, .seen .hero .actions,
.seen .hero-wrap .bigleaf, .seen .hero .hero-photo, .seen .marquee { animation: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .rv { opacity: 1; transform: none; transition: none; }
  .marquee-track { animation: none !important; }
  .hero .meta, .hero h1, .hero .sub, .hero .actions,
  .hero-wrap .bigleaf, .hero .hero-photo, .marquee { animation: none !important; }
  * { transition-duration: 0.01ms !important; }
  @view-transition { navigation: none; }
}

/* Insurance: blocks toggled via the hidden attribute (gated RSVP questions)
   must never be resurrected by a more specific display rule. */
[hidden] { display: none !important; }

/* ---------- nav ---------- */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  gap: 2rem;
  padding: 0.85rem 1.6rem;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--hairline);
}
.nav .mark { color: var(--head); text-decoration: none; display: flex; align-items: center; }
/* Interlocked L·H monogram from the invitation suite (reference/monogram →
   src/_includes/monogram.njk, single path, currentColor). Keep its height
   under the RSVP pill's 38px so the 66px nav (and the 4.25rem scroll-padding
   tied to it) never grows. */
.nav .mark .monogram { height: 2.3rem; width: auto; display: block; }
.nav .links {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 1.8rem;
  font-size: 0.78rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}
.nav a { color: var(--ink); text-decoration: none; }
@media (hover: hover) { .nav a:hover { color: var(--moss); } }
.nav a:focus-visible { outline: 2px solid var(--moss); outline-offset: 3px; }
.nav .cta {
  position: relative;
  background: var(--forest);
  color: var(--on-forest) !important;
  padding: 0.55rem 1.3rem;
  border-radius: 999px;
  transition: background 0.2s;
}
/* Invisible hit-area extension: the pill renders 38px tall; the tap target
   must reach 44px (Apple HIG) without growing the nav (66px — the 4.25rem
   scroll-padding depends on it). */
.nav .cta::after { content: ""; position: absolute; inset: -4px -6px; }
@media (hover: hover) { .nav .cta:hover { background: var(--moss); color: var(--btn-ink) !important; } }
/* Mobile nav keeps Travel: it's a second PAGE, and with the hero's ghost
   button removed (2026-09-09) this link is its only persistent path on
   phones. Section anchors (Photos/Dress code) still drop. */
@media (max-width: 40rem) { .nav .links a:not(.cta):not(.nav-travel) { display: none; } }

/* ---------- hero ---------- */
/* Full-bleed wrapper: the leaf cluster is taller than the hero and hangs off
   the right of the SCREEN — clipping must happen at the viewport edge, not the
   centered 78rem column (a column-edge clip slices the leaf mid-screen on wide
   monitors). */
.hero-wrap {
  position: relative;
  overflow: clip;
}
.hero {
  position: relative;
  /* one viewport minus the marquee: hero + ticker fill the first screen
     together (the ticker peeking is deliberate — info + scroll cue). On short
     viewports where the content needs more room, min-height simply loses and
     the ticker drops below the fold like before. */
  min-height: calc(100svh - var(--marquee-h));
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  /* 5.5rem top + 2.5rem bottom (was 7/3.5) buy the marquee its full peek on
     ~900px-tall desktops, where content height (not min-height) wins — the
     ticker right below acts as the hero's visual base now. Taller viewports
     never notice (flex-end), shorter ones are governed by the query below. */
  padding: 5.5rem 1.6rem 2.5rem;
  max-width: 78rem;
  margin: 0 auto;
}
.hero-wrap .bigleaf {
  position: absolute;
  top: -2rem;
  right: -2vw; /* hangs off the viewport edge — hides the artwork's washed-out right side */
  width: min(48vw, 515px);
  color: var(--leaf);
  opacity: 0.5;
  pointer-events: none;
  /* Alternate treatments (user-reviewed 2026-08-25, kept in back pocket) are
     documented in CLAUDE.md "Hero frond" and mockups/frond-edge-options.html. */
}
/* Mobile art direction (Hunter's pick 2026-09-09: "option 2, or between 2 and
   4" from mockups/leaf-mobile-options.html): the leaf GROWS at phone scale —
   corner bleed off the top-right — instead of shrinking into a watermark.
   Mask tuned 2026-09-09 per Hunter to "barely any" (closer to option 4):
   full strength through 78%, easing to half at the bottom tip — just enough
   to keep the tip from competing with the buttons. Delete the mask entirely
   for pure option 4. Desktop (>56rem) is deliberately untouched. */
@media (max-width: 56rem) {
  .hero-wrap .bigleaf {
    top: -4rem;
    right: -8vw;
    width: min(72vw, 300px);
    opacity: 0.6;
    /* fade strengthened 2026-09-11 (Hunter: between the old 50%-at-tip and
       the option-2 full fade-out) — was #000 78% / 0.5 at 100% */
    -webkit-mask-image: linear-gradient(to bottom, #000 74%, rgba(0, 0, 0, 0.2) 100%);
    mask-image: linear-gradient(to bottom, #000 74%, rgba(0, 0, 0, 0.2) 100%);
  }
}
.hero .hero-photo {
  position: absolute;
  top: 6.5rem;
  right: 1.6rem;
  width: min(38vw, 430px);
  border: 1px solid var(--hairline);
  box-shadow: 0 20px 50px rgba(23, 43, 37, 0.18);
  transform: rotate(2deg);
}
.hero .hero-photo img { aspect-ratio: 4 / 5; object-fit: cover; width: 100%; }
@media (max-width: 56rem) {
  .hero .hero-photo { position: static; width: min(100%, 26rem); margin-bottom: 2rem; transform: none; }
}
.hero .meta {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--soft);
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 2rem;
  margin-bottom: 1.6rem;
  font-variant-numeric: tabular-nums;
}
.hero .meta .m { color: var(--magenta); }
/* On phones the meta stacks one datum per line — coordinates, place, date,
   countdown read as a designed invitation block instead of a flex-wrap
   accident (mobile research rec #12). */
@media (max-width: 40rem) {
  /* tighter gap + margin buy back the height of the extra line so the
     marquee peek survives on ~844px-tall phones */
  .hero .meta { flex-direction: column; gap: 0.35rem; margin-bottom: 1.1rem; }
}
.hero h1 {
  position: relative;
  font-family: var(--display);
  font-weight: 400;
  /* svh cap keeps the 3-line headline + intro + buttons inside one viewport
     on short/wide screens (buttons were falling below the fold); the max-height
     query below reclaims the needed room from spacing, not type size.
     12vw = density-pass mock size (was 12.5vw; ~46.8px at 390). 2.5rem floor
     governs at/below ~333px — a 3rem floor clipped "Lauren&Hunter" on
     320-wide screens (iPhone SE / Display Zoom) */
  font-size: clamp(2.5rem, min(12vw, 17svh), 9.5rem);
  line-height: 0.98;
  letter-spacing: -0.02em;
  color: var(--head);
  text-wrap: balance;
}
.hero h1 .amp {
  font-style: italic;
  color: var(--moss);
  /* asymmetric on purpose: EB Garamond's italic ampersand is a right-leaning
     swash (open lower-left, tail crowds the next cap) — box gaps verified
     symmetric, so this is an optical correction, tuned by eye */
  margin: 0 0.12em 0 0.02em;
}
.hero .sub {
  margin-top: 1.8rem;
  max-width: 34rem;
  font-size: 1.05rem;
  color: var(--ink);
}
.hero .sub .muted { color: var(--soft); }
.hero .actions { margin-top: 2.2rem; display: flex; gap: 1rem; flex-wrap: wrap; }
/* Phase 8 density pass (2026-09-11): close the gap to the approved mobile
   mock, which renders ~7–10% tighter — every saved pixel routes into the air
   above the meta block (bottom-anchored hero). MOBILE-SCOPED: the desktop
   composition (marquee peek at ~900px heights) was tuned separately.
   Baseline before this pass = git tag `pre-density`. */
@media (max-width: 56rem) {
  /* 16.8px/1.65 wrapped 3 lines at 390px; 15.7px/1.58 recovers the mock's
     2-line wrap (~33px, the biggest single win) with slack against
     device font-metric drift */
  .hero .sub { font-size: 0.98rem; line-height: 1.58; margin-top: 1.3rem; }
  .hero .meta { font-size: 0.72rem; } /* mock size; gaps stay as-is */
  .hero .actions { margin-top: 1.6rem; } /* cluster margins to mock values (desktop keeps 1.8/2.2) */
}
/* Thumb-zone CTAs: stacked full-width on phones — composed, not incidentally
   wrapped desktop pills. Same for the travel page's end buttons. */
@media (max-width: 40rem) {
  .hero .actions, .page-end { flex-direction: column; align-items: stretch; }
  .hero .actions .btn, .page-end .btn { width: 100%; text-align: center; }
}
/* Short viewports: tighten vertical rhythm so the hero still fits one screen.
   DESKTOP-ONLY on purpose (2026-09-11): iOS media queries track the LIVE
   layout viewport, so an unguarded max-height query toggles mid-scroll as
   Safari's chrome collapses (~714→814pt crosses this 800px boundary) — the
   hero grew/shrank ~21px while scrolling. Phones keep the roomier spacing
   permanently (the 844px marquee-peek tuning was measured in that state). */
@media (max-height: 50rem) and (min-width: 56rem) {
  .hero { padding-top: 5.5rem; padding-bottom: 2.4rem; }
  .hero .meta { margin-bottom: 1.1rem; }
  .hero .sub { margin-top: 1.2rem; }
  .hero .actions { margin-top: 1.6rem; }
}
.btn {
  display: inline-block;
  font-size: 0.8rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 1rem 2.1rem;
  border-radius: 999px;
  transition: background 0.2s, color 0.2s, border-color 0.2s;
}
.btn.primary { background: var(--forest); color: var(--on-forest); border: 1px solid var(--forest); }
@media (hover: hover) { .btn.primary:hover { background: var(--moss); border-color: var(--moss); color: var(--btn-ink); } }
.btn.ghost { color: var(--head); border: 1px solid var(--hairline); }
@media (hover: hover) { .btn.ghost:hover { border-color: var(--moss); color: var(--moss); } }
.btn:focus-visible { outline: 2px solid var(--moss); outline-offset: 3px; }

/* ---------- marquee ---------- */
.marquee {
  background: var(--forest);
  color: var(--on-forest);
  overflow: hidden;
  padding: 0.85rem 0;
  border-block: 1px solid var(--forest-deep);
}
.marquee-track {
  display: flex;
  gap: 3rem;
  width: max-content;
  animation: slide 30s linear infinite;
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  white-space: nowrap;
}
.marquee-track .dot { color: var(--gold); }
/* WCAG 2.2.2: auto-moving text needs a pause affordance. Hover covers pointer
   users; focus-within is dormant (aria-hidden, no focusable children) but
   keeps the pause if anything focusable ever lands in the ticker.
   Reduced-motion users get animation:none in the block up top. */
@media (hover: hover) { .marquee:hover .marquee-track { animation-play-state: paused; } }
.marquee:focus-within .marquee-track { animation-play-state: paused; }
@keyframes slide { to { transform: translateX(-50%); } }

/* ---------- section scaffold ---------- */
.section {
  max-width: 78rem;
  margin: 0 auto;
  padding: 5.5rem 1.6rem 0;
  display: grid;
  /* minmax(0,…): a bare 1fr's min-content floor let the carousel push the
     column past the viewport on 320px screens (clipped by overflow-x) */
  grid-template-columns: 16rem minmax(0, 1fr);
  gap: 2rem 4rem;
  align-items: start;
}
@media (max-width: 56rem) { .section { grid-template-columns: minmax(0, 1fr); gap: 0.9rem; } }
/* Full-view floors REMOVED (Hunter's pick 2026-09-11, density pass): the
   100svh centered floors on #details/#photos — the parked-snap era's
   equal-viewport rhythm — read as empty air on tall monitors. Sections now
   take natural height everywhere; the next section peeking in is the scroll
   cue, like the marquee peek in the hero. If revisiting scroll snap, the
   equal-sizing question reopens with it. Old rule (was ≥56rem only):
   #details, #photos { min-height: 100svh; align-content: center; } */
.side { position: sticky; top: 5.2rem; }
@media (max-width: 56rem) {
  /* Mobile eyebrow: the desktop rail recomposes into ONE line — "01 — LABEL"
     over its own hairline — a designed mobile signature, not collapsed-grid
     residue (mobile research 2026-09-08). */
  .side {
    position: static;
    display: flex;
    align-items: baseline;
    gap: 0.7em;
    border-bottom: 1px solid var(--hairline);
    padding-bottom: 0.75rem;
  }
  .side .no { margin-bottom: 0; }
  .side .no::after { content: "—"; margin-left: 0.7em; opacity: 0.45; }
  .rsvp .side { border-color: var(--hairline-on-forest); }
}
.side .no {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--moss);
  display: block;
  margin-bottom: 0.5rem;
}
.side .label {
  font-size: 0.78rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--soft);
}
h2 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2rem, 4.5vw, 3.1rem);
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--head);
  margin-bottom: 1.4rem;
  text-wrap: balance;
}
h2 .it { font-style: italic; color: var(--moss); }
.content p { max-width: 40rem; }
.content p + p { margin-top: 1em; }
.muted { color: var(--soft); }

/* ---------- photos carousel ---------- */
/* "H" vocabulary (Hunter 2026-09-11): NO box on the track — each photo sits
   frameless on the page at native ratio with a hairline keyline on the photo
   itself. Print-framing research: a fixed landscape box can't mat a portrait
   convincingly, so the box went away instead. */
.car-track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.car-track::-webkit-scrollbar { display: none; }
.car-track:focus-visible { outline: 2px solid var(--moss); outline-offset: 3px; }
.slide {
  position: relative;
  flex: 0 0 100%;
  scroll-snap-align: start;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
@media (max-width: 40rem) { .slide { aspect-ratio: 4 / 3; } }
/* placeholder slides (no image yet) keep the old tinted box look */
.slide:not(.has-img) {
  border: 1px solid var(--hairline);
  background:
    linear-gradient(160deg,
      color-mix(in srgb, var(--leaf) 16%, var(--surface)),
      var(--surface) 70%);
}
.slide img { border: 1px solid var(--hairline); }
.slide > img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}
/* diptych: two ~3:4 leaves share the slide (pair ≈ 3:2, so it fills the
   desktop stage exactly and self-sizes from width on mobile) */
.slide .duo {
  width: 100%;
  aspect-ratio: 3 / 2;
  max-height: 100%;
  display: flex;
  gap: 6px;
}
.slide .duo img {
  flex: 1 1 0;
  min-width: 0;
  height: 100%;
  object-fit: cover;
}
.slide .tag {
  position: relative;
  z-index: 1;
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--soft);
  padding: 0.7rem 0.9rem;
}
.slide.has-img .tag {
  color: var(--on-forest);
  background: linear-gradient(transparent, rgba(23, 43, 37, 0.65));
  width: 100%;
}
.slide .cam {
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -60%);
  color: var(--moss);
  opacity: 0.65;
}
.car-ui {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 0.9rem;
}
.car-btn {
  background: none;
  border: 1px solid var(--hairline);
  color: var(--head);
  border-radius: 999px;
  width: 2.75rem; /* 44px — HIG touch minimum */
  height: 2.75rem;
  cursor: pointer;
  font-size: 0.95rem;
  transition: border-color 0.2s, color 0.2s;
}
@media (hover: hover) { .car-btn:hover { border-color: var(--moss); color: var(--moss); } }
.car-btn:focus-visible { outline: 2px solid var(--moss); outline-offset: 3px; }
/* Dots: 24×24px buttons (WCAG 2.5.8 minimum) drawing a small visual dot via
   ::after — the old 8.8px dots failed target-size even with the spacing
   exception. */
.car-dots { display: flex; }
.car-dots button {
  width: 24px;
  height: 24px;
  border: none;
  border-radius: 50%;
  background: transparent;
  padding: 0;
  cursor: pointer;
  position: relative;
}
.car-dots button::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  border: 1px solid var(--moss);
}
.car-dots button[aria-current="true"]::after { background: var(--moss); }
/* Mobile carousel: dots hide (touch users neither notice nor tap them) and
   nothing replaces them — the "01 / 13" counter was tried and removed
   (Hunter 2026-09-11: explicitly counting the photos felt off). The element
   still renders + syncs; restore by re-adding display:block <40rem. */
.car-count {
  display: none;
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  color: var(--moss);
  font-variant-numeric: tabular-nums;
}
@media (max-width: 40rem) {
  /* (A 92% swipe-peek was tried 2026-09-09 and reverted: the frame border
     lives on the track, so the next slide's sliver reads as one broken photo,
     not a neighboring card — user flagged it on the deploy preview. The
     arrows alone carry the "there are more" signal.) */
  .car-dots { display: none; }
}
.car-dots button:focus-visible { outline: 2px solid var(--moss); outline-offset: 2px; }

/* ---------- travel guide page ---------- */
.page-head {
  max-width: 78rem;
  margin: 0 auto;
  padding: 8.5rem 1.6rem 1.5rem;
}
.page-head .crumb { margin-bottom: 1.6rem; }
.page-head .crumb a {
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  color: var(--soft);
  text-decoration: none;
}
@media (hover: hover) { .page-head .crumb a:hover { color: var(--moss); } }
.page-head h1 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2.6rem, 7vw, 5rem);
  line-height: 1.02;
  letter-spacing: -0.02em;
  color: var(--head);
  text-wrap: balance;
}
.page-head h1 .it { font-style: italic; color: var(--moss); }
.page-head .sub { margin-top: 1.4rem; max-width: 38rem; color: var(--soft); }
/* Jump links for the 6-viewport guide (NN/g: in-page links gain value as
   screens shrink); scroll-padding-top already lands them below the nav. */
.guide-toc {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1.6rem;
  margin-top: 1.8rem;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.guide-toc a { color: var(--soft); text-decoration: none; padding: 0.5rem 0; } /* padded to tap height */
.guide-toc a .no { color: var(--gold); margin-right: 0.4em; }
@media (hover: hover) { .guide-toc a:hover { color: var(--moss); } }
.guide-toc a:focus-visible { outline: 2px solid var(--moss); outline-offset: 3px; }
.page-end {
  max-width: 78rem;
  margin: 0 auto;
  padding: 5rem 1.6rem 5.5rem;
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}
.content .more { margin-top: 1.8rem; }

/* ---------- details ledger (section 02 — shape C) ---------- */
/* Ledger vocabulary ONLY (Phase 4 rule): mono labels, hairlines, whitespace.
   No card boxes (that's travel's compare language), no sub-numbering
   (section numbers stay the page's only numerals). */
.ledger { max-width: 40rem; }
.ledger .lrow {
  display: grid;
  grid-template-columns: 6.5rem minmax(0, 1fr);
  gap: 0.3rem 1.4rem;
  padding: 0.9rem 0;
  border-bottom: 1px solid var(--hairline);
}
.ledger .lrow:first-child { border-top: 1px solid var(--hairline); }
.ledger dt {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--moss);
  padding-top: 0.2em;
}
.ledger .dot { color: var(--gold); margin: 0 0.35em; }
.ledger.facts dd {
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--head);
  font-variant-numeric: tabular-nums;
  line-height: 1.7;
}
.ledger.notes dd { font-size: 0.95rem; }
/* Single discreet map link on the venue row (Hunter 2026-09-11: the dual
   Google/Apple pair was too much chrome here — the pair lives on /travel/).
   Google's universal link hands off to the native app on either platform. */
.ledger .maplink {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--moss);
  text-decoration: none;
  border-bottom: 1px solid var(--hairline);
  padding-bottom: 0.1em;
  margin-left: 0.9em;
  white-space: nowrap;
}
@media (hover: hover) { .ledger .maplink:hover { color: var(--head); border-color: var(--moss); } }
.ledger .maplink:focus-visible { outline: 2px solid var(--moss); outline-offset: 3px; }
/* phrases never break internally — lines wrap at the middot separators */
.ledger .nb { white-space: nowrap; }
@media (max-width: 40rem) {
  .ledger .lrow { grid-template-columns: minmax(0, 1fr); gap: 0.35rem; }
  .ledger dt { padding-top: 0; }
}
/* Unified composition (Hunter 2026-09-11, round 9 — supersedes the 10b rail
   experiment): ONE system on both breakpoints. Each sub-group = mono label
   on top, content beneath, in the content column; the rail holds only the
   section eyebrow. One display heading (the h2); the dress code has NO
   heading of its own — its body opens "Beach formal". */
.sublab {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--moss);
  margin: 3.2rem 0 1.1rem;
}
#details .side + .content + .sublab { margin-top: 1.2rem; }
#details h2 { margin-bottom: 0.4rem; }
/* Facts row labels are MUTED (Hunter 2026-09-11): WHEN/WHERE/AFTER guide the
   eye without competing — --soft, the site's muted tone (~4.5:1 on cream,
   AA edge; don't go lighter). The notes dts (LITTLE ONES etc.) stay moss —
   they carry real content. NOTE: a dress-group top hairline was tried and
   REVERTED same day (only looked needed while the mobile sublabels existed). */
.ledger.facts dt { color: var(--soft); }
@media (min-width: 56rem) {
  /* labels and content share the content column; spacing is all margins */
  #details { row-gap: 0; }
  #details .sublab, #details .content { grid-column: 2; }
}

/* ---------- travel ---------- */
.cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  max-width: 48rem;
}
@media (max-width: 48rem) { .cards { grid-template-columns: 1fr; } }
.card {
  border: 1px solid var(--hairline);
  background: var(--surface);
  padding: 1.6rem 1.7rem 1.7rem;
  transition: border-color 0.25s, transform 0.25s;
}
@media (hover: hover) { .card:hover { border-color: var(--moss); transform: translateY(-3px); } }
.card .key {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  color: var(--moss);
  display: block;
  margin-bottom: 0.7rem;
  font-variant-numeric: tabular-nums;
}
.card h3 {
  font-family: var(--display);
  font-weight: 400;
  font-size: 1.35rem;
  color: var(--head);
  margin-bottom: 0.5rem;
}
.card p { font-size: 0.95rem; }
/* Map links (guide cards with a map_query): plain https links — Google's
   universal link and maps.apple.com both hand off to the native app on the
   guest's platform, no UA sniffing. */
.card .maplinks {
  margin-top: 0.8rem;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.card .maplinks a { color: var(--moss); text-decoration: none; border-bottom: 1px solid var(--hairline); padding-bottom: 0.1em; }
@media (hover: hover) { .card .maplinks a:hover { color: var(--head); border-color: var(--moss); } }
.card .maplinks a:focus-visible { outline: 2px solid var(--moss); outline-offset: 3px; }
.card .maplinks .dot { color: var(--gold); margin: 0 0.35em; }

/* ---------- rsvp ---------- */
.rsvp {
  margin-top: 6rem;
  background: var(--forest);
  color: var(--on-forest);
  /* Floor history: 100svh removed 2026-09-11 (footer round D: void above the
     footer) — then the RSVP anchor jump lost its dedicated screen (the footer
     intruded into the CTA view; Hunter, 2026-09-12). Restored DESKTOP-ONLY at
     100svh minus the 4.25rem nav offset: an anchor jump fills the viewport
     with the section exactly, footer out of frame, and the bottom-of-page
     void is 4.25rem smaller than the old floor's. Phones stay natural height
     (the form fills their screens anyway). Content stays TOP-aligned. */
}
@media (min-width: 56rem) {
  .rsvp { min-height: calc(100svh - 4.25rem); }
}
/* same scaffold as .section (side rail + content) so "RSVP" and the heading
   line up with the other sections' labels/headings — only recolored for the
   forest background */
.rsvp .wrap {
  max-width: 78rem;
  margin: 0 auto;
  padding: 5.5rem 1.6rem 5.5rem;
  display: grid;
  grid-template-columns: 16rem minmax(0, 1fr);
  gap: 2rem 4rem;
  align-items: start;
}
@media (max-width: 56rem) { .rsvp .wrap { grid-template-columns: minmax(0, 1fr); gap: 0.9rem; } }
.rsvp .side .no { color: var(--gold-on-forest); }
.rsvp .side .label { color: var(--on-forest-soft); }
.rsvp h2 { color: var(--on-forest); font-size: clamp(2.4rem, 5.5vw, 4rem); }
.rsvp h2 .it { color: var(--gold); }
.rsvp .lede { color: var(--on-forest-soft); max-width: 40rem; }
.rsvp .deadline {
  margin-top: 1.6rem;
  font-family: var(--mono);
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gold-on-forest);
}
form { display: flex; flex-direction: column; gap: 1.3rem; }
/* Reserve trimmed to just above the resting (pre-lookup) height, 2026-09-11:
   the old 26rem was sized for the tallest identity-flow state so swaps
   happened in place — but with the section's 100svh floor gone (footer
   round D) that reserve became a permanent void above the footer. Now the
   resting state is tight and taller flow states simply grow the page
   (footer nudges down during a lookup — acceptable; resets are rare).
   Width is capped — the form stacks below the heading in the content column */
form[data-rsvp] { min-height: 13rem; max-width: 40rem; margin-top: 2.4rem; }
@media (max-width: 40rem) { form[data-rsvp] { min-height: 14rem; } }
/* the form sits inside .content now — don't let the prose p+p rhythm leak
   into the JS-rendered flow (its components carry their own margins) */
form[data-rsvp] p + p { margin-top: 0; }
.field label, .field legend {
  display: block;
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--on-forest-soft);
  margin-bottom: 0.4rem;
}
.field fieldset { border: none; }
.field input[type="text"],
.field input[type="email"],
.field select,
.field textarea {
  width: 100%;
  font-family: var(--sans);
  font-size: 1rem;
  color: var(--on-forest);
  background: rgba(234, 242, 220, 0.07);
  border: 1px solid rgba(234, 242, 220, 0.25);
  padding: 0.8rem 0.95rem;
  border-radius: 6px;
}
.field input::placeholder, .field textarea::placeholder { color: rgba(234, 242, 220, 0.4); }
.field textarea { min-height: 5rem; resize: vertical; }
.field :is(input, select, textarea):focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: -1px;
}
.ev-note {
  font-size: 0.85rem;
  color: var(--on-forest-soft);
  letter-spacing: normal;
  text-transform: none;
  margin: -0.1rem 0 0.6rem;
  max-width: 30rem;
}
.choices { display: flex; gap: 1.8rem; flex-wrap: wrap; }
.choices label {
  display: flex; align-items: center; gap: 0.55rem;
  font-size: 0.95rem; cursor: pointer; color: var(--on-forest);
}
.choices input { accent-color: var(--gold); width: 1rem; height: 1rem; }
.hidden { display: none; }
.send {
  align-self: start;
  font-family: var(--sans);
  font-size: 0.8rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--forest-deep);
  background: var(--gold);
  border: none;
  border-radius: 999px;
  padding: 1rem 2.6rem;
  cursor: pointer;
  transition: filter 0.2s, transform 0.2s;
}
@media (hover: hover) { .send:hover { filter: brightness(1.08); transform: translateY(-1px); } }
.send:focus-visible { outline: 2px solid var(--on-forest); outline-offset: 3px; }

/* ---------- RSVP household flow ---------- */
.find {
  margin-top: 0.8rem;
  font-family: var(--sans);
  font-size: 0.78rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--gold-on-forest);
  background: none;
  border: 1px solid var(--gold-on-forest);
  border-radius: 999px;
  padding: 0.95rem 1.8rem; /* ≥44px tall — HIG touch minimum */
  cursor: pointer;
  transition: background 0.2s, color 0.2s;
}
@media (hover: hover) { .find:hover { background: var(--gold-on-forest); color: var(--forest-deep); } }
.find:disabled { opacity: 0.6; cursor: wait; }
.find:focus-visible { outline: 2px solid var(--on-forest); outline-offset: 3px; }
[data-rsvp-flow] { display: flex; flex-direction: column; gap: 1.3rem; }
.rsvp-flow-title {
  font-size: 1.15rem;
  color: var(--on-forest);
  margin-bottom: 0.9rem;
  max-width: 30rem;
}
.rsvp-household .rsvp-flow-title { margin-bottom: 0.3rem; }
.rsvp-flow-title strong { color: var(--gold); font-weight: 600; }
.rsvp-help {
  display: block;
  font-size: 0.8rem;
  color: var(--on-forest-soft);
  margin-top: 0.45rem;
  letter-spacing: normal;
  text-transform: none;
}
.rsvp-error { font-size: 0.9rem; color: var(--gold-on-forest); margin-top: 0.6rem; }
.rsvp-narrow { display: grid; grid-template-columns: 1fr 1fr; gap: 1.1rem; margin-bottom: 1rem; }
@media (max-width: 34rem) { .rsvp-narrow { grid-template-columns: 1fr; } }
.rsvp-narrow label span { display: block; margin-bottom: 0.4rem; }
.rsvp-cards { display: flex; flex-direction: column; gap: 0.7rem; margin-bottom: 0.9rem; }
.rsvp-card {
  text-align: left;
  font-family: var(--sans);
  font-size: 1.05rem;
  color: var(--on-forest);
  background: rgba(234, 242, 220, 0.07);
  border: 1px solid rgba(234, 242, 220, 0.25);
  border-radius: 6px;
  padding: 0.95rem 1.1rem;
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s;
}
@media (hover: hover) { .rsvp-card:hover { border-color: var(--gold); background: rgba(234, 242, 220, 0.12); } }
.rsvp-card:focus-visible { outline: 2px solid var(--gold); outline-offset: -1px; }
.rsvp-card:disabled { opacity: 0.6; cursor: wait; }
.rsvp-reset {
  align-self: flex-start;
  font-family: var(--sans);
  font-size: 0.8rem;
  color: var(--on-forest-soft);
  background: none;
  border: none;
  padding: 0;
  margin-bottom: 0.6rem;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
@media (hover: hover) { .rsvp-reset:hover { color: var(--on-forest); } }
.rsvp-checkgroup { border: none; margin-top: 0.5rem; }
/* questions read as questions, not field captions — undo the label styling;
   float trick makes <legend> flow like a normal block inside the fieldset */
.rsvp-checkgroup legend {
  float: left;
  width: 100%;
  font-family: var(--sans);
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-transform: none;
  color: var(--on-forest);
  margin-bottom: 0.55rem;
}
.rsvp-checkgroup .rsvp-help { clear: both; margin: 0 0 0.8rem; }
.rsvp-checklist { clear: both; display: flex; flex-direction: column; gap: 0.6rem; }
/* .field .rsvp-check outranks the generic .field label caption styling
   (uppercase/tiny/block) — these rows are tappable cards, not captions */
.field .rsvp-check {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin: 0;
  padding: 0.85rem 1rem;
  font-size: 1rem;
  letter-spacing: 0.01em;
  text-transform: none;
  color: var(--on-forest);
  background: rgba(234, 242, 220, 0.06);
  border: 1px solid rgba(234, 242, 220, 0.22);
  border-radius: 6px;
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s;
}
@media (hover: hover) { .field .rsvp-check:hover { border-color: rgba(234, 242, 220, 0.45); } }
/* progressive enhancement: browsers without :has() still get the gold check */
.field .rsvp-check:has(input:checked) {
  border-color: var(--gold);
  background: rgba(176, 138, 80, 0.14);
}
/* custom checkbox in the site palette (native renders white on this green) */
.rsvp-check input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 1.3rem;
  height: 1.3rem;
  margin: 0;
  flex: none;
  border: 1px solid rgba(234, 242, 220, 0.45);
  border-radius: 4px;
  background: transparent center / 0.85rem no-repeat;
  cursor: pointer;
  transition: background-color 0.15s, border-color 0.15s;
}
.rsvp-check input[type="checkbox"]:checked {
  background-color: var(--gold);
  border-color: var(--gold);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23172B25' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round' d='M3 8.5 6.5 12 13 4.5'/%3E%3C/svg%3E");
}
.rsvp-check input[type="checkbox"]:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.rsvp-guest-row { cursor: default; }
.rsvp-guest-row input[type="text"] { flex: 1; max-width: 16rem; padding: 0.5rem 0.8rem; }
.rsvp-guest-row .rsvp-remove { margin-left: auto; }
.rsvp-add,
.rsvp-remove {
  font-family: var(--sans);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  text-underline-offset: 3px;
}
.rsvp-add {
  clear: both;
  align-self: flex-start;
  color: var(--gold-on-forest);
  font-size: 0.9rem;
  margin-top: 0.9rem;
}
@media (hover: hover) { .rsvp-add:hover { text-decoration: underline; } }
.rsvp-remove { color: var(--on-forest-soft); font-size: 0.8rem; text-decoration: underline; }
@media (hover: hover) { .rsvp-remove:hover { color: var(--on-forest); } }
/* event sections sit flush with the wedding checklist — the row cards carry
   the visual weight, the legend carries the separation */
.rsvp-event { padding: 0; margin-top: 0; }
.rsvp-event .ev-note { clear: both; margin: -0.1rem 0 0.85rem; }
.rsvp .ev-note a, .rsvp-help a, .rsvp-flow-title a, .rsvp-error a {
  color: var(--gold);
  text-underline-offset: 3px;
}

/* ---------- footer ---------- */
footer {
  background: var(--forest-deep);
  color: var(--on-forest-soft);
  padding: 4rem 0 3rem;
}
/* same scaffold as .section so "See you on the island." shares the section
   headings' left edge (empty side-rail column) — user's pick over centering.
   Side padding lives INSIDE the 78rem box, like .section's, so the columns
   line up exactly. */
footer .inner {
  max-width: 78rem;
  margin: 0 auto;
  padding: 0 1.6rem;
  display: grid;
  grid-template-columns: 16rem minmax(0, 1fr);
  gap: 0 4rem;
}
footer .inner > * { grid-column: 2; }
/* Monogram in the rail column (Hunter's pick 2026-09-11): anchors the big
   line's indent and bookends the site with the nav mark. Gold, decorative. */
footer .mark-foot { grid-column: 1; grid-row: 1; color: var(--gold); }
footer .mark-foot .monogram { width: 36px; height: auto; display: block; } /* sized to the sign-off-scale line */
@media (max-width: 56rem) {
  footer .inner { grid-template-columns: minmax(0, 1fr); }
  footer .inner > * { grid-column: 1; }
  footer .mark-foot { grid-row: auto; margin-bottom: 1.6rem; }
}
footer .big {
  font-family: var(--display);
  /* Sign-off scale (2026-09-12, post-launch): was clamp(2rem, 6vw, 4rem) —
     heading-sized. Once the RSVP void was dropped, "Tell us you're coming."
     and this line became same-size neighbors and read as a double heading
     (Hunter's "awkward pacing"). The farewell stays; it just whispers. */
  font-size: clamp(1.4rem, 2.4vw, 2rem);
  color: var(--on-forest);
  line-height: 1.05;
  margin-bottom: 2rem;
  text-wrap: balance;
}
footer .big em { color: var(--gold); }
footer .row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 2.5rem;
  justify-content: space-between;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  /* caps like every other small-mono element (nav, meta, marquee, eyebrows) —
     mixed case here was the one register miss on the site (Hunter felt it
     2026-09-11 without naming it) */
  text-transform: uppercase;
  border-top: 1px solid rgba(234, 242, 220, 0.15);
  padding-top: 1.4rem;
  font-variant-numeric: tabular-nums;
}
/* Same designed-stack treatment as the hero meta. */
@media (max-width: 40rem) {
  footer .row { flex-direction: column; gap: 0.5rem; justify-content: flex-start; }
}

/* ---------- thanks page ---------- */
.thanks {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 2rem 1.6rem;
}
.thanks h1 {
  font-family: var(--display);
  font-weight: 400;
  font-size: clamp(2.6rem, 8vw, 5rem);
  color: var(--head);
  margin-bottom: 1rem;
  text-wrap: balance;
}
.thanks h1 em { color: var(--moss); }
.thanks p { max-width: 30rem; color: var(--soft); margin-bottom: 2.2rem; }

/* ========== WEBFONT: EB GARAMOND (font B, chosen 2026-09-10) ==========
   Self-hosted latin + latin-ext woff2 (Google's own subset files, committed
   in src/fonts/); glyph coverage verified against the full guest list.
   The A/B scaffolding (Cormorant Garamond + ?font= toggle + head script)
   was stripped 2026-09-11 — recoverable from the pre-strip fonts commit
   noted in docs/design-polish-plan.md. */
@font-face {
  font-family: 'EB Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/eb-garamond-400-italic-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/eb-garamond-400-italic-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/eb-garamond-400-normal-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'EB Garamond';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/eb-garamond-400-normal-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
