:root {
  --paper: #FBF8F3;
  --paper-2: #F3ECE2;
  --backdrop: #E2D9CB;
  --ink: #3A353D;
  --ink-2: #6B6370;
  --line: #DCCFBC;
  --silver-1: #6A7078;
  --silver-2: #9AA1A9;
  --silver-3: #DCE1E6;
  --champagne: #C8A766;
  --error: #8E3B3B;
  --font-script: "Great Vibes", cursive;
  --font-serif: "Cormorant Garamond", Georgia, serif;
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --col: 440px;
  --tear: 36px; /* height of the torn photo edge */
}

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--backdrop) url("assets/img/paper.webp") center / 512px;
  background-blend-mode: multiply;
  color: var(--ink);
  font: 400 18px/1.6 var(--font-serif);
  font-variant-numeric: lining-nums;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, .note, .kicker, .place__name { text-wrap: balance; }
p { text-wrap: pretty; }
body.is-locked { overflow: hidden; }
/* on a phone the page column covers the backdrop: skip painting its blended paper texture under every tile */
@media (max-width: 440px) { body { background: var(--backdrop); } }
img { max-width: 100%; }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* ---------- Envelope loader ---------- */
.loader {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: #EEE7DB;
  overflow: hidden;
}
.loader.is-ready { background: transparent; }
/* until main.js has laid out and decoded everything (.is-ready) the loader is a plain sheet, so the first frames never show the
   half-placed seal or doors drawing in strip by strip; then the sheet lifts in one short fade */
.loader::after { content: ""; position: absolute; inset: 0; z-index: 10; background: #EEE7DB; pointer-events: none; }
.loader.is-ready::after { opacity: 0; visibility: hidden; transition: opacity .45s ease-out, visibility 0s .45s; }
/* the last dissolve: by now only the swinging doors are left (the light has faded), so they fade by themselves; fading the whole
   loader made the phone redraw all of it offscreen every frame while the first screen was coming in */
.loader.is-done { visibility: hidden; transition: visibility 0s var(--fade); }
.loader.is-done :is(.door__face, .door__back, .seal) { opacity: 0; transition: opacity var(--fade) ease-in-out; }

/* on wide screens the envelope stays phone-width, like the page */
.env { position: absolute; top: 0; bottom: 0; left: 50%; width: min(100%, var(--col)); translate: -50% 0; perspective: 1500px; }

/* A gatefold card with lace pressed into it: two doors held shut by the seal (door widths, photo offsets, --seam: main.js).
   One continuous motion after the tap, about 5 s: the room dims a little → a broad band of warm light runs from the seal
   across the lace (only the raised pattern catches it: doors-light.webp, made by tools/relief-mask.mjs) → light builds along
   the seam, with rays → the light inside is already on as the doors part, so the first gap shows light → the doors swing
   open → the glow fades into the first screen.
   Timeline: each step's start (-at) and length. --pre: the photo starts to show (the hero reveal waits for it);
   --fade: the envelope's last dissolve. main.js (OPEN_MS, FADE_MS) hands over to the page just before the doors finish. */
:root {
  --seal-r: min(18vw, 82px);
  --dim: 4.4s; --dim-at: .05s; --wave: 2.5s; --wave-at: .3s; --seam-d: 2.8s; --seam-at: 1.5s; --rays: 2.6s; --rays-at: 2s;
  --bloom: 2.6s; --bloom-at: 2.6s; --door: 2.4s; --door-at: 2.8s; --pre: 2.8s; --fade: .9s; --glow-at: .1s;
  --smooth: cubic-bezier(.45, .05, .55, .95);
}
.door { position: absolute; top: 0; bottom: 0; transform-style: preserve-3d; }
.door--l { left: 0; transform-origin: 0 50%; z-index: 2; }
.door--r { right: 0; transform-origin: 100% 50%; z-index: 1; }
.door__face, .door__back { position: absolute; inset: 0; backface-visibility: hidden; }
.door__face { background: url("assets/img/doors.webp") no-repeat; }
.door__back { transform: rotateY(180deg); background: linear-gradient(90deg, rgba(255, 226, 172, .55), rgba(255, 226, 172, 0) 60%), #F4EEE5 url("assets/img/paper.webp") 0 0 / 512px; box-shadow: inset 0 0 60px rgba(60, 45, 30, .16); }
.is-opening .door--l { animation: door-l var(--door) cubic-bezier(.45, .05, .3, 1) var(--door-at) both; }
.is-opening .door--r { animation: door-r var(--door) cubic-bezier(.45, .05, .3, 1) var(--door-at) both; }
@keyframes door-l { from { transform: rotateY(0deg); } to { transform: rotateY(-100deg); } }
@keyframes door-r { from { transform: rotateY(0deg); } to { transform: rotateY(100deg); } }

/* the room dims a little after the tap, so the light from inside reads on light paper; it lifts as the doors open */
.door__dim { position: absolute; inset: 0; opacity: 0; }
.door--l .door__dim { background: radial-gradient(130% 75% at 100% 50%, rgba(52, 36, 22, .1), rgba(52, 36, 22, .36)); }
.door--r .door__dim { background: radial-gradient(130% 75% at 0 50%, rgba(52, 36, 22, .1), rgba(52, 36, 22, .36)); }
.is-opening .door__dim { animation: dim var(--dim) var(--smooth) var(--dim-at) both; }
@keyframes dim { 0% { opacity: 0; } 16% { opacity: 1; } 78% { opacity: 1; } 100% { opacity: 0; } }
.door__light { position: absolute; inset: 0; overflow: hidden; -webkit-mask: url("assets/img/doors-light.webp") no-repeat; mask: url("assets/img/doors-light.webp") no-repeat; }
/* The light (the wave, the rays, the bloom) is drawn once by main.js into small canvases that CSS scales up: as CSS gradients
   these were 1266-1688px layers, ×3 on a phone, hundreds of MB of GPU memory, and the opening stuttered. */
/* the wave: a broad, soft band of warm light growing from the seal; the lace glows as it passes and keeps a little warmth */
.wave {
  position: absolute;
  top: 50%;
  width: 150vmax;
  height: 150vmax;
  translate: -50% -50%;
  transform: scale(0);
}
.door--l .wave { left: 100%; }
.door--r .wave { left: 0; }
.is-opening .wave { animation: wave var(--wave) cubic-bezier(.33, .12, .3, 1) var(--wave-at) both; }
@keyframes wave { 0% { transform: scale(.03); opacity: 0; } 6% { opacity: 1; } 100% { transform: scale(1); opacity: 1; } }
/* light building along the seam (with a gap where the seal sits), then rays fanning out of it */
.spill { position: absolute; inset: 0; z-index: 3; pointer-events: none; mix-blend-mode: screen; }
.spill__line {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--seam, 50%);
  width: 70px;
  margin-left: -35px;
  background: linear-gradient(90deg, rgba(255, 214, 150, 0), rgba(255, 220, 160, .5) 36%, #FFF8EA 50%, rgba(255, 220, 160, .5) 64%, rgba(255, 214, 150, 0));
  --gap: linear-gradient(transparent, #000 16%, #000 calc(50% - var(--seal-r) - 14px), transparent calc(50% - var(--seal-r) + 6px), transparent calc(50% + var(--seal-r) - 6px), #000 calc(50% + var(--seal-r) + 14px), #000 84%, transparent);
  -webkit-mask: var(--gap);
  mask: var(--gap);
  opacity: 0;
}
.is-opening .spill__line { animation: seam var(--seam-d) var(--smooth) var(--seam-at) both; }
@keyframes seam { 0% { opacity: 0; transform: scaleX(.04); } 35% { opacity: .85; transform: scaleX(.55); } 55% { opacity: 1; transform: scaleX(1); } 70% { opacity: .88; transform: scaleX(1.1); } 82% { opacity: 1; transform: scaleX(1.2); } 100% { opacity: 0; transform: scaleX(1.7); } }
.spill__rays {
  position: absolute;
  left: var(--seam, 50%);
  top: 50%;
  width: 200vmax;
  height: 200vmax;
  translate: -50% -50%;
  opacity: 0;
}
.is-opening .spill__rays { animation: rays var(--rays) var(--smooth) var(--rays-at) both; }
@keyframes rays { 0% { opacity: 0; transform: scale(.4, .75) rotate(-3deg); } 30% { opacity: .8; } 50% { opacity: .62; } 70% { opacity: .85; } 100% { opacity: 0; transform: scale(1, 1.2) rotate(7deg); } }
/* behind the doors: the light inside, a tall soft column already on when they part, then fading into the first screen */
.bloom {
  position: absolute;
  left: var(--seam, 50%);
  top: 50%;
  width: 150vmax;
  height: 230vmax;
  translate: -50% -50%;
  opacity: 0;
}
.is-opening .bloom { animation: bloom var(--bloom) var(--smooth) var(--bloom-at) both; }
@keyframes bloom { 0% { opacity: 0; transform: scale(.3); } 25% { opacity: .55; } 55% { opacity: .4; } 100% { opacity: 0; transform: scale(1.1); } }
/* the tap is answered by the seal starting to glow, as if lit from inside (a halo behind the wax that fades in) */
.seal::before {
  content: "";
  position: absolute;
  inset: -10%;
  z-index: -1;
  /* the falloff of the old drop-shadow(0 0 16px): the wax edge sits at 75% of this radius */
  background: radial-gradient(closest-side, rgba(255, 212, 146, .88) 57%, rgba(255, 212, 146, .76) 66%, rgba(255, 212, 146, .45) 75%, rgba(255, 212, 146, .14) 84%, rgba(255, 212, 146, .02) 93%, rgba(255, 212, 146, 0));
  opacity: 0;
  pointer-events: none;
}
.is-opening .seal::before { opacity: 1; transition: opacity 1.4s ease-in-out var(--glow-at); }

/* sits on the seam (the left door's right edge) and swings away with the left door */
.seal {
  position: absolute;
  left: 100%;
  top: 50%;
  width: min(36vw, 164px);
  aspect-ratio: 1;
  translate: -50% -50%;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  backface-visibility: hidden;
  transition: scale .25s var(--ease-out);
}
.seal:disabled { cursor: progress; }
.seal:active { scale: .96; }
.seal:focus-visible { outline: 2px solid var(--ink); outline-offset: 6px; border-radius: 50%; }
.seal__wax { display: block; width: 100%; height: 100%; filter: drop-shadow(0 7px 9px rgba(60, 40, 15, .33)); }
.seal__mono { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
/* Italianno: Y and S are equal capitals. Sized to fill the seal face; x shifted 0.85 so the letters' outline is centered */
.seal__mono text { font-family: "Italianno", cursive; font-size: 21.6px; text-anchor: middle; dominant-baseline: central; }
.seal__mono .sh { fill: rgba(70, 48, 18, .55); }
.seal__mono .hl { fill: rgba(255, 242, 212, .6); }
.seal__mono .fill { fill: #9A7A4E; }
/* tap invitation: the seal slowly rises toward you (a deeper shadow grows under it) and a soft light glides over the wax.
   Only transform and opacity move, so the phone's GPU runs it without repainting the wax every frame. */
.seal::after {
  content: "";
  position: absolute;
  inset: 12% 9% -3%;
  z-index: -1;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(60, 40, 15, .34), rgba(60, 40, 15, .14) 70%, rgba(60, 40, 15, 0));
  opacity: 0;
  pointer-events: none;
}
.seal__sheen {
  position: absolute;
  inset: 0;
  overflow: hidden;
  -webkit-mask: url("assets/img/seal.webp") center / contain no-repeat;
  mask: url("assets/img/seal.webp") center / contain no-repeat;
  mix-blend-mode: overlay;
  pointer-events: none;
}
.seal__sheen::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 300%;
  background: linear-gradient(115deg, rgba(255, 238, 196, 0) 44%, rgba(255, 238, 196, .85) 50%, rgba(255, 238, 196, 0) 56%);
  opacity: 0;
}
.is-ready .seal { animation: rise 3.4s ease-in-out .3s infinite; }
.is-ready .seal::after { animation: rise-shadow 3.4s ease-in-out .3s infinite; }
.is-ready .seal__sheen::before { animation: sheen 3.4s ease-in-out .3s infinite; }
.is-opening .seal, .is-opening .seal::after, .is-opening .seal__sheen::before { animation: none; }
@keyframes rise { 0%, 100% { transform: scale(1); } 45% { transform: scale(1.055); } }
@keyframes rise-shadow { 0%, 100% { opacity: 0; transform: none; } 45% { opacity: 1; transform: translateY(7px) scale(1.06); } }
@keyframes sheen { 0%, 28% { opacity: 0; transform: translateX(-66.667%); } 36%, 62% { opacity: 1; } 72%, 100% { opacity: 0; transform: none; } }

/* for screen readers only: the envelope status ("loading…" / "tap the seal"; the seal itself invites the tap), the RSVP question */
.loader__hint, .sr-only { position: absolute; width: 1px; height: 1px; margin: 0; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* ---------- Page column ---------- */
.site {
  position: relative;
  max-width: var(--col);
  margin: 0 auto;
  background: var(--paper) url("assets/img/paper.webp") center top / 512px;
  overflow: hidden;
}
@media (min-width: 520px) {
  .site, .env { box-shadow: 0 0 0 1px rgba(58, 53, 61, .06), 0 40px 90px -40px rgba(60, 45, 30, .45); }
}

/* ---------- Shared pieces ---------- */
.sec { padding: 30px 26px 36px; text-align: center; } /* tighter between sections (Yurii, 12 Oct): was 44 / 52 */
:where(.sec) p { margin: 0 0 14px; }
.kicker { margin: 0 0 10px; font-size: 12px; font-weight: 500; letter-spacing: .3em; text-transform: uppercase; color: var(--ink-2); }
.sec__title { margin: 0 0 28px; font: italic 400 36px/1.15 var(--font-serif); color: var(--ink); }
.note { font-style: italic; color: var(--ink-2); }
.note a { color: inherit; text-decoration-color: rgba(107, 99, 112, .4); text-underline-offset: 3px; }
/* the RSVP deadline, slightly marked: ink instead of grey, a touch heavier, a fine champagne line under it */
.deadline { color: var(--ink); font-weight: 500; white-space: nowrap; text-decoration: underline 1px rgba(200, 167, 102, .8); text-underline-offset: 4px; }

/* silver foil: dark base and a narrow sheen, so no letter ever washes out on light paper */
.foil {
  background: linear-gradient(100deg, var(--silver-1) 0%, var(--silver-2) 16.7%, var(--silver-1) 33.3%, var(--silver-2) 43%, #B9C0C7 50%, var(--silver-2) 57%, var(--silver-1) 66.7%, var(--silver-2) 83.3%, var(--silver-1) 100%) 0 0 / 300% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  padding: 0 .12em .14em;
  animation: foil 20s ease-in-out infinite;
}
@keyframes foil { 0%, 35% { background-position: 100% 0; } 80%, 100% { background-position: 0 0; } }

.names { position: relative; margin: 0; font: 400 clamp(44px, 12.6vw, 54px)/1.1 var(--font-script); white-space: nowrap; }
.names .amp { font-size: .62em; color: var(--silver-1); margin: 0 .04em; }
/* the names: a soft light glides over the foil again and again, from Юрій on to Соломія */
.names { --names-foil: linear-gradient(100deg, var(--silver-1) 0%, var(--silver-2) 16.7%, var(--silver-1) 33.3%, var(--silver-2) 45%, #D3D8DD 50%, var(--silver-2) 55%, var(--silver-1) 66.7%, var(--silver-2) 83.3%, var(--silver-1) 100%); }
.names .foil {
  background-image: var(--names-foil);
  animation: shine 6.5s cubic-bezier(.45, 0, .55, 1) infinite;
}
.names .amp + .foil, .names .amp + .foil .foil__light { animation-delay: 1.2s; } /* the light reaches Соломія as it leaves Юрій */
@keyframes shine { 0%, 12% { background-position: 100% 0; } 62%, 100% { background-position: 0 0; } }
/* Once main.js has drawn the letters into a mask (.is-masked), the same gradient slides under them as a transform: the phone
   no longer restyles and repaints the text every frame. Same timing; translateX(-2/3) of a 300% strip = background-position 100%. */
.names .foil.is-masked { position: relative; background: none; animation: none; -webkit-mask-size: 100% 100%; mask-size: 100% 100%; -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; }
.foil__light { position: absolute; top: 0; bottom: 0; left: 0; width: 300%; background: var(--names-foil); animation: shine-x 6.5s cubic-bezier(.45, 0, .55, 1) infinite; }
@keyframes shine-x { 0%, 12% { transform: translateX(-66.667%); } 62%, 100% { transform: none; } }
.hero__date { margin: 16px 0 0; letter-spacing: .3em; font-size: 16px; font-variant-numeric: lining-nums; }

.twinkle {
  position: absolute;
  width: 13px;
  height: 13px;
  clip-path: polygon(50% 0, 58% 42%, 100% 50%, 58% 58%, 50% 100%, 42% 58%, 0 50%, 42% 42%);
  background: radial-gradient(circle, #fff 0 28%, #C9CFD6 75%);
  opacity: 0;
  animation: twinkle 11s ease-in-out infinite;
  pointer-events: none;
}
.twinkle + .twinkle { animation-delay: 5.5s; width: 10px; height: 10px; }
@keyframes twinkle { 0%, 100% { opacity: 0; transform: scale(.2) rotate(0deg); } 7% { opacity: .75; transform: scale(.85) rotate(45deg); } 14% { opacity: 0; transform: scale(.3) rotate(90deg); } }

/* tiny jewel between sections: three stones set into a fading hairline (stones placed by main.js) */
.jewel { position: relative; width: 190px; height: 14px; margin: 0 auto 22px; }
.jewel::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--silver-2) 30%, var(--silver-2) 70%, transparent);
  -webkit-mask: linear-gradient(90deg, #000 38%, transparent 38% 62%, #000 62%);
  mask: linear-gradient(90deg, #000 38%, transparent 38% 62%, #000 62%);
}
.stone { position: absolute; translate: -50% -50%; filter: drop-shadow(0 .6px .7px rgba(40, 42, 50, .4)); pointer-events: none; }

.btns { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; }
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  min-height: 48px;
  padding: 0 22px;
  border: 1px solid rgba(58, 53, 61, .32);
  border-radius: 999px;
  background: rgba(255, 255, 255, .4);
  color: var(--ink);
  font: 500 17px/1 var(--font-serif);
  letter-spacing: .03em;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color .2s ease, scale .2s var(--ease-out);
}
.btn:active { scale: .98; }

/* endless animations rest while their screen is out of view (main.js) or still under the closed envelope: a phone would
   otherwise keep repainting the foil text and moving the sparkles behind everything */
.is-off *, .is-off ::before, .is-off ::after, .loader:not(.is-opening) ~ .site .hero * { animation-play-state: paused !important; }

/* fade-up when a section scrolls in */
.js .reveal { opacity: 0; translate: 0 18px; transition: opacity .9s ease, translate 1s var(--ease-out); }
.js .reveal.in { opacity: 1; translate: 0 0; }

/* ---------- 1 · First screen ---------- */
.hero { position: relative; min-height: 100svh; display: flex; flex-direction: column; overflow: hidden; text-align: center; }
.hero__photo { position: relative; flex: 1 1 0; min-height: 56svh; margin: 0; }

/* A photo with a torn edge (first screen and finale; Solia's pick, 12 Oct): a print on cotton paper lying on the page.
   .ph-clip holds the photo and the tear cuts it (edge-deckle.webp, a mask); .ph-edge lies over it with the white fibres of the
   tear and a soft shadow (edge-deckle-edge.webp). Both made by tools/edge-prep.mjs. The finale photo is the same box upside down. */
.ph-clip {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  -webkit-mask: linear-gradient(#000 0 0) top / 100% calc(100% - var(--tear) + 2px) no-repeat, url("assets/img/edge-deckle.webp") bottom / 100% var(--tear) no-repeat;
  mask: linear-gradient(#000 0 0) top / 100% calc(100% - var(--tear) + 2px) no-repeat, url("assets/img/edge-deckle.webp") bottom / 100% var(--tear) no-repeat;
}
.ph-edge { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; height: var(--tear); pointer-events: none; background: url("assets/img/edge-deckle-edge.webp") bottom / 100% var(--tear) no-repeat; }
/* the photo moves: a focus pull as the doors open, then a barely-there drift.
   One animation per box: the focus pull (inner) ends at scale(1.04) and stays, the drift (outer) goes on from there, together
   scale(1.04) → scale(1.1) translate(-1%, -.6%). On one box the finished focus pull kept the drift off the GPU.
   Absolute sizing, not height:100%: percentage heights inside a flex item do not resolve the same in every engine (Safari). */
.hero__media, .hero__focus { position: absolute; inset: 0; transform-origin: 50% 40%; }
.hero__media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 40%; } /* both faces and the tower */
.is-open .hero__focus, .loader.is-opening ~ .site .hero__focus { animation: focus 2.4s var(--ease-out) calc(var(--pre, 0s) + .25s) both; }
.is-open .hero__media, .loader.is-opening ~ .site .hero__media { animation: drift 28s ease-in-out calc(var(--pre, 0s) + 2.65s) infinite alternate; }
@keyframes focus { 0% { opacity: 0; transform: scale(1.1); filter: blur(8px); } 22% { opacity: 1; } 100% { opacity: 1; transform: scale(1.04); filter: none; } }
@keyframes drift { from { transform: none; } to { transform: scale(1.0577) translate(-1.04%, -.624%); } } /* × the inner 1.04 */
/* the bottom space holds «гортайте» (it comes up once the date is open) */
.hero__text { position: relative; z-index: 2; display: grid; justify-items: center; padding: 6px 20px calc(env(safe-area-inset-bottom) + 62px); }
.hero__text .kicker { margin-bottom: 12px; }

.scroll-cue {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(env(safe-area-inset-bottom) + 12px);
  z-index: 3;
  margin: 0;
  display: grid;
  justify-items: center;
  gap: 6px;
  font: italic 400 14px/1 var(--font-serif);
  color: var(--ink-2);
}
.scroll-cue::after {
  content: "";
  width: 1px;
  height: 22px;
  background: linear-gradient(var(--ink-2), transparent);
  transform-origin: top;
  animation: cue 2.6s var(--ease-out) infinite;
}
@keyframes cue { 0% { transform: scaleY(0); opacity: 1; } 60% { transform: scaleY(1); opacity: 1; } 100% { transform: scaleY(1); opacity: 0; } }

/* the photo's own lights (Yurii's pick, 12 Oct; main.js lights() puts them on the photo's cover rect, so they move with it):
   the café's bulbs breathe like candlelight, the far Eiffel tower sparkles */
.glints { position: absolute; pointer-events: none; opacity: 0; transition: opacity 1.6s ease 1.2s; }
.is-open .glints { opacity: 1; }
.glint { position: absolute; width: var(--s); height: var(--s); translate: -50% -50%; border-radius: 50%; }
.glint--bulb {
  background: radial-gradient(circle, rgba(255, 242, 210, 1) 0, rgba(255, 212, 145, .62) 20%, rgba(255, 194, 116, .24) 44%, rgba(255, 190, 110, 0) 70%);
  mix-blend-mode: screen;
  animation: breathe var(--d) ease-in-out var(--delay) infinite alternate;
}
@keyframes breathe { from { opacity: .45; transform: scale(.84); } to { opacity: 1; transform: scale(1.12); } }
.glint--spark {
  /* warm gold, not white: the café photo's sky is pale, so a white spark would vanish against it */
  background: radial-gradient(circle, #FFF8E6 0 16%, rgba(255, 210, 128, .95) 34%, rgba(240, 180, 90, .4) 54%, rgba(240, 180, 90, 0) 74%);
  opacity: 0;
  animation: spark var(--d) ease-in-out var(--delay) infinite;
}
@keyframes spark { 0%, 100% { opacity: 0; transform: scale(.3); } 7% { opacity: 1; transform: scale(1); } 16% { opacity: 0; transform: scale(.4); } }

/* reveal after the envelope opens */
.js .hero__text > *, .js .scroll-cue { opacity: 0; transition: opacity 1.1s ease, translate 1s var(--ease-out); }
.js .hero__text > * { translate: 0 14px; }
.is-open .hero__text > *, .loader.is-opening ~ .site .hero__text > * { opacity: 1; translate: 0 0; }
.hero__text > :nth-child(1) { transition-delay: calc(var(--pre, 0s) + 1.05s); }
.hero__text > :nth-child(2) { transition-delay: calc(var(--pre, 0s) + 1.25s); }
.hero__text > :nth-child(3) { transition-delay: calc(var(--pre, 0s) + 1.45s); }
.is-open .scroll-cue { opacity: 1; transition-delay: 2.2s; }

/* ---------- 2 · Letter ---------- */
.letter__greet { margin: 0 0 22px; font: italic 400 30px/1.25 var(--font-serif); }
.letter .letter__lead { margin: -6px 0 18px; font: 400 40px/1.2 var(--font-script); }
.letter p { font-size: 19px; line-height: 1.65; }
.sign { margin-top: 24px; font-style: italic; color: var(--ink-2); }
.sign b { display: block; margin-top: 4px; font: 400 42px/1.15 var(--font-script); color: var(--ink); }
/* the letter is a thick cotton card with a scalloped edge and a raised border (frame.webp, 9-slice).
   No "fill": the middle stays empty and shows the page paper, which frame.webp was color-matched to. */
.letter__card { margin: 0 -18px; padding: 8px 2px 2px; border: 34px solid transparent; border-image: url("assets/img/frame.webp") 118 / 34px round; }
/* the frame (282 KB) loads from the seal tap on, so on a slow phone connection it does not hold up the closed envelope */
.js .loader:not(.is-opening) ~ .site .letter__card { border-image: none; }

/* ---------- 3 · Date ---------- */
.cal { max-width: 320px; margin: 0 auto; }
.cal__month { margin: 0 0 14px; font: italic 400 22px/1 var(--font-serif); }
.cal__grid { display: grid; grid-template-columns: repeat(7, 1fr); row-gap: 4px; font-variant-numeric: lining-nums tabular-nums; }
.cal__dow { padding-bottom: 8px; font-size: 12px; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2); }
.cal__d { position: relative; display: grid; place-items: center; height: 40px; font-size: 18px; }
.cal__d--x { font: 600 28px/1 var(--font-serif); }
.cal__d--x .foil { position: relative; z-index: 1; }
/* the wedding day sits in a pressed heart well, like the opened hearts on the first screen; set a little low, so the number sits
   between the lobes and the tip */
.cal__heart { position: absolute; left: 50%; top: 50%; width: 54px; height: 49px; translate: -50% -44%; } /* painted by scratch.js */
.cal__day-name { margin: 18px 0 0; font-style: italic; font-size: 19px; }
.count { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; margin: 34px auto 30px; max-width: 360px; }
.count__n { display: inline-block; font: 500 44px/1 var(--font-serif); font-variant-numeric: lining-nums tabular-nums; }
.count__l { display: block; margin-top: 2px; font-size: 12px; letter-spacing: .16em; text-transform: uppercase; color: var(--ink-2); }
.count__done { margin: 30px 0; font: italic 400 26px/1.2 var(--font-serif); }

/* ---------- 4 · Program ---------- */
.prog { list-style: none; margin: 0 auto; padding: 0; max-width: 360px; display: grid; gap: 14px; text-align: left; }
.prog li { display: grid; grid-template-columns: 78px 1fr; align-items: center; gap: 18px; }
.prog img { width: 78px; height: 78px; object-fit: contain; }
.prog time { display: block; font: 600 20px/1.1 var(--font-serif); letter-spacing: .06em; font-variant-numeric: lining-nums; }
.prog b { display: block; font-weight: 500; font-size: 20px; line-height: 1.25; }
.prog small { display: block; margin-top: 2px; font: italic 400 16px/1.35 var(--font-serif); color: var(--ink-2); }

/* ---------- 5 · Places ---------- */
.tabs { display: inline-flex; gap: 4px; padding: 4px; border-radius: 999px; background: rgba(58, 53, 61, .05); box-shadow: inset 0 0 0 1px rgba(58, 53, 61, .1); }
.tabs button {
  min-height: 42px;
  padding: 0 22px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--ink-2);
  font: 500 17px/1 var(--font-serif);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color .25s ease, color .25s ease;
}
.tabs button[aria-selected="true"] { background: var(--ink); color: var(--paper); }
.place { margin-top: 24px; }
.place__name { margin: 0 0 4px; font: 500 24px/1.2 var(--font-serif); }
.place__addr { margin: 0; font-style: italic; }
.place__when { margin: 4px 0 0; font-size: 15px; letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2); }
.map { position: relative; aspect-ratio: 4 / 3; margin: 20px 0 18px; border-radius: 18px; overflow: hidden; background: var(--paper-2); box-shadow: 0 0 0 1px rgba(58, 53, 61, .12), 0 18px 34px -24px rgba(60, 45, 30, .5); }
/* ink drawing of the chosen place: both stacked, the tab picks one (main.js sets aria-labelledby) */
.venue { position: relative; aspect-ratio: 16 / 10; margin: 4px -14px 8px; }
.venue img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; transition: opacity .45s ease; }
.venue img:first-child { padding: 4% 0 1%; }
#place[aria-labelledby="tab-church"] .venue img + img, #place[aria-labelledby="tab-manor"] .venue img:first-child { opacity: 0; }
.map iframe { display: block; width: 100%; height: 100%; border: 0; filter: sepia(.32) saturate(.85) contrast(.96); }

/* ---------- 6 · Coach ---------- */
.rides { list-style: none; margin: 0 auto 18px; padding: 0; max-width: 340px; text-align: left; }
.rides li { display: grid; grid-template-columns: 74px 1fr; gap: 14px; align-items: baseline; padding: 14px 0; border-top: 1px solid var(--line); }
.rides li:last-child { border-bottom: 1px solid var(--line); }
.rides time { font: 600 22px/1 var(--font-serif); font-variant-numeric: lining-nums; }
.rides b { display: block; font-weight: 500; font-size: 20px; }
.rides small { display: block; font: italic 400 16px/1.35 var(--font-serif); color: var(--ink-2); }
.sketch { margin: -4px -26px 14px; }
.sketch img { display: block; width: 100%; height: auto; }

/* ---------- 8 · Dress code ---------- */
.swatches { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 0 0 22px; }
.swatches figure { margin: 0; }
.swatches img { display: block; width: 100%; height: auto; filter: drop-shadow(0 6px 8px rgba(60, 45, 30, .18)); }
.swatches figcaption { margin-top: 8px; font: italic 400 15px/1.2 var(--font-serif); color: var(--ink-2); }

/* ---------- 10 · FAQ ---------- */
.faq { text-align: left; border-top: 1px solid var(--line); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  position: relative;
  padding: 16px 36px 16px 0;
  list-style: none;
  font: 500 19px/1.35 var(--font-serif);
  cursor: pointer;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; position: absolute; right: 4px; top: 50%; translate: 0 -50%; font: 300 26px/1 var(--font-serif); color: var(--ink-2); transition: rotate .3s var(--ease-out); }
.faq details[open] summary::after { rotate: 45deg; }
.faq details p { margin: 0 0 16px; color: var(--ink); }

/* ---------- 11 · RSVP ---------- */
.card {
  margin-top: 22px;
  padding: 24px 16px;
  border-radius: 24px;
  text-align: left;
  background: rgba(255, 255, 255, .5);
  box-shadow: 0 0 0 1px rgba(58, 53, 61, .1), 0 24px 44px -30px rgba(60, 45, 30, .45);
}
.field { margin: 0 0 24px; padding: 0; border: 0; min-width: 0; }
.field legend, .lbl { display: block; padding: 0; margin: 0 0 10px; font: 500 18px/1.3 var(--font-serif); }
.lbl small, .field legend small { display: block; font: italic 400 15px/1.3 var(--font-serif); color: var(--ink-2); }
.lbl--sm { margin-top: 14px; font-size: 16px; }
.lbl--gap { margin-top: 18px; }
#fPeople { margin-bottom: 0; } /* the card stays even before a choice; what opens after it brings its own top space */
.sorry { margin: 20px 0 22px; text-align: center; font: italic 400 19px/1.45 var(--font-serif); color: var(--ink-2); }
.hint { margin: 6px 0 0; font: italic 400 15px/1.35 var(--font-serif); color: var(--ink-2); }
.row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.row .lbl { margin: 0; }
.input {
  width: 100%;
  min-height: 48px;
  padding: 10px 14px;
  border: 1px solid rgba(58, 53, 61, .25);
  border-radius: 14px;
  background: rgba(255, 255, 255, .75);
  color: var(--ink);
  font: 400 18px/1.4 var(--font-serif);
  transition: border-color .2s ease, box-shadow .2s ease;
}
.input::placeholder { color: #8B8390; }
.input:focus { outline: none; border-color: var(--ink); box-shadow: 0 0 0 3px rgba(58, 53, 61, .12); }
textarea.input { resize: vertical; min-height: 96px; }
.input[aria-invalid="true"] { border-color: var(--error); }


/* pill radios: the two answers share the row evenly (Solia, 12 Oct: «відповіді порівняла»). The longest, «Потрібен автобус»,
   stays on one line down to a 360px phone; narrower ones wrap it inside the pill. */
.seg { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.seg input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.seg label {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 6px 8px;
  border: 1px solid rgba(58, 53, 61, .25);
  border-radius: 999px;
  background: rgba(255, 255, 255, .6);
  font: 500 16px/1.15 var(--font-serif);
  text-align: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color .2s ease, color .2s ease, border-color .2s ease;
}
.seg input:checked + label { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.seg input:focus-visible + label { outline: 2px solid var(--ink); outline-offset: 2px; }

.stepper { display: inline-flex; align-items: center; gap: 4px; }
.stepper button {
  width: 44px;
  height: 44px;
  border: 1px solid rgba(58, 53, 61, .25);
  border-radius: 50%;
  background: rgba(255, 255, 255, .6);
  color: var(--ink);
  font: 400 24px/1 var(--font-serif);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.stepper button:disabled { opacity: .4; cursor: default; }
.stepper output { min-width: 2.2ch; text-align: center; font: 600 22px/1 var(--font-serif); font-variant-numeric: lining-nums tabular-nums; }

.generic .row { margin-top: 16px; }
.extras { margin-top: 24px; }
.extras[hidden] { display: none; }
.err { margin: 8px 0 0; font-size: 16px; color: var(--error); }
.err--form { margin: 0 0 14px; }
.btn--submit { width: 100%; min-height: 56px; } /* its look: the stationery rule with .pill below */
.btn--submit[aria-busy="true"] { opacity: .7; pointer-events: none; }

.thanks { text-align: center; padding: 12px 0; }
.thanks:focus { outline: none; }
.thanks__title { margin: 0 0 6px; font: 400 46px/1.1 var(--font-script); }
.link { padding: 8px; border: 0; background: none; color: var(--ink); font: italic 400 17px/1 var(--font-serif); text-decoration: underline; text-underline-offset: 4px; cursor: pointer; }

/* ---------- 12 · Contacts ---------- */
.contacts { list-style: none; margin: 0 auto; padding: 0; max-width: 320px; }
.contacts li { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 14px 0; border-bottom: 1px solid var(--line); }
.contacts li:first-child { border-top: 1px solid var(--line); }
.contacts b { font-weight: 500; font-size: 20px; }
.contacts a { color: var(--ink); }
.contacts__soon { font-style: italic; color: var(--ink-2); }

/* ---------- 13 · Finale ---------- */
.finale { display: flex; flex-direction: column; text-align: center; padding-bottom: calc(env(safe-area-inset-bottom) + 56px); }
.finale__photo { position: relative; margin: 20px 0 0; aspect-ratio: 4 / 5; }
/* its edge is at the top: the photo box and its edge piece are turned upside down, the photo itself turned back */
.finale__photo .ph-clip, .finale__photo .ph-clip img { scale: 1 -1; }
.finale__photo .ph-edge { top: 0; bottom: auto; scale: 1 -1; }
.finale__photo img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 100%; } /* bottom-aligned: the feet stay in */
.finale__text { padding: 26px 20px 0; }
.finale__title { margin: 0 0 10px; font: italic 400 34px/1.2 var(--font-serif); }
.names--sm { font-size: clamp(40px, 11vw, 48px); }
.credit { margin: 40px 0 0; font: italic 400 13px/1.4 var(--font-serif); color: var(--ink-2); }

/* ---------- Floating: sound + RSVP pill ---------- */
.sound {
  position: fixed;
  top: calc(env(safe-area-inset-top) + 12px);
  right: max(12px, calc(50vw - var(--col) / 2 + 12px));
  z-index: 50;
  width: 44px;
  height: 44px;
  padding: 0;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: rgba(251, 248, 243, .78);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: 0 0 0 1px rgba(58, 53, 61, .12), 0 8px 18px -10px rgba(60, 45, 30, .45);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.sound[hidden] { display: none; }
.sound__bars { display: flex; align-items: flex-end; gap: 2.5px; height: 14px; }
.sound__bars i { width: 2px; height: 100%; border-radius: 1px; background: var(--ink); transform-origin: bottom; animation: bars 1.1s ease-in-out infinite alternate; }
.sound__bars i:nth-child(2) { animation-delay: -.4s; }
.sound__bars i:nth-child(3) { animation-delay: -.8s; }
.sound__bars i:nth-child(4) { animation-delay: -.2s; }
.sound[aria-pressed="false"] .sound__bars i { animation: none; transform: scaleY(.18); }
@keyframes bars { from { transform: scaleY(.25); } to { transform: scaleY(1); } }

/* Stationery, not app buttons: the RSVP pill and the form's send button are an ivory card with a champagne double rule and
   two stones from the section jewels */
.pill, .btn--submit {
  border: 1px solid rgba(200, 167, 102, .75);
  border-radius: 999px;
  background: var(--paper) url("assets/img/paper.webp") center / 512px;
  background-blend-mode: multiply;
  color: var(--ink);
  font: 600 12.5px/1 var(--font-serif);
  letter-spacing: .24em;
  text-transform: uppercase;
  box-shadow: inset 0 0 0 3px var(--paper), inset 0 0 0 4px rgba(200, 167, 102, .4), 0 12px 26px -14px rgba(60, 45, 30, .55);
}
.pill {
  position: fixed;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom) + 14px);
  z-index: 50;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-height: 48px;
  padding: 0 24px;
  text-decoration: none;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
  translate: -50% calc(100% + 30px);
  transition: translate .55s var(--ease-out), scale .2s var(--ease-out);
}
.btn--submit { gap: 14px; padding: 0 16px; font-size: 13px; letter-spacing: .18em; white-space: nowrap; } /* one line from a 360px phone */
.pill:active { scale: .98; }
.pill.is-shown { translate: -50% 0; }
.pill::before, .pill::after, .btn--submit::before, .btn--submit::after { content: ""; flex: none; width: 10px; height: 10px; margin-top: -1px; rotate: 30deg; background: url("assets/img/rhinestone.webp") center / contain; filter: drop-shadow(0 .6px .7px rgba(40, 42, 50, .4)); }
/* the letter spacing trails after the last letter: pull it back so the text sits centred between the stones */
.pill::after { margin-left: -.24em; }
.btn--submit::after { margin-left: -.18em; }

/* ---------- Scratch-off date (scratch.js): silver foil hearts in pressed wells over 11 · 06 · 2027 ---------- */
/* the page stays on the first screen until the date is open; «гортайте» comes up with it */
html.scr-lock, html.scr-lock body { overflow: hidden; overscroll-behavior: none; }
html.scr-lock body { position: fixed; left: 0; right: 0; top: 0; touch-action: none; }
[data-scratch]:not(.date-revealed) .scroll-cue { opacity: 0; }
.date-revealed .scroll-cue { transition-delay: 1s; }

/* the bottom margin keeps «гортайте» clear of the labels under the hearts */
[data-scratch] #hero .hero__date { position: relative; margin: 14px 0 26px; font-size: 26px; letter-spacing: .02em; }
.scr-row { display: inline-flex; gap: 10px; }
/* layers inside a heart: well 0 · number 1 · foil 2 · light 3 */
.scr-part { position: relative; display: inline-grid; place-items: center; width: 68px; height: 61px; padding-bottom: 7px; isolation: isolate; }
.scr-part::after {
  content: attr(data-l);
  position: absolute;
  left: 50%;
  top: calc(100% + 3px);
  translate: -50% 0;
  font: italic 400 13px/1 var(--font-serif);
  letter-spacing: .06em;
  color: var(--ink-2);
}
/* the number: the page's ink; a silver glint runs over it as its heart opens */
.scr-num {
  position: relative;
  z-index: 1;
  font-size: 26px;
  font-weight: 600;
  background: linear-gradient(100deg, var(--ink) 40%, var(--silver-2) 50%, var(--ink) 60%) 100% 0 / 300% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  padding: 0 .04em .1em;
}
.scr-part:nth-child(3) .scr-num { font-size: 20px; } /* the year, to fit its heart */
.scr-part.is-open .scr-num { animation: scr-glint 1.5s var(--ease-out) .1s both; }
@keyframes scr-glint { from { background-position: 100% 0; } to { background-position: 0 0; } }

.scr-well { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 0; }
.scr, .scr-light { position: absolute; inset: 0; width: 100%; height: 100%; z-index: 2; }
.scr {
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
  cursor: grab;
  filter: drop-shadow(0 1.5px 1.2px rgba(70, 48, 16, .3)); /* the foil has a thickness: a hairline shadow on the well, inside every scratch too */
}
.scr:active { cursor: grabbing; }
.scr:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; border-radius: 14px; }
.scr-light { z-index: 3; pointer-events: none; mix-blend-mode: screen; }
.scr-fx { position: fixed; left: 0; width: 100%; z-index: 40; pointer-events: none; } /* top and height: scratch.js (a band around the hearts) */

/* the date elsewhere on the page was never covered: it catches one champagne glint the first time it comes into view */
.scr-echo {
  background: linear-gradient(100deg, var(--c) 42%, #C8A766 50%, var(--c) 58%) 100% 0 / 300% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: scr-echo 1.8s ease-in-out .3s both;
}
@keyframes scr-echo { from { background-position: 100% 0; } to { background-position: 0 0; } }

/* ---------- Tablet and desktop (Yurii's pick B, 12 Oct; plan: docs/superpowers/plans/2026-10-12-responsive-tablet-desktop-plan.md) ---------- */
/* Phones (< 640px) never reach these rules. From 640px the column grows from 440 to 600px. On landscape screens (laptops, an
   iPad on its side, a phone turned sideways) the gatefold opens into a two-page spread: the first-screen photo is the left page,
   the column the right page. Same DOM and components: only sizes and where the photo box sits change. */
@media (min-width: 640px) {
  :root { --col: 600px; --tear: 48px; }

  /* the envelope: the whole gatefold card at its real 2:3 shape (doors.webp), as tall as the screen; the seal sized to the card */
  .env { width: min(100%, max(440px, 66.667svh)); container-type: inline-size; --seal-r: min(17cqw, 96px); }
  .seal { width: min(34cqw, 192px); }
  /* the card is narrower than the page: paper around it hides the page until the doors swing open */
  .loader::before { content: ""; position: absolute; inset: 0; background: var(--backdrop) url("assets/img/paper.webp") center / 512px; background-blend-mode: multiply; }
  .loader.is-opening::before { opacity: 0; transition: opacity 1.3s ease-in-out calc(var(--door-at) + .5s); }
  /* on a big screen the swung doors would hang over the page as slivers: they dissolve as they finish */
  .is-opening :is(.door__face, .door__back) { animation: door-out 1s ease-in calc(var(--door-at) + 1.1s) both; }

  .names { font-size: 64px; }
  .names--sm { font-size: 56px; }
  .scr-part { width: 78px; height: 70px; } /* scratch.js paints the hearts at this size */
  .scr-num { font-size: 30px; }
  .scr-part:nth-child(3) .scr-num { font-size: 23px; }

  /* blocks that would only get wider, not better */
  .prog { max-width: 400px; }
  .swatches { max-width: 460px; margin-inline: auto; }
  .card { max-width: 500px; margin-inline: auto; }
}
@keyframes door-out { to { opacity: 0; } }
.hero__alt { display: none; } /* the street photo on the left page: landscape only (below) */

@media (min-width: 640px) and (min-aspect-ratio: 5/4) {
  :root { --pane: min(50vw, 66.667vh); } /* the left page: the 2:3 photo as tall as the screen, at most half of it */

  /* the right page: paper from under the photo's torn edge to the screen edge, the column centred on it */
  .site { max-width: none; margin: 0 0 0 calc(var(--pane) - var(--tear) + 2px); box-shadow: none; }
  .site > * { max-width: var(--col); margin-inline: auto; }
  .hero { justify-content: center; }

  /* the left page: the hero photo box is pinned to the screen; its torn edge (the same tear turned 90°) faces the right page */
  .hero__photo { position: fixed; left: 0; top: 0; bottom: 0; width: var(--pane); min-height: 0; }
  .hero__photo .ph-clip {
    -webkit-mask: linear-gradient(#000 0 0) left / calc(100% - var(--tear) + 2px) 100% no-repeat, url("assets/img/edge-deckle-v.webp") right / var(--tear) 100% no-repeat;
    mask: linear-gradient(#000 0 0) left / calc(100% - var(--tear) + 2px) 100% no-repeat, url("assets/img/edge-deckle-v.webp") right / var(--tear) 100% no-repeat;
  }
  .hero__photo .ph-edge { left: auto; top: 0; width: var(--tear); height: auto; background: url("assets/img/edge-deckle-edge-v.webp") right / var(--tear) 100% no-repeat; }
  /* the photo stays on screen the whole time, so its drift and lights go on when the names scroll away */
  #hero.is-off .hero__photo * { animation-play-state: running !important; }

  /* the last spread mirrors the first: at the finale the left page fades to the street photo (main.js finale()), and the right
     page holds «Чекаємо на вас!», the names and the date, so the column's own copy of the photo steps aside */
  .hero__alt { display: block; position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% 100%; opacity: 0; transition: opacity 1.6s ease-in-out; }
  .is-finale .hero__alt { opacity: 1; }
  .finale__photo { display: none; }
  .finale { min-height: 100svh; justify-content: center; }

  .hero .names { font-size: clamp(60px, 5.4vw, 80px); } /* 60px fits the narrow right page of a 1024px iPad */
  .scr-part { width: 86px; height: 77px; }
  .scr-num { font-size: 33px; }
  .scr-part:nth-child(3) .scr-num { font-size: 25px; }

  /* the floating buttons belong to the right page */
  .pill { left: calc(50% + var(--pane) / 2); }
  .sound { right: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition-duration: .01ms !important; transition-delay: 0s !important; }
}
