/* ==============================================================
   YOURI & NIKKESHA - WEDDING INVITATION . STYLESHEET
   All styling for the site lives in this one file.

   * COLOURS - the palette is at the very top (":root"):
      ivory / sage green #8CAE9F / gold. Change any colour of the
      whole site there, in one place.

   Sections: [C0] palette . [C0b] layout (grid/utilities)
     [C1] envelope . [C2] site base . [C2b] utilities
     [C3] RSVP button . [C4] hero . [C5] shared . [C6] story
     [C7] events . [C8] venue . [C9] gallery . [C10] rsvp
     [C11] footer . [C12] timeline
============================================================== */
/* ===== [C0] PALETTE - change any colour of the whole site here ===== */
:root {
  /* === PALETTE - Ivory (main) . Sage green (detail) . Gold (secondary detail) ===
     Change any colour of the whole site here in one place. */
  --ivory:#f8f4e9; --ivory2:#efe7d3; --ivory3:#e2d8bd; --cream:#fbf8f2;
  --sage:#8CAE9F; --sage-lt:#B4CBC1; --sage-dk:#496E5E; --sage-xdk:#22352D;
  --gold:#c8a84f; --gold-lt:#e6cd84; --gold-dk:#9c7a2c;
  --text:#2a1e0a; --text-lt:#5a4a2a;
}
/* === ENVELOPE PAPER - your floral lace paper, embedded (used on every face of the envelope) === */
:root { --paper-band:url("../assets/paper-band.webp"); }

:root { --paper:url("../assets/paper-lace.webp"); }
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
:root{ --font-scale:1; }   /* global text-size dial (set from js/settings.js) */

/* -- Wax seal sizes: desktop / phone ------------------------------
   js/settings.js pushes two values per seal (--seal-scale for wide
   screens, --seal-scale-m for phones). These two lines pick the one
   that applies; every rule below then reads only the "-now" name, so
   the choice is made once, here. Leaving the phone value out simply
   falls back to the desktop one.                                    */
:root{
  --seal-scale-now: var(--seal-scale, 1);
  --fab-scale-now : var(--fab-scale,  1);
  /* Where the floating RSVP seal sits, and how far its label stands off it.
     The fallbacks are what the old clamp() resolved to on a wide screen. */
  --fab-side-now  : var(--fab-side,   28px);
  --fab-bottom-now: var(--fab-bottom, 28px);
  --fab-gap-now   : var(--fab-gap,    12px);
}
@media(max-width:640px){
  :root{
    --seal-scale-now: var(--seal-scale-m, var(--seal-scale, 1));
    --fab-scale-now : var(--fab-scale-m,  var(--fab-scale,  1));
    /* and what it resolved to on a phone */
    --fab-side-now  : var(--fab-side-m,   var(--fab-side,   14px));
    --fab-bottom-now: var(--fab-bottom-m, var(--fab-bottom, 14px));
    --fab-gap-now   : var(--fab-gap-m,    var(--fab-gap,    12px));
  }
}
html{scroll-behavior:smooth;font-size:calc(100% * var(--font-scale, 1));}
body{font-family:'Cormorant Garamond',serif;background:#0F1613;color:var(--text);overflow-x:hidden;}
body.env-open{overflow:hidden;height:100vh;}

/* ===== [C0b] LAYOUT - small replacement for the handful of Bootstrap
   grid/utility classes this page actually used (container, row, col-*,
   gutters, alignment). Bootstrap itself has been removed: it added a
   ~230KB stylesheet + JS bundle for about a dozen classes, almost all
   of it unused. This does the same job in ~30 lines. ===== */
/* CONTENT WIDTH AND SIDE MARGINS - set from SETTINGS.contentLayout.
   --content-pad is the breathing space between the text and the edge of
   the screen. --content-max caps how wide the content column ever gets:
   every step below is wrapped in min(), so lowering it narrows the whole
   site at once while narrow screens keep their own smaller width. */
.container{
  width:100%;
  padding-left:var(--content-pad,12px);
  padding-right:var(--content-pad,12px);
  margin-left:auto;margin-right:auto;
}
@media(min-width:576px) {.container{max-width:min(540px,  var(--content-max,100%));}}
@media(min-width:768px) {.container{max-width:min(720px,  var(--content-max,100%));}}
@media(min-width:992px) {.container{max-width:min(960px,  var(--content-max,100%));}}
@media(min-width:1200px){.container{max-width:min(1140px, var(--content-max,100%));}}
@media(min-width:1400px){.container{max-width:min(1320px, var(--content-max,100%));}}
@media(max-width:991px){ .container{padding-left:var(--content-pad-tablet,var(--content-pad,12px));
                                    padding-right:var(--content-pad-tablet,var(--content-pad,12px));} }
@media(max-width:575px){ .container{padding-left:var(--content-pad-phone,var(--content-pad,12px));
                                    padding-right:var(--content-pad-phone,var(--content-pad,12px));} }
.row{
  --gx: .75rem; /* half-gutter, both sides - 1.5rem gutter by default */
  display:flex;
  flex-wrap:wrap;
  margin-left:calc(var(--gx) * -1);
  margin-right:calc(var(--gx) * -1);
  row-gap:0;
}
.row.g-5{ --gx:1.5rem; row-gap:3rem; }
/* Adding a width? It has to go in BOTH places - this list, which is what
   gives a column its half-gutter padding, and the media query below. A
   width with no entry here is a column with no gutter, flush against its
   neighbour. */
.col-12,.col-md-8,.col-md-10,.col-lg-4,.col-lg-5,.col-lg-7,.col-lg-8{
  padding-left:var(--gx);
  padding-right:var(--gx);
  flex:0 0 auto;
  width:100%;
}
@media(min-width:768px){
  .col-md-8{width:66.66667%;}
  .col-md-10{width:83.33333%;}
}
@media(min-width:992px){
  .col-lg-4{width:33.33333%;}
  .col-lg-5{width:41.66667%;}
  .col-lg-7{width:58.33333%;}
  .col-lg-8{width:66.66667%;}
  .order-lg-1{order:1;}
  .order-lg-2{order:2;}
}
.align-items-center{align-items:center;}
.align-items-start{align-items:start;}
.justify-content-center{justify-content:center;}
.text-center{text-align:center;}
.position-relative{position:relative;}
.mb-5{margin-bottom:3rem;}

/* ===== [C1] ENVELOPE OVERLAY - two lace panels + wax seal ===== */
/* ===============================
   ENVELOPE SCREEN
=============================== */
#envScreen {
  position:fixed;inset:0;z-index:500;
  background:transparent; /* the live website sits right behind the panels */
  cursor:default;
}
#envScreen.gone{display:none;}

/* The envelope box - fills the entire screen (folds and flap are
   percentage-based, so the geometry adapts to any screen shape) */
.env-box {
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
}

/* === THE ENVELOPE = TWO PAPER PANELS over the live website.
   The left half is an envelope fold with a triangle point that
   lies over the right half - the wax seal rests on the point.
   On open, both halves slide apart and the site is unveiled
   from the middle outward. === */
.env-panel {
  position:absolute;top:0;bottom:0;
  background-color:#eee2d2;
  background-size:auto, cover;
  transition:transform 1.9s cubic-bezier(.65,.05,.2,1) .45s;
  will-change:transform;
}
/* Right half - concave inward triangle that receives the fold's point.
   Its notch runs parallel to the fold's edges (2% hidden overlap so the
   seal is airtight until it opens). */
.env-right {
  right:0;width:60%;
  z-index:5;
  background-image:
    linear-gradient(240deg, rgba(255,255,250,.18) 0%, rgba(255,255,250,0) 50%, rgba(90,74,36,.28) 100%),
    linear-gradient(to left, transparent 60%, rgba(70,55,25,.14)),
    var(--paper);
  background-position:center, center, right center;
  box-shadow:inset 0 0 90px rgba(70,55,25,.12);
  clip-path:polygon(0 0, 100% 0, 100% 100%, 0 100%, 40% 50%);
  /* plain border line on the notch edge + layered shadow */
  filter:
    drop-shadow(-1.8px 0 0 rgba(120,98,55,.55))
    drop-shadow(-4px 0 5px rgba(45,34,12,calc(.38 * var(--edge-cast,1))))
    drop-shadow(-12px 0 20px rgba(45,34,12,calc(.36 * var(--edge-cast,1))))
    drop-shadow(-28px 0 50px rgba(30,22,8,calc(.26 * var(--edge-cast,1))));
}
/* cast shadow painted onto this panel along the fold's edge -
   the dark seam between the two papers that sells the overlap */
.fold-cast-shadow{position:absolute;inset:0;width:100%;height:100%;z-index:3;pointer-events:none;}
.fold-cast-shadow .cast-a{stroke:rgba(40,30,10,calc(.5  * var(--edge-cast,1)));}
.fold-cast-shadow .cast-b{stroke:rgba(40,30,10,calc(.34 * var(--edge-cast,1)));}
.fold-cast-shadow .cast-c{stroke:rgba(30,22,8,calc(.24 * var(--edge-cast,1)));}

/* matching plain paper band along the concave notch */
.env-right::after {
  content:'';position:absolute;inset:0;z-index:2;
  background-color:#eee1d2;
  background-image:
    linear-gradient(240deg, rgba(255,253,245,.2) 0%, transparent 55%, rgba(110,90,50,.09) 100%),
    var(--paper-band);
  background-size:auto, 151px 112px;
  background-repeat:no-repeat, repeat;
  background-position:center, 63px 41px;
  clip-path:polygon(0 0, 40% 50%, 0 100%,
    calc(4.4% * var(--edge-band-width,1)) 100%,
    calc(40% + 4% * var(--edge-band-width,1)) 50%,
    calc(4.4% * var(--edge-band-width,1)) 0);
  filter:
    drop-shadow(-1px 0 0 rgba(255,255,252,calc(.8 * var(--edge-rim,1))))
    drop-shadow(2.5px 0 1px rgba(85,64,30,calc(.65 * var(--edge-groove,1))))
    drop-shadow(2.5px 0 0 rgba(255,255,250,calc(.85 * var(--edge-groove,1))))
    drop-shadow(1.5px 0 2px rgba(85,64,30,calc(.3 * var(--edge-groove,1))));
}
/* Left half - the fold: straight edge at centre, triangle point
   reaching over the right half (point tip at 58% of the screen) */
.env-left {
  left:-4%;width:70%; /* 4% bleed past the screen edge for the over-slide */
  z-index:6;
  background-image:
    linear-gradient(120deg, rgba(255,255,250,.36) 0%, rgba(255,255,250,.03) 55%, rgba(90,74,36,.16) 100%),
    var(--paper);
  background-position:center, left center;
  clip-path:polygon(0 0, 65.7% 0, 100% 50%, 65.7% 100%, 0 100%);
  /* plain border line + heavy layered shadow: the fold visibly
     floats OVER the paper beneath it */
  filter:
    drop-shadow(1.8px 0 0 rgba(120,98,55,.55))
    drop-shadow(5px 0 6px rgba(40,30,10,calc(.52 * var(--edge-cast,1))))
    drop-shadow(14px 0 24px rgba(40,30,10,calc(.46 * var(--edge-cast,1))))
    drop-shadow(34px 0 62px rgba(25,18,6,calc(.34 * var(--edge-cast,1))));
}
/* curvature shading - the paper darkens as it turns toward the edge */
.env-left::after {
  content:'';position:absolute;top:0;bottom:0;right:0;width:40%;z-index:1;
  background:linear-gradient(to left,
    rgba(50,38,12,.42) 0%, rgba(55,42,14,.22) 22%,
    rgba(55,42,14,.08) 50%, transparent 78%);
  clip-path:polygon(0 0, 13.7% 0, 100% 50%, 13.7% 100%, 0 100%);
}
/* plain paper band edging the fold - smooth ivory trim against the
   textured lace, like the border strip on a real lined envelope */
.env-left::before {
  content:'';position:absolute;inset:0;z-index:2;
  /* felt-marked paper, pre-tinted to the lace's exact colours - seamless tile */
  background-color:#eee1d2;
  background-image:
    linear-gradient(120deg, rgba(255,253,246,.34) 0%, rgba(255,253,246,.06) 55%, rgba(110,90,50,.1) 100%),
    var(--paper-band);
  background-size:auto, 151px 112px;
  background-repeat:no-repeat, repeat;
  clip-path:polygon(65.7% 0, 100% 50%, 65.7% 100%,
    calc(65.7% - 4.2% * var(--edge-band-width,1)) 100%,
    calc(100% - 4.1% * var(--edge-band-width,1)) 50%,
    calc(65.7% - 4.2% * var(--edge-band-width,1)) 0);
  /* raised strip: lit rim on the outer edge, then a groove (dark
     hairline) and a ridge-light where the band meets the lace -
     the band visibly stands proud of the paper */
  filter:
    drop-shadow(1px 0 0 rgba(255,255,252,calc(.8 * var(--edge-rim,1))))
    drop-shadow(-2.5px 0 1px rgba(85,64,30,calc(.65 * var(--edge-groove,1))))
    drop-shadow(-2.5px 0 0 rgba(255,255,250,calc(.85 * var(--edge-groove,1))))
    drop-shadow(-1.5px 0 2px rgba(85,64,30,calc(.3 * var(--edge-groove,1))));
}

/* === AUTO-OPEN - the fold first glides a touch OVER the paper
   (like a flap being slid free), then both halves sweep apart === */
#envScreen.open .env-left {
  animation:foldSlide 2.1s cubic-bezier(.65,.05,.2,1) .45s forwards;
}
#envScreen.open .env-right { transform:translateX(112%); transition-delay:.75s; }
@keyframes foldSlide {
  0%   { transform:translateX(0); }
  16%  { transform:translateX(calc(2.6% * var(--edge-overslide,1))); }   /* the little slide over the paper */
  100% { transform:translateX(-118%); }
}

/* Wax seal - sits at flap fold point (centre of envelope) */
.env-seal {
  position:absolute;
  /* rests on the fold's point */
  left:66%;top:50%;
  transform:translate(-50%,-50%);
  width:calc(clamp(84px,14vw,150px) * var(--seal-scale-now,1));
  height:calc(clamp(84px,14vw,150px) * var(--seal-scale-now,1));
  z-index:9;

  /* NO transition on transform here, deliberately.
     There used to be one - "rides away with the fold" - but the seal
     does not ride away by transition at all: the open is driven by the
     sealSlide KEYFRAMES below, which ignore it entirely. So the only
     thing that transition could ever do was cause the bug it looked
     innocent enough to hide.

     translate(-50%,-50%) resolves against the seal's OWN width, and
     that width comes from clamp(84px,14vw,150px) x --seal-scale. If
     anything settles a moment after the first paint - the viewport
     width firming up, a slow stylesheet, the phone's address bar - the
     resolved transform changes, and a transition on transform turns
     that correction into a 1.9-second glide across the screen. The
     seal appeared to drift into position on a cold load.

     Nothing here transitions now, so the seal is simply drawn where it
     belongs, in the first frame. It also explains why this never showed
     up in a layout-shift measurement: transform changes are not layout
     shifts, so every CLS check reported a perfect zero. */
}
/* The seal is shown at full size straight away - identical on a fresh
   load and from cache. A gentle fade-in plays if supported, but the
   seal's real, resting state is fully visible: it never depends on an
   animation finishing, so it can't get "stuck" invisible. */
.env-seal img{
  position:absolute;inset:0;width:100%;height:100%;object-fit:contain;display:block;
  filter:drop-shadow(0 5px 9px rgba(10,14,6,.5)) drop-shadow(0 1px 2px rgba(10,14,6,.4));
  opacity:1;
  animation:sealFadeIn .5s ease;
}
@keyframes sealFadeIn{
  from{ opacity:0; }
  to  { opacity:1; }
}
/* Monogram stamped over the seal - scales with the seal automatically */
.seal-mono-svg{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;transition:opacity .45s ease;display:none;}
/* the engraved monogram belongs to the built-in gold seal - it only
   appears when the custom seal is missing and the gold one steps in */
.env-seal.seal-fallback .seal-mono-svg{display:block;}
/* the seal stays whole and rides the fold - same little over-slide,
   same sweep, same distance (118% of the 66vw panel), as one piece */
#envScreen.open .env-seal {
  animation:sealSlide 2.1s cubic-bezier(.65,.05,.2,1) .45s forwards;
}
@keyframes sealSlide {
  0%   { transform:translate(-50%, -50%); }
  16%  { transform:translate(calc(-50% + 1.82vw * var(--edge-overslide,1)), -50%); }
  100% { transform:translate(calc(-50% - 82.6vw), -50%); }
}

/* ===== [C2] SITE BASE - website phase, typography, layout ===== */
/* ===============================
   WEBSITE PHASE
=============================== */
#websitePhase {
  display:none;
  flex-direction:column;
  background:#faf7f0;
  opacity:0;
  transition:opacity .8s ease;
  z-index:300;
}
#websitePhase.visible{display:flex;opacity:1;}

/* ===== [C2b] UTILITIES - lace background tile ===== */
.lace-bg{
  background-color:var(--cream);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='80'%3E%3Ccircle cx='40' cy='40' r='30' fill='none' stroke='rgba(170,135,55,0.08)' stroke-width='1'/%3E%3Ccircle cx='40' cy='40' r='18' fill='none' stroke='rgba(170,135,55,0.06)' stroke-width='.7' stroke-dasharray='2 2'/%3E%3Ccircle cx='40' cy='40' r='2' fill='rgba(170,135,55,0.09)'/%3E%3C/svg%3E");
  background-size:80px 80px;
}


/* ===== [C3] FLOATING RSVP BUTTON - the travelling wax seal ===== */
/* === FLOATING RSVP SEAL BUTTON ===
   A wax-seal button that floats in the bottom corner while you scroll -
   it appears after the hero and hides itself on the RSVP section. */
#rsvpFab {
  position:fixed;
  right:var(--fab-side-now,28px);
  bottom:var(--fab-bottom-now,28px);
  z-index:250;
  width:calc(clamp(76px,15vw,100px) * var(--fab-scale-now,1));
  height:calc(clamp(76px,15vw,100px) * var(--fab-scale-now,1));
  display:flex;align-items:center;justify-content:center;
  text-decoration:none;
  filter:drop-shadow(0 8px 18px rgba(0,0,0,.35)) drop-shadow(0 2px 5px rgba(0,0,0,.3));
  opacity:0;visibility:hidden;
  transform:translateY(18px) scale(.88);
  transition:opacity .45s ease, transform .45s ease, visibility 0s .45s;
}
#rsvpFab.show {
  opacity:1;visibility:visible;
  transform:translateY(0) scale(1);
  transition:opacity .45s ease, transform .45s ease, visibility 0s;
}
#rsvpFab.show:hover { transform:translateY(-2px) scale(1.06); }
#rsvpFab img { position:absolute;inset:0;width:100%;height:100%;object-fit:contain;pointer-events:none; }
/* pulsing gold ring to draw the eye */
#rsvpFab::after {
  content:'';position:absolute;inset:4%;
  border-radius:50%;
  border:1.5px solid rgba(200,168,79,.75);
  opacity:0;
  pointer-events:none;
}
#rsvpFab.show::after { animation:fabPulse 2.6s ease-out infinite; }
@keyframes fabPulse {
  0%   { transform:scale(.82); opacity:.9; }
  70%  { transform:scale(1.28); opacity:0; }
  100% { transform:scale(1.28); opacity:0; }
}
/* little note that greets the first appearance, then slips away */
.fab-note {
  position:absolute;
  right:calc(100% + var(--fab-gap-now,12px));
  top:50%;transform:translateY(-50%) translateX(6px);
  background:rgba(30,44,38,.94);
  border:1px solid rgba(200,168,79,.45);
  color:var(--gold-lt);
  font-family:'Cinzel',serif;font-size:.52rem;letter-spacing:.22em;text-transform:uppercase;
  padding:9px 14px;white-space:nowrap;
  opacity:0;
  transition:opacity .5s ease, transform .5s ease;
  pointer-events:none;
}
#rsvpFab.note-visible .fab-note { opacity:1; transform:translateY(-50%) translateX(0); }

/* THE NOTE ABOVE THE SEAL INSTEAD OF BESIDE IT.
   Switched on by SETTINGS.fabNotePlace in js/settings.js, which puts
   .fabnote-above or .fabnote-left on <html> and keeps it in step as the
   screen changes. Not a media query: the value is a setting now, and a
   media query cannot read one.

   Why a phone wants it: beside the seal the note runs leftwards from a
   button already near the right edge, and white-space:nowrap means it
   cannot give way - so a longer line ("Open your personal link to
   reply") ran off the side of the display and was cut in half. Above,
   it has the whole width and may wrap.

   Its RIGHT edge lines up with the seal's, not its centre. Centred on
   the seal, a wide note hangs off the screen - the seal is only
   --fab-side-now from the edge, so half a 204px pill does not fit
   beside it. Anchored right, it grows leftwards into the space that is
   actually there and can never pass the edge it started from. */
html.fabnote-above .fab-note{
  left:auto; right:0;
  top:auto; bottom:calc(100% + var(--fab-gap-now,12px));
  transform:translateY(6px);
  white-space:normal;
  width:max-content;                 /* one line unless the max-width bites */
  max-width:min(78vw, 260px);
  text-align:center;
  line-height:1.5;
}
html.fabnote-above #rsvpFab.note-visible .fab-note{ transform:translateY(0); }

/* THE LABEL MAY NOT RUN OFF THE SCREEN.
   Beside the seal (fabNotePlace 'left') the note grows leftwards from a
   button that is already only --fab-side-now from the right edge, and
   white-space:nowrap means it cannot give way. On a 375px phone the
   English wording measured 204px past the left edge, and the Dutch
   "Open uw persoonlijke link om te reageren" is longer still - the label
   was simply cut in half.

   This does not overrule the setting: 'left' stays 'left'. It only caps
   the width at the space that actually exists and lets a line that will
   not fit wrap, instead of disappearing. The percentage resolves against
   #rsvpFab, the note's containing block, so the sum is exactly
   "the screen, less the seal, less the gap, less the margin".
   Nothing changes for any label that already fits. */
@media(max-width:640px){
  html.fabnote-left .fab-note{
    white-space:normal;
    text-align:right;
    line-height:1.5;
    max-width:calc(100vw - 100% - var(--fab-gap-now,12px) - var(--fab-side-now,14px) - 8px);
  }
}
@media(max-width:480px){ .fab-note{font-size:.48rem;letter-spacing:.16em;} }

/* ===== [C4] HERO - first screen: video + names ===== */
#wsHero{min-height:100vh;position:relative;display:flex;align-items:center;justify-content:center;overflow:hidden;background:var(--sage-xdk);}
/* Phones held upright: the hero fills the screen and the video covers it
   edge-to-edge - no panels above or below.

   dvh, NOT svh. The three units differ only on a phone:
     svh  the SMALLEST the screen ever is - address bar showing
     lvh  the LARGEST - address bar hidden
     dvh  whatever it is RIGHT NOW, following the bar as it moves

   svh looks safe and is not: it is the height with the browser's bars
   showing, so the moment Safari retracts its address bar the visible
   area grows and the hero does not - leaving a band of the next section
   below the video, which is the whole "it does not fill the screen"
   complaint. dvh keeps up with the bar in both directions.

   The plain vh line first is the fallback for anything that has never
   heard of dvh; it is what this rule used to be. */
@media (orientation:portrait) and (max-width:820px){
  #wsHero{min-height:100vh;  height:100vh;}
  #wsHero{min-height:100dvh; height:100dvh;}
}
.hero-photo{position:absolute;inset:0;background:linear-gradient(to bottom,rgba(26,38,33,.35) 0%,rgba(26,38,33,.1) 40%,rgba(26,38,33,.55) 85%,rgba(26,38,33,.85) 100%),linear-gradient(135deg,#4F7565,#8CAE9F 40%,#22352D);background-size:cover;background-position:center;}
.hero-photo img{width:100%;height:100%;object-fit:cover;object-position:center top;display:block;}
/* hero background video (activated automatically when PHOTOS.heroVideo loads) */
.hero-video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity .6s ease;} /* invisible via opacity, NOT display:none - iOS refuses to load display:none videos */
/* soft dark veil over the video so the names stay readable */
.hero-veil{position:absolute;inset:0;display:none;background:linear-gradient(to bottom,rgba(26,38,33,.42) 0%,rgba(26,38,33,.16) 40%,rgba(26,38,33,.55) 85%,rgba(26,38,33,.82) 100%);}
.ph-ov{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;color:rgba(201,168,76,.28);font-family:'Cinzel',serif;font-size:.56rem;letter-spacing:.3em;pointer-events:none;}
/* NOTE: a radial "pool of shade" behind the names was tried here and
   removed - it read as a grey smudge floating over the footage. If the
   hero text ever needs more contrast, the honest options are a stronger
   text-shadow (below, invisible as a shape) or turning .hero-veil back
   on for the whole frame. Not a soft blob in the middle. */
.hero-content{position:relative;z-index:2;text-align:center;padding:0 20px;
  /* The text hovers high above the footage on a heavy black shadow.
     Every layer below is thrown DOWNWARD, which lifts the words off the
     video but leaves their top edges bare - and that is the edge a bright
     frame eats into. The first line is new: a tight halo with no offset
     at all, hugging each letter on every side. It has no shape of its
     own and cannot be seen as an area; it only stops the strokes
     dissolving when something pale passes behind them. */
  text-shadow:
    0 0 2px rgba(0,0,0,.72),
    0 2px 3px rgba(0,0,0,.85),
    0 5px 10px rgba(0,0,0,.75),
    0 12px 24px rgba(0,0,0,.6),
    0 24px 55px rgba(0,0,0,.5);
  /* the names fade in as the video appears behind the parting envelope */
  opacity:0;transform:translateY(16px);
  transition:opacity 1.7s ease, transform 1.7s ease;}
.hero-content.fade-in{opacity:1;transform:none;}
@keyframes fadeUp{from{opacity:0;transform:translateY(18px)}to{opacity:1;transform:translateY(0)}}
.hero-lbl{font-family:'Cinzel',serif;font-size:clamp(.62rem,1.9vw,.8rem);letter-spacing:.5em;color:var(--gold-lt);text-transform:uppercase;margin-bottom:24px;opacity:.95;animation:fadeUp 1.2s ease .2s both;}
.hero-lbl2{font-family:'Cinzel',serif;font-size:clamp(.6rem,1.8vw,.76rem);letter-spacing:.42em;color:var(--gold-lt);text-transform:uppercase;margin-top:6px;animation:fadeUp 1.2s ease .7s both;}
.hero-names{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:300;font-size:clamp(3rem,10vw,7.5rem);color:var(--ivory);line-height:1;animation:fadeUp 1.2s ease .5s both;}
.hero-amp{display:inline-block;color:var(--gold);font-size:.65em;margin:0 .2em;vertical-align:middle;}
.hero-date{font-family:'Cinzel',serif;font-size:clamp(.65rem,2vw,.9rem);letter-spacing:.4em;color:var(--gold-lt);margin-top:28px;animation:fadeUp 1.2s ease .8s both;}
.hero-div{filter:drop-shadow(0 1px 2px rgba(8,12,9,.5)) drop-shadow(0 3px 10px rgba(8,12,9,.35));display:flex;align-items:center;justify-content:center;gap:16px;margin:28px auto;width:min(320px,80vw);animation:fadeUp 1.2s ease 1s both;}
.hero-div-line{flex:1;height:1px;background:linear-gradient(to right,transparent,rgba(201,168,76,.5));}
.hero-div-line.r{background:linear-gradient(to left,transparent,rgba(201,168,76,.5));}
.hero-div-gem{width:6px;height:6px;background:var(--gold);transform:rotate(45deg);}
/* was .7 - the faintest line on the page, over moving footage. .84 keeps
   it clearly secondary to the names without making it a squint. */
.hero-loc{font-style:italic;font-size:clamp(.9rem,2.5vw,1.1rem);color:rgba(240,228,196,.84);animation:fadeUp 1.2s ease 1.1s both;}
/* The scroll cue is deliberately built to match the envelope's "tap the
   seal to open" hint (.env-hint): same pill, same border, same blur, same
   lettering, so the two read as a pair - one invites you in, the other
   invites you on. It also fixes the old problem of pale gold text sitting
   directly on video, which needed three stacked text-shadows to stay
   readable; the pill does that job properly. */
.scroll-hint{
  position:absolute;
  left:50%;bottom:40px;
  transform:translateX(-50%);
  z-index:2;
  display:flex;
  align-items:center;
  gap:.6em;
  font-family:'Cinzel',serif;
  font-size:clamp(.72rem,2vw,.82rem);
  letter-spacing:.22em;
  text-transform:uppercase;
  color:#F5EEDD;
  background:rgba(20,28,22,.55);
  border:1px solid rgba(201,169,97,.55);
  padding:.7em 1.3em;
  border-radius:999px;
  box-shadow:0 6px 22px rgba(0,0,0,.35);
  backdrop-filter:blur(3px);
  -webkit-backdrop-filter:blur(3px);
  white-space:nowrap;
  /* it is a <button> now - it was the one thing on the hero promising
     more below, and it could not be pressed. The reset keeps it looking
     exactly as it did as a <div>. */
  cursor:pointer;
  appearance:none;-webkit-appearance:none;
  animation:floatHint 2.5s ease-in-out infinite;
}
.scroll-hint[hidden]{display:none;}   /* SETTINGS.scrollHint:false - the
   attribute has to beat display:flex above, or "hidden" hides nothing */
.scroll-hint:hover{background:rgba(20,28,22,.72);}
.scroll-hint:focus-visible{outline:2px solid rgba(201,169,97,.9);outline-offset:3px;}
/* The old 40px hairline becomes a downward arrow sitting in the same icon
   slot the envelope hint uses for its tapping finger - same size, same
   stroke weight, same warm gold. */
.scroll-hint .arr{
  display:block;                 /* it is a <span> inside the button now */
  width:1.15em;height:1.15em;
  flex:0 0 auto;
  background:no-repeat center/contain;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23E8D9B0' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v14'/%3E%3Cpath d='M19 12l-7 7-7-7'/%3E%3C/svg%3E");
}
@keyframes floatHint{0%,100%{transform:translateX(-50%) translateY(0)}50%{transform:translateX(-50%) translateY(8px)}}

/* ===== [C5] SHARED SECTION STYLES - labels, titles, dividers, cards ===== */
section{position:relative;}
.sec-label{font-family:'Cinzel',serif;font-size:clamp(.48rem,1.4vw,.58rem);letter-spacing:.45em;color:var(--sage);text-transform:uppercase;display:block;margin-bottom:14px;}
.sec-title{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:300;font-size:clamp(2rem,5vw,3.5rem);color:var(--sage-dk);line-height:1.1;}
.sec-div{display:flex;align-items:center;gap:12px;margin:20px 0 36px;}
.sec-div .line{flex:1;height:1px;background:linear-gradient(to right,var(--sage),transparent);max-width:120px;}
.sec-div .gem{width:5px;height:5px;background:var(--gold);transform:rotate(45deg);}
.sec-div .line2{flex:0;width:40px;height:1px;background:linear-gradient(to right,rgba(201,168,76,.4),transparent);}
.gold-line{width:100%;height:1px;background:linear-gradient(to right,transparent,rgba(201,168,76,.4),transparent);}
.reveal{opacity:0;transform:translateY(28px);transition:opacity .8s ease,transform .8s ease;}
.reveal.visible{opacity:1;transform:translateY(0);}
.rd1{transition-delay:.1s}.rd2{transition-delay:.2s}
.gold-card{border:1px solid rgba(140,174,159,.5);background:var(--ivory);position:relative;padding:36px;}
.gold-card::before,.gold-card::after{content:'';position:absolute;width:28px;height:28px;border-color:var(--gold);border-style:solid;opacity:.5;}
.gold-card::before{top:8px;left:8px;border-width:1px 0 0 1px;}
.gold-card::after{bottom:8px;right:8px;border-width:0 1px 1px 0;}

/* ===== [C6] OUR STORY - photo grid + text ===== */
#wsStory{padding:100px 0 80px;background:var(--cream);}
/* The --story-* variables are set from SETTINGS.storyLayout in
   js/settings.js. The values after the commas are the fallbacks, i.e.
   the layout as originally designed, used if settings.js is missing.

   grid-auto-rows, NOT grid-template-rows: the old version named an
   exact number of rows, so a photo past the end got no height at all.
   With five photos on a phone that meant photo 5 was 0px tall and
   simply invisible. Auto rows size however many photos there are, so
   adding a sixth to photos.story now just works. */
/* gap is SETTINGS.photoGap - shared with the gallery and reception grids */
.story-grid{
  display:grid;
  grid-template-columns:repeat(var(--story-cols,3),1fr);
  grid-auto-rows:var(--story-row,300px);
  gap:var(--photo-gap,12px);
  /* BLOCK WIDTH - how wide the whole photo block is, as a percentage of
     the space it sits in. The margin keeps it centred at any value, and
     because it goes negative above 100% the block can be made WIDER than
     the text beside it and still stay centred. */
  width:var(--story-width,100%);
  margin-left:calc((100% - var(--story-width,100%)) / 2);
}
@media(max-width:991px){
  .story-grid{
    grid-template-columns:repeat(var(--story-cols-tablet,2),1fr);
    grid-auto-rows:var(--story-row-tablet,220px);
  }
}
@media(max-width:575px){
  .story-grid{
    grid-template-columns:repeat(var(--story-cols-phone,1),1fr);
    grid-auto-rows:var(--story-row-phone,240px);
  }
}
.story-photo{position:relative;overflow:hidden;background:var(--ivory2);}
/* The first photo is the tall one - that is the section's signature.
   Its height is (span x --story-row) + (span - 1) x --photo-gap, so it
   lines up exactly with the shorter photos beside it. */
.story-photo:first-child{grid-row:span var(--story-big-span,2);background:linear-gradient(135deg,#8CAE9F,#4F7565);}
/* Below 991px the columns get narrow and a double-height first photo
   turns into an awkward tower, so it goes back to normal size. */
@media(max-width:991px){.story-photo:first-child{grid-row:span 1;}}
.story-photo img{width:100%;height:100%;object-fit:var(--story-fit,cover);transition:transform .7s ease;display:block;}
.story-photo:hover img{transform:scale(1.04);}
.story-photo .ph-lbl{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;color:rgba(201,168,76,.3);font-family:'Cinzel',serif;font-size:.55rem;letter-spacing:.3em;gap:8px;pointer-events:none;}
.story-body{font-size:clamp(1rem,2vw,1.15rem);color:var(--text-lt);line-height:1.9;font-weight:300;font-style:italic;max-width:480px;}

/* ===== [C7] THE DAY - events section ===== */
#wsEvents{padding:90px 0;background:var(--sage-xdk);position:relative;overflow:hidden;}
#wsEvents::before{content:'';position:absolute;inset:0;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Ccircle cx='50' cy='50' r='40' fill='none' stroke='rgba(201,168,76,0.05)' stroke-width='1'/%3E%3Ccircle cx='50' cy='50' r='3' fill='rgba(201,168,76,0.05)'/%3E%3C/svg%3E");background-size:100px 100px;pointer-events:none;}
#wsEvents .sec-title{color:var(--ivory);}
#wsEvents .sec-label{color:var(--gold-lt);}
#wsEvents .sec-div .line{background:linear-gradient(to right,var(--gold-lt),transparent);}
#wsEvents .sec-div .gem{background:var(--gold-lt);}
/* ===== [C8] VENUE - photos, details, map ===== */
#wsVenue{padding:90px 0;background:var(--cream);}

/* THE VENUE STACK.
   Everything under a heading here - kicker, name, prose, map, card,
   route map, photos - is a SIBLING, because SETTINGS.venueOrder shuffles
   them and a wrapper would stop it. So the running order is a wrapping
   flex column: every block takes a full line by default, and the two
   that say otherwise (the map and its details card) sit side by side
   when they end up next to each other. Put something full-width between
   them and each simply takes its own line again - no rule breaks, the
   layout just relaxes.
   The gap replaces the margins the cards used to carry inline, so the
   spacing no longer depends on which block follows which. */
.venue-stack{
  display:flex;flex-wrap:wrap;align-items:stretch;
  gap:24px;margin:0 auto;
}
.venue-stack > *{flex:0 0 100%;min-width:0;}
/* the two that may share a line */
.venue-stack > .map-box,
.venue-stack > .gt-card{flex:1 1 380px;}
/* a heading belongs to what follows it, not to what came before */
.venue-stack > .venue-block-kicker{margin-bottom:-14px;}
.venue-stack > .venue-hero-lbl-solo{margin:0;}
.venue-loc-addr{font-size:.92rem;color:var(--text-lt);line-height:1.6;font-style:italic;}
.venue-loc-time{font-family:'Cinzel',serif;font-size:.62rem;letter-spacing:.22em;color:var(--gold-dk);margin-top:14px;padding-top:12px;border-top:1px solid rgba(140,174,159,.35);display:inline-block;}
/* the two location blocks: kicker headers with flanking lines */
.venue-block-kicker{display:flex;align-items:center;justify-content:center;gap:18px;font-family:'Cinzel',serif;font-size:clamp(.82rem,2vw,1.05rem);letter-spacing:.45em;color:var(--sage);text-transform:uppercase;margin-bottom:20px;}
.venue-block-kicker::before,.venue-block-kicker::after{content:'';height:1px;flex:1;max-width:150px;}
.venue-block-kicker::before{background:linear-gradient(to right,transparent,var(--sage));}
.venue-block-kicker::after{background:linear-gradient(to left,transparent,var(--sage));}
.venue-block-gap{margin-top:36px;}   /* on top of the stack's own gap */
/* The reception's photo grid: 2 across, so four photos stack 2 on top of 2.
   (Same on phones, hence no separate small-screen rule any more.) */
/* gap is SETTINGS.photoGap - shared with the story and gallery grids.
   margin-top uses it too, so the space below the big reception photo
   always matches the space between the four below it. */
.venue-gallery{
  display:grid;
  grid-template-columns:repeat(var(--venue-cols,2),1fr);
  gap:var(--photo-gap,12px);
  margin-top:var(--photo-gap,12px);
  /* see the note on .story-grid - same idea, its own dial. The left
     margin has to carry the centring AND nothing else, so the top
     margin above stays on its own line. */
  width:var(--venue-width,100%);
  margin-left:calc((100% - var(--venue-width,100%)) / 2);
}
/* Width comes from --venue-cols above (how many share a row); height is one
   plain number per screen size, from SETTINGS.venueLayout.height. Same shape
   as the gallery's rowHeight, and deliberately NOT a clamp() - with a clamp
   the middle term wins at most screen widths, so raising the number appeared
   to do nothing. */
.venue-gi{
  height:var(--venue-h,260px);
  overflow:hidden;position:relative;background:var(--ivory3);
}
@media(max-width:767px){ .venue-gi{ height:var(--venue-h-tablet,200px); } }
@media(max-width:480px){ .venue-gi{ height:var(--venue-h-phone,160px); } }
.venue-gi img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .6s ease;}
.gal-item{cursor:zoom-in;}
.venue-gi:hover img{transform:scale(1.06);}
.map-box{height:280px;position:relative;overflow:hidden;border:1px solid rgba(140,174,159,.35);}
/* Beside a card it should match that card's height, so the pair reads as
   one object; alone on its line it keeps the comfortable tall aspect.
   min-height rather than height is what allows both. */
.map-box.venue-map-tall{height:auto;min-height:clamp(280px,42vw,420px);}
/* THE VENUE'S NAME. It used to inherit text-align:start, so it sat hard
   left under a kicker that centres itself between two rules - the name
   looked like it had come loose from its own heading. Centred now, and
   pulled up tight against the kicker so the two read as one title, with
   the gap below them instead of between them. */
.venue-hero-lbl-solo{
  font-family:'Cormorant Garamond',serif;font-style:italic;
  font-size:clamp(1.5rem,3.6vw,2.3rem);color:var(--sage-dk);
  font-weight:300;line-height:1.15;margin:0;
  text-align:center;
}
/* a small gold diamond under the name, the same mark the section
   dividers use - it closes the title off and separates it from the
   blocks below without another line of text */
.venue-hero-lbl-solo::after{
  content:'';display:block;width:5px;height:5px;margin:14px auto 0;
  background:var(--gold);transform:rotate(45deg);
}
.gt-card{padding:36px 32px;display:flex;flex-direction:column;justify-content:center;}
/* ONE PARAGRAPH STYLE FOR THE WHOLE SECTION.
   These three notes - the words about the venue, and the two "Important"
   blocks - used to be set by inline style attributes that had drifted
   apart (two line-heights for the same size), and they ran the full
   1050px of the card: about 90 characters a line, well past the 45-75 a
   reader is comfortable with. That width is most of why the text looked
   wrong. Capped at 62 characters and centred as a block; the words
   themselves stay left-aligned, which is easier to read than centred
   prose over several lines. */
.venue-note{
  font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:300;
  font-size:1.08rem;line-height:1.8;color:var(--text-lt);
  max-width:62ch;margin-left:auto;margin-right:auto;text-align:left;
}
/* labels: the two "Important" headings used to carry their own font-size,
   which made them half again as big as every other label in the section */
.gt-label{margin-bottom:16px;}
/* on a phone the card is the whole screen minus its own padding, so 32px
   on each side is 64px the words do not get */
@media(max-width:575px){ .gt-card{padding:28px 20px;} }
.map-box iframe{width:100%;height:100%;border:none;}
/* ENLARGE THE MAP. A corner button on top of the live map - the map
   itself stays draggable, because an iframe keeps every click that lands
   on it and there is no listening through one. */
.map-expand{
  position:absolute;right:10px;top:10px;z-index:2;
  display:flex;align-items:center;gap:.6em;
  font-family:'Cinzel',serif;font-size:.55rem;letter-spacing:.22em;
  text-transform:uppercase;
  color:#F5EEDD;background:rgba(20,28,22,.72);
  border:1px solid rgba(201,169,97,.55);border-radius:999px;
  padding:.6em 1.1em;min-height:32px;cursor:pointer;appearance:none;-webkit-appearance:none;
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);
  transition:background .3s ease;
}
.map-expand:hover{background:rgba(20,28,22,.92);}
.map-expand svg{width:1.05em;height:1.05em;flex:0 0 auto;color:var(--gold-lt);}
@media(max-width:575px){
  /* The words go - the icon alone is understood and a phone map is small.
     But an icon-only button measured 25x25, which is not a thumb: the
     minimum below is the 44px every touch guideline asks for, and it is
     why the size is set here rather than left to the padding. */
  .map-expand span{display:none;}
  .map-expand{
    padding:0;right:8px;top:8px;
    min-width:44px;min-height:44px;justify-content:center;
  }
  .map-expand svg{width:20px;height:20px;}
}

/* THE ROUTE MAP'S BUTTON. Cream side of the palette, since it sits on the
   page rather than on a map. */
.dir-open{
  display:inline-flex;align-items:center;gap:.7em;
  font-family:'Cinzel',serif;font-size:.6rem;letter-spacing:.26em;
  text-transform:uppercase;color:var(--sage-dk);
  background:var(--ivory3);border:1px solid rgba(140,174,159,.55);
  border-radius:999px;padding:1em 1.9em;cursor:pointer;
  appearance:none;-webkit-appearance:none;
  transition:background .3s ease,border-color .3s ease;
}
.dir-open:hover{background:var(--ivory);border-color:var(--gold);}
.dir-open svg{width:1.4em;height:1.4em;flex:0 0 auto;color:var(--gold-dk);}

.map-placeholder{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;color:rgba(201,168,76,.4);font-family:'Cinzel',serif;font-size:.58rem;letter-spacing:.3em;background:linear-gradient(135deg,#4F7565,#8CAE9F);text-align:center;padding:20px;}

/* the few words about the venue, and the drawn route map. Spacing is the
   stack's gap now - these carried their own margins and stacked up. */
.venue-trailer{text-align:center;}
.venue-trailer .sec-label{display:block;margin-bottom:14px;}
.venue-directions{text-align:center;}
.venue-directions[hidden]{display:none;}   /* the attribute must win */
.venue-directions .sec-label{display:block;margin-bottom:14px;}

/* the lightbox - one picture, full screen (built by main.js) */
.lb-back{
  position:fixed;inset:0;z-index:200;
  display:flex;align-items:center;justify-content:center;
  padding:24px;
  background:rgba(18,26,21,.94);
  overflow:auto;                 /* so a zoomed picture can be panned */
  -webkit-overflow-scrolling:touch;
  /* keep the scrolling in here. Without it a drag on a fitted picture,
     which has nowhere of its own to go, chains through to the page
     behind - and closing the overlay leaves the guest somewhere else. */
  overscroll-behavior:contain;
}
.lb-back[hidden]{display:none;}  /* the attribute must beat display:flex */
.lb-img{max-width:100%;max-height:calc(100vh - 48px);cursor:zoom-in;display:block;}
/* real size: bigger than the screen on purpose - the backdrop scrolls */
.lb-img.lb-zoom{max-width:none;max-height:none;cursor:zoom-out;margin:auto;}
.lb-close{
  position:fixed;top:14px;right:16px;
  width:44px;height:44px;line-height:1;
  font-size:1.9rem;font-family:'Cormorant Garamond',serif;
  color:#F0E4C4;background:rgba(20,28,22,.6);
  border:1px solid rgba(201,169,97,.55);border-radius:999px;
  cursor:pointer;appearance:none;-webkit-appearance:none;
}
.lb-close:hover{background:rgba(20,28,22,.85);}
/* the framed map */
.lb-frame{
  width:min(1100px,92vw);height:min(760px,82vh);
  border:1px solid rgba(201,169,97,.45);background:var(--ivory);
  display:block;margin:auto;
}
/* stepping through the gallery */
.lb-nav{
  position:fixed;top:50%;transform:translateY(-50%);
  width:46px;height:46px;line-height:1;
  font-size:2rem;font-family:'Cormorant Garamond',serif;
  color:#F0E4C4;background:rgba(20,28,22,.6);
  border:1px solid rgba(201,169,97,.55);border-radius:999px;
  cursor:pointer;appearance:none;-webkit-appearance:none;
}
.lb-nav:hover{background:rgba(20,28,22,.85);}
.lb-prev{left:14px;}
.lb-next{right:14px;}
/* .lb-img and .lb-frame set display themselves, which outranks the
   browser's own [hidden] rule - the same trap as .scroll-hint */
.lb-img[hidden],.lb-frame[hidden],.lb-nav[hidden]{display:none;}
@media(max-width:575px){
  .lb-nav{width:40px;height:40px;font-size:1.7rem;}
  .lb-prev{left:6px;} .lb-next{right:6px;}
}

/* ===== [C9] GALLERY - masonry (built from SETTINGS.photos.gallery) ===== */
#wsGallery{padding:90px 0;background:var(--ivory2);}
/* uniform grid: every cell the same width, fixed row height, photos
   cropped to fill - flows left-to-right with zero holes, whatever the
   mix of portrait and landscape shots. Every 4th photo spans 2 rows
   for rhythm; grid-auto-flow:dense fills any gap that would create. */
/* The gallery grid is driven by SETTINGS.galleryLayout in js/settings.js,
   which sets the --gal-* variables. The values after the commas are the
   fallbacks, so the grid still looks right if settings.js ever fails to
   load. grid-auto-flow:dense lets short photos backfill the gaps that the
   double-height ones leave behind. */
.gallery-masonry{
  display:grid;
  grid-template-columns:repeat(var(--gal-cols,3),1fr);
  grid-auto-rows:var(--gal-row,240px);
  gap:var(--photo-gap,12px);
  /* see the note on .story-grid - same idea, its own dial */
  width:var(--gal-width,100%);
  margin-left:calc((100% - var(--gal-width,100%)) / 2);
  grid-auto-flow:dense;
}
@media(max-width:767px){
  .gallery-masonry{
    grid-template-columns:repeat(var(--gal-cols-tablet,2),1fr);
    grid-auto-rows:var(--gal-row-tablet,200px);
  }
}
@media(max-width:480px){
  .gallery-masonry{
    grid-template-columns:repeat(var(--gal-cols-phone,1),1fr);
    grid-auto-rows:var(--gal-row-phone,230px);
  }
}
.gal-item{position:relative;overflow:hidden;background:var(--ivory3);}
/* A tall photo is a whole number of rows, so it always lines up with the
   short ones beside it. Its real height works out as
   (span x --gal-row) + (span - 1) x --photo-gap. */
.gal-item.tall{grid-row:span var(--gal-tall-span,2);}
.gal-item img{width:100%;height:100%;object-fit:var(--gal-fit,cover);display:block;transition:transform .6s ease;}

/* ---- the photos held back until the guest asks --------------------
   display:none, not opacity:0 - a hidden lazy image is never fetched,
   so the extra photos cost a first-time visitor nothing at all. */
.gal-item.gal-hidden{ display:none; }

/* ---- "View all photos" / "Show fewer photos" ----------------------
   ONE button that toggles - the gallery must not be a one-way door.
   Same shape as .btn-gold on the RSVP form, but this sits on the pale
   ivory gallery background, so the gold has to be the darker one to
   stay readable. */
.gallery-more{
  display:block;
  margin:calc(var(--photo-gap,12px) + 22px) auto 0;
  background:none;
  border:1px solid rgba(201,168,76,.55);
  color:var(--sage-dk);
  font-family:'Cinzel',serif;
  font-size:.62rem;
  letter-spacing:.34em;
  text-transform:uppercase;
  padding:15px 42px;
  cursor:pointer;
  position:relative;
  overflow:hidden;
  transition:color .3s ease, border-color .3s ease, opacity .3s ease, transform .3s ease;
}
.gallery-more::before{
  content:'';position:absolute;inset:0;
  background:rgba(201,168,76,.14);
  transform:scaleX(0);transform-origin:left;
  transition:transform .35s ease;
}
.gallery-more:hover::before{ transform:scaleX(1); }
.gallery-more:hover{ color:var(--gold-dk,#8a6f28); border-color:var(--gold); }
.gallery-more:focus-visible{ outline:2px solid var(--gold); outline-offset:3px; }
@media(max-width:575px){
  .gallery-more{ padding:14px 28px; letter-spacing:.24em; }
}
.gal-item:hover img{transform:scale(1.04);}
.gal-ph{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:rgba(170,130,50,.28);font-family:'Cinzel',serif;font-size:.5rem;letter-spacing:.3em;gap:8px;}

/* ===== [C9b] QUESTIONS & ANSWERS ==================================
   Built by main.js from the "faq" block in the content files, so the
   number of rows is not known here. Everything is drawn from the
   vocabulary already on the page - the gold hairline, Cinzel for the
   question, italic Cormorant for the answer - so the section reads as
   part of the invitation rather than as a support page.

   --cream, the same background Our Story renders. Deliberately NOT the
   .lace-bg class: the story and the venue both carry that class and
   neither shows the lace, because their own #id{background:...} shorthand
   outranks it and blanks the image out. Matching the story means matching
   what it LOOKS like, so this is the plain colour they end up with. */
#wsFaq{padding:90px 0;background:var(--cream);}
.faq-note{
  font-style:italic;color:var(--text-lt);
  font-size:1rem;line-height:1.8;max-width:520px;margin:0 auto;
}

/* The list is a .gold-card (see index.html) - the ivory panel with gold
   corner brackets that the venue's "Getting There" blocks use. Only its
   padding is changed here: a card's 36px all round would push the first
   question's own padding into a double margin. The rows supply their
   own vertical rhythm, so the card gives horizontal room only. */
.faq-list{
  padding:6px clamp(20px,3.5vw,38px);
}

/* <details>, not a JavaScript accordion: the browser already knows how
   to open, close, take focus and announce this to a screen reader. */
.faq-item{border-bottom:1px solid rgba(140,174,159,.32);}
.faq-item:last-child{border-bottom:none;}

.faq-q{
  display:flex;align-items:center;gap:20px;justify-content:space-between;
  padding:24px 0;cursor:pointer;
  font-family:'Cormorant Garamond',serif;
  font-size:clamp(1.1rem,2.2vw,1.34rem);
  color:var(--sage-dk);line-height:1.45;
  transition:color .25s ease;
  /* the default disclosure triangle goes; .faq-mark replaces it in the
     site's own gold. Two rules because Safari needs its own. */
  list-style:none;
}
.faq-q::-webkit-details-marker{display:none;}
.faq-q:hover{color:var(--gold-dk);}
.faq-item[open] .faq-q{color:var(--gold-dk);padding-bottom:14px;}
.faq-q:focus-visible{outline:2px solid var(--gold);outline-offset:3px;}
.faq-q-text{flex:1 1 auto;}

/* The marker is a hairline gold ring around a + that turns into a x -
   the same ring the timeline dots are drawn with, at a quarter of the
   size, so the two sections share a shape as well as a palette. */
.faq-mark{
  flex:0 0 auto;position:relative;
  width:26px;height:26px;
  border:1px solid rgba(201,168,76,.45);
  border-radius:50%;
  transition:transform .35s ease, background-color .25s ease, border-color .25s ease;
}
.faq-mark::before,.faq-mark::after{
  content:'';position:absolute;top:50%;left:50%;
  width:10px;height:1px;margin:-.5px 0 0 -5px;
  background:var(--gold);
  transition:background-color .25s ease;
}
.faq-mark::after{transform:rotate(90deg);}
.faq-q:hover .faq-mark{border-color:var(--gold);background:rgba(201,168,76,.09);}
.faq-item[open] .faq-mark{
  transform:rotate(135deg);
  border-color:var(--gold);
  background:rgba(201,168,76,.14);
}

/* The answer is set in from the left, so it sits clear of the question's
   own line and reads as belonging under it rather than beside it. The
   hairline is the same gradient rule the section dividers use. */
.faq-a{
  padding:0 0 26px;
  margin-left:2px;
  padding-left:16px;
  border-left:1px solid rgba(201,168,76,.35);
  /* was max-width:62ch - a reading-measure cap. On a laptop that stopped
     the answer at 467px inside a 658px row, so it ended two-thirds of the
     way across while the question above it ran the full width, which reads
     as a broken box rather than as a comfortable measure. The card is
     already capped (col-lg-8 inside a 1140px container), so the line
     length is held there instead. */
  font-size:clamp(.98rem,1.9vw,1.08rem);
  font-style:italic;font-weight:300;
  color:var(--text-lt);line-height:1.85;
}

@media (prefers-reduced-motion: reduce){
  .faq-mark,.faq-mark::before,.faq-mark::after{transition:none;}
}
@media(max-width:575px){
  .faq-list{padding:2px 18px;}
  .faq-q{gap:14px;padding:20px 0;font-size:1.06rem;}
  .faq-item[open] .faq-q{padding-bottom:12px;}
  .faq-mark{width:23px;height:23px;}
  .faq-a{padding-bottom:22px;padding-left:13px;}
}

/* ---- [button:Label|url] written into any text ---------------------
   Same shape and lettering as the gallery's "View all photos" button,
   so a button written into a FAQ answer looks like it belongs to the
   site rather than to the browser.

   Two shapes, chosen in the text itself:
     [button:Label|url]          on its own line, text flows above/below
     [button:Label|url|inline]   inside the sentence, on the text baseline
   Separated is the default: these land at the end of a sentence, and a
   full-size gold box mid-paragraph reads as an accident. It was
   inline-block with no break before, so a "block" button still sat in
   the flow beside the words - display:block + width:fit-content is what
   actually gives it its own line without stretching the border edge to
   edge. */
.rich-btn{
  display:block;
  width:fit-content;
  margin:18px 0 4px;
  background:none;
  border:1px solid rgba(201,168,76,.55);
  color:var(--gold-dk);
  font-family:'Cinzel',serif;
  font-size:.6rem;
  letter-spacing:.3em;
  text-transform:uppercase;
  text-decoration:none;
  font-style:normal;
  /* the extra left padding pays back the trailing letter-spacing after
     the last letter, which otherwise pushes the word off-centre */
  padding:13px 30px 13px calc(30px + .3em);
  transition:color .3s ease, border-color .3s ease, background-color .3s ease;
}
.rich-btn-inline{
  display:inline-block;
  width:auto;
  margin:0 .2em;
  padding:5px 14px 5px calc(14px + .2em);
  font-size:.55rem;
  letter-spacing:.2em;
  vertical-align:baseline;
}
.rich-btn:hover{ color:var(--gold-dk); border-color:var(--gold); background:rgba(201,168,76,.12); }
.rich-btn:focus-visible{ outline:2px solid var(--gold); outline-offset:3px; }
/* the two dark panels - pale gold on ivory is unreadable there */
#wsRsvp .rich-btn,
#wsZoom .rich-btn,
#wsEvents .rich-btn{ color:var(--gold-lt); border-color:var(--gold); }
#wsRsvp .rich-btn:hover,
#wsZoom .rich-btn:hover,
#wsEvents .rich-btn:hover{ color:var(--ivory); background:rgba(201,168,76,.18); }
@media(max-width:575px){
  .rich-btn{ padding:12px 22px 12px calc(22px + .22em); letter-spacing:.22em; }
  .rich-btn-inline{ padding:5px 12px 5px calc(12px + .18em); letter-spacing:.18em; }
}

/* ===== [C10] RSVP - form + success/error states ===== */
/* #wsZoom (zoom.html only) rides along on every one of these rules. It
   is the same dark panel in the same place in the page, and .btn-gold
   is drawn for a dark background - pale gold on ivory is unreadable.
   Listing both selectors is what keeps them from drifting apart. */
#wsRsvp,
#wsZoom{padding:90px 0 100px;background:var(--sage-xdk);position:relative;overflow:hidden;}
#wsRsvp::before,
#wsZoom::before{content:'';position:absolute;inset:0;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100' height='100'%3E%3Ccircle cx='50' cy='50' r='40' fill='none' stroke='rgba(201,168,76,0.04)' stroke-width='1'/%3E%3Ccircle cx='50' cy='50' r='2' fill='rgba(201,168,76,0.05)'/%3E%3C/svg%3E");background-size:100px 100px;pointer-events:none;}
#wsRsvp .sec-title,
#wsZoom .sec-title{color:var(--ivory);}
#wsRsvp .sec-label,
#wsZoom .sec-label{color:var(--gold-lt);}
#wsRsvp .sec-div .line,
#wsZoom .sec-div .line{background:linear-gradient(to right,var(--gold-lt),transparent);}
#wsRsvp .sec-div .gem,
#wsZoom .sec-div .gem{background:var(--gold-lt);}

/* ---- the "join us online" panel (zoom.html) ------------------
   Centred rather than the RSVP's two columns: there is no form to
   sit beside, and a single short block reads better down the middle
   of the page than pushed to one side of an empty half. */
#wsZoom .sec-div{justify-content:center;}
/* The personal welcome, above the intro. Cormorant rather than the
   intro's italic so the guest's own name reads as a greeting and not as
   more of the same paragraph. main.js hides it when there is no ?f=. */
.zoom-welcome{font-family:'Cormorant Garamond',serif;font-size:clamp(1.25rem,3.2vw,1.7rem);color:var(--gold-lt);line-height:1.6;max-width:560px;margin:0 auto 18px;}
.zoom-intro{font-size:clamp(.95rem,2vw,1.1rem);color:rgba(220,210,185,.75);font-style:italic;line-height:1.8;max-width:560px;margin:0 auto 20px;}
.zoom-when{font-family:'Cinzel',serif;font-size:clamp(.6rem,1.7vw,.72rem);letter-spacing:.16em;line-height:2;color:var(--gold-lt);max-width:560px;margin:0 auto 40px;}
.zoom-card{background:rgba(245,240,224,.05);border-color:rgba(201,168,76,.22);max-width:520px;margin:0 auto;padding:clamp(28px,5vw,44px);text-align:center;}
/* .btn-gold is an inline-flex built for a <button>; as a link it needs
   the underline taken off and centring of its own. */
.zoom-btn{text-decoration:none;justify-content:center;}
.zoom-meta{margin-top:28px;display:flex;flex-direction:column;gap:12px;}
/* Each row is label on the left, value on the right, with a hairline
   between them - the same engraved pairing the venue card uses. */
.zoom-row{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding-top:12px;border-top:1px solid rgba(201,168,76,.14);}
.zoom-row:first-child{border-top:none;padding-top:0;}
.zoom-key{font-family:'Cinzel',serif;font-size:.52rem;letter-spacing:.3em;text-transform:uppercase;color:var(--gold-lt);opacity:.85;flex:0 0 auto;}
/* The value is a code people copy or type in. Tabular figures keep the
   digits from jittering, and user-select makes it easy to copy. */
.zoom-val{font-family:'Cormorant Garamond',serif;font-size:1.15rem;color:var(--ivory);font-variant-numeric:tabular-nums;letter-spacing:.06em;text-align:right;word-break:break-word;-webkit-user-select:all;user-select:all;}
.zoom-note{font-size:.9rem;color:rgba(220,210,185,.45);font-style:italic;line-height:1.8;max-width:520px;margin:32px auto 0;}
@media(max-width:520px){
  /* Side by side, a long meeting ID squeezes the label to nothing.
     Stacked, both get the full width. */
  .zoom-row{flex-direction:column;gap:6px;}
  .zoom-val{text-align:left;}
}
/* NO max-width, and no bottom margin. It used to need both: it was one
   of several things stacked in a narrow left column. It is now the only
   thing in its own column, so the COLUMN holds the reading measure -
   about 60 characters at 665px, comfortably inside the 45-75 a reader
   wants - and the row's gap does the spacing. The old 520px cap left
   145px of dead space between the end of the sentence and the dress
   code beside it. (Same reasoning as .faq-a, which dropped its own
   62ch cap for exactly this reason.) */
.rsvp-intro{font-size:clamp(.95rem,2vw,1.1rem);color:rgba(220,210,185,.75);font-style:italic;line-height:1.8;margin-bottom:0;}
.rsvp-form-wrap{background:rgba(245,240,224,.05);border:1px solid rgba(201,168,76,.22);padding:clamp(24px,5vw,52px);position:relative;}
.rsvp-form-wrap::before{content:'';position:absolute;top:10px;left:10px;right:10px;bottom:10px;border:1px solid rgba(201,168,76,.09);pointer-events:none;}
.f-group{margin-bottom:26px;}
.f-label{display:block;font-family:'Cinzel',serif;font-size:.52rem;letter-spacing:.3em;color:var(--gold-lt);text-transform:uppercase;margin-bottom:10px;}
.f-input{width:100%;background:rgba(245,240,224,.06);border:none;border-bottom:1px solid rgba(201,168,76,.35);padding:12px 0;color:var(--ivory);font-family:'Cormorant Garamond',serif;font-size:1rem;font-style:italic;outline:none;transition:border-color .3s;-webkit-appearance:none;}
.f-input::placeholder{color:rgba(220,210,185,.3);}
.f-input:focus{border-bottom-color:var(--gold);}
.guest-ctr{display:flex;align-items:center;gap:16px;padding:8px 0;}
.ctr-btn{width:32px;height:32px;border:1px solid rgba(201,168,76,.4);background:none;color:var(--gold-lt);font-size:1.1rem;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .2s;}
.ctr-btn:hover{background:rgba(201,168,76,.1);border-color:var(--gold);}
.ctr-num{font-family:'Cinzel',serif;font-size:1.1rem;color:var(--ivory);min-width:28px;text-align:center;}
.btn-gold{display:inline-flex;align-items:center;gap:12px;background:none;border:1px solid var(--gold);color:var(--gold-lt);font-family:'Cinzel',serif;font-size:.6rem;letter-spacing:.4em;text-transform:uppercase;padding:16px 48px;cursor:pointer;transition:all .3s;position:relative;overflow:hidden;}
.btn-gold::before{content:'';position:absolute;inset:0;background:rgba(201,168,76,.12);transform:scaleX(0);transform-origin:left;transition:transform .35s ease;}
.btn-gold:hover::before{transform:scaleX(1);}
.btn-gold:hover{color:var(--gold);border-color:var(--gold-lt);}
.btn-gold:disabled{opacity:.5;cursor:not-allowed;}
/* The RSVP's two answer boxes, and the question form's, are the same
   two boxes in the same section - so they are one rule, not four.
   #formSuccess/#formError are shown by setting style.display; the ask
   pair uses the hidden attribute, hence the extra line below. */
#formSuccess{display:none;padding:20px 28px;margin-top:20px;border:1px solid rgba(180,203,193,.4);color:var(--sage-lt);background:rgba(180,203,193,.06);font-style:italic;line-height:1.7;}
#formError{display:none;padding:20px 28px;margin-top:20px;border:1px solid rgba(180,80,60,.4);color:#e09080;background:rgba(180,80,60,.06);font-style:italic;line-height:1.7;}
/* The question form's two answer boxes. They were folded into the rules
   above while it lived in the dark RSVP section; back on cream they need
   their own ink, so they are separate again. Shown by dropping the
   hidden attribute, not by setting a style - hence the explicit rule. */
#askSuccess,#askError{
  display:none;padding:16px 20px;margin-top:18px;
  font-family:'Cormorant Garamond',serif;font-style:italic;line-height:1.7;
}
#askSuccess{border:1px solid rgba(140,174,159,.6);background:rgba(140,174,159,.12);color:var(--sage-dk);}
#askError  {border:1px solid rgba(160,70,50,.45); background:rgba(160,70,50,.07); color:#8d3a28;}
#askSuccess:not([hidden]),#askError:not([hidden]){display:block;}

/* The section's own padding, lifted out of a style= on the container. */
.rsvp-container{padding-top:90px;padding-bottom:100px;z-index:1;}

/* THE DRESS-CODE CARD. Was five style= attributes plus two empty <div>s
   whose only job was to draw the corner brackets. The brackets are
   decoration, so they are ::before/::after here and the markup is three
   elements instead of five. Same drawing as .gold-card's corners, at the
   opacity the dark section wants. */
/* The heading sits in a row of its own, so the space under it belongs to
   the heading rather than to the first thing after it. */
.rsvp-row-main{margin-top:34px;}

/* THE 30 / 70 SPLIT. Not a twelfth of anything, so it is set here rather
   than by adding .col-lg-30 and .col-lg-70 to a grid that is otherwise
   twelfths - two odd numbers in the shared vocabulary, for one section.
   Both columns keep .col-12: that is what gives them their half-gutter
   padding, and it is what makes them full width on a phone, where this
   rule does not apply and the three blocks stack in DOM order. */
@media(min-width:992px){
  .rsvp-col-side{width:30%;}
  .rsvp-col-form{width:70%;}
}

/* The dress code sits UNDER the invitation line, in the same column, so
   the gap between them is this one - on a laptop and on a phone alike. */
.rsvp-aside-card{
  margin-top:30px;
  padding:24px;
  border:1px solid rgba(201,168,76,.2);
  position:relative;
}
.rsvp-aside-card::before,.rsvp-aside-card::after{
  content:'';position:absolute;width:20px;height:20px;
  border-color:var(--gold);border-style:solid;opacity:.5;
  pointer-events:none;
}
.rsvp-aside-card::before{top:-1px;left:-1px;border-width:1px 0 0 1px;}
.rsvp-aside-card::after{bottom:-1px;right:-1px;border-width:0 1px 1px 0;}
.rsvp-aside-label{
  font-family:'Cinzel',serif;font-size:.5rem;letter-spacing:.3em;
  color:var(--gold);margin-bottom:10px;
}
.rsvp-aside-value{
  font-size:1.05rem;color:var(--ivory);font-style:italic;font-weight:300;
}

/* the RSVP form's send row and its "* Required" note */
.submit-row{display:flex;align-items:center;gap:20px;flex-wrap:wrap;margin-top:8px;}
.form-required{font-size:.82rem;color:rgba(220,210,185,.35);font-style:italic;}
.form-success-title{
  font-style:normal;font-family:'Cinzel',serif;
  font-size:.58rem;letter-spacing:.3em;
}

/* ---- ASK US A QUESTION (the FAQ's last row) ------------------------
   It is a .faq-item like every other row, so it inherits the question
   type, the gold +/x marker, the hairline and the open/close behaviour
   for free - and it is CLOSED at rest, which is the point: a form that
   nobody has asked for costs nothing but one line of text.

   THE FORM STYLES HAVE TO BE RE-COLOURED HERE. .f-input and .f-label
   were written for the RSVP, which is a DARK section: ivory text on a
   translucent white fill. This section is --cream with an ivory card,
   so unscoped they render ivory-on-ivory - a field that looks empty and
   swallows whatever is typed into it. */
#wsFaq .f-label{color:var(--gold-dk);}
#wsFaq .f-input{
  background:rgba(255,255,255,.6);
  border-bottom-color:rgba(140,174,159,.55);
  color:var(--text);
}
#wsFaq .f-input::placeholder{color:rgba(90,74,42,.45);}
#wsFaq .f-input:focus{border-bottom-color:var(--gold);}
/* the gold button is drawn for a dark ground - on cream it needs its
   own ink, or it is a pale outline with nothing legible inside it */
#wsFaq .btn-gold{color:var(--gold-dk);border-color:rgba(201,168,76,.75);}
#wsFaq .btn-gold:hover{color:var(--text);}

.faq-ask .faq-a{padding-top:4px;}
#askForm[hidden]{display:none;}    /* the attribute must beat any display */
/* the name field is revealed only when there is no code to look the name
   up from. .f-group sets no display, so `hidden` already works - this is
   here because three separate things on this page have been shown by
   accident after being hidden by attribute, and it costs one line. */
#askWhoGroup[hidden]{display:none;}
#askSpinner[hidden]{display:none;}
/* the one field that must not be a single line */
.ask-text{
  min-height:120px;resize:vertical;line-height:1.7;
  border:1px solid rgba(140,174,159,.45);
  padding:12px 14px;
}
/* THE HONEYPOT. Off the side of the screen rather than display:none -
   some bots skip anything that is display:none, and this one is meant to
   be found and filled in. aria-hidden + tabindex=-1 in the markup keep it
   away from anyone real. */
.ask-hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden;}
/* left-aligned, not centred: it sits inside a FAQ answer now, and every
   other word in that column starts at the same place */
.ask-fallback{
  margin:20px 0 0;
  font-family:'Cormorant Garamond',serif;font-style:italic;
  font-size:.98rem;color:var(--text-lt);
}
.ask-fallback a{color:var(--gold-dk);text-decoration:underline;text-underline-offset:3px;}
.spin{display:inline-block;width:16px;height:16px;border:1.5px solid rgba(201,168,76,.3);border-top-color:var(--gold);border-radius:50%;animation:spinAnim .7s linear infinite;}
@keyframes spinAnim{to{transform:rotate(360deg)}}

/* ===== [C11] FOOTER ===== */
#wsFoot{background:#101815;padding:60px 0 40px;border-top:1px solid rgba(201,168,76,.15);text-align:center;}
.foot-mono{font-family:'Cormorant Garamond',serif;font-style:italic;font-size:clamp(2rem,5vw,3.2rem);color:var(--gold);opacity:.7;font-weight:300;}
.foot-date{font-family:'Cinzel',serif;font-size:.58rem;letter-spacing:.5em;color:rgba(201,168,76,.4);margin-top:14px;}
.foot-div{width:60px;height:1px;background:rgba(201,168,76,.25);margin:24px auto;}
.foot-note{font-size:.82rem;color:rgba(220,210,185,.22);font-style:italic;}

/* ===== [C12] INFOGRAPHIC TIMELINE - inside THE DAY ===== */
/* ===============================
   INFOGRAPHIC TIMELINE
=============================== */
.itl-wrap { position:relative; padding:0 0 8px; }

.itl-row {
  display:grid;
  grid-template-columns: 1fr 64px 1fr;
  align-items:flex-start;
  min-height:110px;
}

/* - spine column (centre) - */
.itl-spine-col {
  display:flex;
  flex-direction:column;
  align-items:center;
  position:relative;
}

.itl-dot {
  width:52px; height:52px;
  border-radius:50%;
  background:var(--ivory);
  border:2px solid rgba(180,203,193,.65);
  display:flex; align-items:center; justify-content:center;
  flex-shrink:0;
  position:relative;
  z-index:2;
  box-shadow:0 0 0 6px rgba(201,168,76,.08);
}
.itl-dot-gold {
  background:var(--gold);
  border-color:var(--gold-lt);
  box-shadow:0 0 0 6px rgba(201,168,76,.2), 0 4px 16px rgba(201,168,76,.3);
}

/* -- the icon inside the dot, when it comes from a file --------------
   SETTINGS.timelineIcons names an .svg per row and main.js swaps it in
   for the one written into index.html. The file supplies the SHAPE
   only, as a mask: the colour still comes from the palette here, so a
   replacement icon inherits the design instead of overriding it - which
   an <img> could not do, since an image cannot read a CSS variable.

   28x28 is the size the inline drawings are authored at, so a swapped
   icon lands in exactly the same place at exactly the same size. */
.itl-icon{
  width:28px; height:28px;
  display:block;
  background-color:var(--sage-xdk);
  -webkit-mask:var(--itl-icon) no-repeat center / contain;
          mask:var(--itl-icon) no-repeat center / contain;
}
/* the last dot is gold, and its drawing is the pale one on top of it */
.itl-dot-gold .itl-icon{ background-color:#fff; }

.itl-line {
  width:2px;
  flex:1;
  min-height:40px;
  background:linear-gradient(to bottom, rgba(180,203,193,.45), rgba(180,203,193,.15));
  margin-top:0;
}

/* - time column - */
.itl-time-col {
  display:flex;
  align-items:flex-start;
  justify-content:flex-end;
  padding-top:12px;
  padding-right:16px;
}
.itl-time-right {
  justify-content:flex-start;
  padding-right:0;
  padding-left:16px;
}
.itl-time {
  font-family:'Cinzel', serif;
  font-size:clamp(.75rem, 2vw, 1rem);
  font-weight:600;
  color:var(--gold-lt);
  letter-spacing:.05em;
  white-space:nowrap;
}

/* - content column - */
.itl-content-col {
  padding-top:10px;
  padding-left:16px;
}
.itl-content-left {
  padding-left:0;
  padding-right:16px;
  text-align:right;
}
.itl-event {
  font-family:'Cormorant Garamond', serif;
  font-style:italic;
  font-weight:300;
  font-size:clamp(1.1rem, 2.5vw, 1.45rem);
  color:var(--ivory);
  line-height:1.2;
  margin-bottom:5px;
}
.itl-detail {
  font-size:clamp(.78rem, 1.6vw, .9rem);
  color:rgba(220,210,185,.6);
  line-height:1.6;
  font-style:italic;
}

/* Mobile: collapse to single column */
@media(max-width:575px){
  /* CENTRED on a phone. It used to be a 40px spine hard against the left
     edge with the text beside it, which left the whole section looking
     off-balance next to every other block on the page - those are all
     centred. Here each entry becomes one centred column: the dot, a short
     stem under it, then the time and text centred beneath.

     The long connecting rail is deliberately NOT kept. Centred text with a
     full-height line down the middle would put the line straight through
     the words. The stem under each dot reads as the same idea without the
     collision. On a wide screen nothing changes - the rail is still there,
     with the entries alternating left and right of it. */
  .itl-row {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto;
    justify-items: center;
    min-height: 0;
  }
  /* both side columns of times go - the time is drawn under the dot instead */
  .itl-row .itl-time-col { display:none; }

  .itl-row .itl-spine-col,
  .itl-row.itl-left .itl-spine-col   { order:1; grid-column:1; }
  .itl-row .itl-content-col,
  .itl-row.itl-left .itl-content-col,
  .itl-row.itl-left .itl-content-col.itl-content-left {
    order:2; grid-column:1;
    text-align:center;
    padding:10px 8px 30px;
  }
  .itl-dot { width:40px; height:40px; }
  /* the stem: a short line under each dot, instead of a rail through the text */
  .itl-line { min-height:0; height:16px; flex:0 0 16px; }
  /* Show time inline inside content for mobile.
     On a phone the real .itl-time element is hidden (above) and the time
     is drawn HERE instead, as a pseudo-element. Nothing in JavaScript can
     set a font-size on a pseudo-element, which is why the per-line size
     for day.timeline.*.time appeared to do nothing on phones. The size
     travels as a plain multiplier instead, set by applyTextSizes(). */
  .itl-event::before {
    display:block;
    font-family:'Cinzel',serif;
    font-size:calc(.65rem * var(--tl-time-scale,1));
    font-style:normal;
    color:var(--gold);
    letter-spacing:.08em;
    margin-bottom:3px;
    content:attr(data-time);
  }
}
@keyframes itlSlideIn {
  from { opacity:0; transform:translateY(22px); }
  to   { opacity:1; transform:translateY(0); }
}
/* Items start hidden; .itl-playing parent triggers the animation */
.itl-wrap .itl-ani {
  opacity: 0;
}
.itl-wrap.itl-playing .itl-ani {
  animation: itlSlideIn .7s ease forwards;
}

/* ===== envelope tap hint ===== */
.env-hint{
  position:absolute;
  left:50%;bottom:10vh;
  transform:translateX(-50%);
  z-index:10;
  display:flex;
  align-items:center;
  gap:.6em;
  font-family:'Cinzel',serif;
  font-size:clamp(.8rem,3vw,1rem);
  letter-spacing:.22em;
  text-transform:uppercase;
  color:#F5EEDD;
  background:rgba(20,28,22,.55);
  border:1px solid rgba(201,169,97,.55);
  padding:.7em 1.3em;
  border-radius:999px;
  box-shadow:0 6px 22px rgba(0,0,0,.35);
  backdrop-filter:blur(3px);
  -webkit-backdrop-filter:blur(3px);
  white-space:nowrap;
  max-width:calc(100vw - 24px);
  pointer-events:none;
  /* HIDDEN UNTIL THE WORDING IS FINAL.
     index.html paints the ENGLISH hint and main.js swaps in the guest's
     language a moment later. In Dutch that pill is 94px wider, and since
     it is centred it grew 47px out of each side in full view. So it only
     fades in once main.js has put the real text in - it adds
     .texts-ready to <html> the instant it has.

     A fixed delay was not enough on its own: on slow mobile data main.js
     can easily arrive later than any delay worth waiting, and the jump
     would be back. What is left here IS a fixed delay, but a long one -
     purely a safety net, so that a browser which somehow never gets that
     class still shows the hint after 3 seconds rather than hiding it for
     ever. In practice the class always wins. */
  opacity:1;
  animation:envHintIn .5s ease 3s backwards;
  transition:opacity .4s ease;
}
html.texts-ready .env-hint{
  animation:envHintIn  .5s ease .15s backwards,
            envHintBob 1.8s ease-in-out .65s infinite;
}
@keyframes envHintIn{
  from{ opacity:0; transform:translateX(-50%) translateY(8px); }
  to  { opacity:1; transform:translateX(-50%) translateY(0); }
}
.env-hint::before{
  content:"";
  width:1.15em;height:1.15em;
  flex:0 0 auto;
  background:no-repeat center/contain;
  /* finger-tap icon in warm gold */
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23E8D9B0' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 11V6a1.5 1.5 0 0 1 3 0v5'/%3E%3Cpath d='M12 11V4.5a1.5 1.5 0 0 1 3 0V11'/%3E%3Cpath d='M15 11V6.5a1.5 1.5 0 0 1 3 0V13c0 4-2.5 7-6 7s-6-2.5-6-6.5c0-1 .5-1.8 1.4-2.2L9 11'/%3E%3C/svg%3E");
}
/* The fade-out target only. Freezing the pill in place is done in JS
   (openEnvelope), not here, because it has to pin the OPACITY too:
   animation-play-state:paused on its own held a half-finished entry
   animation - opacity included - and the hint stayed on screen. A bare
   "animation:none" is no good either; it snapped the pill back to its
   resting position mid-bob, up to 7px, so it twitched as it faded. */
#envScreen.open .env-hint{opacity:0;}
#envScreen{cursor:pointer;}
#envScreen:focus-visible{outline:2px solid rgba(201,169,97,.6);outline-offset:-6px;}
@keyframes envHintBob{
  0%,100%{transform:translateX(-50%) translateY(0);}
  50%{transform:translateX(-50%) translateY(-7px);}
}
/* On a narrow phone, let the hint WRAP instead of hanging off the edges.
   The Dutch wording is 29 characters to English's 20, and at 320px wide
   (an iPhone SE) the pill measured 354px - 17px of it clipped off each
   side, with the text cut in half. white-space only breaks a line when
   it has to, so English still sits on one line here; Dutch takes two. */
@media(max-width:430px){
  .env-hint{
    /* NOT flex here. The text is an anonymous flex item, and once it is
       allowed to wrap flex shrinks it to its longest WORD - the pill
       collapsed to 160px and four lines. As a plain block with
       width:max-content it takes its natural one-line width, and only
       the max-width above forces the break. */
    display:block;
    width:max-content;
    white-space:normal;
    text-align:center;
    letter-spacing:.16em;
    line-height:1.55;
  }
  .env-hint::before{
    display:inline-block;
    vertical-align:-.22em;
    margin-right:.5em;
  }
}

@media (prefers-reduced-motion: reduce){
  /* both hints bob; neither should for a guest who asked for less motion.
     The fade-in stays - it is what hides the language swap, and it is a
     dissolve rather than movement. */
  html.texts-ready .env-hint{animation:envHintIn .5s ease .15s backwards;}
  .scroll-hint{animation:none;}
}

/* ===== language switch (EN | NL) ===== */
/* ---- the top-right control strip ---------------------------------
   Sound and language sit in one flex row. Done this way rather than
   giving the sound button its own fixed "right: 78px" so that the two
   can never overlap: the language pill's width depends on its font and
   wording, and any guess made here would eventually be wrong. */
#topControls{
  position:fixed;
  top:calc(env(safe-area-inset-top, 0px) + 14px);
  right:16px;
  z-index:60;
  display:flex;
  align-items:center;
  gap:8px;
}
/* inside the strip the language pill is laid out by the flex row, so it
   gives up the fixed position it uses when it is on its own */
#topControls #langSwitch{ position:static; top:auto; right:auto; }

/* ---- sound on/off -------------------------------------------------
   Same pill, same gold, so the two read as one control. Only put on
   the page when a sound file is actually configured. */
#soundToggle{
  position:relative;
  display:flex;align-items:center;justify-content:center;
  width:34px;height:34px;
  padding:0;
  background:rgba(20,28,22,.5);
  border:1px solid rgba(201,169,97,.4);
  border-radius:999px;
  backdrop-filter:blur(4px);
  -webkit-backdrop-filter:blur(4px);
  color:var(--gold-lt);
  cursor:pointer;
  transition:color .25s ease, border-color .25s ease, opacity .25s ease;
}
#soundToggle:hover{ color:var(--gold); border-color:rgba(201,169,97,.7); }
#soundToggle.is-muted{ color:rgba(230,205,132,.45); }
#soundToggle:focus-visible{ outline:2px solid var(--gold); outline-offset:2px; }
/* fingertip-sized without making the pill itself bigger */
#soundToggle::after{ content:''; position:absolute; inset:-6px; }

#langSwitch{
  position:fixed;
  top:calc(env(safe-area-inset-top, 0px) + 14px);
  right:16px;
  z-index:60;
  display:flex;
  align-items:center;
  gap:.35em;
  padding:.3em .7em;
  background:rgba(20,28,22,.5);
  border:1px solid rgba(201,169,97,.4);
  border-radius:999px;
  backdrop-filter:blur(4px);
  -webkit-backdrop-filter:blur(4px);
  font-family:'Cinzel',serif;
}
#langSwitch button{
  background:none;
  border:none;
  cursor:pointer;
  font-family:inherit;
  font-size:.72rem;
  letter-spacing:.12em;
  color:rgba(220,210,185,.55);
  padding:.15em .25em;
  transition:color .25s ease;
}
#langSwitch button:hover{ color:rgba(220,210,185,.85); }
#langSwitch button.lang-active{ color:var(--gold-lt); }
#langSwitch .lang-sep{ color:rgba(201,169,97,.4); font-size:.7rem; }
#langSwitch button:focus-visible{ outline:1px solid rgba(201,169,97,.6); outline-offset:2px; border-radius:3px; }

/* ===== combined venue "Getting There" panel (address + travel) ===== */
.gt-addr{ text-align:center; margin-bottom:6px; }
.gt-addr .venue-loc-addr{
  font-family:'Cormorant Garamond',serif; font-style:italic;
  font-size:clamp(1.1rem,2.6vw,1.4rem); color:var(--text-lt); line-height:1.6;
}
.gt-addr .venue-loc-time{
  font-family:'Cinzel',serif; font-size:.62rem; letter-spacing:.3em;
  color:var(--gold); margin-top:10px;
}
/* The two times, in bold. Cinzel at .62rem with wide letter-spacing
   reads very light next to the address above it, so both are turned up.
   How far is SETTINGS.venueTimeWeight in js/settings.js - one dial each,
   so either can be leaned on without touching the other.

   Selected by content path rather than a new class, so nothing in
   index.html has to change. The number after the comma is the fallback,
   i.e. what you get if settings.js ever fails to load. */
.gt-addr [data-c="venue.ceremony.time"] { font-weight:var(--venue-time-w-ceremony, 600); }
.gt-addr [data-c="venue.reception.time"]{ font-weight:var(--venue-time-w-reception, 600); }
.gt-divider{
  height:1px; margin:22px auto 26px;
  background:linear-gradient(90deg, transparent, rgba(201,169,97,.5), transparent);
}

/* ===== RSVP personal greeting + gate ===== */
.rsvp-greeting{
  font-family:'Cormorant Garamond',serif;
  font-size:clamp(1.15rem,2.6vw,1.5rem);
  color:var(--ivory);font-style:italic;line-height:1.5;
  margin-bottom:26px;padding-bottom:22px;
  border-bottom:1px solid rgba(201,168,76,.2);
}
.rsvp-greeting .rg-seats{
  display:block;margin-top:8px;
  font-family:'Cinzel',serif;font-style:normal;
  font-size:.62rem;letter-spacing:.24em;color:var(--gold-lt);
}
/* the family's own message, from column H of the sheet. Sits between the
   greeting and the form, so it carries the rule that separates them. */
.rsvp-message{
  font-family:'Cormorant Garamond',serif;
  font-size:clamp(1rem,2.2vw,1.2rem);
  color:rgba(220,210,185,.82);line-height:1.6;
  margin:-10px 0 26px;padding-bottom:22px;
  border-bottom:1px solid rgba(201,168,76,.2);
}
.rsvp-message .rich-btn{ margin-top:14px; }
.rsvp-gate{
  padding:26px 24px;border:1px solid rgba(201,168,76,.25);
  text-align:center;color:var(--ivory);
}
.rsvp-gate .rg-title{
  font-family:'Cinzel',serif;font-size:.6rem;letter-spacing:.3em;
  color:var(--gold);margin-bottom:12px;
}
.rsvp-gate .rg-body{
  font-family:'Cormorant Garamond',serif;font-style:italic;
  font-size:1.1rem;line-height:1.6;color:rgba(220,210,185,.85);
}
/* The address, wherever {email} was written in the wording. This used to
   be an inline style="" on a link built in JS; as a class it is one more
   thing the Content-Security-Policy does not have to make room for. */
.rg-mail{
  color:var(--gold-lt);
  text-decoration:none;
  border-bottom:1px solid rgba(201,168,76,.35);
  font-style:normal;
  white-space:nowrap;
}
.rg-mail:hover{ color:var(--gold); border-bottom-color:var(--gold); }

/* ===== RSVP per-person checkboxes ===== */
.people-list{display:flex;flex-direction:column;gap:2px;}
.person-row{
  display:flex;align-items:center;gap:14px;
  padding:12px 14px;cursor:pointer;
  border:1px solid transparent;border-radius:4px;
  transition:background .18s ease,border-color .18s ease;
}
.person-row:hover{background:rgba(140,174,159,.08);}
.person-row.is-on{border-color:rgba(201,168,76,.35);background:rgba(201,168,76,.06);}
.person-row.person-all{
  border-bottom:1px solid rgba(201,168,76,.22);
  border-radius:4px 4px 0 0;margin-bottom:4px;
}
.person-all .person-name{font-family:'Cinzel',serif;font-size:.66rem;letter-spacing:.16em;text-transform:uppercase;}
.person-box{
  flex:0 0 auto;width:22px;height:22px;border-radius:4px;
  border:1.5px solid rgba(201,168,76,.55);background:transparent;
  position:relative;transition:background .18s ease,border-color .18s ease;
}
.person-row.is-on .person-box{background:var(--gold);border-color:var(--gold);}
.person-box svg{position:absolute;inset:0;width:100%;height:100%;opacity:0;transform:scale(.6);transition:opacity .18s ease,transform .18s ease;}
.person-row.is-on .person-box svg{opacity:1;transform:scale(1);}
.person-name{
  font-family:'Cormorant Garamond',serif;font-size:1.18rem;
  color:var(--ivory);line-height:1.2;
}
.person-row input[type="checkbox"]{position:absolute;opacity:0;width:0;height:0;pointer-events:none;}

/* plus-one (a member row whose name is "(placeholder)").
   It works like any other person: tick the box first, and only then does
   the name field appear. The box itself is locked until the person who is
   bringing them is ticked - a plus one can never come on their own. */
.person-plusone{flex-wrap:wrap;}
/* the tickable part: box + "Plus one". The name field sits OUTSIDE this
   label on purpose - inside it, clicking the field would toggle the box. */
.plusone-head{
  display:flex;align-items:center;gap:14px;
  flex:1 1 auto;cursor:pointer;
}
/* locked = the person bringing them has not been ticked yet */
.person-plusone.is-locked{opacity:.4;}
.person-plusone.is-locked .plusone-head{cursor:not-allowed;}
.person-plusone.is-locked:hover{background:transparent;}
.plusone-input{
  display:none;                 /* revealed by .is-on below */
  flex:1 1 100%;min-width:0;width:100%;margin-top:10px;background:transparent;
  border:1px solid rgba(201,168,76,.4);border-radius:4px;
  font-family:'Cormorant Garamond',serif;font-size:1.18rem;font-style:italic;
  color:var(--ivory);padding:10px 14px;outline:none;
}
.person-plusone.is-on .plusone-input{display:block;}
.plusone-input::placeholder{color:rgba(220,210,185,.5);font-style:italic;}
.plusone-input:focus{border-color:var(--gold);background:rgba(201,168,76,.05);}

/* ===== full-screen typed intro ("Our Journey starts here...") ===== */
#introOverlay{
  /* Anchored to the TOP of the page, sized to one screen height - so it
     sits exactly over the hero video and is HARD-LOCKED to the video's
     centre, not the screen's. It scrolls together with the video, so the
     "Our Journey" line stays dead-centre on the footage even if a guest
     scrolls before it fades (it never jumps to the middle of the screen). */
  position:absolute;top:0;left:0;right:0;
  height:100vh;height:100svh;z-index:200;
  display:none;                       /* switched to flex by JS when it runs */
  align-items:center;justify-content:center;
  /* transparent - the hero video plays behind it. A soft radial scrim
     keeps the text readable over any frame. */
  background:radial-gradient(ellipse at center, rgba(18,26,20,.55) 0%, rgba(18,26,20,.35) 45%, rgba(18,26,20,.15) 100%);
  padding:32px;text-align:center;
  opacity:1;transition:opacity 1s ease;
  pointer-events:none;                /* never blocks taps on the site */
}
#introOverlay.intro-show{display:flex;}
#introOverlay.intro-hide{opacity:0;}
.intro-inner{
  display:inline-flex;align-items:baseline;
  max-width:90%;
}
#introText{
  font-family:'Cormorant Garamond',serif;font-style:italic;
  font-size:clamp(1.6rem,5.5vw,3rem);color:var(--ivory);
  line-height:1.3;white-space:pre-wrap;
  text-shadow:0 2px 18px rgba(0,0,0,.6), 0 1px 3px rgba(0,0,0,.5);
}
.intro-caret{
  display:inline-block;width:2px;height:1em;margin-left:4px;
  background:var(--gold-lt, #d8b45c);
  transform:translateY(.12em);
  animation:introBlink .8s step-end infinite;
}
.intro-caret.caret-done{animation:none;opacity:0;transition:opacity .4s ease;}
@keyframes introBlink{50%{opacity:0;}}
@media (prefers-reduced-motion: reduce){
  .intro-caret{animation:none;}
}

/* ===== RSVP decline + single-seat choices ===== */
.people-head{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  flex-wrap:wrap;margin-bottom:6px;
  border-bottom:1px solid rgba(201,168,76,.22);padding-bottom:6px;
}
.people-head .person-row.person-all{border-bottom:none;margin-bottom:0;flex:1;min-width:160px;}
.decline-btn{
  flex:0 0 auto;background:transparent;cursor:pointer;
  border:1px solid rgba(201,168,76,.4);border-radius:999px;
  color:rgba(220,210,185,.8);
  font-family:'Cinzel',serif;font-size:.56rem;letter-spacing:.14em;text-transform:uppercase;
  padding:9px 16px;transition:all .2s ease;
}
.decline-btn:hover{border-color:var(--gold);color:var(--ivory);}
.decline-btn.is-on{background:rgba(150,60,50,.5);border-color:#c98a80;color:#f2ddd8;}
/* single-seat: two big choices */
.single-choice{display:flex;flex-direction:column;gap:12px;}
.choice-btn{
  width:100%;cursor:pointer;text-align:center;
  background:transparent;border:1px solid rgba(201,168,76,.4);border-radius:6px;
  color:var(--ivory);font-family:'Cormorant Garamond',serif;font-size:1.2rem;
  padding:16px 18px;transition:all .2s ease;
}
.choice-btn:hover{border-color:var(--gold);}
.choice-yes.is-on{background:rgba(201,168,76,.18);border-color:var(--gold);color:var(--gold-lt);}
.choice-no.is-on{background:rgba(150,60,50,.4);border-color:#c98a80;color:#f2ddd8;}


/* ==============================================================
   [C13] PHONES - corrections for small screens and iOS.
   Kept LAST so these win over the rules above. Nothing here
   changes the desktop design; every rule is either inside a
   phone-width media query or is a no-op on a desktop browser.
============================================================== */

/* -- 1. stop the page scrolling sideways ------------------------
   .container gives 12px of side padding, but .row.g-5 pulls itself
   24px WIDER on each side, so the row hung 12px off the screen.
   It looked fine only because body{overflow-x:hidden} was clipping
   it - the page was really 388px wide inside a 375px screen.
   Below 992px every column is already full width, so the horizontal
   half-gutter has nothing left to space apart. Dropping it to match
   the container leaves the text in exactly the same place (12px in
   from the edge) and the row stops overhanging.
   row-gap:3rem is set separately and is untouched.               */
@media(max-width:991px){
  .row.g-5{ --gx:.75rem; }
}

/* -- 2. iOS: 100vh is TALLER than the screen --------------------
   On iPhone, 100vh means "the viewport with the address bar hidden",
   so a 100vh box always overflows what you can actually see.
   Written twice so a browser that has never heard of the new unit
   simply keeps the old value.

   The ENVELOPE keeps svh - the small, bar-visible height. It is a
   scroll lock on a screen nobody scrolls, and locking to the smallest
   height is what guarantees it can never overflow.

   The HERO uses dvh - the height right now, following the address bar
   as it comes and goes. At svh it stayed the bar-visible height even
   after Safari had retracted the bar, and the extra strip of screen
   showed the next section under the video. See the note by #wsHero. */
body.env-open{ height:100vh; height:100svh; }
#wsHero{ min-height:100vh; min-height:100dvh; }

/* -- 3. the floating RSVP seal ----------------------------------
   HISTORY, so nobody re-adds the old fix: fabScale is tuned for a
   desktop screen, and on a 375px phone that same one number rendered
   a 134px seal - over a THIRD of the screen width, sitting on top of
   the content. It used to be capped at 23vw here. That cap has been
   removed because fabScale now has its OWN phone value in
   js/settings.js (fabScale.mobile), so the size is set deliberately
   rather than clamped behind the setting's back. A cap would make
   that new knob look broken - type a bigger number, nothing happens.
   If the seal is too big on a phone, lower fabScale.mobile.        */
/* lift it clear of the iPhone home-indicator bar (0 on every other device).
   The home-indicator inset is ADDED to your setting rather than replacing
   it, so "20px from the bottom" still means 20px of visible gap on an
   iPhone instead of 20px measured from behind the bar. */
#rsvpFab{ bottom:calc(var(--fab-bottom-now,28px) + env(safe-area-inset-bottom, 0px)); }

/* -- 4. fingertip-sized tap targets -----------------------------
   The EN|NL buttons were 25x18px and the email link 176x14px - well
   under the ~44px a fingertip needs. An invisible pseudo-element
   grows the TOUCH area without moving a single pixel on screen, so
   the switch and the link still look exactly as designed.         */
@media(max-width:991px){
  #langSwitch button{ position:relative; }
  #langSwitch button::after{
    content:''; position:absolute; top:50%; left:50%;
    transform:translate(-50%,-50%);
    width:38px; height:44px;          /* 38px = just short of touching its neighbour */
  }
  /* The contact address used to sit in .rsvp-intro. It now lives under
     the question form as .ask-fallback a - the need for a thumb-sized
     tap target moved with it rather than going away. */
  .ask-fallback a{ position:relative; display:inline-block; }
  .ask-fallback a::after{
    content:''; position:absolute; left:0; right:0; top:50%;
    height:44px; transform:translateY(-50%);
  }
}

/* -- 5. the smallest labels ------------------------------------
   The engraved Cinzel labels are letter-spaced, which makes them
   read larger than their size suggests - but 7.7px is below what
   anyone should have to squint at on a phone. Only the ones a guest
   actually needs to READ are nudged up; the hero wording and the
   decorative placeholder captions are left exactly as designed.
   To undo, delete this block.                                    */
@media(max-width:575px){
  .sec-label     { font-size:.58rem; }   /* "Our Story", "The Location"  */
  /* two classes, to outrank the .gt-addr .venue-loc-time rule above */
  .venue-loc-time,
  .gt-addr .venue-loc-time{ font-size:.74rem; }   /* the ceremony / arrival times */
  .foot-date     { font-size:.66rem; }
  /* The contact address needed a size bump here only while it was the
     old Cinzel .62rem line in .rsvp-intro. Under the question form it is
     Cormorant and measures ~20px on a phone already, so there is nothing
     left to fix - and js/sizes.js writes an inline size on that element
     anyway, which no rule here could have beaten. */
}

/* -- 6. small iOS/Android manners -------------------------------
   text-size-adjust: stops iOS inflating text of its own accord when
   the phone is turned to landscape.
   tap-highlight: replaces the default grey flash on tap with a faint
   gold one, so tapping the envelope feels like part of the design.  */
html{ -webkit-text-size-adjust:100%; text-size-adjust:100%; }
#envScreen, #rsvpFab, a, button, .person-row, .plusone-head, .choice-btn{
  -webkit-tap-highlight-color:rgba(201,168,76,.18);
}

/* ===== [C12] THE END OF THE PAGE ===================================
   Keep pushing at the very bottom and one more line appears - see [J8]
   in main.js. Nothing here is on screen until it is found, so a guest
   who stops scrolling when the page stops pays nothing for it.

   max-height rather than height because the line wraps to two on a
   phone and a transition needs a number to animate to. 180px is
   generous on purpose; it is a ceiling, not a size. */
/* The hairline that answers before the line does. --p is the guest's
   progress from 0 to 1, set by [J8]; everything about how that reads is
   decided here. It starts as a 20px thread barely off the background and
   ends 110px wide and clearly gold - enough to notice, never enough to
   explain itself. */
.egg-hint{
  --p:0;
  height:1px; margin:26px auto 0;
  width:calc(20px + var(--p) * 90px);
  background:rgba(201,168,76,.75);
  opacity:0;
  transition:opacity .4s ease, width .4s ease;
}
.egg-hint.on{ opacity:calc(.22 + var(--p) * .6); }
@media (prefers-reduced-motion: reduce){
  .egg-hint{ transition:opacity .2s ease; }
}

.egg-end{
  max-height:0; opacity:0; overflow:hidden; margin-top:0;
  transition:max-height .9s ease, opacity .9s ease, margin-top .9s ease;
}
.egg-end.on{ max-height:180px; opacity:1; margin-top:30px; }
.egg-end-gem{
  width:5px; height:5px; margin:0 auto 14px;
  background:rgba(201,168,76,.55); transform:rotate(45deg);
}
/* one step brighter than .foot-note, which is nearly invisible by
   design - this one is the reward and has to be readable */
.egg-end-line{
  font-family:'Cormorant Garamond',serif; font-style:italic;
  font-size:clamp(.95rem,2.4vw,1.08rem); line-height:1.5;
  color:rgba(220,210,185,.45); max-width:34ch; margin:0 auto;
}
@media (prefers-reduced-motion: reduce){
  .egg-end{ transition:opacity .3s ease; }
}

/* Six bursts across the whole viewport, gone inside 1.8s. Each speck
   carries its own angle, distance, size, tint, delay and lifetime from
   [J8]; the only thing shared is this animation. No canvas, no frame
   loop. */
.egg-spark{
  --s:4px;
  position:fixed; z-index:9000; pointer-events:none;
  width:var(--s); height:var(--s); border-radius:50%;
  /* half its own size back, so left/top name the CENTRE of the speck and
     the burst radiates from the point [J8] aimed at */
  margin:calc(var(--s) / -2) 0 0 calc(var(--s) / -2);
  background:var(--c,#c9a84c); box-shadow:0 0 10px var(--c,#c9a84c);
  animation:eggSpark var(--life,1100ms) cubic-bezier(.15,.6,.35,1) var(--t,0ms) both;
}
/* It has to grow before it shrinks. An earlier version ran .35 -> .15
   and so never reached even half the size it was given - 4px of speck
   drawn at 1.4px, which reads as dust rather than as a firework. Full
   size is at 28%, just after it leaves the centre. */
@keyframes eggSpark{
  0%  { transform:rotate(var(--a,0deg)) translateY(0) scale(.2); opacity:0; }
  12% { opacity:1; }
  28% { transform:rotate(var(--a,0deg)) translateY(calc(var(--d,40px) * -.42)) scale(1); }
  100%{ transform:rotate(var(--a,0deg)) translateY(calc(var(--d,40px) * -1)) scale(.25);
        opacity:0; }
}
/* the gem lands rather than fades in, so the burst has something to
   come from */
.egg-end.on .egg-end-gem{ animation:eggGem .7s cubic-bezier(.2,1.5,.4,1) .2s both; }
@keyframes eggGem{
  from{ transform:rotate(45deg) scale(0); }
  to  { transform:rotate(45deg) scale(1); }
}
@media (prefers-reduced-motion: reduce){
  .egg-end.on .egg-end-gem{ animation:none; }
}

