/* E & L Wedding — shared styles
   Design source: Figma "Wedding" (AmQIvRH3Hs4sDq1N8sPPGy)
   Desktop frame: 1512px · Mobile frame: 402px

   SCALING MODEL
   The root font-size is tied to viewport width, so 1rem = 16px at exactly the
   Figma frame width and every measurement below is the Figma pixel value / 16.
   The whole page therefore scales as one system and matches the frame at any
   window size, instead of each element clamping on its own. */

:root {
  /* 16 / 1512 = 1.0582vw. No ceiling: the page is the Figma frame scaled to
     whatever width the window is, so every ratio in the frame is preserved.
     The floor only stops it collapsing on a very narrow desktop window. */
  font-size: max(11px, 1.0582vw);

  --olive:     #bcbc49;
  --olive-alt: #7c752f;
  --yellow:    #ffe993;
  --burgundy:  #7f214d;
  --white:     #ffffff;
  --pink:      #d8bcc3;    /* kept: the RSVP confirmation message */
  --hover:     #ffe993;    /* every clickable thing, on hover */
  --rule:      #ffffff;

  /* ---- Spacing scale -------------------------------------------------
     Every vertical gap on the site is one of these. They are the values
     already measured off Figma, named so the next block added to a page
     doesn't invent a new number. Pick by relationship, not by eye:

       --rule-drop   a rule to the middle of the line it labels
       --gap-tight   things that belong to each other (a field and its label)
       --gap-body    a heading or label down to its own body copy
       --gap-item    item to item inside one list
       --gap-group   group to group inside one section
       --gap-row     row to row
       --gap-section a section heading down to its first row
       --gap-major   one major page block to the next

     If something needs a gap that isn't here, it's usually a sign the
     relationship is one of these and the number is just wrong. */
  --rule-drop:   0.75rem;   /* 12px */
  --gap-tight:   0.875rem;  /* 14px */
  --gap-body:    1.5rem;    /* 24px — heading down to its own copy */
  --gap-item:    1.25rem;   /* 20px */
  --gap-group:   2.375rem;  /* 38px */
  --gap-row:     3.75rem;   /* 60px — row to row, event to event */
  --gap-section: 3.375rem;  /* 54px */
  --gap-major:   5.5rem;    /* 88px */

  --gutter:    3.625rem;   /* 58px */
  --maxw:      none;       /* no cap — the layout is the frame, scaled */
  --col-rule:  14.75rem;   /* 236px */
  --col-gap:   1.375rem;   /* 22px */
  --col-time:  11.125rem;  /* 178px */
  --measure:   45.4375rem; /* 727px */
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  background: var(--olive);
  color: var(--white);
  font-family: 'Cormorant Upright', 'Cormorant Garamond', Georgia, serif;
  font-weight: 300;
  font-size: 1rem;
  line-height: normal;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Headings must not inherit the browser's bold — the face is Light, and a
   synthesized bold over it is what makes the type look wrong. */
h1, h2, h3, h4, h5, h6 { font-weight: 300; font-size: inherit; }

a { color: inherit; text-decoration: none; }
a:hover { color: var(--hover); }

img { display: block; max-width: 100%; }

.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* ---------- Banner ---------- */

.banner {
  position: relative;
  /* Anchored to the TOP, not centred. Derived from Figma, not guessed:
     the tall banner is 1512x1007 (ratio 1.5015) and orchid.jpg is cut to the
     same ratio, so `cover` shows the frame edge to edge with nothing cropped.
     The short banner is 1512x260 — 25.8% of 1007 — and Figma shows exactly the
     top 25.8% of the same photo, which is what `top` reproduces. Changing this
     to `center` silently re-crops every banner away from the design. */
  background: #efe6e6 url('../orchid.jpg') top center / cover no-repeat;
}

/* The hero is 1007px at the Figma frame width, but never taller than the
   window — otherwise the scroll cue falls below the fold. Everything that
   sits inside the hero is expressed as a fraction of THIS value rather than
   in rem, because rem tracks viewport *width*: on a wide, short window a
   rem-positioned element keeps sliding down while the clamped hero does not,
   which is what pushed the wordmark onto the cue. */
/* lvh, not svh: the hero has to fill the viewport with the browser's
   toolbars HIDDEN, or a scroll that hides them reveals a band of green under
   the photo. The cue is kept above the fold separately, by lifting it the
   height of the toolbars — see .banner--tall .banner__inner below. */
:root { --hero-h: 62.9375rem; }                                    /* 1007px */
@supports (height: 100lvh) { :root { --hero-h: min(62.9375rem, 100lvh); } }

.banner--tall  { min-height: var(--hero-h); }
.banner--short { min-height: 16.25rem; }    /* 260px */

.banner__inner {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-height: inherit;
  padding-top: 3.75rem;     /* 60px */
  padding-bottom: 2.75rem;  /* 44px */
}

/* The hero is as tall as the viewport gets; the cue has to sit inside the
   viewport at its SMALLEST. The difference is exactly the toolbar height, so
   adding it to the padding floats the cue clear of them. Zero on a desktop,
   where the two heights are the same. */
.banner--tall .banner__inner { padding-bottom: 4.125rem; }
@supports (height: 100lvh) {
  .banner--tall .banner__inner {
    padding-bottom: calc(4.125rem + (100lvh - 100svh));
  }
}

/* Phase 2 tall hero: the nav and wordmark have moved into the fixed header,
   so the cue is the only thing left in here. Keep it at the bottom. */
.banner__inner--cue-only { justify-content: flex-end; }

/* ---------- Page header ----------
   One fixed element carries the nav and the wordmark for the whole page. On
   a tall hero the wordmark starts big and low, at its hero position, and
   rides up while shrinking until it settles beside the nav — at which point
   the header IS the bar. Same element throughout, so there is no crossfade
   and nothing to keep in sync.

   The morph is continuous but heavily back-loaded: js/site.js cubes the
   scroll fraction, so the first half of the hero moves it about an eighth of
   the way and the rest arrives over the last stretch. Linear, it read as the
   hero splitting into a half-formed bar while most of the photo was still on
   screen; weighted this way the hero holds, and the bar gathers itself as
   the photo leaves. Both states still live in the calcs below. */

:root {
  /* Everything below is derived from --hdr-p, the scroll progress of the
     header: 0 = full hero, 1 = settled into the bar. js/site.js sets only
     --hdr-p and --hdr-bg; the rest is calc, so the two states are described
     once, here, rather than duplicated in JS.

     Numbers come from the two Figma frames:
       hero    (Homepage 6:29) nav y=17,  wordmark y=464, 90px / 4.5px track
       compact (FAQs     67:32) nav y=24, wordmark y=76,  40px / 2px  track
       bar height 156px */
  --hdr-p:          0;
  --hdr-bg:         0;

  --hdr-h:          9.75rem;     /* 156px */

  /* --hdr-travel lives on .pageheader, not here: it is derived per variant
     from --hero-frac and --hdr-flow, which differ between phase 2 and the
     save-the-date, and between desktop and mobile. See that rule. */

  --hdr-nav-top-hi: 1.0625rem;   /* 17px, hero */
  --hdr-nav-top-lo: 1.5rem;      /* 24px, settled */

  --hdr-word-hi:    5.625rem;    /* 90px */
  --hdr-word-lo:    2.5rem;      /* 40px */
  --hdr-track-hi:   0.28125rem;  /* 4.5px */
  --hdr-track-lo:   0.125rem;    /* 2px */

  --hdr-nav-hi:     1.875rem;    /* 30px */
  --hdr-nav-lo:     1.25rem;     /* 20px */
  --hdr-navtrk-hi:  0.09375rem;  /* 1.5px */
  --hdr-navtrk-lo:  0.0625rem;   /* 1px */
}

.pageheader {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  /* --hdr-inset is 0 everywhere except an in-app browser that draws its
     toolbar over the page; js/site.js sets it there. The bar grows by it and
     pads its contents down by it, so the first line clears the chrome
     instead of hiding behind it. */
  height: calc(var(--hdr-h) + var(--hdr-inset, 0px));
  pointer-events: none;       /* the photo below stays selectable */
}

.pageheader--stdate .pageheader__inner,
.pageheader__inner {
  position: relative;
  z-index: 1;
  padding-top: calc(var(--hdr-inset, 0px) + var(--hdr-nav-top-hi) +
                    (var(--hdr-nav-top-lo) - var(--hdr-nav-top-hi)) * var(--hdr-p));
}

.pageheader .nav,
.pageheader .wordmark { pointer-events: auto; }

/* The orchid slice, revealed only once real content is passing underneath.
   While the banner is still behind the header this stays at 0, so the
   banner's own photo shows through uninterrupted and there is no seam.
   No scrim over it — the bar reads exactly as the top of the banner does. */
.pageheader__bg {
  position: absolute;
  inset: 0;
  background: #efe6e6 url('../orchid.jpg') top center / cover no-repeat;
  opacity: var(--hdr-bg);
  will-change: opacity;
}

/* The wordmark rides up and shrinks at the same time, so it arrives as the
   bar rather than being replaced by it. font-size is animated rather than
   transform: scale() on purpose — scaling text up from its small size
   renders it soft, and the settled state is where people spend their time.
   The header is position:fixed, so relayout is confined to these few nodes
   and never touches the document. */
.pageheader__wordmark {
  /* Clear of the nav, calibrated to put the settled wordmark at y=76. */
  margin-top: 1.75rem;
  transform: translate3d(0, calc((1 - var(--hdr-p)) * var(--hdr-travel)), 0);
  will-change: transform;
}

/* The bar wordmark is the way home, and says so on hover like every other
   clickable thing on the site. */
.pageheader__wordmark .wordmark__name a { color: inherit; }
.pageheader.is-bar .wordmark__name a:hover { color: var(--hover); }

/* Not a link until it is the bar: no pointer, no hover colour, nothing to
   click. It reads as the page's title up there, because that is what it is. */
.pageheader:not(.is-bar) .wordmark__name a { pointer-events: none; cursor: default; }

.pageheader__wordmark .wordmark__name {
  font-size:      calc(var(--hdr-word-hi) +
                       (var(--hdr-word-lo) - var(--hdr-word-hi)) * var(--hdr-p));
  letter-spacing: calc(var(--hdr-track-hi) +
                       (var(--hdr-track-lo) - var(--hdr-track-hi)) * var(--hdr-p));
}

/* The nav stays a constant 20px/1px, hero or settled — only the wordmark
   above shrinks as the header morphs. */
.pageheader .nav {
  font-size:      var(--hdr-nav-lo);
  letter-spacing: var(--hdr-navtrk-lo);
}

/* Short-banner pages have nowhere to travel from: they open settled, and the
   bar's photo strip is on from the first pixel. */
.pageheader:not(.pageheader--tall) { --hdr-p: 1; --hdr-bg: 1; }

/* A tall hero's header is fixed like any other, so the wordmark can ride up
   the screen and come to rest as the bar. js/site.js drives --hdr-p. */
.pageheader--tall {
  position: fixed;
  height: calc(var(--hdr-h) + var(--hdr-inset, 0px));
}



/* Where the hero wordmark sits, as a fraction of the hero height, and the
   wordmark's own flow offset inside the header. js/site.js reads both so the
   numbers live with the other frame measurements rather than inside the
   script — and computes --hdr-travel from them on the header itself.

   This calc is the pre-JS value, and it has to be RIGHT, not merely present:
   it is what the first paint uses. It used to hardcode phase 2's fraction
   and flow for every variant, so a phone drew the hero wordmark 84px too
   high and then dropped it into place once the script ran. */
.pageheader {
  --hero-frac:  0.46077;                 /* 464/1007, phase 2 */
  --hdr-flow:   5rem;                    /* nav above the wordmark */
  --hdr-travel: calc(var(--hero-h) * var(--hero-frac) - var(--hdr-flow));
}

/* ---------- Save the date: the same header, without the nav ---------- */

/* No nav above it, so the wordmark is the first thing in the bar and starts
   at the same inset the nav would have had. */
.pageheader--stdate { --hero-frac: 0.4121;   /* 415/1007 */
                      --hdr-flow:  2.8125rem; }  /* 45px: no nav above it */

/* Name and date/place share a line so the pair can shrink into the bar as
   one; the label and tagline take a full row each beneath them. */
.pageheader--stdate .pageheader__wordmark {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
}

.pageheader--stdate .pageheader__wordmark > .meta { white-space: nowrap; }

.pageheader--stdate .stdate__label,
.pageheader--stdate .stdate__tagline { flex-basis: 100%; }

.pageheader--stdate .wordmark__name a { color: inherit; }

/* "Save the date" and the tagline ride up with the wordmark and are gone
   before it lands — they have no place in a compact bar. They hang below the
   bar's own height, though, so fading them on the morph's progress left them
   sitting on green once the photo had scrolled past. js/site.js fades them on
   the hero's geometry instead: clear before the photo's bottom edge reaches
   the line they occupy, at any window size. */
.pageheader--stdate .stdate__label,
.pageheader--stdate .stdate__tagline {
  clear: both;
  opacity: var(--hdr-sub, 1);
  /* Collapsed at rest so they cannot catch clicks over the page below. */
  visibility: visible;
}

.pageheader--stdate .stdate__label { margin-top: 0.25rem; }

/* Anchors (Back to top) shouldn't land under the fixed header. */
html { scroll-padding-top: var(--hdr-h); }

/* ---------- Nav ---------- */

.nav {
  display: flex;
  justify-content: space-between;
  gap: 2.5rem;
  font-size: 1.875rem;       /* 30px */
  letter-spacing: 0.09375rem;
}

/* The page you're on stays white with a rule under it; yellow is reserved for
   hover, so colour means "you can click this" and never anything else.
   opacity:1 overrides the global a:hover dim — the colour is the signal. */
/* Current page: yellow, no underline. The landing page's own link (RSVP) stays
   plain on first arrival and only turns yellow once you've visited another
   page and come back (html.visited-away, set by an inline script). */
.nav a[aria-current="page"]:not([data-home]),
html.visited-away .nav a[aria-current="page"][data-home] { color: var(--yellow); }
.nav a:hover { color: var(--hover); opacity: 1; }

/* ---------- Mobile nav: hamburger + panel ----------
   The inline .nav above has nowhere to go once the bar narrows down to just
   the wordmark, so under the 860px breakpoint it hides and this button takes
   over. Hidden here and only switched on inside the media query below, so a
   page that somehow lacks the toggle markup (there is currently only the one
   phase-2 header) never shows a dangling button. */

.nav-toggle {
  display: none;
  position: absolute;
  top: 50%;
  right: var(--gutter);
  transform: translateY(-50%);
  /* .nav-panel (a sibling inside .pageheader, needed there so its own
     background sits under the bar) is z-index 49 — this has to beat that
     within .pageheader's own stacking context, not just the page's, or the
     open panel paints over the button that opens/closes it. */
  z-index: 50;
  width: 1.5rem;
  height: 1.0625rem;
  padding: 0;
  border: 0;
  background: none;
  pointer-events: auto;
  cursor: pointer;
}

.nav-toggle__bar {
  position: absolute;
  left: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transition: transform 0.25s ease, opacity 0.2s ease, top 0.25s ease;
}
.nav-toggle__bar:nth-child(1) { top: 0; }
.nav-toggle__bar:nth-child(2) { top: 50%; }
.nav-toggle__bar:nth-child(3) { top: 100%; }

/* Open: the same three bars rotate into an X rather than swapping to a
   second icon, so there is only ever one shape to keep aligned. The middle
   bar fades out — two lines make an X, a third through the middle doesn't. */
body.nav-open .nav-toggle__bar:nth-child(1) { top: 50%; transform: rotate(45deg); }
body.nav-open .nav-toggle__bar:nth-child(2) { opacity: 0; }
body.nav-open .nav-toggle__bar:nth-child(3) { top: 50%; transform: rotate(-45deg); }

.nav-panel { display: none; }

/* ---------- Meta (date — rule — place) ---------- */

.meta {
  display: flex;
  align-items: center;
  gap: 1.125rem;
  font-size: 1.875rem;
  letter-spacing: 0.09375rem;
  color: var(--yellow);
  text-transform: uppercase;
}

.meta__date { position: relative; top: -0.09em; }  /* old-style numerals sit low against the caps */

.meta__rule {
  display: inline-block;
  width: 4.9375rem;  /* 79px */
  height: 1px;
  background: currentColor;
  opacity: 0.85;
  flex: none;
  /* align-items: center centres it against the text's LINE BOX, not the
     glyphs — Cormorant's normal line-height carries noticeably more room
     above the caps than below them (room reserved for accents this all-caps,
     no-descender line never uses), so the line sits visibly above the
     numerals and letters rather than through their middle. Nudged down by
     eye against the rendered font; in em so it holds at any size the meta
     line is used at. */
  position: relative;
  top: 0.03em;
}

/* ---------- Interior page wordmark ---------- */

.wordmark {
  display: flex;
  justify-content: space-between;  /* name left, date/place right, as in the frame */
  /* Centred, not baseline: the date is half the wordmark's size, so sharing a
     baseline drops it well below the optical middle of the bar. Figma centres
     the two boxes on each other. */
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem 1.75rem;
}

.wordmark .meta { white-space: nowrap; }

.wordmark__name {
  font-size: 5.625rem;         /* 90px */
  letter-spacing: 0.28125rem;  /* 4.5px */
  text-transform: uppercase;
  white-space: nowrap;
  line-height: 1.12;
}

.banner__tagline {
  font-family: 'Cormorant Infant', Georgia, serif;
  font-size: 1.25rem;
  letter-spacing: 0.05rem;
  margin-top: 0.875rem;
}

/* ---------- Save the date ---------- */

.stdate {
  display: grid;
  grid-template-areas:
    "name  name"
    "label meta"
    "tag   tag";
  grid-template-columns: 1fr max-content;
  column-gap: 2rem;
  align-items: baseline;
  margin-top: min(22.1875rem, 33vh);  /* wordmark at ~41% down, as in the frame */
}

.stdate__name {
  grid-area: name;
  font-size: 5.625rem;
  letter-spacing: 0.28125rem;
  text-transform: uppercase;
  line-height: 1.12;
}

.stdate__label {
  grid-area: label;
  font-size: 2.5rem;       /* 40px */
  letter-spacing: 0.125rem;
  text-transform: uppercase;
  margin-top: 0.25rem;
}

.stdate__meta { grid-area: meta; justify-self: end; white-space: nowrap; }
.stdate__meta .meta__date  { order: 1; }
.stdate__meta .meta__rule  { order: 2; }
.stdate__meta .meta__place { order: 3; }

.stdate__tagline {
  grid-area: tag;
  font-family: 'Cormorant Infant', Georgia, serif;
  font-style: italic;
  font-size: 1rem;
  letter-spacing: 0.05rem;
  margin-top: 1.0625rem;
}

.scroll-cue {
  align-self: center;
  width: 2px;
  height: 6.9375rem;   /* 111px */
  background: rgba(255, 255, 255, 0.75);
  margin-top: 5.5rem;
  transform-origin: top center;
  /* Fades on scroll, not on the header's progress: that is held flat for the
     first third of the hero now, which left the cue pulsing away long after
     you had started moving. js/site.js fades it over the first ~13% of the
     hero. filter rather than opacity so it multiplies with the pulse
     keyframes instead of fighting them for the same property. */
  filter: opacity(var(--hdr-cue, 1));
  /* Started as the Studio Fritz hero cue's motion (a single grow-pulse in a
     5s cycle). People weren't scrolling past it, so it now shrinks instead of
     growing — a bar that's about to disappear reads as "look, then go" more
     than one that swells — starts almost immediately (0.15s, was 0.6s) and
     repeats on a 1.5s cycle (was 5s, then 2.5s) so the rest between pulses
     stays short. Same keyframe shape otherwise. */
  animation: scrollCuePulse 1.5s ease-out 0.15s infinite both;
}

@keyframes scrollCuePulse {
  0%    { opacity: 0.75; transform: scaleY(1); }
  14.4% { opacity: 1;    transform: scaleY(0.5); }
  36%   { opacity: 0.75; transform: scaleY(1); }
  100%  { opacity: 0.75; transform: scaleY(1); }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-cue { animation: none; }
}

/* ---------- Sections ---------- */

.section { padding: 5.25rem 0 3.75rem; }

/* Phase 2 spacing, straight off the updated frames. Scoped to .phase2 so the
   live save-the-date keeps the spacing it already has.
   Inner pages: bar ends 156, "FAQs" title at 338  -> 182px
   Homepage:    hero ends 1007, "RSVP" title at 1140 -> 133px, the extra room
                that lets the header finish settling before content arrives. */
.phase2 .section { padding-top: 5.75rem; }                /* 92px */
.phase2 .banner--tall + .section { padding-top: 8.3125rem; }  /* 133px */

/* Cue keeps its Figma proportions at any window shape: 43px clear of the
   bottom and 111px tall, of a 1007px hero. */
.phase2 .banner--tall .banner__inner { padding-bottom: calc(var(--hero-h) * 0.0427); }
.phase2 .banner--tall .scroll-cue    { height:         calc(var(--hero-h) * 0.1102); }

/* The inner-page banner is now exactly the height of the header that sits on
   it, so the bar and the photo strip behind it are the same band. */
.phase2 .banner--short { min-height: var(--hdr-h); }   /* 156px */

.section__title--cased { text-transform: none !important; }

.section__title {
  text-transform: uppercase;
  font-size: 2.5rem;
  letter-spacing: 0.125rem;
  margin-bottom: 0.75rem;
}

.section__note {
  font-size: 1rem;
  letter-spacing: 0.05rem;
  max-width: var(--measure);
}

/* ---------- Rows: rule + copy ---------- */

.rows { margin-top: var(--gap-section); }

.row {
  display: grid;
  grid-template-columns: var(--col-rule) 1fr;
  column-gap: var(--col-gap);
  padding-bottom: var(--gap-row);
}

.row__rule { height: 1px; background: var(--rule); margin-top: var(--rule-drop); }

.row__body { max-width: var(--measure); }

.row__label {
  font-size: 1.25rem;         /* 20px */
  letter-spacing: 0.0625rem;
  text-transform: uppercase;
}

.row__text {
  font-size: 1rem;
  letter-spacing: 0.05rem;
  line-height: 1.45;
  margin-top: var(--gap-body);
}

.row__text + .row__text { margin-top: var(--gap-item); }
.row__body > .row__text:first-child { margin-top: 0; }
.row__text a { border-bottom: 1px solid var(--rule); }

/* Bulleted answers (beach and tourism recommendations). */
.row__list {
  list-style: none;
  padding-left: 0;
  margin-top: var(--gap-body);
  font-size: 1rem;
  letter-spacing: 0.05rem;
  line-height: 1.45;
}
.row__list li { position: relative; padding-left: 4rem; }
.row__list li::before { content: ''; position: absolute; left: 0; top: calc(0.78em - 0.5px); width: 3rem; height: 1px; background: var(--rule); }
.row__list li + li { margin-top: 0.375rem; }
.row__list + .row__text { margin-top: var(--gap-item); }
.row__list a { border-bottom: 1px solid var(--rule); }

/* "Visit: ->" under the registry copy. */
.row__text a.visit {
  display: inline-flex;
  align-items: center;
  gap: 1.25rem;
  border-bottom: 0;
}
.visit .arrow { width: 2.375rem; height: 1px; background: currentColor; position: relative; }
.visit .arrow::after {
  content: '';
  position: absolute;
  right: 0;
  top: calc(-0.25rem + 0.5px);
  width: 0.5rem;
  height: 0.5rem;
  border-top: 1px solid currentColor;
  border-right: 1px solid currentColor;
  transform: rotate(45deg);
}

/* ---------- Itinerary ---------- */

.day,
.group {
  font-size: 1.75rem;
  letter-spacing: 0.0625rem;
  margin: var(--gap-section) 0 1.5rem;
}

.day:first-of-type,
.rows > .group:first-child { margin-top: 2.375rem; }

.group { text-transform: uppercase; letter-spacing: 0.09375rem; }

.event {
  display: grid;
  grid-template-columns: var(--col-time) var(--col-rule) 1fr;
  column-gap: var(--col-gap);
  padding-bottom: var(--gap-row);
}

.event__time {
  font-size: 1.25rem;
  letter-spacing: 0.05rem;
  white-space: nowrap;   /* as typed in the sheet: "5:00 PM – late" keeps its lowercase */
}

.event__rule { height: 1px; background: var(--rule); margin-top: var(--rule-drop); }

.event__body { max-width: var(--measure); }

.event__name {
  font-size: 1.25rem;
  letter-spacing: 0.0625rem;
  text-transform: uppercase;
}

.event__name .opt { text-transform: none; font-style: normal; opacity: 0.9; }

.event__text {
  font-size: 1rem;
  letter-spacing: 0.05rem;
  line-height: 1.45;
  margin-top: var(--gap-body);
}

.event__text + .event__text { margin-top: var(--gap-item); }
.event__text a { border-bottom: 1px solid var(--rule); }
.event__text .k { opacity: 0.82; }

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

.field {
  width: 33.125rem;   /* 530px */
  max-width: 100%;
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--white);
  font-family: inherit;
  font-weight: 300;
  font-size: 1rem;
  letter-spacing: 0.05rem;
  padding: 0.4375rem 0.625rem;
  height: 2.25rem;   /* 36px */
  margin-top: 1.25rem;
  border-radius: 0;
}

.field:focus { outline: none; border-color: var(--white); }
.field::placeholder { color: rgba(255, 255, 255, 0.55); }

.guest-list { margin-top: var(--gap-item); display: grid; gap: var(--gap-tight); }

.guest {
  display: flex;
  align-items: center;
  gap: 1rem;
  font-size: 1rem;
  letter-spacing: 0.05rem;
  cursor: pointer;
}

.guest input {
  appearance: none;
  -webkit-appearance: none;
  width: 2.25rem;
  height: 2.25rem;
  border: 1px solid var(--rule);
  background: transparent;
  cursor: pointer;
  flex: none;
  position: relative;   /* the X is positioned against this, not laid out in it */
}

/* Checked state is an X drawn corner to corner, not a filled block — two
   hairline gradients crossing, so it stays a true 1px line at any scale
   rather than blurring the way a rotated border would. */
.guest input:checked::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom right,
      transparent calc(50% - 0.5px), var(--white) calc(50% - 0.5px),
      var(--white) calc(50% + 0.5px), transparent calc(50% + 0.5px)),
    linear-gradient(to bottom left,
      transparent calc(50% - 0.5px), var(--white) calc(50% - 0.5px),
      var(--white) calc(50% + 0.5px), transparent calc(50% + 0.5px));
}

/* ---------- Party code entry ---------- */

.code-entry { position: relative; margin-top: var(--gap-item); max-width: 39.375rem; }
.code-entry__field {
  margin-top: 0;
  width: 100%;
  padding-right: 4.5rem;
  text-transform: uppercase;   /* js/rsvp.js uppercases the value to match */
}

/* The arrow sits inside the field's right edge. It's a real button so it can
   be tabbed to and pressed — the field's own Enter key does the same thing. */
.code-entry__go {
  position: absolute;
  top: 50%;
  right: 1.25rem;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  background: transparent;
  border: none;
  color: var(--white);
  cursor: pointer;
  padding: 0.5rem 0;
}

.code-entry__go:hover { color: var(--hover); }
.code-entry__go .arrow { width: 2.375rem; height: 1px; background: currentColor; position: relative; }
.code-entry__go .arrow::after {
  content: '';
  position: absolute;
  right: 0;
  top: 50%;
  width: 0.5rem;
  height: 0.5rem;
  border-top: 1px solid currentColor;
  border-right: 1px solid currentColor;
  transform: translateY(-50%) rotate(45deg);
}

.diet-list { margin-top: var(--gap-item); display: grid; gap: var(--gap-tight); }

.diet {
  display: grid;
  grid-template-columns: var(--col-time) 1fr;
  align-items: center;
  column-gap: var(--col-gap);
  font-size: 1rem;
  letter-spacing: 0.05rem;
}

.diet .field { margin-top: 0; width: 26.375rem; }  /* 422px */

/* Shown in place of the dietary rows until someone is ticked as attending —
   the question only makes sense once there's a person to ask it about. */
.diet__empty { opacity: 0.7; font-style: italic; margin-top: 0; }

.submit {
  display: inline-flex;
  align-items: center;
  gap: 0.875rem;
  background: transparent;
  border: 0;
  color: var(--white);
  font-family: inherit;
  font-weight: 300;
  font-size: 1.25rem;
  letter-spacing: 0.0625rem;
  cursor: pointer;
  margin-top: 2.125rem;
  padding: 0;
}

.submit:hover:not(:disabled) { color: var(--hover); }
.submit:disabled { opacity: 0.45; cursor: default; }
.submit .arrow { width: 2.375rem; height: 1px; background: currentColor; position: relative; }
.submit .arrow::after {
  content: '';
  position: absolute;
  right: 0;
  top: calc(-0.25rem + 0.5px);
  width: 0.5rem;
  height: 0.5rem;
  border-top: 1px solid currentColor;
  border-right: 1px solid currentColor;
  transform: rotate(45deg);
}

.is-hidden { display: none !important; }

.form-msg {
  font-size: 1rem;
  letter-spacing: 0.05rem;
  margin-top: 1.375rem;
  min-height: 1.2em;
}

.form-msg--error { color: var(--yellow); }
.form-msg--done  { color: var(--pink); }

/* ---------- Footer ---------- */

.footer { padding: 5rem 0 3.5rem; }

.footer__top {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 1.75rem;
}

.footer__name {
  font-size: 2.5rem;
  letter-spacing: 0.125rem;
  text-transform: uppercase;
}

/* The footer wordmark is the way back to the top of the site. */
.footer__name a:hover,
.footer__links a:hover { color: var(--hover); opacity: 1; }

.footer__cols {
  display: grid;
  grid-template-columns: 23.8125rem 1fr auto;  /* 381px */
  gap: 2rem;
  margin-top: var(--gap-group);
  align-items: end;
}

.footer__cols--stdate { grid-template-columns: 1fr auto; margin-top: 1.75rem; }

.footer__links {
  /* Six links read across in pairs; the right-hand link of each pair sits
     flush right so the block ends on one clean edge. */
  display: grid;
  grid-template-columns: auto auto;
  justify-content: start;
  gap: 1.25rem 2rem;
  font-size: 1.25rem;
  letter-spacing: 0.0625rem;
  /* Its own row above the contact/photo grid, so the footer below it stays
     identical to the save-the-date footer. */
  margin-top: 2.375rem;
}

.footer__links a:nth-child(even) { justify-self: end; }

.footer__photo { width: 15.125rem; }  /* 242px */
.footer__photo img { width: 100%; height: auto; object-fit: cover; }

.footer__contact {
  font-size: 1.25rem;
  letter-spacing: 0.0625rem;
  margin-top: 2.125rem;
}



.footer__details {
  list-style: none;
  margin-top: 1.25rem;
  line-height: 1.6;
}

/* The full contact block shows at every width. The one-line version it
   used to collapse to below 860px is gone, and its markup with it. */

.back-to-top { text-align: center; margin-top: 3.5rem; }

.back-to-top a {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 1.625rem;
  font-size: 1.25rem;
  letter-spacing: 0.0625rem;
  text-transform: uppercase;
}

.back-to-top__rule {
  width: 2px;
  height: 4.875rem;   /* 78px, mirrors the scroll cue in the hero */
  background: rgba(255, 255, 255, 0.75);
}

/* ---------- Mobile: rebased on the 402px frame ---------- */

@media (max-width: 860px) {
  :root {
    /* 16 / 402 = 3.9801vw */
    font-size: clamp(13px, 3.9801vw, 17px);

    --gutter:    1rem;       /* 16px */
    --col-rule:  4.0625rem;  /* 65px */
    --measure:   100%;
  }

  /* 873px is the mobile frame's hero, but a phone's visible viewport is
     shorter than its screen once Safari's chrome is on it — an iPhone 17 is
     874 tall and shows about 740. Clamping to svh (the viewport with the
     browser UI expanded, i.e. the smallest it gets) keeps the scroll cue
     above the fold instead of one bar's-worth below it.

     Set on --hero-h rather than on .banner--tall, so everything derived from
     the hero's height agrees with the hero's actual height. Overriding only
     the banner left the two 27px apart on a tall phone. */
  /* Straight off the frame (15:56), no viewport arithmetic. 915px is taller
     than any phone's viewport, so the photo cannot end above the fold in
     either toolbar state, and every position below is the frame's own —
     which is the point: chasing svh/lvh/dvh kept moving things around, and
     the design already says exactly where they go.

     The floor is the only viewport unit left, and only so a phone taller
     than 915 could never show a band of green under the photo. */
  :root { --hero-h: 57.1875rem; }  /* 915px, image 15:69 */
  @supports (height: 100svh) {
    :root { --hero-h: max(57.1875rem, 100svh, 100vh); }
  }

  .banner--short { min-height: 11.875rem; }

  /* On a 402px-wide frame Figma scales the photo to 1311px and pushes it to
     x = -216, so the visible strip starts 216px into 909px of overflow.
     217 / (1374 - 402) = 22.32%. Centring here would show the wrong flower. */
  .banner { background-position: 22.32% top; }

  .banner__inner { padding-top: 1.5rem; padding-bottom: 1.75rem; }
  /* The cue runs y642-720 in a 915 hero, so it sits 195px up from the
     bottom. Fixed, like everything else here — and it lands at 720, well
     inside the 742 a phone shows with its toolbars up. */
  .banner--tall .banner__inner { padding-bottom: 12.1875rem; }

  /* The inline nav hands off to the hamburger under this width — there's no
     room left beside the wordmark for both. */
  .pageheader .nav { display: none; }
  .nav-toggle { display: block; }

  body.nav-open { overflow: hidden; }

  .nav-panel {
    position: fixed;
    inset: 0;
    z-index: 49;   /* under the fixed .pageheader (50), so the bar still reads on top */
    padding: calc(var(--hdr-h) + var(--hdr-inset, 0px) + 2.5rem) var(--gutter) 2.5rem;
    background: #efe6e6 url('../orchid.jpg') top center / cover no-repeat;
    flex-direction: column;
    align-items: center;
    overflow-y: auto;
  }

  /* .nav-panel stays display:none (set outside this media query) until the
     toggle opens it — this is the only rule that turns it on. */
  body.nav-open .nav-panel { display: flex; }

  .nav-panel__links {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2.75rem;   /* 44px, matching the Figma vertical rhythm */
    font-size: 1.5rem;
    letter-spacing: 0.075rem;
  }

  .nav-panel__links a[aria-current="page"]:not([data-home]),
  html.visited-away .nav-panel__links a[aria-current="page"][data-home] { color: var(--yellow); }
  .nav-panel__links a:hover { color: var(--hover); opacity: 1; }

  .nav-panel__photo { width: 11rem; margin-top: 3.5rem; }
  .nav-panel__photo img { width: 100%; height: auto; object-fit: cover; }

  .nav { font-size: 0.875rem; letter-spacing: 0.04375rem; gap: 0.625rem; }

  .wordmark { flex-direction: column; align-items: flex-start; gap: 0.375rem; }
  .wordmark__name { font-size: 3rem; letter-spacing: 0.15rem; white-space: normal; }

  .meta { font-size: 1rem; gap: 0.75rem; }
  .meta__rule { width: 2.6875rem; }  /* 43px */

  /* Save the date: date and place move above the wordmark */
  .stdate {
    grid-template-areas:
      "meta"
      "name"
      "label"
      "tag";
    grid-template-columns: 1fr;
    align-items: start;
    margin-top: 30vh;
  }
  @supports (height: 100svh) { .stdate { margin-top: 26svh; } }

  .stdate__rest { display: none; }              /* "E & L" only, per the mobile frame */
  .stdate__name { font-size: 3.5rem; letter-spacing: 0.175rem; margin-top: 1.5rem; }
  .stdate__label { font-size: 1.25rem; letter-spacing: 0.0625rem; margin-top: 0.5rem; }
  .stdate__tagline { font-size: 0.9375rem; margin-top: 0.5rem; }

  .stdate__meta {
    justify-self: start;
    white-space: normal;
    flex-wrap: wrap;
    gap: 0.375rem 0.75rem;
    font-size: 0.9375rem;
  }

  .stdate__meta .meta__rule  { order: 1; }
  .stdate__meta .meta__date  { order: 2; }
  .stdate__meta .meta__place { order: 3; flex-basis: 100%; }

  /* 460/873 in the mobile frame — the hero is shorter, so the wordmark sits
     proportionally lower than it does on desktop. */
  /* 345/915: where the date — the first line of the block — sits in the
     frame. The wordmark and the two lines under it follow from it. */
  .pageheader--stdate { --hero-frac: 0.377; }

  /* The bar's photo strip is its own crop in the frame, not a slice of the
     hero's: bar frame 78:243 draws the image 1493 wide at x=-307 in a 402
     frame, so it is scaled up about 3.7x and pulled left, which is what puts
     the dark petal behind the wordmark. Percentages rather than pixels so it
     holds at any phone width.
       1493 / 402       = 371.4%
       307 / (1493-402) = 28.14%
     Recovered by matching the frame's render against orchid.jpg rather than
     read off the node, because the crop lives in Figma's image fill and the
     node only reports the box. Source rect x374 y144, 340 wide — the tulle
     fold, with the petal entering at the right. */
  .pageheader__bg {
    background-size: 376.5% auto;
    background-position: 39.79% 19.81%;
  }

  /* The header sets the wordmark's size by calc, which outranks the plain
     .wordmark__name rule below — so the mobile sizes have to be set as the
     header's own tokens or the bar keeps the desktop 90px. */
  .pageheader {
    --hdr-word-hi:  3rem;      /* 48px, the mobile frame's hero wordmark */
    --hdr-track-hi: 0.15rem;
    --hdr-word-lo:  2.5rem;    /* 40px settled — the bar frame (78:248) and
                                  the footer wordmark are the same size */
    --hdr-track-lo: 0.125rem;
  }

  .pageheader--stdate {
    --hdr-word-hi:  3.5rem;    /* 56px — "E & L" alone, so it can be larger */
    --hdr-track-hi: 0.175rem;
  }

  /* Column order, so date and place sit above the name as the frame has
     them. The DOM keeps the name first because it is the page heading. */
  .pageheader--stdate .pageheader__wordmark {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    /* 28px clear of where the nav would be in the hero, nothing in the bar:
       the mobile bar has no nav, and the frame (78:243) starts the date at
       24 — which is the header's own padding and nothing more. */
    margin-top: calc(1.75rem * (1 - var(--hdr-p)));
  }

  .pageheader--stdate .pageheader__wordmark > .meta {
    order: 1;
    float: none;
    display: flex;
    flex-wrap: wrap;
    gap: 0.375rem 0.75rem;
    white-space: normal;
    font-size: 0.9375rem;
  }
  .pageheader--stdate .pageheader__wordmark > .meta .meta__rule  { order: 1; }
  .pageheader--stdate .pageheader__wordmark > .meta .meta__date  { order: 2; }
  .pageheader--stdate .pageheader__wordmark > .meta .meta__place { order: 3; flex-basis: 100%; }

  /* 69px in the hero — the frame has the place line ending at 389 and E & L
     starting at 460 — but 11px once settled, where the bar frame (78:243)
     puts the place at 50 and E & L at 80. It has to travel between the two
     with everything else, or the 69px hero gap pushes the wordmark straight
     out of the bottom of a 156px bar. */
  .pageheader--stdate .wordmark__name {
    order: 2;
    display: block;
    margin-top: calc(4.3125rem + (0.6875rem - 4.3125rem) * var(--hdr-p));
  }
  .pageheader--stdate .stdate__label  { order: 3; }
  .pageheader--stdate .stdate__tagline { order: 4; }

  .scroll-cue { height: 4.875rem; margin-top: 3rem; }

  .section { padding: 3rem 0 2.5rem; }
  .section__title { font-size: 2rem; }

  .row,
  .event,
  .diet { grid-template-columns: 1fr; column-gap: 0; }

  .row__rule,
  .event__rule { width: var(--col-rule); margin: 0 0 0.75rem; }

  .row__label, .event__name { font-size: 1.0625rem; }
  .row__text, .event__text { font-size: 0.9375rem; }

  .event__time { margin-bottom: 0.5rem; font-size: 0.9375rem; }

  .day,
.group { font-size: 1.375rem; margin: 2.25rem 0 1rem; }

  .field { width: 100%; }
  .diet .field { margin-top: 0.375rem; width: 100%; }

  /* The mobile footer is the desktop footer RESTACKED. The wordmark and the
     78px rule keep their desktop size; the contact block and back-to-top drop
     to 16px. What changes most is the order and the alignment. Frame 15:56.

       wordmark   40px    y2453
       contact intro 16px y2529   (+28 from the wordmark)
       email/phone/website y2595   (+28)
       date, then place    y2708   (+56)   rule first, place on its own line
       photo, centred      y2807   (+55)
       back to top  16px   y3013   (+48)

     The DOM keeps the desktop order because that is the one that reads
     correctly without stylesheets; `display: contents` dissolves the two
     wrappers so the pieces can be ordered as one column. Phase 2's footer
     has a nav row and its own grid, so this is scoped away from it. */
  .footer { padding: 3rem 0 2.5rem; }
  .footer__cols { grid-template-columns: 1fr 1fr; align-items: start; }
  .footer__cols--stdate { grid-template-columns: 1fr; }
  .footer__links { font-size: 1rem; gap: 0.875rem 1.5rem; }
  .footer__photo { width: 15.125rem; max-width: 100%; }

  body:not(.phase2) .footer .wrap { display: flex; flex-direction: column; }
  body:not(.phase2) .footer__top,
  body:not(.phase2) .footer__cols--stdate { display: contents; }

  body:not(.phase2) .footer__name      { order: 1; }
  body:not(.phase2) .footer__contact   { order: 2; margin-top: 1.75rem;
                                         font-size: 1rem; }
  body:not(.phase2) .footer__details   { margin-top: 1.75rem; }
  body:not(.phase2) .footer__top .meta { order: 3; margin-top: 2.5rem; }
  body:not(.phase2) .footer__photo     { order: 4; margin-top: 3.5rem;
                                         align-self: center; }
  body:not(.phase2) .back-to-top       { order: 5; margin-top: 4rem; }

  /* Rule and date on one line, place beneath — as the hero's meta already
     does on this width. */
  body:not(.phase2) .footer__top .meta {
    flex-wrap: wrap;
    white-space: normal;
    gap: 0.375rem 0.75rem;
  }
  body:not(.phase2) .footer__top .meta .meta__rule  { order: 1; }
  body:not(.phase2) .footer__top .meta .meta__date  { order: 2; }
  body:not(.phase2) .footer__top .meta .meta__place { order: 3; flex-basis: 100%; }

  .back-to-top a { gap: 1.25rem; font-size: 1rem; }
}

/* ==========================================================================
   Phase 2 desktop sizing, from the updated Figma Homepage frame (6:29).
   Desktop only (the mobile frames are unchanged) and scoped to .phase2 so the
   live save-the-date keeps its own scale.

     margins 49px both sides · page title 64px · body, labels, nav 30px
     checkboxes 36px · fields 530 x 48 · footer wordmark 64px, footer date 40px
   ========================================================================== */
@media (min-width: 861px) {
  :root {
    --gutter:    3.0625rem;   /* 49px */
    --measure:   51.1875rem;  /* 819px */
    --col-time:  17rem;       /* itinerary time column, fits 30px "4:00 PM – 10:00 PM" */

    --rule-drop:   1.125rem;  /* 18px: rule meets the middle of a 30px line */
    --gap-tight:   1.3125rem; /* 21px */
    --gap-body:    2rem;
    --gap-item:    1.75rem;
    --gap-group:   3rem;
    --gap-row:     5.125rem;  /* 82px, Figma row to row */
    --gap-section: 4.25rem;

    --hdr-nav-top-hi: 1.5rem;     /* nav sits at y=24 in the hero too */
    --hdr-nav-lo:     1.875rem;   /* 30px, hero and settled */
    --hdr-navtrk-lo:  0.09375rem; /* 1.5px */
  }

  /* Header: nav is 30px, so the wordmark's clearance under it shrinks to land
     the settled wordmark where the frame has it (y=76). */
  .phase2 .pageheader__wordmark { margin-top: 1rem; }

  /* Page title and the line under it. */
  .phase2 .section__title {
    font-size: 4rem;           /* 64px */
    letter-spacing: 0.2rem;    /* 3.2px */
    margin-bottom: 0.6875rem;
  }
  .phase2 .banner--tall + .section { padding-top: 3.5625rem; }  /* 57px: 1007 -> 1064 */
  .phase2 .section__note { font-size: 1.5625rem; letter-spacing: 0.078125rem; }

  /* Body copy, labels, times: 25px (headings/nav/forms stay 30). */
  .phase2 .row__label,
  .phase2 .event__name,
  .phase2 .event__time { font-size: 1.5625rem; letter-spacing: 0.078125rem; }
  .phase2 .row__text,
  .phase2 .row__list,
  .phase2 .event__text,
  .phase2 .form-msg { font-size: 1.5625rem; letter-spacing: 0.078125rem; }
  .phase2 .row__list li { padding-left: 4.5rem; }
  .phase2 .row__list li::before { top: calc(0.78em - 0.5px); width: 3rem; }

  .phase2 .day,
  .phase2 .group { font-size: 2.5rem; letter-spacing: 0.125rem; }

  /* Form: 36px boxes, 530 x 48 fields. */
  .phase2 .field {
    font-size: 1.875rem;
    letter-spacing: 0.09375rem;
    height: 3rem;                 /* 48px */
    padding: 0 0.9375rem;
  }
  .phase2 .code-entry { max-width: 33.125rem; margin-top: 2.6875rem; }
  .phase2 .code-entry__field { padding-right: 4.5rem; }
  .phase2 .code-entry__go { right: 1.125rem; }
  .phase2 .guest-list { margin-top: 1.375rem; }
  .phase2 .guest { font-size: 1.875rem; letter-spacing: 0.09375rem; gap: 1.3125rem; }
  .phase2 .diet-list { margin-top: 1.8125rem; gap: 1.1875rem; }
  .phase2 .diet {
    grid-template-columns: 15rem 1fr;   /* labels at 306, fields at 546 */
    font-size: 1.875rem;
    letter-spacing: 0.09375rem;
  }
  .phase2 .diet .field { width: 33.125rem; }
  .phase2 .submit {
    font-size: 1.875rem;
    letter-spacing: 0.09375rem;
    gap: 2rem;
    margin-top: 5.25rem;
  }
  .phase2 .visit .arrow,
  .phase2 .code-entry__go .arrow,
  .phase2 .submit .arrow { width: 1.9375rem; }   /* 31px */
  .phase2 .visit .arrow::after,
  .phase2 .code-entry__go .arrow::after,
  .phase2 .submit .arrow::after { width: 0.4375rem; height: 0.4375rem; }
  .phase2 .row__text a.visit { gap: 2rem; }

  /* Footer. One grid: wordmark across the top, links and contact down the
     left, the photo tucked up at the right beside them. */
  .phase2 .footer { padding-bottom: 5rem; }
  .phase2 .footer .wrap {
    display: grid;
    grid-template-columns: 1fr auto;
    column-gap: 2rem;
    align-items: start;
  }
  .phase2 .footer__top { grid-column: 1 / -1; flex-wrap: nowrap; align-items: center; }
  .phase2 .footer__name { font-size: 4rem; letter-spacing: 0.2rem; white-space: nowrap; }
  .phase2 .footer__top .meta { font-size: 2.5rem; letter-spacing: 0.125rem; white-space: nowrap; }

  .phase2 .footer__links {
    grid-column: 1;
    grid-row: 2;
    width: 28.0625rem;               /* 449px: 48 -> 497 */
    grid-template-columns: auto auto;
    justify-content: space-between;
    gap: 0.4375rem 2rem;
    font-size: 1.875rem;
    letter-spacing: 0.09375rem;
    margin-top: 0.875rem;
  }
  /* An odd link out (Registry is hidden for now) takes the whole last row. */
  .phase2 .footer__links a:last-child:nth-child(odd) { grid-column: 1 / -1; }

  .phase2 .footer__cols { display: contents; }
  .phase2 .footer__contact {
    grid-column: 1;
    grid-row: 3;
    font-size: 1.875rem;
    letter-spacing: 0.09375rem;
    margin-top: 4.5rem;
  }
  .phase2 .footer__details { margin-top: 0.375rem; line-height: normal; }
  .phase2 .footer__photo {
    grid-column: 2;
    grid-row: 2 / span 2;
    width: 15.125rem;                /* 242px */
    height: 9.875rem;                /* 158px */
    overflow: hidden;
  }
  .phase2 .footer__photo img { height: 100%; }
  .phase2 .back-to-top { grid-column: 1 / -1; margin-top: 0; }
  .phase2 .back-to-top a { gap: 0.8125rem; }
}

/* ==========================================================================
   Accessible view  (html[data-contrast="high"], switched on by ?view=accessible)

   Figma "Accessible" frames: same layout, white page, black type, thin black
   rules. Boxes only where you type or tick (the fields and checkboxes already
   have one); nothing else is outlined. Type is 20% larger, below.
   ========================================================================== */

html[data-contrast="high"] {
  --olive:   #fff;
  --white:   #000;
  --yellow:  #000;
  --pink:    #000;
  --hover:   #000;
  --rule:    #000;
}

/* No photography behind text: header, hero, and the mobile menu go white. */
html[data-contrast="high"] .banner,
html[data-contrast="high"] .pageheader__bg,
html[data-contrast="high"] .nav-panel { background: #fff; }

html[data-contrast="high"] .scroll-cue,
html[data-contrast="high"] .back-to-top__rule { background: #000; }

html[data-contrast="high"] a:hover { text-decoration: underline; }
/* Figma "Accessible" shows the nav with no current-page underline. */
html[data-contrast="high"] .nav a[aria-current="page"],
html[data-contrast="high"] .nav-panel__links a[aria-current="page"] { border-bottom: 0; }

/* Medium instead of Light: the Light weight is the design's, but it is thin on
   a white page for tired eyes. The 500 face is fetched by the early script in
   <head> only for this view; until it arrives the Light face shows. Headings
   and form controls set their own weight, so they are named here too. */
html[data-contrast="high"] body,
html[data-contrast="high"] h1, html[data-contrast="high"] h2,
html[data-contrast="high"] h3, html[data-contrast="high"] h4,
html[data-contrast="high"] h5, html[data-contrast="high"] h6,
html[data-contrast="high"] .field,
html[data-contrast="high"] button { font-weight: 500; }

/* Dimmed text goes full strength. */
html[data-contrast="high"] .event__text .k,
html[data-contrast="high"] .event__name .opt,
html[data-contrast="high"] .diet__empty { opacity: 1; }

html[data-contrast="high"] .field::placeholder { color: #444; }
html[data-contrast="high"] a:focus-visible,
html[data-contrast="high"] button:focus-visible,
html[data-contrast="high"] .field:focus { outline: 2px solid #000; outline-offset: 3px; }

/* Desktop: the accessible view uses exactly the standard view's sizes. The
   standard type was raised to 30px body / 64px titles from the updated Figma,
   which already covers the +20% this view used to add, so no root override. */
/* On a phone the fixed header is cut to the pixel for a 402px frame and has no
   room to grow, so there the 20% is applied to the content (page body, footer,
   menu links) and the header stays as designed. */
@media (max-width: 860px) {
  html[data-contrast="high"] { font-size: clamp(13px, 3.9801vw, 17px); }
  html[data-contrast="high"] .section,
  html[data-contrast="high"] .footer,
  html[data-contrast="high"] .nav-panel__links { zoom: 1.2; }
  /* RECOMMENDATIONS is the widest word on the site; at +20% it no longer fits
     a phone, so the page titles sit a notch below the scaled size. */
  html[data-contrast="high"] .section__title { font-size: 1.65rem; letter-spacing: 0.0625rem; }
}
