/* Olive Pocketfold, third pass (2026-09-14): lace, the fig engraving and Rose's bronze I & R seal.
   A plain sage envelope on white embroidered lace; a plain olive folder (no printed pattern, per Rose);
   every section is a different piece of paper so no two edges or backgrounds repeat. Facts come from ../shared/content.js; nothing factual is typed here.

   Type rule: Italiana has one hairline weight, so it is used only at 28px and larger.
   Everything smaller is EB Garamond at 500 or 600.

   Contrast (WCAG AA) on ivory #FFF9F3: Espresso 11.6, Olive 9.7, Mauve 6.6, Bronze 6.3.
   Ivory on the folder 6.1, ivory on Mauve 6.6. Sage (4.2) and Taupe (3.3) are for large type
   and decoration only. The art lives in ./art and was prepared from Rose's files on 2026-09-14. */

:root {
  /* Rose's palette, 2026-09-14 */
  --bronze: #72583E;
  --sage: #7C7960;
  --olive: #44422D;
  --tan: #DBC4A5;
  --espresso: #443223;
  --mauve: #755151;
  --taupe: #A08670;
  --ivory: #FFF9F3;

  /* Paper and board */
  --paper: #F7F0E6;
  --table: #F3EDE5; /* the cream backdrop of Rose's countdown photograph (sampled 2026-09-14) */
  --folder: #5E6148;
  --envelope: #6F7259;
  --ink: var(--espresso);
  --sheet-bg: var(--ivory);
  --ease: cubic-bezier(.42, 0, .58, 1); /* easy ease */
  --champagne: #EFE3D1;
  /* Getting There: a sage-tinted paper, so Travel is the one cool card between the olive board and
     the ivory RSVP (Rose asked for a new colour, round 9). Espresso on it 9.8, Bronze 5.4. */
  --travel-paper: #E4E7DA;
  --mat: #FFFDF8;

  --display: "Italiana", Didot, "Bodoni 72", "Libre Bodoni", "Times New Roman", serif;
  --text: "EB Garamond", Garamond, "Adobe Garamond Pro", Baskerville, Georgia, serif;

  /* Deckled edge (px offsets keep the tear the same size on tall and short cards) */
  --deckle: polygon(0.0% 2.3px, 3.0% 4.6px, 5.8% 3.8px, 9.6% 0.4px, 13.8% 0.3px, 17.9% 0.5px, 20.7% 3.0px, 26.1% 0.9px, 29.3% 4.4px, 35.2% 4.0px, 39.1% 6.8px, 41.7% 6.0px, 45.2% 1.0px, 48.1% 2.2px, 53.5% 1.3px, 58.0% 4.5px, 61.8% 3.8px, 64.6% 0.4px, 67.8% 4.8px, 71.8% 2.2px, 76.3% 3.2px, 79.9% 5.6px, 84.8% 1.7px, 89.3% 3.7px, 94.9% 5.1px, 98.4% 6.9px, calc(100% - 2.1px) 2.0%, calc(100% - 0.8px) 9.8%, calc(100% - 0.2px) 16.2%, calc(100% - 3.8px) 23.6%, calc(100% - 4.4px) 30.4%, calc(100% - 3.5px) 36.0%, calc(100% - 2.9px) 43.0%, calc(100% - 4.2px) 49.3%, calc(100% - 2.4px) 58.0%, calc(100% - 0.3px) 65.3%, calc(100% - 3.2px) 72.8%, calc(100% - 4.1px) 81.8%, calc(100% - 1.9px) 87.2%, calc(100% - 0.1px) 94.5%, 100.0% calc(100% - 1.3px), 97.1% calc(100% - 0.5px), 91.9% calc(100% - 1.0px), 88.5% calc(100% - 3.1px), 83.0% calc(100% - 0.6px), 78.9% calc(100% - 4.4px), 73.3% calc(100% - 6.6px), 67.8% calc(100% - 2.2px), 63.8% calc(100% - 2.9px), 58.2% calc(100% - 7.7px), 55.2% calc(100% - 1.4px), 51.9% calc(100% - 1.9px), 47.7% calc(100% - 4.7px), 44.3% calc(100% - 0.0px), 40.3% calc(100% - 3.0px), 35.8% calc(100% - 7.6px), 30.9% calc(100% - 4.1px), 26.3% calc(100% - 5.4px), 23.6% calc(100% - 7.2px), 18.3% calc(100% - 7.0px), 13.1% calc(100% - 3.1px), 9.2% calc(100% - 0.8px), 4.4% calc(100% - 0.5px), 1.7% calc(100% - 1.7px), 1.7px 98.0%, 0.0px 93.7%, 0.5px 89.0%, 0.1px 83.2%, 3.1px 74.8%, 1.3px 70.0%, 1.8px 64.3%, 4.2px 59.7%, 2.3px 50.7%, 0.4px 44.3%, 1.7px 39.8%, 4.1px 34.5%, 0.1px 29.7%, 2.6px 20.9%, 2.7px 16.2%, 2.6px 12.0%, 4.3px 3.2%);

  /* Cotton-paper grain */
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .27 0 0 0 0 .2 0 0 0 0 .14 .3 0 0 0 -.12'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23g)'/%3E%3C/svg%3E");

  /* White embroidered lace, knocked back so it reads as a tone-on-tone ground */
  --texture: url("art/envelope-texture.webp");

  --lace:
    linear-gradient(rgba(247, 240, 230, .76), rgba(247, 240, 230, .76)),
    url("art/lace.webp") 50% 0 / 450px auto;

}

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; scroll-behavior: smooth; }
#top { scroll-margin-top: 44px; }

body {
  margin: 0;
  min-height: 100vh;
  overflow-x: hidden;
  padding: 44px 8px 28px;
  color: var(--ink);
  background: var(--lace), var(--paper);
  font: 400 1.125rem/1.6 var(--text);
  font-kerning: normal;
  -webkit-font-smoothing: antialiased;
}
body.has-cover { overflow: hidden; }
html:has(body.has-cover) { overflow: hidden; }

p, h1, h2, h3, address { margin: 0; }
/* no one-word last lines on the short centred paragraphs */
p, address { text-wrap: pretty; }
.sheet p, .open p, .sheet address, .hero .intro { text-wrap: balance; }
address { font-style: normal; }
img { display: block; max-width: 100%; height: auto; }

.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

a { color: var(--mauve); }

:focus-visible { outline: 2px solid var(--espresso); outline-offset: 3px; }
[tabindex="-1"]:focus { outline: none; }

/* ---------- Shared type ---------- */

/* Small capitals: always Garamond 600, never Italiana */
.label {
  font: 600 .9375rem/1.35 var(--text); /* round 13 (Rose, 2026-09-29): 13 → 15px, tracking .2 → .16em; her mother zoomed to read the card */
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--bronze);
  padding-left: .2em;
}

.amp {
  font-family: var(--text);
  font-style: italic;
  font-weight: 400;
  color: var(--bronze);
}

.to {
  max-width: 100%;
  font: italic 500 1.125rem/1.3 var(--text);
  color: var(--mauve);
  overflow-wrap: anywhere;
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 11px 20px 10px;
  font: 600 .875rem/1.2 var(--text); /* round 13: 13 → 14px */
  letter-spacing: .15em;
  text-transform: uppercase;
  text-decoration: none;
  text-align: center;
  border-radius: 1px;
  transition: background-color .25s ease, color .25s ease;
}
.btn-line { color: var(--espresso); background: rgba(255, 249, 243, .7); box-shadow: inset 0 0 0 1px rgba(114, 88, 62, .6); }
.btn-line:hover { background: rgba(114, 88, 62, .08); }

/* Mauve with a printed inner hairline, like a letterpress border */
.btn-wine {
  min-height: 56px;
  min-width: 11rem;
  padding: 14px 40px 13px;
  font-size: .9375rem;
  letter-spacing: .3em;
  color: var(--ivory);
  background: var(--mauve);
  box-shadow: inset 0 0 0 4px var(--mauve), inset 0 0 0 5px rgba(255, 249, 243, .45);
}
.btn-wine:hover { background: var(--espresso); box-shadow: inset 0 0 0 4px var(--espresso), inset 0 0 0 5px rgba(255, 249, 243, .45); }

.text-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-style: italic;
  font-weight: 500;
  font-size: 1.125rem; /* round 13: 17 → 18px */
  text-decoration-thickness: 1px;
  text-underline-offset: 5px;
  text-decoration-color: rgba(117, 81, 81, .45);
}
.text-link:hover { text-decoration-color: currentColor; }

/* ---------- Intro cover: a textured sage envelope on white lace ---------- */

.cover { display: none; }

.js .cover {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  padding: 24px 0;
  overflow: hidden;
  background: var(--lace), var(--paper);
  transition: opacity .7s var(--ease) 2.75s, visibility 0s linear 3.45s;
}

.cover-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 40px;
  /* Round 12f (Rose, 2026-09-21): the envelope's size, written once so the desktop recentring below can use its height */
  --env-w: min(104vw, 68vh);
  --env-h: calc(var(--env-w) * 5 / 7);
}

/* The envelope, seen from the back: the far panel, the card in its window, a solid front pocket and a
   two-sided flap. Stacking: back (0) · pocket (2) · card window (3) · flap and its shadow (4, then 1 once open) · seal (5).
   No filter sits on .envelope, because a filter would flatten the flap's 3D turn. */
.envelope {
  /* A pointed-flap (announcement) envelope: the flap is a deep triangle and the pocket's throat is cut
     on the same angle, one step shallower. Both are set here so they can never drift apart. */
  --flap: 62%;
  --throat: 38%;
  --tip: 6px; /* Round 12e (Rose, 2026-09-19): the flap's point is very slightly rounded; the arc is the same on both faces, the shadow and the liner */
  position: relative;
  /* Wider than the phone on purpose: the envelope may crop, the flap stays big (Rose, round 9).
     Round 12f (Rose, 2026-09-21): on desktop the cap was 92vh, and the opened card (which rises 0.36 of the
     envelope's height above it) then ran off the top of the screen. The composition with the card up is 1.56
     envelope heights tall, so 68vh keeps it on screen from 768px of height up. The envelope is also the
     container the card's type is measured against (cqw below), so the card fills at any size. */
  width: var(--env-w);
  container-type: inline-size;
  aspect-ratio: 7 / 5;
  perspective: 800px; /* close enough that the flap visibly lifts towards you as it turns */
  transition: transform 1.1s var(--ease) 1.5s;
}

.env-shadow {
  position: absolute;
  inset: 0;
  box-shadow: 0 18px 30px -10px rgba(68, 50, 35, .32), 0 2px 3px rgba(68, 50, 35, .18);
}

/* Rose's paper textures (matelassé relief, watercolour tooth, fibres) pressed into the sage */
.env-back, .env-pocket, .flap-out {
  background-blend-mode: soft-light, normal;
}
.env-back {
  position: absolute;
  inset: 0;
  background: var(--texture) 50% 0 / 100% auto, #5C5F47;
}

/* The window the card is seen through: everything above the envelope, plus the pocket's throat.
   Round 10 (2026-09-18): the throat cut used to sit on the pocket, in front of the card. Rose's recording
   showed that cut being lost whenever anything animated (the pocket painted as a solid rectangle while the
   flap turned and while the card rose, and the V only snapped in once every transition had ended), so the
   card seemed to ignore the envelope. Cutting the card's own ancestor keeps the reveal exact while it moves.
   The window is twice the envelope's height, so 50% here is the envelope's top edge. */
.env-window {
  position: absolute;
  left: 0; right: 0;
  top: -100%; bottom: 0;
  z-index: 3;
  clip-path: polygon(0 0, 100% 0, 100% 50%, 50% calc(50% + var(--throat) / 2), 0 50%);
  pointer-events: none;
}
.env-card {
  position: absolute;
  left: 6%; right: 6%;
  top: 53%; height: 43%; /* 6% and 86% of the envelope, measured in the window */
  pointer-events: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  gap: 4px;
  padding: 16px 14px 0;
  /* Round 12f: everything on the card is measured against the envelope's width (1cqw = 1% of it), with the
     phone's pixel values as the floor, so a phone renders exactly as before and a desktop card is filled.
     The phone reference: envelope 104vw at 390px = 405.6px, so 1cqw = 4.06px there. */
  gap: max(4px, 1cqw);
  padding: max(16px, 4cqw) max(14px, 3.5cqw) 0;
  text-align: center;
  color: var(--ink);
  background: var(--grain), var(--ivory);
  box-shadow: 0 -1px 4px rgba(40, 30, 20, .18);
  transition: transform 1.1s var(--ease) 1.5s;
}
.env-card::before {
  content: "";
  position: absolute;
  inset: 6px;
  inset: max(6px, 1.5cqw);
  border: 1px solid rgba(114, 88, 62, .4);
  pointer-events: none;
}
.env-names {
  font: 400 clamp(2.2rem, 10.4vw, 2.75rem)/1.1 var(--display); /* a touch larger since round 11 */
  font-size: clamp(2.2rem, 10cqw, 6rem); /* round 12f: 10.4vw on a phone = 10cqw; no longer capped at 2.75rem */
  color: var(--espresso);
}
.env-card .label { font-size: max(.9375rem, 3.2cqw); }
.env-names .amp { font-size: .8em; }
/* Round 11 (Rose, 2026-09-18): the revealed card read as mostly blank. Below the date: a double hairline,
   "Lodi, California" in small italics, and the bronze seal pressed on the card. They sit in the top 72%,
   which is the part that leaves the envelope; the bottom stays in the pocket. */
.env-region {
  font: italic 500 1.0625rem/1.3 var(--text);
  font-size: max(1.0625rem, 4.2cqw);
  color: var(--bronze);
}
.env-region::before {
  content: "";
  display: block;
  width: 44px;
  height: 4px;
  margin: 4px auto 8px;
  width: max(44px, 10.8cqw);
  height: max(4px, 1cqw);
  margin: max(4px, 1cqw) auto max(8px, 2cqw);
  border-block: 1px solid rgba(114, 88, 62, .5);
}
.env-seal {
  width: clamp(48px, 13.5vw, 66px);
  margin-top: 8px;
  width: clamp(48px, 13cqw, 128px);
  margin-top: max(8px, 2cqw);
  transform: rotate(-8deg);
  filter: drop-shadow(0 2px 2px rgba(40, 28, 18, .3)) drop-shadow(0 5px 6px rgba(40, 28, 18, .15));
}

/* The front pocket. Its top edge is cut as a V on the same angle as the flap, so once the flap lifts
   the card is revealed through a pointed throat: it appears at the centre point first and widens
   outwards, the way a pointed-flap envelope actually opens (Rose, round 9). The V is shallower than
   the flap, so the closed flap covers it completely. The back of the pocket shows the bottom flap's two
   folded edges, from the bottom corners up to a point just below the centre (round 12d: a dark edge with a
   light edge beside it, so they read as folds in the paper and not as drawn lines). */
.env-pocket {
  position: absolute;
  inset: 0;
  z-index: 2; /* behind the card's window since round 10; its own cut only shows the envelope's inside below the card */
  clip-path: polygon(0 0, 50% var(--throat), 100% 0, 100% 100%, 0 100%);
  background: var(--texture) 50% 0 / 100% auto, var(--envelope);
}
.env-pocket::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to top left, transparent calc(50% - 1.6px), rgba(255, 249, 243, .28) calc(50% - .9px), rgba(40, 42, 28, .55) calc(50% - .1px), rgba(40, 42, 28, .55) calc(50% + .6px), transparent calc(50% + 1.3px)) 0 100% / 50% 46% no-repeat,
    linear-gradient(to top right, transparent calc(50% - 1.6px), rgba(255, 249, 243, .28) calc(50% - .9px), rgba(40, 42, 28, .55) calc(50% - .1px), rgba(40, 42, 28, .55) calc(50% + .6px), transparent calc(50% + 1.3px)) 100% 100% / 50% 46% no-repeat;
}
/* The cut edges of the throat, caught in the light, so the V reads as an edge and not as a colour change.
   Round 12d: the two gradients were on the wrong diagonals (an inverted V from the top centre out to the sides),
   which is what showed as white lines from the side edges while the envelope was still closed. */
.env-pocket::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  height: var(--throat);
  background:
    linear-gradient(to bottom right, transparent calc(50% - 2.4px), rgba(255, 249, 243, .5) calc(50% - 1.2px), transparent calc(50% - .2px)) 100% 0 / 50% 100% no-repeat,
    linear-gradient(to bottom left, transparent calc(50% - 2.4px), rgba(255, 249, 243, .5) calc(50% - 1.2px), transparent calc(50% - .2px)) 0 0 / 50% 100% no-repeat;
  pointer-events: none;
}

/* A crisp shadow line under the flap's two cut edges; it is left out of the 3D flap so nothing flickers */
.flap-shadow {
  position: absolute;
  left: 0; right: 0; top: 0;
  height: var(--flap);
  z-index: 4;
  clip-path: polygon(0 0, 100% 0, calc(50% + -0.749 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.663 * var(--tip)), calc(50% + -0.593 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.805 * var(--tip)), calc(50% + -0.410 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.912 * var(--tip)), calc(50% + -0.210 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.978 * var(--tip)), 50% calc(100% - 1.336 * var(--tip) + 1.000 * var(--tip)), calc(50% + 0.210 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.978 * var(--tip)), calc(50% + 0.410 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.912 * var(--tip)), calc(50% + 0.593 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.805 * var(--tip)), calc(50% + 0.749 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.663 * var(--tip)));
  transform: translateY(4px);
  background: rgba(30, 32, 20, .24);
  transition: opacity .25s var(--ease) .35s;
}

/* The flap turns up over its top edge. Each face hides its back, so the sage shows first and the fig lining after. */
.env-flap {
  position: absolute;
  left: 0; right: 0; top: 0;
  height: var(--flap);
  z-index: 4;
  transform-origin: 50% 0;
  transform-style: preserve-3d;
  /* once it is fully open it drops behind the card, which then slides up over it */
  transition: transform 1.1s var(--ease) .35s, z-index 0s linear 1.45s;
}
.flap-face {
  position: absolute;
  inset: 0;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}
/* Its own piece of paper: a shade darker than the pocket, the texture read from the other end, and a fine
   hairline just inside both cut edges, so the flap stays visible against the pocket all the way through its turn. */
.flap-out {
  clip-path: polygon(0 0, 100% 0, calc(50% + -0.749 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.663 * var(--tip)), calc(50% + -0.593 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.805 * var(--tip)), calc(50% + -0.410 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.912 * var(--tip)), calc(50% + -0.210 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.978 * var(--tip)), 50% calc(100% - 1.336 * var(--tip) + 1.000 * var(--tip)), calc(50% + 0.210 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.978 * var(--tip)), calc(50% + 0.410 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.912 * var(--tip)), calc(50% + 0.593 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.805 * var(--tip)), calc(50% + 0.749 * var(--tip)) calc(100% - 1.336 * var(--tip) + 0.663 * var(--tip)));
  background:
    linear-gradient(to bottom left, transparent calc(50% - 2.5px), rgba(30, 32, 20, .42) calc(50% - 1.5px), transparent calc(50% - .5px)) 0 0 / 50% 100% no-repeat,
    linear-gradient(to bottom right, transparent calc(50% - 2.5px), rgba(30, 32, 20, .42) calc(50% - 1.5px), transparent calc(50% - .5px)) 100% 0 / 50% 100% no-repeat,
    var(--texture) 50% 100% / 100% auto,
    #61644B;
  background-blend-mode: normal, normal, soft-light, normal;
}
/* shading: the outside darkens as it tilts away from the light, the lining brightens as it settles open */
.flap-out::after, .flap-in::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  transition: opacity .55s var(--ease) .35s;
}
.flap-out::after { background: linear-gradient(rgba(20, 22, 12, .08), rgba(20, 22, 12, .5)); opacity: 0; }
.flap-in::after { background: linear-gradient(rgba(20, 22, 12, .45), rgba(20, 22, 12, .1)); opacity: 1; transition-delay: .9s; }
body.is-open .flap-out::after { opacity: 1; }
body.is-open .flap-in::after { opacity: 0; }
.flap-in {
  clip-path: polygon(calc(50% + -0.749 * var(--tip)) calc(0% + 1.336 * var(--tip) - 0.663 * var(--tip)), calc(50% + -0.593 * var(--tip)) calc(0% + 1.336 * var(--tip) - 0.805 * var(--tip)), calc(50% + -0.410 * var(--tip)) calc(0% + 1.336 * var(--tip) - 0.912 * var(--tip)), calc(50% + -0.210 * var(--tip)) calc(0% + 1.336 * var(--tip) - 0.978 * var(--tip)), 50% calc(0% + 1.336 * var(--tip) - 1.000 * var(--tip)), calc(50% + 0.210 * var(--tip)) calc(0% + 1.336 * var(--tip) - 0.978 * var(--tip)), calc(50% + 0.410 * var(--tip)) calc(0% + 1.336 * var(--tip) - 0.912 * var(--tip)), calc(50% + 0.593 * var(--tip)) calc(0% + 1.336 * var(--tip) - 0.805 * var(--tip)), calc(50% + 0.749 * var(--tip)) calc(0% + 1.336 * var(--tip) - 0.663 * var(--tip)), 100% 100%, 0 100%); /* drawn point-up, because the face is turned over; the point is the same small arc */
  transform: rotateX(180deg);
  /* Round 12d (Rose, 2026-09-19): the inside of the flap is the sage paper, and the fig liner sits inside it with a
     border of envelope showing along both cut edges, the way a lined envelope is made; only the fold edge runs
     liner to liner. The liner is the ::before so the shading ::after still covers the whole face. */
  background: var(--texture) 50% 100% / 100% auto, #666951;
  background-blend-mode: soft-light, normal;
}
.flap-in::before {
  content: "";
  position: absolute;
  inset: 0;
  clip-path: polygon(calc(50% + -0.749 * var(--tip)) calc(14% + 1.336 * var(--tip) - 0.663 * var(--tip)), calc(50% + -0.593 * var(--tip)) calc(14% + 1.336 * var(--tip) - 0.805 * var(--tip)), calc(50% + -0.410 * var(--tip)) calc(14% + 1.336 * var(--tip) - 0.912 * var(--tip)), calc(50% + -0.210 * var(--tip)) calc(14% + 1.336 * var(--tip) - 0.978 * var(--tip)), 50% calc(14% + 1.336 * var(--tip) - 1.000 * var(--tip)), calc(50% + 0.210 * var(--tip)) calc(14% + 1.336 * var(--tip) - 0.978 * var(--tip)), calc(50% + 0.410 * var(--tip)) calc(14% + 1.336 * var(--tip) - 0.912 * var(--tip)), calc(50% + 0.593 * var(--tip)) calc(14% + 1.336 * var(--tip) - 0.805 * var(--tip)), calc(50% + 0.749 * var(--tip)) calc(14% + 1.336 * var(--tip) - 0.663 * var(--tip)), 93% 100%, 7% 100%);
  background: url("art/fig-print.webp") 50% 40% / cover, var(--olive);
  box-shadow: inset 0 0 0 1px rgba(30, 32, 20, .25);
}

/* Rose's bronze seal: level, and sitting on the point of the flap the way a real seal does */
.seal {
  position: absolute;
  left: 50%;
  top: var(--flap);
  z-index: 5;
  width: clamp(96px, 28vw, 136px);
  height: clamp(96px, 28vw, 136px);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  transform: translate(-50%, -50%);
  transition: transform .45s var(--ease), opacity .6s var(--ease);
}
.seal img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 3px 3px rgba(40, 28, 18, .35)) drop-shadow(0 1px 1px rgba(40, 28, 18, .3));
}
.seal:hover, .seal:focus-visible { transform: translate(-50%, -50%) rotate(-8deg) scale(1.03); }
.seal:focus-visible { outline-offset: 4px; }

.cover-hint { color: var(--espresso); transition: opacity .5s var(--ease); }

/* Opening, easy ease throughout (about 3.5s): the seal lifts (0-0.6s), the flap turns up (0.35-1.45s),
   the card slides out as the envelope settles (1.5-2.6s), then the cover fades (2.75-3.45s) */
body.is-open .seal { opacity: 0; transform: translate(-50%, -64%) scale(1.06); pointer-events: none; }
body.is-open .flap-shadow { opacity: 0; }
body.is-open .env-flap { transform: rotateX(180deg); z-index: 1; }
body.is-open .env-card { transform: translateY(-72%); }
body.is-open .envelope { transform: translateY(20%); }
body.is-open .cover-hint { opacity: 0; }
body.is-open .cover { opacity: 0; visibility: hidden; pointer-events: none; }

/* ---------- Calla (2026-10-20): Rose's Figma frame STD ----------
   Everything above this line is the Pocketfold base and cover, copied verbatim so the envelope opens exactly as before.
   Below: three full-bleed panels. Measurements are the Figma's (1440 canvas), scaled with clamp() and container units.
   Type: Gotu (Google) for the tracked capitals, Analogue (self-hosted, Rose's licence) for the two headings,
   EB Garamond for the body text in Details. Contrast: cream on slate 11.6, white on the tinted photo >= 6, bronze on cream 9.4. */

@font-face { font-family: "Analogue"; src: url("fonts/Analogue.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
@font-face { font-family: "Analogue"; src: url("fonts/Analogue-Italic.woff2") format("woff2"); font-weight: 400; font-style: italic; font-display: swap; }

:root {
  --c-olive: #606245;
  --c-slate: #273B41;
  --c-hair: #526669;
  --c-cream: #F3ECE1;
  --c-bronze: #4F4014;
  --c-ink: #3B3A2C;
  --caps: "Gotu", "EB Garamond", Georgia, serif;
  --head: "Analogue", "Italiana", Didot, "Bodoni 72", serif;
  --gutter: clamp(12px, 7.3vw, 105px); /* 105 on the 1440 frame */
  color-scheme: light;
}

body { padding: 0; background: var(--c-cream); color: var(--c-ink); }
/* v25 (Rose 10/22, "paper texture over background"): one fixed sheet of paper grain over the whole page, under the envelope
   (z 50). Two tiles of SVG noise that carry their own alpha, so there is no blend mode and nothing repaints on scroll: a fine
   cotton grain (dark fibres and pale flecks) and a wide, fainter pulp mottle. It lies over the photos and the type too,
   which is what makes the page read as printed rather than lit. */
.calla::after {
  content: ""; position: fixed; inset: 0; z-index: 40; pointer-events: none;
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260'%3E%3Cfilter id='d'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.92' numOctaves='2' seed='7' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .2 0 0 0 0 .17 0 0 0 0 .13 .95 0 0 0 -.42'/%3E%3C/filter%3E%3Cfilter id='l'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='2' seed='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 .98 0 0 0 0 .94 0 .85 0 0 -.5'/%3E%3C/filter%3E%3Crect width='260' height='260' filter='url(%23d)'/%3E%3Crect width='260' height='260' filter='url(%23l)'/%3E%3C/svg%3E") 0 0 / 260px 260px,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='700' height='700'%3E%3Cfilter id='m'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.028' numOctaves='4' seed='11' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .3 0 0 0 0 .22 0 0 0 0 .14 .4 0 0 0 -.17'/%3E%3C/filter%3E%3Crect width='700' height='700' filter='url(%23m)'/%3E%3C/svg%3E") 0 0 / 700px 700px;
  opacity: .28;
}
.calla { display: block; }
.panel { position: relative; }
.panel:target, .dsec:target { scroll-margin-top: 0; } /* the slate nav and the jump links land with the heading at the top */

.caps { font-family: var(--caps); font-weight: 400; text-transform: uppercase; letter-spacing: .1em; line-height: 1.2; }
.display { font-family: var(--head); font-weight: 400; letter-spacing: .1em; line-height: 1.02; text-transform: uppercase; }
.display .d-the { display: block; font-size: .61em; }        /* 36 against 59 */
.display .d-big { display: block; }
.display em.d-big { font-style: italic; }
.display .d-huge { display: block; font-size: 1.32em; }      /* 78 against 59 */
.display-cream { color: var(--c-cream); }
.display-bronze { color: var(--c-bronze); }

/* v22 (Rose 10/22): the envelope's card and hint are set in the Calla type (Gotu for the tracked capitals, Analogue for
   the names, as on the page they open onto) instead of the Pocketfold's Garamond and Italiana. Colours and the envelope
   itself are unchanged; .to (a personalised "For ...") stays Garamond italic, which is still the body face. */
.cover .label { font-family: var(--caps); font-weight: 400; letter-spacing: .1em; padding-left: .1em; }
.env-names { font-family: var(--head); text-transform: uppercase; letter-spacing: .1em; text-indent: .1em; font-size: clamp(2rem, 8cqw, 4.8rem); line-height: 1.05; }
.env-names .amp { font-family: var(--head); font-style: italic; font-size: .8em; }
.env-region { font: 400 max(.875rem, 3.2cqw)/1.35 var(--caps); text-transform: uppercase; letter-spacing: .1em; padding-left: .1em; }

/* ----- 1. Hero: olive ground, a rounded card with the calla lilies under an olive tint (the lace corner came off, Rose 10/20) ----- */
.hero {
  --pad-y: clamp(16px, 6.5vw, 94px);
  background: var(--c-olive);
  padding: var(--pad-y) var(--gutter);
  min-height: 100svh;
  display: grid;
  overflow: clip; /* the cutout lily overflows the card into the olive ground, but never past the hero itself */
}
/* 2026-10-22: the frame is one grid cell holding three layers: the clipped card (photo + tint), the cutout lily that
   overflows the card's rounded edge, and the copy on top. The container query unit the copy sizes by moved here from
   the card; the frame is exactly the card's width, so nothing resizes. */
.hero-frame {
  position: relative;
  display: grid;
  container-type: inline-size;
}
.hero-frame > * { grid-area: 1 / 1; }
.hero-card {
  position: relative;
  /* One screen tall (floor 540, cap 836 as in the Figma), but a minimum, not a height: on a wide, short window the
     monogram and the date need more than 540px, and a fixed height clipped "Lodi, California" (Rose 10/21). */
  min-height: clamp(540px, calc(100svh - 2 * var(--pad-y)), 836px);
  border-radius: 20px;
  overflow: clip; /* clip, not hidden: hidden makes the card a scroll container and the parallax view() timeline measures against it */
  background: var(--c-olive);
  isolation: isolate;
}
.hero-photo {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 45%;
}
.hero-card::after { content: ""; position: absolute; inset: 0; background: rgb(96 98 69 / .41); }
/* The cutout (2026-10-22, Rose: the lily "is not locked into the background, so it reads as doubled"): the same two
   lilies cut out on a transparent ground, in a frame 1.2x the photo's crop (10% margin on every side) so they register
   over the photo's own lilies at every card size. The box is the card inset -10%, object-position 50% 45.83% is the
   photo's 50% 45% in the wider frame, and the static transform equals the photo's rest state (-9% / 1.2 = -7.5%), so
   at the top of the page there is one lily. v20 (Rose 10/22, the drift "looked doubled"): the cutout now drifts in
   lockstep with the photo (drift-lily below, the same slide in card pixels, and data-parallax-amp="7.5" for the script
   fallback), so the parallax is photo-and-lily against the still card frame and the copy, never lily against photo.
   The photo is the inpainted, lily-free `calla-bg` so nothing ghosts out from under the cutout. */
.hero-lily {
  position: absolute; z-index: 1;
  inset-block-start: -10%; inset-inline-start: -10%;
  width: 120%; height: 120%; max-width: none; /* the base img rule caps width at 100%; the cutout must pass the frame */
  object-fit: cover; object-position: 50% 45.83%;
  filter: brightness(.9) drop-shadow(0 18px 28px rgb(0 0 0 / .28));
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .hero-lily { transform: translateY(-7.5%) scale(1.2); } /* the photo's rest transform, so the two lilies coincide */
}
.hero-copy {
  position: relative; z-index: 2;
  display: flex; flex-direction: column; align-items: center; justify-content: space-between;
  padding: 6.5cqh 16px 5.5cqh;
  color: #fff;
  text-align: center;
}
.hero-save { font-size: clamp(12px, 2.2cqw, 27px); letter-spacing: .5em; text-indent: .5em; }
.hero-mono { margin: 0; line-height: 0; }
.hero-mono svg {
  width: min(clamp(190px, 37cqw, 456px), 48svh); height: auto; /* capped by the screen's height too, so a short window keeps the date on the card */
  filter: drop-shadow(5px 4px 9.2px rgb(0 0 0 / .25)); /* Figma node 1:15: black 25%, offset 5/4, blur 9.2 */
}
.hero-when { display: grid; gap: .35em; }
.hero-date { font-size: clamp(19px, 3cqw, 37px); }
.hero-region { font-size: clamp(11px, 1.55cqw, 19px); }

/* ----- 2. Countdown: the fig engraving under a dark wash, the slate card, the instant-print slideshow centred on the right ----- */
.count {
  background: #1E1E1E;
  isolation: isolate;
  overflow: clip; /* the parallax print is scaled past the panel edges */
}
.count-bg { /* a real element, not ::before: a view() timeline on a pseudo-element never animated in testing */
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(rgb(30 30 30 / .32), rgb(30 30 30 / .32)), url("art/fig-print-hd.webp") 50% 50% / cover no-repeat;
  /* fig-print-hd is Rose's full-size print (POMEGRANATE-GRAPES.png, 4800x3584, 10/21; original in inputs/2026-10-21-fig-print/)
     at 2400 wide, in place of the 2x upscale of the 736px copy that pixelated (Rose 10/21). The envelope lining keeps the
     small fig-print.webp. Phones get the 1200px copy below. */
}
.count-grid {
  --gap: clamp(24px, 8.9vw, 128px);
  display: grid;
  grid-template-columns: minmax(0, 544fr) minmax(0, 663fr); /* 105 + 544 + 128 + 663 = 1440 in the Figma; v24 adds the right gutter back, since the print no longer runs to the edge */
  column-gap: var(--gap);
  align-items: center; /* the card sits centred in the screen, as in the Figma (96 above, 97 below) */
  padding-inline: var(--gutter);
  min-height: 90svh; /* one desktop screen (Rose 10/20), then 10% shorter (Rose 10/22); taller only when the card's text needs it */
}
.slate {
  position: relative;
  container-type: inline-size;
  --m: clamp(24px, 5.5svh, 96px);
  margin-block: var(--m);
  padding: clamp(22px, 7.9cqw, 43px);
  padding-top: 50px; /* v21 (Rose 10/22): the nav sits near the top of the card (was 118px on desktop); the 68px it gave up moved below RSVP (.slate .display) so the heading and countdown stay put */
  border-radius: 20px;
  background: var(--c-slate);
  color: var(--c-cream);
  display: flex; flex-direction: column;
}
.slate-nav { display: grid; }
.slate-nav a {
  display: block;
  padding-bottom: clamp(12px, 4.8cqw, 26px);
  margin-bottom: clamp(10px, 4.2cqw, 23px);
  border-bottom: 1px solid var(--c-hair);
  font-size: clamp(20px, 6.8cqw, 37px);
  color: inherit; text-decoration: none;
  transition: opacity .25s var(--ease), letter-spacing .25s var(--ease), border-color .25s var(--ease);
}
/* Micro animations (Rose 10/22, "more micro animations"): the hairline under a link brightens with the arrow; a press dims it. */
@media (hover: hover) and (pointer: fine) {
  .slate-nav a:not(.slate-btn):hover { opacity: .8; letter-spacing: .14em; border-color: rgb(255 255 255 / .55); }
}
.slate-nav a:not(.slate-btn):focus-visible { opacity: .8; letter-spacing: .14em; border-color: rgb(255 255 255 / .55); }
.slate-nav a:not(.slate-btn):active { opacity: .6; }
.slate-nav a:focus-visible { outline: 2px solid var(--c-cream); outline-offset: 4px; }
/* Hover arrow on the in-page links (Rose 10/21: "an arrow animation to show that it leads you somewhere"; then "point left, not
   down"; then "oops, I mean to the right"): a right arrow at the far end of the hairline row fades in and slides in from the
   label towards the edge on hover and keyboard focus */
.slate-nav a:not(.slate-btn) { display: flex; justify-content: space-between; align-items: baseline; gap: .5em; }
.slate-nav a:not(.slate-btn)::after {
  content: "\2192"; font-family: var(--text); font-size: .8em; line-height: 1; letter-spacing: 0;
  opacity: 0; transform: translateX(-.3em);
  transition: opacity .2s var(--ease-out), transform .2s var(--ease-out);
}
@media (hover: hover) and (pointer: fine) { .slate-nav a:not(.slate-btn):hover::after { opacity: 1; transform: none; } }
.slate-nav a:not(.slate-btn):focus-visible::after { opacity: 1; transform: none; }
/* RSVP is a button straight to Text My Wedding (Rose 10/20), in place of the third hairline row */
.slate-nav .slate-btn {
  margin-top: clamp(2px, 1cqw, 6px); margin-bottom: 0;
  padding: .62em 1.1em .43em; border: 0; border-radius: 999px; /* same Gotu correction as .btn, smaller here */
  background: var(--c-cream); color: var(--c-slate);
  text-align: center; font-size: clamp(16px, 4.6cqw, 25px); letter-spacing: .22em; text-indent: .22em;
  transition: background-color .2s var(--ease-out), color .2s var(--ease-out), transform .12s var(--ease-out);
}
@media (hover: hover) and (pointer: fine) { .slate-nav .slate-btn:hover { background: #fff; } }
.slate-nav .slate-btn:active { background: #E6DCCB; }
/* Figma leaves about 121px under the last hairline; Rose asked for half that (10/20), so the heading sits 60px below instead of filling the card */
.slate .display { margin-top: calc(68px + clamp(20px, 11cqw, 60px)); font-size: clamp(32px, 10.8cqw, 59px); } /* the old margin plus the padding the nav gave up */
.count-num {
  display: flex; gap: clamp(12px, 5cqw, 28px);
  margin-top: clamp(16px, 5cqw, 28px);
  padding-right: clamp(60px, 28cqw, 150px); /* room for the seal */
  font-family: var(--caps);
}
.count-num .cd { display: grid; }
.count-num .cd-n { font-size: clamp(22px, 5.9cqw, 32px); line-height: 1.1; font-variant-numeric: tabular-nums; }
/* A digit that changes drops in (site.js adds .tick on each change; nothing under reduced motion). */
@media (prefers-reduced-motion: no-preference) {
  .count-num .cd-n.tick { animation: cd-tick .32s var(--ease-out); }
  @keyframes cd-tick { from { opacity: .35; transform: translateY(-.16em); } }
  .slate-nav .slate-btn:active { transform: scale(.97); }
  .slate-seal, .count-seal { animation: seal-float 7s ease-in-out infinite; } /* the wax seal breathes: 4px, slow, ambient */
  @keyframes seal-float { 50% { transform: translateY(-4px); } }
}
.count-num .cd-u { font-size: clamp(9px, 2.1cqw, 11px); letter-spacing: .18em; text-transform: uppercase; opacity: .8; }
.count-num.is-done .cd-n { color: #fff; }
.slate-seal {
  position: absolute;
  right: clamp(16px, 6.8cqw, 37px);
  bottom: clamp(-29px, -5.3cqw, -14px);
  width: clamp(84px, 25.7cqw, 140px); height: auto;
  filter: drop-shadow(0 6px 10px rgb(0 0 0 / .25));
}
.count-seal { display: none; } /* v31: the phone's copy of the seal (index.html); shown below 820px */
/* v24 (Rose 10/22, on the slideshow: "instead of the circular frame, let's try making it look like an old vintage photo OR
   polaroid"): the D-shaped arch is now an instant print. Portrait instant-film proportions (a 54 x 86 frame round a 46 x 62
   window: 4 at the sides and top, 20 below), which keeps nearly all of each portrait photo where a square Polaroid window would
   lose a third of it. Warm paper, a soft shadow, a small tilt, a faint warm tone and vignette on the photo, and the names and
   date in the deep margin. Sized from the panel height and centred in the right column. The slideshow, crossfade and breathing
   zoom are unchanged; they now live inside the window (.arch-win) so the zoom never spills onto the paper. */
.arch {
  --w: calc((100vw - 2 * var(--gutter) - var(--gap)) * 663 / 1207);
  --tilt: rotate(-2.5deg);
  position: relative;
  margin: 0;
  width: min(var(--w), calc(78svh * 54 / 86));
  aspect-ratio: 54 / 86;
  justify-self: center; align-self: center;
  background: linear-gradient(165deg, #FBF8F1, #F0EADC);
  border-radius: 3px;
  box-shadow: 0 1px 2px rgb(0 0 0 / .28), 0 26px 48px -18px rgb(0 0 0 / .65), inset 0 0 0 1px rgb(255 255 255 / .55);
  transform: var(--tilt);
}
.arch-win { position: absolute; inset: 4.65% 7.4% 23.26%; overflow: clip; background: #2B2723; }
.arch-win::after { /* the print's slight vignette and the shadow of the frame's lip */
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(ellipse at 50% 45%, transparent 55%, rgb(40 30 18 / .3));
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / .14), inset 0 2px 6px rgb(0 0 0 / .28);
}
/* Slideshow: every image fills the window; site.js turns .is-on from one to the next (data-slides, data-interval) and they crossfade.
   A single image is simply a still. */
.arch-win img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 60%;
  filter: grayscale(1) sepia(.22) contrast(.94); /* black and white (Rose 10/21) with a little of an old print's warmth (v24); drop sepia() for neutral grey */
  transition: opacity 1.4s var(--ease-out); }
.js [data-slides] img:not(.is-on) { opacity: 0; }
/* v21 (Rose 10/22) crops per slide: the two with a head near the top edge (Rose carried, Rose on Ian's shoulders) pin to the top.
   The window is 0.74 to the photos' 0.70, so about 6% of each one's height is cropped, plus the breathing zoom. */
.arch-win img:nth-child(1), .arch-win img:nth-child(5) { object-position: 50% 0%; }
.arch-win img:nth-child(3) { object-position: 50% 30%; }
.arch-win img:nth-child(6) { object-position: 50% 40%; }
/* The caption in the deep margin: the names in the Calla capitals, the date in italic below */
.arch-cap { position: absolute; left: 7.4%; right: 7.4%; top: 76.74%; bottom: 0; display: grid; place-content: center; gap: .35em; text-align: center; color: var(--c-bronze); }
.arch-cap-names { font: 400 clamp(13px, 1.15vw, 17px)/1.2 var(--head); text-transform: uppercase; letter-spacing: .16em; text-indent: .16em; }
.arch-cap-names .amp { font-style: italic; font-size: .85em; }
.arch-cap-date { font: italic 400 clamp(13px, 1vw, 15px)/1.3 var(--text); letter-spacing: .03em; opacity: .85; }
/* 2026-10-22 (Rose): the print carries data-reveal and slides in from the right as it scrolls into view; the window is exempt from
   the generic data-reveal child rules below so the slideshow's own opacity keeps working (the caption fades in with them). Every
   slide breathes: a slow zoom shared by all of them (in step, so a crossfade never snaps). */
.js .arch[data-reveal] { opacity: 0; transition: opacity 1s var(--ease-out); }
.js .arch[data-reveal].in { opacity: 1; }
.js .arch[data-reveal] > .arch-win { opacity: 1; transform: none; transition: none; }
@media (prefers-reduced-motion: no-preference) {
  .js .arch[data-reveal] { transform: translateX(32px) var(--tilt); transition-property: opacity, transform; transition-duration: 1s, 1.2s; }
  .js .arch[data-reveal].in { transform: var(--tilt); }
  .js [data-slides] img { animation: arch-zoom 14s ease-in-out infinite alternate; }
  @keyframes arch-zoom { from { transform: scale(1); } to { transform: scale(1.06); } }
}

/* ----- 3. Details, FAQs, RSVP: one wine screen (v28, Rose's Figma 2026-10-23, frame 9:2). Two centred columns, 1182px at
   most: the big word and the date on the left, that word's content on the right, a gold rule under both and a short foot
   below it. The links at the foot of the right column swap the word and the content in place (Rose 10/23: "populate in
   the same place - no need for scrolling"); the script at the end of index.html does the swapping. The bouquet is a
   watermark low on the left, multiplied into the wine and masked at its edges. ----- */
.details {
  /* The Figma's colours: a vertical fall from #3A0F24 to #220912 by two thirds, a soft wine pool top left, the headline
     and the italic answers in a dusty rose (#824E4E, 2.4:1 on the wine: Rose's choice; the facts stay cream, 14:1). */
  --d-wine: #3A0F17; --d-plum: #220912; --d-ink: #F3ECE1; --d-ink-2: rgb(243 236 225 / .8); --d-gold: #D9BC8C; --d-rose: #824E4E;
  --d-gutter: clamp(16px, 9vw, 130px);
  --body: 18px; --body-sm: 16px;
  background:
    radial-gradient(70% 45% at 10% 8%, rgb(43 10 16 / .5), transparent 70%),
    linear-gradient(180deg, #3A0F24 0%, var(--d-plum) 68%);
  color: var(--d-ink);
  position: relative; overflow: clip;
}
.details-bg { position: absolute; inset: 0; pointer-events: none; } /* no z-index: a stacking context here would blend the bouquet against nothing */
.details-bg img {
  position: absolute; inset-inline-start: -8.3%; inset-block-start: 12.5%; width: 69%; height: auto; /* the Figma's group: −120,122 at 996 wide on 1440 */
  mix-blend-mode: multiply; opacity: .9; filter: brightness(1.75) saturate(1.1);
  -webkit-mask: radial-gradient(55% 50% at 50% 50%, #000 35%, transparent 100%);
          mask: radial-gradient(55% 50% at 50% 50%, #000 35%, transparent 100%);
}
.details-screen { position: relative; min-height: 100svh; display: grid; grid-template-rows: 1fr auto; }
.details-grid {
  width: min(100% - 2 * var(--d-gutter), 1182px); margin-inline: auto;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); column-gap: clamp(40px, 6.1vw, 88px); align-items: start;
  padding-block: clamp(72px, 19.5svh, 190px) clamp(32px, 4.5svh, 44px); /* the Figma: content from y 194, the rule at 869 */
  border-bottom: 1px solid rgb(217 188 140 / .85);
}
.details-foot { height: clamp(60px, 11svh, 107px); } /* the Figma: 107px of wine under the rule */
.details-lead { display: flex; flex-direction: column; }
.details-flow { display: grid; grid-template-columns: minmax(0, 1fr); align-self: stretch; } /* v37: a sized column, so a panel can never grow past the grid (the Hotel tab ran 40px off a phone screen) */
.details-flow > * { grid-area: 1 / 1; } /* the three panels share one cell, so the one that is shown decides nothing about the others */
.dpanel { display: flex; flex-direction: column; min-width: 0; min-height: 100%; padding-top: clamp(8px, 1.7svh, 22px); } /* the Figma's right column starts 48px below the heading's top */
.dpanel:focus { outline: none; } /* focus lands here by script; the links inside keep their own rings */
.dpanel[hidden] { display: none; }
/* The big word, Analogue in the dusty rose, 131px on the Figma's 1440 */
.d-title { font-family: var(--head); font-weight: 400; font-size: clamp(64px, 9.1vw, 131px); line-height: 1.02; letter-spacing: .1em; text-transform: uppercase; color: var(--d-rose); margin: 0; }
.d-word[hidden] { display: none; }
/* The date under the word: WHEN in Gotu, then the weekday and the day in Analogue, one above the other */
.fact-when { margin-top: clamp(28px, 4.9svh, 48px); }
.fact-when .fact-label { font-size: clamp(13px, 1.25vw, 18px); line-height: 1.2; margin-bottom: 10px; }
.fact-when .fact-place { font-size: clamp(30px, 3.33vw, 48px); line-height: 1.15; display: grid; }
.fact-when .fact-note { margin-top: 14px; }
.facts { display: grid; gap: clamp(28px, 4.4svh, 43px); }
/* Fact hierarchy (Rose 10/21, "add some elegance to the hierarchy here of text - maybe change the fonts"): four voices instead
   of one. The label is Gotu small capitals in gold; the place an invitation line in Analogue, tracked capitals; the town or
   address answers in EB Garamond italic in the dusty rose; the note the quiet roman line underneath. content.js spells d'Art
   with a typographic apostrophe because Analogue has no straight one. */
.fact-label { font-size: 11px; letter-spacing: .26em; color: var(--d-gold); margin-bottom: 10px; opacity: .85; }
.fact-place { font: 400 clamp(1.25rem, 1rem + .7vw, 1.5rem)/1.15 var(--head); letter-spacing: .12em; text-transform: uppercase; color: var(--d-ink); }
.fact-where { font: italic 500 16px/1.4 var(--text); letter-spacing: .01em; color: var(--d-rose); margin-top: 10px; }
.fact-where .text-link { font-style: inherit; }
.fact-note { font: 400 var(--body-sm)/1.55 var(--text); color: var(--d-ink-2); margin-top: 10px; max-width: 330px; }
.body { font: 400 var(--body)/1.55 var(--text); color: var(--d-ink-2); max-width: 52ch; margin-top: .9em; }
.rsvp-copy > .body:first-child { margin-top: 0; }
.details .text-link { color: var(--d-rose); }
.details .text-link:hover, .details .text-link:focus-visible { color: var(--d-ink); }
.details .fact-note .text-link, .details .body .text-link { color: var(--d-rose); }
.faq-list { margin: 0; display: grid; gap: 24px; max-width: 430px; }
.faq-list dt { font: 500 var(--body)/1.4 var(--text); color: var(--d-ink); }
.faq-list dd { margin: 4px 0 0; font: 400 var(--body-sm)/1.55 var(--text); color: var(--d-ink-2); }
/* 2026-10-22: the early-RSVP text-link runs inside an answer, so it stays inline at the answer's size; the 44px tap height
   comes from block padding, which an inline box keeps out of the line height instead of opening a gap above the link. */
.faq-list .text-link { display: inline; min-height: 0; font-size: inherit; padding-block: .55em; }
/* The links at the foot of the right column: Gotu capitals in gold. The ones that swap the content carry no arrow (Rose 10/23);
   only "Back to the top", which really scrolls, keeps its arrow. */
.jump { display: flex; flex-wrap: wrap; align-items: center; gap: .9em 46px; margin: auto 0 0; padding-top: clamp(40px, 8.6svh, 84px); }
.jump a {
  display: inline-flex; align-items: center; gap: .7em; min-height: 44px;
  font-family: var(--caps); font-size: 12px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--d-gold); text-decoration: none;
}
.jump a.top::after { content: "\2191"; font-family: var(--text); font-size: 1.3em; line-height: 1; transition: transform .25s var(--ease-out); }
@media (hover: hover) and (pointer: fine) { .jump a:hover { color: var(--d-ink); } .jump a.top:hover::after { transform: translateY(-3px); } }
.jump a:active { color: var(--d-ink); }
.jump a:focus-visible { outline: 2px solid var(--d-gold); outline-offset: 4px; }
/* The RSVP button, the same link as the slate card's: a cream pill with wine letters */
.btn {
  display: inline-block;
  padding: 1.15em 1.8em .36em; border-radius: 999px; /* Gotu reserves a deep descender space, so caps-only text sits high with even padding; measured 10/21: 12.7px above the caps, 22.3px below at .8em/.7em. These values centre the letters themselves */
  background: var(--d-ink); color: var(--d-wine);
  font-family: var(--caps); font-size: 14px; letter-spacing: .22em; text-indent: .22em; text-transform: uppercase; text-decoration: none;
  transition: background-color .2s var(--ease-out);
}
@media (hover: hover) and (pointer: fine) { .btn:hover { background: #fff; } }
.btn:active { background: #E2D6C2; }
.btn:focus-visible { outline: 2px solid var(--d-gold); outline-offset: 4px; }
.btn-row { margin-top: 32px; max-width: 52ch; }
.btn-row + .body { margin-top: 16px; }
/* v42 (Rose 11/8): the Hotel tab. The garden photo heads the column at its full width (3:2, a gold hairline like the
   section's rule); the booking nudge reads a shade brighter than the body; rooms and amenities sit in two lists that
   share a row when the column has room for two 200px tracks and stack when it doesn't (one rule, no breakpoint). */
.hotel-photo { margin: 0 0 clamp(24px, 3.5svh, 36px); aspect-ratio: 3 / 2; overflow: clip; border: 1px solid rgb(217 188 140 / .55); border-radius: 2px; background: var(--d-plum); }
.hotel-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
.hotel-urgent { color: var(--d-ink); font-weight: 500; }
.hotel .btn-row { margin-top: 28px; }
.hotel-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 32px clamp(24px, 3vw, 40px); margin-top: 40px; max-width: 52ch; }
.hotel-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.hotel-list li { position: relative; padding-inline-start: 1.1em; font: 400 var(--body-sm)/1.5 var(--text); color: var(--d-ink-2); }
.hotel-list li::before { content: ""; position: absolute; inset-inline-start: 0; inset-block-start: .6em; width: 5px; height: 5px; border-radius: 50%; background: var(--d-gold); opacity: .85; }
.hotel-cols .fact-note { max-width: none; }
.hotel-other { margin-top: 40px; }
@media (prefers-reduced-motion: no-preference) {
  .btn, .slate-nav .slate-btn { transition-property: background-color, color, transform; transition-duration: .2s, .2s, .12s; }
  .btn:active, .slate-nav .slate-btn:active { transform: scale(.98); }
  /* A swapped panel and word arrive with a short fade and lift (entrance: ease-out, under 300ms; emil-animate) */
  .dpanel, .d-word { transition: opacity .22s var(--ease-out), translate .22s var(--ease-out); }
  @starting-style { .dpanel, .d-word { opacity: 0; translate: 0 8px; } }
}

@media (max-width: 820px) {
  .hero { --pad-y: 14px; }
  .hero-card { min-height: calc(100svh - 28px); }
  /* v30 (Rose's comment 10/23, "Mobile version: make lily smaller"): the cutout's box no longer fills the card. The
     card is portrait on a phone, so cover sized the landscape cutout off the card's height and the two heads filled it;
     a 66%-tall box (still cover, still the 1.2 drift) brings them down to flank the monogram. The photo under it is
     lily-free, so nothing has to register. */
  .hero-lily { inset-block-start: 10%; height: 66%; mask-image: linear-gradient(to bottom, #000 70%, transparent 97%); } /* the stems fade into the card instead of ending on the box's edge */
  .hero-copy { padding-block: 9cqh 7cqh; }
  .hero-mono svg { width: min(clamp(170px, 56cqw, 300px), 40svh); }
  /* v33 (Rose 10/23, "Mobile: put SAVE OUR DATE on one line"): three words that must never break; the size follows the
     phone's width (12px at 375) so the tracked capitals always fit the card. Headless renders had it on one line already. */
  .hero-save { font-size: min(12px, 3.2vw); white-space: nowrap; }
  .hero-date { font-size: 20px; }
  .hero-region { font-size: 11.5px; }
  .count-grid { grid-template-columns: 1fr; padding-inline: 14px; padding-bottom: 40px; row-gap: 32px; } /* v29 (mobile pass): the print no longer sits on the panel's clipped bottom edge */
  .count-bg { background-image: linear-gradient(rgb(30 30 30 / .32), rgb(30 30 30 / .32)), url("art/fig-print-hd-sm.webp"); }
  .slate { margin-block: 32px 0; padding: 28px 24px 30px; padding-top: 44px; }
  .slate-nav a { font-size: 22px; padding-bottom: 14px; margin-bottom: 12px; }
  .slate .display { font-size: 38px; margin-top: 40px; padding-top: 0; } /* v29: the desktop offset (68px + pad, v21) left a ~140px hole under RSVP on a phone; 40px under the pill, like Rose's 60px on desktop */
  /* v31 (Rose 10/23, "Mobile version: put seal centered at bottom of this section"): the seal leaves the card's corner
     and sits centred under the print, at the foot of the panel; the countdown gets the card's full width back. */
  .count-num { padding-right: 0; }
  .slate-seal { display: none; }
  .count-seal { display: block; width: 92px; height: auto; justify-self: center; margin-top: -4px; filter: drop-shadow(0 6px 10px rgb(0 0 0 / .25)); }
  .arch { width: min(72vw, 340px); margin: 0 auto; } /* v24: the print, centred under the card; the mobile pass comes after desktop sign-off */
  /* v28: one column, the word then the date then the content; the real mobile pass comes after desktop sign-off */
  .details-screen { min-height: 0; }
  .details-grid { grid-template-columns: 1fr; width: min(100% - 40px, 560px); row-gap: 40px; padding-block: 64px 32px; }
  .details-bg img { inset-inline-start: -20%; inset-block-start: 20%; width: 120%; }
  .d-title { font-size: clamp(52px, 14vw, 72px); }
  .fact-when .fact-place { font-size: 30px; }
  /* v32 (Rose 10/23, "Mobile: one line"): at 375 the calendar line fit with 6px to spare, so phones broke it in two.
     The full column, a notch smaller (shrinking with narrower phones) and no wrapping. */
  .fact-when .fact-note { max-width: none; white-space: nowrap; font-size: min(15px, 4vw); }
  .fact-when .fact-note .text-link { font-size: min(17px, 4.5vw); }
  .jump { padding-top: 40px; }
  .details-foot { height: 48px; }
}
/* ----- Motion (Rose's artifact comments, 2026-10-20) -----
   Text loads in: the hero lines wait under the envelope and come in one after another once site.js removes body.has-cover;
   the slate card and the details copy carry data-reveal, so site.js adds .in when they scroll into view and their children follow, staggered.
   Backgrounds drift: the four photos move a little slower than the page (scroll-driven animation; static in browsers without it).
   Opacity fades live outside the motion query; anything that moves lives inside it, so reduced motion keeps the fades only. */
:root { --ease-out: cubic-bezier(.23, 1, .32, 1); }

.js .hero-copy > * { opacity: 0; transition: opacity .9s var(--ease-out); }
.js .hero-copy > :nth-child(2) { transition-delay: 90ms; }
.js .hero-copy > :nth-child(3) { transition-delay: 180ms; }
.js body:not(.has-cover) .hero-copy > * { opacity: 1; }

/* v25 (Rose 10/22, "a fun load in animation with this monogram ... mask out each letter"): the monogram is inline SVG in
   three groups (I, &, R; index.html), so each letter is its own layer. Once body.has-cover goes, each is wiped in along
   its own stroke (the I down its stem, the & sweeping out from the I's foot, the R out to the right), overlapping like a
   hand writing it, 1.65s in all. The h1 no longer fades as a block; the letters are its entrance. Reduced motion: the
   three letters fade in one after another instead. */
.js .hero-copy > .hero-mono { opacity: 1; }
.hero-mono svg { overflow: visible; fill: currentColor; }
.mono-part { transform-box: fill-box; transform-origin: 50% 50%; }
.js .mono-part { opacity: 0; }
.js body:not(.has-cover) .mono-part { animation: mono-fade .9s var(--ease-out) both; }
.js body:not(.has-cover) .mono-amp { animation-delay: .35s; }
.js body:not(.has-cover) .mono-r { animation-delay: .75s; }
@keyframes mono-fade { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: no-preference) {
  .js .hero-copy > .hero-mono { transform: none; transition: none; }
  .js body:not(.has-cover) .mono-i   { animation: mono-i .8s var(--ease-out) both; }
  .js body:not(.has-cover) .mono-amp { animation: mono-amp 1s var(--ease-out) .35s both; }
  .js body:not(.has-cover) .mono-r   { animation: mono-r .9s var(--ease-out) .75s both; }
  @keyframes mono-i   { from { opacity: 0; clip-path: inset(0 0 100% 0); transform: translateY(-5%); } 25% { opacity: 1; } to { opacity: 1; clip-path: inset(0); transform: none; } }
  @keyframes mono-amp { from { opacity: 0; clip-path: inset(0 100% 100% 0); transform: translate(-4%, 4%) rotate(-6deg); } 25% { opacity: 1; } to { opacity: 1; clip-path: inset(0); transform: none; } }
  @keyframes mono-r   { from { opacity: 0; clip-path: inset(0 100% 0 0); transform: translateX(-4%); } 25% { opacity: 1; } to { opacity: 1; clip-path: inset(0); transform: none; } }
}

/* Hero reveal (v20, Rose 10/22, "some sort of fade in reveal"): the photo (card) and the cutout fade up together while
   the envelope is still going (the cover turns transparent 2.75-3.45s after the seal; these start at 2.5s and take
   1.2s), then the copy rises over them once body.has-cover is gone. Both layers share one rule so they stay one lily.
   With ?skip, or no cover, body never has has-cover, so they are simply there. The settle lives in the motion query. */
.js .hero-card, .js .hero-frame > picture { opacity: 0; transition: opacity 1.2s var(--ease-out) 2.5s; }
.js body.is-open .hero-card, .js body.is-open .hero-frame > picture,
.js body:not(.has-cover) .hero-card, .js body:not(.has-cover) .hero-frame > picture { opacity: 1; }
.js body:not(.has-cover) .hero-card, .js body:not(.has-cover) .hero-frame > picture { transition-delay: 0s; }

.js [data-reveal] > * { opacity: 0; transition: opacity .8s var(--ease-out); }
.js [data-reveal] > :nth-child(2) { transition-delay: 80ms; }
.js [data-reveal] > :nth-child(3) { transition-delay: 160ms; }
.js [data-reveal] > :nth-child(4) { transition-delay: 240ms; }
.js [data-reveal].in > * { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  .js .hero-copy > *,
  .js [data-reveal] > * {
    transform: translateY(18px);
    transition-property: opacity, transform;
    transition-duration: .9s, 1s;
  }
  .js body:not(.has-cover) .hero-copy > *,
  .js [data-reveal].in > * { transform: none; }

  /* The hero's settle: the whole frame (photo, cutout and copy as one group, so nothing un-registers) eases from 1.03
     to rest over the fade. 1.03, not more: it is a photograph settling, not a zoom. */
  .js .hero-frame { transform: scale(1.03); transition: transform 2s var(--ease-out) 2.5s; }
  .js body.is-open .hero-frame, .js body:not(.has-cover) .hero-frame { transform: none; }
  .js body:not(.has-cover) .hero-frame { transition-delay: 0s; }

  /* Parallax: each picture is scaled so it has room to slide, then slides down its own box as it travels up the screen,
     so it moves slower than the page. Round 2 used 1.1 / ±5%, which came to 17px over 400px of scroll: invisible
     (Rose 10/21, "still no parallax"). Now 1.2 / ±9%, and the hero's run is its exit only (it starts on screen, so the
     entry half of the range never played). Guarded by @supports: without scroll timelines the keyframes would run as a
     0s animation and park at the end; site.js moves [data-parallax] by hand there instead. */
  @supports (animation-timeline: view()) {
    @keyframes drift {
      from { transform: translateY(-9%) scale(1.2); }
      to   { transform: translateY(9%) scale(1.2); }
    }
    @keyframes drift-soft {
      from { transform: translateY(-5%) scale(1.1); }
      to   { transform: translateY(5%) scale(1.1); }
    }
    .hero-photo, .count-bg, .arch img {
      animation: drift linear both;
      animation-timeline: view();
    }
    .hero-photo { animation-range: exit; }
    /* The cutout rides with the photo. Its box is the card inset -10% (1.2x), so the same slide in card pixels is
       9% / 1.2 = 7.5% of its own box, and the card's exit (card top at the viewport top, to card bottom there) sits at
       8.333%..91.667% of the cutout's own exit range (0.1 / 1.2 and 1.1 / 1.2). Both read their own view() timeline
       off the same scroller, so the two lilies stay one lily all the way out. */
    @keyframes drift-lily {
      from { transform: translateY(-7.5%) scale(1.2); }
      to   { transform: translateY(7.5%) scale(1.2); }
    }
    .hero-lily {
      animation: drift-lily linear both;
      animation-timeline: view();
      animation-range: exit 8.3333% exit 91.6667%;
    }
    .arch img { animation-name: drift-soft; } /* the print is a portrait crop already; a 1.2 scale would lose too much */
  }
}
@media (prefers-reduced-motion: reduce) {
  .slate-nav a, .slate-nav a::after { transition: none; }
  .slate-nav a::after { transform: none; } /* the arrow still appears, it just does not slide */
  .jump a::after { transition: none; }
}

/* ----- Text animation (Rose's artifact comment, 2026-10-22: "text animation throughout the page") -----
   site.js wraps each word of [data-words] (hero lines, the four display headings, the fact labels and places) as .w > span.
   The outer span is a clipped line; the inner word starts below it and rises into place, each word 45ms after the last,
   so a heading reads itself in left to right. The hero waits for the envelope (body.has-cover), everything else for its
   [data-reveal] parent's .in. Split elements drop the block-level 18px lift above, so the words carry the motion alone.
   Children of [data-stagger] (the facts, the FAQ entries, the countdown units) follow each other in at 90ms steps.
   Fades live outside the motion query; the rises inside it, so reduced motion keeps the staggered fades only. */
.w { display: inline-block; overflow: clip; vertical-align: top; padding: 0 .08em .12em 0; margin: 0 -.08em -.12em 0; }
.w > span { display: inline-block; }
.js .w > span { opacity: 0; transition: opacity .55s var(--ease-out); transition-delay: calc(var(--d, 0ms) + var(--i, 0) * 45ms); }
/* --d is a line's head start: the date lines wait for the monogram, and each fact's words wait for the fact itself. */
.hero-date { --d: 300ms; } .hero-region { --d: 480ms; }
.js [data-stagger] > * { --d: calc(140ms + var(--i, 0) * 90ms); }
.js .hero-copy > .is-split, .js .hero-copy > .hero-when { transition-duration: .01s; transition-delay: 0s; }
.js [data-reveal] > .is-split { transition-duration: .01s; }
.js body:not(.has-cover) .hero-copy .w > span,
.js [data-reveal].in .w > span { opacity: 1; }

.js [data-reveal] > :nth-child(5) { transition-delay: 320ms; }
.js [data-reveal] > :nth-child(6) { transition-delay: 400ms; }
.js [data-reveal] > :nth-child(7) { transition-delay: 480ms; }
.js [data-reveal] > [data-stagger] { transition-duration: .01s; }
.js [data-reveal] [data-stagger] > * { opacity: 0; transition: opacity .7s var(--ease-out); transition-delay: calc(140ms + var(--i, 0) * 90ms); }
.js [data-reveal].in [data-stagger] > * { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  .js .w > span { transform: translateY(112%); transition-property: opacity, transform; transition-duration: .55s, .85s; }
  .js body:not(.has-cover) .hero-copy .w > span,
  .js [data-reveal].in .w > span { transform: none; }
  .js .hero-copy > .is-split, .js .hero-copy > .hero-when, .js [data-reveal] > .is-split, .js [data-reveal] > [data-stagger] { transform: none; }
  .js [data-reveal] [data-stagger] > * { transform: translateY(14px); transition-property: opacity, transform; transition-duration: .7s, .9s; }
  .js [data-reveal].in [data-stagger] > * { transform: none; }
}

/* ----- v34 Veil (Rose 10/23): section hand-offs. Desktop: as a panel leaves it lags behind the scroll (translate), its
   frame settles back a touch (scale .96) and a wash in the arriving panel's colour rises over it, feathered at the seam,
   so olive gives way to fig and fig to wine instead of butting against them. The panels are in normal flow with rising
   z-indexes, so the arriving panel simply scrolls over the lagging one: no sticky, no pinned heights, nothing for a tall
   panel to hide. Everything is driven by the panel's own view timeline (a named one, so the veil and the frame stay in
   step with the panel; a pseudo-element's own view() never animated in testing, hence the real .veil element). Phones
   get the feathered seam only; the content's fade and 18px rise are the existing data-reveal. Reduced motion keeps the
   veil (a fade) and drops the lag and the settle. ----- */
.hero { z-index: 1; }
.count { z-index: 2; }
.details { z-index: 3; }
.veil { position: absolute; inset: 0; z-index: 6; pointer-events: none; opacity: 0; }
.hero > .veil { --veil: 30 30 30; }      /* the fig panel's ground, #1E1E1E */
.count > .veil { --veil: 58 15 36; }     /* the Details fall's top, #3A0F24 */
@media (min-width: 821px) {
  .veil { background: linear-gradient(180deg, rgb(var(--veil) / .34) 0%, rgb(var(--veil) / .34) 68%, rgb(var(--veil) / .94) 100%); }
}
@media (max-width: 820px) {
  .veil { opacity: 1; background: linear-gradient(180deg, transparent 0%, transparent 82%, rgb(var(--veil) / .88) 100%); }
}
/* v35: the panel lag/settle (translate + scale on the panel and its frame) were removed; Chrome
   mis-composites them over the nested view() parallax and tears the hero/count into chunks. */
@supports (animation-timeline: view()) {
  @media (min-width: 821px) {
    .hero, .count { view-timeline: --panel block; }
    .hero > .veil, .count > .veil { animation: veil-in linear both; animation-timeline: --panel; animation-range: exit 0% exit 38%; }
  }
}
@keyframes veil-in { from { opacity: 0; } to { opacity: 1; } }
