/* ==========================================================================
   Joshua & Freddy — Invercharron, 29 & 30 May 2027
   A save the date, set as printed stationery.

   Contents
     1. Tokens
     2. Reset & page ground
     3. Typography
     4. Shared ornament (rules, eyebrows, dashes)
     5. Overture
     6. The card
     7. The house drawing (card only)
     8. The days
     9. Getting there & the map
    10. Where to stay
    11. Your address
    12. Closing
    13. Reveal, reduced motion, print
   ========================================================================== */

/* ---------- 1. Tokens ---------------------------------------------------- */
:root {
  /* Inks and stock, sampled from the printed save the date. */
  --paper:         #F2ECE3;
  --paper-neve:    #FBF8F2;
  --paper-shadow:  #E5DCD0;

  --oxblood:       #45120E;
  --burgundy:      #6E1F22;
  --burgundy-lift: #8C2B2B;

  --forest:        #243A2A;
  --forest-deep:   #1B2E20;
  --forest-mid:    #3A5240;

  --gold:          #B9902F;
  --umber:         #6B5433;
  --ink-soft:      #5A4F46;

  /* One family, three voices. Swap --font-script for a copperplate
     (e.g. "Pinyon Script") if the printed suite moves that way. */
  --font: "Cormorant Garamond", "Cormorant", "EB Garamond", "Adobe Garamond Pro",
          "Iowan Old Style", Palatino, "Palatino Linotype", Georgia, serif;
  --font-script: var(--font);

  /* Fluid scale. */
  --t-hero:  clamp(2.6rem, 1.1rem + 6.4vw, 6.2rem);
  --t-sec:   clamp(2rem,   1.1rem + 3.6vw, 3.9rem);
  --t-lede:  clamp(1.18rem, 1.02rem + .62vw, 1.6rem);
  --t-body:  clamp(1.06rem, 1rem + .3vw, 1.2rem);
  --t-small: clamp(.82rem, .78rem + .18vw, .93rem);

  --track-wide: .30em;
  --track-mid:  .22em;
  --track-tight:.14em;

  --gut: clamp(1.4rem, 5vw, 4rem);
  --measure: 34rem;
  --sec-pad: clamp(5.5rem, 12vh, 11rem);

  --ease: cubic-bezier(.22,.61,.36,1);
  --frame-inset: clamp(.9rem, 2.4vw, 2.4rem);
  --frame-in: clamp(.5rem, 1.1vw, 1.1rem);
  /* she stands on the inner rule, not the outer one */
  --ground: calc(var(--frame-inset) + var(--frame-in));
}

/* ---------- 2. Reset & page ground --------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink-soft);
  font-family: var(--font);
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.7;
  font-variant-numeric: oldstyle-num;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

.defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* The tooth of the paper. Fixed, so it never crawls on scroll. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 90;
  pointer-events: none;
  opacity: .035;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");
}

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--oxblood); color: var(--paper-neve);
  padding: .8rem 1.4rem; text-decoration: none;
  letter-spacing: var(--track-tight);
}
.skip:focus { left: 1rem; top: 1rem; }

:focus-visible {
  outline: 2px solid var(--burgundy-lift);
  outline-offset: 4px;
}

.wrap {
  width: min(100% - var(--gut) * 2, 68rem);
  margin-inline: auto;
}

section { padding-block: var(--sec-pad); position: relative; }

/* The only chrome on the page: a hairline that fills as you read. */
.rule-progress {
  position: fixed; top: 12vh; bottom: 12vh; right: max(1.6rem, 2.2vw);
  width: 1px; background: color-mix(in srgb, var(--burgundy) 16%, transparent);
  z-index: 80; pointer-events: none;
}
.rule-progress i {
  display: block; width: 100%; height: 100%;
  background: var(--burgundy);
  transform-origin: top;
  transform: scaleY(var(--scroll, 0));
}
@media (max-width: 900px) { .rule-progress { display: none; } }

/* ---------- 3. Typography ------------------------------------------------ */
h1, h2, h3 { font-weight: 300; margin: 0; text-wrap: balance; }
p { margin: 0; text-wrap: pretty; }

.sec-title {
  font-size: var(--t-sec);
  text-transform: uppercase;
  letter-spacing: var(--track-wide);
  color: var(--burgundy);
  text-indent: var(--track-wide);
  text-align: center;
  line-height: 1.15;
  filter: url(#bleed);
}

.eyebrow {
  font-style: italic;
  font-size: var(--t-small);
  letter-spacing: var(--track-tight);
  color: var(--ink-soft);
  text-align: center;
  margin-bottom: .9rem;
  font-family: var(--font-script);
}
.eyebrow.gold { color: var(--gold); }

.lede {
  font-size: var(--t-lede);
  font-style: italic;
  color: var(--forest);
  max-width: var(--measure);
  line-height: 1.62;
  font-family: var(--font-script);
}
.lede.centred { margin-inline: auto; text-align: center; margin-top: 2.2rem; }

.body { max-width: var(--measure); margin-top: 1.3rem; }
.body.script { font-style: italic; color: var(--forest); font-family: var(--font-script); }

.amp, .amp-sm { font-style: italic; font-weight: 300; }

.mono-slot {
  font-style: normal;
  letter-spacing: .06em;
  color: var(--burgundy-lift);
  background: color-mix(in srgb, var(--burgundy) 7%, transparent);
  padding: .1em .45em;
  border-radius: 1px;
}

/* ---------- 4. Shared ornament ------------------------------------------- */
.card-dash {
  display: block;
  width: 3.4rem; height: 1px;
  margin: 2.1rem auto;
  background: var(--burgundy-lift);
}

.tick {
  position: absolute; left: 50%; bottom: clamp(2.5rem, 7vh, 5rem);
  width: 1px; height: 40px;
  background: linear-gradient(to bottom, transparent, var(--burgundy));
  transform: translateX(-50%);
  opacity: 0;
  animation: tick-in 1s var(--ease) 3.5s forwards,
             breathe 3.8s var(--ease) 4.4s infinite;
}
@keyframes tick-in { to { opacity: .75; } }
@keyframes breathe {
  0%, 100% { height: 40px; }
  50%      { height: 62px; }
}

/* ---------- 5. Overture -------------------------------------------------- */
.overture {
  min-height: 100svh;
  display: grid;
  place-items: center;
  padding: clamp(2rem, 6vh, 5rem) var(--gut);
}

/* The border is ruled onto the page: each hairline grows from the centre
   of its own side. Four elements, no SVG — pathLength normalisation is
   unreliable under non-scaling-stroke with a stretched viewBox. */
.frame { position: absolute; inset: var(--frame-inset); pointer-events: none; }
.fr {
  position: absolute;
  background: var(--burgundy-lift);
  animation: rule 1.6s var(--ease) both;
}
.fr-t, .fr-b { left: 0; right: 0; height: 1px; transform: scaleX(0); }
.fr-l, .fr-r { top: 0; bottom: 0; width: 1px;  transform: scaleY(0); }
.fr-t { top: 0; }  .fr-b { bottom: 0; }
.fr-l { left: 0; } .fr-r { right: 0; }
.fr-l, .fr-r { animation-delay: .18s; }
@keyframes rule { to { transform: none; } }

.frame-in {
  position: absolute;
  inset: var(--frame-in);
  border: 1px solid var(--burgundy-lift);
  opacity: 0;
  animation: fade-in 1.4s var(--ease) .75s forwards;
}
@keyframes fade-in { to { opacity: .55; } }

.overture-inner { text-align: center; position: relative; }

.monogram {
  display: block;
  width: clamp(6.6rem, 14.2vw, 11.1rem);   /* see .gate-mono */
  height: auto;
  margin: 0 auto clamp(2rem, 5vh, 3.6rem);
  color: var(--forest);
  overflow: visible;
  clip-path: inset(0 100% 0 0);
  animation: write 1.7s cubic-bezier(.55,.06,.3,1) .5s forwards;
}
@keyframes write { to { clip-path: inset(0 -2% 0 0); } }

.names {
  font-size: var(--t-hero);
  text-transform: uppercase;
  letter-spacing: var(--track-wide);
  text-indent: var(--track-wide);
  color: var(--burgundy);
  line-height: 1.05;
  filter: url(#bleed);
}
.names .amp {
  text-transform: none;
  letter-spacing: 0;
  font-size: .78em;
  padding-inline: .16em;
}

.overture-date {
  margin-top: clamp(1.4rem, 3.4vh, 2.4rem);
  font-size: clamp(1rem, .82rem + .72vw, 1.45rem);
  letter-spacing: var(--track-wide);
  text-indent: var(--track-wide);
  color: var(--forest);
  font-variant-numeric: lining-nums;
}
.overture-date .amp-sm { padding-inline: .1em; }

.nowrap { white-space: nowrap; }

.overture-place {
  margin-top: 1.1rem;
  font-style: italic;
  font-family: var(--font-script);
  font-size: var(--t-lede);
  letter-spacing: .04em;
  color: var(--forest);
}

/* fade the whole overture up on load */
.overture-inner > * { animation: rise 1.1s var(--ease) both; }
.overture-inner > .monogram { animation: write 1.7s cubic-bezier(.55,.06,.3,1) .5s forwards; }
.names          { animation-delay: 2.7s; }
.overture-date  { animation-delay: 2.95s; }
.overture-place { animation-delay: 3.15s; }
@keyframes rise { from { opacity: 0; transform: translateY(12px); } }

/* ---------- 6. The card -------------------------------------------------- */
.card-stage {
  display: grid;
  place-items: center;
  padding-block: clamp(3rem, 9vh, 8rem);
  perspective: 1400px;
}

.card {
  width: min(100% - var(--gut) * 2, 30rem);
  aspect-ratio: 100 / 140;
  position: relative;
  transform-style: preserve-3d;
  /* translateZ is what makes it lift off the page rather than merely turn. */
  transform:
    translateZ(var(--lift, 0px))
    rotateX(calc(var(--ty, 0) * -1deg))
    rotateY(calc(var(--tx, 0) * 1deg))
    rotate(1.2deg);
  transition: transform .5s var(--ease);
}
.card.lifted { --lift: 26px; transition: transform .18s var(--ease); }

.card-face {
  position: absolute; inset: 0;
  container-type: inline-size;
  background: var(--paper-neve);
  /* The shadow leans away from the tilt, as though the light stayed put. */
  box-shadow:
    0 1px 1px rgba(69, 18, 14, .04),
    calc(var(--tx, 0) * -1.1px) calc(12px + var(--ty, 0) * -1.2px) 28px -14px rgba(69, 18, 14, .16),
    calc(var(--tx, 0) * -2.4px) calc(46px + var(--ty, 0) * -2.6px) 70px -40px rgba(69, 18, 14, .22);
  transition: box-shadow .5s var(--ease);
}
.card.lifted .card-face { transition: box-shadow .18s var(--ease); }

/* ---- the paper behaving like paper --------------------------------------
   Two overlays do the work, both driven by the same numbers as the tilt.

   .card-curl  shades the two side edges. A sheet held in the middle rolls its
               edges away from you, and the giveaway is not the geometry — at
               this size it is a millimetre — but the shading that goes with
               it. The edge you are tipping towards stays open; the far one
               darkens and rolls under, and the corners round off as it goes.
   .card-sheen a soft band of light that follows the pointer, so the stock
               looks coated rather than matte.

   Both sit above the print and below nothing: pointer-events are off, and
   blend modes keep them as light on paper rather than grey paint. */
.card-curl, .card-sheen {
  position: absolute; inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity .5s var(--ease);
}
.card.lifted .card-curl, .card.lifted .card-sheen { opacity: 1; transition: opacity .18s var(--ease); }

.card-curl {
  mix-blend-mode: multiply;
  background:
    linear-gradient(to right,
      rgba(69, 18, 14, calc(.16 * max(0, var(--tx, 0)) / 4.5)) 0,
      rgba(69, 18, 14, 0) calc(7% + var(--tx, 0) * 0.4%),
      rgba(69, 18, 14, 0) calc(93% + var(--tx, 0) * 0.4%),
      rgba(69, 18, 14, calc(.16 * max(0, calc(var(--tx, 0) * -1)) / 4.5)) 100%),
    linear-gradient(to bottom,
      rgba(69, 18, 14, calc(.12 * max(0, var(--ty, 0)) / 3.5)) 0,
      rgba(69, 18, 14, 0) 9%,
      rgba(69, 18, 14, 0) 91%,
      rgba(69, 18, 14, calc(.12 * max(0, calc(var(--ty, 0) * -1)) / 3.5)) 100%);
  border-radius: calc(2px + (var(--tx, 0) * var(--tx, 0)) * 0.09px);
}

.card-sheen {
  /* soft-light, not screen: screen at any useful strength washes the print
     out, and a sheet of stock does not glow — it just picks up a little more
     light where you are holding it towards the window. */
  mix-blend-mode: soft-light;
  background: radial-gradient(
    60% 45% at calc(var(--mx, .5) * 100%) calc(var(--my, .5) * 100%),
    rgba(255, 255, 255, .85) 0%,
    rgba(255, 255, 255, .28) 35%,
    rgba(255, 255, 255, 0) 70%);
}

@media (prefers-reduced-motion: reduce) {
  .card, .card.lifted { transform: rotate(1.2deg); }
  .card-curl, .card-sheen { display: none; }
}
/* long soft fibre in the stock itself */
.card-face::before {
  content: ""; position: absolute; inset: 0;
  opacity: .05; mix-blend-mode: multiply; pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.006 0.09' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='400' height='400' filter='url(%23f)'/%3E%3C/svg%3E");
}

.card-rules { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.card-rules rect { fill: none; stroke: var(--burgundy-lift); vector-effect: non-scaling-stroke; }
.card-rules .cr-out { stroke-width: 1.1; }
.card-rules .cr-in  { stroke-width: .5; opacity: .7; }

.card-body {
  position: absolute; inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  text-align: center;
  /* Laid out from the top so each gap below is a set value rather than a
     by-product of centring. Vertical percentages resolve against width,
     which is what we want: the card's height is locked to its width by
     aspect-ratio, so the whole rhythm scales as one. */
  padding: 10.3% 11% 0;
}

.card-mono {
  width: 17.7%;   /* see .gate-mono */
  color: var(--forest);
  overflow: visible;
  margin-bottom: 7.7%;
}

.card-illo {
  width: 92%;
  height: auto;
  color: var(--forest);
  overflow: visible;
  margin-bottom: 2.6%;
}

.card-title {
  font-size: 3.1cqi;
  text-transform: uppercase;
  letter-spacing: var(--track-wide);
  text-indent: var(--track-wide);
  color: var(--burgundy);
  filter: url(#bleed);
}
.card-sub {
  margin-top: 4%;
  font-style: italic;
  font-family: var(--font-script);
  color: var(--forest);
  letter-spacing: .08em;
  font-size: 2.4cqi;
}
.card-names {
  margin-top: 5%;
  font-size: 2.85cqi;
  text-transform: uppercase;
  letter-spacing: var(--track-mid);
  text-indent: var(--track-mid);
  color: var(--burgundy);
  filter: url(#bleed);
}
.card-date {
  font-size: 2.46cqi;
  letter-spacing: var(--track-mid);
  text-indent: var(--track-mid);
  color: var(--forest);
  font-variant-numeric: lining-nums;
}
.card-addr {
  margin-top: 7%;
  font-style: italic;
  font-family: var(--font-script);
  color: var(--forest);
  letter-spacing: .05em;
  line-height: 1.75;
  font-size: 2.11cqi;
}
.card-foot {
  margin-top: 7%;
  font-style: italic;
  font-family: var(--font-script);
  color: var(--forest);
  opacity: .85;
  letter-spacing: .05em;
  font-size: 1.97cqi;
}
.card .card-dash { width: 2rem; margin: 5% auto; }

/* ---------- 7. The house drawing ---------------------------------------- */
/*  Lives only on the card now. Every stroke carries pathLength="1", so the
    inherited stroke-dashoffset on each <use> host is what would draw a
    layer; with --r left at its default of 1 the whole thing is simply
    present. Kept as-is so the layers can be animated again if wanted.    */
/* The house is a watercolour now, not a line drawing: a raster, sized by the
   card like everything else on it. Its white paper was made transparent so it
   sits on the card stock rather than in a pale box. */
.house { display: block; }

/* ---------- 8. The days -------------------------------------------------- */
.days {
  background: var(--forest-deep);
  color: color-mix(in srgb, var(--paper) 86%, transparent);
}
.days .sec-title { color: var(--paper-neve); }
.days .eyebrow   { color: var(--gold); }
.days .lede      { color: color-mix(in srgb, var(--paper) 82%, transparent); }

.day-grid {
  margin-top: clamp(3rem, 8vh, 5.5rem);
  display: grid;
  grid-template-columns: 1fr 1px 1fr;
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}
.day-grid::before {
  content: "";
  grid-column: 2;
  grid-row: 1;
  justify-self: center;
  width: 1px;
  height: 100%;
  background: var(--forest-mid);
}
.day:first-of-type { grid-column: 1; }
.day:last-of-type  { grid-column: 3; }
.day { grid-row: 1; text-align: center; }

.day-no {
  font-style: italic;
  font-family: var(--font-script);
  color: var(--gold);
  font-size: var(--t-small);
  letter-spacing: var(--track-tight);
}
.day-date {
  margin-top: .5rem;
  font-size: clamp(1.28rem, .9rem + 1.3vw, 1.95rem);
  text-transform: uppercase;
  letter-spacing: var(--track-mid);
  text-indent: var(--track-mid);
  color: var(--paper-neve);
}

.day-list { list-style: none; margin: 2.2rem 0 0; padding: 0; }
.day-list li {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 1.4rem;
  align-items: baseline;
  padding: .85rem 0;
  border-top: 1px solid color-mix(in srgb, var(--forest-mid) 70%, transparent);
  text-align: left;
}
.day-list li:last-child { border-bottom: 1px solid color-mix(in srgb, var(--forest-mid) 70%, transparent); }
.dl-k {
  font-style: italic;
  font-family: var(--font-script);
  color: color-mix(in srgb, var(--paper) 88%, transparent);
  position: relative;
  padding-left: .95rem;
}
/* A small gold dot, not a rule: at .7rem wide the rule read as a long dash,
   which is the one punctuation mark this page does not use. */
.dl-k::before {
  content: ""; position: absolute; left: .1rem; top: .62em;
  width: 3px; height: 3px; border-radius: 50%; background: var(--gold);
}
.dl-v {
  font-size: var(--t-small);
  letter-spacing: .06em;
  color: var(--gold);
  /* the column is right-hand: a value that wraps stays ranged right with it,
     rather than starting the second line hard against the left. */
  text-align: right;
}

/* ---- the Sunday dress code ----------------------------------------------
   "Summer suits" for fifty-five seconds, "Tarts and Vicars" for five, on the
   minute, forever. Both readings occupy the same grid cell, so the cell is as
   wide as the longer of the two and nothing reflows when they change. */
.dress-swap {
  display: inline-grid;
  justify-items: end;
}
.dress-swap > i {
  grid-area: 1 / 1;
  font-style: inherit;
}
.ds-b { opacity: 0; }

.ds-a { animation: ds-a 60s steps(1, end) infinite; }
.ds-b { animation: ds-b 60s steps(1, end) infinite; }
@keyframes ds-a { 0%, 91.66% { opacity: 1 } 91.67%, 100% { opacity: 0 } }
@keyframes ds-b { 0%, 91.66% { opacity: 0 } 91.67%, 100% { opacity: 1 } }

@media (prefers-reduced-motion: reduce) {
  .ds-a, .ds-b { animation: none; }
  .ds-a { opacity: 1; }
}
@media print { .ds-b { display: none; } }

.days-foot, .map-cap {
  margin-top: clamp(2.4rem, 6vh, 4rem);
  text-align: center;
  font-style: italic;
  font-family: var(--font-script);
  font-size: var(--t-small);
  letter-spacing: var(--track-tight);
  opacity: .8;
}

/* The one instruction on the page, so it carries weight and a rule under it.
   500 is the heaviest italic loaded from the font: 600 italic does not exist
   in the webfont and the browser would slant the roman to fake it. */
#days .days-foot {
  font-weight: 500;
  opacity: 1;
  color: var(--paper-neve);
  text-decoration: underline;
  text-underline-offset: .28em;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, var(--gold) 70%, transparent);
}

/* ---------- 9. Getting there & the map ----------------------------------- */
.map-fig { margin: clamp(2.5rem, 7vh, 5rem) 0 0; padding-inline: var(--gut); }

/* The embed is the one thing on the page we don't draw ourselves, so give it
   the same hairline rule as the card and warm it very slightly towards the
   paper, so it doesn't sit on the design as a foreign object. */
.map-frame {
  width: min(100%, 56rem);
  margin-inline: auto;
  aspect-ratio: 16 / 10;
  border: 1px solid var(--burgundy-lift);
  outline: 6px solid var(--paper);
  box-shadow: 0 18px 40px -28px rgba(69, 18, 14, .35);
  background: var(--paper-shadow);
  overflow: hidden;
}
.map-frame iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  filter: sepia(.14) saturate(.86) contrast(1.02);
}

.map-cap a {
  color: var(--burgundy);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--burgundy) 35%, transparent);
  padding-bottom: .1em;
  font-style: normal;
  letter-spacing: var(--track-tight);
  transition: border-color .3s var(--ease), color .3s var(--ease);
}
.map-cap a:hover { color: var(--burgundy-lift); border-color: var(--burgundy-lift); }

.ledger { margin: clamp(3rem, 8vh, 5rem) auto 0; max-width: 46rem; }
.led-row {
  display: grid;
  grid-template-columns: 9.5rem 1fr;
  gap: clamp(1.2rem, 3vw, 2.6rem);
  padding: 1.5rem 0;
  border-top: 1px solid var(--paper-shadow);
  align-items: baseline;
}
.led-row:last-child { border-bottom: 1px solid var(--paper-shadow); }

.ledger dt {
  font-style: italic;
  font-family: var(--font-script);
  color: var(--burgundy);
  font-size: var(--t-lede);
  line-height: 1.4;
}
.ledger dd { margin: 0; }
.ledger dd a {
  color: var(--burgundy);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--burgundy) 35%, transparent);
  padding-bottom: .1em;
  transition: border-color .3s var(--ease), color .3s var(--ease);
}
.ledger dd a:hover { color: var(--burgundy-lift); border-color: var(--burgundy-lift); }
/* a phone number must not break across lines: 01463 222 222 was splitting. */
.ledger dd a[href^="tel:"] { white-space: nowrap; }
.ledger strong { font-weight: 500; color: var(--forest); }

/* ---------- 10. Where to stay ------------------------------------------- */
/* The spray needs somewhere to go, so the last line of this section has to
   clear the top of the champagne's canvas standing on the seam below it. Same
   bargain the cake strikes with the closing section. */
.stay {
  background: var(--paper);
  padding-bottom: max(var(--sec-pad),
                      calc(var(--champ-w) / var(--champ-bottle) * 0.95 + 2rem));
}
.stay .ledger dt { color: var(--forest); }
/* Price bands. A block of its own under the name, so a long hotel name and the
   pound signs never fight for the same line on a phone. */
.cost {
  display: block;
  margin-top: .35em;
  font-style: normal;
  font-size: .82em;
  letter-spacing: .12em;
  color: var(--gold);
}
.cost-key {
  margin: clamp(1.4rem, 3.5vh, 2.2rem) auto 0;
  max-width: 34rem;
  text-align: center;
  font-size: var(--t-small);
  font-style: italic;
  line-height: 1.9;
  color: var(--ink-soft);
}

/* the second list in Where to Stay: a heading part-way down the section */
.stay-more { margin-top: clamp(3.4rem, 9vh, 5.5rem); text-align: center; }

/* ---------- 11. Your address --------------------------------------------- */
.address {
  background: var(--oxblood);
  color: color-mix(in srgb, var(--paper) 84%, transparent);
}
.address .sec-title { color: var(--paper-neve); }
.address .lede      { color: color-mix(in srgb, var(--paper) 82%, transparent); }
.address :focus-visible { outline-color: var(--gold); }

/* Opens the Typeform. A ruled box, not a UI button: no radius, no fill until
   you touch it — the same gold as the form's own buttons once it opens. */
.addr-send {
  display: block;
  margin: clamp(2rem, 5vh, 2.8rem) auto 0;
  padding: .95rem 2.6rem;
  background: transparent;
  border: 1px solid var(--gold);
  border-radius: 0;
  color: var(--gold);
  font-family: var(--font);
  font-size: var(--t-small);
  text-transform: uppercase;
  letter-spacing: var(--track-mid);
  text-indent: var(--track-mid);
  cursor: pointer;
  transition: background-color .35s var(--ease), color .35s var(--ease);
}
.addr-send:hover,
.addr-send:focus-visible { background: var(--gold); color: var(--oxblood); }

.addr-alt {
  max-width: 32rem;
  margin: clamp(1.6rem, 4vh, 2.4rem) auto 0;
  text-align: center;
  font-size: var(--t-small);
  line-height: 2;
  letter-spacing: var(--track-tight);
  color: color-mix(in srgb, var(--paper) 68%, transparent);
}
.addr-alt a {
  color: var(--gold);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
  padding-bottom: .1em;
  transition: border-color .3s var(--ease);
}
.addr-alt a:hover { border-bottom-color: var(--gold); }

/* ---------- 12. Closing -------------------------------------------------- */
/* The bottom padding has to clear the cake, which stands at the foot of this
   section — otherwise the closing lines run underneath it on a narrow phone.
   Derived from the cake's own height so it keeps up if the cake is resized. */
.closing {
  text-align: center;
  padding-bottom: max(clamp(7rem, 16vh, 12rem),
                      calc(var(--cake-w) * 2.159 + var(--ground) + 2.5rem));
}
.closing-mono {
  display: block;
  width: clamp(4rem, 7.1vw, 5.4rem);   /* see .gate-mono */
  margin: 0 auto clamp(2rem, 5vh, 3rem);
  color: var(--forest);
  overflow: visible;
  opacity: .9;
}
.closing-line {
  font-style: italic;
  font-family: var(--font-script);
  font-size: var(--t-lede);
  color: var(--burgundy);
  letter-spacing: .06em;
  text-transform: none;
}
.closing-note { font-size: var(--t-small); letter-spacing: var(--track-tight); line-height: 2.2; }
.closing-mail {
  color: var(--burgundy);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--burgundy) 40%, transparent);
  padding-bottom: .1em;
  transition: border-color .3s var(--ease), color .3s var(--ease);
}
.closing-mail:hover { color: var(--burgundy-lift); border-color: var(--burgundy-lift); }

/* ---------- The two of them on the seam ---------------------------------- */
/*  Sitting on the edge where the cream section meets the green, legs hanging
    over it. Sized so their line weight matches the dog's and the cake's —
    which also lands them at about two and a half times her height, roughly
    a seated adult beside a sitting spaniel.                                 */
:root { --grooms-w: calc(var(--sp-w) * 0.838); }

.grooms {
  position: absolute;
  top: 0;
  right: 19%;
  width: var(--grooms-w);
  height: auto;
  overflow: visible;
  pointer-events: none;
  /* the seam falls at 62% down the drawing — the back of their knees */
  translate: 0 -62%;
  z-index: 2;
}
.grooms use { }
/* forest above the seam, where the ground is paper; paper below it, where the
   ground is forest and forest ink would disappear */
.gr-up   { color: var(--forest); clip-path: inset(0 0 38% 0); }

/* Their legs dangle front to back, the way they would over a ledge. Seen
   head-on that is not a sideways swing — a leg swung towards you or away from
   you is foreshortened, so its apparent length is L·cos(theta) and the foot
   rides up. Both ends of the arc shorten it, so the feet bob at twice the
   frequency of the swing and sit lowest as the legs pass through vertical.
   That doubled rhythm is the whole cue: a lateral swing would read as a
   single side-to-side sweep instead.

   So the transform is a vertical scale, nothing more. Scaling about the seam
   leaves the seam line itself fixed, so the legs stay welded to the torsos
   and the clip edge cannot tear. JS folds a slight perspective term into the
   same number — legs a touch longer swinging towards you than away — which
   keeps the two ends of the arc from looking identical.

   The drawing is a single traced path, so the pair is split down the void at
   x=663 in the viewBox (56% across) that runs the full length of the legs
   between the two figures. Each half then kicks on its own phase. */
.gr-leg {
  color: var(--paper);
  transform-box: fill-box;
  transform-origin: 50% 62%;        /* the seam they are sitting on */
  transform: scaleY(var(--gr-kick, 1));
}
.gr-leg-l { clip-path: inset(62% 44% 0 0); }
.gr-leg-r { clip-path: inset(62% 0 0 56%); }

@media (prefers-reduced-motion: reduce) { .gr-leg { transform: none; } }

@media (max-width: 560px) { .grooms { right: 8%; } }

/* ---------- The champagne on the red seam --------------------------------- */
/*  It stands on the join where the cream section meets the oxblood. Unlike the
    earlier version it is no longer wholly above that line: once the cork lands,
    champagne runs off the bottle and drips below the surface, into the red. So
    it needs the grooms' two-tone treatment — forest ink above the seam where
    the ground is cream, paper ink below it where forest ink would vanish.

    Eight frames of a 466x576 canvas share a registration: the bottle occupies
    x 172-292 and stands on a surface at y 447 in every one of them. Only the
    cork, the spray and the drips move. They are black on transparent, so each
    is a MASK over currentColor rather than an image of flat black, which is
    what lets the same drawing be inked two different colours at once.

    Only ONE image is ever referenced: --src picks the frame, and the two layers
    below both mask themselves with it. That keeps this to two elements instead
    of sixteen.

    The drawn surface sits --champ-surface down the canvas. --champ-sink then
    settles the bottle that much further down, so the seam cuts through the
    bottom of its base rather than grazing it — which is the difference between
    a bottle standing on a surface and one balanced on a line. Both the lift and
    the two clips are driven from --champ-seam, so they cannot drift apart.   */
:root {
  --champ-bottle:  0.2597;   /* 121/466 — the bottle's share of the canvas    */
  --champ-surface: 77.78%;   /* 448/576 — where the drawn surface sits        */
  --champ-sink:    1.4%;     /* how far the bottle settles below the seam     */
  --champ-seam:    calc(var(--champ-surface) - var(--champ-sink));
  --champ-w:       calc(var(--sp-w) * 0.511);   /* width of the bottle itself */
}

.champagne {
  position: absolute;
  top: 0;
  left: 2%;
  width: calc(var(--champ-w) / var(--champ-bottle));
  aspect-ratio: 466 / 576;
  pointer-events: none;
  translate: 0 calc(var(--champ-seam) * -1);   /* stand it on the seam */
  z-index: 2;
  --src: url("champagne-1.png");
}

/*  Geometry of the cut. --champ-cut is the height at which the frozen bottle
    takes over from the per-frame artwork: y=195 of 576, chosen because at that
    height the sealed frame and the frozen one agree on the neck's edges to the
    pixel, so the join cannot be seen. --champ-right is the column the bottle
    occupies, x=294 of 466 — past the glass at 292, before the cork and drips
    that live to its right.                                                  */
:root {
  --champ-cut:   33.85%;   /* 195/576 — where the frozen bottle starts */
  --champ-right: 63.09%;   /* 294/466 — the right edge of its column    */
  --champ-still: url("champagne-4.png");   /* the frozen bottle. Frame 4 is
       the most central of the eight: summed against all the others its body
       disagrees least (17,281 against frame 8's 30,613), and it is dry. */
}

.ch {
  position: absolute;
  inset: 0;
  background: currentColor;
}
/* the six pieces: three from the moving frames, two from the frozen bottle,
   split again at the seam so each side of the surface gets its own ink */
.ch-fx-top, .ch-fx-side, .ch-drip, .ch-fx-foot {
  -webkit-mask: var(--src) center / 100% 100% no-repeat;
          mask: var(--src) center / 100% 100% no-repeat;
}
.ch-body, .ch-foot {
  -webkit-mask: var(--champ-still) center / 100% 100% no-repeat;
          mask: var(--champ-still) center / 100% 100% no-repeat;
}

/* above the seam — forest ink on the cream ground */
.ch-fx-top  { color: var(--forest);
  clip-path: inset(0 0 calc(100% - var(--champ-cut)) 0); }
.ch-fx-side { color: var(--forest);
  clip-path: inset(var(--champ-cut) 0 calc(100% - var(--champ-seam)) var(--champ-right)); }
.ch-body    { color: var(--forest);
  clip-path: inset(var(--champ-cut) calc(100% - var(--champ-right)) calc(100% - var(--champ-seam)) 0); }

/* below it — paper ink, where forest would vanish into the oxblood */
.ch-foot    { color: var(--paper);
  clip-path: inset(var(--champ-seam) calc(100% - var(--champ-right)) calc(100% - var(--champ-surface)) 0); }
.ch-fx-foot { color: var(--paper);
  clip-path: inset(var(--champ-seam) 0 calc(100% - var(--champ-surface)) var(--champ-right)); }
.ch-drip    { color: var(--paper);
  clip-path: inset(var(--champ-surface) 0 0 0); }

/* --src picks which of the eight frames the moving layers mask themselves
   with. The frozen bottle ignores it entirely. */
.champagne[data-pop="2"] { --src: url("champagne-2.png"); }
.champagne[data-pop="3"] { --src: url("champagne-3.png"); }
.champagne[data-pop="4"] { --src: url("champagne-4.png"); }
.champagne[data-pop="5"] { --src: url("champagne-5.png"); }
.champagne[data-pop="6"] { --src: url("champagne-6.png"); }
.champagne[data-pop="7"] { --src: url("champagne-7.png"); }
.champagne[data-pop="8"] { --src: url("champagne-8.png"); }

/* No flipbook for anyone who has asked for less movement — they get the last
   state, cork resting and bottle dripping, so the joke still lands. */
@media (prefers-reduced-motion: reduce) {
  .champagne[data-pop] { --src: url("champagne-8.png"); }
}

/* ---------- The spaniel and the cake ------------------------------------- */
/*  She is fixed to the viewport and walked across it by --scroll, the same
    value that drives the reading rule. Her travel stops short of the cake,
    which sits at the foot of the last section — so she arrives beside it
    exactly as you reach the bottom of the page.                            */
:root {
  --sp-w: clamp(52px, 6.5vw, 84px);
  /* Sized so its bottom tier sits at her nose height. The drawing is tall
     and narrow (608:1211), so this is a cake about two and a half times her
     height — which is about right for three tiers on a stand. */
  /* Sized so the cake's line weight matches the dog's. Measured: her lines
     are 2.14% of her width, the cake's are 1.07% of its own, so the cake has
     to render about twice as wide, relative to itself, to match. */
  --cake-w: calc(var(--sp-w) * 1.159);
  /* Sitting she is compact and nearly square (807:736). Sized so her nose
     lands at the same height as when walking, and offset right so it lands
     at the same x — the cross-fade then leaves her head exactly where it was,
     on the cake. */
  /* Sized by body, not by nose height: a dog sits up taller than it stands,
     so matching the noses made her far too small. This is 1.2x her standing
     height, and --sit-x then puts her nose back on the cake at the height
     she actually carries it when sitting. */
  --sit-w: calc(var(--sp-w) * 0.692);
  --sit-x: calc(var(--sp-w) * 0.241);
  /* Her drawing has ~12% of empty box below the paws, so the box has to hang
     that far below the rule for the paws themselves to stand on it. */
  --sp-paw: calc(var(--sp-w) * 0.0843);
  /* How far her nose stops SHORT of the cake's ink, as a fraction of her own
     width. Measured, not guessed: at -0.257 her nose overlapped the cake's
     bottom tier by 9.8px, so 0.124 backs her off to a clear ~10px gap at the
     default 52px. It scales with her, so the gap stays proportional at every
     width. More positive stops her further away; negative would push her nose
     back into the cake. */
  --sp-gap: calc(var(--sp-w) * 0.124);
}

.spaniel {
  position: fixed;
  bottom: calc(var(--ground) + 1px - var(--sp-paw));
  left: var(--ground);
  width: var(--sp-w);
  aspect-ratio: 140 / 100;
  z-index: 84;
  pointer-events: none;
  color: var(--forest);
  overflow: visible;
  opacity: .88;
  transition: color .45s var(--ease), opacity .45s var(--ease);
  --sp-travel: calc(100vw - var(--sp-w) - var(--cake-w) - var(--sp-gap)
                    - var(--ground) * 2);
  translate: calc(var(--scroll, 0) * var(--sp-travel)) 0;
}

/* paper ink on the forest and oxblood grounds, where forest green vanishes */
.spaniel.on-dark { color: var(--paper); opacity: .8; }

.spaniel > * { position: absolute; bottom: 0; }
.sp-walk { left: 0; width: 100%; overflow: visible; }
.sp-walk, .sp-sit { scale: var(--sp-dir, 1) 1; transform-origin: 50% 50%; }
.sp-sit  { left: var(--sit-x); width: var(--sit-w); height: auto;
           bottom: var(--sp-paw); opacity: 0; overflow: visible;
           fill: currentColor; }

/* ---- she looks up at the cake, and wags -----------------------------------
   The champagne bottle's trick: freeze the big thing, move the small one. Her
   body is one drawing that never changes, and the tail is a second drawing
   turned on top of it. Nothing but the tail *can* move, so there is no frame
   to drift out of register — the failure that made two whole drawings risky,
   and that hid a dud pair whose tails sat in the same place.

   The two files are cut from the one supplied drawing along the join at the
   rump and share its canvas, so they overlay exactly: body + tail at 0deg is
   pixel-for-pixel the supplied tail-low drawing (IoU 1.000, not one pixel
   different), and at 9deg it matches the tail-high one to 0.02%.

   Masks rather than images, so they take the page's ink like everything else. */
.sp-up {
  left: var(--sit-x);
  width: var(--sit-w);
  aspect-ratio: 807.2 / 736.3;
  bottom: var(--sp-paw);
  opacity: 0;
  scale: var(--sp-dir, 1) 1;
  transform-origin: 50% 50%;
  transition: opacity .3s var(--ease);
}
.sp-up i {
  position: absolute;
  inset: 0;
  background: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;   mask-size: 100% 100%;
}
.up-body { -webkit-mask-image: url("dog-up-body.svg"); mask-image: url("dog-up-body.svg"); }

/* The pivot is where the tail meets the rump — (400, 1265) on the 1793x1636
   canvas, expressed as a fraction of this box. A percentage transform-origin
   is exact here because it resolves against the element's own border box; the
   trap to remember is inside the SVG, where transform-box: view-box plus a
   percentage origin does NOT land where the arithmetic says. */
.up-tail {
  -webkit-mask-image: url("dog-up-tail.svg"); mask-image: url("dog-up-tail.svg");
  transform-origin: 22.309% 77.323%;
}

@media (prefers-reduced-motion: reduce) {
  .spaniel[data-phase="sit-end"] .up-tail { animation: none; rotate: 0deg; }
}

/* ---- click her ----------------------------------------------------------
   Live wherever she is, walking or sitting. She was previously only clickable
   while sitting, to stop her swallowing taps meant for the page underneath —
   but that made her feel broken: you click the dog, nothing happens. She is
   52px wide and pinned to the bottom edge of the viewport, so the taps she can
   actually steal are few, and a dog that ignores you is the worse bug.      */
.spaniel {
  pointer-events: auto;
  cursor: pointer;
  -webkit-touch-callout: none;          /* no iOS share sheet on a long press */
  -webkit-user-select: none;
          user-select: none;
  touch-action: manipulation;
}

.spaniel .sp-say {
  position: absolute;
  left: 50%;
  bottom: calc(100% + .4rem);
  translate: -50% 0;
  white-space: nowrap;
  font-family: var(--font-script);
  font-style: italic;
  font-size: clamp(.6rem, 1.4vw, .76rem);
  letter-spacing: .03em;
  line-height: 1;
  padding: .3em .65em .34em;
  border: 1px solid currentColor;
  border-radius: 999px;
  /* filled on the light grounds so the cake's linework does not show through
     it; left bare on the dark ones, where forest and oxblood would need two
     different fills and paper ink reads well enough on its own */
  background: var(--paper);
  opacity: 0;
  scale: .8;
  transform-origin: 50% 150%;
  pointer-events: none;
  transition: opacity .2s var(--ease), scale .2s var(--ease);
}
.spaniel .sp-say::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 100%;
  width: 1px;
  height: .34rem;
  translate: -50% 0;
  background: currentColor;
}
.spaniel.on-dark .sp-say { background: transparent; }
.spaniel.saying .sp-say { opacity: .95; scale: 1; }

@media (prefers-reduced-motion: reduce) {
  .spaniel .sp-say { transition: opacity .2s var(--ease); scale: 1; }
}

/* Pinned at the end of her walk: she stops, sits, and stays put however much
   the page bounces underneath her. */
/* Sat down at either end of the page. Only the one at the cake is pinned to
   the end of her travel — at the top she sits where she starts. */
.spaniel[data-phase="sit-end"] { translate: var(--sp-travel) 0; }
.spaniel[data-phase^="sit"] { animation: none; }
.spaniel[data-phase^="sit"] .sp-walk { opacity: 0; }
.spaniel[data-phase^="sit"] .sp-sit  { opacity: 1; }

/* Only at the cake. Scrolling back up drops the phase, she cross-fades back to
   the level-headed sitting drawing and the wag stops, so the reverse reads as
   her losing interest rather than snapping. */
.spaniel[data-phase="sit-end"] .sp-sit { opacity: 0; }
.spaniel[data-phase="sit-end"] .sp-up  { opacity: 1; }
/* Two positions, held and swapped rather than eased between, which is how the
   supplied pair read. --sp-wag is the whole amplitude of the wag: it was fixed
   at 9deg while it lived in the drawings, and is now one number. */
.spaniel[data-phase="sit-end"] .up-tail { animation: sp-wag-tail .44s steps(1, end) infinite; }
@keyframes sp-wag-tail {
  0%, 49.99% { rotate: 0deg; }
  50%, 100%  { rotate: var(--sp-wag, 9deg); }
}

.spaniel[data-phase^="sit"] .sp-leg,
.spaniel[data-phase^="sit"] .sp-shin,
.spaniel[data-phase^="sit"] .sp-tail,
.spaniel[data-phase^="sit"] .sp-ear { animation: none; }

.sp-far { opacity: .5; }

.sp-ear, .sp-tail { transform-box: fill-box; transform-origin: top center; }
.sp-tail { transform-origin: bottom right; }

/* Each leg hinges twice: the upper at the shoulder or hip, the shin at the
   knee. transform-box: view-box lets the pivots be the actual joints. */
.sp-leg, .sp-shin { transform-box: view-box; }
.lf-near { transform-origin: 84px 57px; }    .lf-near .sp-shin { transform-origin: 85.4px 71px; }
.lf-far  { transform-origin: 89px 53.5px; }  .lf-far  .sp-shin { transform-origin: 90.4px 68px; }
.lh-near { transform-origin: 38px 60.5px; }  .lh-near .sp-shin { transform-origin: 33.6px 72px; }
.lh-far  { transform-origin: 45px 60.5px; }  .lh-far  .sp-shin { transform-origin: 40.6px 72px; }

/* Only while the page is moving. Diagonal pairs, half a cycle apart — a
   negative delay rather than `reverse`, because a stride is not symmetrical
   and playing it backwards would have her walking the wrong way round. */
.spaniel.running .sp-leg  { animation: sp-stride .46s linear infinite; }
.spaniel.running .sp-shin { animation: sp-fold   .46s linear infinite; }
.spaniel.running .sp-b,
.spaniel.running .sp-b .sp-shin { animation-delay: -.23s; }
.spaniel.running .sp-ear  { animation: sp-flop .46s ease-in-out infinite; }
.spaniel.running .sp-tail { animation: sp-wag .23s ease-in-out infinite; }
.spaniel.running          { animation: sp-bob .23s ease-in-out infinite; }

/* 0–55% is stance: the paw is down and the leg sweeps back under her.
   55–100% is swing: it lifts, folds, carries through and plants again. */
@keyframes sp-stride {
  0%   { rotate: 17deg; }
  55%  { rotate: -15deg; }
  72%  { rotate: -4deg; }
  88%  { rotate: 12deg; }
  100% { rotate: 17deg; }
}
@keyframes sp-fold {
  0%, 55% { rotate: 0deg; }
  68%     { rotate: -38deg; }
  84%     { rotate: -20deg; }
  100%    { rotate: 0deg; }
}
@keyframes sp-flop { 0%, 100% { rotate: 4deg; } 50% { rotate: -6deg; } }
@keyframes sp-wag  { 0%, 100% { rotate: -8deg; } 50% { rotate: 8deg; } }
@keyframes sp-bob  { 0%, 100% { margin-bottom: 0; } 50% { margin-bottom: 1.5px; } }

/* --- the cake ------------------------------------------------------------ */
.cake {
  position: absolute;
  right: var(--ground);
  bottom: calc(var(--ground) + 1px);
  width: var(--cake-w);
  height: auto;
  color: var(--forest);
  opacity: .88;
  overflow: visible;
  pointer-events: none;
}

@media (max-width: 480px) { .spaniel, .cake { opacity: .8; } }

/* ---------- The gate ------------------------------------------------------ */
/*  Shown while <html> carries .locked, which the inline script in <head> sets
    before the body is parsed. Everything else is hidden behind it — a
    deterrent and a way of keeping the page out of search, not real security:
    the whole document is in the file either way.                            */
.gate { display: none; }

html.locked { overflow: hidden; }
html.locked body > *:not(.gate):not(.defs) { display: none !important; }
html.locked .gate {
  display: grid;
  place-items: center;
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--paper);
  padding: var(--gut);
}

.gate-frame { position: absolute; inset: var(--frame-inset); pointer-events: none; }

.gate-inner { position: relative; text-align: center; }

/* The mark is wider than the F&J it replaced (1.43:1 against 1.03:1), so each
   slot is widened by sqrt(1.43/1.03) = 1.18 — the same inked area, rather
   than the same width, which would have shrunk it to 85% of its old height. */
.gate-mono {
  display: block;
  width: clamp(6.6rem, 14.2vw, 11.1rem);
  height: auto;
  margin: 0 auto clamp(2.4rem, 6vh, 4rem);
  color: var(--forest);
  overflow: visible;
  clip-path: inset(0 100% 0 0);
  animation: write 1.7s cubic-bezier(.55,.06,.3,1) .35s forwards;
}

.gate-pw {
  display: block;
  /* Wide enough for a comfortably long password. The old min(15rem, 68vw) left
     about 4px of slack for the current one at 375px and went negative on a
     320px phone, so typing to the end scrolled the first characters out of
     sight. Tracking is the real cost here — every .1em of it adds 10% to the
     width of the whole string — so it is now .14em rather than .3em, which
     still reads as spaced without pricing out a longer word. */
  width: min(21rem, 86vw);
  margin: 0 auto;
  background: transparent;
  border: 0;
  border-bottom: 1px solid color-mix(in srgb, var(--burgundy) 45%, transparent);
  border-radius: 0;
  padding: .5rem 0;
  text-align: center;
  font-family: var(--font);
  font-size: var(--t-lede);
  letter-spacing: .14em;
  text-indent: .14em;      /* offsets the trailing letter-space, so it centres true */
  color: var(--burgundy);
  transition: border-color .3s var(--ease);
}
.gate-pw:focus { outline: none; border-bottom-color: var(--burgundy); }

.gate-go {
  display: block;
  margin: 1.5rem auto 0;
  padding: .4rem .6rem;
  background: transparent;
  border: 0;
  border-bottom: 1px solid color-mix(in srgb, var(--burgundy) 32%, transparent);
  border-radius: 0;
  font-family: var(--font-script);
  font-style: italic;
  font-size: var(--t-small);
  letter-spacing: var(--track-tight);
  color: var(--burgundy);
  cursor: pointer;
  transition: border-color .3s var(--ease), opacity .3s var(--ease);
}
.gate-go:hover { border-bottom-color: var(--burgundy); }
.gate-go:focus-visible { outline: 2px solid var(--burgundy); outline-offset: 3px; }

.gate-msg {
  min-height: 1.6em;
  margin-top: 1.4rem;
  font-style: italic;
  font-family: var(--font-script);
  font-size: var(--t-small);
  letter-spacing: var(--track-tight);
  color: var(--burgundy);
  opacity: 0;
  transition: opacity .3s var(--ease);
}
.gate-msg.show { opacity: .85; }

.gate.wrong .gate-inner { animation: gate-no .4s var(--ease); }
@keyframes gate-no {
  0%, 100% { translate: 0; }
  20% { translate: -6px; } 45% { translate: 5px; }
  70% { translate: -3px; } 88% { translate: 2px; }
}

@media (prefers-reduced-motion: reduce) {
  .gate-mono { clip-path: none; animation: none; }
  .gate.wrong .gate-inner { animation: none; }
}
@media print { .gate { display: none !important; } }

/* ---------- 13. Reveal, motion, responsive, print ------------------------ */
.reveal { opacity: 0; transform: translateY(12px); }
.reveal.in {
  opacity: 1; transform: none;
  transition: opacity .8s var(--ease), transform .8s var(--ease);
  transition-delay: var(--stagger, 0s);
}

@media (max-width: 900px) {
  .names .amp { display: block; padding: .1em 0; }
}

@media (max-width: 720px) {
  :root { --track-wide: .22em; --track-mid: .16em; }
  .names .amp { display: block; padding: .12em 0; }
  .map-frame { aspect-ratio: 4 / 5; }
  .day-grid { grid-template-columns: 1fr; gap: 3rem; }
  .day-grid::before { display: none; }
  .day:first-of-type, .day:last-of-type { grid-column: 1; }
  .day:last-of-type { grid-row: 2; }
  .led-row { grid-template-columns: 1fr; gap: .5rem; padding: 1.3rem 0; }
  .card { width: calc(100% - var(--gut) * 2); }
  .card-rules .cr-in { display: none; }
  .frame-in { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .monogram { clip-path: none; }
  .reveal { opacity: 1; transform: none; }
  .spaniel { translate: 0 0; animation: none; }
  .spaniel * { animation: none !important; }
  .fr { transform: none; }
  .frame-in { opacity: .55; }
  .card { transform: rotate(1.2deg); }
  .tick { animation: none; opacity: .7; height: 44px; }
}

/* Printing the page prints the save the date, and nothing else. */
@media print {
  @page { size: A5 portrait; margin: 0; }
  body { background: #fff; }
  .grain, .rule-progress, .skip, .spaniel, .cake, .grooms, .champagne,
  .overture, .days, .travel, .stay, .address, .closing { display: none !important; }
  .card-stage { padding: 0; display: block; }
  .card {
    /* Keep the card's own 100:140 proportion rather than stretching it to
       A5's slightly taller ratio — otherwise the spacing set on screen
       skews on paper. It still sits comfortably within an A5 page. */
    width: 148mm; height: auto; aspect-ratio: 100 / 140;
    transform: none; margin: 0 auto;
  }
  .card-face { box-shadow: none; background: #fff; }
  .card-face::before { display: none; }
  .card-illo, .card-mono { color: #243A2A; }
  .card-title, .card-names { color: #6E1F22; filter: none; }
}
