/* Setayesh & Aydin — 8 October 2026
   Ivory and antique gold. Names set in Pinyon Script, with a Great Vibes S and ampersand. */

/* The names' two faces are served from assets/fonts and preloaded from
   index.html. font-display: block means "invisible until loaded", never a
   fallback face that swaps out a moment later. Latin subset only. */
@font-face {
  font-family: "Pinyon Script";
  src: url("assets/fonts/pinyon-script-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

@font-face {
  font-family: "Great Vibes";
  src: url("assets/fonts/great-vibes-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}

:root {
  /* The page has one palette and no dark variant. This declines both the
     browser's own dark repaint (Chrome's auto dark theme on Android) and any
     dark default for form controls, so the ivory stays ivory everywhere. */
  color-scheme: only light;

  --ivory: #F9F4ED;
  --ivory-warm: #F3EADB;
  --gold: #A8843C;
  /* display type and hairlines */
  --gold-deep: #85662A;
  /* small gold text — 4.9:1 on ivory */
  --gold-glint: #E4C989;
  /* the shimmer highlight */
  --ink: #4A4137;
  /* 9.1:1 */
  --ink-soft: #7A6E60;
  /* 4.6:1 */

  --gap: clamp(2.6rem, 9vw, 4rem);

  /* The names: Pinyon Script, with the capital S of Setayesh and the ampersand
     in Great Vibes. Everything set in the names face reads these. */
  --names-family: "Pinyon Script", cursive;
  --names-size: clamp(3.1rem, 16vw, 4.8rem);
  --names-weight: 400;
  --names-style: normal;
  --names-lh: 1.3;
  --names-tracking: 0;
  --names-transform: none;
  --names-small: 0.7;
  /* scales the small settings: envelope note, footer, countdown */
  --amp-family: "Great Vibes", cursive;
  --amp-style: normal;
  --amp-size: clamp(2rem, 8.5vw, 2.6rem);

  /* the sprigs: sage greens against the ivory, with the page's gold in the
     stems and the pollen so they belong to the same card */
  --leaf-deep: #6B7548;
  --leaf: #8A9463;
  --leaf-pale: #A8B187;
  --leaf-wash: #C3C9AB;
  --stem: #8C7642;
  --petal: #FDFBF4;
  --petal-shade: #E8E4D4;
  --pollen: #C9A24B;
  --pollen-deep: #A57E2F;

  --bg-photo-opacity: 0.2;
  /* the photographs behind the three text sections */
}

/* The capital S of Setayesh is borrowed from Great Vibes; the rest is Pinyon. */
.names .names__line:nth-child(1)::first-letter,
.foot__names::first-letter,
.envelope__note-names::first-letter {
  font-family: "Great Vibes", cursive;
  font-size: 0.9em;
}

/* The ampersand inside a one-line "Setayesh & Aydin" (envelope card, footer)
   takes the same face as the one between the big names. */
.amp {
  font-family: var(--amp-family);
  font-style: var(--amp-style);
}

.names,
.countdown__done,
.foot__names,
.envelope__note-names {
  font-family: var(--names-family);
  font-weight: var(--names-weight);
  font-style: var(--names-style);
  letter-spacing: var(--names-tracking);
  text-transform: var(--names-transform);
}

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

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

body {
  margin: 0;
  background: var(--ivory);
  color: var(--ink);
  font-family: "EB Garamond", Garamond, Georgia, serif;
  font-size: 1.0625rem;
  line-height: 1.7;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

/* Faint paper tooth, so the ivory reads as a material rather than a flat fill. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

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

.card {
  position: relative;
  max-width: 620px;
  margin-inline: auto;
  background: var(--ivory);
  overflow-x: clip;
  padding-bottom: env(safe-area-inset-bottom);
}

/* ---------- hero ---------- */

.hero {
  position: relative;
  line-height: 0;
  overflow: hidden;
}

.hero img {
  display: block;
  width: 100%;
  height: auto;
}

/* The served photo is already cut above the shoes; this dissolves the
   remaining quayside into the page so the picture has no bottom edge. */
.hero__veil {
  position: absolute;
  inset: auto 0 -1px 0;
  height: 24%;
  pointer-events: none;
  background: linear-gradient(to bottom,
      rgba(249, 244, 237, 0) 0%,
      rgba(249, 244, 237, 0.25) 40%,
      rgba(249, 244, 237, 1) 80%,
      rgba(249, 244, 237, 1) 100%);
  /* The photograph underneath animates a transform for fifteen seconds, which
     puts it on a compositing layer of its own. Safari on iOS then has to
     promote this gradient as well, because it overlaps, and it drops the
     gradient for a frame or two while it does — the veil blinks out and comes
     back around the time the card settles. Giving it a layer up front leaves
     nothing to promote mid-flight. */
  transform: translateZ(0);
}

/* ---------- the photograph behind the text ---------- */

/* Each section is its own stacking context so the picture can sit at z -1:
   above the section's ivory, below its text and frame. It blends as a
   sepia print rather than a faded colour photo. */
.panel {
  position: relative;
  isolation: isolate;
  background: var(--ivory);
}

.panel__bg {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 18%, #000 82%, transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 18%, #000 82%, transparent);
}

.panel__bg img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 40%;
  /* the mosque and the person, whatever the section's height */
  opacity: var(--bg-photo-opacity);
  filter: sepia(0.8) saturate(0.65);
  mix-blend-mode: multiply;
}


/* ---------- gold frame ---------- */

.panel__inner {
  position: relative;
  padding: 0 1.5rem;
}

.panel--hero .panel__inner {
  padding-top: 2.75rem;
}

.frame {
  position: absolute;
  pointer-events: none;
}

/* Scroll mode: one frame around everything below the photograph. The image has
   a fixed 1.385 aspect and fills the card, so its height is calculable and
   nothing has to be measured at runtime. */
.frame--page {
  inset: calc(min(100vw, 620px) * 1.385 + 1.25rem) 0.9rem 1.1rem;
  z-index: 1;
  /* above the sections, which now carry their own background */
}

.frame--panel {
  inset: 1.25rem 0.4rem 1.1rem;
}

.mode-scroll .frame--panel,
.mode-swipe .frame--page {
  display: none;
}

/* The room between the last thing on the card and the frame around it. It sits
   on the panel, not on the footer: the signature is free to move up the panel
   without taking the card's bottom margin with it. */
.mode-scroll .deck>.panel:last-child .panel__inner {
  padding-bottom: calc(var(--gap) * 1.1);
}

.frame i {
  position: absolute;
  background: var(--gold);
  opacity: 0.55;
}

.frame i:nth-child(1) {
  top: 0;
  left: 0;
  height: 1px;
  width: 100%;
  transform-origin: left center;
}

.frame i:nth-child(2) {
  top: 0;
  right: 0;
  width: 1px;
  height: 100%;
  transform-origin: center top;
}

.frame i:nth-child(3) {
  bottom: 0;
  right: 0;
  height: 1px;
  width: 100%;
  transform-origin: right center;
}

.frame i:nth-child(4) {
  bottom: 0;
  left: 0;
  width: 1px;
  height: 100%;
  transform-origin: center bottom;
}

/* ---------- names ---------- */

.intro {
  position: relative;
  text-align: center;
}

/* The artwork itself is defined once in index.html; this only holds it. */
.sprig-source {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* One sprig either side of the opening line, reaching up into the dissolved
   foot of the photograph. */
.sprig {
  position: absolute;
  top: -3.4rem;
  width: clamp(5rem, 19vw, 6.8rem);
  height: auto;
  opacity: 0.88;
  pointer-events: none;
  /* Behind the words, above the section's photograph: big enough to read as a
     spray, and the opening line lies over the leaves the way it would on a
     printed card. .panel carries isolation: isolate, so -1 lands between its
     background picture and its text rather than behind the card. */
  z-index: -1;
}

.sprig--l {
  left: 0;
}

.sprig--r {
  right: 0;
  transform: scaleX(-1);
}

.eyebrow {
  margin: 0 auto;
  max-width: 20em;
  font-family: Cinzel, Georgia, serif;
  font-size: clamp(0.6rem, 2.5vw, 0.6rem);
  letter-spacing: 0.24em;
  text-indent: 0.24em;
  text-transform: uppercase;
  line-height: 1.9;
  color: var(--gold-deep);
  text-wrap: balance;
}

.names {
  margin: 0.5rem 0 0;
  font-size: var(--names-size);
  line-height: var(--names-lh);
  color: var(--gold);
}

.names__line {
  display: block;
  padding-inline: 0.06em;
  /* the scripts' swashes overhang the glyph box */
  text-indent: var(--names-tracking);
  /* balances the trailing tracking on a centred line */
}

.names__amp {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.9rem;
  margin-block: -0.04em;
  margin-top: 0.5rem;
  font-family: var(--amp-family);
  font-style: var(--amp-style);
  font-weight: var(--names-weight);
  font-size: var(--amp-size);
  line-height: 1;
  color: var(--gold-deep);
}

.names__rule {
  display: block;
  height: 1px;
  width: clamp(2.5rem, 16vw, 5.5rem);
  background: var(--gold);
  opacity: 0.5;
}

/* ---------- details ---------- */

.detail {
  width: min(100%, 18.5rem);
  margin: var(--gap) auto 0;
  display: grid;
  grid-template-columns: auto 1px 1fr;
  align-items: center;
  gap: 0 1rem;
  text-align: right;
}

/* All three are placed explicitly. The ::before is the first grid child, so
   leaving auto-placement to sort it out puts the icon in the 1fr column and
   wraps the text onto a second row. */
.detail::before {
  content: "";
  grid-area: 1 / 2;
  align-self: stretch;
  background: var(--gold);
  opacity: 0.4;
}

.ico {
  grid-area: 1 / 1;
  align-self: center;
  width: 1.55rem;
  height: 1.55rem;
  fill: none;
  stroke: var(--gold);
  stroke-width: 1.15;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.detail__body {
  grid-area: 1 / 3;
  padding-block: 0.15rem;
}

.detail__body p {
  margin: 0;
}

.detail__lead {
  font-family: Cinzel, Georgia, serif;
  font-size: clamp(0.72rem, 3.4vw, 0.8125rem);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1.6;
}

.detail__lead--fa {
  font-family: Vazirmatn, "Segoe UI", Tahoma, sans-serif;
  font-weight: 500;
  font-size: 1.0625rem;
  letter-spacing: 0;
  text-transform: none;
}

.detail__sub {
  font-size: 0.95rem;
  font-style: italic;
  color: var(--ink-soft);
  line-height: 1.5;
}

.detail__fa {
  font-family: Vazirmatn, "Segoe UI", Tahoma, sans-serif;
  font-weight: 300;
  font-size: 0.9375rem;
  line-height: 1.9;
  color: var(--ink-soft);
}

/* ---------- countdown ---------- */

.countdown {
  margin-top: var(--gap);
}

.panel--hero .panel__inner>.intro {
  text-align: center;
}

.countdown__row {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  max-width: 21rem;
  margin: 0 auto;
  padding: 0;
  list-style: none;
  text-align: center;
}

.countdown__row li {
  padding-block: 0.2rem;
  border-inline-start: 1px solid rgba(168, 132, 60, 0.3);
}

.countdown__row li:first-child {
  border-inline-start: 0;
}

.countdown__n {
  display: block;
  font-family: Cinzel, Georgia, serif;
  font-size: clamp(1.35rem, 6.2vw, 1.7rem);
  line-height: 1.25;
  color: var(--gold);
  font-variant-numeric: lining-nums tabular-nums;
}

.countdown__l {
  display: block;
  font-family: Cinzel, Georgia, serif;
  font-size: 0.6875rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.countdown__done {
  margin: 0;
  font-size: calc(3rem * var(--names-small));
  line-height: 1.1;
  color: var(--gold);
}

/* ---------- map ---------- */

.map {
  position: relative;
  margin: var(--gap) auto 0;
  aspect-ratio: 16 / 10;
  max-height: 34svh;
  overflow: hidden;
  border: 1px solid rgba(168, 132, 60, 0.5);
  outline: 1px solid rgba(168, 132, 60, 0.22);
  outline-offset: 4px;
  background: var(--ivory-warm);
}

.map__frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  filter: sepia(0.4) saturate(0.65) contrast(0.95);
  /* tones the map to the paper */
}

/* Until it is tapped the map is a picture: a swipe across it turns the page. */
.map__veil {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 0.6rem;
  border: 0;
  background: transparent;
  cursor: pointer;
  display: grid;
  align-content: start;
  justify-items: end;
  /* top right: clear of the pin and of Google's own bar */
}

.map__veil>span:first-child {
  padding: 0.35rem 0.7rem;
  border: 1px solid rgba(168, 132, 60, 0.45);
  background: rgba(249, 244, 237, 0.92);
  font-family: Cinzel, Georgia, serif;
  font-size: 0.6rem;
  letter-spacing: 0.18em;
  text-indent: 0.18em;
  text-transform: uppercase;
  color: var(--gold-deep);
}

.map__veil:focus-visible {
  outline: 2px solid var(--gold-deep);
  outline-offset: -4px;
}

.map.is-live .map__veil,
html:not(.js) .map__veil {
  display: none;
}

/* nothing would lift it without script */

.map+.detail {
  margin-top: 1.9rem;
}

/* ---------- buttons ---------- */

.actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.7rem;
  margin-top: var(--gap);
}

.btn {
  position: relative;
  display: inline-block;
  min-height: 2.875rem;
  padding: 0.68rem 1.35rem;
  border: 1px solid rgba(168, 132, 60, 0.6);
  font-family: Cinzel, Georgia, serif;
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-indent: 0.14em;
  text-transform: uppercase;
  color: var(--gold-deep);
  text-decoration: none;
  min-width: 12.5rem;
  /* so the pair matches when they stack */
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  isolation: isolate;
  transition: color 240ms ease, border-color 240ms ease;
}

/* the gold wipes in from the left rather than simply switching colour */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 340ms cubic-bezier(0.32, 0.72, 0.28, 1);
}

.btn:hover {
  color: var(--ivory);
  border-color: var(--gold);
}

.btn:hover::before {
  transform: scaleX(1);
}

.btn:focus-visible {
  outline: 2px solid var(--gold-deep);
  outline-offset: 3px;
}

.btn--solid {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--ivory);
}

.btn--solid::before {
  background: var(--gold-deep);
}

.btn--solid:hover {
  color: var(--ivory);
  border-color: var(--gold-deep);
}

/* ---------- divider ---------- */

.divider {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.85rem;
  margin: var(--gap) auto 0;
  max-width: 15rem;
}

.divider__rule {
  flex: 1;
  height: 1px;
  background: var(--gold);
  opacity: 0.5;
}

.divider__heart {
  width: 0.95rem;
  height: 0.95rem;
  flex: none;
  fill: var(--gold);
}

/* ---------- rsvp & footer ---------- */

.rsvp {
  margin-top: var(--gap);
  text-align: center;
}

.rsvp__lead {
  margin: 0 auto 1.6rem;
  max-width: 20rem;
  font-size: 1.0625rem;
  font-style: italic;
  color: var(--ink-soft);
  text-wrap: balance;
}

.party {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  margin: 0 auto 1.4rem;
}

.party__label {
  margin-right: 0.4rem;
  font-family: Cinzel, Georgia, serif;
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.party__step {
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid rgba(168, 132, 60, 0.55);
  border-radius: 50%;
  background: none;
  display: grid;
  place-items: center;
  font: 1.15rem/1 "EB Garamond", Garamond, Georgia, serif;
  color: var(--gold-deep);
  cursor: pointer;
  transition: background-color 200ms ease, color 200ms ease;
}

.party__step:hover:not(:disabled) {
  background: var(--gold);
  color: var(--ivory);
}

.party__step:focus-visible {
  outline: 2px solid var(--gold-deep);
  outline-offset: 3px;
}

.party__step:disabled {
  opacity: 0.35;
  cursor: default;
}

.party__n {
  min-width: 1.4em;
  text-align: center;
  font-family: Cinzel, Georgia, serif;
  font-size: 1.05rem;
  color: var(--gold);
  font-variant-numeric: lining-nums tabular-nums;
}

html:not(.js) .party {
  display: none;
}

/* the count only reaches the message through script */

.actions--rsvp {
  margin-top: 0;
}

.foot {
  margin-top: var(--gap);
  text-align: center;
}

.foot__names {
  margin: 0;
  font-size: calc(2.6rem * var(--names-small));
  line-height: 1;
  color: var(--gold);
  opacity: 0.85;
}

/* ============================ motion ============================ */

@media (prefers-reduced-motion: no-preference) {

  /* The photograph settles, once, as the page opens. */
  .hero img {
    animation: settle 15s cubic-bezier(0.16, 0.7, 0.3, 1) both;
  }

  @keyframes settle {
    from {
      transform: scale(1.075);
    }

    to {
      transform: scale(1);
    }
  }

  /* The signature moment: each name is written on, left to right, behind a
     soft leading edge. Browsers without mask support just show the text. */
  .names__line {
    -webkit-mask-image: linear-gradient(90deg, #000 0 34%, transparent 40%);
    mask-image: linear-gradient(90deg, #000 0 34%, transparent 40%);
    -webkit-mask-size: 300% 100%;
    mask-size: 300% 100%;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    animation: write 4s cubic-bezier(0.36, 0.1, 0.2, 1) both;
  }

  /* nth-child, not nth-of-type: the ampersand is also a span, so it would
     otherwise absorb Aydin's turn. Children are 1 = Setayesh, 2 = &, 3 = Aydin.
     The delays count from the moment the seal is pressed (the animation is
     held paused until then), so they wait out the envelope: it is torn down at
     5 s (TEARDOWN_AFTER in script.js) and the eyebrow arrives just after. */
  .names .names__line:nth-child(1) {
    animation-delay: 5.8s;
  }

  .names .names__line:nth-child(3) {
    animation-delay: 8.1s;
  }

  @keyframes write {
    from {
      -webkit-mask-position: 100% 0;
      mask-position: 100% 0;
    }

    to {
      -webkit-mask-position: 0% 0;
      mask-position: 0% 0;
    }
  }

  /* One glint of gold leaf travels across the names once they are written.
     The gradient tiles and opens and closes on --gold, so the letters stay
     filled wherever the glint happens to be — with no-repeat the background
     would sit off the element for most of the sweep and, against
     color: transparent, the names would simply disappear. */
  @supports ((-webkit-background-clip: text) or (background-clip: text)) {
    .names__line {
      background-image: linear-gradient(100deg,
          var(--gold) 0%, var(--gold) 42%, var(--gold-glint) 50%,
          var(--gold) 58%, var(--gold) 100%);
      background-size: 280% 100%;
      background-repeat: repeat;
      background-position: 0% 0;
      -webkit-background-clip: text;
      background-clip: text;
      color: transparent;
      animation: write 4s cubic-bezier(0.36, 0.1, 0.2, 1) both,
        glint 5.6s ease-in-out;
    }

    /* Paired delays — a single value would be reused for the glint as well. */
    .names .names__line:nth-child(1) {
      animation-delay: 5.8s, 12.9s;
    }

    .names .names__line:nth-child(3) {
      animation-delay: 6.1s, 13.2s;
    }

    @keyframes glint {
      from {
        background-position: 130% 0;
      }

      to {
        background-position: -30% 0;
      }
    }
  }

  /* The sprigs grow out of their own stem foot rather than being drawn on:
     these are painted leaves, not line work. The mirror lives on the <svg>, so
     the growth goes on the <use> inside it and the two never fight. */
  .sprig {
    opacity: 0;
  }

  .sprig.is-in {
    animation: sprigFade 2s ease 1s both;
  }

  .sprig use {
    transform-box: view-box;
    transform-origin: 12% 97%;
  }

  .sprig.is-in use {
    animation: sprigGrow 2.4s cubic-bezier(0.22, 0.68, 0.24, 1) 1s both;
  }

  @keyframes sprigFade {
    from {
      opacity: 0;
    }

    to {
      opacity: 0.88;
    }
  }

  @keyframes sprigGrow {
    from {
      transform: scale(0.62) rotate(-7deg);
    }

    to {
      transform: none;
    }
  }

  /* The frame draws itself, corner to corner. */
  .frame i {
    transform: scale(0);
  }

  .frame.is-in i:nth-child(1) {
    animation: drawX 1400ms ease-out 400ms both;
  }

  .frame.is-in i:nth-child(2) {
    animation: drawY 1400ms ease-out 1120ms both;
  }

  .frame.is-in i:nth-child(3) {
    animation: drawX 1400ms ease-out 1800ms both;
  }

  .frame.is-in i:nth-child(4) {
    animation: drawY 1400ms ease-out 2480ms both;
  }

  @keyframes drawX {
    from {
      transform: scaleX(0);
    }

    to {
      transform: scaleX(1);
    }
  }

  @keyframes drawY {
    from {
      transform: scaleY(0);
    }

    to {
      transform: scaleY(1);
    }
  }

  /* Everything else rises into place as it is reached. */
  [data-reveal] {
    opacity: 0;
    transform: translateY(1.1rem);
    transition: opacity 1800ms ease, transform 1800ms cubic-bezier(0.2, 0.7, 0.25, 1);
  }

  [data-reveal].is-in {
    opacity: 1;
    transform: none;
  }

  .names__rule {
    transform: scaleX(0);
    transition: transform 1800ms cubic-bezier(0.2, 0.7, 0.25, 1) 520ms;
  }

  .names__amp.is-in .names__rule {
    transform: scaleX(1);
  }

  .divider__rule {
    transform: scaleX(0);
    transition: transform 1800ms ease 240ms;
  }

  .divider.is-in .divider__rule {
    transform: scaleX(1);
  }

  /* the countdown digit lifts as it changes */
  .countdown__n.is-ticking {
    animation: tick 420ms ease-out;
  }

  @keyframes tick {
    from {
      opacity: 0;
      transform: translateY(0.34em);
    }

    to {
      opacity: 1;
      transform: none;
    }
  }
}

/* ---------- desktop ---------- */

@media (min-width: 680px) {
  body {
    background: #EFE6D7;
  }

  .card {
    box-shadow: 0 2px 60px rgba(90, 68, 30, 0.16);
  }

  .panel__inner {
    padding-inline: 3rem;
  }

  .frame--page {
    inset: calc(min(100vw, 620px) * 1.385 + 1.6rem) 1.6rem 1.6rem;
  }

  .mode-scroll .deck>.panel:last-child .panel__inner {
    padding-bottom: calc(var(--gap) * 1.25);
  }

  .detail {
    gap: 0 1.35rem;
  }
}

/* ======================= envelope ======================= */

/* Progressive enhancement: without JS the .js class is never set, the
   envelope never displays, the page never locks, and the invitation
   behaves exactly as it did before. */

.envelope {
  display: none;
}

.js .envelope {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: clamp(1.75rem, 7vw, 2.75rem);
  background: var(--ivory);
}

.js .envelope[hidden] {
  display: none;
}

.js:not(.is-open),
.js:not(.is-open) body {
  overflow: hidden;
}

/* Hold the opening animations at their first frame until the flap lifts,
   so nobody watches the names write themselves behind a closed envelope. */
.js:not(.is-open) .names__line,
.js:not(.is-open) .hero img {
  animation-play-state: paused;
}

\1

/* Nothing on the envelope is shown in a fallback face: script.js adds
   is-ready once the fonts are in (or after three seconds). */
.js:not(.is-ready) .envelope__hint,
.js:not(.is-ready) .envelope__note,
.js:not(.is-ready) .envelope__to,
.js:not(.is-ready) .envelope__mono {
  visibility: hidden;
}

/* paper tooth over the envelope too: body::after sits beneath this overlay */
.js .envelope::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* The shell stays flat: its children stack by z-index, not by 3D depth, so the
   flap can be told exactly when it goes behind the rising card. Only the flap
   itself is a 3D context, for its two faces. */
.envelope__shell {
  position: relative;
  width: min(84vw, 400px);
  aspect-ratio: 1 / 0.66;
  perspective: 1500px;
  perspective-origin: 50% 20%;
  border-radius: 4px;
  box-shadow: 0 18px 50px rgba(90, 68, 30, 0.2);
}

.envelope__shell>* {
  position: absolute;
}

/* The pocket and its front are SVG (see index.html): the back carries the
   patterned liner, the front the three folds that meet in a V. */
.envelope__back,
.envelope__front {
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.envelope__back {
  z-index: 1;
}

.envelope__front {
  z-index: 3;
}

/* the card, tucked in, that rises out */
.envelope__note {
  z-index: 2;
  left: 4%;
  right: 4%;
  top: 7%;
  height: 86%;
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 0.55rem;
  border: 1px solid rgba(168, 132, 60, 0.5);
  outline: 1px solid rgba(168, 132, 60, 0.22);
  /* the inner of a double rule */
  outline-offset: -5px;
  border-radius: 2px;
  background: var(--ivory);
  transform: translateY(5%);
}

.envelope__note-names {
  font-size: calc(clamp(2rem, 9vw, 2.6rem) * var(--names-small));
  line-height: 1;
  color: var(--gold);
}

.envelope__note-rule {
  width: 3rem;
  height: 1px;
  background: var(--gold);
  opacity: 0.5;
}

.envelope__note-date {
  font-family: Cinzel, Georgia, serif;
  font-size: 0.58rem;
  letter-spacing: 0.22em;
  text-indent: 0.22em;
  text-transform: uppercase;
  color: var(--gold-deep);
}

/* the guest's name, written on the front below the seal (?to=Name) */
.envelope__to {
  z-index: 3;
  left: 10%;
  right: 10%;
  top: 79%;
  text-align: center;
  font-family: var(--names-family);
  font-weight: var(--names-weight);
  font-style: var(--names-style);
  letter-spacing: var(--names-tracking);
  text-transform: var(--names-transform);
  font-size: calc(clamp(1.6rem, 8vw, 2rem) * var(--names-small));
  line-height: 1;
  color: var(--gold-deep);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.envelope__to[hidden] {
  display: none;
}

/* The flap, folded down when closed. Two faces: the printed front, and the
   lined underside that turns to face the guest as the flap lifts. */
.envelope__flap {
  top: 0;
  left: 0;
  width: 100%;
  height: 59.1%;
  z-index: 4;
  transform-origin: top center;
  transform: rotateX(0deg);
  transform-style: preserve-3d;
}

.envelope__flap-face,
.envelope__flap-liner {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

.envelope__flap-liner {
  transform: rotateX(180deg);
}

/* the wax seal at the point of the flap */
.envelope__seal {
  z-index: 5;
  top: 57%;
  left: 50%;
  width: clamp(4.2rem, 18vw, 5.2rem);
  aspect-ratio: 1;
  margin: 0;
  padding: 0;
  border: 0;
  display: block;
  background: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transform: translate(-50%, -50%);
}

.envelope__seal:focus-visible {
  outline: 2px solid var(--gold-deep);
  outline-offset: 6px;
  border-radius: 50%;
}

.envelope__wax {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  /* the molten edge and its shadow reach past the box */
  filter: drop-shadow(0 4px 7px rgba(90, 68, 30, 0.38));
}

.envelope__mono {
  font-family: "Pinyon Script", cursive;
}

.envelope__mono-gv {
  font-family: "Great Vibes", cursive;
}

.envelope__hint {
  margin: 0;
  font-family: Cinzel, Georgia, serif;
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  text-indent: 0.22em;
  text-transform: uppercase;
  color: var(--gold-deep);
  opacity: 0.75;
}

/* ---- opening ----
   Keyframes rather than transitions: this is one choreographed sequence, so
   the timings belong in one place, and z-index can step discretely at the
   exact frame the flap clears the pocket and the card comes out in front. */

.is-open .envelope {
  pointer-events: none;
  animation: envFade 1560ms ease 3240ms both;
}

.is-open .envelope__hint {
  animation: hintOut 480ms ease both;
}

.is-open .envelope__seal {
  animation: sealBreak 840ms cubic-bezier(0.5, -0.4, 0.7, 0.4) both;
}

.is-open .envelope__flap {
  animation: flapLift 1800ms cubic-bezier(0.62, 0.02, 0.28, 1) 600ms both;
}

.is-open .envelope__note {
  animation: noteRise 1840ms cubic-bezier(0.2, 0.8, 0.25, 1) 1600ms both;
}

.is-open .card {
  animation: cardSettle 2200ms cubic-bezier(0.2, 0.75, 0.25, 1) 3000ms both;
}

@keyframes sealBreak {
  from {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1) rotate(0deg);
  }

  to {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.5) rotate(-18deg);
  }
}

/* A positive angle swings the tip toward the guest and up, over the pocket;
   a negative one would swing it into the page. Past 90 degrees the lined
   underside faces the guest (backface-visibility on the two faces does the
   swap), and z-index steps at that frame so the rising card passes in front. */
@keyframes flapLift {
  0% {
    transform: rotateX(0deg);
    z-index: 4;
  }

  49% {
    z-index: 4;
  }

  50% {
    z-index: 1;
  }

  100% {
    transform: rotateX(176deg);
    z-index: 1;
  }
}

/* The card keeps the z-index it has at rest (2: above the pocket's back, below
   its front), for the whole rise. It is a card coming OUT of an envelope, so
   the part still inside has to stay behind the front and its folds — stepping
   it above them would lift the whole card onto the envelope instead. It shows
   through the mouth, and above the top edge it passes in front of the opened
   flap, which by then has stepped down to 1. */
@keyframes noteRise {
  0% {
    transform: translateY(5%);
    box-shadow: 0 0 0 rgba(90, 68, 30, 0);
  }

  100% {
    transform: translateY(-70%);
    box-shadow: 0 14px 30px rgba(90, 68, 30, 0.22);
  }
}

@keyframes envFade {
  from {
    opacity: 1;
  }

  to {
    opacity: 0;
  }
}

@keyframes hintOut {
  from {
    opacity: 0.75;
  }

  to {
    opacity: 0;
  }
}

@keyframes cardSettle {
  from {
    transform: scale(0.965);
  }

  to {
    transform: scale(1);
  }
}

/* Reduced motion: the envelope is still there and still opens, it simply
   dissolves rather than unfolding. */
@media (prefers-reduced-motion: reduce) {

  .is-open .envelope__seal,
  .is-open .envelope__flap,
  .is-open .envelope__note,
  .is-open .card {
    animation: none;
  }

  .js:not(.is-open) .card {
    transform: none;
  }

  .is-open .envelope {
    animation: envFade 480ms ease both;
  }
}

@media (min-width: 680px) {
  .envelope__shell {
    width: 440px;
  }
}

/* ======================= swipe mode ======================= */

/* In scroll mode the panels are plain blocks and the page reads as one
   continuous card — the markup order is already the reading order, so nothing
   needs rearranging. */

.dots {
  display: none;
}

.mode-swipe .card {
  display: grid;
  grid-template-rows: 1fr auto;
  height: 100svh;
  overflow: hidden;
}

.mode-swipe .deck {
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}

.mode-swipe .deck::-webkit-scrollbar {
  display: none;
}

.mode-swipe .panel {
  position: relative;
  flex: 0 0 100%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
  display: grid;
  align-content: center;
  overflow-y: auto;
  scrollbar-width: none;
}

.mode-swipe .panel::-webkit-scrollbar {
  display: none;
}

/* The photograph keeps its full composition and its full width: on a short
   screen the cover card scrolls a little rather than cutting or shrinking the
   picture, and a vertical flick scrolls the card before it turns the page. */
/* The frame belongs to the whole card here, not to the text block inside it,
   so it resolves against .panel rather than .panel__inner. That is why the
   markup keeps it outside .panel__inner: the reveal below transforms the inner
   block, and a transformed element is the containing block for absolutely
   positioned descendants, which would shrink this frame to the text. */
.mode-swipe .panel__inner {
  position: static;
  padding-block: 2rem;
}

/* The cover is carried by the photograph; a frame across it only collides. */
.mode-swipe .panel--hero .frame--panel {
  display: none;
}

/* The words under the photograph are the invitation, so they always stay on
   screen and the picture is what gives way. The hero's overflow: hidden leaves
   it an automatic minimum size of 0, so the grid takes every shortfall out of
   its row and none out of the text below. */
.mode-swipe .panel--hero {
  align-content: start;
}

/* Centring the picture inside that shortened box is what makes the crop even:
   the row is still the photograph's full height, so the part that does not fit
   hangs equally off the top and the bottom and is clipped on both sides. Left
   to align at the start, the whole loss would come off the bottom. */
.mode-swipe .hero {
  width: 100%;
  margin-inline: auto;
  min-height: 26svh;
  display: grid;
  align-content: center;
}

.mode-swipe .panel--hero .panel__inner {
  padding-block: 2.5rem 0;
}

.mode-swipe .names {
  font-size: calc(var(--names-size) * 0.5);
}

.mode-swipe .detail {
  margin-top: 0;
}

.mode-swipe .map {
  margin-top: 0;
}

.mode-swipe .map+.detail {
  margin-top: 1.75rem;
  margin-bottom: 8rem;
}

.mode-swipe .countdown,
.mode-swipe .actions {
  margin-top: 2.25rem;
}

.mode-swipe .actions--rsvp {
  margin-top: 0;
}

.mode-swipe .divider {
  margin-top: 0;
}

.mode-swipe .foot {
  margin-top: 2.5rem;
}

/* ---- page dots ---- */

/* script.js builds the buttons once the envelope is gone. The row is reserved
   at its final height from the first paint — 2rem of button inside 2rem of
   padding — so their arrival does not resize the deck above them. Keep this in
   step with the button height below. */
.mode-swipe .dots {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 0.35rem;
  /* Reserved at the height the filled row will be, so script.js adding the
     dots does not resize the deck above. The arrows are the tallest thing in
     it, so keep this in step with them: 3rem of arrow inside 2rem of
     padding. */
  min-height: calc(5rem + env(safe-area-inset-bottom));
  padding: 1rem 1rem calc(1rem + env(safe-area-inset-bottom));
}

.dots__list {
  display: flex;
  align-items: center;
  gap: 0.85rem;
}

.dots__list button {
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  display: grid;
  place-items: center;
}

.dots__list button::before {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background: var(--gold);
  opacity: 0.32;
  transition: opacity 260ms ease, transform 260ms cubic-bezier(0.2, 0.8, 0.25, 1);
}

.dots__list button[aria-current="true"]::before {
  opacity: 1;
  transform: scale(1.55);
}

/* ---- the arrows either side ---- */

.dots__arrow {
  width: 3rem;
  height: 3rem;
  padding: 0;
  border: 0;
  background: none;
  display: grid;
  place-items: center;
  cursor: pointer;
  color: var(--gold-deep);
  opacity: 0.85;
  transition: opacity 260ms ease, transform 260ms cubic-bezier(0.2, 0.8, 0.25, 1);
}

.dots__arrow svg {
  width: 1.4rem;
  height: 1.4rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.dots__arrow:hover:not(:disabled) {
  opacity: 1;
}

/* At the ends there is nowhere to go, so the arrow says so rather than
   disappearing and moving the dots sideways. */
.dots__arrow:disabled {
  opacity: 0.28;
  cursor: default;
}

#dots-prev:hover:not(:disabled) {
  transform: translateX(-0.12rem);
}

#dots-next:hover:not(:disabled) {
  transform: translateX(0.12rem);
}

.dots button:focus-visible {
  outline: 2px solid var(--gold-deep);
  outline-offset: -2px;
  border-radius: 50%;
}

/* Each card arrives as it reaches the middle, rather than all four at once. */
@media (prefers-reduced-motion: no-preference) {
  .mode-swipe .panel__inner {
    opacity: 0;
    transform: translateY(0.9rem);
    transition: opacity 1240ms ease, transform 1240ms cubic-bezier(0.2, 0.7, 0.25, 1);
  }

  .mode-swipe .panel.is-active .panel__inner {
    opacity: 1;
    transform: none;
  }
}

@media (min-width: 680px) {
  .mode-swipe .hero {
    width: 100%;
  }
}

/* ========================= the music button ========================= */

/* Revealed by script.js once the music is going (or once the browser has
   refused to start it, so the guest can start it themselves). */
.sound {
  position: fixed;
  right: 0.9rem;
  bottom: calc(0.9rem + env(safe-area-inset-bottom));
  z-index: 40;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  display: grid;
  place-items: center;
  border: 1px solid rgba(168, 132, 60, 0.45);
  border-radius: 50%;
  background: rgba(249, 244, 237, 0.88);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  color: var(--gold-deep);
  cursor: pointer;
  transition: background-color 240ms ease, color 240ms ease, border-color 240ms ease;
}

.sound[hidden] {
  display: none;
}

.sound:hover {
  background: var(--gold);
  border-color: var(--gold);
  color: var(--ivory);
}

.sound:focus-visible {
  outline: 2px solid var(--gold-deep);
  outline-offset: 3px;
}

.sound svg {
  width: 1.15rem;
  height: 1.15rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.sound__body {
  fill: currentColor;
  stroke-linejoin: round;
}

/* One icon, two states: the waves when it is playing, the cross when it is not. */
.sound__slash {
  display: none;
}

.sound.is-off .sound__wave {
  display: none;
}

.sound.is-off .sound__slash {
  display: block;
}

/* The dots sit bottom centre in swipe mode, so the button clears them. */
.mode-swipe .sound {
  bottom: calc(0.55rem + env(safe-area-inset-bottom));
}

/* ======================= preview panel (?dev=1) ======================= */

/* Built by script.js only when <html> carries .is-dev, so guests never load it. */

.dev {
  position: fixed;
  left: 0.75rem;
  bottom: 0.75rem;
  z-index: 60;
  width: 18rem;
  max-height: calc(100svh - 1.5rem);
  overflow: auto;
  padding: 0.85rem 1rem 1rem;
  border: 1px solid rgba(168, 132, 60, 0.5);
  background: rgba(249, 244, 237, 0.97);
  box-shadow: 0 10px 30px rgba(90, 68, 30, 0.22);
  font-family: "EB Garamond", Garamond, Georgia, serif;
  font-size: 0.9rem;
  line-height: 1.4;
  color: var(--ink);
}

.dev__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 0.5rem;
}

.dev__title,
.dev legend {
  font-family: Cinzel, Georgia, serif;
  font-size: 0.62rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--gold-deep);
}

.dev legend {
  padding: 0;
  margin-bottom: 0.2rem;
  color: var(--ink-soft);
}

.dev__close {
  margin: 0;
  padding: 0 0.2rem;
  border: 0;
  background: none;
  font: inherit;
  font-size: 1.2rem;
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
}

.dev fieldset {
  margin: 0 0 0.7rem;
  padding: 0;
  border: 0;
  min-width: 0;
}

.dev label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.15rem 0;
  cursor: pointer;
}

.dev input {
  margin: 0;
  accent-color: var(--gold);
}

.dev input[type="range"] {
  flex: 1;
}


.dev output {
  min-width: 2.2em;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}

.dev__btn {
  display: block;
  width: 100%;
  margin-top: 0.6rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid rgba(168, 132, 60, 0.6);
  background: none;
  font-family: Cinzel, Georgia, serif;
  font-size: 0.62rem;
  letter-spacing: 0.16em;
  text-indent: 0.16em;
  text-transform: uppercase;
  color: var(--gold-deep);
  cursor: pointer;
}

.dev__btn:hover {
  background: var(--gold);
  color: var(--ivory);
}