/* =====================================================================
   Jerald and Divya · Save the date
   ONE reveal primitive. ONE easing. ONE stagger unit. No blur, no drift,
   no ambient loops. Ink on paper; the photograph appears only at the water.
   ===================================================================== */
@import url("/fonts/fonts.css?v=6");

:root{
  /* Every value below was sampled from the printed card, not chosen. */
  --paper:#EDE8E4; --plate:#F3EFEB; --ink:#1A1817; --ink-60:#48463F;
  /* was #6E6F66 — 4.20:1, under AA for the small labels it carries */
  --ink-40:#5F6058;
  --rule:#D6D2C9;
  /* was #8C8A80 — 2.86:1, under the 3:1 a form boundary needs */
  --field:#6A6860;
  --sage:#6E7A50; --sage-deep:#4F5B3C;
  /* the olive the couple's names are printed in, and the rule/ornament gold */
  --olive:#5B5C49; --gold:#AA9D87;
  /* The emphasis colour is the card's own green. The mauve it replaced was
     2.70:1 on paper — the least readable word on the page was "good" — and
     it belonged to no other part of this wedding. */
  --accent:#5B5C49;
  --night:#14160F; --night-ink:#F2EFE8; --night-60:rgba(242,239,232,.72);

  --serif:'Cormorant Garamond',Georgia,'Times New Roman',serif;
  /* The printed card is set entirely in one serif. A geometric sans beside
     it read as a different document, so the sans is gone: labels and dates
     are the same face, letterspaced, exactly as they are on the card. */
  --sans:'Cormorant Garamond',Georgia,'Times New Roman',serif;
  --script:'Pinyon Script','Snell Roundhand','Apple Chancery',cursive;

  --t-names:clamp(58px,17vw,124px);
  --t-head:clamp(40px,10vw,66px);
  --t-lede:clamp(21px,5.4vw,28px);
  --t-nano:13px;

  /* ONE motion contract. Everything obeys these three values. */
  --dur:640ms;
  --ease:cubic-bezier(.22,.61,.36,1);
  /* a touch of overshoot — what makes a press feel physical rather than flat */
  --spring:cubic-bezier(.34,1.56,.64,1);
  --stagger:96ms;
  --word:38ms;

  --gutter:24px;
  --measure:34rem;
  /* Vertical rhythm is a SCALE, not one value. Each section gets the room
     its job needs: the two full-height moments breathe, the practical
     middle is denser, the ask is isolated. */
  /* Sections used to carry the same generous padding top AND bottom, so at
     every seam two of them stacked: 252px of blank paper between the hero and
     the invitation, 308px around the RSVP. The space now sits mostly on top,
     where it reads as a section beginning, and the bottom carries a little
     under half, so nothing doubles. */
  --pad-xl:clamp(78px,12vh,132px);
  --pad-lg:clamp(62px,9.5vh,104px);
  --pad-md:clamp(50px,7.5vh,86px);
  --pad-tail:.44;
}
@media(min-width:768px){:root{--gutter:6vw}}
@media(min-width:1024px){:root{--gutter:56px}}

*{margin:0;padding:0;box-sizing:border-box}
html{-webkit-text-size-adjust:100%;background:var(--paper);scroll-behavior:smooth;
  font-optical-sizing:auto;overflow-x:clip}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
body{font-family:var(--sans);color:var(--ink);background:var(--paper);line-height:1.55;
  overflow-x:clip;-webkit-font-smoothing:antialiased;
  /* The card is set with old-style text figures: 2027 sits on the baseline
     with the 2 and 7 descending, the way a book sets numbers. Lining figures
     were the giveaway that this was a web page and that a letterpress card. */
  font-variant-numeric:oldstyle-nums proportional-nums;
  font-kerning:normal;
  font-variant-ligatures:common-ligatures contextual;
  -webkit-tap-highlight-color:transparent;
  text-rendering:optimizeLegibility}

/* Numbers that must line up in a column stay lining and tabular: a countdown
   whose digits change width jitters, and a calendar grid must align. */
.countdown,.cal-days,.tl-time,.receipt dd,.prog,.stamp{
  font-variant-numeric:lining-nums tabular-nums}
body.locked{position:fixed;top:0;left:0;right:0;bottom:0;width:100%;
  overflow:hidden;overscroll-behavior:none}
::selection{background:var(--olive);color:var(--plate)}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
:focus-visible{outline:2px solid var(--sage);outline-offset:3px;border-radius:2px}
img{max-width:100%;display:block}
/* The travelling layers are deliberately wider than their frames. The blanket
   max-width above was capping them back to 100%, so an image shifted left by
   8% left 8% of bare paper showing on the right. That was the white edge. */
.band-mask img,.frame img{max-width:none}
/* The names are drawn as script glyphs revealed by a clip path; screen
   readers get the plain text instead. */
.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* ---------- the ground ---------- */
.ground{position:fixed;inset:0;z-index:0;pointer-events:none;background:var(--paper)}
/* Light travelling down the sheet as you scroll. This is most of why the
   page reads as alive rather than as a static document. */
.ground::after{content:"";position:absolute;inset:0;
  background:radial-gradient(72% 46% at 50% var(--lightY,18%),
    rgba(255,253,247,.85) 0%,rgba(255,253,247,.32) 42%,transparent 74%)}
/* Not synthetic noise any more — this is the card's own cotton fibre,
   high-passed so it carries texture and no tone, laid over the paper with
   soft-light. The screen and the printed piece are now the same stock. */
/* Two layers of real paper: the printer's own debossed sheet, and over it a
   tile of the card's cotton fibre. Neither is invented. */
/* Slightly larger than the screen so it can move without ever showing an
   edge. It is the deepest layer, so it moves most. */
/* The cathedral, knocked back to just over half and multiplied into the
   paper. It is meant to be felt rather than seen - the flat sheet that
   replaced it read as a texture instead of a place. */
.sheet{position:absolute;inset:-3%;
  background:url('/images/paper-cathedral.webp?v=1') no-repeat center top/cover;
  opacity:.55;mix-blend-mode:multiply;will-change:translate}
.grain{position:absolute;inset:0;opacity:.28;mix-blend-mode:soft-light;
  background-image:url('/images/paper.webp?v=2');background-size:260px 260px}
main{position:relative;z-index:1}

.plate-mark{position:fixed;inset:clamp(9px,2.4vw,16px);z-index:2;pointer-events:none;
  border:1px solid rgba(120,110,95,.13)}
.plate-mark i{position:absolute;width:4px;height:4px;background:var(--gold);opacity:.5;
  transform:rotate(45deg)}
.plate-mark i:nth-child(1){top:-2px;left:-2px}
.plate-mark i:nth-child(2){top:-2px;right:-2px}
.plate-mark i:nth-child(3){bottom:-2px;left:-2px}
.plate-mark i:nth-child(4){bottom:-2px;right:-2px}
/* over the water it becomes the light edge instead of the dark one */
.plate-mark.night{border-color:rgba(242,239,232,.16)}
.plate-mark.night i{background:rgba(242,239,232,.5)}

/* =====================================================================
   THE ONE REVEAL PRIMITIVE
   .r  = rise + fade. .ln>span = mask wipe. .w = script write-on.
   Stagger comes from --i (an integer), never a hand-typed millisecond.
   ===================================================================== */
.r{opacity:0;transform:translateY(16px);
  transition:opacity calc(var(--dur) * 1.35) var(--ease),
             transform calc(var(--dur) * 1.35) var(--ease);
  transition-delay:calc(var(--i,0) * var(--stagger))}
[data-in] .r,[data-in].r{opacity:1;transform:none}

.ln{display:block;overflow:hidden;padding:.16em .05em .12em;margin:-.16em 0 -.12em}
.ln>span{display:block;transform:translateY(105%);
  transition:transform var(--dur) var(--ease);
  transition-delay:calc(var(--i,0) * var(--stagger))}
[data-in] .ln>span{transform:none}
.split .ln>span{transform:none!important;transition:none!important}

.w{clip-path:inset(0 100% -.3em 0);
  transition:clip-path calc(var(--dur) * 2.2) var(--ease);
  transition-delay:calc(var(--i,0) * var(--stagger))}
[data-in] .w{clip-path:inset(0 -.1em -.3em 0)}

/* ---- word-level entrance ----
   Each word rides up out of its own mask on its own clock. This is the whole
   difference between "text appeared" and "text arrived". */
.wd{display:inline-block;overflow:hidden;vertical-align:bottom;
  padding:.1em .04em .06em;margin:-.1em -.02em -.06em}
.wd>span{display:inline-block;transform:translateY(110%);opacity:0;
  transition:transform 900ms var(--ease),opacity 620ms var(--ease);
  transition-delay:calc(var(--w,0) * var(--word))}
[data-in] .wd>span{transform:none;opacity:1}

/* ---- the pen ----
   Copperplate earns its beauty from thick/thin contrast, which cannot be
   drawn as a single stroke — that is exactly why the monoline hand read as
   childish. So the letterforms stay intact and the INK arrives instead,
   wiped in along an edge slanted to match the script's own lean, with a nib
   riding the leading edge. The scroll drives it. */
.pen{position:relative;display:block;width:fit-content;max-width:100%;
  font-family:var(--script);line-height:1.04;color:var(--olive)}
.pen>i{display:block;font-style:normal;
  padding:.34em .32em .46em .28em;
  margin:-.34em -.32em -.46em -.28em;
  -webkit-mask-image:linear-gradient(112deg,#000 -19%,rgba(0,0,0,.72) -15.6%,rgba(0,0,0,.22) -12.6%,rgba(0,0,0,0) -10%);
  mask-image:linear-gradient(112deg,#000 -19%,rgba(0,0,0,.72) -15.6%,rgba(0,0,0,.22) -12.6%,rgba(0,0,0,0) -10%)}
.pen::after{content:"";position:absolute;top:66%;left:var(--nib,0%);
  width:.038em;height:.038em;border-radius:50%;background:currentColor;
  transform:translate(-50%,-50%);opacity:0;pointer-events:none}
.pen.writing::after{opacity:.55}
.no-pen .pen>i{-webkit-mask-image:none!important;mask-image:none!important}
.no-pen .pen::after{display:none}

/* ---- (retired) handwriting canvas ----
   .ink is the live canvas Vara draws into. .ink-fb is the printed script
   face, shown ONLY if Vara is unavailable, so the names are never missing. */
.names{display:flex;flex-direction:column;align-items:center}
.names .pen{font-size:var(--t-names);line-height:1.10;margin-inline:auto}
.close-names .pen{font-size:clamp(34px,9vw,56px);line-height:1.2;margin-inline:auto;
  /* olive is the ink colour on paper; over the water it has to be the paper */
  color:var(--night-ink)}

/* ---- continuous drift ----
   Owned outright by the scroll engine. NO transition may live here, or the
   two systems tear at each other every frame. Reveals live on descendants,
   which is why they compose cleanly instead of fighting. */
.drift{will-change:transform}

/* ---- parallax layer ----
   NOTHING here carries a CSS transition on transform. The scroll engine owns
   these transforms outright; a transition would fight it every frame. That
   collision is exactly what made the old build feel skewed. */
.px{will-change:transform}

/* ---- scroll progress ---- */
.progress{position:fixed;top:0;left:0;right:0;height:1px;z-index:70;
  background:rgba(30,31,26,.08);pointer-events:none}
.progress span{display:block;height:100%;width:0;background:var(--sage-deep);
  transform-origin:left;opacity:.75}

/* =====================================================================
   TYPE
   ===================================================================== */
.kicker,.nano{font-family:var(--sans);font-weight:600;font-size:13px;
  text-transform:uppercase;letter-spacing:.3em;margin-right:-.3em;color:var(--ink-40)}
.head{font-family:var(--serif);font-weight:500;font-size:var(--t-head);line-height:1.04;
  letter-spacing:-.015em;text-wrap:balance}
.head em{font-style:italic;color:var(--accent)}
.lede{font-family:var(--serif);font-weight:400;font-size:var(--t-lede);line-height:1.5;
  max-width:var(--measure);color:var(--ink-60);
  /* a lone word on the last line is the difference between set and typed */
  text-wrap:pretty}
.script{font-family:var(--script);font-weight:400;line-height:1.02;color:var(--olive)}
/* A hairline of light on the lower edge and a whisper of shadow on the
   upper: the way ink sits in a debossed impression. Almost invisible, and
   the reason the type reads as pressed rather than printed on top. */
.pen>i,.sign{text-shadow:0 1px 0 rgba(255,253,248,.55), 0 -1px 0 rgba(60,55,40,.05)}
.close-names .pen>i{text-shadow:0 1px 2px rgba(20,22,15,.45)}
.underlink{position:relative;display:inline-block;font-family:var(--sans);font-weight:600;
  font-size:12px;letter-spacing:.14em;text-transform:uppercase;
  padding-bottom:4px;min-height:44px;line-height:34px}
.underlink::before{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;
  background:currentColor;opacity:.34}
.underlink::after{content:"";position:absolute;left:0;bottom:0;height:1px;width:100%;
  background:currentColor;transform:scaleX(0);transform-origin:left;
  transition:transform .5s var(--ease)}
.underlink:hover::after,.underlink:focus-visible::after{transform:scaleX(1)}
.underlink[hidden]{display:none}
.rule{position:relative;width:146px;height:1px;background:none;
  background-image:linear-gradient(90deg,var(--gold) 0 40%,transparent 40% 60%,var(--gold) 60% 100%)}
.rule::after{content:"";position:absolute;left:50%;top:50%;width:5px;height:5px;
  background:var(--gold);transform:translate(-50%,-50%) rotate(45deg)}

/* =====================================================================
   SECTIONS — one rhythm, one measure
   ===================================================================== */
.sec{position:relative;z-index:1;padding-inline:var(--gutter);
  padding-block:var(--pad-lg) calc(var(--pad-lg) * var(--pad-tail));
  max-width:var(--wide,68rem);margin-inline:auto}
.sec.day{position:relative;isolation:isolate;overflow:hidden;
  padding-block:var(--pad-md) calc(var(--pad-md) * var(--pad-tail))}
.day-art{position:absolute;left:0;right:0;top:8%;height:64%;z-index:-1;pointer-events:none;
  background:url('/images/art-combined.webp?v=1') no-repeat center/cover;
  opacity:0;transition:opacity 2400ms var(--ease) 400ms;
  -webkit-mask-image:linear-gradient(180deg,transparent,#000 22%,#000 72%,transparent 100%);
  mask-image:linear-gradient(180deg,transparent,#000 22%,#000 72%,transparent 100%)}
[data-in] .day-art{opacity:.42}
.day-art,.rsvp-art{will-change:translate}

/* ---- the celebration, 5 March ----------------------------------------
   The plate above is drawn from the wedding's own two venues, and the wide
   drawing on the timeline is its reception pavilion. Neither is where this
   evening happens, so the celebration page does not wear them. It keeps the
   same slot and the same weight and puts the card's botanical mark there
   instead, which makes the two pages read as a pair rather than a copy. */
/* The ground is not neutral stock: St Matthew-in-the-City is drawn into the
   sheet itself, so the wedding's church was showing faintly through every
   section of an evening held at Karaka. This page gets the same cotton fibre
   with no building in it, which is also what makes the two pages read apart
   at a glance rather than only in their words.
   Not paper.webp either: that is the grain overlay, drawn to be tiled small
   at soft-light, and as a multiply base it tiles visibly and reads as dirt.
   So this page takes no sheet image at all, only the light falling across the
   top of it. The grain layer above still carries the cotton fibre. */
body.ev-celebration .sheet{
  background:radial-gradient(128% 82% at 50% 4%,
    rgba(255,255,255,.62) 0%, rgba(255,255,255,.22) 42%, rgba(255,255,255,0) 72%);
  opacity:1;mix-blend-mode:normal}
/* The architectural plate is gone rather than swapped. A botanical mark in
   the same slot landed across the address and the maps link on a phone, and a
   graphic that collides with an address is worse than no graphic at all. */
body.ev-celebration .day-art{display:none}
/* the small drawn pieces carry the least, so they read as nearer the eye */
[data-tilt]{will-change:translate}
.sec.rsvp{padding-block:var(--pad-xl) calc(var(--pad-xl) * var(--pad-tail))}

/* Every section opens with the same gesture: the label, then a hairline
   drawn out across the page. It tells you a new movement has begun. */
.sec>.kicker{display:flex;align-items:center;gap:14px;
  margin-bottom:clamp(24px,4.2vh,40px)}
.hero>.kicker{justify-content:center}
.hero>.kicker::before{content:"";width:5px;height:5px;flex:none;background:var(--gold);
  transform:rotate(45deg) scale(0);transition:transform 900ms var(--spring) 260ms}
[data-in]>.kicker::before{transform:rotate(45deg) scale(1)}
.sec>.kicker::after{content:"";width:5px;height:5px;flex:none;background:var(--gold);
  transform:rotate(45deg) scale(0);transform-origin:center;
  transition:transform 900ms var(--spring) 260ms}
[data-in]>.kicker::after{transform:rotate(45deg) scale(1)}
.close>.kicker::after{background:rgba(242,239,232,.55)}

/* ---- the drawn pieces ---- */
.ornament{width:min(78%,340px);height:auto;margin-top:0}
[data-in] .ornament{opacity:.9}
.tl-art{width:clamp(96px,26vw,132px);height:auto;margin-bottom:14px;opacity:.92}
.tl-art.wide{width:clamp(140px,38vw,196px)}
.rsvp-art{position:absolute;right:-14%;bottom:0;width:min(96vw,520px);
  aspect-ratio:900/1599;z-index:-1;pointer-events:none;
  background:url('/images/art-reception-tall.webp?v=1') no-repeat bottom right/contain;
  opacity:0;transition:opacity 2200ms var(--ease) 500ms;
  -webkit-mask-image:linear-gradient(to top,#000 40%,transparent 96%);
  mask-image:linear-gradient(to top,#000 40%,transparent 96%)}
[data-in] .rsvp-art{opacity:.3}
.sec.rsvp{position:relative;isolation:isolate;overflow:hidden}

/* the couple's own mark, the same artwork that opens the printed card */
.mono{width:clamp(76px,19vw,104px);height:auto;margin-bottom:clamp(22px,3.6vh,34px)}

/* ---- 01 hero ---- */
.hero{position:relative;min-height:100svh;display:flex;flex-direction:column;
  align-items:center;text-align:center;isolation:isolate;
  /* The eye reads the centre of a field slightly ABOVE its true middle, so
     the block is seated at 46%, not 50%. Measured before: it sat 44px below
     the optical centre and read as bottom-heavy. */
  justify-content:center;
  padding-top:clamp(44px,6.5vh,76px);
  padding-bottom:clamp(140px,19vh,210px)}
.names{font-weight:400;font-size:var(--t-names);line-height:.92;letter-spacing:0}
.names .script{display:block;width:fit-content;max-width:100%}
.names .amp{margin:.14em 0 .06em}
.amp{display:block;font-family:var(--serif);font-style:italic;font-size:.3em;
  color:var(--ink-40);margin:.06em 0 .02em .08em}
.hero .ornament{margin-top:clamp(26px,4.4vh,40px);margin-inline:auto}
.stamp{margin-top:20px;font-family:var(--sans);font-size:13px;letter-spacing:.1em;
  color:var(--ink-60);display:flex;flex-direction:column;gap:6px;align-items:center}
.stamp>span:first-child{text-transform:uppercase}
.stamp .dot{display:none}
@media(min-width:560px){
  .stamp{flex-direction:row;align-items:baseline;gap:10px}
  .stamp .dot{display:inline;color:var(--ink-40)}
}
.scrollcue{position:absolute;left:0;right:0;margin-inline:auto;width:fit-content;
  bottom:clamp(22px,3.6vh,38px);
  display:flex;flex-direction:column;align-items:center;gap:9px;
  min-height:44px;padding:6px 10px;
  transition:opacity 700ms var(--ease),transform 700ms var(--ease)}
/* It has one job: tell a guest who has just scanned a QR code that there is
   more below the fold. The moment they scroll they know, so it leaves and
   does not come back. A permanent arrow on a finished page is clutter. */
.scrollcue.gone{opacity:0;transform:translateY(12px);pointer-events:none}
.scrollcue.gone .scrollcue-line::after{animation:none}
.scrollcue .cue-word{font-family:var(--sans);font-weight:600;font-size:9.5px;
  text-transform:uppercase;letter-spacing:.36em;margin-right:-.36em;color:var(--ink-40)}
/* a hairline well with a diamond falling down it, over and over */
.scrollcue-line{position:relative;display:block;width:1px;height:34px;
  background:linear-gradient(180deg,rgba(120,110,95,.32),rgba(120,110,95,.05))}
.scrollcue-line::after{content:"";position:absolute;left:50%;top:0;width:5px;height:5px;
  background:var(--gold);transform:translate(-50%,0) rotate(45deg);
  animation:cueFall 2.9s cubic-bezier(.5,0,.5,1) infinite}
@keyframes cueFall{
  0%{transform:translate(-50%,-2px) rotate(45deg);opacity:0}
  22%{opacity:.95}
  78%{opacity:.95}
  100%{transform:translate(-50%,30px) rotate(45deg);opacity:0}}
@media(prefers-reduced-motion:reduce){.scrollcue-line::after{animation:none;opacity:.8}}

/* ---- 02 the invitation ---- */
.verse{margin-top:clamp(24px,4vh,34px);border-left:1px solid var(--rule);
  padding-left:clamp(16px,3vw,24px);max-width:32ch}
.close-verse p{text-indent:0}
.verse p{font-family:var(--serif);font-style:italic;font-size:clamp(17px,4.2vw,20px);
  line-height:1.55;color:var(--ink-60);text-wrap:pretty;
  /* the opening quote hangs into the margin so the text edge stays straight */
  text-indent:-.42em;
  hanging-punctuation:first last}
.verse cite{display:block;margin-top:10px;font-family:var(--sans);font-style:normal;
  font-size:var(--t-nano);text-transform:uppercase;letter-spacing:.2em;color:var(--sage-deep)}
.invite-body{margin-top:clamp(38px,6vh,60px);display:grid;gap:clamp(32px,6vw,60px);
  grid-template-columns:1fr}
@media(min-width:820px){.invite-body{grid-template-columns:1fr auto;align-items:start}}
.prose>.lede+.lede{margin-top:1.1em}
.sign{margin-top:clamp(26px,4vh,38px);font-family:var(--script);
  font-size:clamp(28px,6.4vw,42px);color:var(--olive);
  white-space:pre-line;line-height:1.24;text-wrap:balance}
.portrait{width:min(290px,74vw);display:flex;flex-direction:column;gap:12px}
/* stacked under the names on a phone, so it wants centring; on the wide
   layout it sits in its own column and must stay put */
@media(max-width:819px){.portrait{margin-inline:auto}}
.frame{position:relative;aspect-ratio:4/5;background:var(--plate);border:1px solid var(--rule);
  box-shadow:0 1px 2px rgba(60,55,40,.06),
             0 22px 56px -34px rgba(30,31,26,.5);overflow:hidden}
/* the faint inner line of a print sitting inside its mount */
.frame::after{content:"";position:absolute;inset:0;pointer-events:none;z-index:2;
  box-shadow:inset 0 0 0 1px rgba(60,55,40,.10)}
.frame img{position:absolute;left:-7%;top:-8%;width:114%;height:116%;object-fit:cover;
  opacity:0;transition:opacity 900ms var(--ease)}
.frame img.ready{opacity:1}
.frame img[hidden]{display:none}
.plate{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:14px;color:var(--sage-deep)}
.plate[hidden]{display:none}
.plate img{width:96px;height:auto;opacity:.92}
.plate-note{font-family:var(--sans);font-size:var(--t-nano);text-transform:uppercase;
  letter-spacing:.16em;margin-right:-.16em;color:var(--ink-40)}
.portrait figcaption{letter-spacing:.16em;margin-right:-.16em}

/* the card's own opening line, and the full names beneath it */
.families{margin-top:clamp(28px,4.5vh,40px);font-family:var(--sans);font-size:12px;
  text-transform:uppercase;letter-spacing:.2em;line-height:1.9;color:var(--ink-40);max-width:28ch}
.sign{margin-top:14px}

/* ---- 03 the day ---- */
.day-cols{margin-top:clamp(36px,6vh,58px);display:grid;gap:clamp(40px,7vw,72px);
  grid-template-columns:1fr}
@media(min-width:820px){.day-cols{grid-template-columns:1fr minmax(280px,340px)}}
.timeline{list-style:none}
.tl{display:grid;grid-template-columns:auto 16px 1fr;column-gap:clamp(14px,3vw,22px);align-items:start}
.tl-time{font-family:var(--serif);font-size:clamp(17px,4.4vw,21px);white-space:nowrap;
  padding-top:2px;font-variant-numeric:tabular-nums}
.tl-rail{position:relative;display:flex;flex-direction:column;align-items:center;align-self:stretch}
.tl-dot{width:8px;height:8px;border-radius:50%;background:var(--sage);margin-top:9px;flex:none;
  box-shadow:0 0 0 4px rgba(110,122,80,.14)}
.tl-line{width:1px;flex:1;min-height:24px;background:var(--rule);margin-top:6px}
.tl:last-child .tl-line{display:none}
.tl-body{padding-bottom:clamp(30px,5vh,46px)}
.tl:last-child .tl-body{padding-bottom:0}
.tl-kicker{color:var(--sage-deep);letter-spacing:.16em;margin-right:-.16em}
.tl-place{font-family:var(--serif);font-size:clamp(25px,6vw,36px);line-height:1.06;
  letter-spacing:-.012em;margin-top:8px;text-wrap:balance;hyphens:none}

/* On a phone the time sits ABOVE the venue rather than beside it, so a long
   name like "St Matthew-in-the-City" gets the full column width. */
@media(max-width:640px){
  .tl{grid-template-columns:14px 1fr;column-gap:16px}
  .tl-rail{grid-column:1;grid-row:1 / span 2}
  .tl-time{grid-column:2;grid-row:1;padding-top:0;font-size:19px;color:var(--ink-60)}
  .tl-body{grid-column:2;grid-row:2;padding-top:6px}
  .tl-dot{margin-top:6px}
}
.tl-address{margin-top:8px;font-family:var(--sans);font-size:13px;letter-spacing:.02em;
  line-height:1.6;color:var(--ink-40);max-width:28ch}
.tl-note[hidden]{display:none}
.tl-note{margin-top:10px;max-width:26ch;font-family:var(--serif);
  font-size:clamp(16px,4vw,18px);line-height:1.5;color:var(--ink-60)}
/* The month, the dates, then the counting down to them: one object, not
   two that happen to sit near each other. A hairline joins them rather
   than a gap separating them. */
.day-aside{display:flex;flex-direction:column;gap:0}
.cd-block{margin-top:clamp(20px,3vh,26px);padding-top:clamp(20px,3vh,26px);
  border-top:1px solid var(--rule)}
.cal-week,.cal-days{display:grid;grid-template-columns:repeat(7,1fr)}
.cal-week span{font-family:var(--sans);font-weight:500;font-size:var(--t-nano);
  text-transform:uppercase;letter-spacing:.1em;color:var(--ink-40);text-align:center;padding-bottom:10px}
.cal-days span{position:relative;height:clamp(32px,8vw,40px);display:flex;align-items:center;
  justify-content:center;font-family:var(--serif);font-size:clamp(14px,3.6vw,17px);
  color:var(--ink-40);font-variant-numeric:tabular-nums}
.cal-days span.nul{visibility:hidden}
.cal-days span.day{color:var(--ink)}
.cal-days .circle{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:230%;height:210%;overflow:visible;color:var(--sage);pointer-events:none}
.circle path{stroke-dasharray:100;stroke-dashoffset:100}
.no-pen .circle path{stroke-dashoffset:0}
.cd-block .nano{margin-bottom:14px}
.countdown{display:flex;align-items:baseline;gap:clamp(18px,5vw,28px);margin-bottom:22px}
.cd{display:flex;flex-direction:column;gap:4px}
.cd b{font-family:var(--serif);font-weight:400;font-size:clamp(30px,7.5vw,42px);line-height:1;
  font-variant-numeric:tabular-nums}
.cd .nano{color:var(--ink-40);margin:0}
.acts{display:flex;flex-wrap:wrap;align-items:center;gap:8px 26px}
.underlink.subtle{color:var(--ink-40);border-bottom-color:var(--ink-40);font-size:11px}
.cal-month{margin:0 0 10px;font-family:var(--sans);font-size:11px;font-weight:600;
  letter-spacing:.2em;text-transform:uppercase;color:var(--ink-40);text-align:center}

.both{margin-top:clamp(34px,5vh,48px);font-family:var(--sans);font-size:13px;
  letter-spacing:.06em;color:var(--ink-40)}

/* "Dear Mary," - where a letter puts it */
.salutation{margin:clamp(20px,3vh,28px) 0 0;font-family:var(--script);
  font-size:clamp(26px,6vw,34px);line-height:1.2;color:var(--olive)}

/* the line that meets a returning guest by name */
.backagain{max-width:var(--measure);margin:14px 0 0;
  font-family:var(--serif);font-style:italic;font-size:17px;line-height:1.55;
  color:var(--olive)}

/* the calendar action on the thank-you screen, where a guest is most likely
   to want it - they have just said yes */
.thanks-acts{margin-top:clamp(22px,3.5vh,30px);
  display:flex;flex-wrap:wrap;align-items:center;gap:8px 22px;
  padding-top:clamp(18px,2.6vh,24px);border-top:1px solid var(--rule)}

/* who is hosting the celebration evening - the wedding page has no host
   line, so this only ever renders there */
.host{margin-top:clamp(20px,3vh,28px);font-style:italic;letter-spacing:.02em;
  text-transform:none;font-size:13px}

/* ---- 03b on gifts ----
   A quiet half-page between the coast and the reply. It gets the same
   treatment as the other sections so it reads as part of the invitation
   rather than a notice pinned to it. */
.sec.gifts{padding-block:var(--pad-lg)}
.gifts-note{max-width:34rem;margin-top:clamp(24px,3.6vh,34px)}
.gifts-note strong{font-weight:500;color:var(--ink);
  border-bottom:1px solid var(--gold);padding-bottom:1px}

/* ---- 04 rsvp ---- */
/* The form sits on its own card of the same stock: a reply card slipped in
   with the invitation, not a web form dropped on a page. */
.rsvp-col{position:relative;max-width:var(--measure);width:100%;
  padding:clamp(24px,6.5vw,40px) clamp(15px,5vw,36px) clamp(10px,2vw,18px);
  background:linear-gradient(180deg,rgba(251,249,246,.97),rgba(246,243,238,.95));
  border:1px solid rgba(120,110,95,.28);
  box-shadow:0 1px 2px rgba(60,55,40,.06),
             0 34px 64px -40px rgba(60,55,40,.5);
  backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px)}
/* the engraver's inner rule, a hair inside the edge */
.rsvp-col::before{content:"";position:absolute;inset:7px;pointer-events:none;
  border:1px solid rgba(120,110,95,.19)}
/* On a narrow phone the card's gutter and the section's gutter stack up and
   cost the fields about 85px between them - enough to push the tail of a long
   surname out of sight. The card keeps its own inset rule clear at 15px, so
   the section gives its share back below 400px. */
@media(max-width:400px){
  .sec.rsvp{padding-left:14px;padding-right:14px}
}
/* The party, read back on the closing step. It wraps, unlike the input. */
.noscript-reply{display:none;margin-top:8px}
.partyback{margin:14px 0 0;font-family:var(--serif);font-size:17px;line-height:1.45;
  color:var(--ink-60);overflow-wrap:anywhere}

.step-cue{display:flex;align-items:center;gap:14px;margin-bottom:clamp(26px,4.4vh,36px)}
.step-cue .nano{margin:0;flex:none;color:var(--ink-40)}
.prog{position:relative;flex:1;height:1px;background:rgba(120,110,95,.24)}
.prog span{display:block;height:1px;background:var(--olive);width:16.6%;
  transition:width 700ms var(--ease)}
/* a small diamond rides the progress, the way it rides the card's rules */
.prog::after{content:"";position:absolute;top:50%;left:0;width:5px;height:5px;
  background:var(--gold);transform:translate(-50%,-50%) rotate(45deg)}
/* ---- who is coming ----
   A list you add to. "Add someone" sits UNDER the last person, because the
   old + button lived above the names and scrolled off the top by the third
   guest, which read as the form refusing to add anyone else. */
.party{display:flex;flex-direction:column;margin-top:22px}

.you{padding:0 0 16px;display:flex;flex-direction:column;gap:2px}
.you-name{font-family:var(--serif);font-size:18px;color:var(--ink)}
.you-sub{font-size:13px;color:var(--ink-40)}

/* No box. Measured on a 375px phone, the old nested frame left a name field
   only 247px wide - the page, the reply card and the guest card each took
   their own padding before anyone could type. A hairline does the same job
   of separating people and costs nothing. */
.guest{display:grid;gap:2px 18px;grid-template-columns:1fr;
  padding:10px 0 10px;
  border-top:1px solid rgba(120,110,95,.2);
  animation:guestIn .34s var(--ease) both}
@keyframes guestIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
.guest .field{margin-top:0;min-height:50px}
/* The name is the answer; the other two are conveniences. They stay out of
   the way until there is a name to attach them to, which keeps a row for
   someone whose details you do not have down to a single box. Toggled by a
   class, never by re-rendering the step. */
.guest .gsub{min-height:44px;font-size:15px;color:var(--ink-60);
  display:none}
.guest.has-name .gsub{display:block;animation:subIn .3s var(--ease) both}
@keyframes subIn{from{opacity:0;transform:translateY(-3px)}to{opacity:1;transform:none}}
.guest .gsub::placeholder{color:var(--ink-40);opacity:.85}

/* Two up once there is room for it - a name and an email are one thought. */
/* Once there is room: the name takes the full width it deserves, and the two
   optional details share the line below it. */
@media(min-width:560px){
  .guest{grid-template-columns:1fr 1fr;align-items:end}
  .guest > [data-g]{grid-column:1 / -1}
  .guest .gx{grid-column:1 / -1}
}

/* Says what it does. "Remove" beats a bare cross for guests who did not grow
   up with one, and below the fields it can never steal a tap meant for them. */
.gx{justify-self:end;min-height:40px;padding:0 2px;border:0;background:transparent;
  color:var(--ink-40);cursor:pointer;
  font-family:var(--sans);font-size:11px;font-weight:600;letter-spacing:.13em;
  text-transform:uppercase;text-decoration:underline;text-underline-offset:4px;
  text-decoration-color:rgba(120,110,95,.3);
  transition:color .25s var(--ease),text-decoration-color .25s var(--ease)}
.gx:hover{color:var(--ink);text-decoration-color:var(--olive)}
.gx:focus-visible{outline:2px solid var(--sage-deep);outline-offset:2px}

.addguest{width:100%;min-height:54px;margin-top:4px;
  border:0;border-top:1px solid rgba(120,110,95,.2);
  background:transparent;cursor:pointer;text-align:left;
  font-family:var(--sans);font-size:12px;font-weight:600;letter-spacing:.14em;
  text-transform:uppercase;color:var(--olive);
  transition:color .3s var(--ease);-webkit-tap-highlight-color:transparent}
.addguest:hover{color:var(--ink)}
.addguest:focus-visible{outline:2px solid var(--sage-deep);outline-offset:3px}

.partycount{margin-top:14px;font-style:italic}

.step{animation:stepIn 760ms var(--ease) both}
@keyframes stepIn{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
.step .ask{animation:stepIn 820ms var(--ease) 60ms both}
.step .choices,.step .field,.step .nav{animation:stepIn 820ms var(--ease) 140ms both}
.ask{font-family:var(--serif);font-weight:500;font-size:clamp(28px,7vw,42px);line-height:1.1;
  letter-spacing:-.015em;text-wrap:balance}
.help{font-size:13px;color:var(--ink-40);margin-top:10px}
.choices{margin-top:26px;border-top:1px solid var(--rule)}
.choice{display:flex;align-items:center;justify-content:space-between;gap:14px;width:100%;
  min-height:56px;padding:0 2px;border-bottom:1px solid var(--rule);font-family:var(--serif);
  font-size:clamp(20px,5vw,25px);text-align:left;transition:opacity .25s var(--ease)}
.choice{transition:opacity .25s var(--ease),transform .32s var(--spring)}
.choice:hover{opacity:.65}
.choice:active{transform:scale(.985)}
.ev{transition:transform .32s var(--spring)}
.ev:active{transform:scale(.985)}
.actionbar{-webkit-tap-highlight-color:transparent}
.actionbar:active{transform:translateY(0) scale(.99)}
.choice .arw{font-family:var(--sans);font-size:16px;color:var(--ink-40)}
.field{position:relative;width:100%;min-height:54px;padding:12px 2px;
  font-family:var(--serif);font-size:17px;letter-spacing:.01em;
  color:var(--ink);background:transparent;border:0;
  border-bottom:1px solid rgba(120,110,95,.34);
  border-radius:0;outline:none;transition:border-color .35s var(--ease)}
/* ---- country code + number ----
   A native select, so a phone gives the guest its own picker rather than a
   list we have styled badly. It wears the same hairline as every other
   field; only the chevron is ours. */
.telrow{display:flex;gap:12px;align-items:flex-end;margin-top:16px}
.tel-cc-wrap{position:relative;display:block;flex:0 0 clamp(92px,27%,118px);min-width:0}
/* the select keeps its hairline and chevron but hands its text to the face,
   which says "+64 NZ" instead of "+64 New ..." */
.tel-cc-face{position:absolute;left:2px;right:22px;bottom:0;height:54px;line-height:54px;
  font-family:var(--serif);font-size:17px;letter-spacing:.01em;color:var(--ink);
  white-space:nowrap;overflow:hidden;pointer-events:none}
.tel-cc{color:transparent}
.tel-cc option{color:var(--ink)}
.tel-cc{width:100%;min-width:0;
  -webkit-appearance:none;appearance:none;cursor:pointer;
  padding-right:22px;text-overflow:ellipsis;
  background-image:linear-gradient(45deg,transparent 50%,var(--ink-40) 50%),
                   linear-gradient(135deg,var(--ink-40) 50%,transparent 50%);
  background-position:calc(100% - 10px) calc(50% + 1px),calc(100% - 5px) calc(50% + 1px);
  background-size:5px 5px,5px 5px;background-repeat:no-repeat}
.tel-cc:focus{background-image:linear-gradient(45deg,transparent 50%,var(--olive) 50%),
                              linear-gradient(135deg,var(--olive) 50%,transparent 50%)}
.tel-num{flex:1 1 auto;min-width:0}

.field:focus{border-bottom-color:var(--olive);border-bottom-width:1px}
.field::placeholder{color:var(--ink-40)}
.field:focus{border-bottom-color:var(--sage)}
.ev{display:flex;align-items:center;gap:14px;width:100%;min-height:56px;
  border-bottom:1px solid var(--rule);font-family:var(--serif);
  font-size:clamp(18px,4.4vw,23px);text-align:left}
.ev .box{width:9px;height:9px;outline:1px solid rgba(120,110,95,.5);flex:none;
  transform:rotate(45deg);transition:background .3s var(--ease),outline-color .3s var(--ease)}
.ev.on .box{background:var(--olive);outline-color:var(--olive)}
.flabel{display:block;font-family:var(--sans);font-weight:500;font-size:12px;
  text-transform:uppercase;letter-spacing:.12em;color:var(--ink-40);margin:20px 0 4px}
.err{font-size:13px;color:var(--ink);border-bottom:2px solid var(--ink);
  display:inline-block;margin-top:20px;padding-bottom:2px}
/* the box that needs fixing, and its message tucked right under it */
.field[aria-invalid="true"]{border-bottom:2px solid var(--ink)}
.field[aria-invalid="true"] + .err,.telrow + .err{margin-top:8px;border-bottom:0;padding-bottom:0}
.guest .err{grid-column:1 / -1}
.nav{display:flex;align-items:center;gap:22px;margin-top:34px}
.back{font-family:var(--sans);font-weight:500;font-size:12px;text-transform:uppercase;
  letter-spacing:.1em;color:var(--ink-40);min-height:44px}
.back:hover{opacity:.65}
.thanks .lede{margin-top:clamp(24px,4vh,34px)}
/* What we actually received. A thank-you that shows nothing back leaves the
   guest with no way to know what we have — or to spot their own mistake. */
.receipt{margin-top:clamp(24px,4vh,32px);border-top:1px solid var(--rule)}
.receipt div{display:flex;gap:16px;padding:11px 0;border-bottom:1px solid var(--rule)}
.receipt dt{flex:0 0 8.5rem;font-family:var(--sans);font-size:var(--t-nano);
  text-transform:uppercase;letter-spacing:.14em;color:var(--ink-40);padding-top:.25em}
.receipt dd{flex:1;font-family:var(--serif);font-size:17px;line-height:1.45;color:var(--ink)}
.thanks:focus{outline:none}

/* ---- 05 the water (the ONE photograph) ---- */
.close{min-height:100svh;display:flex;align-items:flex-end;isolation:isolate;overflow:hidden;
  color:var(--night-ink);--wide:none;max-width:none;
  padding-block:clamp(80px,13vh,140px) clamp(60px,9vh,100px)}
.close-photo{position:absolute;left:-7%;top:-9%;width:114%;height:118%;z-index:-1;background:
  linear-gradient(180deg,rgba(20,22,15,.45) 0%,rgba(20,22,15,.30) 32%,rgba(20,22,15,.72) 62%,rgba(20,22,15,.95) 100%),
  var(--close-photo,radial-gradient(120% 90% at 50% 15%,#2C3326 0%,#14160F 70%));
  background-size:cover;background-position:50% 50%}
.close-inner{max-width:var(--measure);width:100%;margin-inline:auto;
  display:flex;flex-direction:column;align-items:center;text-align:center}
.close .lede{color:rgba(242,239,232,.95)}
.close .nano{color:var(--night-60)}
.close .script{color:var(--night-ink)}
/* The badge that stood here repeated the date the page had already given
   twice, and sat as a sticker on top of a photograph. The water and the
   words carry the ending on their own. */
.close-script{font-size:clamp(38px,10vw,64px);width:fit-content;max-width:100%}
.close .lede{margin-top:18px;margin-inline:auto;text-align:center}
.close .nano{margin-top:20px}
.close-verse{margin-top:22px;border-left:0;border-top:1px solid rgba(242,239,232,.22);
  padding:20px 0 0;text-align:center;max-width:32ch}
.close-verse p{color:var(--night-60)}
.close-verse cite{color:rgba(242,239,232,.6)}
.quiet-note{text-transform:none;letter-spacing:.02em;font-size:12.5px;line-height:1.7;
  text-align:center;margin-inline:auto;
  color:rgba(242,239,232,.88)!important;
  margin-top:26px;padding-top:20px;border-top:1px solid rgba(242,239,232,.18);max-width:34ch}

/* ---- full-bleed photo bands ----
   The page breathes: paper, photograph, paper, photograph. Each band is
   edge-to-edge on a phone and rises a touch as it enters. */
.band{position:relative;width:100%;background:var(--plate)}
.band-mask{position:relative;display:block;width:100%;overflow:hidden;
  aspect-ratio:var(--band-ratio,4/3);
  clip-path:inset(6% 0 6% 0);opacity:0;
  transition:clip-path 1400ms var(--ease),opacity 900ms var(--ease)}
[data-in] .band-mask{clip-path:inset(0 0 0 0);opacity:1}
/* the image is taller than its mask so it can travel inside it */
.band-mask img{position:absolute;left:-8%;top:-10%;width:116%;height:120%;object-fit:cover;
  opacity:0;transition:opacity 900ms var(--ease)}
.band-mask img.ready{opacity:1}
/* a faint sheen that moves with the phone, the way light moves on a print */
.band-mask .gloss{position:absolute;inset:0;pointer-events:none;opacity:.5;
  background:radial-gradient(60% 44% at var(--gx,50%) var(--gy,32%),
    rgba(255,255,255,.34) 0%,rgba(255,255,255,.10) 38%,transparent 72%);
  mix-blend-mode:soft-light}
.band-mask::after{content:"";position:absolute;inset:0;pointer-events:none;
  opacity:0;transition:opacity 900ms var(--ease);
  background:linear-gradient(180deg,rgba(237,232,228,.5) 0%,transparent 14%,
    transparent 86%,rgba(237,232,228,.5) 100%)}
.band-mask:has(img.ready)::after{opacity:1}
.band[data-band="wide"]{--band-ratio:16/10}
.band[data-band="ring"]{--band-ratio:3/2}
@media(min-width:900px){
  .band{max-width:68rem;margin-inline:auto}
  .band[data-band="ring"]{--band-ratio:2/1}
}

/* =====================================================================
   FIXED CHROME
   ===================================================================== */
.actionbar{position:fixed;left:0;right:0;bottom:0;z-index:50;width:100%;
  display:flex;align-items:center;justify-content:center;gap:10px;
  background:var(--ink);color:var(--paper);font-family:var(--sans);font-weight:500;
  font-size:12px;letter-spacing:.14em;text-transform:uppercase;
  padding:20px 18px calc(20px + env(safe-area-inset-bottom));min-height:56px;
  transform:translateY(100%);transition:transform var(--dur) var(--ease)}
.actionbar::after{content:"↓";font-size:13px;letter-spacing:0;opacity:.7}
/* inside the form the button moves you on to the next step, not down the page */
.nav .actionbar::after{content:"→"}
.actionbar.up{transform:none}
.actionbar{box-shadow:0 -14px 34px -22px rgba(30,31,26,.55)}
.actionbar[hidden]{display:none}
.music{position:fixed;top:16px;right:16px;z-index:60;width:44px;height:44px;
  border-radius:50%;color:var(--ink-60);
  background:radial-gradient(circle at 50% 34%,rgba(250,247,243,.94),rgba(238,233,228,.88));
  border:1px solid rgba(120,110,95,.28);
  box-shadow:0 1px 2px rgba(60,55,40,.08),0 8px 18px -12px rgba(60,55,40,.45);
  backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
  display:flex;align-items:center;justify-content:center;
  transition:transform .4s var(--spring),box-shadow .4s var(--ease)}
.music::after{content:"";position:absolute;inset:3px;border-radius:50%;
  border:1px solid rgba(120,110,95,.18)}
.music:active{transform:scale(.94)}
/* a slow breath while it is playing, so the control shows it is alive */
.music.playing{animation:musicBreath 5.5s ease-in-out infinite}
@keyframes musicBreath{0%,100%{box-shadow:0 1px 2px rgba(60,55,40,.08),0 8px 18px -12px rgba(60,55,40,.45)}
  50%{box-shadow:0 1px 2px rgba(60,55,40,.08),0 10px 22px -12px rgba(91,92,73,.42)}}
@media(prefers-reduced-motion:reduce){.music.playing{animation:none}}
.music[hidden]{display:none}

/* ---- the gate ----
   Nothing but the mark and an invitation to touch it. It sits above the
   video, and dissolves into it. */
.gate{position:fixed;inset:0;z-index:130;display:flex;align-items:center;
  justify-content:center;cursor:pointer;background:var(--paper);
  transition:opacity 900ms var(--ease)}
.gate-inner{display:flex;flex-direction:column;align-items:center;
  gap:clamp(20px,3.6vh,30px);
  transition:transform 900ms var(--ease),opacity 700ms var(--ease)}
.gate-mono{width:clamp(96px,25vw,132px);height:auto}
.gate-rule{display:block;width:74px;height:1px;position:relative;
  background:linear-gradient(90deg,transparent,var(--gold) 22%,var(--gold) 78%,transparent)}
.gate-rule::after{content:"";position:absolute;left:50%;top:50%;width:5px;height:5px;
  background:var(--gold);transform:translate(-50%,-50%) rotate(45deg)}
/* The only instruction on the first screen, read by grandparents in
   daylight. It was 11px in the mid-grey and breathed down to 55% - which left
   it at 2.3:1 for half of every breath. Now it is in the monogram's own ink
   and only breathes between 84% and full, which stays above 4.5:1 throughout. */
.gate-word{font-family:var(--sans);font-weight:600;font-size:12.5px;
  text-transform:uppercase;letter-spacing:.32em;margin-right:-.32em;color:var(--ink);
  animation:gateGlow 3.2s ease-in-out infinite}
@keyframes gateGlow{0%,100%{opacity:.84}50%{opacity:1}}
@keyframes cueGlow{0%,100%{opacity:.55}50%{opacity:1}}
.gate.gone{opacity:0;pointer-events:none}
.gate.gone .gate-inner{transform:scale(1.03);opacity:0}
.gate.removed{display:none}
@media(prefers-reduced-motion:reduce){.gate-word{animation:none}}

/* ---- the envelope, opening by itself ----
   It sits above the card screen and dissolves off it. Nothing is timed
   against nothing: if the video will not autoplay, or will not load, it is
   removed at once and the card screen was already there underneath. */
/* ---- the opening -----------------------------------------------------
   The guest has just scanned the QR code on this card. Showing it back to
   them, code and all, is the one thing the first screen must not do. So the
   card sits half-drawn from a paper pocket: the monogram, the names and the
   date are out; the reply block and the code stay in. On the tap it draws
   the rest of the way and dissolves into the page, which is the same paper.
   ------------------------------------------------------------------- */
.intro{position:fixed;inset:0;z-index:100;display:block;cursor:pointer;
  overflow:hidden;background:var(--paper);transition:opacity 1000ms var(--ease)}
.intro::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:radial-gradient(78% 46% at 50% 26%,rgba(255,253,247,.62) 0%,transparent 76%)}

/* Placed so the monogram, the names and the date clear the pocket lip, and
   the reply block with its code stays below it. */
.card-stage{position:absolute;z-index:1;left:0;right:0;margin-inline:auto;
  top:21svh;width:min(78vw,326px);
  transition:transform 1600ms var(--ease),opacity 1000ms var(--ease)}
.card-img{display:block;width:100%;height:auto;border-radius:2px;
  box-shadow:0 2px 3px rgba(60,55,40,.08),
             0 26px 44px -26px rgba(60,55,40,.42)}

/* the pocket: a lip of the same stock, with the shadow the card casts into it */
/* A child of the card, so the fraction it covers is exact on every screen:
   the monogram, the names and the date clear the lip; the reply block and
   its code never appear. It spans far past the viewport on all sides. */
.pocket{position:absolute;left:-100vw;right:-100vw;top:79%;height:220svh;z-index:2;
  background:linear-gradient(180deg,#E6E0DA 0%,#EDE8E4 3%,#EDE8E4 100%);
  box-shadow:0 -1px 0 rgba(120,110,95,.20), 0 -20px 28px -22px rgba(60,55,40,.38)}
.pocket::before{content:"";position:absolute;left:100vw;right:100vw;top:0;height:1px;
  background:linear-gradient(90deg,transparent,rgba(120,110,95,.55) 12%,
    rgba(120,110,95,.55) 88%,transparent)}

/* light travelling across the sheet */
.card-sheen{position:absolute;inset:0;border-radius:2px;pointer-events:none;overflow:hidden}
.card-sheen::after{content:"";position:absolute;top:-40%;bottom:-40%;left:-60%;width:38%;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.42),transparent);
  transform:rotate(8deg);
  animation:sheen 7s cubic-bezier(.4,0,.5,1) 1.6s infinite}
@keyframes sheen{0%{left:-60%}55%,100%{left:150%}}

/* --- the tap: it is drawn the rest of the way out, then gone --- */
.intro.opening .card-stage{transform:translateY(-24%) scale(1.04);opacity:0;
  transition:transform 1500ms var(--ease),opacity 1100ms var(--ease) 380ms}
.intro.opening .pocket{transform:translateY(26%);opacity:0;
  transition:transform 1300ms var(--ease),opacity 900ms var(--ease)}
.intro.opening .card-sheen{opacity:0;transition:opacity 300ms linear}

.intro-cue{position:absolute;left:0;right:0;bottom:5svh;text-align:center;
  z-index:3;color:var(--ink-40);
  animation:cueGlow 3s ease-in-out infinite}
@keyframes cueGlow{0%,100%{opacity:.5}50%{opacity:1}}
.intro.opening .intro-cue{opacity:0;animation:none;transition:opacity 350ms var(--ease)}
.intro.gone{opacity:0;pointer-events:none}
.intro.removed{display:none}

/* =====================================================================
   REDUCED MOTION — everything lands, nothing moves
   ===================================================================== */
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;
    transition-duration:.01ms!important;transition-delay:0ms!important}
  .r{opacity:1;transform:none}
  .ln>span{transform:none}
  .w{clip-path:none}
  .pen>i{-webkit-mask-image:none!important;mask-image:none!important}
  .pen::after{display:none}
  .wd>span{transform:none;opacity:1}
  .band-mask{clip-path:none;opacity:1}
  .px,.drift,[data-tilt]{transform:none!important;translate:none!important}
  .band-mask .gloss{display:none}
  .card-stage{opacity:1;transform:none}
  .card-sheen{display:none}
  .circle path{stroke-dashoffset:0!important}
  .grain{display:none}
}
