/* =====================================================
   მარიამი & ანრი: wedding invitation (mobile only)

   Mobile-first throughout. The single min-width query at the bottom only swaps
   the page for an "open me on a phone" notice on desktop; there is no desktop
   layout and none is intended.

   Two surfaces carry the design, both cut from ONE linen tile so the weave is
   identical: taupe.jpg (the tile recoloured to the linen behind the lace in
   frame.jpg) and linen.jpg (the tile as delivered). A section change reads as
   a second sheet of the same cloth, not a colour swap.

   Shape rule: the childhood cut-outs in the hero carry their own white
   sticker outline, every boxy surface (cards, maps, inputs) is 12px, the
   timetable watercolours 14px, every control (choices, buttons, links, the
   seal) is round or a pill, and the gallery prints are paper at 3px.
   ===================================================== */

/* ── Fonts ───────────────────────────────────────────
   'BPG Nino' is BPG Nino Mtavruli (Georgian capitals plus its own Latin),
   subset to Georgian + Latin + punctuation. 'Bodoni Moda' comes from Google
   Fonts in <head> and only ever sets Latin display lines; Georgian in a Bodoni
   line falls through to BPG Nino per character. */

@font-face {
  font-family: 'BPG Nino';
  src: url('fonts/nino-book.woff2') format('woff2');
  font-weight: 300;
  font-style: normal;
  /* block, not swap: #loader covers the block period, so a guest never watches
     the invitation re-flow out of a fallback face */
  font-display: block;
}

@font-face {
  font-family: 'BPG Nino';
  src: url('fonts/nino-normal.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

/* Bold is used for one thing only, the scroll hint, so it can be read at a
   glance over the lace */
@font-face {
  font-family: 'BPG Nino';
  src: url('fonts/nino-bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: block;
}

:root {
  /* --vh is overwritten before first paint by the inline script in <head>.
     This value is the no-JS fallback, never the live one. */
  --vh: 1vh;

  /* Read off the photographs and the hero art: taupe is the linen behind the
     lace, ivory the dress, ink the dark trousers, brick the arcade walls. Brick
     is the ONE accent; nothing else on the page is allowed a hue. */
  --taupe:      #c8b9a6;
  --taupe-deep: #b5a48f;
  --linen:      #e0d4c4;
  --ivory:      #f4eee5;
  --beige:      #ece2d3;
  --ink:        #3a2a20;
  --ink-soft:   #533f32;
  --stone:      #9a8a7a;
  --brick:      #8f4f3b;
  --brick-deep: #77402f;

  /* 280 CSS px against an 840px tile is a clean 3x on the phones this is built
     for, so the weave never resamples into mush. */
  --tile: 280px;

  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);

  --f-display: 'Bodoni Moda', 'BPG Nino', Georgia, serif;
  --f-text: 'BPG Nino', 'Bodoni Moda', Georgia, serif;
}

*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  scroll-behavior: smooth;
  /* Single scroll container, single clip. overflow-x on BOTH html and body
     nests two clipping contexts and breaks trackpad scrolling in Chrome on
     macOS, so body deliberately does not repeat it. */
  overflow-x: hidden;
  scrollbar-width: thin;
  scrollbar-color: var(--stone) var(--taupe-deep);
}

/* Stops the in-app browsers bouncing the frame against the pinned --vh. Scoped
   to touch because on macOS it also kills Chrome's two-finger trackpad scroll,
   and nothing with a fine pointer has a sliding nav bar to defend against. */
@media (pointer: coarse) {
  html, body { overscroll-behavior-y: none; }
}

body {
  font-family: var(--f-text);
  font-weight: 400;
  background-color: var(--taupe);
  background-image: url('taupe.jpg');
  background-size: var(--tile) var(--tile);
  color: var(--ink);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
}

::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: var(--taupe-deep); }
::-webkit-scrollbar-thumb {
  background: var(--stone);
  border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover { background: var(--ink-soft); }

img { display: block; max-width: 100%; }

/* Nothing here ever transforms case: BPG Nino Mtavruli is capitals by design,
   and Latin is written in the case it should appear. */

/* ── Shared type ─────────────────────────────────── */

.title-en {
  font-family: var(--f-display);
  font-weight: 400;
  font-size: 2.3rem;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.title-ka {
  margin-top: 8px;
  font-weight: 300;
  font-size: 0.95rem;
  letter-spacing: 0.18em;
  /* letter-spacing pads the right of the last glyph too, which pulls a centred
     line visibly off-centre at this tracking */
  text-indent: 0.18em;
  color: var(--ink-soft);
}

/* A Georgian line doing the job of a section heading on its own */
.title-ka--lead {
  margin-top: 0;
  font-weight: 400;
  font-size: 1.3rem;
  letter-spacing: 0.14em;
  text-indent: 0.14em;
  line-height: 1.4;
  color: var(--ink);
}

.rule {
  display: block;
  width: 48px;
  height: 1px;
  margin: 18px auto;
  background-color: var(--brick);
  opacity: 0.55;
}

/* ── Sheets ──────────────────────────────────────────
   Section separation is geometric: rounded top corners, a shadow cast upward
   and a negative margin so each sheet overlaps the one before it. z-index has
   to climb down the page or a later sheet slides underneath.

   background-position stays at each sheet's own origin, so the weave phase
   jumps at every boundary, which is exactly what a second sheet looks like. */

.sheet {
  position: relative;
  margin-top: -26px;
  padding: 60px 22px 64px;
  border-top-left-radius: 26px;
  border-top-right-radius: 26px;
  text-align: center;
  background-color: var(--taupe);
  background-image: url('taupe.jpg');
  background-size: var(--tile) var(--tile);
  box-shadow: 0 -12px 30px rgba(74, 54, 40, 0.22);
}

.sheet--light {
  background-color: var(--linen);
  background-image: url('linen.jpg');
  box-shadow: 0 -12px 30px rgba(74, 54, 40, 0.18);
}

.gallery   { z-index: 2; }
.details   { z-index: 3; }
.venue     { z-index: 4; }
.note      { z-index: 5; }
.dress     { z-index: 6; }
.rsvp      { z-index: 7; }
.final     { z-index: 8; }

/* Carries the drift so the contents lag against the sheet's edge on scroll.
   The cloth itself must never move or the overlap stops lining up. */
.sheet__body {
  position: relative;
  z-index: 1;
  will-change: transform;
}

/* ── Motion ──────────────────────────────────────────
   One entry for every line of type: a soft rise out of a slight blur, like a
   photograph coming into focus. Cards and portraits get the same curve without
   the blur, which reads as a rendering fault on an image. */

@keyframes softRise {
  0%   { opacity: 0; transform: translateY(16px); filter: blur(4px); }
  100% { opacity: 1; transform: translateY(0);    filter: blur(0); }
}

@keyframes cardRise {
  0%   { opacity: 0; transform: translateY(26px) scale(0.97); }
  100% { opacity: 1; transform: translateY(0)    scale(1); }
}

/* ── HERO ────────────────────────────────────────────
   z-index: 0 is load-bearing. position: relative alone creates no stacking
   context, so .hero-inner would be compared against the sheets and the names
   would paint over the first sheet on scroll. */

.hero {
  position: relative;
  z-index: 0;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Room for the scroll hint below the lace, above the first sheet's overlap:
     34px offset + 46px pill + 6px of nudge */
  padding: 0 0 88px;
}

.vh-lock .hero { min-height: calc(var(--vh) * 100); }

/* Letterboxed, never cropped. max-width is height-derived so the lace always
   fits top to bottom; the taupe it letterboxes against is the same linen. */
.hero-art {
  position: relative;
  z-index: 1;
  width: 100%;
  /* Height-derived so the lace always fits between the top of the screen and
     the 88px of hint room: (100vh - 88px) * 941/1672 */
  max-width: calc(56.28vh - 49.5px);
  aspect-ratio: 941 / 1672;
  animation: frameIn 1.6s var(--ease-out) both;
}

.vh-lock .hero-art { max-width: calc(var(--vh) * 56.28 - 49.5px); }

/* Everything inside the oval is sized in cqw (1% of the frame's width), so
   type and cut-outs scale with the lace itself, whatever the phone. Each rule
   gives a rem value first as the fallback for browsers without container
   units. */
.hero-art { container-type: inline-size; }

@keyframes frameIn {
  0%   { opacity: 0; transform: scale(1.05); }
  100% { opacity: 1; transform: scale(1); }
}

.hero-frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* The oval is an ellipse, so its usable width collapses near the top and
   bottom. These insets keep the short eyebrow and invite lines where the
   lace still leaves room for them; the wide cut-outs land in the middle. */
.hero-inner {
  position: absolute;
  top: 14%;
  right: 13%;
  bottom: 15%;
  left: 13%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  color: var(--ink);
}

.hero-eyebrow {
  font-family: var(--f-display);
  font-size: 0.82rem;
  font-size: 4.3cqw;
  letter-spacing: 0.08em;
  text-indent: 0.08em;
  line-height: 1.3;
  color: var(--ink-soft);
  animation: softRise 0.9s var(--ease-out) 0.5s both;
}

.hero-names {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin-top: 1.5cqw;
  font-family: var(--f-display);
  font-weight: 400;
  font-size: 2.4rem;
  font-size: 12cqw;
  line-height: 0.98;
  letter-spacing: -0.015em;
  color: var(--ink);
}

/* inline-block or the transform has nothing to animate about */
.hero-name { display: inline-block; animation: softRise 1s var(--ease-out) both; }
.hero-name--a { animation-delay: 0.75s; }
.hero-name--b { animation-delay: 1.15s; }

.hero-amp {
  display: inline-block;
  margin: 0.5cqw 0;
  font-size: 1.2rem;
  font-size: 6cqw;
  line-height: 1;
  color: var(--brick);
  animation: softRise 1s var(--ease-out) 0.95s both;
}

/* flex-grow sets each cut-out's width, and its height follows from the
   file's own shape (Anri 0.73 wide per unit of height, Mariami 0.96). Both
   are cut at the same 2.65 head-heights, so height stands in for head size;
   at 0.65 against 0.84 Anri's head reads a touch smaller than Mariami's, as
   the client asked. The veil tucks over his shoulder via the negative margin. */
.hero-kids {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  width: 66cqw;
  margin: 3.5cqw 0 4cqw;
}

.hero-kid--a { flex: 0.65 1 0; }
.hero-kid--b { flex: 0.84 1 0; margin-left: -3.5cqw; }

.hero-kid { animation: cardRise 1s var(--ease-out) both; }
.hero-kid--a { animation-delay: 1.45s; }
.hero-kid--b { animation-delay: 1.65s; }

.hero-kid__in {
  display: block;
  will-change: transform;
}

/* The white outline is baked into the file, so the only frame here is a soft
   shadow; a CSS border on top would read as two frames. */
.hero-kid__in img {
  width: 100%;
  height: auto;
  filter: drop-shadow(0 6px 9px rgba(74, 54, 40, 0.3));
}

/* They lean in towards each other and stay leaning: the rest angle is part
   of the pose, so it lives inside the float keyframes. */
@keyframes kidFloatA {
  0%, 100% { transform: rotate(-2.5deg) translateY(0); }
  50%      { transform: rotate(-2.5deg) translateY(-1.4cqw); }
}

@keyframes kidFloatB {
  0%, 100% { transform: rotate(2.5deg) translateY(0); }
  50%      { transform: rotate(2.5deg) translateY(-1.4cqw); }
}

.hero-kid--a .hero-kid__in { animation: kidFloatA 5.2s ease-in-out 2.6s infinite; transform: rotate(-2.5deg); }
.hero-kid--b .hero-kid__in { animation: kidFloatB 5.2s ease-in-out 3s infinite; transform: rotate(2.5deg); }

.hero-date {
  font-family: var(--f-display);
  font-size: 1.08rem;
  font-size: 5.4cqw;
  letter-spacing: 0.16em;
  text-indent: 0.16em;
  line-height: 1.2;
  color: var(--ink);
  animation: softRise 0.9s var(--ease-out) 2s both;
}

.hero-invite {
  margin-top: 2.5cqw;
  font-weight: 300;
  font-size: 0.74rem;
  font-size: 3.7cqw;
  letter-spacing: 0.12em;
  text-indent: 0.12em;
  line-height: 1.5;
  color: var(--ink-soft);
  animation: softRise 0.9s var(--ease-out) 2.2s both;
}

/* Asked for by the client, and asked for LOUD: a solid beige pill with bold
   ink type is the strongest thing on the screen after the names. Beige is
   lighter than the taupe linen it floats over, so it still lifts off the page
   (helped by the shadow and a hairline edge) without a second accent hue. It fades
   in once the hero entry has finished, then the pill keeps nudging downward. */
.scroll-hint {
  position: absolute;
  /* Centred by left/right, not translateX(-50%): softRise animates transform
     and would wipe a centring translate for the whole entry and after it. */
  left: 0;
  right: 0;
  bottom: 34px;
  z-index: 2;
  display: flex;
  justify-content: center;
  pointer-events: none;
  animation: softRise 0.9s var(--ease-out) 2.4s both;
}

/* The bounce lives on the pill, the entry on its parent: one transform each */
.scroll-hint__pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 18px 11px 22px;
  border-radius: 999px;
  border: 1px solid rgba(58, 42, 32, 0.14);
  background-color: var(--beige);
  box-shadow: 0 8px 20px rgba(74, 54, 40, 0.28);
  animation: hintNudge 1.6s ease-in-out 3.3s infinite;
}

.scroll-hint__text {
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: 0.1em;
  line-height: 1;
  white-space: nowrap;
  color: var(--ink);
}

.scroll-hint__arrow {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: var(--ink);
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

@keyframes hintNudge {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(6px); }
}

/* ── Scroll reveal ───────────────────────────────────
   script.js adds .is-in as each element enters the viewport and then stops
   watching it, so nothing replays on the way back up. */

.reveal { opacity: 0; }
.reveal.is-in { animation: softRise 0.9s var(--ease-out) both; }

/* Two classes deep to outrank .reveal.is-in regardless of file order */
.reveal.reveal--card.is-in { animation: cardRise 0.9s var(--ease-out) both; }

/* ── PHOTO GALLERY ───────────────────────────────────
   Prints stacked face up; only the front one takes pointer input. All eleven
   photographs are the same 2:3 portrait, so there is one print size and the
   window's height comes from the image's own width/height attributes. */

.deck-wrap { margin-top: 34px; }

.deck {
  position: relative;
  width: 228px;
  /* Card: 10 + 312 (208px window at 2:3) + 34 = 356px, plus the sunk cards'
     9px steps and room for the rotation. Fixed, because every card is absolute
     and the stack would otherwise have no height; re-derive it if the print
     size changes. */
  height: 404px;
  margin: 0 auto;
  /* pan-y, not none: a vertical swipe that starts on a card still scrolls the
     page. The horizontal axis is claimed in JS. */
  touch-action: pan-y;
  -webkit-tap-highlight-color: transparent;
}

.film {
  position: absolute;
  /* Centred, matching the -50% in cardTransform in script.js */
  top: 50%;
  left: 50%;
  width: 228px;
  /* The deep bottom margin is the print's border, not a caption slot */
  padding: 10px 10px 34px;
  background-color: var(--ivory);
  border: 1px solid rgba(58, 42, 32, 0.1);
  border-radius: 3px;
  box-shadow: 0 10px 24px rgba(74, 54, 40, 0.2);
  transform-origin: 50% 60%;
  transition: transform 0.55s var(--ease-out), opacity 0.45s ease;
  user-select: none;
  -webkit-user-select: none;
  will-change: transform;
}

/* Killed while a finger is on the card so it tracks the drag exactly */
.film.is-dragging { transition: none; }

.film__window {
  position: relative;
  overflow: hidden;
  background-color: var(--linen);
}

.film__window img {
  width: 100%;
  height: auto;
  pointer-events: none;
}

.deck-dots {
  display: flex;
  justify-content: center;
  gap: 7px;
  margin-top: 22px;
}

.deck-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background-color: var(--stone);
  opacity: 0.5;
  transition: background-color 0.35s ease, opacity 0.35s ease, transform 0.35s ease;
}

.deck-dot.is-active {
  background-color: var(--brick);
  opacity: 1;
  transform: scale(1.35);
}

.deck-hint {
  margin-top: 14px;
  font-weight: 300;
  font-size: 0.82rem;
  letter-spacing: 0.16em;
  text-indent: 0.16em;
  color: var(--ink-soft);
}

/* ── THE DAY ─────────────────────────────────────── */

.details-date {
  font-family: var(--f-display);
  font-size: 2.6rem;
  letter-spacing: 0.06em;
  text-indent: 0.06em;
  line-height: 1.05;
  color: var(--ink);
}

.details-date-ka {
  margin-top: 8px;
  font-weight: 300;
  font-size: 0.92rem;
  letter-spacing: 0.16em;
  text-indent: 0.16em;
  color: var(--ink-soft);
}

/* One stop per block, stacked down the middle: a large watercolour, then the
   time and the two labels under it. The illustrations are full scenes, so
   they are the stars here and get the column's whole width, up to 320px. Stops are
   joined by a short dashed thread rather than a spine, so nothing has to
   line up with the centre of an image. */
.schedule {
  max-width: 320px;
  margin: 34px auto 0;
  list-style: none;
}

.sched {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

/* The thread between two stops, drawn above every stop but the first */
.sched + .sched::before {
  content: '';
  display: block;
  width: 1px;
  height: 46px;
  margin: 18px 0;
  background-image: repeating-linear-gradient(
    to bottom,
    var(--stone) 0 3px,
    transparent 3px 8px
  );
}

/* No mat: the painting sits straight on the linen. A soft warm shadow lifts
   it and a hairline ring (drawn on ::after, since an <img> cannot take an
   inset shadow) keeps the pale watercolour edges from dissolving into the
   taupe. 14px corners, a touch softer than the cards. */
.sched__art {
  position: relative;
  width: 100%;
  max-width: 320px;
  border-radius: 14px;
  box-shadow: 0 14px 30px rgba(74, 54, 40, 0.2), 0 2px 6px rgba(74, 54, 40, 0.12);
}

.sched__art::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px rgba(58, 42, 32, 0.1);
  pointer-events: none;
}

.sched__art img {
  width: 100%;
  aspect-ratio: 1 / 1;
  height: auto;
  object-fit: cover;
  border-radius: inherit;
}

.sched__text { margin-top: 20px; }

.sched__time {
  display: block;
  font-family: var(--f-display);
  font-size: 2.1rem;
  line-height: 1;
  color: var(--brick);
}

.sched__ka {
  margin-top: 10px;
  font-size: 1.05rem;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  line-height: 1.4;
  color: var(--ink);
}

.sched__en {
  margin-top: 3px;
  font-family: var(--f-display);
  font-size: 1rem;
  line-height: 1.35;
  color: var(--ink-soft);
}

/* ── VENUES ──────────────────────────────────────── */

.place {
  margin-top: 22px;
  padding: 20px 12px 20px;
  border: 1px solid rgba(58, 42, 32, 0.12);
  border-radius: 12px;
  background-color: rgba(244, 238, 229, 0.55);
}

.place__label {
  font-weight: 300;
  font-size: 0.82rem;
  letter-spacing: 0.22em;
  text-indent: 0.22em;
  color: var(--brick);
}

.place__name-ka {
  margin-top: 4px;
  font-size: 1.3rem;
  letter-spacing: 0.1em;
  text-indent: 0.1em;
  line-height: 1.35;
  color: var(--ink);
}

.place__name {
  margin-top: 2px;
  font-family: var(--f-display);
  font-weight: 400;
  font-size: 1.2rem;
  line-height: 1.3;
  color: var(--ink-soft);
}

.place__addr {
  margin-top: 4px;
  font-weight: 300;
  font-size: 0.86rem;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
}

.place__map {
  overflow: hidden;
  margin-top: 16px;
  aspect-ratio: 3 / 2;
  border-radius: 12px;
  background-color: var(--taupe);
}

.place__map iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  /* Knocks the bright Google basemap back into the linen without hiding the
     streets */
  filter: sepia(0.18) saturate(0.7) brightness(0.97);
}

.place__link {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-top: 16px;
  padding: 9px 20px;
  border: 1px solid rgba(58, 42, 32, 0.35);
  border-radius: 999px;
  font-size: 0.86rem;
  letter-spacing: 0.1em;
  color: var(--ink);
  text-decoration: none;
  transition: background-color 0.25s ease, border-color 0.25s ease, transform 0.2s ease;
}

.place__link:active {
  background-color: rgba(58, 42, 32, 0.08);
  border-color: var(--ink);
  transform: scale(0.98);
}

.pin {
  width: 14px;
  height: 14px;
  fill: currentColor;
}

/* ── A NOTE FROM US ──────────────────────────────── */

.note { padding: 64px 26px 76px; }

/* Stands in for a quote mark: Bodoni's low quote reads as two stray dots */
.rule--top { margin-top: 0; margin-bottom: 26px; }

.note-quote {
  position: relative;
  font-size: 1.08rem;
  letter-spacing: 0.06em;
  line-height: 1.95;
  color: var(--ink);
  /* No hand-placed <br>s: the capitals run wide enough that any fixed break
     wraps again on a narrow phone. Balance evens the lines out instead. */
  text-wrap: balance;
}

/* ── DRESS CODE ──────────────────────────────────── */

.dress-code {
  font-family: var(--f-display);
  font-size: 1.45rem;
  line-height: 1.35;
  color: var(--ink);
}

/* ── RSVP ────────────────────────────────────────── */

.rsvp-sub {
  margin-top: 10px;
  font-weight: 300;
  font-size: 0.92rem;
  letter-spacing: 0.08em;
  line-height: 1.55;
  color: var(--ink-soft);
}

.rsvp-form {
  max-width: 360px;
  margin: 30px auto 0;
  text-align: left;
}

.field { margin-bottom: 16px; }

.field__label {
  display: block;
  margin-bottom: 7px;
  font-size: 0.96rem;
  letter-spacing: 0.12em;
  line-height: 1.45;
  font-weight: 800;
  color: var(--ink-soft);
}

.field__hint {
  font-weight: 300;
  letter-spacing: 0.06em;
}

.field__input {
  width: 100%;
  padding: 12px 14px;
  border: 1px solid rgba(58, 42, 32, 0.22);
  border-radius: 12px;
  background-color: rgba(244, 238, 229, 0.72);
  font-family: var(--f-text);
  /* 16px or more, or iOS zooms the page on focus */
  font-size: 17px;
  letter-spacing: 0.04em;
  color: var(--ink);
  transition: border-color 0.25s ease, background-color 0.25s ease;
}

.field__input:focus {
  outline: none;
  border-color: var(--brick);
  background-color: var(--ivory);
}

.group {
  margin-bottom: 16px;
  border: 0;
}

.choices {
  display: flex;
  gap: 8px;
}

.choice { flex: 1 1 0; }

/* The input is the control, the span the surface. Clipped rather than
   display:none so it stays in the tab order. */
.choice input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.choice span {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  min-height: 46px;
  padding: 8px 10px;
  border: 1px solid rgba(58, 42, 32, 0.22);
  border-radius: 999px;
  background-color: rgba(244, 238, 229, 0.5);
  font-size: 0.86rem;
  letter-spacing: 0.04em;
  line-height: 1.25;
  text-align: center;
  color: var(--ink);
  cursor: pointer;
  transition: background-color 0.25s ease, border-color 0.25s ease, color 0.25s ease;
}

.choice input:checked + span {
  border-color: var(--ink);
  background-color: var(--ink);
  color: var(--ivory);
}

.choice input:focus-visible + span { outline: 2px solid var(--brick); outline-offset: 2px; }

.choices--count span {
  font-family: var(--f-display);
  font-size: 1.1rem;
}

/* Folded away when the guest declines */
#rsvp-guests.is-hidden { display: none; }

.rsvp-submit {
  width: 100%;
  margin-top: 12px;
  padding: 15px 20px;
  border: 0;
  border-radius: 999px;
  background-color: var(--brick);
  font-family: var(--f-text);
  font-size: 1rem;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  color: var(--ivory);
  cursor: pointer;
  transition: background-color 0.25s ease, opacity 0.25s ease, transform 0.2s ease;
}

.rsvp-submit:active { background-color: var(--brick-deep); transform: scale(0.98); }
.rsvp-submit:disabled { opacity: 0.55; cursor: default; transform: none; }

.rsvp-status {
  min-height: 22px;
  margin-top: 12px;
  font-weight: 300;
  font-size: 0.88rem;
  letter-spacing: 0.04em;
  text-align: center;
  color: var(--ink-soft);
}

.rsvp-status.is-ok    { color: var(--ink); }
.rsvp-status.is-error { color: var(--brick-deep); }

/* Past the date there is nothing to fill in, so the fields go rather than sit
   there collecting answers that cannot be sent. */
.rsvp-form.is-closed .field,
.rsvp-form.is-closed .group,
.rsvp-form.is-closed .rsvp-submit { display: none; }

/* ── FINAL ───────────────────────────────────────── */

.final { padding-bottom: 34px; }

.final-ka {
  font-size: 1.6rem;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  line-height: 1.3;
  color: var(--ink);
}

.final-love {
  margin-top: 6px;
  font-weight: 300;
  font-size: 0.86rem;
  letter-spacing: 0.16em;
  text-indent: 0.16em;
  color: var(--ink-soft);
}

.final-names {
  margin-top: 6px;
  font-family: var(--f-display);
  font-size: 2.3rem;
  line-height: 1.2;
  color: var(--ink);
}

.credit {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 50px;
  font-weight: 300;
  font-size: 0.74rem;
  letter-spacing: 0.1em;
  color: var(--ink);
  text-decoration: none;
  opacity: 0.3;
  transition: opacity 0.3s ease;
}

.credit:hover, .credit:active { opacity: 0.7; }

.credit img {
  width: 20px;
  height: auto;
}

/* ── OPENING SCREEN ──────────────────────────────────
   The tap that lets iOS start the music. A monogram seal on the same taupe
   linen as the page, a pulse ring round it, and the same bold beige pill as
   the scroll hint so the two read as one family of instructions.

   inset: 0 and no forced height: the document is locked in JS, not with
   position:fixed, so inset reaches the real bottom of the glass on its own.
   Any --vh / svh height here would oversize it under the Chrome tab bar. */

.entry {
  position: fixed;
  inset: 0;
  /* Under the loader (10000), over everything on the page */
  z-index: 9000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background-color: var(--taupe);
  background-image: url('taupe.jpg');
  background-size: var(--tile) var(--tile);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: opacity 0.9s ease 0.25s, transform 1.2s var(--ease-out) 0.25s;
}

.entry:focus-visible { outline: none; }
.entry:focus-visible .entry__seal { box-shadow: 0 0 0 3px var(--brick), 0 16px 34px rgba(74, 54, 40, 0.3); }

.entry.is-opening {
  opacity: 0;
  transform: scale(1.04);
  pointer-events: none;
}

/* A wax-seal disc: ivory, with a brick hairline ring set in from the edge */
.entry__seal {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 148px;
  height: 148px;
  border-radius: 50%;
  background-color: var(--ivory);
  box-shadow:
    inset 0 0 0 7px var(--ivory),
    inset 0 0 0 8px rgba(143, 79, 59, 0.55),
    0 16px 34px rgba(74, 54, 40, 0.3);
  transition: transform 0.6s var(--ease-out), opacity 0.5s ease;
}

.entry.is-opening .entry__seal { transform: scale(1.12); opacity: 0; }

/* The ring that keeps pulsing out of the seal says "touch me" without words */
.entry__seal::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1px solid var(--brick);
  animation: sealPulse 2.4s ease-out infinite;
  pointer-events: none;
}

@keyframes sealPulse {
  0%   { transform: scale(1);    opacity: 0.6; }
  100% { transform: scale(1.45); opacity: 0; }
}

.entry__mono {
  font-family: var(--f-display);
  font-size: 2.6rem;
  line-height: 1;
  letter-spacing: 0.02em;
  color: var(--ink);
}

.entry__amp {
  margin: 0 0.12em;
  font-size: 0.6em;
  vertical-align: 0.28em;
  color: var(--brick);
}

.entry__names {
  margin-top: 30px;
  font-family: var(--f-display);
  font-size: 1.6rem;
  line-height: 1.2;
  color: var(--ink);
}

.entry__hint {
  margin-top: 26px;
  padding: 12px 22px;
  border: 1px solid rgba(58, 42, 32, 0.14);
  border-radius: 999px;
  background-color: var(--beige);
  box-shadow: 0 8px 20px rgba(74, 54, 40, 0.24);
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: 0.1em;
  line-height: 1;
  white-space: nowrap;
  color: var(--ink);
  animation: hintNudge 1.6s ease-in-out infinite;
}

/* Waits for the loader like everything else, so nothing is mid-cycle when
   the guest first sees it */
.is-loading .entry__seal::after,
.is-loading .entry__hint { animation-play-state: paused; }

/* Scroll lock during the opening screen. NOT position:fixed or overflow:
   hidden: either freezes Chrome iOS (opened from a chat app) in its short
   viewport and leaves a band of page under the overlay. The gesture itself
   is trapped in script.js. */
html.is-locked,
html.is-locked body {
  overscroll-behavior: none;
  touch-action: none;
}

/* ── MUSIC TOGGLE ────────────────────────────────────
   Hidden until html.is-open so it never sits on the opening screen. Four bars
   that equalise while playing and rest short while paused, so the state reads
   without an icon swap. */

.music-toggle {
  position: fixed;
  top: 14px;
  right: 14px;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  padding: 0;
  border: 1px solid rgba(58, 42, 32, 0.18);
  border-radius: 50%;
  background-color: rgba(244, 238, 229, 0.85);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  box-shadow: 0 4px 16px rgba(74, 54, 40, 0.18);
  cursor: pointer;
  opacity: 0;
  transform: translateY(-8px);
  pointer-events: none;
  transition: opacity 1s ease, transform 1s var(--ease-out);
}

.is-open .music-toggle {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  transition: opacity 1s ease 1.4s, transform 1s var(--ease-out) 1.4s;
}

.music-toggle:active { transform: scale(0.94); transition: transform 0.15s ease; }

.music-toggle:focus-visible {
  outline: 2px solid var(--brick);
  outline-offset: 3px;
}

.music-toggle__bars {
  display: flex;
  align-items: flex-end;
  gap: 2.5px;
  height: 15px;
}

.music-toggle__bars span {
  width: 2.5px;
  height: 100%;
  border-radius: 1px;
  background-color: var(--ink);
  transform-origin: bottom center;
  transform: scaleY(0.3);
  transition: transform 0.3s ease;
}

.music-toggle.playing .music-toggle__bars span { animation: eq 1.15s ease-in-out infinite; }
.music-toggle.playing .music-toggle__bars span:nth-child(2) { animation-delay: -0.76s; }
.music-toggle.playing .music-toggle__bars span:nth-child(3) { animation-delay: -0.38s; }
.music-toggle.playing .music-toggle__bars span:nth-child(4) { animation-delay: -0.95s; }

@keyframes eq {
  0%, 100% { transform: scaleY(0.3); }
  50%      { transform: scaleY(1); }
}

/* ── LOADING SCREEN ──────────────────────────────────
   style.css is render-blocking, so this paints taupe on the very first frame,
   no white flash before the linen arrives. */

#loader {
  position: fixed;
  inset: 0;
  /* Above everything on the page, including the desktop notice */
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--taupe);
  transition: opacity 0.6s ease;
}

#loader.is-hidden {
  opacity: 0;
  pointer-events: none;
}

.loader__spin {
  width: 32px;
  height: 32px;
  border: 1.5px solid rgba(58, 42, 32, 0.18);
  border-top-color: var(--brick);
  border-radius: 50%;
  animation: loaderSpin 0.9s linear infinite;
}

@keyframes loaderSpin {
  to { transform: rotate(360deg); }
}

/* Holds the hero on its first frame until the loader clears and the guest
   has tapped the opening screen, otherwise the entry plays out behind them
   and the guest uncovers a finished scene.
   The sheets below are gated by IntersectionObserver and need no hold. */
.is-loading .hero-art,
.is-loading .hero-art *,
.is-loading .scroll-hint,
.is-loading .scroll-hint__pill,
.is-locked .hero-art,
.is-locked .hero-art *,
.is-locked .scroll-hint,
.is-locked .scroll-hint__pill {
  animation-play-state: paused;
}

/* ── Reduced motion ──────────────────────────────────
   Everything still arrives, it just arrives already in place. */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  .hero-art, .hero-eyebrow, .hero-name, .hero-amp, .hero-kid,
  .hero-kid--a .hero-kid__in, .hero-kid--b .hero-kid__in,
  .hero-date, .hero-invite, .scroll-hint, .scroll-hint__pill,
  .reveal.is-in, .reveal.reveal--card.is-in,
  .entry__seal::after, .entry__hint, .music-toggle.playing .music-toggle__bars span {
    animation: none;
  }

  .reveal { opacity: 1; }
  .entry, .entry__seal { transition: none; }
  .film { transition: none; }
}

/* ── DESKTOP NOTICE ──────────────────────────────────
   The only min-width query in the file, and it replaces the page rather than
   laying it out again: this invitation is built for phones. */

.mobile-prompt { display: none; }

@media (min-width: 769px) {
  .mobile-prompt {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 9999;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 20px;
    padding: 40px;
    text-align: center;
    background-color: var(--taupe);
    background-image: url('taupe.jpg');
    background-size: var(--tile) var(--tile);
  }

  .mobile-prompt__icon {
    width: 52px;
    opacity: 0.8;
  }

  .mobile-prompt p {
    font-weight: 300;
    font-size: 18px;
    letter-spacing: 0.12em;
    line-height: 1.7;
    color: var(--ink);
  }

  .page, .entry, .music-toggle { display: none; }

  /* Outranks .mobile-prompt, so without this the desktop notice sits buried
     under a spinner that nothing will ever dismiss. */
  #loader { display: none; }

  /* ── ?debug ────────────────────────────────────────
     Development escape hatch: the real page in a phone-width column. Every
     selector is one class deeper than the rule it undoes, so ordering inside
     this block is not load-bearing. */
  .is-debug .mobile-prompt { display: none; }
  .is-debug #loader,
  .is-debug .entry,
  .is-debug .music-toggle { display: flex; }

  .is-debug .page {
    display: block;
    width: 430px;
    margin: 0 auto;
    box-shadow: 0 0 0 1px rgba(74, 54, 40, 0.3);
  }
}
