/* ══════════════════════════════════════════════════════════════════════
   VITALI & ANNA — a wedding editorial that moves
   1 Tokens · 2 Base · 3 Primitives · 4 Entrance · 5 Sections · 6 Responsive
   ══════════════════════════════════════════════════════════════════════ */

/* ── 1 · Tokens ─────────────────────────────────────────────────────── */
:root {
  --ink:        #0B0B0B;
  --ink-soft:   #2E2E2C;
  --paper:      #F1EDE6;   /* warm off-white, the fingerprint's paper */
  --paper-hi:   #F7F4EF;
  --grey:       #9A968E;
  --grey-dim:   #6E6B65;
  /* The same quiet grey, lifted just far enough to clear WCAG AA against
     the black chapters — #6E6B65 on #0B0B0B is 3.7:1, and these labels are
     12px, which needs 4.5:1. Used only where the ground is ink. */
  --grey-onink: #8F8B83;
  --stone:      #D8D2C6;

  /* The one colour in the invitation. Oxblood on paper; the same red
     lifted a step so it still reads as red against black. */
  --red:        #6E1B23;
  --red-lit:    #9E2E36;

  /* Bodoni Moda has no Cyrillic. Playfair Display does and is also a
     high-contrast Didone, so Russian falls through to it per glyph
     instead of dropping to a system serif. */
  --serif: 'Bodoni Moda', 'Playfair Display', 'Didot', 'Bodoni MT', 'Times New Roman', serif;
  --sans:  'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Arial, sans-serif;

  --gutter: clamp(1.25rem, 5vw, 5.5rem);
  --bay:    clamp(6rem, 16vh, 13rem);

  /* One tracking scale. Uppercase labels use --track; the two name
     lockups use --track-wide; display-sized uppercase uses --track-tight. */
  --track:       .3em;
  --track-wide:  .42em;
  --track-tight: .18em;

  /* One type scale for the whole invitation. Every section pulls from
     these five steps, so a heading in one chapter can never quietly
     out-shout the same rank in another. Minima keep mobile readable;
     maxima keep desktop from turning absurd. */
  --fs-display: clamp(3.4rem, 13vw, 11.5rem);   /* Vitali · Anna · LOVE   */
  --fs-chapter: clamp(2.1rem, 6.2vw, 5.4rem);   /* chapter titles         */
  --fs-time:    clamp(1.9rem, 4.4vw, 3.6rem);   /* itinerary hours        */
  --fs-lead:    clamp(1.02rem, 1.1vw + .78rem, 1.6rem); /* intimate copy  */
  --fs-body:    clamp(.95rem, .28vw + .89rem, 1.05rem); /* running text   */
  --fs-meta:    clamp(.68rem, .2vw + .63rem, .78rem);   /* tracked labels */

  --ease:      cubic-bezier(.16, 1, .3, 1);
  --ease-soft: cubic-bezier(.33, .1, .2, 1);
}

/* ── 2 · Base ───────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; background: var(--ink); }

body {
  margin: 0;
  overflow-x: clip;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--sans);
  font-weight: 300;
  font-size: clamp(.95rem, .28vw + .89rem, 1.05rem);
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
}

/* While the entrance is up nothing scrolls behind it. */
.is-entering body,
html.is-entering { overflow: hidden; height: 100%; }

img, video, canvas, svg { display: block; max-width: 100%; }
h1, h2, h3, p, figure, ol, blockquote { margin: 0; padding: 0; }
ol { list-style: none; }

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

.skip-link {
  position: absolute; left: 50%; top: -140px; transform: translateX(-50%);
  z-index: 400; background: var(--paper); color: var(--ink);
  padding: .8rem 1.5rem; text-decoration: none;
  font-family: var(--sans); font-size: .68rem;
  letter-spacing: var(--track); text-transform: uppercase;
  transition: top .25s var(--ease);
}
.skip-link:focus { top: 1rem; }

:focus-visible { outline: 1px solid currentColor; outline-offset: 6px; }

/* ── 3 · Primitives ─────────────────────────────────────────────────── */
/* Running copy gets a better rag where the browser can do it: no stranded
   last word, no line left much shorter than the one above. Ignored
   entirely by browsers that do not have it, which costs nothing. */
.welcome__line, .welcome__sub, .invite__wish, .invite__sub,
.dress__detail, .place__address, .love__line, .final__line {
  text-wrap: pretty;
}
.label {
  font-family: var(--sans);
  font-weight: 400;
  font-size: var(--fs-meta);
  letter-spacing: var(--track);
  text-transform: uppercase;
  line-height: 1.9;
}
.label--rule { display: inline-block; padding-bottom: .55rem; border-bottom: 1px solid currentColor; }

.slash { display: inline-block; margin: 0 .8em; opacity: .45; }

.textlink {
  display: inline-block;
  position: relative;
  margin-top: clamp(1.5rem, 3vw, 2.25rem);
  padding: .9rem 0 .75rem;
  font-family: var(--sans);
  font-size: var(--fs-meta);
  letter-spacing: var(--track); text-transform: uppercase;
  color: inherit; text-decoration: none;
}
.textlink::after {
  content: ''; position: absolute; left: 0; bottom: .45rem;
  width: 100%; height: 1px; background: currentColor;
  transform-origin: right; transition: transform .55s var(--ease);
}
.textlink:hover::after, .textlink:focus-visible::after { transform: scaleX(0); }

/* Sections carry their own ground; the story moves between black and paper. */
.on-paper { background: var(--paper); color: var(--ink); }
.on-ink   { background: var(--ink);   color: var(--paper); }

/* Reveal states — only while JS is driving. */
.js .rise { opacity: 0; }
.js.motion-ready .rise, .no-motion .rise { opacity: 1; }

/* ── 4 · Entrance ───────────────────────────────────────────────────── */
.entrance {
  position: fixed; inset: 0; z-index: 200;
  display: none;                       /* only exists when JS runs */
  place-items: center;
  background: var(--paper);
  color: var(--ink);
  padding: var(--gutter);
  clip-path: inset(0 0 0 0);
  will-change: clip-path;
}
.js .entrance { display: grid; }
.entrance.is-lifting {
  clip-path: inset(100% 0 0 0);
  transition: clip-path .95s cubic-bezier(.7, 0, .25, 1);
}
.entrance.is-gone-soft { opacity: 0; transition: opacity .42s var(--ease); }
.entrance.is-gone { display: none; }

.entrance__fx {
  position: fixed; inset: 0; z-index: 210;
  pointer-events: none;
}
.js .has-entered .entrance__fx { pointer-events: none; }

.entrance__inner { display: grid; justify-items: center; text-align: center; }

.entrance__names {
  color: var(--ink-soft);
  letter-spacing: var(--track-wide);
  margin-bottom: clamp(2.5rem, 8vh, 5rem);
}

/* The fingerprint is the button. No pill, no border, no chrome. */
.fp {
  position: relative;
  display: block;
  margin: 0; padding: 0;
  border: 0; background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.fp__img {
  width: clamp(9rem, 26vw, 13.5rem);
  height: auto;
  animation: fp-breath 4s var(--ease-soft) infinite;
  transform-origin: center;
}
@keyframes fp-breath {
  0%, 100% { transform: scale(1); }
  46%      { transform: scale(1.015); }
}

/* The press: a compression, not a bounce. */
.fp.is-pressed .fp__img { animation: fp-press .42s var(--ease) forwards; }
@keyframes fp-press {
  0%   { transform: scale(1); }
  32%  { transform: scale(.96); }
  100% { transform: scale(1.03); }
}

/* Ink ripple from the exact point touched. */
.fp__ripple {
  position: absolute;
  left: var(--rx, 50%); top: var(--ry, 50%);
  width: 12px; height: 12px; margin: -6px 0 0 -6px;
  border-radius: 50%;
  border: 1px solid rgba(11, 11, 11, .5);
  opacity: 0; pointer-events: none;
}
.fp.is-pressed .fp__ripple { animation: fp-ripple .85s var(--ease) forwards; }
@keyframes fp-ripple {
  0%   { opacity: .55; transform: scale(.3); }
  100% { opacity: 0;   transform: scale(22); }
}

/* Idle: a slow pulse outward, almost subliminal. */
.fp::after {
  content: ''; position: absolute; inset: -6%;
  border-radius: 50%;
  border: 1px solid rgba(11, 11, 11, .16);
  opacity: 0;
  animation: fp-idle 5.5s var(--ease-soft) infinite;
  pointer-events: none;
}
.fp.is-pressed::after { animation: none; opacity: 0; }
@keyframes fp-idle {
  0%   { opacity: 0;   transform: scale(.92); }
  22%  { opacity: .5; }
  70%, 100% { opacity: 0; transform: scale(1.22); }
}

/* The print leaves as its ridges are carried off. */
.fp.is-dissolving .fp__img {
  opacity: 0;
  transition: opacity .5s cubic-bezier(.5, 0, .75, 0);
}

.entrance__prompt {
  margin-top: clamp(2.25rem, 7vh, 4rem);
  color: var(--ink);
  letter-spacing: var(--track-wide);
}
.entrance__date {
  margin-top: .9rem;
  color: var(--grey-dim);
  font-size: .66rem;
  letter-spacing: var(--track);
}
.entrance.is-lifting .entrance__prompt,
.entrance.is-lifting .entrance__date,
.entrance.is-lifting .entrance__names { opacity: 0; transition: opacity .3s; }

/* ── Sound ──────────────────────────────────────────────────────────── */
.sound {
  position: fixed; z-index: 150;
  right: clamp(1rem, 2.5vw, 2rem);
  bottom: clamp(1rem, 2.5vw, 2rem);
  display: inline-flex; align-items: center; gap: .55rem;
  min-height: 44px; min-width: 44px;
  padding: .7rem .85rem;
  background: none; border: 0;
  color: var(--paper);
  font-family: var(--sans);
  font-size: .66rem; letter-spacing: var(--track); text-transform: uppercase;
  cursor: pointer; opacity: 0;
  mix-blend-mode: difference;         /* legible on black and on paper alike */
  transition: opacity .8s var(--ease);
}
.sound.is-shown { opacity: .75; }
.sound.is-shown:hover { opacity: 1; }

.sound__eq { display: flex; align-items: flex-end; gap: 2px; height: 10px; }
.sound__eq i { width: 1px; height: 2px; background: currentColor; }
.sound[aria-pressed="true"] .sound__eq i { animation: eq 1.1s var(--ease-soft) infinite; }
.sound[aria-pressed="true"] .sound__eq i:nth-child(2) { animation-delay: .14s; }
.sound[aria-pressed="true"] .sound__eq i:nth-child(3) { animation-delay: .29s; }
.sound[aria-pressed="true"] .sound__eq i:nth-child(4) { animation-delay: .43s; }
@keyframes eq { 0%, 100% { height: 2px; } 50% { height: 10px; } }

/* ── 5 · Sections ───────────────────────────────────────────────────── */

/* ── 01 Opening film ────────────────────────────────────────────────── */
.film { position: relative; height: 230svh; background: var(--ink); }
@supports not (height: 100svh) { .film { height: 230vh; } }

.film__stage {
  position: sticky; top: 0;
  height: 100svh; height: 100dvh;
  overflow: hidden; background: #000;
}
@supports not (height: 100svh) { .film__stage { height: 100vh; } }

.film__video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  /* 9:16 footage on a 16:9 screen: this holds the bride through the clip. */
  object-position: 50% 48%;
  transform-origin: 50% 45%;
  filter: grayscale(1) contrast(1.06);
  will-change: transform;
}
.film__grade {
  position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg, rgba(0,0,0,.5) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0) 46%, rgba(0,0,0,.72) 100%);
}
.film__vignette {
  position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(115% 85% at 50% 45%, rgba(0,0,0,0) 40%, rgba(0,0,0,.6) 100%);
}
.film__black { position: absolute; inset: 0; background: #000; opacity: 0; pointer-events: none; }

.film__type {
  position: absolute; z-index: 2; inset: auto 0 0 0;
  padding: 0 var(--gutter) clamp(3rem, 11vh, 7rem);
  text-align: center; color: var(--paper);
}
.film__names {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(2.6rem, 8.5vw, 7rem);
  line-height: .98;
  letter-spacing: .01em;
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: center; gap: clamp(.5rem, 2vw, 1.6rem);
  margin-bottom: clamp(1.25rem, 3.5vh, 2.25rem);
}
.film__amp { font-style: italic; font-weight: 400; font-size: .45em; opacity: .8; }
.film__date { font-size: clamp(.72rem, .5vw + .6rem, .92rem); letter-spacing: var(--track); margin-bottom: 1rem; }

/* ── 02 Save the date ───────────────────────────────────────────────── */
/* An editorial spread, not a centred stationery card: SAVE THE DATE turned
   on its side as a typographic spine down the left edge, the date beside it
   as a block of numerals, and the whole weight held in the left half with
   the right side left deliberately open. */
.date {
  position: relative;
  background: var(--paper); color: var(--ink);
  min-height: 100svh;
  padding: clamp(1.75rem, 5vh, 3.5rem) var(--gutter);
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) auto;
  grid-template-areas:
    'spine stamp .'
    'spine stamp meta';
  column-gap: clamp(1rem, 4.5vw, 4.5rem);
  align-items: center;
  justify-content: start;
  overflow: hidden;
}
@supports not (height: 100svh) { .date { min-height: 100vh; } }

/* One continuous line turned ninety degrees — vertical-rl rotates the run,
   it does not stack the letters. app.js sizes it to the height it has. */
.save {
  grid-area: spine;
  align-self: center;
  writing-mode: vertical-rl;
  font-family: var(--serif);
  font-weight: 900;
  /* Bodoni's optical-size axis: at a display setting the hairlines go
     razor-thin, which is right for the numerals and wrong for a spine.
     Pinning it low gives the sturdier cut — the same face, drawn for
     weight rather than for drama. */
  font-variation-settings: 'opsz' 14;
  text-transform: uppercase;
  /* only the pre-script value; the fitter in app.js sets the real one */
  font-size: clamp(1.75rem, 7vh, 5rem);
  line-height: 1;
  /* nearly solid: tracked out, the phrase runs a fifth longer and has to be
     set a fifth smaller to fit the same height — tight is what makes it
     read as a heavy spine rather than a caption on its side */
  letter-spacing: .04em;
  white-space: nowrap;
  color: var(--ink);
  margin: 0;
}
/* Turned the other way: vertical-rl alone runs the line top-to-bottom with
   the glyphs rotated clockwise. Spinning the run 180° about its own centre
   reverses both — it now reads bottom-to-top, counter-clockwise, which is
   the left-hand spine you tilt your head left for. The rotation sits on the
   span rather than the h2 so it survives the entrance tween, which writes
   its own transform on the heading. */
.save > span { display: inline-block; transform: rotate(180deg); }

/* 03 / 13 / 27 at one size on one axis, leaded tight: a block of numerals
   rather than three numbers with air between them. */
.stamp {
  grid-area: stamp;
  align-self: center;
  display: grid;
  justify-items: start;
  font-family: var(--serif);
  font-weight: 700;
  /* bounded by the height, because three rows of it is a tall object */
  font-size: min(46vw, 24.5vh);
  /* tight, but not so tight that the rows collide — at .8 the 1 of 13 sat
     on top of the 2 of 27 */
  line-height: .86;
  letter-spacing: -.055em;
  font-variant-numeric: lining-nums tabular-nums;
}
.stamp__n { display: block; }

/* Secondary, and set at the far corner: the counterweight to all that
   left-hand mass, so the open side is a decision rather than a leftover. */
.date__meta {
  grid-area: meta;
  justify-self: end; align-self: end;
  text-align: right;
  display: grid;
  gap: .1rem;
  /* the sound control is fixed in this corner; stay clear of it */
  margin-bottom: clamp(2.25rem, 6vh, 3.5rem);
}
.date__reading { color: var(--ink); }
.date__where   { color: var(--grey-dim); }

/* ── 03 The names ───────────────────────────────────────────────────── */
/* Vitali across the top, Anna across the bottom, the ampersand its own
   element at the right edge between them, and the note kept small on the
   left. The names are fitted to their column by app.js, so they use the
   width they actually have at every size instead of a guessed one. */
.invite {
  position: relative;
  background: var(--paper); color: var(--ink);
  padding: var(--bay) var(--gutter);
  min-height: 100svh;
  display: grid;
  grid-template-columns: minmax(7rem, 15%) minmax(0, 1fr);
  align-content: center;
  column-gap: clamp(1.25rem, 3vw, 2.5rem);
  overflow-x: clip;
}
@supports not (height: 100svh) { .invite { min-height: 100vh; } }

.invite__text { grid-column: 1; align-self: center; max-width: 22rem; }

.invite__names {
  grid-column: 2;
  display: grid;
  justify-items: start;
  font-family: var(--serif);
  font-weight: 500;
  /* the pre-script value only; fitNames() sets the real one against the
     column, leaving the ampersand its lane */
  font-size: clamp(4rem, 22vw, 18rem);
  line-height: .86;
  letter-spacing: -.025em;
  white-space: nowrap;
  margin: 0;
}
.invite__line { display: block; }
.invite__line--b { margin-top: clamp(.5rem, 3vh, 2rem); }

/* Oversized, and allowed to approach the edge — but the fitter reserves
   this lane, so it never lands on a name. */
.invite__amp {
  position: absolute;
  right: clamp(.25rem, 1.5vw, 2rem);
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(4.5rem, 15vw, 13rem);
  line-height: 1;
  color: var(--ink);
  opacity: .9;
  pointer-events: none;
}

/* ── 04 Hands ───────────────────────────────────────────────────────── */
.hands { position: relative; height: 190svh; background: var(--ink); }
@supports not (height: 100svh) { .hands { height: 190vh; } }

.hands__stage {
  position: sticky; top: 0;
  height: 100svh; overflow: hidden;
  display: grid; place-items: center;
  background: var(--ink);
}
@supports not (height: 100svh) { .hands__stage { height: 100vh; } }

.hands__mask { position: absolute; inset: 0; clip-path: inset(12% 0 12% 0); }
.hands__img {
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 44%;
  filter: grayscale(1) contrast(1.08) brightness(.92);
  transform: scale(1.12);
  will-change: transform;
}
.hands__mark {
  position: relative; z-index: 2;
  display: flex; align-items: center; gap: clamp(.6rem, 2.5vw, 2rem);
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(5rem, 22vw, 18rem);
  line-height: 1;
  color: #fff;
  /* `difference` alone made the V vanish over the pale hand while the A
     stayed bright. A scrim behind the mark (below) evens the ground out so
     both initials read, and the photograph still shows through around them. */
  mix-blend-mode: difference;
}
/* Sits between the photograph and the mark. */
.hands__stage::after {
  content: ''; position: absolute; inset: 0; z-index: 1;
  pointer-events: none;
  background: radial-gradient(60% 42% at 50% 50%, rgba(0,0,0,.72) 0%, rgba(0,0,0,.40) 45%, rgba(0,0,0,0) 78%);
}
.hands__plus { font-size: .3em; font-weight: 400; }

/* ── 05 LOVE ────────────────────────────────────────────────────────── */
/* One typographic column, set left of centre, with the story travelling
   down beside it and stepping right as it goes. The letters are centred on
   a shared axis and set on a line-height tight enough that the four of
   them read as one object rather than four letters in a list. */
.love {
  background: var(--paper); color: var(--ink);
  padding: var(--bay) var(--gutter);
  min-height: 100svh;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: clamp(1.75rem, 6vw, 5.5rem);
  align-content: center;
  /* the column sits toward the left, and the story crosses the page from
     there — the air is on the diagonal rather than in one dead corner */
  justify-content: start;
  padding-left: max(var(--gutter), 7vw);
  /* The column is a tall object, so this chapter buys its height back from
     the top and bottom margins rather than from the letters. */
  padding-block: clamp(3rem, 8vh, 6rem);
  overflow-x: clip;
}
@supports not (height: 100svh) { .love { min-height: 100vh; } }

.love__col {
  display: grid;
  justify-items: center;
  align-content: start;
  row-gap: clamp(1.25rem, 4vh, 2.5rem);
}

.love__word {
  display: grid;
  justify-items: center;
  font-family: var(--serif);
  font-weight: 700;
  /* bounded by height as well as width — it is a column, so it is the
     viewport's height that runs out first on a laptop */
  font-size: clamp(4.25rem, min(17vw, 19vh), 13rem);
  /* the spacing of the word: tight enough to connect, open enough to read */
  line-height: .82;
  letter-spacing: 0;
  margin: 0;
}
/* Every letter on the one axis, so the column has two clean edges even
   though L, O, V and E are four different widths. */
.love__ltr { display: block; text-align: center; }

/* The finishing mark at the foot of the column, and the one thing on this
   page you can touch. */
.heart--love svg { width: clamp(30px, 3.6vw, 52px); opacity: .82; }

/* Stretched against the column and spread through it, so the first line
   starts with the L and the last one lands with the E — the story travels
   the letters rather than sitting in a block beside them. */
.love__lines {
  align-self: stretch;
  display: grid;
  align-content: space-between;
  gap: clamp(1.5rem, 4vh, 2.5rem);
  /* held in from the ends of the column, so the four lines still read as
     one passage rather than four notices spread down a wall */
  padding-block: clamp(1rem, 7vh, 4.5rem);
}
.love__line {
  font-family: var(--serif);
  font-size: var(--fs-lead);
  line-height: 1.5;
  color: var(--ink-soft);
  text-wrap: balance;
  margin: 0;
}
/* The story steps right as it goes down, so the copy travels across the
   page rather than sitting in a block beside the letters. */
.love__line { max-width: 36ch; }
.love__line:nth-child(2) { margin-left: 12%; }
.love__line:nth-child(3) { margin-left: 24%; }
.love__line:nth-child(4) { margin-left: 36%; }
.love__line:nth-child(even) { font-style: italic; }

/* ── 06 The wedding day ─────────────────────────────────────────────── */
/* One page, four moments, one thread drawn through them. The times are
   deliberately modest: this is a schedule, not a poster. */
.day {
  position: relative;
  background: var(--ink); color: var(--paper);
  padding: var(--bay) var(--gutter);
  overflow-x: clip;
}
.day__head { margin-bottom: clamp(3rem, 9vh, 6rem); }
.day__title {
  display: grid;
  font-family: var(--serif);
  font-weight: 700;
  font-size: var(--fs-chapter);
  line-height: .88;
  letter-spacing: -.03em;
  margin: 0;
}
.day__title span:nth-child(2) { justify-self: center; font-style: italic; font-weight: 400; }
.day__title span:nth-child(3) { justify-self: end; }
.day__meta { margin-top: clamp(1.25rem, 3.5vh, 2rem); color: var(--grey); }

.run__wrap {
  position: relative;
  /* held in from the edges so the thread reads as one drawn line rather
     than a wire stretched across the viewport */
  width: min(64rem, 100%);
  margin-inline: auto;
}

/* drawn through the stops by app.js, so it always connects where they are */
.run__thread {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  color: rgba(241, 237, 230, .34);
  pointer-events: none;
}

.run {
  position: relative; z-index: 2;
  display: grid;
  gap: clamp(3.5rem, 11vh, 7rem);
  margin: 0; padding: 0; list-style: none;
}
.stop {
  display: grid;
  gap: .4rem;
  width: max-content; max-width: 100%;
}
.stop--l { justify-self: start; }
.stop--r { justify-self: end; text-align: right; }
.stop--c { justify-self: center; text-align: center; }

.stop__time {
  display: flex; align-items: baseline; gap: .45em;
  font-family: var(--serif);
  font-weight: 500;
  font-size: var(--fs-time);
  line-height: 1;
  letter-spacing: -.02em;
  font-variant-numeric: lining-nums;
}
.stop--r .stop__time, .stop--c .stop__time { justify-content: flex-end; }
.stop--c .stop__time { justify-content: center; }
.stop__pm {
  font-family: var(--sans); font-style: normal; font-weight: 300;
  font-size: var(--fs-meta);
  letter-spacing: var(--track);
  color: var(--grey-onink);
}
.stop__label { color: var(--paper); }

/* A small heart where the thread reaches each moment. It sits on the
   INSIDE edge — right of a left-hand stop, left of a right-hand one —
   because that is the side the line arrives from; put it outside and the
   line has to cross the time to get to it. */
.stop__pin {
  position: absolute;
  display: block;
  top: .34em;
  left: auto; right: clamp(-2.6rem, -2.4vw, -1.7rem);
  width: clamp(11px, 1vw, 14px);
  opacity: 0;
  transition: opacity .5s var(--ease);
}
.stop { position: relative; }
.stop--r .stop__pin { right: auto; left: clamp(-2.6rem, -2.4vw, -1.7rem); }
.stop--c .stop__pin { left: auto; right: clamp(-2.6rem, -2.4vw, -1.7rem); }
.stop__pin svg { width: 100%; height: auto; fill: var(--paper); opacity: .85; }
.stop.is-in .stop__pin { opacity: 1; }
.no-motion .stop__pin { opacity: 1; }

/* ── 07 Location ────────────────────────────────────────────────────── */
.place {
  position: relative;
  background: var(--paper); color: var(--ink);
  padding: var(--bay) var(--gutter);
  overflow: hidden;
}
.place__label { color: var(--grey-dim); }
.place__name {
  font-family: var(--serif);
  font-weight: 700;
  line-height: .82;
  letter-spacing: -.035em;
  margin: clamp(1.5rem, 4vh, 2.5rem) 0 clamp(2.5rem, 7vh, 4.5rem);
}
.place__the {
  display: block;
  font-weight: 400; font-style: italic;
  font-size: clamp(2rem, 7vw, 5rem);
  letter-spacing: -.01em;
  margin-left: .06em;
}
.place__merion {
  display: block;
  /* crops past the right edge */
  font-size: clamp(4.5rem, 22vw, 19rem);
  white-space: nowrap;
}
.place__figure { margin: 0 0 clamp(2rem, 6vh, 3.5rem); }
.place__figure img { width: 100%; height: auto; filter: grayscale(1) contrast(1.05); }
.place__address { max-width: 24rem; margin-top: .75rem; color: var(--ink-soft); }

/* ── 08 Countdown ───────────────────────────────────────────────────── */
/* No flower, no filler. The dead zone the flower used to occupy is closed
   by composition instead: the head sits over the figure, and the ticker
   comes up onto the same baseline as the numerals, so the three parts read
   as one block rather than three things adrift on a black field. */
.count {
  background: var(--ink); color: var(--paper);
  padding: var(--bay) var(--gutter);
  min-height: 100svh;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas:
    'head   head'
    'figure ticker';
  align-content: center;
  justify-content: start;
  column-gap: clamp(1.5rem, 5vw, 4rem);
  row-gap: clamp(1.75rem, 6vh, 3.5rem);
}
@supports not (height: 100svh) { .count { min-height: 100vh; } }

.count__head { grid-area: head; max-width: min(34rem, 100%); }
.count__kicker { color: var(--grey); }
.count__title {
  font-family: var(--serif);
  font-weight: 500; font-style: italic;
  font-size: clamp(1.9rem, 5vw, 3.6rem);
  line-height: 1.05;
  margin-top: clamp(.6rem, 1.6vh, 1rem);
}

.count__figure {
  grid-area: figure;
  display: flex; align-items: flex-start;
  gap: clamp(.6rem, 1.8vw, 1.4rem);
}
.count__days {
  display: flex;
  font-family: var(--serif);
  font-weight: 700;
  font-size: clamp(5.5rem, 26vw, 21rem);
  line-height: .78;
  letter-spacing: -.045em;
  font-variant-numeric: lining-nums tabular-nums;
}
/* The day word runs down the side of the figure, centred against it. */
.count__word {
  writing-mode: vertical-rl;
  align-self: stretch;
  /* in vertical writing this centres along the numerals, not across them */
  text-align: center;
  color: var(--grey);
  letter-spacing: var(--track);
}

.count__ticker {
  grid-area: ticker;
  /* on the numerals' own baseline — that is what closes the gap */
  align-self: end;
  justify-self: end;
  /* flex-start so the colons line up with the numerals, not the labels */
  display: flex; align-items: flex-start;
  gap: clamp(.5rem, 1.4vw, 1rem);
  color: var(--paper);
  padding-bottom: .12em;
}

.count__t { display: grid; justify-items: center; gap: clamp(.5rem, 1.4vh, .85rem); }
.count__tn {
  display: flex;
  font-family: var(--sans);
  font-weight: 300;
  font-size: clamp(1.6rem, 4.5vw, 3rem);
  line-height: 1;
  letter-spacing: -.03em;
  font-variant-numeric: tabular-nums;
}
.count__tl { color: var(--grey-onink); font-size: var(--fs-meta); letter-spacing: var(--track); }
.count__colon {
  font-family: var(--sans); font-weight: 300;
  font-size: clamp(1.6rem, 4.5vw, 3rem);
  line-height: 1; color: var(--grey-onink);
  animation: tick 2s steps(1, end) infinite;
}
@keyframes tick { 0%, 50% { opacity: .55; } 50.01%, 100% { opacity: .16; } }
.no-motion .count__colon { animation: none; opacity: .4; }

/* Numerals roll rather than blink. */
/* 1ch is the advance of a figure in the current face, so with tabular
   numerals the box is exactly one digit wide; the hair of extra width
   keeps any glyph overhang from being shaved by the overflow. */
.digit { display: block; height: 1em; overflow: hidden; width: calc(1ch + .02em); }
.digit__reel { display: block; transition: transform .8s var(--ease); }
/* Fast units must settle before they change again, or the reel is caught
   mid-roll almost permanently and reads as a broken glyph. */
[data-unit="seconds"] .digit__reel { transition-duration: .26s; }
[data-unit="minutes"] .digit__reel { transition-duration: .5s; }
.digit__reel b { display: block; height: 1em; line-height: 1; font-weight: inherit; }

/* ── 09 Dress code ──────────────────────────────────────────────────── */
/* The bow is the section, not an illustration set beside it. It is hung
   from the top-right corner and allowed to run off both edges — the same
   crop the artwork itself has — and the type is set low and left, in the
   space the satin leaves. A campaign page, not a product card. */
.dress {
  position: relative;
  background: var(--paper); color: var(--ink);
  padding: var(--bay) var(--gutter);
  min-height: 100svh;
  display: grid;
  /* the right column is empty on purpose: it is the bow's air */
  grid-template-columns: minmax(0, 1fr) minmax(0, 34%);
  grid-template-areas:
    'kicker .'
    'title  .'
    'note   .'
    'detail .';
  align-items: start;
  align-content: end;
  column-gap: clamp(2rem, 6vw, 5rem);
  row-gap: clamp(.5rem, 1.6vh, 1rem);
  overflow: hidden;
}
@supports not (height: 100svh) { .dress { min-height: 100vh; } }

.dress__kicker { grid-area: kicker; color: var(--grey-dim); }
.dress__title {
  grid-area: title;
  display: grid;
  font-family: var(--serif);
  font-weight: 700;
  font-size: clamp(3.4rem, 15vw, 12rem);
  line-height: .82;
  letter-spacing: -.035em;
  margin: clamp(.75rem, 2.5vh, 1.5rem) 0 clamp(1rem, 3vh, 1.75rem);
}
/* Both words in the one face at the one weight — the second is expressive
   by scale and placement, not by switching to an italic. */
.dress__title span:nth-child(2) {
  justify-self: end;
  font-size: 1.1em;
  margin-top: -.04em;
}
.dress__note {
  grid-area: note;
  font-size: var(--fs-meta);
  letter-spacing: var(--track-wide);
  color: var(--ink);
}
.dress__detail {
  grid-area: detail;
  margin-top: clamp(.75rem, 2.2vh, 1.25rem);
  font-size: var(--fs-lead);
  font-family: var(--serif);
  color: var(--ink-soft);
  max-width: 30ch;
}

/* ── 10 Final ───────────────────────────────────────────────────────── */
/* Nothing drawn across it and nothing in the corners. The names, the two
   facts, the sentence, and one heart — which is the only picture on the
   page and is therefore allowed to be the thing you look at. */
.final {
  position: relative;
  background: var(--ink); color: var(--paper);
  padding: var(--bay) var(--gutter) clamp(3rem, 8vh, 5rem);
  min-height: 100svh;
  display: grid; align-content: center; justify-items: center;
  text-align: center;
  gap: 0;
}
@supports not (height: 100svh) { .final { min-height: 100vh; } }

.final__names {
  font-family: var(--serif);
  font-weight: 500;
  font-size: clamp(2.6rem, 9vw, 7rem);
  line-height: .95;
  display: grid; justify-items: center;
}
.final__amp { font-style: italic; font-weight: 400; font-size: .4em; opacity: .7; margin: .1em 0; }
.final__meta { margin-top: clamp(2rem, 6vh, 3.5rem); color: var(--grey); line-height: 2.2; }
.final__line {
  margin-top: clamp(2.5rem, 7vh, 4rem);
  font-family: var(--serif);
  font-style: italic;
  font-size: var(--fs-lead);
  line-height: 1.6;
}
/* The one visual moment: bigger than the hearts elsewhere, still small. */
.final__heart { margin-top: clamp(2.5rem, 8vh, 4rem); }
.final__heart .heart svg { width: clamp(30px, 4.2vw, 46px); opacity: .92; }
.final__sig { margin-top: clamp(1rem, 3vh, 1.75rem); color: var(--grey-onink); }

/* The studio credit: the smallest thing on the page, in the corner the
   sound control does not use. */
.credit {
  position: absolute;
  left: var(--gutter);
  bottom: clamp(1rem, 3vh, 1.75rem);
  margin: 0;
  font-family: var(--sans);
  /* the smallest thing on the page, but still on the scale — it was
     falling to 9.6px on a 360px screen with a clamp of its own */
  font-size: clamp(.66rem, .12vw + .63rem, .72rem);
  letter-spacing: var(--track-tight);
  text-transform: uppercase;
  color: var(--grey-onink);
  line-height: 1.6;
}
.credit__link {
  color: var(--grey);
  /* Underlined with text-decoration rather than a border: the box is
     stretched to a 44px tap target, and a border would draw the rule at
     the bottom of that box instead of under the words. */
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .32em;
  text-decoration-color: rgba(154, 150, 142, .45);
  /* the mark stays tiny; the tap target does not */
  display: inline-flex; align-items: center;
  min-height: 44px;
  padding: .85rem .4rem;
  margin: -.85rem -.4rem;
  transition: color .3s var(--ease), text-decoration-color .3s var(--ease);
}
.credit__link:hover, .credit__link:focus-visible {
  color: var(--paper);
  text-decoration-color: var(--paper);
}

/* ── 6 · Responsive ─────────────────────────────────────────────────── */

@media (max-width: 900px) {
  /* Art-directed, not shrunk: the spine and the numerals keep the whole
     height, and the supporting lines drop under the date and take the full
     width of that column — squeezed into a third one they wrapped a word
     at a time down the edge of the screen. */
  .date {
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-areas:
      'spine stamp'
      'spine meta';
    column-gap: clamp(.75rem, 3vw, 2rem);
  }
  .date__meta { white-space: nowrap; }

  .welcome__sub { font-size: .9rem; }
  .date__month { font-size: clamp(.95rem, 5vw, 1.6rem); }
  .date__row { gap: .75rem; }
}

@media (max-width: 620px) {
  /* Portrait footage nearly fills a portrait screen. */
  .film__video { object-position: 50% 50%; }
  .film { height: 200svh; }
  .hands { height: 170svh; }

  /* Tracking that survives a narrow column. */
  /* One step tighter on narrow screens, applied through the token so
     every label moves together. */
  :root { --track: .24em; --track-wide: .32em; --track-tight: .14em; }

  .date__num { font-size: clamp(5.5rem, 40vw, 11rem); }

  .invite__names { line-height: .9; }

  .place__merion { font-size: clamp(3.5rem, 30vw, 9rem); }
  .hands__mark { font-size: clamp(5rem, 34vw, 11rem); }

  .count__days { font-size: clamp(6rem, 42vw, 12rem); }
  .count__ticker { justify-self: start; gap: .5rem; }
  .count__tn, .count__colon { font-size: clamp(1.5rem, 7vw, 2.2rem); }
  .digit { width: .58em; }


  .sound__label { display: none; }
  .sound { padding: .8rem; }
}

/* ── Reduced motion ─────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .count__colon { animation: none; opacity: .4; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .fp__img, .fp::after { animation: none; }
  .film, .hands { height: auto; }
  .film__stage, .hands__stage { position: relative; }
  .hands__mask { clip-path: inset(0 0 0 0); }
  .hands__img { transform: none; }
  .js .rise { opacity: 1; }
  .entrance.is-gone-soft { opacity: 0; }
}


/* ══ Language, welcome, wish line and the gap quotes ═════════════════ */

/* ── Language toggle ────────────────────────────────────────────────── */
.lang {
  position: fixed; z-index: 220;
  top: clamp(1rem, 2.5vw, 2rem);
  right: clamp(1rem, 2.5vw, 2rem);
  /* a 44px-tall target around a very small label */
  display: inline-flex; align-items: center;
  min-height: 44px; min-width: 44px;
  padding: .55rem .7rem;
  background: none; border: 0;
  color: var(--paper);
  font-family: var(--sans);
  font-size: .66rem; letter-spacing: var(--track); text-transform: uppercase;
  cursor: pointer; opacity: .7;
  mix-blend-mode: difference;      /* legible on the paper and the black alike */
  transition: opacity .4s var(--ease);
}
.lang:hover, .lang:focus-visible { opacity: 1; }

/* ── 01b · Welcome ──────────────────────────────────────────────────── */
.welcome {
  background: var(--ink); color: var(--paper);
  padding: calc(var(--bay) * 1.1) var(--gutter);
  min-height: 62svh;
  display: grid; place-items: center;
  text-align: center;
}
@supports not (height: 100svh) { .welcome { min-height: 62vh; } }
.welcome__sub {
  margin-top: clamp(1.5rem, 4vh, 2.5rem);
  font-family: var(--sans);
  font-weight: 300;
  font-size: clamp(.92rem, 1.15vw, 1.08rem);
  line-height: 1.8;
  color: var(--grey);
  max-width: 74ch;
  margin-inline: auto;
  text-wrap: balance;
}
/* The sentence carries the page, so it is given the width to say it in: a
   measure wide enough for two lines rather than four, at a size that has
   barely changed. The air on this page is horizontal, not vertical. */
.welcome__line {
  font-family: var(--serif);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(1.45rem, 3.2vw, 2.95rem);
  line-height: 1.32;
  letter-spacing: .005em;
  max-width: 46ch;
  margin-inline: auto;
  text-wrap: balance;
}

@media (max-width: 620px) {
  .lang { font-size: .66rem; padding: .5rem; }
  .welcome__line { max-width: none; font-size: clamp(1.3rem, 5.6vw, 1.9rem); }
  .welcome__sub  { max-width: none; }
  .invite__wish { max-width: none; }
  /* On a narrow column, alternating alignment reads as accidental. */
}


/* ══ Image windows, hearts, the venue wash and the bow tie ═══════════ */

/* ── Editorial image windows ────────────────────────────────────────── */
/* An empty slot keeps a designed tonal aperture — never a grey box.
   Fill the matching entry in config.js → images and the photograph
   takes over the same frame. */
.window { margin: 0; }
.window__frame {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(120% 85% at 28% 18%, rgba(247,244,239,.85) 0%, rgba(247,244,239,0) 58%),
    linear-gradient(158deg, rgba(216,210,198,.85) 0%, rgba(154,150,142,.55) 46%, rgba(11,11,11,.14) 100%);
}
/* a fine grain, so the empty state reads as paper rather than flat fill */
.window__frame::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  opacity: .5; mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='.32'/%3E%3C/svg%3E");
}
.window__frame::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(11,11,11,.16);
}
.window__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;                  /* never stretched */
  filter: grayscale(1) contrast(1.05);
  opacity: 0;                          /* only shown once a src is set */
  transition: opacity .8s var(--ease);
}
.window__img.is-loaded { opacity: 1; }

/* On the black chapters the aperture is tuned darker. */
.window--portrait  .window__frame { aspect-ratio: 3 / 4.4; }
.window--landscape .window__frame { aspect-ratio: 16 / 9; }
.window--tall      .window__frame { aspect-ratio: 3 / 4.5; }
.window--cine      .window__frame { aspect-ratio: 21 / 9; }
.window--bleed     .window__frame { aspect-ratio: 16 / 10; }

/* LOVE — the portrait crosses the O, the landscape slips behind V and E */
/* ── The heart, wherever it appears ─────────────────────────────────── */
.heartfx { position: fixed; inset: 0; z-index: 160; pointer-events: none; }

.heart {
  display: inline-flex; align-items: center; justify-content: center;
  /* the mark stays small; the tap target does not */
  min-width: 44px; min-height: 44px;
  padding: 10px;
  background: none; border: 0;
  color: var(--ink);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* Filled and a size up from where it was: the mark should read as a
   drawn heart, not as a thin dingbat. */
.heart svg {
  width: clamp(19px, 1.8vw, 26px); height: auto;
  fill: currentColor; opacity: .72;
  transition: opacity .35s var(--ease), transform .35s var(--ease);
}
.heart:hover svg, .heart:focus-visible svg { opacity: 1; transform: scale(1.1); }
.heart--light { color: var(--paper); }
.heart.is-beat svg { animation: heart-beat .5s var(--ease); }
@keyframes heart-beat {
  0% { transform: scale(1); } 28% { transform: scale(.86); } 100% { transform: scale(1.06); }
}
.no-motion .heart.is-beat svg { animation: none; }

.heart--film { position: relative; z-index: 3; margin-top: .35rem; }
.heart--invite { margin-left: -10px; margin-top: clamp(.75rem, 2vh, 1.25rem); }
/* ── 07 The Merion — the photograph faded into the section ──────────── */
.place__bg {
  position: absolute; inset: 0; z-index: 0;
  pointer-events: none; overflow: hidden;
}
/* Not an opacity fade: the picture is exposed outward from the chandelier
   as you scroll, the way a print comes up in a tray. motion.js scrubs the
   mask outward and the opacity with it; without JS it is simply developed. */
.place__bg-img {
  width: 100%; height: 100%;
  object-fit: cover;                     /* aspect preserved, never stretched */
  object-position: 50% 42%;              /* holds the chandeliers and the floor */
  filter: grayscale(1) contrast(1.12) brightness(1.04);
  opacity: .62;
  will-change: transform;
}
.place__bg-img.is-developing {
  opacity: .06;
  -webkit-mask-image: radial-gradient(circle, #000 42%, rgba(0,0,0,0) 74%);
          mask-image: radial-gradient(circle, #000 42%, rgba(0,0,0,0) 74%);
  -webkit-mask-repeat: no-repeat;        mask-repeat: no-repeat;
  -webkit-mask-position: 50% 24%;        mask-position: 50% 24%;
  -webkit-mask-size: 0% 0%;              mask-size: 0% 0%;
}
/* ivory bleeding in from the edges so it never reads as a photo card */
.place__bg::after {
  content: ''; position: absolute; inset: 0;
  background:
    linear-gradient(180deg, var(--paper) 0%, rgba(241,237,230,.14) 24%, rgba(241,237,230,.14) 72%, var(--paper) 100%),
    linear-gradient(90deg, var(--paper) 0%, rgba(241,237,230,.06) 20%, rgba(241,237,230,.06) 80%, var(--paper) 100%),
    /* a soft ivory pool under the small type so the address stays legible */
    radial-gradient(58% 42% at 20% 82%, rgba(241,237,230,.88) 0%, rgba(241,237,230,0) 70%);
}
.place > *:not(.place__bg) { position: relative; z-index: 2; }

/* ── 09 Dress code — the bow ────────────────────────────────────────── */
/* Hung from the top-right corner and cropped by both edges, the way the
   artwork is cropped itself, with the shadow of something actually
   resting on the paper. */
.tie {
  position: absolute;
  z-index: 1;
  right: clamp(-6rem, -5vw, -1.5rem);
  top: clamp(-7rem, -9vh, -2rem);
  width: clamp(17rem, 44vw, 38rem);
  pointer-events: none;
  will-change: transform;
  filter:
    drop-shadow(0 22px 34px rgba(11, 11, 11, .16))
    drop-shadow(0 4px 6px rgba(11, 11, 11, .12));
}
.tie__img {
  width: 100%; height: auto;
  object-fit: contain;                   /* no box, no background */
  opacity: 0;
  transition: opacity .9s var(--ease);
}
.tie__img.is-loaded { opacity: 1; }
/* the type sits over the satin's tails, never under them */
.dress > *:not(.tie) { position: relative; z-index: 2; }

/* ══ The calla, kept for one page only ═══════════════════════════════
   Every other flower has been taken out. The one that stays is the pale
   calla behind The Merion, where it is a wash in the paper rather than an
   object on it. */
.bloom { position: absolute; pointer-events: none; z-index: 1; }
.bloom__draw { width: 100%; height: 100%; }
.bloom__img {
  width: 100%; height: 100%;
  object-fit: cover;                 /* cropped by its frame, never stretched */
  opacity: 0;
  transition: opacity 1.1s var(--ease);
}
.bloom__img.is-loaded { opacity: 1; }
.bloom.has-photo .bloom__draw { display: none; }
.bloom:not(.has-photo) .bloom__img { display: none; }

.bloom--pale {
  /* multiply keeps the shading the right way round while the flower
     sinks to a ghost of itself on the warm paper */
  mix-blend-mode: multiply;
  filter: grayscale(1) brightness(.66) contrast(.85);
  opacity: .3;
}
.bloom--place {
  right: -6vw; bottom: -6%;
  width: clamp(10rem, 20vw, 20rem);
  height: 76%;
  transform: rotate(8deg) scaleX(-1);
  opacity: .2;
}
.bloom--place .bloom__img { object-fit: contain; object-position: 100% 50%; }
.place > .bloom { position: absolute; z-index: 1; }

/* ══ 6 · Responsive ══════════════════════════════════════════════════
   The rule everywhere below is recompose, not shrink: a section changes
   its arrangement at a breakpoint rather than being scaled down until the
   type is unreadable. Sizes themselves are already handled by the clamps
   in the token scale, so almost nothing here touches font-size. */

/* ── Tablet and small laptop ────────────────────────────────────────── */
@media (max-width: 1024px) {
  /* the note moves above the names rather than beside them; the names get
     the whole width, which is what makes them read as big at this size */
  .invite {
    grid-template-columns: minmax(0, 1fr);
    row-gap: clamp(2rem, 6vh, 3.5rem);
  }
  .invite__text { grid-column: 1; max-width: 34rem; }
  .invite__names { grid-column: 1; }
  .invite__amp { font-size: clamp(4.5rem, 15vw, 10rem); }

  /* the timeline stops alternating and becomes one column read downwards */
  .run { justify-items: start; }
  .stop, .stop--l, .stop--r, .stop--c {
    justify-self: start; text-align: left;
    padding-left: clamp(2.25rem, 7vw, 3.25rem);
  }
  .stop--r .stop__time, .stop--c .stop__time { justify-content: flex-start; }
  /* one column, so every heart goes back to the left margin */
  .stop__pin, .stop--r .stop__pin, .stop--c .stop__pin {
    left: 0; right: auto;
  }
}

/* ── Portrait tablet and large phone ────────────────────────────────── */
@media (max-width: 900px) {
  /* the bow comes off the corner and sits over the top of the type,
     because there is no room beside it here */
  .dress {
    min-height: auto;
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: 'bow' 'kicker' 'title' 'note' 'detail';
    row-gap: 0;
  }
  .dress__title { margin-bottom: clamp(1rem, 3vh, 1.75rem); }
  .tie {
    position: relative;
    grid-area: bow;
    inset: auto;
    width: min(78vw, 24rem);
    margin: clamp(-2rem, -5vw, 0rem) calc(var(--gutter) * -0.6) clamp(-1.5rem, -4vw, 0rem) auto;
    transform: none;
  }

  /* one column: the figure is too wide at this size to keep a second
     one beside it, and the ticker would be pushed off the screen */
  .count {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: 'head' 'figure' 'ticker';
    row-gap: clamp(1.25rem, 4vh, 2.25rem);
  }
  .count__ticker { justify-self: start; align-self: auto; padding-bottom: 0; }
}

/* ── Phones ─────────────────────────────────────────────────────────── */
@media (max-width: 620px) {
  /* the italic bowl overhangs its own box, so it needs a touch more edge */
  .invite__amp { right: clamp(.6rem, 3vw, 1.25rem); }

  /* LOVE stays vertical and stays large: on a phone it is the height that
     sets the letter, not the width, and the story narrows and weaves down
     beside it rather than the whole thing being scaled down. */
  .love {
    padding-left: var(--gutter);
    column-gap: clamp(.85rem, 5vw, 1.5rem);
  }
  .love__word { font-size: min(30vw, 16vh); }
  .love__line { max-width: none; }
  .love__line:nth-child(2) { margin-left: 6%; }
  .love__line:nth-child(3) { margin-left: 12%; }
  .love__line:nth-child(4) { margin-left: 18%; }

  .place__bg-img { object-position: 50% 46%; opacity: .42; }
  .bloom--place { width: 34vw; right: -10vw; height: 58%; opacity: .17; }

  /* three stacked numerals need the height back that the word was taking */
  .count__word { writing-mode: horizontal-tb; align-self: end; padding-bottom: .3em; }

  .final__meta { line-height: 2; }
}

@media (max-width: 380px) {
  .stop, .stop--l, .stop--r, .stop--c { padding-left: 2rem; }
}

/* ── Reduced motion ─────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .tie, .bloom { transform: none; }
  .bloom--place { transform: rotate(8deg) scaleX(-1); }
  .heart.is-beat svg { animation: none; }
  .window__img, .tie__img, .bloom__img { transition: none; }
  /* the thread is simply there rather than drawn */
  .run__thread path { stroke-dasharray: none !important; stroke-dashoffset: 0 !important; }
  .stop__pin { opacity: 1; }
}
