/* ==================================================
   1. CSS RESET
   ================================================== */

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

html,
body {
    width: 100%;
    min-height: 100%;
}

body {
    overflow-x: hidden;                 /* pengaman: tidak pernah scroll ke samping */
    background-color: var(--page-bg);   /* area background DI LUAR kartu undangan */
    font-family: 'Jost', 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

img {
    max-width: 100%;
}

a:focus-visible,
:focus-visible {
    outline: 2px solid #7F1717;
    outline-offset: 3px;
    border-radius: 2px;
}

/* ==================================================
   2. TOKEN WARNA & TIPOGRAFI
   Warna diambil langsung dari sampling frame video.
   ================================================== */

:root {
    --cream: #FEEEE5;          /* latar section WITH LOVE */
    --maroon: #7F1717;         /* judul, nama, ikon, simbol & */
    --maroon-soft: #8B2E2E;    /* body text: ayat, deskripsi, orang tua */
    --mat: #FAF3E8;            /* warna dasar section BRIDE & GROOM */
    --mat-inner: #F7EFE1;      /* alas di dalam bingkai (sampling baground1.png) */

    /* Warna area di luar kartu undangan (desktop / tablet).
       Nilainya sama persis dengan warna body sebelumnya. */
    --page-bg: #EFDCD3;

    /* ---- LEBAR KARTU UNDANGAN ----
       Satu-satunya angka yang perlu diubah bila ingin kartu
       lebih lebar / lebih ramping di desktop. Contoh nilai aman:
       420px (ramping) - 480px (default) - 520px (lega).
       Kartu TIDAK PERNAH melebihi angka ini, jadi undangan tidak
       pernah memenuhi 100% lebar monitor. */
    --invite-max: 480px;

    --serif: 'Playfair Display', 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
    --script: 'Pinyon Script', 'Cormorant Garamond', cursive;
    --sans: 'Jost', 'Questrial', 'Segoe UI', Helvetica, Arial, sans-serif;

    --ease: cubic-bezier(.22, .61, .36, 1);
}

/* ==================================================
   3. KANVAS UNDANGAN + SATUAN SKALA

   .invite       : KARTU undangan. Lebarnya 100% pada HP dan
                   berhenti di --invite-max (480px) pada tablet
                   & desktop, lalu diposisikan di tengah layar
                   dengan margin: 0 auto. Sisa layar di kiri dan
                   kanan menjadi area background (body).

   .invite-inner : pemegang satuan skala.

   Seluruh ukuran halaman ini memakai satuan "design px",
   yaitu piksel pada kanvas selebar 738px (lebar asli
   baground1.png). 1 design px = var(--u).

   --u : satuan geometri (posisi, ukuran, bingkai foto)
   --t : satuan tipografi, punya batas bawah 0.5px supaya
         teks tetap terbaca pada layar 320px - 360px.

   Karena semua ukuran memakai --u, tata letak otomatis
   proporsional pada 320 / 360 / 375 / 390 / 414 / 768 /
   1024px dan desktop: tidak ada elemen yang gepeng,
   terpotong, atau keluar layar.
   ================================================== */

.invite {
    position: relative;
    width: 100%;
    max-width: var(--invite-max);   /* <- kunci: tidak pernah full layar */
    margin: 0 auto;                 /* <- kunci: selalu di tengah layar  */
    background-color: var(--cream);
    box-shadow: 0 0 60px rgba(90, 30, 25, .10);
    overflow: hidden;               /* menjaga isi full-bleed tetap rapi di tepi */

    /* Bila suatu saat ingin sudut kartu membulat di desktop,
       cukup hapus tanda komentar pada baris di bawah ini.
       Video full-bleed otomatis ikut terpotong rapi karena
       .invite sudah memakai overflow: hidden.
    border-radius: 18px;
    */
}

.invite-inner {
    --u: calc(min(100vw, var(--invite-max)) / 738);
    --t: max(var(--u), .5px);
    position: relative;
    width: 100%;
}

@supports (width: 1cqw) {

    .invite {
        container-type: inline-size;
    }

    .invite-inner {
        --u: .13550136cqw;   /* 100 / 738 -> selalu relatif LEBAR KARTU */
        --t: max(var(--u), .5px);
    }
}

/* ==================================================
   4. SCROLL REVEAL

   Kondisi awal semua elemen ber-class .reveal adalah
   tersembunyi, dan hanya muncul setelah masuk viewport
   (ditandai class .is-visible dari IntersectionObserver).
   ================================================== */

.reveal {
    transition-property: opacity, transform;
    transition-duration: 1.05s;
    transition-timing-function: var(--ease);
    transition-delay: var(--delay, 0s);
    will-change: opacity, transform;
}

html.js .reveal {
    opacity: 0;
    transform: translateY(calc(26 * var(--u)));
}

html.js .reveal-photo {
    transform: translateY(calc(34 * var(--u))) scale(.955);
    transition-duration: 1.3s;
}

html.js .reveal-soft {
    transform: none;
    transition-duration: 1.5s;
}

html.js .reveal.is-visible {
    opacity: 1;
    transform: translateY(0) scale(1);
}

@media (prefers-reduced-motion: reduce) {

    html.js .reveal,
    html.js .reveal-photo,
    html.js .reveal-soft {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
    }
}

/* ==================================================
   4a. SECTION 0 — VIDEO PEMBUKA (img/mw.mp4)

   Rasio kotak (--video-ratio) di-set otomatis oleh
   script.js sesuai rasio asli mw.mp4, sehingga:
       kotak = rasio video  ->  object-fit: cover
       tidak gepeng, tidak terpotong, tidak ada bar hitam.

   URUTAN LAYER:
       1. video            .hero-video              z-index 1
       2. skeleton loading .video-frame::before     z-index 2
       3. hairline frame   .video-frame::after      z-index 4
       4. fallback error   .video-fallback          z-index 5
       5. tombol           .video-play/.video-sound z-index 6
   ================================================== */

.video-section {
    position: relative;
    z-index: 1;
    width: 100%;
    background-color: var(--cream);

    /* REVISI: padding dinolkan supaya video menempel penuh ke
       tepi kartu undangan. Tidak ada lagi jalur kosong di kiri,
       kanan, atas, maupun bawah video. */
    padding: 0;
    overflow: hidden;                 /* pengaman scroll horizontal */
}

.video-frame {
    --video-w: 672;                   /* lebar kartu dalam design px (mode kartu) */
    --video-ratio: 4 / 5;             /* dipakai sebelum metadata terbaca */
    position: relative;
    display: block;
    width: calc(var(--video-w) * var(--u));
    max-width: 100%;
    margin: 0 auto;
    aspect-ratio: var(--video-ratio);
    border-radius: calc(18 * var(--u));
    background-color: #F3E3DA;
    overflow: hidden;
    box-shadow: 0 calc(14 * var(--u)) calc(38 * var(--u)) rgba(127, 23, 23, .16);
}

/* Video portrait (mis. 9:16) dipersempit sedikit supaya tinggi
   kartu tetap nyaman dilihat di layar HP. (Hanya berlaku pada
   mode kartu; pada mode full-bleed lebar tetap 100%.) */
.video-frame.is-portrait { --video-w: 580; }

/* ---------------------------------------------------
   MODE FULL-BLEED  (class .video-frame--bleed)

   Video memenuhi SELURUH lebar kartu undangan:
   - width 100%  -> menempel ke sisi kiri dan kanan
   - margin 0    -> tidak ada jarak putih di sekelilingnya
   - radius 0    -> sudut tidak membulat, jadi tidak muncul
                    celah warna di keempat pojok
   - shadow 0    -> tidak ada bayangan yang terlihat seperti garis
   - hairline    -> dimatikan (lihat ::after di bawah)

   Tingginya mengikuti rasio ASLI video (--video-ratio dari
   script.js), sehingga video tidak gepeng dan tidak terpotong,
   serta tidak pernah menyebabkan scroll horizontal karena
   lebarnya persis sama dengan lebar kartu undangan.
   --------------------------------------------------- */

.video-frame--bleed,
.video-frame--bleed.is-portrait {
    --video-w: 738;                   /* = lebar penuh kanvas undangan */
    display: block;
    width: 100%;
    max-width: 100%;
    margin: 0;
    border-radius: 0;
    box-shadow: none;
}

/* Catatan: penonaktifan garis hairline pada mode full-bleed
   ditulis tepat SESUDAH aturan .video-frame::after (lihat di
   bawah), karena kedua selector punya bobot yang sama sehingga
   yang menang adalah yang ditulis paling akhir. */

.hero-video {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: block;                   /* menghilangkan celah baseline */
    width: 100%;
    height: 100%;
    border-radius: inherit;           /* fix sudut membulat di iOS Safari */
    object-fit: cover;                /* memenuhi area, tidak gepeng */
    object-position: center;
    background-color: #F3E3DA;
}

/* ---- Skeleton halus selama video belum siap diputar ---- */

.video-frame::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 2;
    background-image: linear-gradient(100deg, #F3E3DA 30%, #FBEDE5 50%, #F3E3DA 70%);
    background-size: 220% 100%;
    animation: videoShimmer 1.6s linear infinite;
    transition: opacity .6s var(--ease);
    pointer-events: none;
}

.video-frame.is-ready::before {
    opacity: 0;
    animation: none;
}

html:not(.js) .video-frame::before { display: none; }

@keyframes videoShimmer {
    from { background-position: 120% 0; }
    to   { background-position: -120% 0; }
}

/* ---- Hairline elegan di tepi dalam kartu (mode kartu) ---- */

.video-frame::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 4;
    border-radius: inherit;
    pointer-events: none;
    box-shadow:
        inset 0 0 0 calc(2 * var(--u)) rgba(255, 255, 255, .55),
        inset 0 0 0 calc(3.4 * var(--u)) rgba(127, 23, 23, .16);
}

/* Pada mode FULL-BLEED garis hairline dimatikan, supaya tidak
   terlihat sebagai garis terang di tepi kiri dan kanan video.
   Aturan aslinya di atas tetap utuh dan langsung aktif kembali
   bila class .video-frame--bleed dilepas dari index.html. */

.video-frame--bleed::after {
    box-shadow: none;
}

/* ---- Animasi masuk viewport (fade + translate + scale) ---- */

html.js .video-reveal {
    opacity: 0;
    transform: translateY(calc(34 * var(--u))) scale(.965);
    transition-property: opacity, transform;
    transition-duration: 1.25s;
    transition-timing-function: var(--ease);
    transition-delay: var(--delay, 0s);
    will-change: opacity, transform;
}

html.js .video-reveal.is-visible {
    opacity: 1;
    transform: translateY(0) scale(1);
}

/* Pada mode full-bleed animasinya cukup fade saja.
   Alasannya: scale/translate akan membuat video sesaat lebih
   sempit dari kartu, sehingga sempat terlihat celah warna di
   kiri-kanan. Dengan fade murni, tepi video selalu menempel. */

html.js .video-frame--bleed.video-reveal,
html.js .video-frame--bleed.video-reveal.is-visible {
    transform: none;
}

/* ---- Tombol putar & tombol suara ---- */

.video-play,
.video-sound {
    position: absolute;
    z-index: 6;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    border-radius: 50%;
    color: #FFFFFF;
    background-color: rgba(127, 23, 23, .62);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    cursor: pointer;
    transition: transform .35s var(--ease),
                background-color .35s var(--ease),
                opacity .45s var(--ease);
}

.video-play svg,
.video-sound svg {
    display: block;
}

.video-sound {
    right: calc(22 * var(--u));
    bottom: calc(22 * var(--u));
    width: calc(56 * var(--u));
    height: calc(56 * var(--u));
}

.video-sound svg {
    width: calc(28 * var(--u));
    height: calc(28 * var(--u));
}

.video-sound:hover,
.video-sound:focus-visible { transform: scale(1.07); }

.video-sound .icon-on { display: none; }

.video-frame.is-unmuted .video-sound .icon-on  { display: block; }
.video-frame.is-unmuted .video-sound .icon-off { display: none; }

.video-play {
    left: 50%;
    top: 50%;
    width: calc(96 * var(--u));
    height: calc(96 * var(--u));
    transform: translate(-50%, -50%) scale(.9);
    opacity: 0;
    visibility: hidden;
}

.video-play svg {
    width: calc(40 * var(--u));
    height: calc(40 * var(--u));
    margin-left: calc(4 * var(--u));   /* optical centering segitiga play */
}

.video-frame.is-paused .video-play {
    opacity: 1;
    visibility: visible;
    transform: translate(-50%, -50%) scale(1);
}

.video-frame.is-paused .video-play:hover,
.video-frame.is-paused .video-play:focus-visible {
    transform: translate(-50%, -50%) scale(1.07);
}

/* ---------------------------------------------------
   VIDEO SELESAI (class .is-ended dari script.js)

   Video hanya diputar SATU KALI. Setelah selesai, video
   berhenti pada frame terakhir dan seluruh tombol kontrol
   disembunyikan supaya video tidak dapat diputar ulang.
   Aturan ini sengaja ditulis SESUDAH aturan .is-paused
   agar tombol putar tetap tersembunyi.
   --------------------------------------------------- */

.video-frame.is-ended .video-play,
.video-frame.is-ended .video-sound {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

/* Tanpa JavaScript tombol tidak berfungsi -> disembunyikan. */
html:not(.js) .video-play,
html:not(.js) .video-sound { display: none; }

/* ---- Fallback bila video gagal dimuat ---- */

.video-fallback {
    position: absolute;
    inset: 0;
    z-index: 5;
    display: none;
    align-items: center;
    justify-content: center;
    padding: calc(30 * var(--u));
    background-color: var(--mat-inner);
    text-align: center;
}

.video-fallback p {
    color: var(--maroon-soft);
    font-family: var(--sans);
    font-size: calc(19 * var(--t));
    line-height: 1.6;
}

.video-fallback a {
    color: var(--maroon);
    text-decoration: underline;
}

.video-frame.is-error .video-fallback { display: flex; }
.video-frame.is-error::before,
.video-frame.is-error .video-play,
.video-frame.is-error .video-sound { display: none; }

@media (prefers-reduced-motion: reduce) {

    html.js .video-reveal {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
    }

    .video-frame::before { animation: none !important; }
}

@media print {

    html.js .video-reveal {
        opacity: 1;
        transform: none;
    }

    .video-play,
    .video-sound,
    .video-frame::before { display: none !important; }
}

/* ==================================================
   5. SECTION 1 — WITH LOVE
   ================================================== */

.invitation-page {
    position: relative;
    width: 100%;
    background-color: var(--cream);
    overflow: hidden;
}

.invitation-content {
    position: relative;
    z-index: 1;
    width: 100%;
    padding-top: calc(117 * var(--u));
    padding-bottom: calc(86 * var(--u));
}

/* ---- Kartu foto pasangan ---- */

.image-card {
    position: relative;
    z-index: 1;
    width: calc(672 * var(--u));
    aspect-ratio: 672 / 705;
    margin: 0 auto;
    background-color: #FFFFFF;
    border-radius: calc(18 * var(--u));
    overflow: hidden;
}

.couple-image {
    display: block;
    width: 100%;
    height: 100%;
    /* Ganti ke "contain" bila pasangan.png berupa ilustrasi
       bertransparansi dan tidak boleh terpotong. */
    object-fit: cover;
    object-position: center;
}

/* ---- WITH LOVE + ayat ---- */

.love-section {
    position: relative;
    z-index: 20;
    text-align: center;
}

.love-section h1 {
    color: var(--maroon);
    font-family: var(--serif);
    font-weight: 600;
    font-size: calc(40 * var(--t));
    line-height: 1.2;
    letter-spacing: calc(3 * var(--t));
    text-transform: uppercase;
    margin-top: calc(31 * var(--u));
}

.verse {
    color: var(--maroon-soft);
    font-family: var(--sans);
    font-weight: 400;
    font-size: calc(19 * var(--t));
    line-height: 1.6;
    max-width: calc(660 * var(--u));
    margin: calc(41 * var(--u)) auto 0;
    padding: 0 calc(30 * var(--u));
    text-align: center;
}

.reference {
    color: var(--maroon-soft);
    font-family: var(--sans);
    font-weight: 400;
    font-size: calc(19 * var(--t));
    line-height: 1.6;
    margin-top: calc(42 * var(--u));
    text-align: center;
}

/* ---- Dekorasi bunga ----
   Posisi & ukuran absolute sekarang dipegang oleh wrapper
   .flower-sway, sehingga transform animasi "reveal" (fade-in
   saat masuk viewport, pada elemen <img>.flower) tidak bentrok
   dengan transform animasi floating/sway (pada wrapper). */

.flower-sway {
    position: absolute;
    z-index: 10;
    pointer-events: none;
    transform-origin: 50% 15%;
}

.flower-sway--1 {
    top: calc(70 * var(--u));
    right: calc(-12 * var(--u));
    width: calc(200 * var(--u));
    animation: flowerSway1 7.5s ease-in-out .4s infinite;
}

.flower-sway--2 {
    top: calc(500 * var(--u));
    left: calc(-27 * var(--u));
    width: calc(200 * var(--u));
    animation: flowerSway2 8.6s ease-in-out 1.1s infinite;
}

.flower {
    position: static;
    display: block;
    width: 100%;
    height: auto;
    pointer-events: none;
    user-select: none;
    -webkit-user-drag: none;
}

/* Gerakan sway/floating sangat halus: sedikit naik-turun +
   pergeseran horizontal tipis + rotasi mikro, meniru bunga
   dekorasi tertiup angin ringan pada video referensi. Amplitudo
   kecil & timing berbeda antar bunga supaya terasa natural,
   tidak sinkron dan tidak mencolok. */

@keyframes flowerSway1 {
    0%   { transform: translate(0, 0) rotate(0deg); }
    25%  { transform: translate(calc(-2.5 * var(--u)), calc(-3.5 * var(--u))) rotate(-0.6deg); }
    50%  { transform: translate(calc(1 * var(--u)), calc(-6 * var(--u))) rotate(0.3deg); }
    75%  { transform: translate(calc(2.5 * var(--u)), calc(-2.5 * var(--u))) rotate(0.7deg); }
    100% { transform: translate(0, 0) rotate(0deg); }
}

@keyframes flowerSway2 {
    0%   { transform: translate(0, 0) rotate(0deg); }
    30%  { transform: translate(calc(2.5 * var(--u)), calc(-4 * var(--u))) rotate(0.6deg); }
    60%  { transform: translate(calc(-1.5 * var(--u)), calc(-6.5 * var(--u))) rotate(-0.5deg); }
    80%  { transform: translate(calc(-2.5 * var(--u)), calc(-2 * var(--u))) rotate(-0.3deg); }
    100% { transform: translate(0, 0) rotate(0deg); }
}

@media (prefers-reduced-motion: reduce) {
    .flower-sway--1,
    .flower-sway--2 {
        animation: none;
    }
}

/* ==================================================
   6. SECTION 2 — BRIDE & GROOM

   .couple-stage memakai img/baground1.png dengan rasio
   terkunci 738 x 2132 (ukuran asli file), sehingga
   background tidak pernah terdistorsi dan semua ornamen
   tepinya tetap berada pada posisi aslinya.

   URUTAN LAYER (dari belakang ke depan):
       1. img/baground1.png : background .couple-stage
       2. alas foto         : .couple-photo-frame::before
       3. foto pengantin    : .photo-pengantin      z-index 1
       4. bingkai           : .bingkai-pengantin    z-index 2
       5. teks & ikon       : .zone                 z-index 5
   ================================================== */

.couple-section {
    position: relative;
    width: 100%;
    background-color: var(--mat);
}

.couple-stage {
    position: relative;
    width: 100%;
    aspect-ratio: 738 / 2132;
    background-color: var(--mat);
    background-image: url("img/baground1.png");
    background-repeat: no-repeat;
    background-position: center top;
    /* Rasio kotak sama dengan rasio file, jadi 100% 100%
       identik dengan "cover": motif tidak gepeng. */
    background-size: 100% 100%;
    overflow: hidden;
}

.stage-inner {
    position: absolute;
    inset: 0;
}

/* ---- Zona teks (diposisikan pada kanvas 738 x 2132) ---- */

.zone {
    position: absolute;
    left: 0;
    right: 0;
    z-index: 5;          /* teks selalu di atas background & bingkai */
    text-align: center;
}

.zone--intro  { top: calc(120 * var(--u)); }
.zone--groom  { top: calc(760 * var(--u)); }
.zone--bride  { top: calc(1740 * var(--u)); }

/* ---- Judul & kalimat undangan ---- */

.section-title {
    color: var(--maroon);
    font-family: var(--serif);
    font-weight: 600;
    font-size: calc(40 * var(--t));
    line-height: 1.2;
    letter-spacing: calc(2.5 * var(--t));
}

.invite-text {
    color: var(--maroon-soft);
    font-family: var(--sans);
    font-weight: 400;
    font-size: calc(19 * var(--t));
    line-height: 1.32;
    max-width: calc(610 * var(--u));
    margin: calc(28 * var(--u)) auto 0;
    padding: 0 calc(20 * var(--u));
}

/* ==================================================
   6a. BINGKAI FOTO PENGANTIN (bingkaipengantin.png)

   Bingkai TIDAK dibuat dengan CSS dan TIDAK dipakai
   sebagai background: ia adalah <img> tersendiri yang
   menumpuk di atas foto, sehingga bentuk, warna, dan
   dekorasi bunganya persis seperti file aslinya.

   Ukuran asli asset : 1186 x 1327 px
   Lubang foto (oval): x 202-957, y 14-1259 pada asset
                       -> 17.032% / 1.055% / 63.744% / 93.896%
                       -> radius sudut = 378px = 50% / 30.337%

   Semua angka di atas dipakai dalam PERSEN terhadap
   bingkai, jadi foto selalu terkunci di lubang oval
   berapa pun lebar layarnya.
   ================================================== */

.couple-photo-frame {
    position: absolute;
    left: calc(169 * var(--u));          /* (738 - 400) / 2 */
    z-index: 3;                          /* di atas background, di bawah teks */
    width: calc(400 * var(--u));
    aspect-ratio: 1186 / 1327;           /* rasio asli bingkaipengantin.png */
    margin: 0;
}

.couple-photo-frame--groom { top: calc(296 * var(--u)); }
.couple-photo-frame--bride { top: calc(1280 * var(--u)); }

/* Alas mat di dalam lubang bingkai. Muncul bersama bingkai,
   lalu menjadi "tempat mendarat" foto pada langkah kedua. */

.couple-photo-frame::before {
    content: "";
    position: absolute;
    left: 17.032%;
    top: 1.055%;
    width: 63.744%;
    height: 93.896%;
    z-index: 0;
    border-radius: 50% / 30.337%;
    background-color: var(--mat-inner);
    transform-origin: 51.7% 52.1%;       /* titik pusat bingkai */
}

/* ---- Foto pengantin: layer BAWAH ---- */

.photo-pengantin {
    position: absolute;
    left: 17.032%;
    top: 1.055%;
    width: 63.744%;
    height: 93.896%;
    z-index: 1;
    display: block;
    border-radius: 50% / 30.337%;        /* mengikuti bentuk lubang bingkai */
    background-color: var(--mat-inner);
    object-fit: cover;                   /* foto tidak pernah gepeng */
    /* Geser ke "center 20%" bila wajah terlalu ke bawah,
       atau "center center" untuk foto setengah badan. */
    object-position: center top;
    transform-origin: 51.7% 52.1%;
}

/* ---- Bingkai: layer ATAS (overlay) ---- */

.bingkai-pengantin {
    position: absolute;
    left: 0;
    top: 0;
    z-index: 2;
    display: block;
    width: 100%;
    height: auto;                        /* proporsi asset dipertahankan */
    pointer-events: none;
    user-select: none;
    -webkit-user-drag: none;
}

/* ==================================================
   6b. ANIMASI: BINGKAI DULU, FOTO MENYUSUL

   Dikendalikan script.js lewat dua class:
       .is-active    -> bingkai + alas mulai muncul
       .is-photo-in  -> foto pengantin mulai muncul
   Tanpa JavaScript (html tanpa class .js) semuanya
   langsung terlihat, jadi undangan tetap terbaca.
   ================================================== */

html.js .couple-photo-frame::before,
html.js .couple-photo-frame .bingkai-pengantin,
html.js .couple-photo-frame .photo-pengantin {
    opacity: 0;
    will-change: opacity, transform;
}

/* Langkah 1 — bingkai */

html.js .couple-photo-frame::before,
html.js .couple-photo-frame .bingkai-pengantin {
    transform: scale(.93);
    transition-property: opacity, transform;
    transition-duration: 1.15s;
    transition-timing-function: var(--ease);
}

/* Langkah 2 — foto (dimulai setelah bingkai, diatur script.js) */

html.js .couple-photo-frame .photo-pengantin {
    transform: scale(.955);
    transition-property: opacity, transform;
    transition-duration: 1.2s;
    transition-timing-function: var(--ease);
}

html.js .couple-photo-frame.is-active::before,
html.js .couple-photo-frame.is-active .bingkai-pengantin {
    opacity: 1;
    transform: scale(1);
}

html.js .couple-photo-frame.is-photo-in .photo-pengantin {
    opacity: 1;
    transform: scale(1);
}

/* Setelah animasi selesai, lepaskan will-change agar
   scroll tetap ringan di HP. */

html.js .couple-photo-frame.is-photo-in::before,
html.js .couple-photo-frame.is-photo-in .bingkai-pengantin,
html.js .couple-photo-frame.is-photo-in .photo-pengantin {
    will-change: auto;
}

@media (prefers-reduced-motion: reduce) {

    html.js .couple-photo-frame::before,
    html.js .couple-photo-frame .bingkai-pengantin,
    html.js .couple-photo-frame .photo-pengantin {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
    }
}

/* ---- Nama script (Habib / Adiba) ---- */

.script-name {
    color: var(--maroon);
    font-family: var(--script);
    font-weight: 400;
    font-size: calc(74 * var(--t));
    line-height: 1.15;
    margin-top: calc(20 * var(--u));
}

/* ---- Nama lengkap ---- */

.full-name {
    color: var(--maroon);
    font-family: var(--serif);
    font-weight: 600;
    font-size: calc(30 * var(--t));
    line-height: 1.2;
    letter-spacing: calc(2 * var(--t));
    margin-top: calc(14 * var(--u));
}

/* ---- Keterangan orang tua ---- */

.parents {
    color: var(--maroon-soft);
    font-family: var(--sans);
    font-weight: 400;
    font-size: calc(19 * var(--t));
    line-height: 1.55;
    margin-top: calc(20 * var(--u));
    padding: 0 calc(40 * var(--u));
}

/* ---- Ikon Instagram ---- */

.social {
    line-height: 0;
    margin-top: calc(26 * var(--u));
}

.ig-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: calc(50 * var(--u));
    height: calc(50 * var(--u));
    border-radius: 50%;
    background-color: var(--maroon);
    color: #FFFFFF;
    text-decoration: none;
    transition: transform .35s var(--ease);
}

.ig-badge:hover,
.ig-badge:focus-visible {
    transform: scale(1.06);
}

.ig-badge svg {
    width: calc(27 * var(--u));
    height: calc(27 * var(--u));
    display: block;
}

/* ---- Simbol penghubung "&" (img/dan.png) ----

   REVISI: sebelumnya simbol "&" berupa teks (font-size besar,
   font-style italic). Sekarang diganti <img src="img/dan.png">
   agar tampil sebagai elemen bergambar, bukan karakter teks.

   Elemen tetap memakai class "amp reveal" yang sama, jadi:
   - animasi reveal (fade + translate) tetap berjalan seperti
     sebelumnya, dikendalikan sepenuhnya oleh class .reveal;
   - posisi vertikal dipertahankan lewat margin-top yang sama
     (56 design px) seperti versi teks sebelumnya, sehingga
     tidak menggeser posisi nama kedua mempelai di atasnya
     (script-name, full-name, dsb. berada SEBELUM elemen ini
     dalam urutan HTML, jadi tidak terpengaruh sama sekali).

   Lebar gambar diatur dalam design px lewat --amp-w, lalu
   height:auto mengunci rasio ASLI file dan.png -> gambar
   tidak pernah gepeng atau terdistorsi di layar mana pun.
   Nilai 120 dipilih agar proporsinya sepadan dengan ukuran
   "&" sebelumnya (elegan, tidak mendominasi); ubah angka ini
   bila ingin dan.png tampil lebih besar/kecil. */

.amp {
    --amp-w: 120;                          /* lebar gambar, dalam design px */
    display: block;
    width: calc(var(--amp-w) * var(--u));
    height: auto;
    margin: calc(56 * var(--u)) auto 0;    /* auto -> tetap center seperti teks */
    pointer-events: none;
    user-select: none;
    -webkit-user-drag: none;
}

/* ==================================================
   6c. SECTION 3 — SAVE THE DATE

   Layout mengikuti referensi: pasangan tampil UTUH (tidak
   di-crop/cover) di tengah, dengan latar polos bernuansa hangat
   (sampling warna dari foto), judul di atas, dan countdown
   sejajar horizontal mengapit pasangan di kiri & kanan.

   .std-stage  = kanvas section, tinggi menyesuaikan konten
                 (tidak dikunci rasio 9:16 -> supaya pasangan
                 tidak pernah gepeng/terpotong, mengikuti aturan
                 "jangan mengubah bentuk/proporsi img/pasangan.png").
   .std-row    = flex row: [countdown kiri] [foto] [countdown kanan]
   ================================================== */

.std-section {
    position: relative;
    width: 100%;
    /* Warna hangat netral, sampling nuansa dari foto pasangan,
       dipakai sebagai latar polos di sekeliling foto (bukan
       overlay DI ATAS foto), persis seperti referensi. */
    background-color: #AD9599;
    overflow: hidden;
}

.std-stage {
    position: relative;
    width: 100%;
    padding: calc(56 * var(--u)) calc(20 * var(--u)) calc(64 * var(--u));
    display: flex;
    flex-direction: column;
    align-items: center;
}

/* Overlay sangat tipis di seluruh kanvas (bukan di atas foto
   secara langsung) agar judul & angka tetap kontras walau warna
   latar berubah-ubah tanpa pernah menggelapkan foto pasangan
   itu sendiri secara berlebihan. */

.std-overlay {
    position: absolute;
    inset: 0;
    z-index: 0;
    background: linear-gradient(
        to bottom,
        rgba(40, 22, 20, .10) 0%,
        rgba(40, 22, 20, 0) 22%,
        rgba(40, 22, 20, 0) 78%,
        rgba(40, 22, 20, .14) 100%
    );
    pointer-events: none;
}

.std-title {
    position: relative;
    z-index: 1;
    color: #FFFFFF;
    font-family: var(--serif);
    font-weight: 600;
    font-size: calc(30 * var(--t));
    letter-spacing: calc(3 * var(--t));
    line-height: 1.25;
    text-align: center;
    text-shadow: 0 calc(2 * var(--u)) calc(14 * var(--u)) rgba(0, 0, 0, .25);
}

.std-row {
    position: relative;
    z-index: 1;
    width: 100%;
    margin-top: calc(18 * var(--u));
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: calc(6 * var(--u));
}

/* ---- Foto pasangan: UTUH, proporsi asli dipertahankan ---- */

.std-couple {
    flex: 0 0 auto;
    width: calc(280 * var(--u));
    max-width: 42%;
    height: auto;               /* rasio asli terkunci, tidak pernah gepeng */
    display: block;
    pointer-events: none;
    user-select: none;
    -webkit-user-drag: none;
}

/* ---- Countdown: dua kolom di kiri, dua kolom di kanan ---- */

.std-countdown {
    flex: 1 1 0;
    display: flex;
    align-items: flex-end;
}

.std-countdown--left  { justify-content: flex-end; }
.std-countdown--right { justify-content: flex-start; }

.std-unit {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: calc(84 * var(--u));
}

.std-number {
    color: #FFFFFF;
    font-family: var(--serif);
    font-weight: 600;
    font-size: calc(44 * var(--t));
    line-height: 1;
    font-variant-numeric: tabular-nums;
    text-shadow: 0 calc(2 * var(--u)) calc(14 * var(--u)) rgba(0, 0, 0, .3);
}

.std-label {
    margin-top: calc(8 * var(--u));
    color: rgba(255, 255, 255, .88);
    font-family: var(--sans);
    font-weight: 400;
    font-size: calc(15 * var(--t));
    letter-spacing: calc(.5 * var(--t));
}

/* ---- Pesan setelah acara dimulai ---- */

.std-started {
    position: relative;
    z-index: 1;
    margin-top: calc(22 * var(--u));
    color: #FFFFFF;
    font-family: var(--serif);
    font-style: italic;
    font-weight: 500;
    font-size: calc(19 * var(--t));
    text-align: center;
}

.std-started[hidden] {
    display: none;
}

@media (max-width: 360px) {

    .std-unit {
        width: calc(68 * var(--u));
    }

    .std-number {
        font-size: calc(36 * var(--t));
    }
}

/* ==================================================
   6d. UNDANGAN — EFEK LATAR "JENDELA" (digabung dari style2.css)

   Seluruh isi style2.css ditempatkan di sini, tepat setelah
   bagian 6c. SECTION 3 — SAVE THE DATE.
   ================================================== */

/* =========================================================
   UNDANGAN PERNIKAHAN — Efek latar "jendela" (fixed reveal)
   ---------------------------------------------------------
   Mekanisme (hasil analisis video referensi):
   - Setiap seksi punya latar yang DIAM terhadap layar.
   - Seksi hanya bertindak sebagai "jendela" yang menyingkap
     latar tersebut, sehingga saat scroll konten bergerak
     sementara pepohonan / wayang / Borobudur tetap di tempat.
   - Batas antar-seksi tajam & lurus (tanpa fade).

   Implementasi: pseudo-layer position:fixed di dalam elemen
   ber-`clip-path: inset(0)`. Ini menggantikan
   `background-attachment: fixed` yang RUSAK di iOS Safari
   dan banyak browser mobile.
   ========================================================= */

:root {
  /* Palet hasil sampling video */
  --maroon-bg:     #6f080e;   /* dasar bg.png */
  --maroon-text:   #7a1e24;   /* judul & teks pada latar krem */
  --cream-bg:      #fdefe0;   /* dasar bg2.png */
  --cream-card:    #fdeee3;
  --gold-1:        #d9bd7a;
  --gold-2:        #b8964a;
  --gold-3:        #8f6f2e;

  /* Lebar konten mengikuti proporsi video: kartu ±90,5% layar */
  --page-max: 480px;
  --col-w: 100%;                        /* lebar kolom berlatar; dibatasi di layar lebar */
  --gutter: 4.8%;

  --font-head: "Playfair Display", "Cinzel", Georgia, serif;
  --font-body: "Mate", Georgia, "Times New Roman", serif;
}

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

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

.page {
  margin: 0;
  background: var(--maroon-bg);        /* cadangan bila gambar belum termuat */
  color: var(--maroon-text);
  font-family: var(--font-body);
  font-size: 13px;
  line-height: 1.6;
  overflow-x: hidden;
}

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

/* =========================================================
   SEKSI + LATAR "JENDELA"
   ========================================================= */
.page {
  position: relative;
  max-width: var(--col-w);
  margin-inline: auto;
}

.sect {
  position: relative;
  isolation: isolate;                   /* konteks tumpukan sendiri */
  clip-path: inset(0);                  /* ← kunci: memotong anak position:fixed */
  overflow: hidden;
}

/* Lapisan latar: fixed terhadap viewport, terpotong oleh .sect */
.sect__bg {
  position: fixed;
  top: 0;
  left: 50%;
  translate: -50% 0;
  width: min(100%, var(--col-w));       /* mengikuti kolom → cover tidak memperbesar berlebihan */
  height: 100vh;                        /* fallback */
  height: 100lvh;                       /* tinggi layar terbesar: aman dari bar URL mobile */
  z-index: -1;
  pointer-events: none;
  background-repeat: no-repeat;
  background-size: cover;               /* tidak gepeng / melar / ada area kosong */
  will-change: transform;               /* promosi ke layer GPU → scroll mulus */
  transform: translateZ(0);
}

/* ---------- Latar MERAH (bg.png) ---------- */
.sect--red .sect__bg {
  background-color: var(--maroon-bg);
  background-image:
    /* Overlay putih bergradasi: α 0,09 (atas) → 0,23 (bawah)
       hasil hitung dari selisih warna bg.png asli vs video */
    linear-gradient(to bottom,
      rgba(255,255,255,0.09) 0%,
      rgba(255,255,255,0.11) 20%,
      rgba(255,255,255,0.18) 45%,
      rgba(255,255,255,0.23) 72%,
      rgba(255,255,255,0.22) 100%),
    url("img/bg.png");
  background-position: center, center center;
}

/* ---------- Latar KREM (bg2.png) ---------- */
.sect--cream .sect__bg {
  background-color: var(--cream-bg);
  background-image:
    /* Overlay krem α=0,50 — hasil pencocokan RMSE terhadap frame video */
    linear-gradient(rgba(253,239,224,0.50), rgba(253,239,224,0.50)),
    url("img/bg2.png");
  background-position: center, center bottom;  /* Borobudur menempel di dasar layar */
}

/* Konten berada di atas latar */
.sect__content {
  position: relative;
  z-index: 1;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

/* =========================================================
   SEKSI 1 — RESEPSI (kartu oval)
   ---------------------------------------------------------
   Hasil ukur video: oval = 90,7% lebar layar (x 20→400 dari 420).
   akad.png punya margin transparan ±3,8% kiri-kanan, sehingga
   bitmap harus dilebarkan ke ±99% agar oval tampil 90,7%.
   Rasio bitmap asli (999×1575) dijaga → tidak melar.
   ========================================================= */
#resepsi .sect__content {
  max-width: none;                       /* kartu dihitung terhadap lebar layar */
  padding: 0;
}
#resepsi .sect__content > .oval { margin-inline: auto; }

#resepsi { padding-top: 0; padding-bottom: 0; }

/* Lebar bitmap relatif terhadap kolom (mengikuti --page-max di desktop) */
.oval {
  --bitmap-w: min(99vw, calc(var(--page-max) * 0.99));
  position: relative;
  width: var(--bitmap-w);
  aspect-ratio: 999 / 1575;
  translate: 0.2% 0;                    /* pusat oval (bukan pusat bitmap) di tengah layar */
  margin-top: 10px;                      /* oval mulai ±10 px di dalam bitmap → tepi atas ≈ layar */
  margin-bottom: 26px;                   /* ruang merah di bawah oval untuk ornamen wayang */
  background: url("img/akad.png") center / 100% 100% no-repeat;
  filter: drop-shadow(0 8px 18px rgba(40, 0, 4, .32));
}

/* Ornamen sudut (batik kotak-kotak) — terlihat di video pada kanan-atas oval */
.oval__inner {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  /* konten hanya di dalam oval: 3,8% margin transparan + ruang lengkung */
  padding: 12.6% 12% 0;
}

/* Tipografi kartu — ukuran & jarak disalin dari video (layar 420 px)
   judul 17px-tinggi-kapital ≈ 21px font · angka 37px-tinggi ≈ 52px font
   Semua memakai vw agar proporsional di semua lebar layar.            */
.oval__title {
  margin: 0;
  font: 500 clamp(17px, 5.1vw, 24px)/1.15 var(--font-head);
  letter-spacing: .05em;
  color: var(--maroon-text);
}
.oval__day   { margin: 5.6% 0 0;  font-size: clamp(11px, 3.3vw, 15px); letter-spacing: .05em; }
.oval__date  { margin: 2.2% 0 0;  font: 400 clamp(38px, 12.4vw, 58px)/1 var(--font-head); }
.oval__month { margin: 6.6% 0 0;  font-size: clamp(11px, 3.3vw, 15px); letter-spacing: .05em; }
.oval__time  { margin: 4.4% 0 0;  font-size: clamp(11px, 3.3vw, 15px); }

.divider {
  display: flex; align-items: center; gap: 10px;
  width: 84%; margin: 5.8% 0 4.6%;
  color: var(--maroon-text);
}
.divider span { flex: 1; height: 1px; background: currentColor; opacity: .55; }
.divider svg { width: 18px; height: 18px; }

.oval__place { margin: 0 0 6.6%; font-size: clamp(11px, 3.3vw, 15px); line-height: 1.55; }

/* Tombol emas */
.btn {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 22px;
  border: 0; border-radius: 999px;
  font: 500 clamp(9px, 2.6vw, 11px)/1 var(--font-head);
  letter-spacing: .08em;
  text-decoration: none;
  color: #fff8e6;
  text-shadow: 0 1px 1px rgba(80,50,0,.35);
  background: linear-gradient(180deg, var(--gold-1) 0%, var(--gold-2) 55%, var(--gold-3) 100%);
  box-shadow: 0 3px 8px rgba(90,60,10,.28), inset 0 1px 0 rgba(255,255,255,.35);
  cursor: pointer;
}
.btn:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }

/* =========================================================
   SEKSI 2 — LOVE STORY / GALERI / LIVE STREAMING
   ========================================================= */
#story .sect__content { padding-top: 34px; padding-bottom: 46px; }

.block { text-align: center; }
.block + .block { margin-top: 56px; }

.heading {
  margin: 0 0 22px;
  font: 500 clamp(17px, 5vw, 22px)/1.2 var(--font-head);
  letter-spacing: .04em;
  color: var(--maroon-text);
}
.heading--small { font-size: clamp(12px, 3.4vw, 15px); opacity: .85; }

/* Foto Love Story */
.photo {
  margin: 0 0 22px;
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 8px 20px rgba(80,20,20,.18);
}
.photo img { width: 100%; aspect-ratio: 5 / 6; object-fit: cover; }

/* Teks cerita */
.story { text-align: left; }
.story h3 {
  margin: 18px 0 6px;
  font: 500 clamp(14px, 4vw, 17px)/1.25 var(--font-head);
  color: var(--maroon-text);
}
.story p { margin: 0; font-size: clamp(11px, 3.1vw, 13px); line-height: 1.7; }

/* Galeri: 2 kolom seperti video */
.gallery {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 9px;
}
.gallery img {
  width: 100%;
  aspect-ratio: 3 / 4.2;
  object-fit: cover;
  border-radius: 12px;
  box-shadow: 0 5px 14px rgba(80,20,20,.16);
}
.gallery img:nth-child(3n+2) { aspect-ratio: 3 / 3.6; }

/* Live streaming */
.lead { margin: 0 0 16px; font-size: clamp(11px, 3.1vw, 13px); line-height: 1.65; }
.meta { margin: 0 0 8px; font-size: clamp(11px, 3.1vw, 13px); }
.block--live .btn { margin-top: 10px; }

/* =========================================================
   SEKSI 3 — AMPLOP DIGITAL & UCAPAN (bingkai emas)
   ========================================================= */
#amplop .sect__content { padding-top: 46px; padding-bottom: 60px; }

.frame {
  position: relative;
  padding: 30px 8% 32px;
  text-align: center;
  border-radius: 18px;
  border: 2px solid #c9a55c;
  background:
    linear-gradient(rgba(253,238,227,.93), rgba(253,238,227,.93));
  box-shadow: 0 10px 26px rgba(40,0,4,.28), inset 0 0 0 4px rgba(253,238,227,.55);
}
.frame + .frame { margin-top: 22px; }

.frame__title {
  margin: 0 0 14px;
  font: 500 clamp(16px, 4.8vw, 21px)/1.2 var(--font-head);
  letter-spacing: .04em;
  color: var(--maroon-text);
}
.frame__text { margin: 0 0 20px; font-size: clamp(11px, 3vw, 13px); line-height: 1.7; }
.frame__text--sm { margin-bottom: 14px; }

/* ---------------------------------------------------------
   KARTU REKENING + KARTU HADIAH (revisi AMPLOP DIGITAL)
   ---------------------------------------------------------
   .amplop-reveal   : pembungkus, tersembunyi via [hidden] sampai
                       tombol KLIK DISINI ditekan.
   .amplop-card     : bingkai krem+emas selaras dengan .frame,
                       menampung gambar kartu (proporsi asli
                       dijaga oleh aspect-ratio, tidak pernah
                       gepeng/terpotong pada layar mana pun).
   .amplop-copy     : tombol Copy kecil, memakai gradasi emas
                       yang sama dengan .btn supaya menyatu.
   --------------------------------------------------------- */

.amplop-reveal {
  display: grid;
  gap: 16px;
  margin-top: 22px;
}

/* Animasi muncul halus: fade + sedikit naik dari bawah.
   Dipicu oleh class .is-open (ditambahkan lewat JS SETELAH
   atribut [hidden] dilepas, agar transisi sempat dijalankan
   oleh browser alih-alih langsung melompat ke state akhir). */
.amplop-reveal[hidden] { display: none; }
.amplop-reveal:not([hidden]) {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .55s var(--ease), transform .55s var(--ease);
}
.amplop-reveal:not([hidden]).is-open {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  .amplop-reveal:not([hidden]) { transition: none; opacity: 1; transform: none; }
}

.amplop-card {
  position: relative;
  padding: 14px;
  border-radius: 16px;
  border: 1.5px solid #c9a55c;
  background: linear-gradient(rgba(253,238,227,.55), rgba(253,238,227,.55));
  box-shadow: 0 8px 20px rgba(40,0,4,.22), inset 0 0 0 3px rgba(253,238,227,.4);
}

/* Gambar kartu: lebar penuh kolom, tinggi mengikuti rasio ASLI
   file (kartu.png & hadiah.png ≈ 1.68:1) lewat aspect-ratio,
   jadi selalu proporsional & rapi tanpa gepeng/terpotong pada
   HP sempit (320px) maupun desktop. object-fit sebagai
   pengaman tambahan bila rasio berkas sumber sedikit berbeda. */
.amplop-card__img {
  width: 100%;
  height: auto;
  aspect-ratio: 1.68 / 1;
  object-fit: contain;
  border-radius: 10px;
  display: block;
}

.amplop-card__row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  margin-top: 12px;
  flex-wrap: wrap;
}

.amplop-card__number {
  font: 600 clamp(15px, 4.6vw, 19px)/1 var(--font-head);
  letter-spacing: .06em;
  color: var(--maroon-text);
  user-select: all;                 /* memudahkan seleksi manual sbg cadangan Copy */
}

/* Tombol Copy -- gradasi emas sama seperti .btn supaya menyatu
   dengan desain, hanya lebih ringkas (ikon + label). */
.amplop-copy {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  border: 0;
  border-radius: 999px;
  font: 500 clamp(9px, 2.6vw, 11px)/1 var(--font-head);
  letter-spacing: .06em;
  color: #fff8e6;
  text-shadow: 0 1px 1px rgba(80,50,0,.35);
  background: linear-gradient(180deg, var(--gold-1) 0%, var(--gold-2) 55%, var(--gold-3) 100%);
  box-shadow: 0 3px 8px rgba(90,60,10,.28), inset 0 1px 0 rgba(255,255,255,.35);
  cursor: pointer;
}
.amplop-copy:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* Dua ikon (copy & centang) ditumpuk lewat crossfade -- hanya
   satu yang terlihat tergantung state .is-copied, jadi lebar
   tombol TIDAK melompat saat teks label berubah. */
.amplop-copy__icon { display: block; transition: opacity .2s var(--ease); }
.amplop-copy__icon--check { position: absolute; left: 14px; opacity: 0; }
.amplop-copy.is-copied .amplop-copy__icon--copy { opacity: 0; }
.amplop-copy.is-copied .amplop-copy__icon--check { opacity: 1; }

.amplop-copied {
  height: 14px;                      /* ruang dicadangkan agar layout tidak "lompat" saat teks muncul/hilang */
  margin: 6px 0 0;
  font-size: clamp(10px, 2.8vw, 12px);
  letter-spacing: .04em;
  color: var(--gold-3);
  opacity: 0;
  transition: opacity .25s var(--ease);
}
.amplop-copied.is-visible { opacity: 1; }

.wish { display: grid; gap: 10px; }
.wish input, .wish textarea {
  width: 100%;
  padding: 10px 12px;
  font: inherit; font-size: 12px;
  color: var(--maroon-text);
  background: rgba(255,255,255,.75);
  border: 1px solid #d8bd85;
  border-radius: 8px;
}
.wish input:focus, .wish textarea:focus { outline: 2px solid var(--gold-2); outline-offset: 1px; }
.wish .btn { justify-self: center; }

/* =========================================================
   ANIMASI MASUK (reveal) — berbasis waktu, bukan posisi scroll
   ---------------------------------------------------------
   Kurva diukur dari video (15 fps): foto tumbuh dari titik kecil
   (skala ≈0,08) → 0,74 dalam 0,09 dtk, lalu melambat halus → 1,00
   pada ≈0,84 dtk. Itu kurva ease-out yang kuat.
   ========================================================= */
.page .reveal { opacity: 0; will-change: opacity, transform; }

.page .reveal[data-reveal="fade"] {
  transform: none;
  transition: opacity .9s ease;
}
.page .reveal[data-reveal="zoom"] {
  transform: scale(.08);
  transform-origin: 50% 50%;
  transition:
    opacity   .55s ease-out,
    transform .95s cubic-bezier(.16, .8, .3, 1);    /* cepat di awal, melambat halus */
}
.page .reveal.is-in { opacity: 1; transform: none; }

/* Hormati preferensi pengurangan gerak */
@media (prefers-reduced-motion: reduce) {
  .page .reveal, .page .reveal[data-reveal] { opacity: 1; transform: none; transition: none; }
}

/* =========================================================
   RESPONSIF
   ---------------------------------------------------------
   Mobile  : kolom = 100% lebar layar (persis seperti video).
   Tablet/Desktop: kolom dibatasi ±480 px & dipusatkan supaya
   latar potret (bg.png 2:3, bg2.png ≈1:2) TIDAK diperbesar
   berlebihan / terpotong. Sisi kiri-kanan diisi warna dasar.
   ========================================================= */
@media (min-width: 620px) {
  :root { --col-w: 480px; --page-max: 480px; }
  body:has(.page) { background: #3d0508; }   /* marun gelap di luar kolom */
  .page { box-shadow: 0 0 60px rgba(0,0,0,.45); }
}

/* ==================================================
   7. RESPONSIVE

   Seluruh tata letak sudah proporsional terhadap LEBAR
   KARTU melalui --u, sehingga tidak ada breakpoint yang
   mengubah komposisi. Aturan di bawah hanya merapikan
   hal-hal kecil di layar sempit dan menata area di luar
   kartu pada layar lebar.
   ================================================== */

@media (max-width: 360px) {

    /* Pada layar sangat sempit, tipografi ditahan agar tidak
       ikut mengecil berlebihan (--t sudah punya batas 0.5px).
       Padding teks dikurangi supaya baris tidak pecah. */

    .verse {
        padding: 0 calc(18 * var(--u));
    }

    .invite-text {
        padding: 0 calc(10 * var(--u));
    }

    .parents {
        padding: 0 calc(24 * var(--u));
    }
}

/* ---- Tablet & desktop: kartu di tengah, background di luarnya ----

   Kartu berhenti melebar di --invite-max, sisa layar menjadi
   area background --page-bg. Padding vertikal memberi jarak
   nafas di atas dan bawah kartu sehingga terlihat seperti
   kartu undangan yang diletakkan di atas meja. */

@media (min-width: 481px) {

    body {
        padding: 28px 0;
    }

    .invite {
        box-shadow: 0 0 80px rgba(90, 30, 25, .16);
    }
}

@media (min-width: 1024px) {

    body {
        padding: 44px 0;
    }

    .invite {
        box-shadow: 0 24px 90px rgba(90, 30, 25, .18);
    }
}

@media print {

    html.js .reveal {
        opacity: 1;
        transform: none;
    }

    html.js .couple-photo-frame::before,
    html.js .couple-photo-frame .bingkai-pengantin,
    html.js .couple-photo-frame .photo-pengantin {
        opacity: 1;
        transform: none;
    }

    body {
        background: #FFFFFF;
        padding: 0;
    }
}

/* ==========================================================================
   8. OPENING SCREEN  (utama.mp4 + couplemw.png + BUKA UNDANGAN)
   --------------------------------------------------------------------------
   Blok ini DITAMBAHKAN di akhir file dan tidak mengubah satu pun aturan
   undangan di atasnya. Semua selector memakai awalan "opn-" (atau html.opn-*)
   sehingga tidak bentrok dengan class/id yang sudah ada.

   KOORDINAT VIDEO
   utama.mp4 = 1080 x 1920 (portrait 9:16 setelah rotasi). Hasil pengukuran
   piksel pada frame video:

     bingkai oval  : x 274-802  | y 312-1019   (garis merah, tengah garis)
                     -> left 25.370% | top 16.250% | width 48.981% | height 36.875%
     "TAMU UNDANGAN": y 1326-1361 (teks ada DI DALAM video)
     ruang kosong   : y 1400-1650 -> tempat tombol
     bingkai muncul : detik 1.85 (fade-in penuh sekitar detik 2.4)

   Semua angka dipakai dalam PERSEN terhadap .opn-stage (rasio 9:16), jadi
   foto & tombol selalu menempel pada video di ukuran layar apa pun.
   ========================================================================== */

/* ---- Kunci scroll & sembunyikan isi selama opening tampil ---- */

html.opn-lock,
html.opn-lock body {
    overflow: hidden;
    height: 100%;
    overscroll-behavior: none;
    touch-action: none;
}

/* Isi undangan disembunyikan (bukan dihapus): kode lama tetap termuat &
   berjalan, hanya saja tidak terlihat dan tidak bisa difokus/diklik. */
html.opn-lock .opn-content {
    visibility: hidden;
}

/* ---- Pembungkus overlay: menutup seluruh layar ---- */

.opn {
    position: fixed;
    inset: 0;
    z-index: 2147483000;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    /* Warna dasar = warna tepi video, supaya area di luar stage (layar
       desktop yang lebar) menyatu dan tidak terlihat sebagai kotak kosong. */
    background-color: #7d2a30;
    transform: translate3d(0, 0, 0);
    /* Transisi slide-up. Durasi dipisah agar mudah diatur. */
    transition: transform 1.05s cubic-bezier(.77, 0, .175, 1);
    will-change: transform;
    -webkit-tap-highlight-color: transparent;
}

/* Layar yang punya notch / home-indicator: tinggi dinamis (iOS Safari) */
@supports (height: 100dvh) {
    .opn {
        height: 100dvh;
        bottom: auto;
    }
}

/* STATE: sedang slide-up (dipasang oleh script.js saat tombol diklik) */
.opn.is-leaving {
    transform: translate3d(0, -100%, 0);
    pointer-events: none;
}

/* STATE: selesai -> dihilangkan sepenuhnya dari layout */
.opn.is-gone {
    display: none;
}

/* ---- Stage: kotak ber-rasio 9:16 yang mengikuti video ----

   Stage SELALU berrasio 9:16 (sama dengan video), sehingga foto & tombol
   yang diposisikan dalam PERSEN selalu menempel di tempat yang sama pada
   video, di layar apa pun.

   Tinggi stage = tinggi layar (H). Lebar stage = H x 9/16.
   Hasilnya tergantung rasio layar:

   A) Layar LEBIH LEBAR dari 9:16 (tablet, laptop, desktop, HP 9:16)
      -> lebar stage <= lebar layar. Stage di tengah, video utuh tanpa
         bagian terpotong; sisa kiri-kanan diisi warna dasar #7d2a30
         yang senada dengan tepi video.

   B) Layar LEBIH TINGGI dari 9:16 (hampir semua HP modern: 9:19.5, 9:20,
      9:21) -> stage akan lebih sempit dari layar, menyisakan kolom
      kosong di kiri-kanan. Agar tidak ada kolom kosong, stage
      DIPERBESAR sampai selebar layar (mode COVER): lebar = lebar layar,
      tinggi = lebar x 16/9, sehingga tinggi stage sedikit MELEBIHI
      layar. Bagian yang terpotong hanya karangan bunga di tepi atas
      dan bawah; bingkai, foto, teks & tombol tetap terlihat utuh.

   Rumus ringkas memakai max():
       lebar  = max(lebar layar,  tinggi layar x 9/16)   di HP tinggi
       tinggi = lebar x 16/9
   Pada layar lebar (kasus A) max() memilih tinggi layar x 9/16, jadi
   tampilannya sama dengan mode contain. Satuan dvh dipakai bila tersedia
   (iOS Safari), dengan vh sebagai cadangan.                              */

.opn-stage {
    --opn-vh: 100vh;
    --opn-w: max(100vw, calc(var(--opn-vh) * 9 / 16));
    --opn-h: calc(var(--opn-w) * 16 / 9);
    position: relative;
    flex: 0 0 auto;
    width: var(--opn-w);
    height: var(--opn-h);
    overflow: hidden;
    background-color: #7d2a30;
}

@supports (height: 100dvh) {
    .opn-stage {
        --opn-vh: 100dvh;
    }
}

/* Layar LEBAR (rasio >= 9:16): mode CONTAIN. Stage dibatasi oleh tinggi
   layar supaya seluruh video utuh; kolom kiri-kanan diisi warna dasar. */

@media (min-aspect-ratio: 9/16) {
    .opn-stage {
        --opn-w: calc(var(--opn-vh) * 9 / 16);
        --opn-h: var(--opn-vh);
    }
}

/* ---- Video: memenuhi stage, rasio asli terjaga ---- */

.opn-video {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;      /* stage sudah 9:16 = rasio video -> tidak ada crop */
    object-position: center;
    background-color: #7d2a30;
    pointer-events: none;
    user-select: none;
    -webkit-user-select: none;
}

/* ---- Foto di dalam bingkai oval ----

   Geometri oval hasil ukur piksel (dalam persen stage):
       left 25.370% | top 16.250% | width 48.981% | height 36.875%
   Foto dibuat menjangkau sampai TENGAH garis bingkai (inset ~0.4%) agar
   tepi foto tertutup garis merah bingkai, tanpa celah krem.
   Bentuk = "stadium" (garis lurus di sisi + setengah lingkaran atas/bawah),
   sama persis dengan bentuk bingkai pada video.                           */

.opn-photo {
    position: absolute;
    z-index: 2;
    left: 25.75%;
    top: 16.45%;
    width: 48.23%;
    height: 36.48%;
    /* Setengah lebar oval sebagai radius -> bentuk stadium. Nilai persen
       ganda (horizontal / vertikal) menjaga bentuknya di semua ukuran. */
    border-radius: 50% / 36.2%;
    overflow: hidden;
    background-color: #f8eadf;
    opacity: 0;
    transform: scale(.94);
    transition: opacity .55s cubic-bezier(.22, .61, .36, 1),
                transform .9s cubic-bezier(.22, .61, .36, 1);
    will-change: opacity, transform;
    pointer-events: none;
}

.opn-photo img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;          /* foto tidak pernah gepeng */
    object-position: center;
    user-select: none;
    -webkit-user-select: none;
    -webkit-user-drag: none;
}

/* Foto muncul saat bingkai pada video muncul (dipasang script.js) */
.opn-photo.is-shown {
    opacity: 1;
    transform: scale(1);
}

/* ---- Tombol BUKA UNDANGAN ----

   Diletakkan di bawah teks "TAMU UNDANGAN" (y 1326-1361 pada video),
   di ruang kosong sebelum karangan bunga (mulai sekitar y 1650).
   Titik tengah tombol pada y ~ 1490 / 1920 = 77.6% tinggi stage.        */

.opn-actions {
    position: absolute;
    z-index: 3;
    left: 0;
    right: 0;
    top: 74.6%;
    display: flex;
    justify-content: center;
    pointer-events: none;   /* hanya tombolnya yang menerima klik */
    opacity: 0;
    transform: translateY(10px);
    transition: opacity .7s cubic-bezier(.22, .61, .36, 1),
                transform .7s cubic-bezier(.22, .61, .36, 1);
}

.opn-actions.is-shown {
    opacity: 1;
    transform: translateY(0);
}

.opn-btn {
    /* Ukuran tombol mengikuti lebar stage (satuan cqw tidak dipakai agar
       kompatibel luas) -> dihitung dari --opn-w yang sudah ada. */
    --opn-fs: calc(var(--opn-w) * .0345);
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: calc(var(--opn-w) * .47);
    padding: calc(var(--opn-w) * .034) calc(var(--opn-w) * .072);
    border: 0;
    border-radius: 999px;
    color: #fff8e6;
    font-family: 'Cinzel', 'Playfair Display', Georgia, 'Times New Roman', serif;
    font-weight: 600;
    font-size: max(var(--opn-fs), 12px);
    line-height: 1;
    letter-spacing: .14em;
    text-transform: uppercase;
    white-space: nowrap;
    cursor: pointer;
    pointer-events: auto;
    /* Warna diambil dari palet undangan: marun bingkai + emas tepi lengkung */
    background: linear-gradient(180deg, #a3242c 0%, #7f1717 55%, #650f14 100%);
    box-shadow:
        0 0 0 1.5px rgba(217, 189, 122, .95),
        0 calc(var(--opn-w) * .012) calc(var(--opn-w) * .04) rgba(60, 5, 10, .38),
        inset 0 1px 0 rgba(255, 255, 255, .22);
    text-shadow: 0 1px 1px rgba(40, 0, 4, .45);
    -webkit-appearance: none;
    appearance: none;
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
    transition: transform .35s cubic-bezier(.22, .61, .36, 1),
                box-shadow .35s cubic-bezier(.22, .61, .36, 1),
                filter .35s cubic-bezier(.22, .61, .36, 1);
}

/* Kilau halus yang lewat saat hover (tidak berlebihan) */
.opn-btn::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: linear-gradient(105deg, transparent 35%, rgba(255, 244, 214, .30) 50%, transparent 65%);
    background-size: 250% 100%;
    background-position: 130% 0;
    transition: background-position .8s cubic-bezier(.22, .61, .36, 1);
    pointer-events: none;
}

@media (hover: hover) {
    .opn-btn:hover {
        transform: translateY(-2px);
        filter: brightness(1.08);
        box-shadow:
            0 0 0 1.5px rgba(232, 208, 148, 1),
            0 calc(var(--opn-w) * .018) calc(var(--opn-w) * .05) rgba(60, 5, 10, .42),
            inset 0 1px 0 rgba(255, 255, 255, .28);
    }

    .opn-btn:hover::after {
        background-position: -30% 0;
    }
}

/* Respons sentuh/klik: menekan halus */
.opn-btn:active {
    transform: translateY(0) scale(.965);
    filter: brightness(.96);
    transition-duration: .12s;
}

.opn-btn:focus-visible {
    outline: 2px solid #fff8e6;
    outline-offset: 4px;
}

/* Tombol nonaktif sesaat setelah diklik (mencegah klik ganda) */
.opn-btn[disabled] {
    pointer-events: none;
    cursor: default;
}

/* ---- Tanpa JavaScript: opening tidak ditampilkan sama sekali ----
   (class "opn-lock" tidak pernah terpasang, tetapi overlay tetap ada di
   HTML, jadi disembunyikan agar isi undangan langsung bisa dibaca.)   */

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

/* ---- Preferensi kurangi gerak ---- */

@media (prefers-reduced-motion: reduce) {

    .opn {
        transition-duration: .4s;
    }

    .opn-photo,
    .opn-actions {
        transition: none;
    }

    .opn-photo {
        transform: none;
    }

    .opn-actions {
        transform: none;
    }

    .opn-btn,
    .opn-btn::after {
        transition: none;
    }
}

@media print {

    .opn {
        display: none !important;
    }

    html.opn-lock .opn-content {
        visibility: visible;
    }

    html.opn-lock,
    html.opn-lock body {
        overflow: visible;
        height: auto;
    }
}