/* ============================================================================
   FREDRIKA & HOSEA — graphite, gold foil and a thread of blush

   The parent of the two proposal pieces in this folder. bridesmaid-fh is a
   light blush ground; groomsman-fh is near-black charcoal. This one is the
   middle and the origin of both: WARM GREY, taken from the card film itself,
   which travels from pewter (#5A5750 in the first frame) down into graphite
   (#191817 in the last). The client asked for grey.

   Three consequences run through this file:

     1. Grey is unforgiving. It fails by looking FLAT, so this is carried by
        tonal range — four distinct grounds from #0E0D0C to #34302C — generous
        space and real typographic hierarchy, never by decoration.
     2. Every grey here is WARM. Red channel above blue in every token. A
        neutral or blue grey next to gold reads as concrete.
     3. The foil is the LUMINOUS dark-ground variant (#D6B166 → #FFF6DC →
        #D6B166), the same gold as groomsman-fh. On a light blush ground a
        near-white highlight makes letters vanish mid-sweep; on graphite it is
        the shine, and a deep-bronze gradient would go muddy instead. The
        bridesmaid file therefore carries a deliberately DIFFERENT gold, and
        neither should be copied into the other.

   Blush appears only in slivers — a rule, a chip, the Day Two glow. It is the
   thread that makes these three pages read as one wedding.
   ========================================================================== */

:root{
  /* graphite → pewter. Sampled from the film's own frames. Warm throughout. */
  --gr-980:#0C0B0A;
  --gr-950:#121110;
  --gr-900:#191817;
  --gr-850:#201E1C;
  --gr-800:#272422;
  --pw-700:#332F2C;
  --pw-600:#3E3A36;
  --pw-500:#4C4741;
  --pw-400:#5E5850;
  --pw-300:#7A736A;

  /* gold — DARK-ground variant, luminous end to end */
  --gold:#C9A24B;
  --gold-hi:#FFF6DC;
  --gold-mid:#D6B166;
  --gold-deep:#B98F3C;
  --gold-line:rgba(201,162,75,.30);
  --gold-line-soft:rgba(201,162,75,.13);

  /* blush — the envelope lining and the thread to the two proposal pieces.
     Slivers only, never a field. */
  --blush:#E5B7A9;
  --blush-deep:#D89C8D;
  --blush-line:rgba(229,183,169,.28);

  /* ink — warm ivory, never white. White on graphite glares and loses the
     paper feeling entirely. */
  --ivory:#F1E8DC;
  --ivory-strong:#FBF5EC;
  --ivory-soft:rgba(241,232,220,.80);
  --ivory-mute:rgba(241,232,220,.56);
  --ivory-faint:rgba(241,232,220,.26);

  --ease:cubic-bezier(.22,1,.36,1);
  --serif:'Cormorant Garamond',Georgia,serif;
  --script:'Pinyon Script',cursive;
  --sans:'Jost',-apple-system,'Segoe UI',sans-serif;

  --maxw:560px;
  --maxw-wide:700px;

  /* keep in step with films.dissolveMs in js/config.js */
  --dissolve:1400ms;

  /* The damask on the envelope, pressed INTO the paper: an ogee lattice with a
     palmette hung inside it and scroll curls at the nodes, drawn twice — a
     light copy offset up-left and a dark copy offset down-right. A closed
     single motif tiles as a field of recognisable shapes; the lattice is what
     makes it read as texture instead. Two data-URIs, no image request.
     Kept very faint: a damask you can identify is wallpaper, not texture. */
  --damask-light:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27180%27%20height%3D%27180%27%20viewBox%3D%270%200%20180%20180%27%3E%3Cg%20fill%3D%27none%27%20stroke%3D%27%23ffffff%27%20stroke-width%3D%271.5%27%20stroke-linecap%3D%27round%27%3E%3Cpath%20d%3D%27M0%2090%20C%2022%2052%2C%2056%2030%2C%2090%200%27%2F%3E%3Cpath%20d%3D%27M90%200%20C%20124%2030%2C%20158%2052%2C%20180%2090%27%2F%3E%3Cpath%20d%3D%27M0%2090%20C%2022%20128%2C%2056%20150%2C%2090%20180%27%2F%3E%3Cpath%20d%3D%27M90%20180%20C%20124%20150%2C%20158%20128%2C%20180%2090%27%2F%3E%3Cpath%20d%3D%27M90%2062%20C%20100%2074%2C%20109%2082%2C%20109%2093%20C%20109%20105%2C%20100%20113%2C%2090%20124%20C%2080%20113%2C%2071%20105%2C%2071%2093%20C%2071%2082%2C%2080%2074%2C%2090%2062%27%2F%3E%3Cpath%20d%3D%27M90%2077%20C%2095%2084%2C%20100%2088%2C%20100%2094%20C%20100%20100%2C%2095%20104%2C%2090%20110%20C%2085%20104%2C%2080%20100%2C%2080%2094%20C%2080%2088%2C%2085%2084%2C%2090%2077%27%2F%3E%3Cpath%20d%3D%27M90%2062%20C%2094%2053%2C%20101%2048%2C%20109%2045%27%2F%3E%3Cpath%20d%3D%27M90%2062%20C%2086%2053%2C%2079%2048%2C%2071%2045%27%2F%3E%3Cpath%20d%3D%27M90%20124%20C%2095%20134%2C%20102%20139%2C%20111%20142%27%2F%3E%3Cpath%20d%3D%27M90%20124%20C%2085%20134%2C%2078%20139%2C%2069%20142%27%2F%3E%3Cpath%20d%3D%27M82%208%20C%2086%2015%2C%2094%2015%2C%2098%208%27%2F%3E%3Cpath%20d%3D%27M82%20172%20C%2086%20165%2C%2094%20165%2C%2098%20172%27%2F%3E%3Cpath%20d%3D%27M8%2082%20C%2015%2086%2C%2015%2094%2C%208%2098%27%2F%3E%3Cpath%20d%3D%27M172%2082%20C%20165%2086%2C%20165%2094%2C%20172%2098%27%2F%3E%3Cpath%20d%3D%27M36%2040%20C%2044%2044%2C%2048%2051%2C%2047%2059%27%2F%3E%3Cpath%20d%3D%27M144%2040%20C%20136%2044%2C%20132%2051%2C%20133%2059%27%2F%3E%3Cpath%20d%3D%27M36%20140%20C%2044%20136%2C%2048%20129%2C%2047%20121%27%2F%3E%3Cpath%20d%3D%27M144%20140%20C%20136%20136%2C%20132%20129%2C%20133%20121%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
  --damask-dark:url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20width%3D%27180%27%20height%3D%27180%27%20viewBox%3D%270%200%20180%20180%27%3E%3Cg%20fill%3D%27none%27%20stroke%3D%27%23000000%27%20stroke-width%3D%271.5%27%20stroke-linecap%3D%27round%27%3E%3Cpath%20d%3D%27M0%2090%20C%2022%2052%2C%2056%2030%2C%2090%200%27%2F%3E%3Cpath%20d%3D%27M90%200%20C%20124%2030%2C%20158%2052%2C%20180%2090%27%2F%3E%3Cpath%20d%3D%27M0%2090%20C%2022%20128%2C%2056%20150%2C%2090%20180%27%2F%3E%3Cpath%20d%3D%27M90%20180%20C%20124%20150%2C%20158%20128%2C%20180%2090%27%2F%3E%3Cpath%20d%3D%27M90%2062%20C%20100%2074%2C%20109%2082%2C%20109%2093%20C%20109%20105%2C%20100%20113%2C%2090%20124%20C%2080%20113%2C%2071%20105%2C%2071%2093%20C%2071%2082%2C%2080%2074%2C%2090%2062%27%2F%3E%3Cpath%20d%3D%27M90%2077%20C%2095%2084%2C%20100%2088%2C%20100%2094%20C%20100%20100%2C%2095%20104%2C%2090%20110%20C%2085%20104%2C%2080%20100%2C%2080%2094%20C%2080%2088%2C%2085%2084%2C%2090%2077%27%2F%3E%3Cpath%20d%3D%27M90%2062%20C%2094%2053%2C%20101%2048%2C%20109%2045%27%2F%3E%3Cpath%20d%3D%27M90%2062%20C%2086%2053%2C%2079%2048%2C%2071%2045%27%2F%3E%3Cpath%20d%3D%27M90%20124%20C%2095%20134%2C%20102%20139%2C%20111%20142%27%2F%3E%3Cpath%20d%3D%27M90%20124%20C%2085%20134%2C%2078%20139%2C%2069%20142%27%2F%3E%3Cpath%20d%3D%27M82%208%20C%2086%2015%2C%2094%2015%2C%2098%208%27%2F%3E%3Cpath%20d%3D%27M82%20172%20C%2086%20165%2C%2094%20165%2C%2098%20172%27%2F%3E%3Cpath%20d%3D%27M8%2082%20C%2015%2086%2C%2015%2094%2C%208%2098%27%2F%3E%3Cpath%20d%3D%27M172%2082%20C%20165%2086%2C%20165%2094%2C%20172%2098%27%2F%3E%3Cpath%20d%3D%27M36%2040%20C%2044%2044%2C%2048%2051%2C%2047%2059%27%2F%3E%3Cpath%20d%3D%27M144%2040%20C%20136%2044%2C%20132%2051%2C%20133%2059%27%2F%3E%3Cpath%20d%3D%27M36%20140%20C%2044%20136%2C%2048%20129%2C%2047%20121%27%2F%3E%3Cpath%20d%3D%27M144%20140%20C%20136%20136%2C%20132%20129%2C%20133%20121%27%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E");
}

*,*::before,*::after{ box-sizing:border-box }

/* The `hidden` attribute is applied by the UA stylesheet as `display:none`,
   which ANY author `display` declaration silently overrides. Several things
   here are flex/grid containers that are ALSO toggled with `hidden` — the
   countdown, the map frame, the banking card, the RSVP confirmation, the
   scroll hint, the hero name line. Without this rule they stay in the layout,
   and a full-bleed one sits invisibly over the page swallowing every click,
   "Tap To Open" included. This has to beat all of them. */
[hidden]{ display:none !important }

html,body{ margin:0; padding:0 }
html{ -webkit-text-size-adjust:100%; scroll-behavior:auto }

body{
  background:var(--gr-900);
  /* Cormorant Garamond ships OLDSTYLE figures by default: "11:00" then reads
     as "I I:oo" and "27" drops below the baseline. Charming in running prose,
     unusable on the one page whose whole job is dates and times. */
  font-variant-numeric:lining-nums;
  font-feature-settings:"lnum" 1;
  color:var(--ivory);
  font-family:var(--serif);
  font-weight:300;
  line-height:1.6;
  overflow-x:hidden;
  -webkit-font-smoothing:antialiased;
}
img{ max-width:100%; display:block }
button{ font:inherit; color:inherit; cursor:pointer; border:0; background:none }
a{ color:var(--gold-mid); text-decoration:none }

main{ position:relative }

html.intro-lock, html.intro-lock body{
  overflow:hidden !important; height:100%;
  touch-action:none; overscroll-behavior:none;
}

::selection{ background:rgba(201,162,75,.30); color:var(--ivory-strong) }

/* ── type primitives ─────────────────────────────────────────────────────── */
.eyebrow{
  font-family:var(--sans); font-weight:200;
  font-size:clamp(.6rem,2.4vw,.68rem);
  letter-spacing:.42em; text-indent:.42em; text-transform:uppercase;
  color:var(--gold-mid); margin:0;
}
.micro{
  font-family:var(--sans); font-weight:200;
  font-size:clamp(.6rem,2.3vw,.66rem);
  letter-spacing:.24em; text-indent:.24em; text-transform:uppercase;
  color:var(--ivory-mute); margin:0;
}
.body{
  font-size:clamp(1rem,3.8vw,1.09rem); line-height:1.85;
  color:var(--ivory-soft); margin:0; font-weight:300; max-width:46ch;
}
.lede{
  font-size:clamp(1.06rem,4.2vw,1.22rem); line-height:1.75;
  color:var(--ivory-soft); font-style:italic; margin:0; max-width:36ch;
}

.script{
  font-family:var(--script); font-weight:400;
  font-size:clamp(2.4rem,10.5vw,3.5rem);
  /* Script faces draw ink OUTSIDE their line box, and background-clip:text
     only paints inside it — any overshoot renders transparent, which looks
     like the letters have been sliced. Room on all four sides, and never a
     tight line-height. */
  line-height:1.2;
  padding:.18em .12em .2em;
  margin:0;
}
.script.xl{ font-size:clamp(2.9rem,12.5vw,4.6rem) }

/* Foil for DARK grounds: luminous end to end, so the sweep varies WARMTH
   rather than brightness and contrast never drops out mid-animation.
   NO `filter` on foil, ever. A filter forces a composited layer that Chrome
   rasterises once and rescales, and type painted through background-clip then
   goes permanently soft. */
.foil{
  background-image:linear-gradient(100deg,
    #B98F3C 0%, #D6B166 14%, #EFD79E 30%,
    #FFF6DC 46%, #EFD79E 62%, #D0A957 80%, #B98F3C 100%);
  background-size:260% 100%;
  -webkit-background-clip:text; background-clip:text;
  -webkit-text-fill-color:transparent; color:transparent;
  animation:foilSweep 11s ease-in-out infinite;
  text-rendering:geometricPrecision;
}
@keyframes foilSweep{
  0%,100%{ background-position:0% 50% }
  50%    { background-position:100% 50% }
}

/* The FH monogram is a plain <img> with the gold baked into the pixels
   (assets/monogram-foil.png and assets/monogram-seal.png, generated from the
   client's Logo 2.png). It used to be a CSS gradient masked through the
   artwork's alpha, which picked up the same living foil sweep as the headings
   — but a mask that does not apply renders as NOTHING, and a logo that can
   silently disappear is worse than one that does not shimmer. An <img> either
   shows the mark or shows a broken-image icon. */

/* ── the wax seal ─────────────────────────────────────────────────────────
   The brand of this wedding. The card film ends on it, so it is used
   throughout: the waiting state, the RSVP confirmation, the finale.

   Poured wax, not a coin — hence the four uneven radii, the off-centre
   highlight, and a rim that catches light on the upper left and shadow on the
   lower right. The monogram inside is painted DARKER than the wax, because a
   seal is struck INTO wax rather than printed onto it. */
.wax-seal{
  position:relative; display:grid; place-items:center; flex:0 0 auto;
  width:clamp(76px,18vw,112px); height:clamp(76px,18vw,112px);
  border-radius:47% 53% 52% 48% / 51% 47% 53% 49%;
  background:
    radial-gradient(circle at 34% 28%, rgba(255,238,196,.60) 0%, rgba(255,238,196,0) 46%),
    radial-gradient(circle at 62% 74%, rgba(60,38,10,.55) 0%, rgba(60,38,10,0) 52%),
    linear-gradient(150deg,#D8B063 0%,#BE944A 34%,#96702F 66%,#6E5121 100%);
  box-shadow:
    inset 0 2px 4px rgba(255,242,208,.42),
    inset 0 -3px 7px rgba(38,24,6,.55),
    0 18px 34px -18px rgba(0,0,0,.95);
}
/* the pressed rim */
.wax-seal::before{
  content:""; position:absolute; inset:9%;
  border-radius:inherit;
  box-shadow:
    inset 0 2px 3px rgba(40,26,8,.55),
    inset 0 -2px 2px rgba(255,240,200,.28);
}
/* Struck INTO the wax, not printed on it. The espresso face, the dark edge
   above and the catch-light below are all baked into monogram-seal.png — the
   light in the film's own seal falls from the upper left, so the shadow inside
   an impression sits at the top of each stroke. */
.wax-seal .ws-mark{
  position:relative; z-index:2; display:block;
  width:64%; height:auto;
}
/* A shallow well under the mark, so the impression sits in the wax rather than
   floating on top of it. */
.wax-seal::after{
  content:""; position:absolute; inset:16%; border-radius:50%;
  background:radial-gradient(circle at 50% 46%, rgba(60,40,10,.30), rgba(60,40,10,0) 72%);
  pointer-events:none; z-index:1;
}
.wax-seal.small{ width:64px; height:64px }

@keyframes sealBreathe{
  0%,100%{ transform:scale(1);    box-shadow:inset 0 2px 4px rgba(255,242,208,.42), inset 0 -3px 7px rgba(38,24,6,.55), 0 18px 34px -18px rgba(0,0,0,.95), 0 0 0 0 rgba(201,162,75,.20) }
  50%    { transform:scale(1.035); box-shadow:inset 0 2px 4px rgba(255,242,208,.50), inset 0 -3px 7px rgba(38,24,6,.55), 0 22px 40px -18px rgba(0,0,0,.95), 0 0 0 18px rgba(201,162,75,0) }
}
.wax-seal.breathing{ animation:sealBreathe 3.6s ease-in-out infinite }

.rule{
  display:block; width:min(230px,62%); height:1px; position:relative;
  background:linear-gradient(90deg,transparent,var(--gold-line),transparent);
  margin:.3rem 0 .45rem;
}
.rule::after{
  content:""; position:absolute; left:50%; top:50%;
  width:5px; height:5px; transform:translate(-50%,-50%) rotate(45deg);
  background:var(--gold); box-shadow:0 0 8px rgba(201,162,75,.55);
}
/* the blush variant — used once per day section, and nowhere else */
.rule.blush{ background:linear-gradient(90deg,transparent,var(--blush-line),transparent) }
.rule.blush::after{ background:var(--blush); box-shadow:0 0 8px rgba(229,183,169,.45) }

.orn{ display:block; width:min(200px,56vw); height:10px; position:relative }
.orn::before,.orn::after{
  content:""; position:absolute; top:50%; width:44%; height:1px;
  background:linear-gradient(90deg,transparent,rgba(201,162,75,.85));
}
.orn::before{ left:0 }
.orn::after{ right:0; transform:scaleX(-1) }
.orn i{
  position:absolute; left:50%; top:50%; width:6px; height:6px;
  transform:translate(-50%,-50%) rotate(45deg);
  background:var(--gold); box-shadow:0 0 9px rgba(201,162,75,.6);
}

/* ── stages ──────────────────────────────────────────────────────────────── */
/* Every ground here is a CSS gradient painted on the section itself, and a
   gradient always fills its own box exactly — so sections hug their content
   without the background stopping short. Nothing on this page uses a
   photograph as a section background, which is what would force 100svh. */
.stage{
  position:relative; width:100%;
  min-height:62svh;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center; overflow:hidden;
  padding:clamp(4rem,11vh,6.5rem) clamp(1.4rem,6vw,4rem);
}
.stage>.inner{
  max-width:var(--maxw); width:100%; position:relative; z-index:2;
  display:flex; flex-direction:column; align-items:center;
  gap:clamp(.6rem,1.8vh,1rem);
}
.stage>.inner.wide{ max-width:var(--maxw-wide) }

/* The tonal range that keeps grey from reading as flat. Four grounds, and the
   two day sections have two more of their own. */
.bg-graphite{
  background:
    radial-gradient(96% 60% at 50% 0%,   rgba(94,88,80,.24) 0%, rgba(94,88,80,0) 62%),
    radial-gradient(130% 82% at 14% 100%, rgba(12,11,10,.85) 0%, rgba(12,11,10,0) 60%),
    linear-gradient(180deg,#1A1918 0%,#161514 52%,#111010 100%);
}
.bg-pewter{
  background:
    radial-gradient(96% 62% at 50% 2%,   rgba(122,115,106,.26) 0%, rgba(122,115,106,0) 60%),
    radial-gradient(126% 80% at 86% 100%, rgba(201,162,75,.10) 0%, rgba(201,162,75,0) 58%),
    linear-gradient(180deg,#2C2825 0%,#252220 54%,#1D1B19 100%);
}

/* Damask pressed into the paper. Small tile, very low opacity: at any size
   where you can read the motif it stops being texture and becomes wallpaper. */
.stage::before,.hero-damask{
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
  background-image:var(--damask-light),var(--damask-dark);
  background-position:1px 1px, -1px -1px;
  background-size:170px 170px, 170px 170px;
  opacity:.032;
}
.bg-pewter::before{ opacity:.042 }
/* feathered seam so neighbouring grounds melt instead of banding */
.stage::after{
  content:""; position:absolute; left:0; right:0; bottom:0;
  height:clamp(52px,10vh,110px); z-index:0; pointer-events:none;
  background:linear-gradient(180deg,rgba(10,9,8,0),rgba(10,9,8,.34));
}

/* ── reveals ─────────────────────────────────────────────────────────────── */
.reveal{
  opacity:0; transform:translateY(18px); filter:blur(4px);
  transition:opacity 1.4s var(--ease), transform 1.7s var(--ease), filter 1.4s var(--ease);
  will-change:opacity,transform;
}
/* `filter:none`, NOT `blur(0)`. A zero-radius blur is still an active filter,
   so the element keeps its composited layer and the foil headings stay
   permanently soft. `none` drops the layer and the type snaps back sharp. */
.reveal.in{ opacity:1; transform:none; filter:none; will-change:auto }
/* Foil never reveals through a blur — see the note on .foil. */
.reveal.foil{ filter:none }

/* ══════════════════ THE GOLD THREAD ══════════════════
   One fine line drawn with stroke-dashoffset as the guest scrolls. Two strands
   at the top — Fredrika's and Hosea's — converging into one at the ceremony
   and continuing as one to the finale, where it resolves into the seal.

   The wrapper is absolutely positioned over the whole document behind the
   content, and the path geometry is written by main.js from the measured
   positions of the sections that actually exist. About 1 KB of SVG, no
   library. Driven from scroll position, never from a rAF-only loop. */
.thread{
  position:absolute; left:0; right:0; top:0; bottom:0;
  z-index:1; pointer-events:none; overflow:hidden;
}
.thread svg{
  display:block; width:100%; height:100%; overflow:visible;
  filter:drop-shadow(0 0 4px rgba(201,162,75,.30));
}
.thread path{
  fill:none; stroke-linecap:round;
  stroke-dasharray:1; stroke-dashoffset:1;
}
.th-strand{ stroke:rgba(201,162,75,.34); stroke-width:1 }
.th-main  { stroke:rgba(201,162,75,.44); stroke-width:1.2 }

/* ══════════════════ 1 · OPENING ══════════════════ */
.opening-scene{
  position:relative; height:100svh; height:100dvh; overflow:hidden;
  background:#191817; cursor:pointer; z-index:3;
}
.opening-sticky{ position:absolute; inset:0; overflow:hidden }
.opening-video{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; pointer-events:none; background:#191817;
  z-index:2;
  transition:opacity var(--dissolve) cubic-bezier(.42,0,.35,1);
}
.opening-video.faded{ opacity:0 }

/* The envelope arrives sealed and in low light; the light comes up as it
   opens. It also gives the breathing seal a ground of its own, so the seal
   reads as the subject rather than as a sticker over the artwork. */
/* Three states, and they carry the whole opening beat.

     WAITING   near-opaque. The screen is dark and the only thing on it is the
               FH seal, breathing. That is the ceremony the loading gate is
               meant to read as — and it also resolves a real collision: the
               film's own frames carry a wax seal of their own, and a second
               one laid over visible artwork reads as a sticker.
     READY     lifts to a half-scrim. The envelope appears, and the prompt is
               the brightest thing on it.
     PLAYING   gone. Full light, and the film runs clean. */
.film-scrim{
  position:absolute; inset:0; z-index:3; pointer-events:none;
  background:
    radial-gradient(56% 40% at 50% 50%, rgba(8,7,7,.70) 0%, rgba(8,7,7,.95) 100%),
    linear-gradient(180deg, rgba(8,7,7,.88), rgba(8,7,7,.95));
  opacity:1;
  transition:opacity 1.1s var(--ease);
}
.opening-scene.is-ready   .film-scrim{ opacity:.5 }
.opening-scene.is-playing .film-scrim{ opacity:0 }

/* The designed hero the film dissolves INTO. It sits underneath the film, in
   the graphite the film's last frame actually ends on, so the guest sees the
   invitation opening rather than one picture cutting to another.
   The card has already said YOU ARE INVITED — this is where the names land. */
.hero-plate{
  position:absolute; inset:0; z-index:1;
  display:flex; align-items:center; justify-content:center;
  /* Sampled from the film's closing frame: #0D0C0C at the flap, #1F1E1C
     through the card face, #3B3937 out on the damask border. */
  background:
    radial-gradient(74% 54% at 50% 44%, rgba(48,44,41,.92) 0%, rgba(48,44,41,0) 70%),
    radial-gradient(128% 84% at 50% -8%, rgba(8,7,7,.92) 0%, rgba(8,7,7,0) 56%),
    linear-gradient(178deg,#151413 0%,#201E1C 46%,#2C2825 100%);
  overflow:hidden;
}
/* On the card the damask sits on the OUTER envelope, not on the face — so it
   is masked away from the middle, where the names live. */
.hero-plate .hero-damask,.finale-scene .hero-damask{
  /* A damask you can identify is wallpaper. At full-bleed size the tile has
     to grow AND fade or it competes with the names, which are the whole
     point of this screen. */
  opacity:.03; z-index:0;
  background-size:260px 260px, 260px 260px;
  -webkit-mask-image:radial-gradient(closest-side at 50% 46%, transparent 40%, #000 96%);
  mask-image:radial-gradient(closest-side at 50% 46%, transparent 40%, #000 96%);
}
/* a soft vignette so the type sits on paper, not on a flat swatch */
.hero-plate::after{
  content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:radial-gradient(104% 70% at 50% 44%, rgba(0,0,0,0) 26%, rgba(0,0,0,.70) 100%);
}

.hero-type{
  position:relative; z-index:2;
  display:flex; flex-direction:column; align-items:center;
  text-align:center; gap:.15rem;
  padding:clamp(1.2rem,4vh,2rem) clamp(1.2rem,6vw,2rem);
  max-width:min(760px,94vw);
}
/* Opacity and transform only — no blur. A filter on foil type leaves it
   permanently soft (see .foil). */
.hero-type [data-r]{
  opacity:0; transform:translateY(16px);
  transition:opacity 1.15s var(--ease), transform 1.4s var(--ease);
}
/* The FH mark carried out of the envelope and into the page. It is the first
   thing that resolves in the hero and the last thing on the page, which is
   what makes the scroll close on itself. */
.hero-mono{
  display:block;
  width:clamp(92px,23vw,132px); height:auto;
  margin:0 auto clamp(1rem,3.8vh,1.8rem);
}
/* A small mark closing a section, in place of a second rule. */
.mono-divider{
  display:block;
  width:clamp(38px,9vw,52px); height:auto;
  margin:clamp(1.8rem,6vh,2.8rem) auto 0;
  opacity:.6;
}
.hero-name{
  margin:0 0 .35rem; font-family:var(--serif); font-weight:300; font-style:italic;
  font-size:clamp(1.5rem,6.4vw,2.3rem); line-height:1.3;
  letter-spacing:.02em; color:var(--blush);
}
/* With a guest's name in the link this line runs to 33 characters, which will
   not fit on one line at 375px in letterspaced caps. `text-wrap:balance` makes
   the two lines even instead of stranding "OF" on its own; the max-width is
   the fallback for browsers that do not support it. */
.hero-lead{
  margin:0 0 .1rem; font-family:var(--sans); font-weight:200;
  font-size:clamp(.58rem,2.6vw,.72rem);
  letter-spacing:.38em; text-indent:.38em; text-transform:uppercase;
  color:var(--ivory-mute);
  text-wrap:balance; line-height:1.9;
}
@media (min-width:600px){ .hero-lead{ letter-spacing:.46em; text-indent:.46em } }
/* The names. On a phone they stack with the ampersand between; from 560px
   they sit on one line. This is the moment the whole piece turns on, so it
   gets the largest type on the page and the slowest stagger. */
.hero-names{
  margin:0; display:flex; flex-direction:column; align-items:center;
  gap:0; font-weight:400;
}
.hero-names .hn-one,.hero-names .hn-two{
  font-family:var(--script); font-weight:400;
  font-size:clamp(3rem,15vw,5.6rem); line-height:1.15;
  padding:.14em .1em .18em; display:block;
}
.hero-names .hn-amp{
  font-family:var(--serif); font-style:italic; font-weight:300;
  font-size:clamp(1.1rem,4.6vw,1.6rem); line-height:1;
  color:var(--gold-mid); margin:-.35em 0 -.2em;
}
@media (min-width:600px){
  .hero-names{ flex-direction:row; align-items:baseline; gap:clamp(.5rem,2vw,1rem) }
  .hero-names .hn-amp{ margin:0 }
}
.hero-dates{
  margin:.5rem 0 0; font-family:var(--sans); font-weight:200;
  font-size:clamp(.66rem,2.9vw,.8rem);
  letter-spacing:.4em; text-indent:.4em; text-transform:uppercase;
  color:var(--gold-mid);
}
.hero-place{ margin-top:.35rem; color:var(--ivory-faint) }
.hero-type .orn{ margin-top:.6rem }

/* ── buffering gate ──────────────────────────────────────────────────────
   The prompt is withheld until the film is wholly in memory, so the tap can
   never land on an unbuffered file — play() on an empty element sticks at
   currentTime 0 forever, which reads as a dead button. Strict is only safe
   because readiness cannot hang: see initOpening() for the fetch-error
   fallback, the 30s stall detector, the 5s post-download watchdog and the
   absolute ceiling.

   There is no percentage. The seal breathes; that is the whole indicator. */
.film-loader .wax-seal{ width:clamp(94px,22vw,132px); height:clamp(94px,22vw,132px) }
.film-loader{
  position:absolute; left:50%; top:50%;
  transform:translate(-50%,-50%); z-index:7;
  display:flex; flex-direction:column; align-items:center; gap:1.1rem;
  pointer-events:none;                       /* never swallows a tap */
  transition:opacity .6s var(--ease), transform .6s var(--ease);
}
.film-loader.gone{ opacity:0; transform:translate(-50%,-50%) scale(.96) }
.loader-label{
  font-family:var(--sans); font-weight:200;
  font-size:.56rem; letter-spacing:.36em; text-indent:.36em; text-transform:uppercase;
  color:var(--ivory-mute); text-shadow:0 1px 12px rgba(0,0,0,.9);
}

/* The prompt sits below the seal, in the same column. A solid gold pill with
   deep graphite type is the highest-contrast thing on the screen — a pale
   prompt on the pale first frame of this film would be invisible. */
.tap-cue{
  position:absolute; left:50%; top:50%; z-index:8;
  transform:translate(-50%,calc(-50% + clamp(80px,15vh,120px)));
  opacity:0; pointer-events:none;
  /* the delay lets the loader clear before this arrives */
  transition:opacity .8s var(--ease) .35s;
}
.opening-scene.is-ready .tap-cue{ opacity:1; pointer-events:auto }
.film-cue{ transition:opacity .9s var(--ease), transform .9s var(--ease) }
@media (max-width:360px){ .tap-cue .label{ font-size:.6rem; letter-spacing:.26em; text-indent:.26em } }
.tap-cue .label{
  display:inline-block;
  font-family:var(--sans); font-weight:400;
  font-size:.68rem; letter-spacing:.38em; text-indent:.38em; text-transform:uppercase;
  /* At 320px the label wraps and the pill becomes a tall rounded box, which
     reads as a broken button rather than an invitation to press it. */
  white-space:nowrap;
  color:#1A1614; padding:.95rem clamp(1.3rem,6vw,2rem); border-radius:999px;
  background:linear-gradient(100deg,#B98F3C 0%,#D6B166 30%,#F4E2B4 50%,#D6B166 70%,#B98F3C 100%);
  background-size:200% 100%;
  border:1px solid rgba(255,246,220,.5);
  animation:tapBreathe 3s ease-in-out infinite;
}
@keyframes tapBreathe{
  0%,100%{ box-shadow:0 16px 38px -16px rgba(0,0,0,.95), 0 0 0 6px rgba(201,162,75,.16) }
  50%    { box-shadow:0 16px 38px -16px rgba(0,0,0,.95), 0 0 0 17px rgba(201,162,75,0) }
}
.opening-scene.is-playing .film-cue{
  opacity:0; pointer-events:none;
  transform:translate(-50%,calc(-50% + clamp(80px,15vh,120px))) scale(.94);
}

.scroll-hint{
  position:absolute; left:50%; bottom:1.5rem; transform:translateX(-50%);
  z-index:7; display:flex; flex-direction:column; align-items:center; gap:.45rem;
  opacity:0; transition:opacity 1.2s var(--ease);
  font-family:var(--sans); font-weight:200; font-size:.56rem;
  letter-spacing:.36em; text-indent:.36em; text-transform:uppercase; color:var(--ivory-mute);
}
.scroll-hint.in{ opacity:1 }
.scroll-hint i{
  width:1px; height:34px; display:block;
  background:linear-gradient(180deg,transparent,rgba(201,162,75,.9));
  animation:hintDrop 2.4s ease-in-out infinite;
}
@keyframes hintDrop{
  0%,100%{ transform:scaleY(.5); transform-origin:top; opacity:.4 }
  50%    { transform:scaleY(1);  transform-origin:top; opacity:1 }
}

/* ══════════════════ 2 · COUNTDOWN ══════════════════
   `tabular-nums` and a min-width on each figure keep the row from twitching
   sideways every second as glyph widths change — without it the whole block
   jitters, which is the fastest way to make a countdown feel cheap. */
#s-countdown{ min-height:52svh }

/* Nine tiles have to fit a 320px phone between the stage padding, so the tile
   scales off a small vw factor and the gaps tighten with it. */
.countdown{
  display:flex; justify-content:center; align-items:flex-start;
  gap:clamp(.5rem,2.8vw,1.5rem);
  margin:clamp(1.2rem,4vh,1.9rem) auto clamp(.4rem,1.4vh,.7rem);
  max-width:100%; flex-wrap:nowrap;
}
.fc-unit{ display:flex; flex-direction:column; align-items:center; gap:.7rem }
.fc-digits{ display:flex; gap:3px }
.fc-label{
  font-family:var(--sans); font-weight:200;
  font-size:.52rem; letter-spacing:.26em; text-indent:.26em; text-transform:uppercase;
  color:var(--ivory-faint);
}
.fc-digit{
  --fh:clamp(38px,11.5vw,70px);
  position:relative; width:calc(var(--fh) * .7); height:var(--fh);
  perspective:340px;
  font-family:var(--serif); font-weight:400;
  font-size:calc(var(--fh) * .58); line-height:1;
  font-variant-numeric:lining-nums tabular-nums;
  filter:drop-shadow(0 10px 20px rgba(0,0,0,.7));
}
/* The tiles must be OPAQUE. A translucent leaf lets the new number show
   through the old one as it falls, which reads as the old digit lingering
   behind the flip rather than being replaced by it. */
.fc-half,.fc-flap{
  position:absolute; left:0; width:100%; height:50%; overflow:hidden;
  background-color:#211E1C;
  background-image:linear-gradient(180deg, rgba(255,250,242,.07), rgba(255,250,242,0));
  color:var(--gold-mid);
  box-shadow:inset 0 0 0 1px var(--gold-line-soft);
}
.fc-top,.fc-flap-top{ top:0;    border-radius:5px 5px 0 0 }
.fc-bot,.fc-flap-bot{ bottom:0; border-radius:0 0 5px 5px }
/* The glyph is a full-height absolutely-placed block clipped by the half —
   deterministic, unlike relying on flex alignment of an oversized item. */
.fc-half i,.fc-flap i{
  position:absolute; left:0; right:0; text-align:center;
  height:var(--fh); line-height:var(--fh); font-style:normal;
}
.fc-top i,.fc-flap-top i{ top:0 }
.fc-bot i,.fc-flap-bot i{ top:calc(var(--fh) / -2) }

.fc-hinge{
  position:absolute; left:0; right:0; top:50%; height:1px; z-index:4;
  background:linear-gradient(90deg,transparent,rgba(8,7,7,.9) 16%,rgba(8,7,7,.9) 84%,transparent);
  pointer-events:none;
}
.fc-flap{ z-index:3; display:none; backface-visibility:hidden }
.fc-digit.flipping .fc-flap{ display:block }
.fc-digit.flipping .fc-flap-top{
  transform-origin:50% 100%;
  animation:fcTop .3s cubic-bezier(.4,0,.8,.3) forwards;
}
.fc-digit.flipping .fc-flap-bot{
  transform-origin:50% 0%; transform:rotateX(90deg);
  animation:fcBot .3s cubic-bezier(.2,.8,.4,1) .3s forwards;
}
@keyframes fcTop{ from{ transform:rotateX(0) }     to{ transform:rotateX(-90deg) } }
@keyframes fcBot{ from{ transform:rotateX(90deg) } to{ transform:rotateX(0) } }

/* A leaf turning away from the light darkens as it goes — this is most of
   what makes the flip read as a physical card rather than a CSS rotation. */
.fc-flap::after{
  content:""; position:absolute; inset:0; background:#000;
  opacity:0; pointer-events:none;
}
.fc-digit.flipping .fc-flap-top::after{ animation:fcShadeOut .3s linear forwards }
.fc-digit.flipping .fc-flap-bot::after{ animation:fcShadeIn  .3s linear .3s forwards }
@keyframes fcShadeOut{ from{ opacity:0 }   to{ opacity:.6 } }
@keyframes fcShadeIn { from{ opacity:.6 }  to{ opacity:0 } }

[data-cd-note]{ margin-top:.7rem; color:var(--gold-mid) }
.cd-also{
  margin:.3rem 0 0; font-family:var(--serif); font-style:italic; font-weight:300;
  font-size:clamp(.95rem,3.7vw,1.05rem); color:var(--ivory-mute); letter-spacing:.03em;
}

/* ══════════════════ 3 · OUR STORY ══════════════════
   Four chapters, alternating side, each with a numeral in the margin. Type
   only — no photographs were supplied, and each chapter's optional image
   simply does not render. */
.chapters{
  width:100%; margin-top:clamp(1.4rem,5vh,2.4rem);
  display:flex; flex-direction:column; gap:clamp(2.4rem,8vh,3.6rem);
}
.chapter{
  position:relative; display:grid;
  grid-template-columns:auto 1fr; gap:0 clamp(1rem,4vw,1.8rem);
  text-align:left; max-width:52ch;
  opacity:0; transform:translateY(20px);
  transition:opacity 1.2s var(--ease), transform 1.4s var(--ease);
}
.chapter.in{ opacity:1; transform:none }
.chapter:nth-child(even){ align-self:flex-end }
.chapter:nth-child(odd) { align-self:flex-start }
/* With no numeral there is nothing to sit in the first column, so the
   paragraph takes the whole width rather than leaving an empty gutter. */
.chapter.no-numeral{ grid-template-columns:1fr }
.chapter.no-numeral .ch-figure{ grid-column:1 }
.ch-no{
  grid-row:1 / span 3; margin:0; padding-top:.15rem;
  font-family:var(--serif); font-weight:300; font-style:italic;
  font-size:clamp(1.5rem,6vw,2rem); line-height:1;
  color:var(--gold-line); letter-spacing:.04em; min-width:1.6em;
}
.ch-title{
  margin:0 0 .5rem; font-family:var(--sans); font-weight:200;
  font-size:clamp(.64rem,2.7vw,.72rem);
  letter-spacing:.34em; text-indent:.34em; text-transform:uppercase;
  color:var(--gold-mid);
}
.ch-text{
  margin:0; font-size:clamp(1rem,3.8vw,1.09rem); line-height:1.9;
  color:var(--ivory-soft); font-weight:300;
}
.ch-figure{ grid-column:2; margin:1rem 0 0; overflow:hidden; border-radius:3px }
.ch-figure img{ width:100%; aspect-ratio:4/3; object-fit:cover }

/* ══════════════════ 4 & 5 · THE TWO DAYS ══════════════════
   Two movements, not one section with two entries. Same architecture, two
   temperatures: Day One cooler and more formal, Day Two warmer, softer and
   later. Both are dark — the difference is WARMTH, not brightness, because
   two grounds of different lightness would read as one being more important. */
.day-scene{
  position:relative; width:100%; min-height:84svh;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center; overflow:hidden;
  padding:clamp(4.5rem,12vh,7rem) clamp(1.4rem,6vw,4rem);
}
.day-scene::before{
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none;
  background-image:var(--damask-light),var(--damask-dark);
  background-position:1px 1px, -1px -1px;
  background-size:170px 170px, 170px 170px;
  opacity:.03;
}
.day-scene::after{
  content:""; position:absolute; left:0; right:0; bottom:0;
  height:clamp(52px,10vh,110px); z-index:0; pointer-events:none;
  background:linear-gradient(180deg,rgba(10,9,8,0),rgba(10,9,8,.34));
}
/* Day One — cooler, more formal. The grey pulls towards slate. */
.day-cool{
  background:
    radial-gradient(90% 56% at 50% 0%,   rgba(108,110,112,.20) 0%, rgba(108,110,112,0) 60%),
    radial-gradient(130% 84% at 20% 100%, rgba(8,8,9,.9) 0%, rgba(8,8,9,0) 62%),
    linear-gradient(180deg,#141516 0%,#1B1C1D 50%,#121313 100%);
}
/* Day Two — warmer, softer, later. Umber, and the one place blush is allowed
   to sit as light rather than as a line. */
.day-warm{
  background:
    radial-gradient(94% 58% at 50% 4%,   rgba(229,183,169,.13) 0%, rgba(229,183,169,0) 58%),
    radial-gradient(120% 80% at 82% 96%, rgba(201,162,75,.12) 0%, rgba(201,162,75,0) 56%),
    radial-gradient(140% 86% at 12% 100%, rgba(12,9,8,.88) 0%, rgba(12,9,8,0) 60%),
    linear-gradient(180deg,#1D1815 0%,#251E1A 52%,#171310 100%);
}
.day-inner{
  position:relative; z-index:2; width:100%; max-width:var(--maxw-wide);
  display:flex; flex-direction:column; align-items:center;
  gap:clamp(.5rem,1.6vh,.85rem);
}
.day-tag{
  margin:0; font-family:var(--sans); font-weight:200;
  font-size:clamp(.6rem,2.5vw,.7rem);
  letter-spacing:.46em; text-indent:.46em; text-transform:uppercase;
  color:var(--gold-mid);
}
.day-warm .day-tag{ color:var(--blush) }
.day-date{
  margin:.35rem 0 0; display:flex; align-items:baseline; gap:.55rem;
  font-family:var(--serif); font-weight:300; color:var(--ivory-strong);
}
.day-date b{
  font-weight:300; font-size:clamp(3.6rem,16vw,5.4rem); line-height:1;
  letter-spacing:.01em;
}
.day-date span i{ font-style:normal; display:block }
.day-date span{
  display:flex; flex-direction:column; align-items:flex-start; gap:.1rem;
  font-size:clamp(.9rem,3.6vw,1.05rem); letter-spacing:.14em; text-transform:uppercase;
  font-family:var(--sans); font-weight:200; color:var(--ivory-mute);
}
.day-title{
  margin:.5rem 0 0; font-family:var(--script); font-weight:400;
  font-size:clamp(2.3rem,10vw,3.3rem); line-height:1.2; padding:.16em .12em .2em;
}
.day-lede{ margin:.2rem 0 0; text-align:center }
.day-when{
  margin:.5rem 0 0; font-family:var(--serif); font-style:italic; font-weight:400;
  font-size:clamp(1.05rem,4.2vw,1.2rem); color:var(--gold-mid); letter-spacing:.02em;
}
/* Not `.micro`: a full sentence set in letterspaced caps is a headline
   pretending to be a note, and at 375px it runs edge to edge. */
.day-arrive{
  margin:.35rem 0 0; max-width:34ch;
  font-family:var(--serif); font-weight:300; font-style:italic;
  font-size:clamp(.92rem,3.5vw,1rem); line-height:1.7;
  letter-spacing:.02em; text-transform:none; text-indent:0;
  color:var(--ivory-mute);
}

/* the programme — a thin timeline, not a table */
.programme{
  list-style:none; margin:clamp(1.6rem,5vh,2.4rem) auto 0; padding:0;
  width:max-content; max-width:100%;
  display:flex; flex-direction:column;
}
.pr-step{
  position:relative; display:grid;
  grid-template-columns:clamp(56px,16vw,74px) 1fr;
  gap:0 clamp(1rem,4vw,1.6rem);
  text-align:left; padding-bottom:clamp(1.05rem,3.6vh,1.5rem);
  opacity:0; transform:translateY(12px);
  transition:opacity 1s var(--ease), transform 1.15s var(--ease);
}
.pr-step.in{ opacity:1; transform:none }
.pr-step:last-child{ padding-bottom:0 }
.pr-time{
  margin:0; text-align:right; padding-right:clamp(.9rem,3.5vw,1.3rem);
  position:relative;
  font-family:var(--serif); font-weight:300;
  font-size:clamp(1rem,4vw,1.14rem); line-height:1.45; color:var(--gold-mid);
  font-variant-numeric:tabular-nums;
}
/* the rail and its node, drawn on the time column's right edge */
.pr-time::after{
  content:""; position:absolute; right:-3.5px; top:.42em;
  width:7px; height:7px; transform:rotate(45deg);
  background:var(--gold); box-shadow:0 0 8px rgba(201,162,75,.5);
}
.pr-step:not(:last-child) .pr-time::before{
  content:""; position:absolute; right:0; top:calc(.42em + 10px); bottom:-1.1rem;
  width:1px; background:linear-gradient(180deg,var(--gold-line),rgba(201,162,75,.06));
}
.day-warm .pr-time::after{ background:var(--blush); box-shadow:0 0 8px rgba(229,183,169,.45) }
.day-warm .pr-step:not(:last-child) .pr-time::before{
  background:linear-gradient(180deg,var(--blush-line),rgba(229,183,169,.05));
}
.pr-what{
  margin:0; font-family:var(--serif); font-weight:300;
  font-size:clamp(1rem,4vw,1.14rem); line-height:1.45; color:var(--ivory-soft);
  letter-spacing:.02em;
}

/* the day's venue + dress note, side by side from 640px */
.day-facts{
  width:100%; margin-top:clamp(1.8rem,6vh,2.8rem);
  display:grid; grid-template-columns:1fr; gap:clamp(.9rem,3vw,1.2rem);
}
@media (min-width:640px){ .day-facts{ grid-template-columns:1fr 1fr } }
.fact-card{
  position:relative; padding:clamp(1.4rem,5vw,1.9rem) clamp(1.2rem,4.5vw,1.6rem);
  background:linear-gradient(168deg, rgba(255,250,242,.045), rgba(255,250,242,.018));
  border:1px solid var(--gold-line-soft); border-radius:3px;
  display:flex; flex-direction:column; align-items:center; gap:.35rem;
  text-align:center;
  opacity:0; transform:translateY(16px);
  transition:opacity 1.1s var(--ease), transform 1.3s var(--ease);
}
.fact-card.in{ opacity:1; transform:none }
.fc-label{
  margin:0; font-family:var(--sans); font-weight:200;
  font-size:.56rem; letter-spacing:.34em; text-indent:.34em; text-transform:uppercase;
  color:var(--gold-mid);
}
.day-warm .fc-label{ color:var(--blush) }
.fc-main{
  margin:.25rem 0 0; font-family:var(--serif); font-weight:400;
  font-size:clamp(1.15rem,4.6vw,1.35rem); line-height:1.35; color:var(--ivory-strong);
  letter-spacing:.02em;
}
.fc-sub{
  margin:0; font-family:var(--serif); font-weight:300;
  font-size:clamp(.95rem,3.7vw,1.02rem); color:var(--ivory-mute); line-height:1.6;
}
.fc-note{
  margin:.4rem 0 0; font-family:var(--sans); font-weight:200;
  font-size:.64rem; letter-spacing:.14em; line-height:1.8; color:rgba(241,232,220,.45);
}

/* ══════════════════ 6 · WHERE ══════════════════ */
.venue-cards{
  width:100%; margin-top:clamp(1.4rem,4.5vh,2rem);
  display:grid; grid-template-columns:1fr; gap:clamp(.9rem,3vw,1.2rem);
}
@media (min-width:640px){ .venue-cards[data-two]{ grid-template-columns:1fr 1fr } }
.venue-card{
  position:relative; padding:clamp(1.6rem,5.5vw,2.1rem) clamp(1.2rem,4.5vw,1.7rem);
  background:linear-gradient(168deg, rgba(255,250,242,.05), rgba(255,250,242,.02));
  border:1px solid var(--gold-line); border-radius:3px;
  display:flex; flex-direction:column; align-items:center; gap:.3rem;
  opacity:0; transform:translateY(16px);
  transition:opacity 1.1s var(--ease), transform 1.3s var(--ease);
}
.venue-card.in{ opacity:1; transform:none }
.venue-card::after{
  content:""; position:absolute; inset:6px; border-radius:2px;
  border:1px solid var(--gold-line-soft); pointer-events:none;
}
.vc-tag{
  margin:0; font-family:var(--sans); font-weight:200;
  font-size:.56rem; letter-spacing:.34em; text-indent:.34em; text-transform:uppercase;
  color:var(--gold-mid);
}
.vc-name{
  margin:.3rem 0 0; font-family:var(--serif); font-weight:400;
  font-size:clamp(1.3rem,5.4vw,1.6rem); line-height:1.3; color:var(--ivory-strong);
}
.vc-address{ margin:.15rem 0 0; font-size:clamp(.95rem,3.7vw,1.03rem); color:var(--ivory-mute) }
.vc-time{
  margin:.35rem 0 0; font-family:var(--serif); font-style:italic;
  font-size:clamp(.98rem,3.8vw,1.06rem); color:var(--gold-mid);
}
.vc-note{
  margin:.3rem 0 0; font-family:var(--sans); font-weight:200;
  font-size:.64rem; letter-spacing:.14em; color:rgba(241,232,220,.45); line-height:1.8;
}
.vc-dir{
  margin-top:.9rem; padding:.6rem 1.3rem; border-radius:999px;
  border:1px solid var(--gold-line);
  font-family:var(--sans); font-weight:300; font-size:.58rem;
  letter-spacing:.26em; text-indent:.26em; text-transform:uppercase; color:var(--gold-mid);
  transition:border-color .5s var(--ease), color .5s var(--ease);
}
.vc-dir:hover{ border-color:var(--gold); color:var(--gold-hi) }

.map-frame{
  width:100%; margin:clamp(1.4rem,4.5vh,2rem) 0 0; padding:0;
  border:1px solid var(--gold-line-soft); border-radius:3px; overflow:hidden;
  background:var(--gr-950); line-height:0;
}
.map-frame iframe{
  width:100%; height:clamp(220px,34vh,320px); border:0; display:block;
  /* the embed is a bright daytime map in a graphite page — graded down so it
     belongs, and lifted back on hover for anyone actually reading it */
  filter:grayscale(.55) contrast(.92) brightness(.72) sepia(.14);
  transition:filter .7s var(--ease);
}
.map-frame:hover iframe{ filter:grayscale(.2) contrast(1) brightness(.95) sepia(.05) }

/* add-to-calendar: a ring draws, a tick draws, the label settles */
.cal-btn{
  margin-top:clamp(1.4rem,4.5vh,2rem);
  display:inline-flex; align-items:center; gap:.75rem;
}
/* Hidden until the click. An empty ring next to the label reads as an
   unticked checkbox — as though the calendar entry had failed. */
.cal-ring{
  width:0; height:0; border-radius:50%; display:grid; place-items:center;
  border:1px solid rgba(26,22,20,0); overflow:hidden;
  transition:width .4s var(--ease), height .4s var(--ease), border-color .4s var(--ease);
}
.cal-btn.saving .cal-ring,.cal-btn.saved .cal-ring,.cal-btn.done .cal-ring{
  width:20px; height:20px; border-color:rgba(26,22,20,.45);
}
.cal-ring svg{ width:12px; height:12px; display:block }
.cal-tick{
  fill:none; stroke:#1A1614; stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:26; stroke-dashoffset:26;
}
.cal-btn.saving .cal-ring{ background:rgba(26,22,20,.14) }
.cal-btn.saved .cal-tick{ animation:tickDraw .55s var(--ease) forwards }
.cal-btn.done .cal-tick{ stroke-dashoffset:0 }
@keyframes tickDraw{ to{ stroke-dashoffset:0 } }

/* ══════════════════ 7 · WHAT TO WEAR ══════════════════ */
.palette{
  display:flex; flex-wrap:wrap; justify-content:center;
  gap:clamp(.9rem,3.5vw,1.5rem);
  margin:clamp(1.4rem,4.5vh,2.2rem) 0 clamp(.4rem,1.5vh,.8rem);
}
.swatch{ display:flex; flex-direction:column; align-items:center; gap:.5rem }
.swatch i{
  width:clamp(34px,9vw,46px); height:clamp(34px,9vw,46px); border-radius:50%;
  box-shadow:0 0 0 1px rgba(201,162,75,.28), 0 12px 24px -14px rgba(0,0,0,.9);
}
.swatch span{
  font-family:var(--sans); font-weight:200; font-size:.52rem;
  letter-spacing:.2em; text-transform:uppercase; color:var(--ivory-faint);
}

/* per-day guidance — the point of the section on a two-day wedding */
.wear-days{
  width:100%; margin-top:clamp(1.6rem,5vh,2.4rem);
  display:grid; grid-template-columns:1fr; gap:clamp(.9rem,3vw,1.2rem);
}
@media (min-width:640px){ .wear-days[data-two]{ grid-template-columns:1fr 1fr } }
.wear-day{
  position:relative; padding:clamp(1.4rem,5vw,1.9rem) clamp(1.2rem,4.5vw,1.6rem);
  border:1px solid var(--gold-line-soft); border-radius:3px;
  background:linear-gradient(168deg, rgba(255,250,242,.045), rgba(255,250,242,.015));
  display:flex; flex-direction:column; align-items:center; gap:.3rem;
  opacity:0; transform:translateY(16px);
  transition:opacity 1.1s var(--ease), transform 1.3s var(--ease);
}
.wear-day.in{ opacity:1; transform:none }
.wear-day.is-two{ border-color:var(--blush-line) }
.wd-tag{
  margin:0; font-family:var(--sans); font-weight:200;
  font-size:.56rem; letter-spacing:.34em; text-indent:.34em; text-transform:uppercase;
  color:var(--gold-mid);
}
.wear-day.is-two .wd-tag{ color:var(--blush) }
.wd-label{
  margin:.3rem 0 0; font-family:var(--serif); font-weight:400;
  font-size:clamp(1.2rem,4.8vw,1.4rem); color:var(--ivory-strong); letter-spacing:.02em;
}
.wd-note{
  margin:.25rem 0 0; font-size:clamp(.95rem,3.7vw,1.02rem); line-height:1.75;
  color:var(--ivory-mute);
}

.guide-cards{
  width:100%; margin-top:clamp(1.6rem,5vh,2.4rem);
  display:grid; grid-template-columns:1fr; gap:clamp(.8rem,2.6vw,1rem);
}
@media (min-width:720px){ .guide-cards{ grid-template-columns:repeat(3,1fr) } }
.guide-card{
  padding:clamp(1.3rem,4.5vw,1.6rem) clamp(1.1rem,4vw,1.4rem);
  border-top:1px solid var(--gold-line-soft);
  text-align:left;
  opacity:0; transform:translateY(14px);
  transition:opacity 1.05s var(--ease), transform 1.2s var(--ease);
}
.guide-card.in{ opacity:1; transform:none }
.guide-card h4{
  margin:0 0 .55rem; font-family:var(--sans); font-weight:200;
  font-size:.6rem; letter-spacing:.3em; text-indent:.3em; text-transform:uppercase;
  color:var(--gold-mid);
}
.guide-card p{
  margin:0; font-size:clamp(.95rem,3.6vw,1.02rem); line-height:1.85; color:var(--ivory-soft);
}

/* ══════════════════ A GLIMPSE (removed when empty) ══════════════════ */
.gallery-grid{
  width:100%; margin-top:clamp(1.4rem,4.5vh,2rem);
  display:grid; grid-template-columns:1fr 1fr; gap:clamp(.6rem,2.4vw,1rem);
}
@media (min-width:720px){ .gallery-grid{ grid-template-columns:repeat(3,1fr) } }
.gal-item{
  margin:0; overflow:hidden; border-radius:3px; background:var(--gr-850);
  opacity:0; transform:translateY(16px);
  transition:opacity 1.1s var(--ease), transform 1.3s var(--ease);
}
.gal-item.in{ opacity:1; transform:none }
.gal-item img{ width:100%; aspect-ratio:3/4; object-fit:cover }

/* A value the couple have not supplied — obviously unfinished, never
   invented. Shared by the banking rows. */
.bank-row dd.pending{
  font-family:var(--serif); font-style:italic; text-transform:none; letter-spacing:.02em;
  font-size:.88rem; color:var(--pw-300);
  border-bottom:1px dashed rgba(122,115,106,.6); padding-bottom:1px;
}

/* ══════════════════ shared paper card ══════════════════ */
/* Lifted well clear of the graphite so it reads as a card laid ON the ground
   rather than a panel cut out of it. Still a dark card — the ivory type and
   the gold rules on it have to keep their contrast — but light enough that its
   edge is obvious without relying on the hairline border alone. The deep drop
   shadow is what sells the lift; without it a lighter fill just looks like a
   patch. */
.paper-card{
  position:relative; width:100%; margin-top:clamp(1.2rem,4vh,1.8rem);
  padding:clamp(1.9rem,6vw,2.6rem) clamp(1.3rem,5vw,2.1rem);
  background:linear-gradient(168deg, rgba(255,248,238,.15), rgba(255,248,238,.075));
  border:1px solid var(--gold-line); border-radius:3px;
  box-shadow:
    0 1px 0 rgba(255,250,242,.10) inset,
    0 34px 60px -40px rgba(0,0,0,.95);
  text-align:left;
}
.paper-card::after{
  content:""; position:absolute; inset:6px; border-radius:2px;
  border:1px solid var(--gold-line-soft); pointer-events:none;
}
.ec-corner{ position:absolute; width:13px; height:13px; pointer-events:none; z-index:3 }
.ec-corner::before,.ec-corner::after{ content:""; position:absolute; background:var(--gold); opacity:.5 }
.ec-corner::before{ width:100%; height:1px }
.ec-corner::after { width:1px; height:100% }
.ec-corner.tl{ left:12px; top:12px }
.ec-corner.tr{ right:12px; top:12px } .ec-corner.tr::after{ right:0 }
.ec-corner.bl{ left:12px; bottom:12px } .ec-corner.bl::before{ bottom:0 }
.ec-corner.br{ right:12px; bottom:12px } .ec-corner.br::before{ bottom:0 } .ec-corner.br::after{ right:0 }

/* ══════════════════ 8 · RSVP — PER DAY ══════════════════ */
.rsvp-card{ display:flex; flex-direction:column; gap:.35rem }
.invited-chip{
  align-self:flex-start; margin:0 0 .5rem;
  padding:.48rem 1.05rem; border-radius:999px;
  font-family:var(--sans); font-weight:200; font-size:.56rem;
  letter-spacing:.22em; text-transform:uppercase; color:var(--blush);
  border:1px solid var(--blush-line); background:rgba(229,183,169,.06);
}
.rsvp-perday{
  margin:0 0 1.4rem; font-family:var(--serif); font-style:italic; font-weight:300;
  font-size:clamp(.95rem,3.6vw,1.02rem); color:var(--ivory-mute); line-height:1.7;
}
.rsvp-days{ display:flex; flex-direction:column; gap:1.5rem; margin-bottom:1.9rem }
/* These sit INSIDE the card, so they have to lift again — at the old value
   they read as holes punched in the lighter paper rather than blocks resting
   on it. */
.rsvp-day{
  display:flex; flex-direction:column; gap:.85rem;
  padding:clamp(1.1rem,4vw,1.4rem);
  border:1px solid var(--gold-line-soft); border-radius:3px;
  background:rgba(255,248,238,.055);
}
.rsvp-day.is-two{ border-color:var(--blush-line) }
.rd-head{ display:flex; flex-direction:column; gap:.15rem }
.rd-tag{
  margin:0; font-family:var(--sans); font-weight:200;
  font-size:.56rem; letter-spacing:.32em; text-indent:.32em; text-transform:uppercase;
  color:var(--gold-mid);
}
.rsvp-day.is-two .rd-tag{ color:var(--blush) }
.rd-when{
  margin:0; font-family:var(--serif); font-weight:400;
  font-size:clamp(1.05rem,4.2vw,1.18rem); color:var(--ivory-strong); letter-spacing:.02em;
}
.rd-when em{ font-style:italic; font-weight:300; color:var(--ivory-mute); font-size:.9em }

.seg-row{ display:grid; grid-template-columns:1fr 1fr; gap:.6rem }
.seg{
  padding:.85rem .6rem; border-radius:999px;
  border:1px solid var(--gold-line); background:rgba(12,11,10,.30);
  font-family:var(--sans); font-weight:300; font-size:.6rem;
  letter-spacing:.22em; text-indent:.22em; text-transform:uppercase;
  color:var(--ivory-mute);
  transition:border-color .45s var(--ease), color .45s var(--ease), background .45s var(--ease);
}
.seg:hover{ border-color:var(--gold); color:var(--ivory) }
.seg.on{
  border-color:transparent; color:#1A1614;
  background:linear-gradient(100deg,#B98F3C 0%,#D6B166 30%,#F4E2B4 50%,#D6B166 70%,#B98F3C 100%);
}
.seg.on.seg-no{
  color:var(--ivory-strong);
  background:linear-gradient(100deg,rgba(122,115,106,.5),rgba(78,72,66,.5));
  border-color:var(--gold-line-soft);
}
.seg:focus-visible{ outline:2px solid var(--gold); outline-offset:3px }

.rd-count{ display:none }
.rsvp-day.attending .rd-count{ display:block }

.field{ position:relative }
.field input,.field textarea,.field select{
  width:100%; padding:1.3rem .1rem .55rem; border:0;
  border-bottom:1px solid var(--gold-line);
  background:transparent; color:var(--ivory-strong);
  font-family:var(--serif); font-weight:400; font-size:1.04rem; line-height:1.4;
  border-radius:0; outline:none; resize:none;
  transition:border-color .5s var(--ease);
}
.field select{ appearance:none; -webkit-appearance:none; cursor:pointer }
.field select option{ background:#2A2724; color:var(--ivory-strong) }
.field-select::after{
  content:""; position:absolute; right:.35rem; bottom:1.1rem; width:7px; height:7px;
  border-right:1px solid var(--gold); border-bottom:1px solid var(--gold);
  transform:rotate(45deg); pointer-events:none;
}
.field label{
  position:absolute; left:.1rem; top:1.2rem; pointer-events:none;
  font-family:var(--sans); font-weight:200; font-size:.76rem;
  letter-spacing:.16em; text-transform:uppercase; color:rgba(241,232,220,.5);
  transition:transform .45s var(--ease), color .45s var(--ease);
  transform-origin:left top;
}
.field input:focus~label,.field textarea:focus~label,
.field input:not(:placeholder-shown)~label,.field textarea:not(:placeholder-shown)~label,
.field select~label{
  transform:translateY(-1.15rem) scale(.78); color:var(--gold-mid);
}
.field input:focus,.field textarea:focus,.field select:focus{ border-bottom-color:var(--gold) }
.field.invalid input,.field.invalid textarea,.field.invalid select{ border-bottom-color:#B4544F }

.btn-gold{
  position:relative;
  padding:1.05rem 2.2rem; border-radius:999px;
  font-family:var(--sans); font-weight:400;
  font-size:.66rem; letter-spacing:.3em; text-indent:.3em; text-transform:uppercase;
  color:#1A1614;
  background:linear-gradient(100deg,#B98F3C 0%,#D6B166 30%,#F4E2B4 50%,#D6B166 70%,#B98F3C 100%);
  background-size:200% 100%;
  box-shadow:0 18px 34px -22px rgba(0,0,0,.95), 0 0 0 1px rgba(255,246,220,.35);
  transition:background-position .9s var(--ease), transform .5s var(--ease);
}
.btn-gold:hover{ background-position:100% 50%; transform:translateY(-1px) }
.btn-gold:active{ transform:translateY(0) scale(.985) }
.btn-gold:focus-visible{ outline:2px solid var(--gold-hi); outline-offset:3px }

.btn-quiet{
  padding:.95rem 1.9rem; border-radius:999px;
  font-family:var(--sans); font-weight:300;
  font-size:.64rem; letter-spacing:.28em; text-indent:.28em; text-transform:uppercase;
  color:var(--gold-mid);
  background:transparent; border:1px solid var(--gold-line);
  transition:transform .45s var(--ease), border-color .45s var(--ease), color .45s var(--ease);
}
.btn-quiet:hover{ border-color:var(--gold); color:var(--gold-hi) }
.btn-quiet:focus-visible{ outline:2px solid var(--gold); outline-offset:3px }

.rsvp-card .btn-gold{ margin-top:clamp(1.2rem,4vh,1.8rem); align-self:center }
.form-error{
  margin:.8rem 0 0; text-align:center;
  font-family:var(--sans); font-weight:300; font-size:.66rem;
  letter-spacing:.16em; color:#D08078;
}

.rsvp-done{
  width:100%; margin-top:clamp(1.2rem,4vh,1.8rem);
  display:flex; flex-direction:column; align-items:center; gap:.5rem;
  padding:clamp(2rem,7vw,2.8rem) 1.2rem;
  text-align:center;
}
.rsvp-done .wax-seal{ margin-bottom:.9rem }
.rsvp-done .script{ font-size:clamp(2.1rem,9vw,2.9rem) }
.rsvp-recap{
  list-style:none; margin:1.1rem 0 0; padding:0;
  display:flex; flex-direction:column; gap:.4rem; width:100%; max-width:22rem;
}
.rsvp-recap li{
  display:flex; justify-content:space-between; align-items:baseline; gap:1rem;
  padding:.55rem 0; border-bottom:1px solid var(--gold-line-soft);
  font-family:var(--sans); font-weight:200; font-size:.58rem;
  letter-spacing:.2em; text-transform:uppercase; color:var(--ivory-mute);
}
.rsvp-recap li b{
  font-family:var(--serif); font-weight:400; font-size:1rem;
  letter-spacing:.02em; text-transform:none; color:var(--gold-mid);
}
.rsvp-done .btn-gold{ margin-top:1.4rem }

/* ══════════════════ 9 · GIFTS ══════════════════ */
.bank-card.sealed{ opacity:0; transform:translateY(-8px) }
.bank-card.unsealed{
  opacity:1; transform:none;
  transition:opacity .7s var(--ease), transform .8s var(--ease);
}
.bank-rows{ margin:0; display:flex; flex-direction:column; gap:.1rem }
.bank-row{
  display:flex; align-items:baseline; justify-content:space-between; gap:1rem;
  padding:.7rem 0; border-bottom:1px solid var(--gold-line-soft);
}
.bank-row:last-child{ border-bottom:0 }
/* Lifted with the card. --ivory-faint is a rule colour; on the lighter paper
   it made the labels read as disabled. */
.bank-row dt{
  font-family:var(--sans); font-weight:200;
  font-size:.56rem; letter-spacing:.24em; text-transform:uppercase;
  color:rgba(241,232,220,.5);
}
.bank-row dd{
  margin:0; font-family:var(--serif); font-weight:400;
  font-size:clamp(1rem,4vw,1.1rem); color:var(--ivory-strong); text-align:right;
  letter-spacing:.03em;
}
.bank-foot{ margin-top:1.1rem; text-align:center; text-indent:0; letter-spacing:.16em }

/* ══════════════════ 10 · FINALE ══════════════════
   The thread arrives here and resolves into the seal: the ring draws itself
   closed around the monogram as the last of the line lands. */
.finale-scene{
  position:relative; width:100%; min-height:94svh;
  display:flex; align-items:center; justify-content:center; overflow:hidden;
  background:
    radial-gradient(86% 56% at 50% 30%, rgba(60,55,50,.5) 0%, rgba(60,55,50,0) 62%),
    radial-gradient(120% 78% at 50% 104%, rgba(6,5,5,.95) 0%, rgba(6,5,5,0) 58%),
    linear-gradient(180deg,#1C1A18 0%,#161413 50%,#0D0C0B 100%);
}
.finale-type{
  position:relative; z-index:2; width:100%; max-width:var(--maxw);
  display:flex; flex-direction:column; align-items:center; gap:.5rem;
  text-align:center; padding:clamp(3rem,10vh,5rem) 1.4rem;
}
.finale-seal{
  width:clamp(138px,36vw,196px); height:clamp(138px,36vw,196px);
  margin-bottom:clamp(1.6rem,5.5vh,2.6rem);
  opacity:0; transform:translateY(18px) scale(.9);
  transition:opacity 1.3s var(--ease), transform 1.5s var(--ease);
}
.finale-seal.in{ opacity:1; transform:none }
/* The seal lifts very slightly as the circle closes around it. */
.finale-seal{ transition:opacity 1.3s var(--ease), transform 1.5s var(--ease) }
.finale-seal.resolved{ transform:scale(1.03) }
/* Symmetrical, so the shadow does not fall across the lower arc of the ring
   and make a concentric circle read as sitting high. */
.finale-seal{
  box-shadow:
    inset 0 2px 4px rgba(255,242,208,.42),
    inset 0 -3px 7px rgba(38,24,6,.55),
    0 0 42px -6px rgba(0,0,0,.9);
}
/* `inset` alone. Setting inset AND width/height overconstrains the box, and
   the ring lands off-centre from the seal it is supposed to close around. */
/* --ring-inset is the SINGLE source of truth for how far the circle stands off
   the seal. main.js reads this property to decide where the thread stops, so
   the line always lands exactly on the circle no matter what this is set to.
   Change it here and nowhere else. */
.finale-seal{ --ring-inset:.22 }
.seal-ring{
  position:absolute; inset:calc(var(--ring-inset, .13) * -100%);
  overflow:visible; pointer-events:none;
  /* the same glow the thread carries, so the circle reads as the same line */
  filter:drop-shadow(0 0 4px rgba(201,162,75,.35));
}
/* The transition lives ONLY on the resolved rule. Declared on the base rule it
   also animates the way IN to the hidden state, and in a tab that is not
   compositing that transition freezes part-drawn — leaving a stray arc beside
   the seal that looks like a rendering fault. */
/* The circle the thread closes.

   `butt` caps, not `round`: a round cap adds half a stroke width past each
   end, so the two ends overlap into a visible lump exactly where the line is
   supposed to meet itself.

   NO dash values here, and no transition. `pathLength="1"` is not honoured
   consistently, which left stroke-dasharray at a literal 1px against a
   295-unit circumference and painted a broken crescent instead of a circle.
   main.js measures getTotalLength() and sets both dasharray and dashoffset in
   real units — and drives the offset from SCROLL POSITION, exactly like the
   rest of the thread, because this arc IS the last stretch of that line. A
   CSS transition here also meant a guest could catch the circle three-quarters
   drawn and read it as an off-centre crescent. */
.sr-arc{
  fill:none; stroke:rgba(201,162,75,.72); stroke-width:1.4; stroke-linecap:butt;
  vector-effect:non-scaling-stroke;
}
.fin-sub{
  margin:0; font-family:var(--serif); font-style:italic; font-weight:300;
  font-size:clamp(1.05rem,4.2vw,1.2rem); color:var(--ivory-soft); letter-spacing:.04em;
}
.hashtag{
  margin:clamp(.9rem,3vh,1.4rem) 0 0;
  padding:.55rem 1.3rem; border-radius:999px;
  font-family:var(--sans); font-weight:200;
  font-size:clamp(.6rem,2.5vw,.68rem);
  letter-spacing:.24em; text-indent:.24em;
  color:var(--gold-mid);
  border:1px solid var(--gold-line);
}

/* ══════════════════ scroll progress ══════════════════ */
.progress-rail{
  position:fixed; left:0; right:0; bottom:0; height:2px; z-index:60;
  background:rgba(241,232,220,.07); pointer-events:none;
}
.progress-rail i{
  display:block; height:100%; width:0;
  background:linear-gradient(90deg,#B98F3C,#D6B166 40%,#F4E2B4 70%,#B98F3C);
  box-shadow:0 0 10px rgba(201,162,75,.6);
}

/* ══════════════════ reduced motion ══════════════════ */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important; animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
  }
  .reveal,.hero-type [data-r],.chapter,.pr-step,.fact-card,.venue-card,
  .wear-day,.guide-card,.gal-item,.finale-seal{
    opacity:1 !important; transform:none !important; filter:none !important;
  }
  /* the thread is drawn whole rather than animated */
  .thread path{ stroke-dashoffset:0 !important }
}
