/* Andrew & Putri — undangan pernikahan.

   Susunan mengikuti referensi pilihan klien: satu foto latar yang diam di
   tempat, isi undangan digulir di atasnya, judul bagian berupa gabungan
   huruf serif dan huruf tulisan tangan, kartu acara putih, galeri dua
   kolom. Tone abu-abu, hitam, dan putih mengikuti undangan fisik.

   Aturan yang dipegang:
   - Semua isi tertulis di HTML. CSS hanya menyembunyikan sesuatu untuk
     dianimasikan kalau script.js sudah memasang kelas .reveal-on, jadi
     kalau skrip gagal, tidak ada isi yang ikut hilang.
   - Tidak ada yang boleh melebar melewati tepi kiri-kanan layar. */

:root {
  --putih: #fbfaf7;
  --tinta: #222220;
  --tinta-2: #4a4945;
  --emas: #c9a45f;
  --anggur: #93202f;
  --gelap: #2b2b29;

  --serif: 'Cormorant Garamond', 'Cormorant', Georgia, 'Times New Roman', serif;
  --script: 'Great Vibes', 'Snell Roundhand', 'Brush Script MT', cursive;
  --sans: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;

  /* Bayangan teks dua lapis: satu rapat supaya tepi huruf tegas, satu
     lebar dan lembut supaya huruf tetap terbaca di atas bagian foto yang
     terang. Ini jawaban untuk "tulisan bentrok dengan warna": huruf putih
     tidak pernah berdiri langsung di atas langit abu-abu tanpa pemisah. */
  --bayang: 0 1px 2px rgba(0, 0, 0, .5), 0 2px 18px rgba(0, 0, 0, .38);

  --lebar: 460px;
  --ease: cubic-bezier(.2, .7, .2, 1);
  --ease-kain: cubic-bezier(.62, .04, .28, 1);
}

*, *::before, *::after { box-sizing: border-box; }

[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  background: var(--gelap);
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--gelap);
  color: var(--putih);
  font-family: var(--serif);
  font-weight: 500;
  font-size: 18px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  /* Dua pengaman supaya halaman tidak bisa digeser ke kiri-kanan di iOS:
     clip memotong apa pun yang melebar tanpa membuat wadah gulir baru,
     dan touch-action menolak geseran mendatar sama sekali. */
  overflow-x: clip;
  touch-action: pan-y pinch-zoom;
}

body.is-locked { overflow: hidden; }

img, video { display: block; max-width: 100%; height: auto; }
p, figure, blockquote { margin: 0; }
a { color: inherit; }

/* Baris dibagi rata supaya tidak ada satu kata yatim di baris terakhir,
   seperti "S.H" yang tertinggal sendirian di bawah nama kediaman. */
h1, h2, h3, p { text-wrap: balance; }

.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}


/* ================================================================
   Huruf yang dipakai berulang
   ================================================================ */

.kecil {
  margin: 0;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .32em;
  text-transform: uppercase;
  text-shadow: var(--bayang);
}

/* Nama bertumpuk seperti di undangan fisik, dengan huruf pertama dari
   huruf tulisan tangan seperti di referensi. */
.nama-besar {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  font-family: var(--serif);
  font-weight: 600;
  font-size: clamp(42px, 13vw, 60px);
  line-height: 1;
  letter-spacing: .06em;
  text-transform: uppercase;
  text-shadow: var(--bayang);
}

.nama-besar__kata { display: block; white-space: nowrap; }

.nama-besar .awal {
  position: relative;
  top: .06em;
  margin-right: -.02em;
  font-family: var(--script);
  font-weight: 400;
  font-size: 1.36em;
  line-height: .7;
  letter-spacing: 0;
  text-transform: none;
}

.nama-besar__dan {
  font-family: var(--script);
  font-weight: 400;
  font-size: .8em;
  line-height: 1.15;
  letter-spacing: 0;
  text-transform: none;
}

/* Judul bagian: kata serif, lalu kata tulisan tangan yang menumpuk di
   bawahnya agak ke kanan - susunan khas referensi. */
.judul {
  margin: 0 0 40px;
  font-weight: 400;
  line-height: 1;
  text-shadow: var(--bayang);
}

.judul__serif {
  display: block;
  font-family: var(--serif);
  font-weight: 500;
  font-size: 60px;
  letter-spacing: .01em;
}

.judul__script {
  display: block;
  margin-top: -.34em;
  padding-left: 1.5em;
  font-family: var(--script);
  font-size: 64px;
  line-height: 1.25;
}


/* ================================================================
   Sampul: tirai 3D berfoto
   Mekanisme tirai dari versi awal (bilah kain yang melipat seperti
   akordeon lalu tersibak ke kiri-kanan), dengan foto sampul sebagai
   permukaannya. Tiap bilah memuat potongan foto sesuai letaknya di layar
   - dihitung script.js - jadi saat tertutup semuanya menyusun satu foto
   utuh, dan saat dibuka fotonya ikut terbelah dan melipat.
   ================================================================ */

.sampul {
  position: fixed;
  inset: 0;
  z-index: 50;
  overflow: hidden;
}

/* Napas pelan pada seluruh tirai. Diskalakan sebagai satu kesatuan, jadi
   potongan foto antar bilah tetap bersambung. */
.sampul__tirai {
  position: absolute;
  inset: 0;
  overflow: hidden;
  perspective: 1500px;
  perspective-origin: 50% 42%;
  animation: napas 16s ease-in-out infinite alternate;
}

/* Latar gelap di belakang kain. Saat bilah berputar, lebar proyeksinya
   menyusut dan menyisakan celah; tanpa latar ini celah itu terbaca
   sebagai jeruji, bukan lipatan. Memudar belakangan, jadi pembuka di
   bawahnya tersingkap seiring kain menjauh. */
.sampul__tirai::before {
  content: '';
  position: absolute;
  inset: 0;
  background: #121211;
}

.sampul.is-open .sampul__tirai::before {
  animation: pudar .9s var(--ease-kain) .7s forwards;
}

/* Lebar sisi (51%), jumlah bilah (8), dan lebar bilah (1,7 langkah
   dengan tumpang -0,7) harus sama dengan angka di pasangBilah() pada
   script.js - dari situ letak potongan foto tiap bilah dihitung. */
.sampul__sisi {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 1;
  width: 51%;
  display: flex;
  transform-style: preserve-3d;
}

.sampul__sisi--l { left: 0; }
.sampul__sisi--r { right: 0; flex-direction: row-reverse; }

.bilah {
  flex: 0 0 auto;
  position: relative;
  width: calc(100% / 8 * 1.7);
  margin-right: calc(100% / 8 * -.7);
  transform-origin: left center;
  backface-visibility: hidden;
  background: #3b3b39 url("img/sampul.webp") no-repeat;
}

.sampul__sisi--r .bilah {
  transform-origin: right center;
  margin-right: 0;
  margin-left: calc(100% / 8 * -.7);
}

/* Foto utuh yang menutupi bilah selama tirai diam. Bilah yang berdiri
   berdampingan meninggalkan garis sambungan tipis di bagian foto yang
   polos seperti langit; begitu tirai dibuka, foto ini hilang seketika dan
   bilah di bawahnya - yang memuat foto yang sama - langsung melipat. */
.sampul__foto {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 62%;
}

.sampul.is-open .sampul__foto { visibility: hidden; }

/* Sisi bilah yang membelakangi cahaya menggelap saat ia berputar. */
.bilah::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, rgba(0, 0, 0, .78), rgba(0, 0, 0, 0) 72%);
  opacity: 0;
}

.sampul.is-open .bilah {
  animation: bilah-lipat .9s var(--ease-kain) forwards;
  animation-delay: calc(var(--i, 0) * 52ms);
}

.sampul.is-open .sampul__sisi--r .bilah { animation-name: bilah-lipat-r; }

.sampul.is-open .bilah::after {
  animation: bilah-bayang .9s var(--ease-kain) forwards;
  animation-delay: calc(var(--i, 0) * 52ms);
}

.sampul.is-open .sampul__sisi--l { animation: sisi-keluar-l 1.2s var(--ease-kain) .52s forwards; }
.sampul.is-open .sampul__sisi--r { animation: sisi-keluar-r 1.2s var(--ease-kain) .52s forwards; }

@keyframes bilah-lipat {
  from { transform: rotateY(0deg) translateZ(0); }
  to   { transform: rotateY(-55deg) translateZ(5px); }
}

@keyframes bilah-lipat-r {
  from { transform: rotateY(0deg) translateZ(0); }
  to   { transform: rotateY(55deg) translateZ(5px); }
}

@keyframes bilah-bayang {
  from { opacity: 0; }
  to   { opacity: .58; }
}

@keyframes sisi-keluar-l {
  from { transform: translateX(0); }
  to   { transform: translateX(-88%); }
}

@keyframes sisi-keluar-r {
  from { transform: translateX(0); }
  to   { transform: translateX(88%); }
}

@keyframes pudar {
  to { opacity: 0; }
}

/* Teks dan tombol di atas tirai, bersama selubung gelapnya: gelap di
   atas tempat nama, bening di tengah tempat mereka berdiri, gelap lagi
   di bawah untuk tombol. Semuanya memudar lebih dulu saat tirai dibuka,
   jadi yang melipat hanya fotonya. */
.sampul__panggung {
  position: relative;
  z-index: 2;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
  /* Atas dikurangi dari 58px: blok teks di bawahnya bertambah satu
     baris (kotak nama tamu), dan di layar pendek itu bisa menimpa wajah
     di foto - padding lebih kecil menggeser seluruh blok naik tanpa
     mengecilkan hurufnya lagi. */
  padding: max(32px, calc(env(safe-area-inset-top) + 12px)) 24px
           max(60px, calc(env(safe-area-inset-bottom) + 44px));
  text-align: center;
  background: linear-gradient(180deg,
    rgba(22, 22, 21, .66) 0%,
    rgba(22, 22, 21, .34) 34%,
    rgba(22, 22, 21, 0) 54%,
    rgba(22, 22, 21, .08) 76%,
    rgba(22, 22, 21, .5) 100%);
  transition: opacity .5s ease, translate .5s ease;
}

.sampul.is-open .sampul__panggung {
  opacity: 0;
  translate: 0 -10px;
  pointer-events: none;
}

.sampul__isi {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.sampul__isi > * { animation: masuk 1s var(--ease) both; }
.sampul__isi > :nth-child(1) { animation-delay: .15s; }
.sampul__isi > :nth-child(2) { animation-delay: .3s; }
.sampul__isi > :nth-child(3) { animation-delay: .5s; }
.sampul__isi > :nth-child(4) { animation-delay: .6s; }

.sampul .kecil { margin-bottom: 22px; }

.sampul__kepada {
  margin-top: 14px;
  font-style: italic;
  font-size: 20px;
  text-shadow: var(--bayang);
}

/* "Bapak/Ibu/Saudara/i" - baris umum yang selalu tampil, sesuai undangan
   fisik. Nama khusus dari ?to= (.sampul__tamu di bawah) menambah, bukan
   menggantikan baris ini. */
.sampul__umum {
  margin-top: 0;
  font-family: var(--serif);
  font-weight: 600;
  /* Tetap lebih kecil dari .sampul__tamu: baris umum ini tidak boleh
     lebih menonjol dari nama tamu di bawahnya - nama tamu yang
     spesifik untuk penerima itu yang harus paling menarik perhatian,
     bukan sapaan generiknya. */
  font-size: 18px;
  letter-spacing: .01em;
  text-shadow: var(--bayang);
}

/* Nama tamu (dari ?to=) dulu memakai huruf sans kecil - satu-satunya
   teks di sampul yang tidak memakai huruf serif tema undangan, jadi
   terasa seperti label formulir, bukan bagian dari kartu undangan.
   Sekarang memakai huruf serif yang sama dengan nama mempelai
   (.orang__nama), dan dibingkai garis tepi saja (tanpa latar) supaya
   menonjol tapi tetap ringan - latar putih tipis dicoba lebih dulu,
   terasa terlalu "kotak formulir" menurut klien. */
.sampul__tamu {
  /* min-width mencegah kotak menyusut kecil-kecil untuk nama pendek
     (mis. "Ida") - .sampul__isi pakai align-items:center jadi lebar
     kotak defaultnya cuma mengikuti panjang teks. */
  min-width: 12em;
  max-width: 20em;
  margin-top: 4px;
  padding: 4px 12px;
  border: 1px solid rgba(255, 255, 255, .55);
  border-radius: 9px;
  font-family: var(--serif);
  font-weight: 600;
  /* Baris ini pernah diciutkan sampai 15px karena nama panjang bisa
     turun cukup jauh untuk menimpa wajah di foto sampul di layar
     pendek (375x667, seukuran iPhone SE). Sekarang dinaikkan lagi ke
     19px atas permintaan klien (dianggap terlalu kecil) - untuk
     mengimbangi, margin/padding di sekitar baris ini dan baris-baris
     di atasnya (.sampul__kepada, .sampul__umum) dipangkas, BUKAN
     hurufnya, dan .sampul__panggung padding-top juga dikurangi. Kalau
     mau naikkan ukuran lagi, uji dulu di 375x667 sebelum commit. */
  font-size: 19px;
  line-height: 1.25;
  letter-spacing: .01em;
  text-align: center;
  text-shadow: var(--bayang);
}

.tombol-buka {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 15px 26px;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: #fff;
  background: #151514;
  border: 1px solid rgba(255, 255, 255, .2);
  border-radius: 6px;
  box-shadow: 0 14px 30px -8px rgba(0, 0, 0, .65);
  cursor: pointer;
  animation: masuk 1s var(--ease) .85s both, ajak 2.6s ease-in-out 2.2s infinite;
}

.tombol-buka svg { width: 18px; height: 18px; flex: none; }


/* ================================================================
   Latar tetap
   ================================================================ */

/* Tinggi dipatok 100lvh (layar terbesar, saat bilah alamat Safari
   tersembunyi), bukan inset: 0. Dengan inset, tinggi latar ikut berubah
   setiap kali bilah alamat muncul-hilang, dan fotonya melompat. */
.latar {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 0;
  width: 100%;
  height: 100vh;
  height: 100lvh;
  overflow: hidden;
  background: #3b3b39;
}

.latar__foto {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 30%;
  opacity: 0;
  transition: opacity 1.6s ease;
}

.latar__foto.is-aktif { opacity: 1; }

/* z-index wajib: ::before muncul sebelum foto dalam urutan dokumen, jadi
   tanpa ini selubungnya tergambar di bawah foto dan tidak menggelapkan
   apa pun. */
.latar::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
}

/* Selubung baca: rata dan cukup gelap supaya teks putih kontras di mana
   pun letaknya di layar. */
.latar::before { background: rgba(26, 26, 25, .44); }

main {
  position: relative;
  z-index: 1;
}


/* ================================================================
   Pembuka
   Dikembalikan dari versi awal atas permintaan klien: foto penuh
   dengan butiran kertas, nama bertumpuk, tangkai daun, dan foto yang
   mundur perlahan saat halaman digulir. Hurufnya dipertebal sedikit
   dari versi awal (permintaan "tulisan agak ditebalkan"), tapi label
   dan tanggal tetap diredupkan supaya fotonya yang lebih dulu terlihat.
   ================================================================ */

/* Transisi ke bagian berikutnya, dibuat seperti larutan antar-adegan di
   film: pembuka menempel di layar (sticky - tidak ada sentakan saat mulai
   dan berhenti), fotonya mendekat sangat pelan sambil larut ke foto latar
   di belakangnya, dan teksnya naik lalu memudar. Bagian ayat meluncur
   naik di atasnya. Tidak ada bentuk kartu, sudut, atau kemiringan -
   versi kartu yang mundur terasa buatan.

   script.js mengisi dua angka:
   --p  seberapa jauh pembuka sudah tergulir (0 sampai 1, per satu layar),
   --pe angka yang sama setelah dilunakkan (smoothstep): pudar mulai pelan,
        cepat di tengah, dan melambat lagi di akhir, jadi tidak ada titik
        awal atau akhir yang terasa. */
.hero {
  position: relative;
  z-index: 0;
  min-height: 100vh;
  min-height: 100svh;
  display: grid;
  align-content: end;
  overflow: hidden;
  isolation: isolate;
}

/* Foto pembuka larut ke foto latar yang sama persis (cover.webp, lihat
   data-latar di index.html). Karena itu ukuran dan titik fokusnya
   disamakan dengan .latar__foto: tinggi 100lvh, fokus 50% 30%, skala
   awal 1.03. Di awal larutan keduanya berimpit, dan yang terlihat hanya
   foto yang terus mendekat - bukan dua foto yang saling menumpuk. */
.hero__frame {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: -1;
  height: 100vh;
  height: 100lvh;
  overflow: hidden;
  background: #1b1b19;
  opacity: calc(1 - var(--pe, 0));
  will-change: opacity;
}

/* Foto mendekat pelan dan naik sedikit - seperti kamera yang terus
   melaju ke dalam adegan saat adegannya larut. */
.hero__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 30%;
  transform-origin: 50% 35%;
  transform: translate3d(0, calc(var(--p, 0) * -1.5%), 0) scale(calc(1.03 + var(--pe, 0) * .09));
  will-change: transform;
}

/* Selubung untuk keterbacaan teks pembuka. */
.hero__frame::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(180deg,
    rgba(10, 10, 10, .28) 0%,
    rgba(10, 10, 10, .04) 30%,
    rgba(10, 10, 10, .55) 72%,
    rgba(10, 10, 10, .78) 100%);
}

/* Sedikit menggelap selama larut, supaya teks ayat yang meluncur di
   atasnya tetap terbaca. Opacity, bukan warna latar: opacity dikerjakan
   kartu grafis tanpa menggambar ulang. */
.hero__redup {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: #151514;
  opacity: calc(var(--pe, 0) * .45);
}

/* Butiran halus di atas foto: undangan cetak mereka dicetak di kertas,
   dan sedikit butir membuat foto di layar terasa sekeluarga dengan
   versi kertasnya. */
.hero__frame::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  opacity: .055;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* Teks naik lebih cepat dari foto dan sudah hilang seluruhnya sebelum
   ayat tiba di tempat yang sama. Versi sebelumnya masih tersisa samar
   (sekitar 26%) saat ayat datang, dan keduanya terbaca bertumpuk. */
.hero__text {
  padding: 32px 24px max(56px, calc(env(safe-area-inset-bottom) + 40px));
  text-align: center;
  transform: translate3d(0, calc(var(--p, 0) * -40vh), 0);
  opacity: calc(1 - var(--pe, 0) * 3.4);
}

/* Menempel hanya kalau mesin gulir di script.js jalan. Tanpa mesin itu
   kartunya tidak pernah memudar, dan pembuka yang menempel akan terlihat
   di balik seluruh isi undangan. */
.gulir-on .hero {
  position: sticky;
  top: 0;
}

/* Setelah terlewati, pembuka tetap menempel di atas tapi disembunyikan
   supaya tidak ikut digambar di sepanjang sisa halaman. */
.hero.is-lewat { visibility: hidden; }

/* Semua bagian sesudah pembuka berada di atasnya. */
.bagian { z-index: 1; }

.eyebrow {
  margin: 0;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 10.5px;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: rgba(251, 250, 247, .66);
  text-shadow: 0 1px 10px rgba(0, 0, 0, 1), 0 0 4px rgba(0, 0, 0, .95);
}

.couple {
  margin: 12px 0 0;
  display: grid;
  justify-items: center;
  gap: .1em;
  font-family: var(--serif);
  font-weight: 500;
  line-height: 1.04;
  letter-spacing: .15em;
  text-transform: uppercase;
  text-shadow: 0 2px 18px rgba(0, 0, 0, .8);
}

.couple__name { font-size: clamp(2.4rem, 11.5vw, 3.6rem); }

.couple__amp {
  font-style: italic;
  font-size: clamp(1rem, 4.2vw, 1.2rem);
  letter-spacing: .12em;
  text-transform: lowercase;
  color: rgba(251, 250, 247, .78);
}

/* Tangkai daun garis tipis mengapit satu titik merah anggur - mengutip
   rangkaian bunga putih di panggung dan buket merah di sesi foto. */
.rule--sprig {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .55rem;
  margin: 18px 0;
  color: rgba(251, 250, 247, .8);
}

.rule--sprig span {
  width: 6px;
  height: 6px;
  background: var(--anggur);
  box-shadow: 0 0 8px rgba(147, 32, 47, .55);
  transform: rotate(45deg);
}

.sprig { width: min(80px, 21vw); height: auto; opacity: .7; }
.sprig--flip { transform: scaleX(-1); }

.date {
  margin: 0;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 11.5px;
  letter-spacing: .3em;
  text-transform: uppercase;
  color: rgba(251, 250, 247, .86);
  text-shadow: 0 2px 16px rgba(0, 0, 0, .85);
}

.hero__tag {
  margin: 22px 0 0;
  font-style: italic;
  font-size: 16px;
  letter-spacing: .04em;
  color: rgba(251, 250, 247, .72);
  text-shadow: 0 1px 12px rgba(0, 0, 0, .8);
}

/* Penanda bahwa halaman masih berlanjut: tamu yang berhenti di foto
   pembuka tidak tahu ada jam dan alamat di bawahnya. */
.scroll-cue {
  display: block;
  width: 1px;
  height: 42px;
  margin: 30px auto 0;
  background: linear-gradient(180deg, transparent, rgba(251, 250, 247, .85));
  transform-origin: top center;
  animation: cue 2.6s var(--ease-kain) infinite;
}

@keyframes cue {
  0%, 100% { transform: scaleY(.45); opacity: .35; }
  45%      { transform: scaleY(1);   opacity: .9; }
}

/* Isi pembuka naik satu per satu setelah tirai dibuka. Tanpa skrip,
   .is-opened tidak pernah terpasang dan isinya langsung terlihat. */
html.is-opened .hero__text > * { animation: rise 1.1s var(--ease-kain) both; }
html.is-opened .hero__text > :nth-child(1) { animation-delay: .90s; }
html.is-opened .hero__text > :nth-child(2) { animation-delay: 1.00s; }
html.is-opened .hero__text > :nth-child(3) { animation-delay: 1.15s; }
html.is-opened .hero__text > :nth-child(4) { animation-delay: 1.25s; }
html.is-opened .hero__text > :nth-child(5) { animation-delay: 1.40s; }

/* Penanda gulir perlu dua animasi: naik bersama yang lain, lalu kembali
   berdenyut. Tanpa aturan ini denyutnya tertimpa animasi naik. */
html.is-opened .hero__text > .scroll-cue {
  animation: rise 1.1s var(--ease-kain) 1.6s both, cue 2.6s var(--ease-kain) 2.7s infinite;
}

@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}


/* ================================================================
   Ayat
   ================================================================ */

.bagian--ayat { padding-top: 88px; padding-bottom: 24px; }

.ayat { max-width: 20em; margin: 0 auto; }

.ayat p {
  font-style: italic;
  font-size: clamp(22px, 6.2vw, 27px);
  line-height: 1.5;
  text-shadow: var(--bayang);
}

.ayat cite {
  display: block;
  margin-top: 14px;
  font-family: var(--script);
  font-style: normal;
  font-size: 34px;
  line-height: 1.2;
  text-shadow: var(--bayang);
}


/* ================================================================
   Bingkai kaca (Turut Mengundang, teks Love Gift)
   Isian putih tipis supaya foto latar masih terlihat di baliknya, garis
   tepi luar, dan garis kedua di dalam seperti bingkai kartu cetak.
   ================================================================ */

.kaca {
  position: relative;
  background: rgba(255, 255, 255, .08);
  border: 1px solid rgba(255, 255, 255, .45);
  border-radius: 22px;
  box-shadow: 0 26px 50px -30px rgba(0, 0, 0, .75);
}

.kaca::before {
  content: '';
  position: absolute;
  inset: 7px;
  border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 16px;
  pointer-events: none;
}


/* ================================================================
   Bingkai kertas deckle
   Foto dicetak di atas kertas krem bertepi sobek - kertas handmade yang
   biasa dipakai undangan cetak mewah - lengkap dengan serat halus dan
   bayangan lembut yang mengikuti bentuk sobekannya. Foto mempelai
   mendapat mat lebih lebar dengan garis emas tipis.

   Bentuk sobekan dibuat dengan skrip sekali pakai (polygon acak dengan
   benih tetap), empat varian untuk foto kecil dan satu varian lebih dangkal untuk
   bingkai besar: kedalaman dalam persen ikut membesar di bingkai lebar
   dan akan memotong garis emasnya.
   ================================================================ */

:root {
  --sobek-1: polygon(0.00% 1.28%, 3.70% 0.76%, 7.41% 1.34%, 11.11% 1.20%, 14.81% 0.49%, 18.52% 0.84%, 22.22% 1.41%, 25.93% 1.05%, 29.63% 1.28%, 33.33% 1.55%, 37.04% 2.11%, 40.74% 1.70%, 44.44% 2.02%, 48.15% 2.99%, 51.85% 2.16%, 55.56% 1.25%, 59.26% 0.70%, 62.96% 0.90%, 66.67% 0.86%, 70.37% 0.87%, 74.07% 0.95%, 77.78% 1.65%, 81.48% 1.61%, 85.19% 1.33%, 88.89% 1.83%, 92.59% 2.26%, 96.30% 1.74%, 100.00% 1.55%, 99.12% 3.70%, 98.25% 7.41%, 98.55% 11.11%, 98.18% 14.81%, 97.97% 18.52%, 98.21% 22.22%, 98.67% 25.93%, 98.48% 29.63%, 98.83% 33.33%, 99.55% 37.04%, 99.09% 40.74%, 98.97% 44.44%, 99.03% 48.15%, 98.23% 51.85%, 98.00% 55.56%, 97.52% 59.26%, 97.34% 62.96%, 98.24% 66.67%, 98.77% 70.37%, 98.49% 74.07%, 98.93% 77.78%, 99.49% 81.48%, 99.15% 85.19%, 98.54% 88.89%, 99.13% 92.59%, 98.36% 96.30%, 97.75% 100.00%, 96.30% 98.53%, 92.59% 99.16%, 88.89% 99.65%, 85.19% 99.40%, 81.48% 98.92%, 77.78% 98.64%, 74.07% 99.08%, 70.37% 98.65%, 66.67% 97.90%, 62.96% 97.71%, 59.26% 98.27%, 55.56% 98.46%, 51.85% 98.14%, 48.15% 98.32%, 44.44% 99.13%, 40.74% 98.43%, 37.04% 98.05%, 33.33% 98.96%, 29.63% 99.57%, 25.93% 99.05%, 22.22% 98.53%, 18.52% 98.28%, 14.81% 98.28%, 11.11% 98.41%, 7.41% 97.68%, 3.70% 97.84%, 0.00% 98.39%, 1.18% 96.30%, 1.63% 92.59%, 2.36% 88.89%, 2.00% 85.19%, 1.13% 81.48%, 0.99% 77.78%, 1.42% 74.07%, 1.13% 70.37%, 0.78% 66.67%, 1.72% 62.96%, 1.81% 59.26%, 1.79% 55.56%, 1.63% 51.85%, 1.94% 48.15%, 1.96% 44.44%, 1.69% 40.74%, 0.77% 37.04%, 0.65% 33.33%, 1.10% 29.63%, 1.38% 25.93%, 0.77% 22.22%, 1.07% 18.52%, 1.76% 14.81%, 2.40% 11.11%, 1.48% 7.41%, 1.37% 3.70%);
  --sobek-2: polygon(0.00% 1.61%, 3.70% 1.75%, 7.41% 1.14%, 11.11% 0.78%, 14.81% 0.35%, 18.52% 0.79%, 22.22% 1.15%, 25.93% 1.08%, 29.63% 1.17%, 33.33% 1.47%, 37.04% 2.16%, 40.74% 1.96%, 44.44% 1.75%, 48.15% 1.42%, 51.85% 1.83%, 55.56% 1.60%, 59.26% 1.17%, 62.96% 0.38%, 66.67% 0.80%, 70.37% 1.03%, 74.07% 1.86%, 77.78% 1.76%, 81.48% 1.49%, 85.19% 1.65%, 88.89% 2.18%, 92.59% 2.14%, 96.30% 1.78%, 100.00% 1.85%, 97.66% 3.70%, 98.67% 7.41%, 98.85% 11.11%, 98.52% 14.81%, 99.64% 18.52%, 99.36% 22.22%, 98.71% 25.93%, 99.07% 29.63%, 98.87% 33.33%, 97.78% 37.04%, 98.32% 40.74%, 98.29% 44.44%, 97.71% 48.15%, 98.43% 51.85%, 98.92% 55.56%, 98.44% 59.26%, 98.80% 62.96%, 98.71% 66.67%, 98.85% 70.37%, 99.10% 74.07%, 98.65% 77.78%, 98.14% 81.48%, 98.32% 85.19%, 97.98% 88.89%, 97.54% 92.59%, 98.59% 96.30%, 98.66% 100.00%, 96.30% 97.77%, 92.59% 98.46%, 88.89% 98.53%, 85.19% 97.94%, 81.48% 97.32%, 77.78% 99.18%, 74.07% 99.05%, 70.37% 98.60%, 66.67% 99.35%, 62.96% 99.26%, 59.26% 98.26%, 55.56% 98.09%, 51.85% 98.25%, 48.15% 98.27%, 44.44% 97.72%, 40.74% 98.44%, 37.04% 98.56%, 33.33% 98.26%, 29.63% 98.71%, 25.93% 99.18%, 22.22% 99.32%, 18.52% 98.92%, 14.81% 98.72%, 11.11% 98.94%, 7.41% 98.23%, 3.70% 98.04%, 0.00% 98.12%, 1.24% 96.30%, 2.29% 92.59%, 1.60% 88.89%, 1.45% 85.19%, 1.92% 81.48%, 1.43% 77.78%, 0.67% 74.07%, 1.43% 70.37%, 1.21% 66.67%, 0.43% 62.96%, 1.04% 59.26%, 1.67% 55.56%, 1.24% 51.85%, 1.55% 48.15%, 2.66% 44.44%, 2.86% 40.74%, 1.76% 37.04%, 2.02% 33.33%, 1.12% 29.63%, 0.73% 25.93%, 1.19% 22.22%, 1.25% 18.52%, 0.95% 14.81%, 1.76% 11.11%, 1.68% 7.41%, 1.73% 3.70%);
  --sobek-3: polygon(0.00% 2.21%, 3.70% 1.92%, 7.41% 1.00%, 11.11% 1.24%, 14.81% 1.46%, 18.52% 0.82%, 22.22% 0.56%, 25.93% 0.88%, 29.63% 1.39%, 33.33% 1.40%, 37.04% 0.79%, 40.74% 1.89%, 44.44% 3.06%, 48.15% 2.59%, 51.85% 1.82%, 55.56% 1.67%, 59.26% 2.36%, 62.96% 1.75%, 66.67% 1.30%, 70.37% 1.25%, 74.07% 1.16%, 77.78% 0.91%, 81.48% 0.26%, 85.19% 0.75%, 88.89% 1.02%, 92.59% 0.90%, 96.30% 0.99%, 100.00% 1.53%, 99.19% 3.70%, 98.94% 7.41%, 98.68% 11.11%, 98.24% 14.81%, 98.74% 18.52%, 98.29% 22.22%, 98.00% 25.93%, 98.06% 29.63%, 98.56% 33.33%, 98.23% 37.04%, 98.03% 40.74%, 98.48% 44.44%, 99.10% 48.15%, 99.29% 51.85%, 99.35% 55.56%, 98.68% 59.26%, 98.65% 62.96%, 98.91% 66.67%, 98.15% 70.37%, 97.80% 74.07%, 97.98% 77.78%, 98.28% 81.48%, 98.63% 85.19%, 98.42% 88.89%, 98.70% 92.59%, 99.23% 96.30%, 99.30% 100.00%, 96.30% 98.47%, 92.59% 98.92%, 88.89% 99.71%, 85.19% 99.14%, 81.48% 98.75%, 77.78% 98.69%, 74.07% 98.57%, 70.37% 98.00%, 66.67% 97.90%, 62.96% 97.78%, 59.26% 97.80%, 55.56% 98.86%, 51.85% 99.09%, 48.15% 99.06%, 44.44% 99.61%, 40.74% 99.08%, 37.04% 98.30%, 33.33% 97.98%, 29.63% 98.40%, 25.93% 98.20%, 22.22% 98.15%, 18.52% 98.21%, 14.81% 99.07%, 11.11% 99.74%, 7.41% 99.05%, 3.70% 98.84%, 0.00% 98.46%, 1.77% 96.30%, 1.94% 92.59%, 2.37% 88.89%, 1.66% 85.19%, 0.75% 81.48%, 0.57% 77.78%, 0.93% 74.07%, 0.83% 70.37%, 1.00% 66.67%, 1.24% 62.96%, 2.01% 59.26%, 2.85% 55.56%, 3.05% 51.85%, 1.57% 48.15%, 1.51% 44.44%, 1.38% 40.74%, 1.19% 37.04%, 0.52% 33.33%, 0.63% 29.63%, 1.33% 25.93%, 1.73% 22.22%, 1.85% 18.52%, 1.39% 14.81%, 1.78% 11.11%, 1.83% 7.41%, 1.69% 3.70%);
  --sobek-4: polygon(0.00% 1.69%, 3.70% 2.06%, 7.41% 1.97%, 11.11% 1.50%, 14.81% 1.37%, 18.52% 1.47%, 22.22% 1.17%, 25.93% 0.83%, 29.63% 0.41%, 33.33% 1.83%, 37.04% 2.90%, 40.74% 2.05%, 44.44% 1.79%, 48.15% 1.92%, 51.85% 2.12%, 55.56% 1.74%, 59.26% 0.80%, 62.96% 0.62%, 66.67% 0.65%, 70.37% 1.42%, 74.07% 1.38%, 77.78% 1.22%, 81.48% 1.52%, 85.19% 2.36%, 88.89% 1.87%, 92.59% 1.23%, 96.30% 1.16%, 100.00% 1.06%, 98.26% 3.70%, 98.89% 7.41%, 98.31% 11.11%, 98.76% 14.81%, 99.50% 18.52%, 99.04% 22.22%, 98.86% 25.93%, 98.97% 29.63%, 98.15% 33.33%, 97.76% 37.04%, 98.26% 40.74%, 97.98% 44.44%, 98.08% 48.15%, 98.43% 51.85%, 98.08% 55.56%, 98.13% 59.26%, 99.25% 62.96%, 99.47% 66.67%, 98.59% 70.37%, 98.90% 74.07%, 98.51% 77.78%, 97.67% 81.48%, 97.83% 85.19%, 98.49% 88.89%, 97.93% 92.59%, 98.50% 96.30%, 99.44% 100.00%, 96.30% 98.81%, 92.59% 98.89%, 88.89% 98.48%, 85.19% 97.82%, 81.48% 98.22%, 77.78% 98.28%, 74.07% 97.64%, 70.37% 98.51%, 66.67% 99.21%, 62.96% 98.60%, 59.26% 99.06%, 55.56% 99.71%, 51.85% 98.69%, 48.15% 98.83%, 44.44% 98.62%, 40.74% 98.14%, 37.04% 98.15%, 33.33% 98.66%, 29.63% 97.95%, 25.93% 98.11%, 22.22% 98.16%, 18.52% 98.61%, 14.81% 98.85%, 11.11% 99.50%, 7.41% 99.47%, 3.70% 98.35%, 0.00% 98.86%, 0.93% 96.30%, 0.92% 92.59%, 1.03% 88.89%, 1.78% 85.19%, 2.21% 81.48%, 1.53% 77.78%, 1.79% 74.07%, 1.95% 70.37%, 0.87% 66.67%, 0.41% 62.96%, 1.25% 59.26%, 1.35% 55.56%, 1.08% 51.85%, 1.44% 48.15%, 2.41% 44.44%, 1.70% 40.74%, 1.66% 37.04%, 1.93% 33.33%, 1.74% 29.63%, 1.32% 25.93%, 1.76% 22.22%, 1.29% 18.52%, 1.27% 14.81%, 0.95% 11.11%, 1.66% 7.41%, 2.23% 3.70%);
  --sobek-halus: polygon(0.00% 0.31%, 3.03% 0.40%, 6.06% 0.51%, 9.09% 0.38%, 12.12% 0.50%, 15.15% 0.66%, 18.18% 0.71%, 21.21% 0.62%, 24.24% 0.86%, 27.27% 0.99%, 30.30% 0.93%, 33.33% 0.72%, 36.36% 0.64%, 39.39% 0.80%, 42.42% 0.59%, 45.45% 0.35%, 48.48% 0.44%, 51.52% 0.70%, 54.55% 0.69%, 57.58% 0.50%, 60.61% 0.60%, 63.64% 0.67%, 66.67% 0.41%, 69.70% 0.62%, 72.73% 0.98%, 75.76% 0.85%, 78.79% 0.77%, 81.82% 0.70%, 84.85% 0.85%, 87.88% 0.76%, 90.91% 0.63%, 93.94% 0.53%, 96.97% 0.64%, 100.00% 0.38%, 98.97% 3.03%, 98.94% 6.06%, 99.37% 9.09%, 99.33% 12.12%, 99.34% 15.15%, 99.48% 18.18%, 99.80% 21.21%, 99.72% 24.24%, 99.47% 27.27%, 99.59% 30.30%, 99.48% 33.33%, 98.90% 36.36%, 98.78% 39.39%, 99.03% 42.42%, 99.29% 45.45%, 99.29% 48.48%, 99.16% 51.52%, 99.26% 54.55%, 99.59% 57.58%, 99.46% 60.61%, 99.44% 63.64%, 99.67% 66.67%, 99.89% 69.70%, 99.58% 72.73%, 99.36% 75.76%, 99.29% 78.79%, 99.32% 81.82%, 99.07% 84.85%, 99.10% 87.88%, 99.16% 90.91%, 99.34% 93.94%, 99.14% 96.97%, 99.44% 100.00%, 96.97% 99.34%, 93.94% 99.29%, 90.91% 99.46%, 87.88% 99.67%, 84.85% 99.79%, 81.82% 99.84%, 78.79% 99.53%, 75.76% 99.18%, 72.73% 99.24%, 69.70% 99.23%, 66.67% 99.23%, 63.64% 99.18%, 60.61% 99.13%, 57.58% 99.31%, 54.55% 99.53%, 51.52% 99.37%, 48.48% 99.04%, 45.45% 99.37%, 42.42% 99.70%, 39.39% 99.61%, 36.36% 99.56%, 33.33% 99.22%, 30.30% 98.95%, 27.27% 99.15%, 24.24% 99.21%, 21.21% 99.46%, 18.18% 99.36%, 15.15% 99.30%, 12.12% 99.66%, 9.09% 99.67%, 6.06% 99.69%, 3.03% 99.44%, 0.00% 99.37%, 0.89% 96.97%, 1.00% 93.94%, 0.75% 90.91%, 0.80% 87.88%, 0.93% 84.85%, 0.90% 81.82%, 0.65% 78.79%, 0.59% 75.76%, 0.77% 72.73%, 0.55% 69.70%, 0.36% 66.67%, 0.57% 63.64%, 0.96% 60.61%, 0.64% 57.58%, 0.30% 54.55%, 0.71% 51.52%, 0.73% 48.48%, 0.61% 45.45%, 0.59% 42.42%, 1.08% 39.39%, 1.05% 36.36%, 0.66% 33.33%, 0.60% 30.30%, 0.79% 27.27%, 0.74% 24.24%, 0.37% 21.21%, 0.30% 18.18%, 0.57% 15.15%, 0.44% 12.12%, 0.25% 9.09%, 0.47% 6.06%, 0.70% 3.03%);
}

.kertas {
  position: relative;
  display: block;
  height: 100%;
  padding: var(--mat, 6px);
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='f'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .42 0 0 0 0 .35 0 0 0 0 .24 0 0 0 .6 0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23f)' opacity='.2'/%3E%3C/svg%3E") 0 0 / 140px 140px,
    linear-gradient(155deg, #fbf8f1 0%, #f2ecdf 52%, #e7dfcb 100%);
  clip-path: var(--sobek, var(--sobek-1));
}

.kertas img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Tepi foto sedikit tenggelam ke kertas, seperti cetakan yang ditempel. */
.kertas::after {
  content: '';
  position: absolute;
  inset: var(--mat, 6px);
  box-shadow: inset 0 0 0 1px rgba(40, 30, 15, .22), inset 0 1px 6px rgba(0, 0, 0, .3);
  pointer-events: none;
}

/* Bayangan dipasang pada pembungkusnya, karena clip-path pada kertas
   memotong bayangan milik kertas itu sendiri. Sengaja box-shadow, bukan
   filter drop-shadow: drop-shadow yang mengikuti bentuk sobekan membuat
   halaman sepertiga lebih lambat saat digulir (diukur), dan karena
   bayangannya lembut, bentuk kotaknya tidak terlihat. Spread negatif
   menariknya ke balik kertas supaya tidak mengintip di celah sobekan. */
.orang__bingkai,
.kolase__foto,
.ft {
  box-shadow: 0 16px 26px -10px rgba(0, 0, 0, .6);
}

.orang__bingkai { box-shadow: 0 26px 40px -14px rgba(0, 0, 0, .7); }

/* Foto mempelai: mat lebar, sobekan dangkal, garis emas tipis. */
.orang__bingkai .kertas {
  --mat: 16px;
  --sobek: var(--sobek-halus);
}

.orang__bingkai .kertas::before {
  content: '';
  position: absolute;
  inset: 8px;
  z-index: 1;
  border: 1px solid rgba(176, 138, 72, .85);
  box-shadow: 0 1px 0 rgba(255, 240, 205, .6);
  pointer-events: none;
}


/* ================================================================
   Bagian umum
   ================================================================ */

/* overflow-x: clip adalah jaring pengaman: apa pun di dalam bagian -
   sorotan, bayangan, benda yang miring 3D - tidak bisa lagi melebarkan
   halaman. clip, bukan hidden, karena hidden akan membuat tiap bagian
   menjadi wadah gulir sendiri. */
.bagian {
  position: relative;
  max-width: var(--lebar);
  margin: 0 auto;
  padding: 96px 24px;
  text-align: center;
  overflow-x: clip;
}


/* ================================================================
   Mempelai
   ================================================================ */

.lengkung {
  display: block;
  width: min(300px, 88%);
  margin: 0 auto 10px;
  overflow: visible;
  fill: var(--putih);
  font-family: var(--serif);
  font-weight: 600;
  font-size: 23px;
  letter-spacing: .3em;
  filter: drop-shadow(0 1px 5px rgba(0, 0, 0, .45));
}

.pengantar {
  max-width: 22em;
  margin: 0 auto 56px;
  font-size: 19px;
  text-shadow: var(--bayang);
}

.orang {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.orang__bingkai {
  position: relative;
  width: min(76%, 296px);
  aspect-ratio: 3 / 4;
  margin-bottom: 20px;
}

/* Foto dibungkus supaya bisa bergeser di dalam bingkai (efek jendela),
   sementara bingkainya sendiri miring mengikuti gulir. */
.orang__foto {
  display: block;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.orang__foto img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}


.orang__nama {
  margin: 0;
  font-family: var(--serif);
  font-weight: 700;
  font-size: 32px;
  line-height: 1.15;
  text-shadow: var(--bayang);
}

.orang__ortu {
  max-width: 20em;
  margin-top: 6px;
  font-size: 18px;
  line-height: 1.45;
  text-shadow: var(--bayang);
}

.sosmed {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding: 8px 16px 8px 12px;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: .02em;
  text-decoration: none;
  background: rgba(255, 255, 255, .14);
  border: 1px solid rgba(255, 255, 255, .55);
  border-radius: 999px;
}

.sosmed svg { width: 16px; height: 16px; flex: none; }

.dan {
  margin: 34px 0 30px;
  font-family: var(--script);
  font-size: 76px;
  line-height: 1;
  text-shadow: var(--bayang);
}


/* ================================================================
   Acara
   ================================================================ */

/* Tiga foto berbingkai putih yang saling menumpuk. */
.kolase {
  position: relative;
  width: 100%;
  max-width: 380px;
  aspect-ratio: 380 / 272;
  margin: 0 auto 48px;
}

.kolase__foto {
  position: absolute;
  transition: translate 1.2s var(--ease), rotate 1.2s var(--ease),
              scale 1.2s var(--ease), opacity .9s ease;
}

.kolase__foto .kertas { --mat: 5px; }
.kolase__foto--kiri .kertas  { --sobek: var(--sobek-3); }
.kolase__foto--kanan .kertas { --sobek: var(--sobek-4); }
.kolase__foto--tengah .kertas { --mat: 6px; --sobek: var(--sobek-2); }

.kolase__foto img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Wajah di foto potret ada di sepertiga atas; potongan 4:5 diambil
     dari atas supaya tidak ada wajah yang terpotong. */
  object-position: 50% 22%;
}

.kolase__foto--kiri {
  left: 0;
  top: 24%;
  width: 36%;
  aspect-ratio: 4 / 5;
  rotate: -5deg;
}

.kolase__foto--tengah {
  left: 50%;
  top: 0;
  z-index: 2;
  width: 46%;
  aspect-ratio: 3 / 4;
  translate: -50% 0;
}

.kolase__foto--kanan {
  right: 0;
  top: 30%;
  width: 36%;
  aspect-ratio: 4 / 5;
  rotate: 5deg;
}

/* Hitung mundur */
.hitung {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin: 0 auto 44px;
  text-shadow: var(--bayang);
}

.cd__unit {
  width: 70px;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 12px 0 10px;
  background: rgba(255, 255, 255, .1);
  border: 1px solid rgba(255, 255, 255, .45);
  border-radius: 12px;
}

.cd__num {
  font-family: var(--serif);
  font-weight: 700;
  font-size: 38px;
  line-height: 1;
  font-variant-numeric: lining-nums tabular-nums;
}

.cd__label {
  margin-top: 6px;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
}

.cd__sep { display: none; }

.cd__num.is-tick { animation: detak .5s var(--ease); }

.hitung__pesan {
  margin: 0 auto 40px;
  font-style: italic;
  font-size: 21px;
  text-shadow: var(--bayang);
}

/* Kartu acara */
.kartu {
  margin: 0 0 20px;
  padding: 34px 24px 30px;
  color: var(--tinta);
  background: rgba(251, 250, 247, .96);
  border-radius: 22px;
  box-shadow: 0 24px 50px -22px rgba(0, 0, 0, .8);
}

.kartu__judul {
  margin: 0 0 10px;
  font-family: var(--script);
  font-weight: 400;
  font-size: 52px;
  line-height: 1.1;
  color: #4f4e49;
}

.kartu__tanggal {
  font-weight: 700;
  font-size: 25px;
  letter-spacing: .02em;
}

.kartu__jam {
  margin: 6px 0 18px;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: .16em;
  color: var(--tinta-2);
}

.kartu__tempat {
  font-weight: 700;
  font-size: 21px;
  line-height: 1.3;
}

.kartu__alamat {
  margin-top: 6px;
  font-size: 17px;
  line-height: 1.5;
  color: var(--tinta-2);
}

.kartu__peta { margin-top: 22px; }


/* ================================================================
   Tombol
   ================================================================ */

.tombol {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 13px 22px;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: 999px;
  cursor: pointer;
}

.tombol svg { width: 17px; height: 17px; flex: none; }

.tombol--peta {
  color: #fff;
  background: var(--tinta);
}

.tombol--garis {
  margin-top: 18px;
  color: #fff;
  background: rgba(255, 255, 255, .1);
  border-color: rgba(255, 255, 255, .7);
}

.tombol--terang {
  color: var(--tinta);
  background: rgba(251, 250, 247, .96);
  box-shadow: 0 14px 30px -12px rgba(0, 0, 0, .7);
}


/* ================================================================
   Video
   ================================================================ */

/* Video memakai kertas yang sama, tapi bertepi lurus: elemen video di
   dalam wadah ber-clip-path dan ber-transform 3D rawan salah gambar di
   Safari iOS. */
.film__bingkai {
  padding: 7px;
  background: linear-gradient(155deg, #fbf8f1 0%, #f2ecdf 52%, #e7dfcb 100%);
  box-shadow: 0 24px 50px -20px rgba(0, 0, 0, .8), 0 2px 4px rgba(0, 0, 0, .25);
}

.film__video {
  width: 100%;
  aspect-ratio: 832 / 464;
  background: #000;
}


/* ================================================================
   Galeri
   ================================================================ */

.galeri {
  max-width: 520px;
  padding-left: 14px;
  padding-right: 14px;
}

.galeri__panel {
  padding: 10px;
  background: rgba(22, 22, 21, .5);
  border-radius: 6px;
}

.galeri__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

/* Tiap cetakan sedikit miring dan tepinya sobek berbeda, seperti foto
   yang ditata tangan di halaman album, bukan kisi yang dicetak mesin. */
.ft .kertas { --mat: 6px; }
.ft:nth-child(4n+1) .kertas { --sobek: var(--sobek-1); }
.ft:nth-child(4n+2) .kertas { --sobek: var(--sobek-2); }
.ft:nth-child(4n+3) .kertas { --sobek: var(--sobek-3); }
.ft:nth-child(4n)   .kertas { --sobek: var(--sobek-4); }

.ft:nth-child(odd)  { rotate: -.7deg; }
.ft:nth-child(even) { rotate: .8deg; }
.ft--lebar, .ft--utama { rotate: .3deg; }

.ft img {
  width: 100%;
  height: 100%;
  aspect-ratio: 2 / 3;
  object-fit: cover;
}

.ft--lebar,
.ft--utama { grid-column: span 2; }

.ft--lebar img { aspect-ratio: 3 / 2; }

.ft--utama img {
  aspect-ratio: 4 / 5;
  object-position: 50% 80%;
}

.galeri__sub {
  margin: 28px 0 2px;
  font-family: var(--script);
  font-size: 40px;
  line-height: 1.2;
  text-shadow: var(--bayang);
}

.galeri__sub-tanggal {
  margin: 0 0 14px;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .24em;
  text-transform: uppercase;
  color: rgba(251, 250, 247, .8);
  text-shadow: var(--bayang);
}


/* ================================================================
   Hadiah
   ================================================================ */

/* Panel teks: bingkai kaca yang sedikit lebih gelap dari Turut Mengundang,
   dengan ikon kado emas, supaya kalimatnya berdiri sendiri di atas foto. */
.hadiah__panel {
  padding: 30px 22px 28px;
  background: rgba(18, 18, 17, .34);
}

.hadiah__ikon {
  display: block;
  width: 38px;
  height: 38px;
  margin: 0 auto 14px;
  color: var(--emas);
  filter: drop-shadow(0 2px 8px rgba(0, 0, 0, .5));
}

.hadiah__teks {
  font-size: 20px;
  font-weight: 600;
  line-height: 1.55;
  text-shadow: var(--bayang);
}

/* Panggung kartu: sorotan cahaya hangat di belakangnya membuat kartu
   putih itu menjadi benda paling terang di bagian ini. */
.rek-panggung {
  position: relative;
  isolation: isolate;
  margin: 40px auto 0;
  max-width: 360px;
}

/* Sorotan hanya melebar ke atas-bawah. Versi pertamanya melebar 18% ke
   kiri-kanan dan ikut membesar-mengecil, sehingga menjulur melewati tepi
   layar dan membuat seluruh halaman bisa digeser ke samping. */
.rek-panggung::before {
  content: '';
  position: absolute;
  inset: -34% 0;
  z-index: -1;
  background: radial-gradient(closest-side, rgba(255, 236, 196, .34), rgba(255, 236, 196, .1) 55%, transparent);
  animation: sorot 4.5s ease-in-out infinite alternate;
}

@keyframes sorot {
  from { opacity: .7; scale: .94; }
  to   { opacity: 1; scale: 1.04; }
}

.hadiah__catatan {
  margin-top: 22px;
  font-family: var(--sans);
  font-size: 13px;
  color: rgba(251, 250, 247, .85);
  text-shadow: var(--bayang);
}

.hadiah__catatan strong { font-weight: 600; color: #fff; }

/* Kartu rekening meniru kartu pada referensi: kartu putih berpola lengkung
   samar dengan titik-titik di sudut kanan bawah, nama bank di kanan atas,
   chip emas di kiri, nomor berhuruf kartu, tombol salin berbentuk pil,
   dan nama pemilik di kanan bawah. Nama bank ditulis sebagai teks, bukan
   logo resmi bank. */
.rek {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  aspect-ratio: 1.75;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 20px 22px 18px;
  text-align: left;
  color: #232322;
  background:
    radial-gradient(90% 110% at 102% 112%, rgba(0, 0, 0, .045) 0 44%, transparent 44.6%),
    radial-gradient(70% 80% at 18% 128%, rgba(0, 0, 0, .04) 0 52%, transparent 52.6%),
    linear-gradient(118deg, transparent 0 44%, rgba(0, 0, 0, .028) 44% 63%, transparent 63%),
    linear-gradient(160deg, #fff 0%, #f6f6f5 55%, #ededec 100%);
  border-radius: 14px;
  box-shadow: 0 30px 60px -22px rgba(0, 0, 0, .85), 0 2px 6px rgba(0, 0, 0, .12);
  /* Mengambang pelan. Memakai properti translate, bukan transform, supaya
     tidak bertabrakan dengan kemiringan 3D yang mengikuti gulir. */
  animation: ambang 5s ease-in-out infinite alternate;
}

@keyframes ambang {
  from { translate: 0 -5px; }
  to   { translate: 0 5px; }
}

/* Kilau yang melintas di permukaan kartu tiap beberapa detik, seperti
   pantulan lampu pada kartu sungguhan. Lewat di belakang tulisan dan
   tombol: kalau di depan, tombol Salin ikut tampak pudar saat dilewati. */
.rek__kilau {
  position: absolute;
  top: -30%;
  bottom: -30%;
  left: 0;
  z-index: 0;
  width: 38%;
  pointer-events: none;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, .95) 46%, rgba(255, 244, 214, .55) 56%, transparent);
  transform: skewX(-18deg);
  animation: kilau 5.5s ease-in-out 1.5s infinite;
}

@keyframes kilau {
  0%       { translate: -140% 0; }
  45%, 100% { translate: 360% 0; }
}

/* Pola titik di sudut kanan bawah, memudar ke tengah kartu. */
.rek::after {
  content: '';
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: -1;
  width: 55%;
  height: 60%;
  background-image: radial-gradient(rgba(0, 0, 0, .13) 1px, transparent 1.5px);
  background-size: 7px 7px;
  -webkit-mask-image: radial-gradient(120% 120% at 100% 100%, #000 18%, transparent 68%);
  mask-image: radial-gradient(120% 120% at 100% 100%, #000 18%, transparent 68%);
}

.rek__chip,
.rek__nomor,
.rek__bawah {
  position: relative;
  z-index: 1;
}

.rek__bank {
  position: absolute;
  top: 14px;
  right: 22px;
  z-index: 1;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 30px;
  letter-spacing: -.03em;
  line-height: 1.2;
  color: #0b3a73;
}

.rek__chip {
  display: block;
  width: 48px;
  height: 37px;
  margin-bottom: 8px;
}

.rek__nomor {
  font-family: 'Oxanium', var(--sans);
  font-weight: 500;
  font-size: 23px;
  letter-spacing: .06em;
  font-variant-numeric: tabular-nums;
  color: #1c1c1b;
}

/* Tombol di kiri, nama pemilik di baris sendiri di kanan bawah - seperti
   referensi. Kalau dipaksa sebaris, nama yang panjang terlipat dua. */
.rek__bawah {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 8px;
}

.rek__salin {
  align-self: flex-start;
  min-width: 86px;
  padding: 6px 18px 7px;
  font-family: var(--serif);
  font-weight: 600;
  font-size: 18px;
  line-height: 1.2;
  color: #fff;
  background: #6b776c;
  border: 0;
  border-radius: 999px;
  cursor: pointer;
}

.rek__salin:disabled { opacity: .45; cursor: default; }

.rek__nama {
  align-self: flex-end;
  text-align: right;
  font-weight: 600;
  font-size: 19px;
  line-height: 1.25;
}


/* ================================================================
   Ucapan
   ================================================================ */

.ucapan__intro {
  max-width: 22em;
  margin: 0 auto 28px;
  font-size: 18.5px;
  text-shadow: var(--bayang);
}

.form { text-align: left; }

.isian {
  display: block;
  width: 100%;
  margin: 0 0 12px;
  padding: 13px 15px;
  font-family: var(--sans);
  font-size: 16px;
  color: var(--tinta);
  background: rgba(251, 250, 247, .96);
  border: 1px solid transparent;
  border-radius: 8px;
}

.isian::placeholder { color: #85847f; }

.isian:focus {
  outline: 2px solid var(--emas);
  outline-offset: 1px;
}

.isian--area {
  min-height: 112px;
  resize: vertical;
}

.tombol--kirim {
  display: flex;
  min-width: 150px;
  margin: 6px auto 0;
  color: #fff;
  background: #1d1d1c;
  border-color: rgba(255, 255, 255, .22);
}

.tombol--kirim:disabled { opacity: .5; cursor: not-allowed; }

.form__note {
  min-height: 1.2em;
  margin-top: 12px;
  font-family: var(--sans);
  font-size: 13.5px;
  line-height: 1.5;
  text-align: center;
  text-shadow: var(--bayang);
}

.form__note--ok { color: #dcefd2; }
.form__note--bad { color: #ffd2cb; }

.ucapan__jumlah {
  margin-top: 40px;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .24em;
  text-transform: uppercase;
  text-shadow: var(--bayang);
}

.ucapan__daftar {
  margin-top: 10px;
  text-align: left;
}

.wish {
  padding: 16px 2px;
  border-bottom: 1px solid rgba(255, 255, 255, .3);
  animation: masuk .6s var(--ease) both;
}

.wish__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
}

.wish__ikon {
  font-size: 16px;
  line-height: 1;
  /* Emoji dari sistem, warnanya sendiri - text-shadow milik teks di
     sekitarnya tidak boleh ikut menimpanya. */
  text-shadow: none;
}

.wish__name {
  font-family: var(--sans);
  font-weight: 600;
  font-size: 15.5px;
  text-shadow: var(--bayang);
}

.wish__time {
  font-family: var(--sans);
  font-size: 12px;
  color: rgba(251, 250, 247, .8);
}

.wish__text {
  margin-top: 4px;
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.55;
  overflow-wrap: anywhere;
  text-wrap: pretty;
  text-shadow: var(--bayang);
}

.halaman {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 4px;
  margin-top: 22px;
}

.halaman button {
  min-width: 36px;
  height: 36px;
  padding: 0 6px;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 15px;
  color: #fff;
  background: transparent;
  border: 0;
  border-radius: 6px;
  cursor: pointer;
}

.halaman button[aria-current="page"] {
  color: var(--tinta);
  background: var(--emas);
}

.halaman button:disabled { opacity: .35; cursor: default; }


/* ================================================================
   Penutup
   ================================================================ */

.penutup { padding-bottom: 80px; }

.penutup__teks {
  max-width: 22em;
  margin: 0 auto;
  font-size: 19px;
  text-shadow: var(--bayang);
}

.penutup__berkat {
  margin-top: 32px;
  font-family: var(--sans);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .28em;
  text-transform: uppercase;
  text-shadow: var(--bayang);
}

.penutup__ttd {
  margin-top: 12px;
  font-family: var(--script);
  font-size: 54px;
  line-height: 1.2;
  text-shadow: var(--bayang);
}

.kaki {
  margin: 0;
  padding: 18px 16px calc(18px + env(safe-area-inset-bottom));
  font-family: var(--sans);
  text-align: center;
  background: rgba(20, 20, 19, .85);
}

.kaki__pasangan {
  margin: 0;
  color: rgba(255, 255, 255, .82);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: .24em;
  text-transform: uppercase;
}

.kaki__credit {
  margin: 9px 0 0;
  color: rgba(255, 255, 255, .6);
  font-size: 11px;
  letter-spacing: .01em;
}

.kaki__credit a {
  color: rgba(255, 255, 255, .76);
  text-decoration: underline;
  text-decoration-color: rgba(255, 255, 255, .35);
  text-underline-offset: 3px;
}

.kaki__credit a:hover,
.kaki__credit a:focus-visible {
  color: #fff;
  text-decoration-color: currentColor;
}


/* ================================================================
   Tombol musik
   ================================================================ */

.musik {
  position: fixed;
  left: 16px;
  bottom: calc(16px + env(safe-area-inset-bottom));
  z-index: 40;
  width: 46px;
  height: 46px;
  padding: 0;
  display: grid;
  place-items: center;
  color: #fff;
  background: rgba(20, 20, 19, .88);
  border: 1px solid rgba(255, 255, 255, .35);
  border-radius: 50%;
  cursor: pointer;
}

.musik > * { grid-area: 1 / 1; }
.musik svg { width: 18px; height: 18px; }

.musik__cincin {
  width: 100%;
  height: 100%;
  border: 1px dashed rgba(255, 255, 255, .55);
  border-radius: 50%;
  animation: putar 8s linear infinite;
}

.musik.is-diam .musik__cincin { animation-play-state: paused; opacity: .4; }
.musik .musik__putar { display: none; }
.musik.is-diam .musik__putar { display: block; }
.musik.is-diam .musik__jeda { display: none; }


/* ================================================================
   Kedalaman 3D yang mengikuti gulir
   script.js menulis dua angka ke tiap benda yang sedang terlihat:
   --g  dari 1 (benda di bawah layar) lewat 0 (tepat di tengah) ke -1
        (di atas layar), dan
   --ga nilai mutlaknya (0 di tengah, 1 di tepi).
   Semua efek hanya transform: dikerjakan kartu grafis, tidak ada yang
   menggeser tata letak. Aturan ini hanya berlaku kalau skripnya jalan
   (.gulir-on), dan dimatikan untuk tamu yang memilih kurangi gerak.
   ================================================================ */

/* Bingkai, kartu, dan foto berputar pelan seperti di permukaan drum:
   condong ke belakang saat masuk dari bawah, rata di tengah layar,
   condong ke depan saat keluar di atas. Tiap benda memakai perspektifnya
   sendiri, jadi letaknya di halaman tidak memengaruhi sudut pandangnya.

   .ft (24 foto galeri) sengaja TIDAK ikut di sini. Galeri adalah bagian
   terpanjang di halaman, jadi gulir cepat paling lama melintasinya, dan
   saat itu beberapa .ft sekaligus berada dalam jangkauan efek -
   masing-masing dengan clip-path sobekan kertas, bayangan, dan transform
   3D yang ditulis ulang tiap bingkai. Itu titik paling berat di seluruh
   halaman, dan dari laporan tamu yang menggulir cepat bolak-balik, masih
   terasa patah-patah walau sudah dikurangi sebelumnya. Foto galeri tetap
   punya animasi masuk (muncul sekali via .muncul), hanya tidak lagi
   miring terus-menerus mengikuti posisi gulir. */
.gulir-on .orang__bingkai,
.gulir-on .kartu,
.gulir-on .film__bingkai,
.gulir-on .kaca {
  transform: perspective(1100px)
             rotateX(calc(var(--g, 0) * var(--sudut, 14deg)))
             scale(calc(1 - var(--ga, 0) * var(--susut, .06)));
}

.gulir-on .kaca { --sudut: 8deg; --susut: .03; }

/* Efek jendela: foto di dalam bingkai bergerak lebih lambat daripada
   bingkainya, seolah pemandangannya jauh di belakang kaca. */
.gulir-on .orang__foto img {
  transform: translate3d(0, calc(var(--g, 0) * -8%), 0) scale(1.2);
}

/* Kolase tiga lapis: foto tengah paling depan, dua foto samping di
   belakangnya dan bergerak dengan kecepatan berbeda sambil berputar. */
.gulir-on .kolase {
  transform-style: preserve-3d;
  transform: perspective(900px) rotateX(calc(var(--g, 0) * 10deg));
}

.gulir-on .kolase__foto--kiri {
  transform: translate3d(0, calc(var(--g, 0) * 30px), 0) rotateY(calc(var(--g, 0) * 16deg));
}

.gulir-on .kolase__foto--tengah {
  transform: translate3d(0, calc(var(--g, 0) * -16px), 34px);
}

.gulir-on .kolase__foto--kanan {
  transform: translate3d(0, calc(var(--g, 0) * 50px), 0) rotateY(calc(var(--g, 0) * -16deg));
}

/* Kotak hitung mundur berjenjang seperti anak tangga yang merapat. */
.gulir-on .hitung { perspective: 700px; }

.gulir-on .cd__unit {
  transform: translate3d(0, calc(var(--g, 0) * var(--k, 0) * 1px), 0)
             rotateX(calc(var(--g, 0) * 24deg));
}

.cd__unit:nth-child(1) { --k: 4; }
.cd__unit:nth-child(2) { --k: 11; }
.cd__unit:nth-child(3) { --k: 18; }
.cd__unit:nth-child(4) { --k: 25; }

/* Judul bergeser sebagai satu kesatuan. Dulu kata serif dan kata tulisan
   tangan bergeser berlawanan arah, dan di bagian atas layar kata tulisan
   tangannya naik sampai menutupi kata serif hampir seluruhnya (tumpukan
   57px, dari rancangan 22px) - itu yang terlihat "bertimpa" saat digulir. */
.gulir-on .judul { transform: translate3d(0, calc(var(--g, 0) * -14px), 0); }

/* Tulisan melengkung berputar seperti stempel, tanda & membesar. */
.gulir-on .lengkung { transform: rotate(calc(var(--g, 0) * -12deg)); }

.gulir-on .dan {
  transform: scale(calc(1 - var(--g, 0) * .1)) rotate(calc(var(--g, 0) * 12deg));
}

/* Kartu rekening miring dua arah, dengan perspektif dari panggungnya. */
.gulir-on .rek-panggung { perspective: 900px; }

.gulir-on .rek {
  transform: rotateX(calc(var(--g, 0) * 18deg)) rotateY(calc(var(--g, 0) * -12deg));
}

/* Foto latar mendekat perlahan sepanjang halaman (--lp, 0 sampai 1). */
.latar__foto {
  transform: scale(calc(1.03 + var(--lp, 0) * .12));
  will-change: transform;
}


/* ================================================================
   Kunang-kunang dua lapis
   Lapis jauh berada di belakang isi dan bergerak lambat saat digulir;
   lapis dekat - lebih sedikit, lebih besar, lebih kabur - melayang di
   depan isi dan bergerak lebih cepat dari halaman. Perbedaan kecepatan
   itulah yang terbaca sebagai jarak.
   ================================================================ */

.kunang {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  height: 100lvh;
  overflow: hidden;
  pointer-events: none;
}

/* mix-blend-mode dibuang seluruhnya (dulu dipakai di lapis dekat supaya
   cahayanya hanya bisa menerangi, tidak pernah menggelapkan). Elemen fixed
   yang mode campurnya harus dihitung ulang tiap kali transform-nya berubah
   - dan transform-nya berubah di setiap bingkai gulir - itu salah satu
   penyebab paling umum Safari iOS tersendat saat gulir cepat. Sudah
   dipersempit sekali ke satu lapis saja, tapi tamu yang menggulir cepat
   bolak-balik masih melaporkan patah-patah, jadi kali ini dibuang total.
   Cahayanya sendiri (radial-gradient di .kunang__titik) sedikit
   dipertegas supaya tetap terbaca sebagai kunang-kunang bercahaya tanpa
   mode campur. */
.kunang--jauh { z-index: 0; }
.kunang--dekat { z-index: 3; }

/* Dua kali tinggi layar, berisi dua salinan kunang-kunang yang sama.
   Digeser skrip lalu kembali ke nol tiap selesai satu layar, dan karena
   salinan kedua identik, sambungannya tidak terlihat. */
.kunang__lapis {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 200%;
  will-change: transform;
}

/* Satu elemen berisi inti yang menyala putih-emas dan pendar lebar yang
   memudar ke luar. --u adalah garis tengah pendarnya; intinya hanya
   beberapa persen di tengah. Elemen digeser setengah ukurannya supaya
   titik left/top jatuh tepat di inti. */
.kunang__titik {
  position: absolute;
  width: var(--u, 80px);
  height: var(--u, 80px);
  margin: calc(var(--u, 80px) / -2) 0 0 calc(var(--u, 80px) / -2);
  border-radius: 50%;
  background: radial-gradient(circle,
    #fffdf3 0%,
    #fff3c4 2.6%,
    rgba(255, 222, 140, .95) 4.8%,
    rgba(255, 208, 118, .42) 12%,
    rgba(255, 198, 108, .17) 30%,
    rgba(255, 190, 100, .06) 48%,
    rgba(255, 190, 100, 0) 68%);
  animation:
    melayang var(--dur, 12s) ease-in-out infinite alternate,
    kedip var(--kedip, 3s) ease-in-out infinite;
}

@keyframes melayang {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(var(--dx, 40px), var(--dy, -70px), 0); }
}

/* Tidak pernah padam sama sekali: meredup ke separuh lalu menyala penuh. */
@keyframes kedip {
  0%, 100% { opacity: .5; }
  50%      { opacity: 1; }
}


/* ================================================================
   Muncul saat digulir
   Keadaan tersembunyi hanya berlaku kalau script.js sudah memasang
   .reveal-on. Tanpa skrip, semuanya langsung terlihat.
   ================================================================ */

.reveal-on .muncul {
  opacity: 0;
  translate: 0 28px;
  transition: opacity .9s ease, translate 1s var(--ease),
              scale 1.1s var(--ease), rotate 1.2s var(--ease);
  transition-delay: var(--d, 0s);
}

.reveal-on .muncul.is-in {
  opacity: 1;
  translate: 0 0;
}

.reveal-on .muncul--zoom {
  translate: 0 0;
  scale: .9;
}

.reveal-on .muncul--zoom.is-in { scale: 1; }

/* Judul: kata serif naik, kata tulisan tangan "ditulis" dari kiri ke
   kanan. clip-path diberi nilai negatif supaya ujung huruf tulisan
   tangan yang menjulur keluar kotaknya tidak ikut terpotong. */
.reveal-on .judul.muncul { opacity: 1; translate: 0 0; }

.reveal-on .judul .judul__serif {
  opacity: 0;
  translate: 0 20px;
  transition: opacity .9s ease, translate 1s var(--ease);
}

.reveal-on .judul .judul__script {
  clip-path: inset(-40% 105% -50% -20%);
  transition: clip-path 1.6s cubic-bezier(.55, .1, .35, 1) .35s;
}

.reveal-on .judul.is-in .judul__serif { opacity: 1; translate: 0 0; }
.reveal-on .judul.is-in .judul__script { clip-path: inset(-40% -20% -50% -20%); }

/* Kalau skrip sudah memecah kata serif menjadi huruf, yang bergerak
   hurufnya: masing-masing berdiri dari posisi rebah ke belakang, satu
   per satu dari kiri. */
.reveal-on .judul .judul__serif--huruf {
  opacity: 1;
  translate: 0 0;
  perspective: 480px;
}

.reveal-on .judul .huruf {
  display: inline-block;
  opacity: 0;
  translate: 0 .3em;
  rotate: x -88deg;
  transform-origin: 50% 100%;
  transition: opacity .7s ease, translate .9s var(--ease), rotate 1s var(--ease);
  transition-delay: calc(var(--n, 0) * 60ms + .05s);
}

.reveal-on .judul.is-in .huruf {
  opacity: 1;
  translate: 0 0;
  rotate: x 0deg;
}

/* Tanda tangan penutup ditulis dari kiri ke kanan, seperti judul.
   clip-path sengaja dipasang pada span di dalamnya, bukan pada paragraf
   yang diamati: Chrome ikut memperhitungkan clip-path elemen itu sendiri
   saat menilai apakah ia terlihat, jadi elemen yang mulai dengan
   clip-path kosong tidak pernah dianggap masuk layar dan tidak pernah
   dimunculkan. */
.reveal-on .muncul.tulis { opacity: 1; translate: 0 0; }

.reveal-on .muncul.tulis > span {
  display: inline-block;
  clip-path: inset(-40% 105% -50% -20%);
  transition: clip-path 2.2s cubic-bezier(.55, .1, .35, 1) .2s;
}

.reveal-on .muncul.tulis.is-in > span { clip-path: inset(-40% -20% -50% -20%); }

/* Paragraf muncul dari kabur ke tajam, seperti lensa yang mencari fokus. */
.reveal-on .muncul:is(.ayat, .pengantar, .orang__ortu, .penutup__teks, .ucapan__intro, .hadiah__catatan) {
  filter: blur(8px);
  transition: opacity .9s ease, translate 1s var(--ease), filter 1.1s ease;
  transition-delay: var(--d, 0s);
}

.reveal-on .muncul.is-in:is(.ayat, .pengantar, .orang__ortu, .penutup__teks, .ucapan__intro, .hadiah__catatan) {
  filter: none;
}

/* Foto galeri dan kartu acara juga membesar sedikit saat masuk. */
.reveal-on .muncul:is(.ft, .kartu, .kaca, .rek-panggung) { scale: .92; }
.reveal-on .muncul.is-in:is(.ft, .kartu, .kaca, .rek-panggung) { scale: 1; }

/* Tulisan melengkung berputar masuk ke tempatnya. */
.reveal-on .lengkung.muncul {
  translate: 0 0;
  rotate: -14deg;
  transition: opacity 1s ease, rotate 1.4s var(--ease);
}

.reveal-on .lengkung.muncul.is-in { rotate: 0deg; }

/* Kolase: foto samping mulai dari balik foto tengah lalu membuka ke
   kiri dan kanan seperti kartu yang dikipas. Geraknya dari tengah ke
   luar, jadi tidak ada yang pernah melewati tepi layar. */
.reveal-on .kolase.muncul { opacity: 1; translate: 0 0; }
.reveal-on .kolase .kolase__foto { opacity: 0; }
.reveal-on .kolase .kolase__foto--kiri { translate: 36% 8%; rotate: 0deg; }
.reveal-on .kolase .kolase__foto--kanan { translate: -36% 8%; rotate: 0deg; }
.reveal-on .kolase .kolase__foto--tengah { scale: .88; }

.reveal-on .kolase.is-in .kolase__foto { opacity: 1; }
.reveal-on .kolase.is-in .kolase__foto--tengah { scale: 1; }

.reveal-on .kolase.is-in .kolase__foto--kiri {
  translate: 0 0;
  rotate: -5deg;
  transition-delay: .25s;
}

.reveal-on .kolase.is-in .kolase__foto--kanan {
  translate: 0 0;
  rotate: 5deg;
  transition-delay: .25s;
}


/* ================================================================
   Gerak
   ================================================================ */

@keyframes masuk {
  from { opacity: 0; translate: 0 18px; }
  to   { opacity: 1; translate: 0 0; }
}

@keyframes napas {
  from { scale: 1; }
  to   { scale: 1.07; }
}

@keyframes ajak {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -4px; }
}

@keyframes detak {
  from { opacity: .3; translate: 0 -6px; }
  to   { opacity: 1; translate: 0 0; }
}

@keyframes putar {
  to { rotate: 360deg; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0s !important;
  }
}
