/* ═══════════════════════════════════════════════════════════════════════════
   card.css — ENJIN Studio Intro Kad (diden 2026-07-15)

   Satu fail dikongsi SEMUA rekaan studio. Sebelum ni setiap rekaan = satu blade
   dgn CSS inline sendiri (71 blade, 96KB CSS, dihantar semula setiap kali kad
   dibuka, tak boleh cache). Rekaan studio tak bawa CSS langsung — ia cuma
   pancarkan beberapa pemboleh ubah CSS per node. Fail ini yang tahu caranya.

   Prefix `kd-` (kad). BUKAN `ad-` — ad-blocker sorok elemen berawalan itu.

   ⚠️ "token" dlm projek ni = DUIT (Wallet/TokenTransaction). Nilai rupa kad
      dipanggil TEMA / pemboleh ubah CSS. Jangan campur.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── KANVAS ────────────────────────────────────────────────────────────────
   Nisbah TETAP (keputusan diden): koordinat % pereka = koordinat % user, di
   semua telefon. container-type:size membolehkan unit `cqw` — saiz font/elemen
   berskala dgn LEBAR KAD, bukan lebar skrin, jadi rekaan tak pecah dlm editor
   (kad dlm bingkai kecil) mahupun penuh skrin.                              */
.kd-canvas{
    position:relative; width:100%; margin:0 auto;
    aspect-ratio:var(--kd-ar, 420/672);
    container-type:size;
    overflow:hidden; isolation:isolate;
    background:var(--kd-bg, transparent);
}
.kd-canvas.is-full{aspect-ratio:auto; min-height:100dvh}

/* ── MOD MODUL (diden 2026-07-19, mula KU-413) ────────────────────────────
   Kanvas modul TIDAK ikut preset tinggi intro — tinggi DINAMIK ikut kandungan (setiap modul
   beza tinggi, sama spt blade modul asal). Lapisan content masuk ALIRAN dokumen (relative)
   dan MENENTUKAN tinggi kanvas; lapisan lain kekal absolute menutup tinggi itu.
   container-type mesti inline-size: containment saiz penuh kolapskan tinggi auto ke 0;
   cqw (unit lebar) kekal sah dgn inline-size. NOTA: perlukan stack layout ON utk content
   (anak absolute tak beri tinggi) — v1 modul memang stack. */
.kd-canvas.kd-mod{aspect-ratio:auto; container-type:inline-size; min-height:96px; display:grid}
/* wrapper NAV studio (diden 2026-07-21): (1) hiasan design boleh menjulur ATAS bar
   (crest awan, pelangi) — jangan klip; (2) container-type = LAYOUT CONTAINMENT yang
   MEMERANGKAP position:fixed → nav tak lekat bawah skrin (lapor diden: "tak function
   dekat rekabuilder preview") — matikan utk jenis ini. Saiz label cqw selamat: _nav
   set container-type pd .jd-nv sendiri. */
.blk-bnav-st .kd-canvas{overflow:visible;container-type:normal}
/* kd-mod grid min-height 96px MEREGANG nav dlm kanvas EDITOR (nav relative dlm aliran;
   diden 2026-07-21 "ketinggian tak tepat dlm builder") — tinggi nav ikut MENU sendiri.
   Kad tetamu tak terkesan: nav position:fixed, keluar aliran grid. */
.blk-bnav-st .kd-canvas.kd-mod{min-height:0}

/* ── FULL CODE (mode 'code', diden 2026-07-19) — elemen output <jd:slot/> ─────
   .jd-slot = teks (inline; pereka style pembalutnya sendiri). .jd-gfx = grafik arabic
   (saiz asas dari CSS pereka; --gfx-s = slider Saiz USER, corak sama _bismillah). */
.jd-slot{display:inline}
/* flex tengah + max-width:none (fix diden 2026-07-20): dulu max-width:100% MENGUNCI
   --gfx-s > 1 → slider saiz gfx user "tak berkesan" bila membesar (KU-459). Flex →
   limpah melebihi wrapper diagih seimbang kiri/kanan. */
.jd-gfx{display:flex;justify-content:center;line-height:0;margin:0 auto}
.jd-gfx img{display:block;flex:none;width:calc(100% * var(--gfx-s,1));max-width:none;height:auto}
/* gfx BERTINT (colors.gfx design): .kd-tint img{width:100%} (bawah) menang atas formula di atas
   → skala dipindah ke PEMBALUT tint; img dlmnya kekal 100% (kontrak mask). (fix diden 2026-07-20:
   "saiz gfx tak berubah" — KU-459 bertint #c3a054) */
.jd-gfx .kd-tint{width:calc(100% * var(--gfx-s,1));flex:none}
.jd-gfx-txt{display:block;line-height:1.7;direction:rtl;font-family:var(--font-arabic,'Amiri',serif)}
/* <jd:countdown/> — gaya DEFAULT neutral; CSS design bebas override (.jd-cd dll) */
.jd-cd{display:flex;justify-content:center;gap:3cqw;margin:2% 0}
.jd-cd-u{display:flex;flex-direction:column;align-items:center;min-width:11cqw;padding:2cqw 1.5cqw;
  border:1px solid currentColor;border-radius:8px;opacity:.92}
.jd-cd-n{font-size:5cqw;font-weight:600;line-height:1.1;font-variant-numeric:tabular-nums}
.jd-cd-l{font-size:2.2cqw;letter-spacing:.12em;text-transform:uppercase;opacity:.75;margin-top:.6cqw}
/* grid: lapisan content (satu-satunya anak dlm-aliran) REGANG ikut tinggi kanvas — bila
   Ketinggian Modul (mh) dinaikkan, stack center menegak betul, bukan lekat atas */
.kd-mod .kd-l-content{position:relative; inset:auto}

/* ── LAPISAN (urutan z: diden 2026-07-15) ──────────────────────────────────
   1 background · 2 frame · 3 back · 4 content · 5 front
   `back` DI ATAS `frame` — sengaja: ornamen/daun boleh MELIMPAH menyilang garis
   bingkai (rupa kad kahwin klasik). Kalau frame di atas, bingkai memotong daun. */
.kd-l{position:absolute; inset:0}
.kd-l-background{z-index:1; background-size:cover; background-position:center; background-repeat:no-repeat}
.kd-l-frame     {z-index:2}
.kd-l-back      {z-index:3}
.kd-l-content   {z-index:4}
.kd-l-front     {z-index:5; pointer-events:none}   /* dekorasi hadapan tak boleh halang klik teks */
/* content-stack (auto-rhythm, diden 2026-07-16) — layer content jadi flex-column BERPUSAT; anak FLOW
   (bukan absolute) dgn gap sekata. Frame/latar/front kekal absolute. */
.kd-l-content.kd-stack{ display:flex; flex-direction:column; align-items:var(--sal,center);
    justify-content:center; gap:var(--sgap,4cqw); padding:7% 0; }
.kd-l-content.kd-stack > .kd-n{ position:static; left:auto; top:auto; width:var(--w,auto); max-width:92%;
    transform:rotate(var(--rot,0deg)) rotate(var(--sw,0deg)); }

/* ── NODE ──────────────────────────────────────────────────────────────────
   Kedudukan bebas: x/y = % kanvas, w = % lebar kanvas, rot = putaran.
   --tx/--ty diisi oleh kelas anchor supaya `left/top` merujuk titik yg pereka
   maksudkan (tengah node, bukan sentiasa bucu kiri-atas).                   */
.kd-n{
    position:absolute;
    left:var(--x,0); top:var(--y,0); width:var(--w,auto);
    transform:translate(var(--tx,0), var(--ty,0)) rotate(var(--rot,0deg)) rotate(var(--sw,0deg));
    transform-origin:var(--org, 50% 50%);   /* putaran berpaksi pd TITIK ANCHOR (diden) */
    opacity:var(--o,1);
}
.kd-a-c  {--tx:-50%; --ty:-50%; --org:50% 50%}        /* tengah (default teks) */
.kd-a-tl {--tx:0;    --ty:0;    --org:0 0}
.kd-a-tc {--tx:-50%; --ty:0;    --org:50% 0}
.kd-a-tr {--tx:-100%; --ty:0;    --org:100% 0}
.kd-a-l  {--tx:0;    --ty:-50%; --org:0 50%}
.kd-a-r  {--tx:-100%; --ty:-50%; --org:100% 50%}
.kd-a-bl {--tx:0;    --ty:-100%; --org:0 100%}
.kd-a-bc {--tx:-50%; --ty:-100%; --org:50% 100%}
.kd-a-br {--tx:-100%; --ty:-100%; --org:100% 100%}

/* ── TEKS ──────────────────────────────────────────────────────────────────
   size dlm `cqw` = % lebar kad. Pereka set 7 → teks sentiasa 7% lebar kad.
   Berat 500 default (arahan diden: 700 terlalu tebal utk UI/kad).           */
.kd-t{
    margin:0;
    /* fallback muktamad = font asas kad (Cormorant, spt blade modul lama): tanpa ini,
       konteks yg TIDAK definisikan --font-body (studio, preview) jatuh ke font UI sans —
       pereka nampak rupa BERBEZA drpd tetamu (laporan diden 2026-07-19). Kad yg set
       --font-body sendiri tetap menang. */
    font-family:var(--f, var(--font-body, 'Cormorant Garamond', Georgia, serif));
    font-size:calc(var(--s, 4cqw) * var(--fit, 1));   /* --fit dikecilkan oleh jd-fit.js utk .kd-fit */
    font-weight:var(--fw, 500);
    color:var(--c, var(--kd-primary, #3d3427));
    letter-spacing:var(--ls, 0);
    line-height:var(--lh, 1.3);
    text-align:var(--ta, center);
    font-style:var(--fs, normal);
    white-space:pre-line;      /* pereka boleh pecah baris dlm teks */
    text-wrap:balance;
    overflow-wrap:anywhere;    /* perkataan panjang tanpa ruang WAJIB break (elak overflow melintang)
                                  walau Height=Auto (tanpa .kd-fit) — fix diden 2026-07-22 */
}
.kd-t:focus{outline:none}
.kd-t[data-field]:focus{background:rgba(124,58,237,.10); border-radius:4px}   /* sama dgn blok lain */
/* Auto-fit teks (diden 2026-07-22): kotak TETAP Width×Height (--w/--h). jd-fit.js kecikkan
   --fit sampai teks muat — nama panjang user tak pecah layout, tak terpotong. overflow:hidden =
   jaring bila JS belum jalan (potong, bukan melimpah). grid+align-content = teks tengah menegak. */
.kd-fit{ height:var(--h); overflow:hidden; overflow-wrap:anywhere; word-break:break-word;
    display:flex; flex-direction:column; justify-content:var(--fit-jc, center); }
/* RULE OF CENTER (diden 2026-07-22): teks center menegak dlm kotak fit, DEFAULT — reusable
   di blok lain (override --fit-jc: flex-start atas / flex-end bawah). break perkataan panjang
   -> overflow jadi menegak. jd-fit ukur guna top-align SEMENTARA (center pecahkan scrollHeight). */

/* ── DEKORASI (imej/SVG) ───────────────────────────────────────────────────
   Sentiasa <img> (bukan background-image): boleh di-cache, ada width/height,
   dan aset boleh ditukar tanpa sentuh CSS.                                  */
.kd-d{display:block; width:100%; height:auto; pointer-events:none; user-select:none}
.kd-d.is-cover{height:100%; object-fit:cover}

/* Latar video — menutup lapisan background sepenuhnya, di BELAKANG segalanya. */
.kd-bgv{position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block}

/* ── GRAFIK BERWARNA (mask) ────────────────────────────────────────────────
   SVG/PNG telus boleh diwarna semula: aset jadi MASK, warna/gradien jadi latar. <img> tak boleh
   diwarna; mask boleh — dan ia mengekalkan bentuk tepat kaligrafi/ornamen.
   Imej dalam (visibility:hidden) yang memberi kotak ini TINGGI mengikut nisbah asli aset —
   tanpa ia, kita tak tahu tinggi aset dan mask jadi kotak leper. */
.kd-tint{
    display:block; width:100%;
    background:var(--fill, currentColor);
    -webkit-mask:var(--m) center/contain no-repeat;
            mask:var(--m) center/contain no-repeat;
}
.kd-tint img{width:100%; height:auto; display:block; visibility:hidden}

/* ── BINGKAI ───────────────────────────────────────────────────────────────
   Dua rupa: garis (CSS, sifar bait aset) atau imej PNG/SVG (bingkai berukir). */
.kd-frame-line{
    position:absolute; inset:var(--inset, 5%);
    border:var(--bw, 1px) var(--bs, solid) var(--bc, var(--kd-frame, #c3a054));
    border-radius:var(--br, 0);
}

/* ── ANIMASI ───────────────────────────────────────────────────────────────
   Pustaka DIKONGSI — ditakrif SEKALI di sini. Rekaan cuma pilih nama.
   Sebelum ni seluruh sistem ada 1 @keyframes; kalau setiap template tulis
   sendiri, pendua merebak dari kosong. Ini menghentikannya sebelum bermula.  */
/* --sw = sudut animasi (swing/sway/spin). DIPISAHKAN dari --rot (putaran statik pereka).
   Didaftar sbg <angle> supaya keyframe interpolate lancar; disuap ke transform .kd-n DALAM
   rantaian yg sama dgn translate anchor → putaran TAK memutar offset kedudukan (sifar hanyut). */
@property --sw { syntax:'<angle>'; inherits:false; initial-value:0deg; }
.kd-anim{ animation:var(--anim, none); animation-play-state:running; }

/* ── CETUSAN OPENER ────────────────────────────────────────────────────────
   Animasi TAK boleh main sebelum tetamu buka sampul kad (opener). Bila opener wujud, <body> dapat
   `.kd-hold` (server) → animasi DIBEKUKAN. jdOpenCard() tambah `.kd-revealed` → animasi MAIN dari
   awal. Tiada opener = tiada .kd-hold → main masa load (selamat tanpa JS). */
.kd-hold:not(.kd-revealed) .kd-anim{ animation-play-state:paused; }

/* ENTRANCE — main sekali, berakhir pd REHAT (opacity 1, translate/scale/rotate 0). */
@keyframes kd-fade      {from{opacity:0}                                to{opacity:1}}
@keyframes kd-reveal-up {from{opacity:0; translate:0 14%}               to{opacity:1; translate:0 0}}
@keyframes kd-reveal-dn {from{opacity:0; translate:0 -14%}              to{opacity:1; translate:0 0}}
@keyframes kd-zoom-in   {from{opacity:0; scale:.88}                     to{opacity:1; scale:1}}
@keyframes kd-zoom-out  {from{opacity:0; scale:1.12}                    to{opacity:1; scale:1}}
@keyframes kd-slide-l   {from{opacity:0; translate:-12% 0}              to{opacity:1; translate:0 0}}
@keyframes kd-slide-r   {from{opacity:0; translate:12% 0}               to{opacity:1; translate:0 0}}
@keyframes kd-unfurl    {from{opacity:0; scale:.6; rotate:-8deg}        to{opacity:1; scale:1; rotate:0deg}}
/* rotate-gentle: ENTRANCE putaran PENUH 360° — sebulat ke KIRI (fade masuk) lalu sebulat ke KANAN,
   mendap ke rehat. Main SEKALI. (diden: putaran penuh 360 dari kiri kemudian kanan slowly) */
@keyframes kd-rotate-gentle {0%{opacity:0; rotate:0deg} 50%{opacity:1; rotate:-360deg} 100%{rotate:0deg}}

/* ── LOOP ──────────────────────────────────────────────────────────────────
   TEKNIK PROTOTYPE diden (swing-gentle.html): 2 keyframe (REHAT → puncak) + `ease-in-out` +
   `alternate` (lihat animCss). alternate = ayun ke satu arah lalu balik; ease-in-out atas 2-keyframe
   = PERLAHAN di setiap hujung (bandul sebenar) → licin, tiada micro-terhenti. `from` = rehat =
   keadaan akhir entrance → sambungan MULUS. (Spin = kes khas: 360 penuh, linear, tiada alternate.) */
@keyframes kd-swing     {from{--sw:0deg} to{--sw:var(--sd,20deg)}}   /* sudut boleh dilaras (Swing angle) — 10 gentle … 360 sweep penuh */
/* Swing "both sides" (R↔L) — pendulum PENUH lalu tengah: mula/akhir di REHAT (0) → sambungan
   entrance kekal mulus; laju di tengah, perlahan di hujung (easing per-stop, animasi = linear). */
@keyframes kd-swing-both {
    0%   {--sw:0deg;                       animation-timing-function:ease-out}
    25%  {--sw:calc(-1 * var(--sd,20deg)); animation-timing-function:ease-in}
    50%  {--sw:0deg;                       animation-timing-function:ease-out}
    75%  {--sw:var(--sd,20deg);            animation-timing-function:ease-in}
    100% {--sw:0deg}
}
@keyframes kd-sway      {from{--sw:0deg} to{--sw:14deg}}
@keyframes kd-sway-soft {from{--sw:0deg} to{--sw:7deg}}
@keyframes kd-sway-wide {from{--sw:0deg} to{--sw:26deg}}
/* Spin (Ease) (diden 2026-07-21): SATU loop — easing per-pusingan dipilih user (animCss),
   arah via --sp (360deg ikut jam / -360deg lawan jam). Ganti Spin 360 + Spin (ease).
   v2: JANGAN henti 0-ke-0 di sempadan pusingan (diden) — slope bezier hujung = slope mula
   dan >0 pd SEMUA gaya, jadi sambungan pusingan sentiasa hidup. Lembut/Denyut = bezier
   simetri luaran (perlahan di sempadan, 36%/10% laju purata). Pecut/Lepas tak simetri →
   keyframes sendiri: titik perlahan DALAM kitaran, sempadan dipadankan pd 0.35x. */
@keyframes kd-spin      {from{--sw:0deg} to{--sw:var(--sp,360deg)}}
/* Wind-up: slow half (50% time, 126°) … faster half — v3 (diden: "tak berapa smooth"):
   derivatif bezier dikira numerik — puncak 1.62x purata (v1: 3.2x), sempadan padan 0.56x,
   junction 0.74x → gelombang lembut, kontras ~3:1. Karakter lebih dramatik = panjangkan
   Loop speed (sampai 20s). */
@keyframes kd-spin-in {
    0%  {--sw:0deg;                            animation-timing-function:cubic-bezier(.35,.28,.7,.68)}
    50% {--sw:calc(var(--sp,360deg) * .35);    animation-timing-function:cubic-bezier(.3,.17,.65,.85)}
    100%{--sw:var(--sp,360deg)}
}
/* Release: faster half first … slow glide — mirror of Wind-up, same softened contrast */
@keyframes kd-spin-out {
    0%  {--sw:0deg;                            animation-timing-function:cubic-bezier(.35,.15,.7,.83)}
    50% {--sw:calc(var(--sp,360deg) * .65);    animation-timing-function:cubic-bezier(.3,.32,.65,.72)}
    100%{--sw:var(--sp,360deg)}
}
/* ── LOOP v2 (diden 2026-07-21): dua-stop from→to kasar diganti MULTI-STOP organik —
     0% = 100% (seamless, linear normal), easing PER-STOP (corak terbukti kd-swing-both),
     transform/opacity shj (GPU compositor). ── */
/* LEGASI: spin-ease digabung ke kd-spin + gaya 'inout' (migrasi client+server); keyframe
   kekal utk render cache lama sahaja — jangan guna utk kerja baru. */
@keyframes kd-spin-ease {0%{--sw:0deg;animation-timing-function:cubic-bezier(.45,.05,.55,.95)} 100%{--sw:360deg}}
/* v2.1 (diden 2026-07-21): --la = KEKUATAN loop (amplitud, default 1 — slider "Kekuatan"
   di studio, corak sama Swing angle). Float/drift ditala semula supaya natural. */
/* Float: sine bob tulen — naik-turun lembut (easeInOutSine kedua-dua hala; x-wobble
   dibuang: pd elemen kecil ia nampak gugup) */
@keyframes kd-float {
    0%  {translate:0 0;                         animation-timing-function:cubic-bezier(.37,0,.63,1)}
    50% {translate:0 calc(-3% * var(--la,1));   animation-timing-function:cubic-bezier(.37,0,.63,1)}
    100%{translate:0 0}
}
/* Drift: ORBIT elips 12-stop, linear antara stop (laju malar = hanyutan habuk sebenar);
   bermula & berakhir di REHAT (0,0) — pusat orbit dianjak supaya laluan melalui asal */
@keyframes kd-drift {
    0%     {translate:0 0}
    8.33%  {translate:calc(-.2% * var(--la,1))   calc(.53% * var(--la,1))}
    16.67% {translate:calc(-.75% * var(--la,1))  calc(.91% * var(--la,1))}
    25%    {translate:calc(-1.5% * var(--la,1))  calc(1.05% * var(--la,1))}
    33.33% {translate:calc(-2.25% * var(--la,1)) calc(.91% * var(--la,1))}
    41.67% {translate:calc(-2.8% * var(--la,1))  calc(.53% * var(--la,1))}
    50%    {translate:calc(-3% * var(--la,1))    0}
    58.33% {translate:calc(-2.8% * var(--la,1))  calc(-.53% * var(--la,1))}
    66.67% {translate:calc(-2.25% * var(--la,1)) calc(-.91% * var(--la,1))}
    75%    {translate:calc(-1.5% * var(--la,1))  calc(-1.05% * var(--la,1))}
    83.33% {translate:calc(-.75% * var(--la,1))  calc(-.91% * var(--la,1))}
    91.67% {translate:calc(-.2% * var(--la,1))   calc(-.53% * var(--la,1))}
    100%   {translate:0 0}
}
/* Drift (Left & right only): ayunan mendatar harmonik — easeInOutSine dua-dua hala,
   perlahan di hujung laju di tengah (bandul); tiada gerak menegak. (diden 2026-07-21) */
@keyframes kd-drift-lr {
    0%  {translate:0 0;                        animation-timing-function:cubic-bezier(.37,0,.63,1)}
    25% {translate:calc(-3% * var(--la,1)) 0;  animation-timing-function:cubic-bezier(.37,0,.63,1)}
    50% {translate:0 0;                        animation-timing-function:cubic-bezier(.37,0,.63,1)}
    75% {translate:calc(3% * var(--la,1)) 0;   animation-timing-function:cubic-bezier(.37,0,.63,1)}
    100%{translate:0 0}
}
/* Breathe: tarik nafas → TAHAN sekejap → hembus (bukan zoom robotik) */
@keyframes kd-breathe {
    0%  {scale:1;                                animation-timing-function:cubic-bezier(.37,0,.63,1)}
    45% {scale:calc(1 + .045 * var(--la,1));     animation-timing-function:linear}
    58% {scale:calc(1 + .045 * var(--la,1));     animation-timing-function:cubic-bezier(.37,0,.63,1)}
    100%{scale:1}
}
/* Shimmer: kelipan lembut tak sekata (bukan pudar mekanikal 1→.55) */
@keyframes kd-shimmer {
    0%  {opacity:1;                              animation-timing-function:ease-in-out}
    35% {opacity:calc(1 - .28 * var(--la,1));    animation-timing-function:ease-in-out}
    52% {opacity:calc(1 - .1 * var(--la,1));     animation-timing-function:ease-in-out}
    72% {opacity:calc(1 - .4 * var(--la,1));     animation-timing-function:ease-in-out}
    100%{opacity:1}
}

/* Hormat pilihan pengguna — SEKALI, di satu tempat. Rekaan tak perlu ingat.  */
@media (prefers-reduced-motion: reduce){
    .kd-anim{animation:none !important; opacity:1 !important; translate:none !important; scale:none !important; rotate:none !important}
}

/* Nama pasangan berstruktur — A / & / B (auto-split pada ' & '). & lebih kecil, baris sendiri. */
.kd-couple .kd-cn{display:block}
.kd-couple .kd-amp{display:block;font-size:.52em;line-height:1.25;opacity:1;color:var(--kd-amp-c,inherit)}
