    console.log("%c[app.jsx] BUILD 2026-10-04-OTP-FIX","color:#16a34a;font-weight:bold");
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
    import { createRoot } from 'react-dom/client';
    import { 
        ArrowLeft, Share2, Eye, Calendar, MapPin, Send, 
        PenTool, Trash2, ImageIcon as LucideImageIcon, Edit,
        Upload, Image as ImageIcon, Heart, ShieldCheck,
        LayoutDashboard, Code, Users, DollarSign, FileCode, Plus, Save, User, Moon, Sun, Check,
        UserPlus, X, Key, ShoppingCart, Copy, Clock, CreditCard, Smartphone, RefreshCw, CheckCircle2, MoreVertical, LogOut,
        MessageSquare, Mail, Lock, Crown, QrCode, Download, Music, Disc, Laptop, Star,
        ChevronLeft, ChevronRight
    } from 'lucide-react';

    import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js";
    import { getFirestore, collection, collectionGroup, addDoc, getDocs, query, where, updateDoc, doc, setDoc, getDoc, deleteDoc, orderBy, limit, onSnapshot, serverTimestamp, arrayUnion } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js";
    
    // 🔥 បន្ថែម Storage Library នៅទីនេះ
    import { getStorage, ref, uploadString, getDownloadURL } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-storage.js";

    import { getAuth, signInWithPopup, GoogleAuthProvider, onAuthStateChanged, signOut, RecaptchaVerifier, signInWithPhoneNumber } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-auth.js";
    import { initializeAppCheck, ReCaptchaV3Provider } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app-check.js";

    // --- FEATURE 1: SECURE ADMIN ACCESS CONFIGURATION ---
    const ADMIN_EMAIL = "phathongya@gmail.com";

    const firebaseConfig = {
        apiKey: "AIzaSyCFD-6aB2RmNhomEFG6kjfvlDAmO2rpFD4",
        authDomain: "phumtheapka.com",
        projectId: "heangweddingdigital",
        storageBucket: "heangweddingdigital.firebasestorage.app",
        messagingSenderId: "422526904540",
        appId: "1:422526904540:web:6c2dc2ae4cb9169f636c19",
        measurementId: "G-04ZYX25RF0"
    };

    let app, db, storage; // 🔥 បន្ថែម storage Variable
    try {
        app = initializeApp(firebaseConfig);
        db = getFirestore(app);
        storage = getStorage(app); // 🔥 បង្កើត instance សម្រាប់ storage

/* try {
    const appCheck = initializeAppCheck(app, {
        provider: new ReCaptchaV3Provider('6LdDQSwsAAAAAPmon5unBYO3w6uckVyrl3tVBgQu'),
        isTokenAutoRefreshEnabled: true
    });
} catch (err) {
    console.warn(err);
}
*/

        // --- ផ្នែកដោះស្រាយបញ្ហា ReCaptcha ---
        window.firebaseAuth = getAuth(app);

        window.FirebaseAuthConfig = { 
            RecaptchaVerifier: RecaptchaVerifier, 
            signInWithPhoneNumber: signInWithPhoneNumber, 
            GoogleAuthProvider: GoogleAuthProvider, 
            signInWithPopup: signInWithPopup 
        };

    } catch (e) {
        console.warn("Firebase failed to initialize:", e);
    }

const uploadImageToStorage = async (base64Str, folderName) => {
    // បើមិនមែនជា Base64 (ជា URL រួចហើយ) ឱ្យវាមកវិញភ្លាម
    if (!base64Str || !base64Str.startsWith('data:image')) return base64Str;
    
    try {
        const storageRef = ref(storage, `${folderName}/${Date.now()}_${Math.random().toString(36).substring(7)}.jpg`);
        // Upload ទៅ Storage
        await uploadString(storageRef, base64Str, 'data_url');
        // ទាញយក URL មកវិញ
        return await getDownloadURL(storageRef);
    } catch (e) {
        console.error("Upload error:", e);
        return base64Str; 
    }
};

    // ===== MEDIA OPTIMIZATION (Cloudinary + Lazy loading) =====
    // គោលបំណង៖ បន្ថយទំហំរូបភាព និងកុំ Load អ្វីៗទាំងអស់ក្នុងពេលតែមួយ ដើម្បីកុំឱ្យ Safari/iPhone Crash ដោយសារ Memory
    const CLD_UPLOAD_RE = /^(https:\/\/res\.cloudinary\.com\/[^/]+\/image\/upload\/)(.+)$/;
    const cldOpt = (url, width = 800) => {
        if (typeof url !== 'string') return url;
        const m = url.match(CLD_UPLOAD_RE);
        if (!m) return url;                                   // មិនមែន Cloudinary (Firebase, base64, ...) → មិនប៉ះពាល់
        if (/^[a-z]{1,3}_[^/]*\//i.test(m[2])) return url;    // មាន Transformation រួចហើយ → មិនប៉ះពាល់
        return m[1] + 'f_auto,q_auto,c_limit,w_' + width + '/' + m[2];   // c_limit = បង្រួមតែពេលធំជាង មិនពង្រីករូបតូច
    };

    // --- Nav: ធ្វើអោយប៊ូតុង Back លើទូរស័ព្ទ ថយម្តងមួយជំហាន (បិទ Popup/រូបធំ/ទំព័រ ជំនួសឱ្យចេញពី Website) ---
    // រាល់ "ស្រទាប់" (Popup ឬទំព័រ) ដែលបើក នឹងបន្ថែម history 1 entry; ចុច Back = បិទស្រទាប់លើគេ
    const Nav = (() => {
        const layers = [];   // ស្រទាប់ដែលកំពុងបើក ( តាមលំដាប់ depth)
        const queue = [];    // ស្រទាប់ដែលរង់ចាំ (ពេល browser កំពុង go back)
        let depth = 0, busy = false, seq = 0;
        try { depth = (history.state && history.state.navDepth) || 0; } catch (e) {}
        // បើ history ខ្ពស់ជាងស្រទាប់ដែលបើកពិត → ថយក្រោយអោយស្មើ
        const settle = () => {
            if (busy) return;
            const top = layers.length ? Math.max(...layers.map(l => l.depth)) : 0;
            if (top < depth) { busy = true; history.go(top - depth); }
        };
        const flush = () => {
            while (!busy && queue.length) {
                const q = queue.shift();
                depth += 1;
                history.pushState({ navDepth: depth }, '');
                layers.push({ id: q.id, depth, onBack: q.onBack });
            }
        };
        window.addEventListener('popstate', (e) => {
            depth = (e.state && e.state.navDepth) || 0;
            busy = false;
            while (layers.length && layers[layers.length - 1].depth > depth) layers.pop().onBack();
            settle();
            flush();
        });
        if (depth > 0) setTimeout(settle, 0);   // Reload ពេល history ជាប់ស្រទាប់ចាស់
        return {
            open(onBack) { const id = ++seq; queue.push({ id, onBack }); flush(); return id; },
            close(id) {
                if (id == null) return;
                const qi = queue.findIndex(q => q.id === id);
                if (qi >= 0) { queue.splice(qi, 1); return; }
                const li = layers.findIndex(l => l.id === id);
                if (li < 0) return;
                layers.splice(li, 1);
                settle();
            }
        };
    })();
    const useNavLayer = (active, onClose) => {
        const cb = useRef(onClose);
        cb.current = onClose;
        useEffect(() => {
            if (!active) return;
            const id = Nav.open(() => { if (cb.current) cb.current(); });
            return () => Nav.close(id);
        }, [active]);
    };

    // ដាក់ lazy ទៅលើ iframe (Google Maps), img, audio ក្នុងលិខិតអញ្ជើញ
    const optimizeInviteHtml = (html) => {
        let out = String(html || '');
        out = out.replace(/<iframe(?![^>]*\sloading=)/gi, '<iframe loading="lazy"');
        out = out.replace(/<audio(?![^>]*\spreload=)(?![^>]*id="bgMusic")/gi, '<audio preload="none"');
        out = out.replace(/<img(?![^>]*\sloading=)(?![^>]*id="bg-image")/gi, '<img loading="lazy" decoding="async"');
        out = out.replace(/img\.src = src;/g, "img.loading = 'lazy'; img.decoding = 'async'; img.src = src;");
        return out;
    };

    // សម្រាប់ Preview/គំរូ៖ មិនចាំបាច់ភ្ជាប់ Firebase ពិត (ស្រាល Memory និង Network) → ប្ដូរ import ទៅជា function ទទេ
    const stubFirebaseImports = (html) => String(html || '').replace(
        /import\s*\{([^}]*)\}\s*from\s*["']https:\/\/www\.gstatic\.com\/firebasejs\/[^"']+["']\s*;?/g,
        (m, names) => names.split(',').map(x => x.trim()).filter(Boolean).map(x => {
            const local = x.split(/\s+as\s+/).pop().trim();
            if (local === 'onSnapshot') return 'const onSnapshot=()=>()=>{};';
            if (local === 'serverTimestamp') return 'const serverTimestamp=()=>Date.now();';
            if (/^(addDoc|getDocs|getDoc|setDoc|updateDoc|deleteDoc)$/.test(local)) return 'const ' + local + '=()=>Promise.resolve({});';
            return 'const ' + local + '=()=>({});';
        }).join('') + ';'
    );

    // --- 1. HOME PAGE GALLERY (Phone Mockups) ---
    const HOME_GALLERY_IMAGES = [
        "https://res.cloudinary.com/duwva5xdx/image/upload/v1766760600/101_seztsr.webp",
        "https://res.cloudinary.com/duwva5xdx/image/upload/v1766760600/102_wjrjuk.webp",
        "https://res.cloudinary.com/duwva5xdx/image/upload/v1766760600/103_wxlunq.webp",
        "https://res.cloudinary.com/duwva5xdx/image/upload/v1766760600/104_dytou3.webp",
        "https://res.cloudinary.com/duwva5xdx/image/upload/v1766760600/105_at24ur.webp",
		"https://res.cloudinary.com/duwva5xdx/image/upload/v1766760600/107_cwwfhz.webp",
        "https://res.cloudinary.com/duwva5xdx/image/upload/v1766760770/110_sxlgmg.webp",
        "https://res.cloudinary.com/duwva5xdx/image/upload/v1766760600/108_nfvavv.webp",
        "https://res.cloudinary.com/duwva5xdx/image/upload/v1767101299/114_gybc7u.webp",
        "https://res.cloudinary.com/duwva5xdx/image/upload/v1767101300/115_wqsuyf.webp"
    ];

    // --- 2. TEMPLATE DEFAULT GALLERY (Couple Photos for Editor) ---
    const TEMPLATE_DEFAULT_IMAGES = [
        "https://res.cloudinary.com/dlnze2pct/image/upload/v1/111_ivndbt",
        "https://res.cloudinary.com/dlnze2pct/image/upload/v1/109_zupfsj",
        "https://res.cloudinary.com/dlnze2pct/image/upload/v1/110_xq1m4s",
        "https://res.cloudinary.com/dlnze2pct/image/upload/v1/106_xf7xa1",
        "https://res.cloudinary.com/dlnze2pct/image/upload/v1/103_trorbv",
        "https://res.cloudinary.com/dlnze2pct/image/upload/v1/111_ivndbt",
        "https://res.cloudinary.com/dlnze2pct/image/upload/v1/109_zupfsj"
    ];

    const APP_CONFIG = {
        DEFAULT_IMAGE: 'https://res.cloudinary.com/dlnze2pct/image/upload/v1763878728/1005_zuqi3p.png',
        
        // 🔥 ១. ដាក់ឈ្មោះចាស់នេះមកវិញ (សំខាន់ណាស់សម្រាប់ Preview)៖
        MUSIC_URL: "https://res.cloudinary.com/dlnze2pct/video/upload/v1763379550/Audio-4_wdhril.mp3",
        
        // 🔥 ២. ឈ្មោះថ្មីសម្រាប់បំបែកប្រភេទធៀប (ទុកដដែល)៖
        WEDDING_MUSIC: "https://res.cloudinary.com/dlnze2pct/video/upload/v1763379550/Audio-4_wdhril.mp3",
        ENGAGEMENT_MUSIC: "https://res.cloudinary.com/dlnze2pct/video/upload/v1763379550/Audio-4_wdhril.mp3",
        HOUSEWARMING_MUSIC: "https://res.cloudinary.com/dlnze2pct/video/upload/v1763379550/Audio-4_wdhril.mp3",
        BIRTHDAY_MUSIC: "https://res.cloudinary.com/duwva5xdx/video/upload/v1766308257/Audio-6_ewhxe2.mp3", 

        SOCIAL: {
            TELEGRAM_CHANNEL: 'https://t.me/HinHeang',
            TIKTOK: 'https://www.tiktok.com/@phumtheapka',
            WEBSITE: 'https://web.facebook.com/Phumtheapka/'
        }
    };

// --- តម្លៃ Template (Template Pricing) ---
// ម៉ូតធម្មតា = $9.99 | ម៉ូត Premium (មាន Intro) = $12.99
// ចង់ដូរតម្លៃ ឬបន្ថែមម៉ូត Premium ថ្មី → កែត្រង់ TEMPLATE_PRICES ប៉ុណ្ណោះ
const DEFAULT_TEMPLATE_PRICE = 9.99;
const TEMPLATE_PRICES = {
    'golden-dawn': 12.99,
    'royal-crimson': 12.99,
    'luxury-diamond': 12.99
};
const getTemplatePrice = (id) => TEMPLATE_PRICES[id] ?? DEFAULT_TEMPLATE_PRICE;
const formatTemplatePrice = (id) => '$' + getTemplatePrice(id).toFixed(2);
const hasTemplateIntro = (id) => getTemplatePrice(id) > DEFAULT_TEMPLATE_PRICE; // ម៉ូត Premium = មាន Intro

// --- តម្លៃប្រូម៉ូសិន (Promo Pricing) ---
// បង្ហាញតម្លៃពេញ (ឆូតកាត់) + តម្លៃបញ្ចុះ ដើម្បីទាក់ទាញអ្នកទិញ
// ម៉ូតធម្មតា: $12.99 → $9.99 | ម៉ូត Premium: $14.99 → $12.99
// ចង់ដូរតម្លៃពេញ → កែត្រង់ DEFAULT_ORIGINAL_PRICE / TEMPLATE_ORIGINAL_PRICES ប៉ុណ្ណោះ
// QR ទូទាត់ ABA: ម៉ូតធម្មតា $9.99 | ម៉ូត Premium $12.99
const QR_DEFAULT_9_99 = "https://res.cloudinary.com/dlnze2pct/image/upload/v1765371225/QR_qlr9wh.webp";
const QR_PREMIUM_12_99 = "https://res.cloudinary.com/duwva5xdx/image/upload/v1791082464/QR_12.99_voehp9.webp";
const DEFAULT_ORIGINAL_PRICE = 12.99;
const TEMPLATE_ORIGINAL_PRICES = {
    'golden-dawn': 14.99,
    'royal-crimson': 14.99,
    'luxury-diamond': 14.99
};
const getOriginalPrice = (id) => TEMPLATE_ORIGINAL_PRICES[id] ?? DEFAULT_ORIGINAL_PRICE;
const getDiscountPercent = (id) => { const o = getOriginalPrice(id), n = getTemplatePrice(id); return o > n ? Math.round((1 - n / o) * 100) : 0; };

const PriceTag = ({ id, compact = false }) => {
    const premium = hasTemplateIntro(id);
    const off = getDiscountPercent(id);
    return (
        <span className={`promo-price ${premium ? 'is-premium' : ''} ${compact ? 'is-compact' : ''}`}>
            {off > 0 && (
                <span className="promo-top">
                    <span className="promo-old">${getOriginalPrice(id).toFixed(2)}</span>
                </span>
            )}
            <span className="promo-main">
                <span className="promo-label">{off > 0 ? 'តម្លៃប្រូម៉ូសិន៖' : 'តម្លៃ៖'}</span>
                <span className="promo-now">{formatTemplatePrice(id)}</span>
            </span>
        </span>
    );
};


// --- ចំនួនមើល (View count) ដាក់ក្នុង Card ម៉ូតធៀប ---
// 1.2K → 8.6K, ខុសគ្នាគ្រប់ម៉ូត, និងថេរ (មិនប្តូរពេល Refresh)
const buildViewCounts = (templates) => {
    const MIN = 12, MAX = 86;                 // ឯកតា 0.1K  (12 = 1.2K, 86 = 8.6K)
    const RANGE = MAX - MIN + 1;
    const hash = (str) => { let h = 2166136261; for (let i = 0; i < str.length; i++) { h ^= str.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; };
    const used = new Set();
    const out = {};
    [...templates].map(t => String(t.id)).sort().forEach((id) => {
        let v = MIN + (hash(id) % RANGE);
        let guard = 0;
        while (used.has(v) && guard++ < RANGE) v = v === MAX ? MIN : v + 1;   // ជៀសវាងលេខស្ទួន
        used.add(v);
        out[id] = (v / 10).toFixed(1) + 'K';
    });
    return out;
};

const ViewBadge = ({ label, className = '' }) => (
    <span className={`inline-flex items-center gap-1 text-xs font-semibold text-gray-500 dark:text-gray-400 whitespace-nowrap ${className}`}>
        <Eye size={14} strokeWidth={2} /> {label} view
    </span>
);

// --- THEME_ASSETS (កន្លែងកំណត់រូបភាព) ---
                const THEME_ASSETS = {
        // 1. Wedding (មង្គលការ) - Classic 3 Designs
        'classic-gold': {
            bgImage: "https://res.cloudinary.com/duwva5xdx/image/upload/v1789996840/5_jgdmmc.webp",
            defaultCover: "https://res.cloudinary.com/duwva5xdx/image/upload/v1789996842/2_ueyndw.webp"
        },
        'royal-gold': {
            bgImage: "https://res.cloudinary.com/duwva5xdx/image/upload/v1789996844/6_ygqkzz.webp",
            defaultCover: "https://res.cloudinary.com/duwva5xdx/image/upload/v1789996852/1_pv1aee.webp"
        },
        'luxury-gold': {
            bgImage: "https://res.cloudinary.com/dlnze2pct/image/upload/v1765286242/124_rwsqc3.webp",
            defaultCover: "https://res.cloudinary.com/dtz6i2xtg/image/upload/v1772284743/Phumtheapka_BG_11_h08uc7.webp"
        },

        // 1.2 Premium Wedding + Intro (៣ ម៉ូតថ្មី — $12.99)
        'golden-dawn': {
            bgImage: "https://res.cloudinary.com/duwva5xdx/image/upload/v1789996840/5_jgdmmc.webp",
            defaultCover: "https://res.cloudinary.com/duwva5xdx/image/upload/v1789996842/2_ueyndw.webp"
        },
        'royal-crimson': {
            bgImage: "https://res.cloudinary.com/duwva5xdx/image/upload/v1789996844/6_ygqkzz.webp",
            defaultCover: "https://res.cloudinary.com/duwva5xdx/image/upload/v1789996852/1_pv1aee.webp"
        },
        'luxury-diamond': {
            bgImage: "https://res.cloudinary.com/dlnze2pct/image/upload/v1765286242/124_rwsqc3.webp",
            defaultCover: "https://res.cloudinary.com/dtz6i2xtg/image/upload/v1772284743/Phumtheapka_BG_11_h08uc7.webp"
        },

        // 1.1 New Wedding Styles (៣ ម៉ូត Cover ថ្មីដូចរូបគំរូ ១០០%)
        'sweet-blossom': {
            bgImage: "https://res.cloudinary.com/lhl1eokp/image/upload/v1788180226/703.webp",
            defaultCover: "https://res.cloudinary.com/lhl1eokp/image/upload/v1788179443/702.webp",
            particle: 'sweet-blossom'
        },
        'night-sky-romance': {
            bgImage: "https://res.cloudinary.com/lhl1eokp/image/upload/v1788180226/703.webp",
            defaultCover: "https://res.cloudinary.com/lhl1eokp/image/upload/v1788180226/703.webp",
            particle: 'night-sky-romance'
        },
        'green-leaves-minimal': {
            bgImage: "https://res.cloudinary.com/lhl1eokp/image/upload/v1788181772/704.webp",
            defaultCover: "https://res.cloudinary.com/lhl1eokp/image/upload/v1788181772/704.webp",
            particle: 'green-leaves-minimal'
        },

        // 1.5 Gift Reception (ពិធីកាត់ចំណងដៃ)
        'gift-classic': {
            bgImage: "https://res.cloudinary.com/dlnze2pct/image/upload/v1765282957/123_t32osy.webp",
            defaultCover: "https://res.cloudinary.com/duwva5xdx/image/upload/v1772112738/Kbach_7_djz6ta.webp"
        },
        'gift-royal': {
            bgImage: "https://res.cloudinary.com/duwva5xdx/image/upload/v1766301037/134_qehjtg.webp",
            defaultCover: "https://res.cloudinary.com/dtz6i2xtg/image/upload/v1772956574/Phumtheapka_BG_21_gqrfua.webp"
        },
        'gift-luxury': {
            bgImage: "https://res.cloudinary.com/dlnze2pct/image/upload/v1765286242/124_rwsqc3.webp",
            defaultCover: "https://res.cloudinary.com/dtz6i2xtg/image/upload/v1772956567/Phumtheapka_BG_23_zywukg.webp"
        },

        // 2. Engagement (ភ្ជាប់ពាក្យ)
        'eng-classic': {
            bgImage: "https://res.cloudinary.com/dlnze2pct/image/upload/v1765282957/123_t32osy.webp", 
            defaultCover: "https://res.cloudinary.com/lhl1eokp/image/upload/v1788185432/707.webp"
        },
        'eng-royal': {
            bgImage: "https://res.cloudinary.com/duwva5xdx/image/upload/v1766301037/134_qehjtg.webp",
            defaultCover: "https://res.cloudinary.com/lhl1eokp/image/upload/v1788185432/706.webp"
        },

        // 3. Housewarming (ឡើងគេហដ្ឋាន)
        'house-classic': {
            bgImage: "https://res.cloudinary.com/dlnze2pct/image/upload/v1765282957/123_t32osy.webp",
            defaultCover: "https://res.cloudinary.com/dtz6i2xtg/image/upload/v1772331283/Phumtheapka_15_hif9t4.webp"
        },
        'house-royal': {
            bgImage: "https://res.cloudinary.com/duwva5xdx/image/upload/v1766301037/134_qehjtg.webp",
            defaultCover: "https://res.cloudinary.com/duwva5xdx/image/upload/v1766305971/138_e7y8b7.webp"
        },

        // 4. Baby Ceremony (ពិធីបង្កក់កូន)
        'baby-classic': {
            bgImage: "https://res.cloudinary.com/duwva5xdx/image/upload/v1766240716/128_kraa8s.webp",
            defaultCover: "https://res.cloudinary.com/duwva5xdx/image/upload/v1766298433/131_esll68.webp"
        },
        'baby-royal': {
            bgImage: "https://res.cloudinary.com/duwva5xdx/image/upload/v1766240716/127_oyplfx.webp",
            defaultCover: "https://res.cloudinary.com/duwva5xdx/image/upload/v1766298433/131_esll68.webp"
        },

        // 5. Birthday (ខួបកំណើត)
        'birthday-classic': {
            bgImage: "https://res.cloudinary.com/duwva5xdx/image/upload/v1766240716/128_kraa8s.webp",
            defaultCover: "https://res.cloudinary.com/dtz6i2xtg/image/upload/v1772334373/Phumtheapka_17_bkeej7.webp"
        },
        'birthday-royal': {
            bgImage: "https://res.cloudinary.com/duwva5xdx/image/upload/v1766240716/127_oyplfx.webp",
            defaultCover: "https://res.cloudinary.com/duwva5xdx/image/upload/v1766297901/129_tcql6n.webp"
        }
    };

    const compressImage = (file) => {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = (event) => {
            const img = new Image();
            img.src = event.target.result;
            img.onload = () => {
                const canvas = document.createElement('canvas');
                
                // កំណត់ទំហំទទឹងអតិបរមា ៨០០px (ច្បាស់ល្មមសម្រាប់ទូរស័ព្ទ និងមិនធំពេកសម្រាប់ Bot Facebook)
                const MAX_WIDTH = 800; 
                let width = img.width;
                let height = img.height;

                if (width > MAX_WIDTH) {
                    height *= MAX_WIDTH / width;
                    width = MAX_WIDTH;
                }

                canvas.width = width;
                canvas.height = height;
                const ctx = canvas.getContext('2d');
                
                // ជួយឱ្យរូបភាពរលោងស្អាតពេលបង្រួម
                ctx.imageSmoothingEnabled = true;
                ctx.imageSmoothingQuality = 'high';
                
                ctx.drawImage(img, 0, 0, width, height);

                // កែសម្រួល Quality មកត្រឹម ០.៦ (60%) 
                // កម្រិតនេះនឹងធ្វើឱ្យ File size តូចខ្លាំង (ក្រោម 500KB) ងាយស្រួលឱ្យ Messenger ចាប់ Thumbnail ភ្លាមៗ
                resolve(canvas.toDataURL('image/jpeg', 0.4)); 
            };
            img.onerror = (error) => reject(error);
        };
        reader.onerror = (error) => reject(error);
    });
};

    // --- CLASSIC_GOLD_TEMPLATE ---
    // NOTE: We use ${'</'+'script>'} inside the template string to prevent browser parsing errors.
    const CLASSIC_GOLD_TEMPLATE = `<!DOCTYPE html>
<html lang="km">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>Wedding Invitation | សិរីមង្គលអាពាហ៍ពិពាហ៍</title>
  <link rel="preload" as="image" href="{{COVER_IMAGE}}" fetchpriority="high">
  
  <link href="https://fonts.googleapis.com/css2?family=Kantumruy+Pro:wght@300;400;500;600;700&family=Moul&family=Chenla&family=Noto+Sans+Khmer:wght@300;400;500;700&family=Great+Vibes&family=Cinzel:wght@400;700;900&family=Playfair+Display:ital,wght@0,400;0,700;1,400;1,700&family=Suwannaphum:wght@300;400;700&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  <style>
    /* TASK 2: SCROLL BEHAVIOR & BOTTOM NAV CSS UPDATE */
    html, body { scroll-behavior: smooth !important; overflow-x: hidden; }
    
    :root { --primary: #FFD700; --primary-light: #FFF8D6; --primary-dark: #B8860B; --text: #ffffff; }
    * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
    body { margin: 0; padding: 0; font-family: 'Suwannaphum', serif; font-style: italic; background-color: #000; color: var(--text); overflow-x: hidden; padding-bottom: 100px; }
    #bg-image { position: fixed; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; z-index: -1; }
    /* FIXED: Darker Overlay (50%) */
    .overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.0); z-index: 0; }
    
    /* 1. Cover must be the HIGHEST layer */
    #invite-cover { 
        position: fixed; top: 0; left: 0; width: 100%; height: 100%; 
        background: url('{{COVER_IMAGE}}') no-repeat center center; 
        background-size: cover; 
        background-color: #070a18; /* สีพื้นแข็ง: ป้องกันไม่ให้เห็นเนื้อหาด้านหลังขณะรูป Cover ยังโหลดไม่เสร็จ */
        z-index: 20000 !important; /* Highest priority */
        display: flex; flex-direction: column; align-items: center; 
        justify-content: flex-start !important; 
        text-align: center; 
        transition: transform 0.8s cubic-bezier(0.77, 0, 0.175, 1); 
        padding: 28px 20px 40px 20px !important; /* បន្ថយ Padding ខាងលើពី 60px មក 28px ដើម្បីលើកមាតិកាទាំងអស់ (ចំណងជើង+ឈ្មោះ+រូបភាព+ប៊ូតុង) ឡើងលើបន្តិច */
        transform: translate3d(0,0,0); /* iPhone Performance Fix */
    }
    
    #invite-cover::before {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.65);
        z-index: 0;
        pointer-events: none;
    }
    #invite-cover > * {
        position: relative;
        z-index: 2; 
    }

    #invite-cover.open { transform: translate3d(0, -100%, 0); /* iPhone Performance Fix */ }
    
    .cover-title { 
        font-family: 'Moul', cursive; 
        color: #FFD700 !important; 
        font-size: 1.35rem !important; 
        padding-top: 0 !important; 
        margin-top: 0 !important;
        margin-bottom: 35px !important; /* រក្សាគម្លាតចុះក្រោមសមរម្យ */
        text-shadow: 0 0 15px rgba(255, 215, 0, 0.7), 0 2px 4px rgba(0,0,0,0.9) !important;
        z-index: 10;
    }

    .cover-names { 
        font-family: 'Great Vibes', cursive; 
        font-size: 2.4rem !important; 
        color: #FFD700; 
        -webkit-text-stroke: 0.4px #FFD700; 
        margin-bottom: 35px !important; 
        text-shadow: 0 0 15px rgba(255, 215, 0, 0.6), 0 2px 4px rgba(0,0,0,0.9);
        line-height: 1.6 !important;
        overflow: visible !important;
        padding-top: 5px !important;
        padding-bottom: 5px !important;
        height: auto !important;
        display: block;
    }

    .cover-subtitle { 
        font-family: 'Suwannaphum', serif; 
        color: #FFD700; 
        font-size: 1.05rem !important; 
        margin-bottom: 30px !important; 
        font-weight: bold; 
        text-shadow: 1px 1px 2px black; 
    }
    
    .cover-guest-banner { 
        background: url('https://res.cloudinary.com/dlnze2pct/image/upload/f_auto,q_auto,c_limit,w_900/v1764480464/khach_2_cdjmfg.png') no-repeat center center;
        background-size: 100% 100%;
        color: #ffffff; 
        padding: 18px 32px !important;
        margin-bottom: 45px !important; /* បន្ថែម Margin ខាងក្រោមដើម្បីជំរុញប៊ូតុងខាងក្រោមឱ្យចុះក្រោមបន្តិច */
        font-family: 'Moul', cursive; 
        font-weight: normal; 
        font-size: 0.98rem !important; 
        position: relative; 
        width: 100%; 
        max-width: 380px; 
        text-shadow: 0 1px 2px rgba(0,0,0,0.5);
        display: flex;
        align-items: center;
        justify-content: center;
        border: none;
        box-shadow: none;
        min-height: 75px !important;
        line-height: 1.6 !important;
        overflow: visible !important;
        height: auto !important;
    }
    .cover-guest-banner::before, .cover-guest-banner::after { display: none; }
    
    .btn-frame-container {
        position: relative;
        display: inline-block;
        cursor: pointer;
        margin-top: 25px !important; /* បន្ថែម margin-top ពី 10px ទៅ 25px ដើម្បីឱ្យប៊ូតុងចុះក្រោមបន្តិច */
        z-index: 10001;
        animation: bounce-gentle 2s infinite;
    }

    .btn-frame-outer {
        background: transparent; 
        padding: 3px;
        border-radius: 50px;
        border: 2px solid #FFD700;
        box-shadow: 0 0 10px rgba(255, 215, 0, 0.6), inset 0 0 5px rgba(255, 215, 0, 0.4);
    }

    .btn-frame-inner {
        background: linear-gradient(to bottom, #800000, #500000); 
        padding: 2px;
        border-radius: 45px;
        border: 1px solid #FFD700;
    }

    .btn-frame-content {
        border: none;
        border-radius: 40px;
        padding: 8px 25px; /* Compact Size */
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 8px;
        background: transparent;
    }

    .btn-frame-text {
        font-family: 'Suwannaphum', serif;
        font-size: 1rem;
        color: #FFD700; 
        font-weight: bold;
        text-shadow: 0 1px 2px rgba(0,0,0,0.8);
        letter-spacing: 0.5px;
    }

    @keyframes bounce-gentle {
        0%, 100% { transform: translateY(0); }
        50% { transform: translateY(-3px); }
    }
    
    @keyframes pulse-gold { 0% { box-shadow: 0 0 10px rgba(255, 215, 0, 0.3); transform: scale(1); } 50% { box-shadow: 0 0 25px rgba(255, 215, 0, 0.6); transform: scale(1.02); } 100% { box-shadow: 0 0 10px rgba(255, 215, 0, 0.3); transform: scale(1); } }
    
    @keyframes slideLeftRight { from { opacity: 0; transform: translateX(-50px); } to { opacity: 1; transform: translateX(0); } }
    @keyframes slideTopBottom { from { opacity: 0; transform: translateY(-50px); } to { opacity: 1; transform: translateY(0); } }
    @keyframes slideRightLeft { from { opacity: 0; transform: translateX(50px); } to { opacity: 1; transform: translateX(0); } }
    @keyframes slideBottomTop { from { opacity: 0; transform: translateY(50px); } to { opacity: 1; transform: translateY(0); } }
    @keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
    @keyframes shimmer { 0% { background-position: -200% center; } 100% { background-position: 200% center; } }
    @keyframes zoomFocus { from { opacity: 0; transform: scale(0.9); filter: blur(2px); } to { opacity: 1; transform: scale(1); filter: blur(0); } }

    .animate-box { opacity: 0; transition: all 1s ease-out; }
    
    .anim-left-right.visible { animation: slideLeftRight 1s ease-out forwards; opacity: 1; }
    .anim-top-bottom.visible { animation: slideTopBottom 1s ease-out forwards; opacity: 1; }
    .anim-right-left.visible { animation: slideRightLeft 1s ease-out forwards; opacity: 1; }
    .anim-bottom-top.visible { animation: slideBottomTop 1s ease-out forwards; opacity: 1; }
    .anim-focus.visible { animation: zoomFocus 1.2s ease-out forwards; opacity: 1; }
    
    .anim-highlight { display: inline-block; animation: float 4s ease-in-out infinite; }

    .gallery-grid .gal-img { opacity: 0; transition: transform 0.5s, filter 0.5s; }
    .gallery-grid .gal-img.visible { opacity: 1; transform: translate(0,0); }
    .gallery-grid .gal-img:hover { transform: scale(1.03); filter: brightness(1.1); z-index: 10; box-shadow: 0 10px 20px rgba(0,0,0,0.5); }

    .container { max-width: 480px; margin: 0 auto; padding: 20px 15px; position: relative; z-index: 2; text-align: center; }
    
    .section-card { 
        background: transparent; 
        padding: 10px 5px !important; 
        margin-bottom: 20px !important; 
        text-align: center; 
        position: relative; 
    }
    
    .font-dangrek { font-family: 'Moul', cursive; } .font-script { font-family: 'Great Vibes', cursive; } .font-cinzel { font-family: 'Cinzel', serif; }
    
    .section-title { 
        font-family: 'Moul', cursive; 
        font-size: 1.5rem; 
        color: #FFD700; 
        text-shadow: 0 0 15px rgba(255, 215, 0, 0.6), 0 2px 4px rgba(0,0,0,0.9);
        font-style: italic; 
        line-height: 1.4 !important;
        padding-top: 5px !important;
        padding-bottom: 5px !important;
        margin-bottom: 5px !important;
        overflow: visible !important;
        display: inline-block;
        width: 100%;
        height: auto !important;
    }
    
    .section-subtitle { 
        font-family: 'Great Vibes', cursive; 
        color: #e0e0e0; 
        font-size: 1.4rem !important; 
        letter-spacing: 1px; 
        margin-bottom: 10px !important; 
        position: relative; 
        display: inline-block; 
        font-weight: 400; 
        text-shadow: 0 2px 4px #000; 
        line-height: 1.2; 
    }

    .section-subtitle::after { content: '✦'; display: block; color: #FFD700; font-size: 1.2rem; margin-top: 5px; text-shadow: 0 0 10px #FFD700; font-family: 'Cinzel', serif; }
    
    .main-title { 
        font-family: 'Moul', cursive; 
        font-size: 1.5rem; 
        margin-bottom: 5px; 
        color: #FFD700; 
        text-shadow: 0 0 15px rgba(255, 215, 0, 0.6), 0 2px 4px rgba(0,0,0,0.9);
        font-style: italic; 
        line-height: 1.4 !important;
        padding-top: 10px !important;
        padding-bottom: 5px !important;
        overflow: visible !important;
        display: inline-block;
        width: 100%;
        height: auto !important;
    }
    
    .couple-names { display: flex; flex-direction: column; align-items: center; gap: 5px; margin: 30px 0; }
    
    .c-name { 
        font-family: 'Moul', cursive; 
        font-size: 2.30rem; 
        color: #FFD700; 
        text-shadow: 0 0 15px rgba(255, 215, 0, 0.6), 0 2px 4px rgba(0,0,0,0.9);
        font-style: italic; 
        line-height: 1.8 !important; 
        padding-top: 15px !important;
        padding-bottom: 15px !important;
        margin-top: 0 !important;
        margin-bottom: 0 !important;
        overflow: visible !important;
        display: inline-block;
        width: 100%;
        height: auto !important;
    }

    .c-amp { font-family: 'Great Vibes', cursive; font-size: 2.0rem; color: #F9E4B7; text-shadow: 0 0 15px rgba(212, 175, 55, 0.8); }
    
    .footer-text-styled {
        font-family: 'Playfair Display', serif !important;
        font-weight: 700 !important;
        font-style: italic !important;
        font-size: 18px !important;
        color: #D4AF37 !important;
        text-shadow: 0 1px 2px rgba(0,0,0,0.8);
    }
    
    .parents-row { display: flex; flex-direction: column; align-items: center; gap: 20px; font-size: 1.2rem; margin: 20px 0; text-align: center; padding: 0 15px; }
    .parent-block { width: 100%; text-align: center; }
    .p-label { 
       font-family: 'Suwannaphum', serif; 
       font-size: 1.3rem; 
       color: #FF69B4 !important;  /* PINK COLOR */
       margin-bottom: 5px; 
       text-shadow: 0 1px 2px rgba(0,0,0,0.8); 
       font-weight: bold; 
    }
    .p-name { 
       font-weight: 700; 
       color: #FFD700 !important; /* GOLD COLOR */
       font-size: 1.2rem; 
       text-shadow: 0 0 10px rgba(255, 215, 0, 0.5); /* Gold Glow */
       letter-spacing: 0.5px; 
    }
    /* ធ្វើឱ្យអក្សរឪពុកម្ដាយលេចច្បាស់ជាងមុន (រចនាបថដដែល គ្រាន់តែបន្ថែមស្រមោលខ្មៅជុំវិញអក្សរ) */
    /* ឪពុកម្ដាយ៖ ចំណងជើង = មាស (Gold), ឈ្មោះ = ពណ៌ស ដូចអក្សរម៉ោងកម្មវិធី */
    .parents-row .p-label {
       color: #FFD700 !important;
       text-shadow: 0 2px 6px rgba(0,0,0,0.95) !important;
    }
    .parents-row .p-name {
       color: #FFFFFF !important;
       font-weight: 700 !important;
       text-shadow: 0 2px 6px rgba(0,0,0,1) !important;
    }
    
    .calendar-container { background: rgba(0,0,0,0.3); border-radius: 20px; padding: 25px; border: 1px solid rgba(212, 175, 55, 0.3); backdrop-filter: blur(4px); box-shadow: 0 10px 30px rgba(0,0,0,0.5); margin: 0 auto; }
    .cal-header { font-family: 'Great Vibes', cursive; font-weight: 400; color: #FFD700; margin-bottom: 15px; font-size: 2.2rem; letter-spacing: 1px; text-shadow: 0 2px 4px black; }
    .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 8px; font-family: 'Cinzel'; }
    .cal-day { aspect-ratio: 1; display: flex; align-items: center; justify-content: center; font-size: 1rem; border-radius: 50%; color: #fff; font-weight: bold; text-shadow: 0 1px 3px black; }
    .cal-day.active { background: radial-gradient(circle, #FCF6BA 0%, #B38728 100%); color: #2c1810; font-weight: 900; box-shadow: 0 0 20px rgba(255, 215, 0, 0.8); transform: scale(1.2); text-shadow: none; border: 1px solid #fff; }
    .cal-title-day { font-weight: bold; color: #FFD700; }
    
    .countdown-flex { display: flex; justify-content: center; gap: 12px; margin-top: 10px; }
    .time-box { background: rgba(20,20,20,0.5); border: 1px solid rgba(212, 175, 55, 0.5); border-radius: 12px; padding: 15px 10px; min-width: 70px; backdrop-filter: blur(5px); }
    .time-val { font-family: 'Cinzel'; font-size: 2rem; font-weight: 900; color: #fff; line-height: 1; text-shadow: 0 0 10px rgba(212, 175, 55, 0.5); }
    .time-lbl { font-family: 'Suwannaphum', serif; font-size: 1rem; color: #FFD700; margin-top: 5px; text-transform: uppercase; font-weight: bold; letter-spacing: 0.5px; }
    
    .timeline { position: relative; padding-left: 20px; text-align: left; margin-top: 10px; display: inline-block; width: 100%; max-width: 350px; }
    .timeline::before { content: ''; position: absolute; top: 0; bottom: 0; left: 7px; width: 2px; background: linear-gradient(to bottom, transparent, #FFD700, transparent); }
    .t-item { position: relative; padding-left: 40px; margin-bottom: 35px; text-align: left; opacity: 0; transition: all 1s ease; transform: translateX(30px); }
    .t-item.visible { opacity: 1; transform: translateX(0); }
    .t-dot { position: absolute; left: 0; top: 5px; width: 16px; height: 16px; background: #FFD700; border: 2px solid #fff; border-radius: 50%; box-shadow: 0 0 10px #FFD700; }
    .t-time { font-family: 'Suwannaphum', serif; font-weight: 700; color: #F9E4B7; font-size: 1.1rem; text-shadow: 0 2px 4px black; margin-bottom: 2px; }
    .t-desc { font-family: 'Suwannaphum', serif; font-size: 1.1rem; color: #fff; font-weight: 700; text-shadow: 0 2px 6px black; }
    
    .ty-text { line-height: 2.2; color: #fff; font-size: 1.3rem; text-align: center; font-weight: 600; text-shadow: 0 2px 8px rgba(0,0,0,1); background: rgba(0,0,0,0.2); padding: 20px; border-radius: 15px; border: 1px solid rgba(255,255,255,0.1); margin: 0 auto; }
    
    .map-frame { border-radius: 20px; overflow: hidden; border: 3px solid rgba(255, 215, 0, 0.6); margin-bottom: 20px; height: 250px; box-shadow: 0 10px 30px rgba(0,0,0,0.6); }
    .btn-map { background: linear-gradient(45deg, #000, #333); color: #FFD700; border: 1px solid #FFD700; padding: 12px 30px; border-radius: 50px; font-weight: bold; text-decoration: none; display: inline-flex; align-items: center; gap: 10px; box-shadow: 0 5px 15px rgba(0,0,0,0.5); transition: all 0.3s; font-family: 'Suwannaphum', serif; font-size: 1.2rem; }
    .btn-map:hover { background: #FFD700; color: #000; transform: scale(1.05); }
    
    .gallery-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
    .gal-img { width: 100%; height: 200px; object-fit: cover; border-radius: 10px; border: 2px solid rgba(255,255,255,0.3); transition: transform 0.3s; cursor: pointer; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }
    .gal-img:first-child { grid-column: span 2; height: 300px; }
    
    .qr-box { background: #fff; padding: 10px; display: inline-block; border-radius: 15px; margin-bottom: 15px; border: 4px solid #FFD700; box-shadow: 0 0 40px rgba(255, 215, 0, 0.4); }
    .qr-img { width: 220px; height: 220px; object-fit: cover; }
    
    #section-qr .section-subtitle { margin-bottom: 5px !important; }

    .wish-container { padding: 10px; }
    .form-group { margin-bottom: 20px; text-align: center; }
    .form-label { display: block; font-family: 'Suwannaphum', serif; font-size: 1.1rem; color: #F9E4B7; margin-bottom: 8px; text-shadow: 0 2px 4px black; font-weight: bold; }
    
    .form-control { 
        width: 100%; 
        padding: 15px; 
        border: 1px solid #e0e0e0 !important; 
        border-radius: 12px !important; 
        /* កំណត់ Font Suwannaphum ឱ្យចំគោលដៅ */
        font-family: 'Suwannaphum', serif !important; 
        font-size: 1.1rem; 
        background: #ffffff !important; 
        color: #8B4513 !important;
        outline: none; 
        transition: all 0.3s; 
        text-align: center; 
    }
    .form-control::placeholder { color: #999999 !important; }
    .form-control:focus { border-color: #FFD700; box-shadow: 0 0 10px rgba(255,215,0,0.3); }
    
    .btn-submit { width: 100%; background: linear-gradient(to right, #FFD700, #DAA520); color: #2c1810; border: none; padding: 15px; border-radius: 50px; font-family: 'Suwannaphum', serif; font-size: 1.4rem; cursor: pointer; box-shadow: 0 5px 20px rgba(255, 215, 0, 0.4); transition: transform 0.2s; font-weight: bold; }
    .btn-submit:active { transform: scale(0.95); }
    .btn-telegram { background: linear-gradient(45deg, #0088cc, #005f8f); color: #fff; border: 1px solid rgba(255,255,255,0.3); padding: 12px 30px; border-radius: 50px; font-weight: bold; text-decoration: none; display: inline-flex; align-items: center; gap: 10px; box-shadow: 0 5px 15px rgba(0,136,204,0.4); transition: all 0.3s; font-family: 'Suwannaphum', serif; font-size: 1.2rem; margin-top: 10px; }
    .btn-telegram:hover { transform: scale(1.05); box-shadow: 0 8px 20px rgba(0,136,204,0.6); border-color: #fff; }
    
    .social-row { display: flex; justify-content: center; gap: 20px; margin-bottom: 20px; margin-top: 10px; }
    .social-btn { width: 50px; height: 50px; border-radius: 50%; color: #fff; display: flex; align-items: center; justify-content: center; font-size: 1.4rem; text-decoration: none; box-shadow: 0 5px 15px rgba(0,0,0,0.5); transition: transform 0.3s; }
    .social-btn:hover { transform: scale(1.1); } .social-btn.telegram { background: #0088cc; } .social-btn.tiktok { background: #000; border: 1px solid #333; } .social-btn.web { background: linear-gradient(to bottom right, #FF9800, #F57C00); }
    
    /* 2. Nav Bar must be LOWER than Cover, but HIGHER than page content */
    .bottom-nav { 
        position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%) translate3d(0,0,0); /* iPhone Performance Fix */
        background: rgba(40, 40, 40, 0.75); backdrop-filter: blur(10px); 
        padding: 5px 12px !important; /* COMPACT PADDING */
        border-radius: 50px; 
        display: flex; justify-content: space-between; gap: 5px !important; /* COMPACT GAP */
        align-items: center; 
        z-index: 10000 !important; /* Lower than 20000, so it sits BEHIND the cover */
        width: max-content !important; /* COMPACT WIDTH */
        min-width: 0;
        box-shadow: 0 5px 20px rgba(0,0,0,0.4);
    }
    .nav-link { 
        color: #fff; width: 36px; height: 36px; /* COMPACT SIZE */
        border-radius: 50%; 
        display: flex; align-items: center; justify-content: center; 
        font-size: 0.95rem; /* COMPACT FONT */
        transition: all 0.2s ease; opacity: 0.6; text-decoration: none;
    }
    .nav-link.active { 
        background: #FFC107; color: #000; opacity: 1; transform: translateY(-5px) scale(1.1); 
        box-shadow: 0 0 15px rgba(255, 193, 7, 0.6);
    }
    
    #snow-canvas {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 99999;
}
    
    .wish-list { max-height: 400px; overflow-y: auto; text-align: left; margin-top: 20px; padding: 10px; background: transparent; border-radius: 10px; }
    
    .wish-item { 
        background: #ffffff; 
        border-radius: 10px; 
        padding: 15px; 
        margin-bottom: 10px; 
        box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        border: none;
    }
    
    .wish-name { 
        color: #8B4513; 
        font-weight: bold; 
        font-family: 'Kantumruy Pro', sans-serif;
        font-size: 1.1rem; 
        margin-bottom: 4px; 
    }
    
    .wish-msg { 
        color: #E91E63 !important; 
        font-family: 'Suwannaphum', serif; 
        font-size: 1rem; 
        line-height: 1.4; 
        word-wrap: break-word;
        font-style: italic;
    }

    .wish-date {
        display: block; width: 100%; text-align: right;
        color: #999999; font-size: 0.75rem; margin-top: 8px;
        font-family: sans-serif;
    }
    
    /* Lightbox Styles */
    #lightbox { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.95); z-index: 100000; display: none; align-items: center; justify-content: center; flex-direction: column; opacity: 0; transition: opacity 0.3s; }
    #lightbox.active { display: flex; opacity: 1; }
    #lightbox-img { max-width: 90%; max-height: 80vh; border-radius: 5px; box-shadow: 0 0 20px rgba(0,0,0,0.8); transition: transform 0.3s; object-fit: contain; }
    .lb-btn { position: absolute; color: #fff; font-size: 2rem; cursor: pointer; background: rgba(255,255,255,0.1); width: 50px; height: 50px; border-radius: 50px; display: flex; align-items: center; justify-content: center; transition: background 0.2s; user-select: none; z-index: 100001; }
    .lb-btn:hover { background: rgba(255,255,255,0.3); }
    .lb-close { top: 20px; right: 20px; font-size: 1.5rem; }
    .lb-prev { left: 20px; top: 50%; transform: translateY(-50%); }
    .lb-next { right: 20px; top: 50%; transform: translateY(-50%); }

    @keyframes snow-fall { 0% { transform: translateY(-20px) translateX(0); opacity: 0; } 10% { opacity: 0.8; } 100% { transform: translateY(105vh) translateX(20px); opacity: 0; } }

    /* OVERRIDES FOR COLOR CONTRAST */
    /* FIXED: Overlay opacity 0.5 */
    /* OVERRIDES FOR COLOR CONTRAST */
    /* ដាក់ពណ៌ខ្មៅ ៦០% ពីលើ Background ដើម្បីឱ្យអក្សរលេចច្បាស់ល្អ */
    #invite-cover::before { background: rgba(0, 0, 0, 0.3) !important; }
    /* Overlay ពណ៌ Navy Blue ស្រាល ៗ លើ Background ដើម្បីឱ្យអក្សរមាស/ស លេចច្បាស់ (ដើម្បីលើកកម្រិតតាម Background ភ្លឺ → --ov-extra ត្រូវបានគណនាស្វ័យប្រវត្តិ) */
    .overlay { background: linear-gradient(180deg, rgba(6,16,52,calc(.52 + var(--ov-extra,0))) 0%, rgba(8,20,60,calc(.44 + var(--ov-extra,0))) 45%, rgba(4,10,38,calc(.58 + var(--ov-extra,0))) 100%) !important; }
    /* Background ដែល User Upload → Overlay ងងឹតជាងបន្តិចទៀត (មិនស្គាល់ពន្លឺរូប) */
    .overlay[data-custom] { --ov-extra: .1; }

    .main-title, .section-title, .c-name, .cover-names, .cover-title { 
        color: #FFD700 !important;
        text-shadow: 0 0 15px rgba(255, 215, 0, 0.6), 0 2px 4px rgba(0,0,0,0.9) !important;
    }
    
    .section-subtitle, .cover-subtitle, .p-label, .form-label, .cal-header {
        color: #FFF8D6 !important;
        text-shadow: 0 1px 3px rgba(0,0,0,1) !important;
    }

    .ty-text, .p-name, .t-desc, .t-time, .cover-guest-banner {
        color: #FFFFFF !important;
        text-shadow: 0 1px 2px rgba(0,0,0,0.8) !important;
    }
  </style>
  <img id="bg-image" src="{{BG_IMAGE}}" alt="Background">
  <div class="overlay"></div>
  <script>
  /* Auto-contrast: បើ Background ភ្លឺ (រួមទាំងរូបដែល User Upload) → បន្ថែម Overlay បន្តិចឱ្យអក្សរច្បាស់ជានិច្ច */
  (function () { try {
    var img = document.getElementById('bg-image'), ov = document.querySelector('.overlay'); if (!img || !ov) return;
    var src = img.getAttribute('src') || ''; var isData = src.indexOf('data:') === 0;
    if (window.self !== window.top && !isData) return;
    var im = new Image(); if (!isData) im.crossOrigin = 'anonymous';
    im.onload = function () { try {
      var c = document.createElement('canvas'); c.width = c.height = 24;
      var x = c.getContext('2d'); x.drawImage(im, 0, 0, 24, 24);
      var d = x.getImageData(0, 0, 24, 24).data, s = 0, n = d.length / 4;
      for (var i = 0; i < d.length; i += 4) s += (0.2126 * d[i] + 0.7152 * d[i + 1] + 0.0722 * d[i + 2]) / 255;
      var extra = (ov.hasAttribute('data-custom') ? 0.1 : 0) + Math.max(0, Math.min(0.2, (s / n - 0.4) * 0.45));
      ov.style.setProperty('--ov-extra', extra.toFixed(3));
    } catch (e) {} };
    im.src = src;
  } catch (e) {} })();
  ${'</'+'script>'}
  <canvas id="snow-canvas"></canvas>
  <audio id="bgMusic" loop preload="auto"><source src="{{MUSIC_URL}}" type="audio/mpeg"></audio>
  
<div id="invite-cover">
    <div class="cover-title" style="margin-top: 30px; margin-bottom: 20px;">{{PROGRAM_TITLE}}</div>
    
    <div class="cover-names anim-highlight">
        <div style="display:block">{{GROOM_EN}}</div>
        <div style="font-size:1.3rem; margin:8px 0; letter-spacing:4px;">✦ 💛 ✦</div>
        <div style="display:block">{{BRIDE_EN}}</div>
    </div>
    
    <div style="font-family: 'Suwannaphum', serif; color: #FF69B4; font-size: 1.2rem; margin-top: 10px; margin-bottom: 5px; text-shadow: 0 2px 4px rgba(0,0,0,0.8); font-weight: bold; font-style: italic;">សូមគោរពអញ្ជើញ</div>
    
    <div class="cover-guest-banner animate-box anim-bottom-top">{{GUEST_NAME}}</div>
    
    <div class="btn-frame-container" onclick="openInvitation()">
        <div class="btn-frame-outer">
            <div class="btn-frame-inner">
                <div class="btn-frame-content">
                    <i class="fa-solid fa-envelope-open" style="color:#FFD700; font-size:0.9rem;"></i>
                    <span class="btn-frame-text">សូមចុចដើម្បីមើលសំបុត្រ</span>
                </div>
            </div>
        </div>
    </div>
</div> 
  <div class="container">
    <!-- TASK 4: CORRECT SECTION IDs for Navigation -->
    <div class="section-card" id="section-home">
        <div class="main-title animate-box anim-left-right">{{PROGRAM_TITLE}}</div>
        <div class="section-subtitle">Wedding Celebration</div>
        <div class="parents-row">
            <div class="parent-block animate-box anim-left-right"><div class="p-label">ឪពុកម្ដាយខាងកូនកំលោះ</div><div class="p-name">លោក {{GROOM_FATHER}}</div><div class="p-name">អ្នកស្រី {{GROOM_MOTHER}}</div></div>
            <div class="parent-block animate-box anim-right-left"><div class="p-label">ឪពុកម្ដាយខាងកូនក្រមុំ</div><div class="p-name">លោក {{BRIDE_FATHER}}</div><div class="p-name">អ្នកស្រី {{BRIDE_MOTHER}}</div></div>
        </div>
        <div class="couple-names animate-box anim-top-bottom"><div class="c-name">{{GROOM}}</div><div class="c-amp anim-highlight">&</div><div class="c-name">{{BRIDE}}</div></div>
        <div class="animate-box anim-bottom-top" style="font-size:1.3rem; color:#FF69B4; font-weight:bold; text-shadow: 0 2px 3px black;">ប្រព្រឹត្តទៅនៅ</div>
        <div class="font-dangrek animate-box anim-bottom-top" style="font-size:1.8rem; color:#FFD700; text-shadow: 0 2px 4px black;">{{DATE_KH}}</div>
        <div class="animate-box anim-bottom-top" style="font-family: 'Suwannaphum', serif; font-size: 1.1rem; color: #FFD700; margin-top: 15px; margin-bottom: 20px; padding: 0 10px; line-height: 1.6; text-shadow: 0 1px 2px rgba(0,0,0,0.8);">{{LOCATION_DESCRIPTION}}</div>
    </div>
    
    <div class="section-card animate-box anim-bottom-top" id="section-calendar"><div class="section-title">ប្រតិទិន</div><div class="section-subtitle">Event Calendar</div><div class="calendar-container"><div class="cal-header" id="cal-header">CALENDAR</div><div class="cal-grid" id="calendar-grid"><div class="cal-title-day">S</div><div class="cal-title-day">M</div><div class="cal-title-day">T</div><div class="cal-title-day">W</div><div class="cal-title-day">T</div><div class="cal-title-day">F</div><div class="cal-title-day">S</div></div></div></div>
    
    <div class="section-card animate-box anim-focus" id="section-countdown">
        <div class="section-title">រាប់ថយក្រោយ</div>
        <div class="section-subtitle">Event Countdown</div>
        <div class="countdown-flex">
            <div class="time-box"><div class="time-val" id="d">00</div><div class="time-lbl">ថ្ងៃ</div></div>
            <div class="time-box"><div class="time-val" id="h">00</div><div class="time-lbl">ម៉ោង</div></div>
            <div class="time-box"><div class="time-val" id="m">00</div><div class="time-lbl">នាទី</div></div>
            <div class="time-box"><div class="time-val" id="s">00</div><div class="time-lbl">វិនាទី</div></div>
        </div>
        <!-- TASK 5: CALENDAR LINK INJECTION -->
        <a href="{{CALENDAR_LINK}}" target="_blank" class="btn-calendar" style="display:inline-block; margin-top:20px; padding:10px 25px; background:linear-gradient(to right, #FFD700, #DAA520); color:#2c1810; border-radius:30px; text-decoration:none; font-weight:bold; font-family:'Suwannaphum',serif; font-size:1rem; box-shadow: 0 4px 10px rgba(0,0,0,0.5); transition: transform 0.2s;"><i class="fa-solid fa-calendar-plus"></i> Add to Calendar</a>
    </div>
    
    <div class="section-card" id="section-program"><div class="section-title">កម្មពិធីសិរីសួស្ដី</div><div class="section-subtitle">Wedding Program</div><div class="timeline" id="program-timeline"></div></div>
    
    <div class="section-card animate-box anim-top-bottom" id="section-thankyou"><div style="font-size:3rem; color:#FFD700; margin-bottom:10px; filter:drop-shadow(0 2px 5px black);"><i class="fa-solid fa-hands-praying"></i></div><div class="section-title">អរគុណ & អធ្យាស្រ័យ</div><div class="section-subtitle">Thank You & Sorry</div><div class="ty-text">យើងខ្ញុំ សូមថ្លែងអំណរគុណយ៉ាងជ្រាលជ្រៅ ចំពោះវត្តមាន ភ្ញៀវកិត្តិយសទាំងអស់ ដែលបានចំណាយពេលវេលាដ៏មានតម្លៃអញ្ជើញចូលរួមជាអធិបតី និងជាភ្ញៀវកិត្តិយសក្នុងពិធីនេះ។<br><br>ប្រសិនបើមានការខ្វះខាត ឬទទួលភ្ញៀវមិនបានដិតដល់ត្រង់ចំណុចណាមួយ សូមមេត្តាខន្តី និងអធ្យាស្រ័យដោយក្តីអនុគ្រោះ។</div></div>
    
    <div class="section-card animate-box anim-focus" id="section-map"><div class="section-title">ទីតាំងពិធី</div><div class="section-subtitle">Google Maps</div><div class="map-frame"><iframe width="100%" height="100%" frameborder="0" style="border:0" src="https://maps.google.com/maps?q={{VENUE_ENCODED}}&t=&z=14&ie=UTF8&iwloc=&output=embed" allowfullscreen></iframe></div><a href="{{MAP_LINK}}" target="_blank" class="btn-map"><i class="fa-solid fa-location-dot"></i> Link ទៅកាន់ Google Maps</a></div>
    
    <div class="section-card" id="section-gallery"><div class="section-title">រូបថតអនុស្សារីយ៍</div><div class="section-subtitle">Photo Gallery</div><div class="gallery-grid" id="gallery-grid"></div></div>
    
    <div class="section-card animate-box anim-bottom-top" id="section-qr">
        <div class="section-title">ជូនចំណងដៃ</div><div class="section-subtitle">Scan QR Code</div>
        <div class="qr-box"><img src="{{QR_CODE_URL}}" class="qr-img"></div>
        <div class="text-shadow animate-box anim-bottom-top" style="font-size:1.1rem; margin-top:10px; font-weight:bold; text-shadow:0 2px 4px black; font-family: 'Great Vibes'; color: #FF69B4;">Scan to Gift</div>
        <div class="animate-box anim-bottom-top" style="font-size:1.1rem; color: #FF69B4; font-family: 'Suwannaphum', serif;">{{GROOM}} & {{BRIDE}}</div>
    </div>
    
    <div class="section-card animate-box anim-bottom-top" id="section-rsvp">
        <div class="section-title">ការចូលរួម</div>
        <div class="section-subtitle">RSVP</div>
        <div class="wish-container">
            <div class="form-group">
                <label class="form-label">ឈ្មោះរបស់អ្នក (Your Name)</label>
                <input type="text" id="rsvp-name" class="form-control" placeholder="បញ្ចូលឈ្មោះពេញ..." style="text-align: center;">
            </div>
            <div class="form-group">
                <label class="form-label">តើអ្នកនឹងអញ្ជើញចូលរួមដែរឬទេ?</label>
                <div style="display:flex; justify-content:center; gap:20px; margin-bottom:15px;">
                    <label style="color:#fff; font-size:1.2rem; cursor:pointer;"><input type="radio" name="attendance" value="going" checked style="accent-color:#FFD700; transform:scale(1.2);"> ចូលរួម</label>
                    <label style="color:#fff; font-size:1.2rem; cursor:pointer;"><input type="radio" name="attendance" value="not_going" style="accent-color:#FFD700; transform:scale(1.2);"> មិនបានចូលរួម</label>
                </div>
            </div>
            <div class="form-group">
                <label class="form-label">ចំនួនអ្នកចូលរួម (Number of Guests)</label>
                <select id="guest-count" class="form-control" style="text-align-last:center;">
                    <option value="1">1 នាក់</option>
                    <option value="2">2 នាក់</option>
                    <option value="3">3 នាក់</option>
                    <option value="4">4 នាក់</option>
                    <option value="5">5+ នាក់</option>
                </select>
            </div>
            <button id="btn-rsvp" class="btn-submit" onclick="submitRSVP()"><i class="fa-solid fa-check-circle"></i> យល់ព្រម (Confirm)</button>
            <div id="rsvp-list" class="wish-list" style="margin-top:20px;"></div>
        </div>
    </div>
    
    <div class="section-card animate-box anim-right-left" id="section-wishes"><div class="section-title">សារជូនពរ</div><div class="section-subtitle">Best Wishes</div><div class="wish-container"><div class="form-group"><label class="form-label">ឈ្មោះរបស់អ្នក (Your Name)</label><input type="text" id="w-name" class="form-control" placeholder="បញ្ចូលឈ្មោះ..."></div><div class="form-group"><label class="form-label">សារជូនពររបស់អ្នក (Your Message)</label><textarea id="w-message" class="form-control" rows="4" placeholder="សរសេរពាក្យជូនពរ..."></textarea></div><button id="btn-wish" class="btn-submit" onclick="submitWish()"><i class="fa-solid fa-paper-plane"></i> ផ្ញើរសារជូនពរ</button><div id="wishes-list" class="wish-list"></div></div></div>
    
    <div class="section-card animate-box anim-bottom-top" id="section-contact" style="padding-bottom: 80px;">
        <div class="section-title" style="font-size: 1.5rem; margin-bottom: 5px; color: #F9E4B7;">ទំនាក់ទំនងម្ចាស់ពិធីមង្គល</div>
        <div class="footer-text-styled" style="margin-bottom: 15px; letter-spacing: 1px;">Telegram: {{TELEGRAM_TEXT}}</div>
        <a href="{{TELEGRAM_LINK}}" target="_blank" class="btn-telegram"><i class="fa-brands fa-telegram"></i> ចូល Telegram ម្ចាស់ពិធី</a>
        <div style="width: 100%; height: 1px; background: linear-gradient(to right, transparent, rgba(255,255,255,0.2), transparent); margin: 40px 0;"></div>
        <div class="footer-text-styled anim-highlight" style="margin-bottom: 15px;">Phumtheapka</div>
        <div class="social-row"><a href="${APP_CONFIG.SOCIAL.TELEGRAM_CHANNEL}" target="_blank" class="social-btn telegram"><i class="fa-brands fa-telegram"></i></a><a href="${APP_CONFIG.SOCIAL.TIKTOK}" target="_blank" class="social-btn tiktok"><i class="fa-brands fa-tiktok"></i></a><a href="${APP_CONFIG.SOCIAL.WEBSITE}" target="_blank" class="social-btn web"><i class="fa-solid fa-flag"></i></a></div></div>
  </div>
  
  <div class="bottom-nav">
      <a href="#" onclick="scrollToSection(event, 'section-home')" class="nav-link"><i class="fa-solid fa-heart"></i></a>
      <a href="#" onclick="scrollToSection(event, 'section-calendar')" class="nav-link"><i class="fa-solid fa-calendar-days"></i></a>
      <a href="#" onclick="scrollToSection(event, 'section-countdown')" class="nav-link"><i class="fa-solid fa-hourglass-half"></i></a>
      <a href="#" onclick="scrollToSection(event, 'section-program')" class="nav-link"><i class="fa-solid fa-list-check"></i></a>
      <a href="#" onclick="scrollToSection(event, 'section-map')" class="nav-link"><i class="fa-solid fa-location-dot"></i></a>
      <a href="#" onclick="scrollToSection(event, 'section-gallery')" class="nav-link"><i class="fa-solid fa-images"></i></a>
      <a href="#" onclick="scrollToSection(event, 'section-contact')" class="nav-link"><i class="fa-solid fa-phone"></i></a>
    </div>
  
  <div id="lightbox">
      <div class="lb-btn lb-close" onclick="closeLightbox()">&times;</div>
      <div class="lb-btn lb-prev" onclick="changeImage(-1)">&#10094;</div>
      <img id="lightbox-img" src="" alt="Gallery Image">
      <div class="lb-btn lb-next" onclick="changeImage(1)">&#10095;</div>
  </div>

  <script type="module">
    import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js";
    import { getFirestore, collection, addDoc, onSnapshot, query, orderBy, serverTimestamp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js";

    const firebaseConfig = {
      apiKey: "AIzaSyCFD-6aB2RmNhomEFG6kjfvlDAmO2rpFD4",
      authDomain: "phumtheapka.com",
      projectId: "heangweddingdigital",
      storageBucket: "heangweddingdigital.firebasestorage.app",
      messagingSenderId: "422526904540",
      appId: "1:422526904540:web:6c2dc2ae4cb9169f636c19",
      measurementId: "G-04ZYX25RF0"
    };

    let app, db;
    try {
        app = initializeApp(firebaseConfig);
        db = getFirestore(app);
    } catch(e) {
        console.warn("Firebase failed to load (likely inside preview/iframe):", e);
    }

    const eventId = "{{EVENT_ID}}";
    
    // MOCK DATA INJECTION
    const MOCK_WISHES = [
        { name: "លោក ចន្ទ សុផល", msg: "Congratulations", date: "13-10-2025 | 23:36 PM" },
    ];

    function renderMockWishes() {
        const list = document.getElementById('wishes-list');
        if(!list) return;
        
        // Clear logic handled elsewhere or append
        if(list.children.length === 0) {
            MOCK_WISHES.forEach(w => {
                const div = document.createElement('div');
                div.className = 'wish-item';
                div.innerHTML = '<div class="wish-name">' + w.name + '</div><div class="wish-msg">' + w.msg + '</div><div class="wish-date">' + w.date + '</div>';
                list.appendChild(div);
            });
        }
    }
    
    const MOCK_RSVPS = [
        { name: "លោក ចន្ទ សុផល", status: "បានឆ្លើយតប៖ ចូលរួម (1 នាក់)" },
    ];

    function renderMockRsvps() {
        const list = document.getElementById('rsvp-list');
        if(!list) return;
        if(list.children.length === 0) {
            MOCK_RSVPS.forEach(r => {
                const div = document.createElement('div');
                div.className = 'wish-item';
                div.innerHTML = '<div class="wish-name">' + r.name + '</div><div class="wish-msg">' + r.status + '</div>';
                list.appendChild(div);
            });
        }
    }
    renderMockRsvps();

    window.openInvitation = function() { const cover = document.getElementById('invite-cover'); const audio = document.getElementById('bgMusic'); cover.classList.add('open'); if(audio.paused) { audio.play().catch(e => console.log("Audio autoplay failed", e)); } };
    
    // TASK 2: SMOOTH SCROLL ENGINE
    window.scrollToSection = function(event, id) {
        event.preventDefault(); // STOP page reload
        const element = document.getElementById(id);
        if (element) {
            document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
            event.currentTarget.classList.add('active');
            const y = element.getBoundingClientRect().top + window.pageYOffset - 80;
            window.scrollTo({top: y, behavior: 'smooth'});
        }
    };

    const observer = new IntersectionObserver((entries) => { 
        entries.forEach(entry => { 
            if(entry.isIntersecting) { 
                entry.target.classList.add('visible'); 
                document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active')); 
                const id = entry.target.id; 
                if(id && id.startsWith('section-')) {
                    const link = document.querySelector('.nav-link[onclick*="' + id + '"]'); 
                    if(link) link.classList.add('active'); 
                }
            } 
        }); 
    }, { threshold: 0.15 }); 
    
    document.querySelectorAll('.animate-box, .section-card').forEach(el => observer.observe(el));
    
    const targetDateStr = "{{DATE_ISO}}"; const targetTimeStr = "{{TIME_ISO}}"; const weddingDate = new Date(targetDateStr + "T" + targetTimeStr);
    const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; document.getElementById('cal-header').innerText = monthNames[weddingDate.getMonth()] + ' ' + weddingDate.getFullYear();
    const grid = document.getElementById('calendar-grid'); const firstDay = new Date(weddingDate.getFullYear(), weddingDate.getMonth(), 1).getDay(); const daysInMonth = new Date(weddingDate.getFullYear(), weddingDate.getMonth() + 1, 0).getDate();
    for(let i=0; i<firstDay; i++) { const d = document.createElement('div'); grid.appendChild(d); } for(let i=1; i<=daysInMonth; i++) { const d = document.createElement('div'); d.className = 'cal-day'; if(i === weddingDate.getDate()) d.classList.add('active'); d.innerText = i; grid.appendChild(d); }
    setInterval(() => { const now = new Date(); const diff = weddingDate - now; if(diff > 0) { const d = Math.floor(diff / (1000*60*60*24)); const h = Math.floor((diff % (1000*60*60*24)) / (1000*60*60)); const m = Math.floor((diff % (1000*60*60)) / (1000*60)); const s = Math.floor((diff % (1000*60)) / 1000); document.getElementById('d').innerText = d < 10 ? '0'+d : d; document.getElementById('h').innerText = h < 10 ? '0'+h : h; document.getElementById('m').innerText = m < 10 ? '0'+m : m; document.getElementById('s').innerText = s < 10 ? '0'+s : s; } }, 1000);
    
    const scheduleRaw = '{{SCHEDULE}}'; const tlContainer = document.getElementById('program-timeline'); const cleanSchedule = scheduleRaw.replace(/&lt;br&gt;/g, '\\n').replace(/<br>/g, '\\n'); const lines = cleanSchedule.split(/\\r?\\n/);
    for (let i = 0; i < lines.length; i++) { 
        let line = lines[i].trim(); if (!line) continue; 
        const item = document.createElement('div');
        item.className = 't-item';
        setTimeout(() => observer.observe(item), i * 100);

        if (line.startsWith('-')) { 
            let time = line.substring(1).trim(); 
            let desc = ''; 
            if (i + 1 < lines.length) { let nextLine = lines[i+1].trim(); if (nextLine && !nextLine.startsWith('-')) { desc = nextLine; i++; } } 
            var html = '<div class="t-dot"></div>'; 
            if(time) { html += '<div class="t-time">' + time + '</div>'; } 
            html += '<div class="t-desc">' + desc + '</div>'; 
            item.innerHTML = html; 
        } else { 
            item.innerHTML = '<div class="t-dot"></div><div class="t-desc" style="margin-top:0;">' + line + '</div>'; 
        }
        tlContainer.appendChild(item); 
    }
    
    const galleryRaw = '{{GALLERY_JSON}}'; 
    let images = []; 
    let galleryImagesList = [];
    let currentImgIndex = 0;

    if(galleryRaw.includes('{{')) { 
        const def = '${APP_CONFIG.DEFAULT_IMAGE}'; 
        images = [def, def, def, def, def, def, def]; 
    } else { 
        try { 
            images = JSON.parse(galleryRaw.replace(/&quot;/g, '"')); 
        } catch(e) { 
            const def = '${APP_CONFIG.DEFAULT_IMAGE}'; 
            images = [def, def, def, def, def, def, def]; 
        } 
    } 
    
    galleryImagesList = images;
    const galGrid = document.getElementById('gallery-grid'); 
    const animClasses = ['anim-left-right', 'anim-top-bottom', 'anim-right-left', 'anim-bottom-top'];
    
    images.forEach((src, index) => { 
        const img = document.createElement('img'); 
        img.src = src; 
        img.className = 'gal-img animate-box ' + animClasses[index % 4]; 
        img.onclick = () => openLightbox(index); 
        galGrid.appendChild(img); 
        observer.observe(img);
    });

    window.openLightbox = function(index) {
        currentImgIndex = index;
        const lb = document.getElementById('lightbox');
        const lbImg = document.getElementById('lightbox-img');
        lbImg.src = galleryImagesList[currentImgIndex];
        lb.classList.add('active');
    };

    window.closeLightbox = function() {
        document.getElementById('lightbox').classList.remove('active');
    };

    window.changeImage = function(direction) {
        currentImgIndex += direction;
        if (currentImgIndex >= galleryImagesList.length) currentImgIndex = 0;
        if (currentImgIndex < 0) currentImgIndex = galleryImagesList.length - 1;
        
        const lbImg = document.getElementById('lightbox-img');
        lbImg.style.opacity = 0;
        setTimeout(() => {
            lbImg.src = galleryImagesList[currentImgIndex];
            lbImg.style.opacity = 1;
        }, 150);
    };

    document.getElementById('lightbox').addEventListener('click', (e) => {
        if(e.target.id === 'lightbox') window.closeLightbox();
    });

    (function () {
        var canvas = document.getElementById("snow-canvas");
        var ctx = canvas.getContext("2d");
        var width = window.innerWidth;
        var height = window.innerHeight;
        canvas.width = width;
        canvas.height = height;

        // ធៀបណាដែលកំណត់ PARTICLE_TYPE ជាក់លាក់ (Sweet Blossom / Night Sky Romance / Golden Sunset)
        // ប្រើ Animation ថ្មី។ ធៀបផ្សេងទៀតទាំងអស់ប្រើ Animation ដើមទុកដដែល (មិនប៉ះពាល់)។
        var particleTheme = "{{PARTICLE_TYPE}}";
        if (!particleTheme || particleTheme.indexOf('{{') === 0) particleTheme = '';

        var SPECIAL_THEMES = { 'sweet-blossom': 1, 'night-sky-romance': 1, 'green-leaves-minimal': 1 };

        if (SPECIAL_THEMES[particleTheme]) {
            // ============================================================
            // ANIMATION ថ្មី៖ សម្រាប់តែ ៣ ម៉ូតនេះប៉ុណ្ណោះ
            // ============================================================
            var THEME_MIX = {
                // Sweet Blossom → មេអំបៅពណ៌ផ្ការឈូក (មិនច្រើនពេក) + ផ្កាធ្លាក់ជាចម្បង
                'sweet-blossom':       [['butterfly', 0.15], ['petal', 0.55], ['sunset-gold', 0.18], ['gold', 0.12]],
                // Night Sky Romance → សត្វអំពិលអំពេលភ្លឺផ្លែតៗ + បេះដូងទន់ៗលើមេឃយប់
                'night-sky-romance':   [['firefly', 0.5], ['heart', 0.35], ['gold', 0.15]],
                // Golden Sunset (green-leaves-minimal) → ចំណុចមាសព្រះអាទិត្យលិចដិតៗ ធ្លាក់ខ្លាំង
                'green-leaves-minimal':[['sunset-gold', 0.75], ['gold', 0.25]]
            };
            var mix = THEME_MIX[particleTheme];

            function pickType() {
                var r = Math.random(), acc = 0;
                for (var i = 0; i < mix.length; i++) {
                    acc += mix[i][1];
                    if (r <= acc) return mix[i][0];
                }
                return mix[0][0];
            }

            var EMOJI = { heart: '❤', petal: '🌸' };
            var LOTUS_OUTER = '#F783B0';
            var LOTUS_INNER = '#FFD6E8';
            var LOTUS_BODY  = '#8A3B5C';
            var FIREFLY_COLOR = '#E9FF7A';
            var FIREFLY_GLOW  = '#BFFF3C';

            var maxFlakes = (particleTheme === 'sweet-blossom') ? 46 : (particleTheme === 'green-leaves-minimal' ? 70 : 55);
            var flakes = [];

            function makeFlake() {
                var type = pickType();
                var size;
                if (type === 'gold') size = Math.random() * 2.5 + 1;
                else if (type === 'sunset-gold') size = Math.random() * 3.4 + 2;
                else if (type === 'firefly') size = Math.random() * 2 + 1.6;
                else if (type === 'butterfly') {
                    // លាយគ្នារវាងតូច និងធំ ដើម្បីមើលទៅមានជម្រៅ
                    size = (Math.random() < 0.5) ? (Math.random() * 6 + 11) : (Math.random() * 12 + 22);
                }
                else if (type === 'petal') {
                    // លាយគ្នារវាងផ្កាតូច និងផ្កាធំ
                    size = (Math.random() < 0.5) ? (Math.random() * 5 + 9) : (Math.random() * 10 + 20);
                }
                else size = Math.random() * 8 + 14; // heart

                var speed;
                if (type === 'butterfly') speed = Math.random() * 0.3 + 0.2;
                else if (type === 'firefly') speed = Math.random() * 0.12 + 0.04;
                else if (type === 'petal') speed = Math.random() * 0.7 + 0.55;
                else if (type === 'sunset-gold' || type === 'gold') speed = Math.random() * 0.9 + 0.55;
                else speed = Math.random() * 0.6 + 0.4; // heart

                return {
                    x: Math.random() * width,
                    y: Math.random() * height,
                    r: Math.random() * 2.4 + 1.2,
                    d: Math.random() * maxFlakes,
                    s: speed,
                    o: Math.random() * 0.5 + 0.45,
                    type: type,
                    size: size,
                    rot: Math.random() * Math.PI * 2,
                    rotSpeed: (Math.random() - 0.5) * (type === 'petal' ? 0.07 : 0.02),
                    flap: Math.random() * Math.PI * 2,
                    blinkSpeed: Math.random() * 0.04 + 0.02
                };
            }

            for (var i = 0; i < maxFlakes; i++) flakes.push(makeFlake());

            // គូរមេអំបៅដោយផ្ទាល់ ដើម្បីអាចប្តូរពណ៌ជាផ្ការឈូក (Lotus Pink)
            function drawButterflyShape(size, flap) {
                var s = size / 22;
                var spread = 0.55 + Math.abs(Math.sin(flap)) * 0.55;
                for (var side = -1; side <= 1; side += 2) {
                    ctx.save();
                    ctx.scale(side, 1);
                    ctx.beginPath();
                    ctx.ellipse(s * 6 * spread, -s * 4.5, s * 6.5, s * 5, -0.45, 0, Math.PI * 2);
                    ctx.fillStyle = LOTUS_OUTER;
                    ctx.fill();
                    ctx.beginPath();
                    ctx.ellipse(s * 5.2 * spread, -s * 4, s * 3.4, s * 2.4, -0.45, 0, Math.PI * 2);
                    ctx.fillStyle = LOTUS_INNER;
                    ctx.fill();
                    ctx.beginPath();
                    ctx.ellipse(s * 4.3 * spread, s * 3.2, s * 4, s * 3.2, -0.15, 0, Math.PI * 2);
                    ctx.fillStyle = LOTUS_OUTER;
                    ctx.globalAlpha *= 0.92;
                    ctx.fill();
                    ctx.restore();
                }
                ctx.beginPath();
                ctx.fillStyle = LOTUS_BODY;
                ctx.ellipse(0, 0, s * 1, s * 6.5, 0, 0, Math.PI * 2);
                ctx.fill();
                ctx.strokeStyle = LOTUS_BODY;
                ctx.lineWidth = Math.max(s * 0.6, 0.6);
                ctx.beginPath();
                ctx.moveTo(0, -s * 5.5);
                ctx.quadraticCurveTo(s * 2.2, -s * 9, s * 3, -s * 10.5);
                ctx.moveTo(0, -s * 5.5);
                ctx.quadraticCurveTo(-s * 2.2, -s * 9, -s * 3, -s * 10.5);
                ctx.stroke();
            }

            function drawFlakes() {
                ctx.clearRect(0, 0, width, height);
                for (var i = 0; i < maxFlakes; i++) {
                    var f = flakes[i];
                    ctx.save();
                    ctx.globalAlpha = f.o;

                    if (f.type === 'gold') {
                        ctx.beginPath();
                        ctx.fillStyle = 'rgba(255,215,0,1)';
                        ctx.shadowBlur = 8;
                        ctx.shadowColor = 'gold';
                        ctx.arc(f.x, f.y, f.r, 0, Math.PI * 2, true);
                        ctx.fill();
                    } else if (f.type === 'sunset-gold') {
                        ctx.beginPath();
                        ctx.fillStyle = 'rgba(255, 158, 66, 1)';
                        ctx.shadowBlur = 12;
                        ctx.shadowColor = '#FF7A00';
                        ctx.arc(f.x, f.y, f.r, 0, Math.PI * 2, true);
                        ctx.fill();
                    } else if (f.type === 'butterfly') {
                        ctx.translate(f.x, f.y);
                        ctx.rotate(f.rot * 0.15 + Math.sin(f.flap) * 0.25);
                        ctx.shadowBlur = 4;
                        ctx.shadowColor = 'rgba(0,0,0,0.2)';
                        drawButterflyShape(f.size, f.flap);
                    } else if (f.type === 'firefly') {
                        // ភ្លឺផ្លែតៗដូចអំពិលអំពេលពិត (Blink ស្រទន់ៗឡើងចុះ)
                        var blink = 0.25 + 0.75 * Math.pow((Math.sin(f.flap) + 1) / 2, 1.6);
                        ctx.globalAlpha = f.o * blink;
                        ctx.beginPath();
                        ctx.fillStyle = FIREFLY_COLOR;
                        ctx.shadowBlur = 10 + blink * 10;
                        ctx.shadowColor = FIREFLY_GLOW;
                        ctx.arc(f.x, f.y, f.r * (0.7 + blink * 0.6), 0, Math.PI * 2, true);
                        ctx.fill();
                    } else {
                        // petal, heart
                        ctx.translate(f.x, f.y);
                        ctx.rotate(f.rot);
                        ctx.font = f.size + "px Arial";
                        ctx.textAlign = 'center';
                        ctx.textBaseline = 'middle';
                        ctx.shadowBlur = f.type === 'heart' ? 5 : 4;
                        ctx.shadowColor = f.type === 'heart' ? 'pink' : 'rgba(255,255,255,0.4)';
                        if (f.type === 'heart') ctx.fillStyle = 'rgba(255, 105, 180, 1)';
                        ctx.fillText(EMOJI[f.type], 0, 0);
                    }
                    ctx.restore();
                }
                moveFlakes();
            }

            var angle = 0;
            function moveFlakes() {
                angle += 0.01;
                for (var i = 0; i < maxFlakes; i++) {
                    var f = flakes[i];

                    if (f.type === 'firefly') {
                        // អំពិលអំពេលមិនធ្លាក់ដូចព្រិលទេ វាហោះវិល ស្រូតៗយឺតៗ
                        f.x += Math.sin(f.flap * 0.6 + f.d) * 0.9;
                        f.y += Math.cos(f.flap * 0.5 + f.d * 0.7) * 0.5 + f.s;
                        f.flap += f.blinkSpeed;

                        if (f.y > height + 20) { f.y = -20; f.x = Math.random() * width; }
                        if (f.y < -20) { f.y = height + 20; }
                        if (f.x > width + 20) f.x = -20;
                        if (f.x < -20) f.x = width + 20;
                        continue;
                    }

                    f.y += f.s;

                    var swing;
                    if (f.type === 'butterfly') swing = 2.2;
                    else if (f.type === 'petal') swing = 1.5;
                    else if (f.type === 'heart') swing = 1.5;
                    else swing = 0.6;

                    f.x += Math.sin(angle + f.d) * swing;
                    f.rot += f.rotSpeed;
                    f.flap += f.type === 'butterfly' ? 0.12 : 0.05;
                    if (f.type === 'butterfly') f.y += Math.sin(f.flap) * 0.5;

                    if (f.y > height + 30) { f.y = -30; f.x = Math.random() * width; }
                    if (f.x > width + 30) f.x = -30;
                    if (f.x < -30) f.x = width + 30;
                }
            }

            function animateSnow() {
                drawFlakes();
                requestAnimationFrame(animateSnow);
            }

            animateSnow();
        } else {
            // ============================================================
            // ANIMATION ដើម៖ សម្រាប់ម៉ូតផ្សេងទៀតទាំងអស់ (មិនប្តូរអ្វីទាំងអស់)
            // ============================================================
            var maxFlakes = 50; 
            var flakes = [];

            for (var i = 0; i < maxFlakes; i++) {
                var typeRandom = Math.random();
                var type = 'snow';
                if (typeRandom < 0.15) type = 'heart';
                else if (typeRandom < 0.30) type = 'gold';

                flakes.push({
                    x: Math.random() * width,
                    y: Math.random() * height,
                    r: Math.random() * 2 + 1,
                    d: Math.random() * maxFlakes,
                    s: Math.random() * 0.5 + 0.3, 
                    o: Math.random() * 0.5 + 0.4, 
                    type: type,
                    size: type === 'heart' ? (Math.random() * 8 + 10) : (Math.random() * 3 + 1)
                });
            }

            var drawFlakes = function () {
                ctx.clearRect(0, 0, width, height);
                
                for (var i = 0; i < maxFlakes; i++) {
                    var f = flakes[i];
                    ctx.beginPath();
                    
                    if (f.type === 'heart') {
                        ctx.font = f.size + "px Arial";
                        ctx.fillStyle = "rgba(255, 105, 180, " + f.o + ")"; 
                        ctx.shadowBlur = 5;
                        ctx.shadowColor = "pink"; 
                        ctx.fillText("❤", f.x, f.y);
                    } else if (f.type === 'gold') {
                        ctx.fillStyle = "rgba(255, 215, 0, " + f.o + ")";
                        ctx.shadowBlur = 8;
                        ctx.shadowColor = "gold"; 
                        ctx.arc(f.x, f.y, f.r, 0, Math.PI * 2, true);
                        ctx.fill();
                    } else {
                        ctx.fillStyle = "rgba(255, 255, 255, " + f.o + ")";
                        ctx.shadowBlur = 4;
                        ctx.shadowColor = "white";
                        ctx.arc(f.x, f.y, f.r, 0, Math.PI * 2, true);
                        ctx.fill();
                    }
                }
                moveFlakes();
            };

            var angle = 0;
            var moveFlakes = function () {
                angle += 0.01;
                for (var i = 0; i < maxFlakes; i++) {
                    var f = flakes[i];
                    f.y += f.s; 
                    var swing = f.type === 'heart' ? 1.5 : 0.5;
                    f.x += Math.sin(angle + f.d) * swing; 

                    if (f.y > height) {
                        flakes[i].y = -20;
                        flakes[i].x = Math.random() * width;
                    }
                }
            };

            var animateSnow = function () {
                drawFlakes();
                requestAnimationFrame(animateSnow);
            };

            animateSnow();
        }

        window.addEventListener("resize", function () {
            width = window.innerWidth;
            height = window.innerHeight;
            canvas.width = width;
            canvas.height = height;
        });
    })();

    window.submitRSVP = async () => {
        if(!db) { alert("Database connection unavailable."); return; }
        if(!eventId || eventId.startsWith('{{')) { alert("Demo Mode: RSVP not saved."); return; }
        
        const nameVal = document.getElementById('rsvp-name').value;
        if(!nameVal) { alert("Please enter your name (សូមបញ្ចូលឈ្មោះរបស់អ្នក)."); return; }
        
        const attendance = document.querySelector('input[name="attendance"]:checked').value;
        const guests = document.getElementById('guest-count').value;
        const btn = document.getElementById('btn-rsvp');
        
        btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Saving...';
        try {
             await addDoc(collection(db, "events", eventId, "rsvp"), {
                name: nameVal, attendance, guests, createdAt: serverTimestamp(), userAgent: navigator.userAgent
             });
             alert("Thank you! Your RSVP has been sent.");
             btn.innerHTML = '<i class="fa-solid fa-check-circle"></i> Sent!';
        } catch(e) {
            console.error(e);
            alert("Error sending RSVP");
            btn.innerHTML = '<i class="fa-solid fa-check-circle"></i> យល់ព្រម (Confirm)';
        }
    };

    const wishListEl = document.getElementById('wishes-list');
    
    if(db && eventId && !eventId.startsWith('{{')) {
        try {
            const q = query(collection(db, "events", eventId, "wishes"), orderBy("createdAt", "desc"));
            onSnapshot(q, (snapshot) => {
                wishListEl.innerHTML = '';
                if(snapshot.empty) {
                    renderMockWishes();
                } else {
                    snapshot.forEach((doc) => {
                        const data = doc.data();
                        const div = document.createElement('div');
                        div.className = 'wish-item';
// ១. បង្កើតឈ្មោះ (Name) ដោយសុវត្ថិភាព
                    const nameDiv = document.createElement('div');
                    nameDiv.className = 'wish-name';
                    nameDiv.textContent = data.name || 'Anonymous'; 

                    // ២. បង្កើតសារ (Message) ដោយសុវត្ថិភាព (ការពារ XSS)
                    const msgDiv = document.createElement('div');
                    msgDiv.className = 'wish-msg';
                    msgDiv.textContent = data.message || ''; 

                    // ៣. គណនាកាលបរិច្ឆេទ (Date Logic)
                    let dateStr = "";
                    if(data.createdAt) {
                        try {
                            const d = data.createdAt.toDate();
                            dateStr = d.toLocaleDateString() + ' | ' + d.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
                        } catch(e) {}
                    }
                    const dateDiv = document.createElement('div');
                    dateDiv.className = 'wish-date';
                    dateDiv.textContent = dateStr;

                    // ៤. បញ្ចូលចូលគ្នា (Append)
                    div.appendChild(nameDiv);
                    div.appendChild(msgDiv);
                    div.appendChild(dateDiv);

                    // បន្ថែមបន្ទាត់ខាងក្រោមនេះ ដើម្បីឱ្យវាបង្ហាញចេញលើអេក្រង់
                    wishListEl.appendChild(div); 
                }); // បិទ forEach
            } // បិទ else
        }); // បិទ onSnapshot
    } catch(e) {
            console.warn("Error loading wishes", e); 
            renderMockWishes();
        }
    } else {
        renderMockWishes();
    }

    window.submitWish = async () => {
        if(!db) { alert("Database connection unavailable."); return; }
        if(!eventId || eventId.startsWith('{{')) { alert("Demo Mode: Wish not saved."); return; }
        const nameInput = document.getElementById('w-name');
        const msgInput = document.getElementById('w-message');
        const name = nameInput.value.trim();
        const msg = msgInput.value.trim();
        
        if (!name || !msg) { alert("Please enter your name and message."); return; }
        
        const btn = document.getElementById('btn-wish');
        btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Sending...';
        
        try {
            await addDoc(collection(db, "events", eventId, "wishes"), {
                name, message: msg, createdAt: serverTimestamp()
            });
            nameInput.value = '';
            msgInput.value = '';
            btn.innerHTML = '<i class="fa-solid fa-paper-plane"></i> ផ្ញើរសារជូនពរ';
        } catch(e) {
            console.error(e);
            alert("Error sending wish");
             btn.innerHTML = '<i class="fa-solid fa-paper-plane"></i> ផ្ញើរសារជូនពរ';
        }
    };
  ${'</'+'script>'}
</body>
</html>
`;

// ===========================================
// ១. បង្កើតធៀបភ្ជាប់ពាក្យ (Engagement Template)
// ===========================================
const ENGAGEMENT_TEMPLATE = CLASSIC_GOLD_TEMPLATE
    .replace(/សិរីមង្គលអាពាហ៍ពិពាហ៍/g, "ពិធីភ្ជាប់ពាក្យ")
    .replace(/Wedding Celebration/g, "Engagement Ceremony")
    .replace(/Wedding Invitation/g, "Engagement Ceremony");

// ===========================================
// ២. បង្កើតធៀបខួបកំណើត (Birthday Template)
// ===========================================
const BIRTHDAY_TEMPLATE = CLASSIC_GOLD_TEMPLATE
    .replace(/សិរីមង្គលអាពាហ៍ពិពាហ៍/g, "ពិធីខួបកំណើត")
    .replace(/Wedding Celebration/g, "Birthday Celebration")
    .replace(/Wedding Invitation/g, "Birthday Invitation")
    .replace('ឪពុកម្ដាយខាងកូនកំលោះ', 'មាតាបិតា') 
    .replace('<div class="parent-block animate-box anim-right-left"><div class="p-label">ឪពុកម្ដាយខាងកូនក្រមុំ</div><div class="p-name">លោក {{BRIDE_FATHER}}</div><div class="p-name">អ្នកស្រី {{BRIDE_MOTHER}}</div></div>', '') 
    .replace('<div class="couple-names animate-box anim-top-bottom"><div class="c-name">{{GROOM}}</div><div class="c-amp anim-highlight">&</div><div class="c-name">{{BRIDE}}</div></div>', 
             '<div class="couple-names animate-box anim-top-bottom"><div class="c-name" style="font-size:3rem">{{GROOM}}</div></div>')
    .replace('<div class="cover-names anim-highlight"><div style="display:block">{{GROOM_EN}}</div><div style="font-size:1.3rem; margin:8px 0; letter-spacing:4px;">✦ 💛 ✦</div><div style="display:block">{{BRIDE_EN}}</div></div>', 
             '<div class="cover-names anim-highlight"><div style="display:block">{{GROOM_EN}}</div></div>');

// ===========================================
// ៣. បង្កើតធៀបសិរីសួស្ដីពិធីបង្កក់កូន (Baby Ceremony Template)
// ===========================================
const BABY_CEREMONY_TEMPLATE = BIRTHDAY_TEMPLATE
    .replace(/ពិធីខួបកំណើត/g, "សិរីសួស្ដីពិធីបង្កក់កូន")
    .replace(/Birthday Celebration/g, "Baby Blessing Ceremony")
    .replace(/Birthday Invitation/g, "Baby Blessing Invitation")
    .replace(/ម្ចាស់ខួប/g, "បុត្រា/បុត្រី")
    .replace(/Birthday Owner/g, "Baby Name")
    .replace(/Happy Birthday/g, "Baby Blessing Day");

// ===========================================
// ៤. បង្កើតធៀបឡើងគេហដ្ឋាន (Housewarming Template)
// ===========================================
const HOUSEWARMING_TEMPLATE = CLASSIC_GOLD_TEMPLATE
    .replace(/សិរីមង្គលអាពាហ៍ពិពាហ៍/g, "ពិធីឡើងគេហដ្ឋាន")
    .replace(/Wedding Celebration/g, "Housewarming Ceremony")
    .replace(/Wedding Invitation/g, "Housewarming Ceremony")
    .replace('<div class="parents-row">', '<div class="parents-row" style="display:none">');




// --- កូដថ្មីត្រូវកែ (New Code) ---
const SWEET_BLOSSOM_BASE = CLASSIC_GOLD_TEMPLATE
    .replace('Wedding Invitation', 'Sweet Blossom Invitation')
    .replace(
        '<div style="font-size:1.3rem; margin:8px 0; letter-spacing:4px;">✦ 💛 ✦</div>',
        `<div class="heart-divider-blossom">
            <span class="line"></span>
            <span class="heart-icon">💞</span>
            <span class="line"></span>
        </div>`
    )
    .replace('</style>', `
    .heart-divider-blossom {
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
        gap: 8px !important;
        margin: 8px 0 !important;
    }
    .heart-divider-blossom .line {
        width: 30px !important;
        height: 2px !important;
        background-color: #FF69B4 !important;
        display: inline-block !important;
    }
    .heart-divider-blossom .heart-icon {
        font-size: 1.2rem !important;
        line-height: 1 !important;
        display: inline-block !important;
        filter: drop-shadow(0 0 6px #FF69B4) !important;
        animation: heartbeat 1.4s ease-in-out infinite !important;
    }
    @keyframes heartbeat {
        0% { transform: scale(1); }
        14% { transform: scale(1.3); }
        28% { transform: scale(1); }
        42% { transform: scale(1.3); }
        70% { transform: scale(1); }
    }
</style>`);

const NIGHT_SKY_ROMANCE_BASE = CLASSIC_GOLD_TEMPLATE
    .replace('Wedding Invitation', 'Night Sky Romance Invitation')
    .replace(
        '<div style="font-size:1.3rem; margin:8px 0; letter-spacing:4px;">✦ 💛 ✦</div>',
        `<div class="heart-divider">
            <span class="line"></span>
            <i class="fa-solid fa-heart heart-icon"></i>
            <span class="line"></span>
        </div>`
    )
    .replace('</style>', `
    .cover-names > div:first-child {
        transform: translateX(-30px) !important;
    }
    .cover-names > div:last-child {
        transform: translateX(30px) !important;
    }
    .heart-divider {
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
        gap: 8px !important;
        margin: 8px 0 !important;
    }
    .heart-divider .line {
        width: 30px !important;
        height: 2px !important;
        background-color: #FF69B4 !important;
        display: inline-block !important;
    }
    .heart-divider .heart-icon {
        font-size: 1.1rem !important;
        color: #FF69B4 !important;
        display: inline-block !important;
        filter: drop-shadow(0 0 6px #FF69B4) !important;
        animation: heartbeat 1.4s ease-in-out infinite !important;
    }
    @keyframes heartbeat {
        0% { transform: scale(1); }
        14% { transform: scale(1.3); }
        28% { transform: scale(1); }
        42% { transform: scale(1.3); }
        70% { transform: scale(1); }
    }
</style>`);

const GREEN_LEAVES_MINIMAL_BASE = CLASSIC_GOLD_TEMPLATE
    .replace('Wedding Invitation', 'GOLDEN SUNSET Invitation')
    .replace(
        '<div style="font-size:1.3rem; margin:8px 0; letter-spacing:4px;">✦ 💛 ✦</div>',
        `<div class="heart-divider-sunset">
            <span class="line"></span>
            <span class="heart-icon">👩‍❤️‍👨</span>
            <span class="line"></span>
        </div>`
    )
    .replace('</style>', `
    /* កំណត់អក្សរ Wedding Invitation ឱ្យធំ និងច្បាស់ជាងមុន */
    #invite-cover .cover-title {
        font-family: 'Great Vibes', cursive !important;
        font-size: 2.8rem !important; /* ដំឡើងទំហំពី 2.3rem ទៅ 2.8rem */
        font-weight: normal !important;
        -webkit-text-stroke: 0.5px #FFD700 !important;
        text-shadow: 0 0 15px rgba(255, 215, 0, 0.7), 0 2px 4px rgba(0,0,0,0.9) !important;
        line-height: 1.4 !important;
        margin-bottom: 20px !important;
    }

    .heart-divider-sunset {
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
        gap: 8px !important;
        margin: 8px 0 !important;
    }
    .heart-divider-sunset .line {
        width: 30px !important;
        height: 2px !important;
        background-color: #FF69B4 !important;
        display: inline-block !important;
    }
    .heart-divider-sunset .heart-icon {
        font-size: 1.3rem !important;
        line-height: 1 !important;
        display: inline-block !important;
        filter: drop-shadow(0 0 6px #FF69B4) !important;
        animation: heartbeat 1.4s ease-in-out infinite !important;
    }
    @keyframes heartbeat {
        0% { transform: scale(1); }
        14% { transform: scale(1.3); }
        28% { transform: scale(1); }
        42% { transform: scale(1.3); }
        70% { transform: scale(1); }
    }
</style>`);

// ===========================================================
// ចំណងជើងអង់គ្លេស "Wedding Invitation" សម្រាប់ធៀបអាពាហ៍ពិពាហ៍ ៣ ម៉ូត
// (SWEET BLOSSOM / NIGHT SKY ROMANCE / GOLDEN SUNSET)
// - ប្ដូរ Font ទាំងក្រប (Cover) និងសាច់ខាងក្នុង ឲ្យស្អាតដូចគ្នា
// - លុបអក្សរ "Wedding Celebration" ចេញ
// ===========================================================
const WEDDING_EN_TITLE_CSS = `
    /* --- ចំណងជើង Wedding Invitation (ក្រប + ខាងក្នុង) --- */
    #invite-cover .cover-title,
    .container #section-home .main-title {
        font-family: 'Great Vibes', cursive !important;
        font-style: normal !important;
        font-weight: normal !important;
        color: #FFD700 !important;
        -webkit-text-stroke: 0.5px #FFD700 !important;
        text-shadow: 0 0 18px rgba(255, 215, 0, 0.75), 0 2px 4px rgba(0,0,0,0.9) !important;
        line-height: 1.45 !important;
        letter-spacing: 1px !important;
        overflow: visible !important;
        height: auto !important;
    }
    #invite-cover .cover-title {
        font-size: 2.8rem !important;
        padding-top: 0 !important;
        margin-top: 0 !important;
        margin-bottom: 22px !important;
    }
    .container #section-home .main-title {
        font-size: 2.6rem !important;
        padding-top: 10px !important;
        padding-bottom: 5px !important;
        margin-bottom: 20px !important;
    }
    @media (max-width: 420px) {
        #invite-cover .cover-title { font-size: 2.4rem !important; }
        .container #section-home .main-title { font-size: 2.2rem !important; }
    }
    .section-divider-emoji {
        font-size: 1.2rem !important;
        letter-spacing: 4px !important;
        color: #FFD700 !important;
        text-shadow: 0 0 10px rgba(255, 215, 0, 0.6), 0 2px 4px rgba(0,0,0,0.8) !important;
        margin-bottom: 15px !important;
        margin-top: -5px !important;
        display: block;
        animation: heartbeat 2.4s ease-in-out infinite;
    }
`;

const applyWeddingEnTitle = (tpl) => tpl
    // លុបអក្សរ "Wedding Celebration" ចេញពីសាច់ធៀប
    .replace('<div class="section-subtitle">Wedding Celebration</div>', '<div class="section-divider-emoji">✦ 💛 ✦</div>')
    // សម្អាតចំណងជើង Tab របស់ Browser
    .replace(/\s*\|\s*សិរីមង្គលអាពាហ៍ពិពាហ៍/g, '')
    // បញ្ចូល CSS ចំណងជើងថ្មី
    .replace('</style>', WEDDING_EN_TITLE_CSS + '\n</style>');

const SWEET_BLOSSOM_TEMPLATE      = applyWeddingEnTitle(SWEET_BLOSSOM_BASE);
const NIGHT_SKY_ROMANCE_TEMPLATE  = applyWeddingEnTitle(NIGHT_SKY_ROMANCE_BASE);
const GREEN_LEAVES_MINIMAL_TEMPLATE = applyWeddingEnTitle(GREEN_LEAVES_MINIMAL_BASE);

// ===========================================================
// ប្ដូរសញ្ញា ✦ 💛 ✦ ចន្លោះឈ្មោះកូនកំលោះ-កូនក្រមុំ
// ទៅជា Emoji លោត (Jumping Emoji) សម្រាប់ម៉ូត ៣ ខាងលើ
// CLASSIC GOLD = 💞 | ROYAL GOLD = ❤️ | LUXURY GOLD = 👩‍❤️‍👨
// ===========================================================
const GOLD_EMOJI_DIVIDER_CSS = `
    .heart-divider-gold {
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
        gap: 10px !important;
        margin: 8px 0 !important;
    }
    .heart-divider-gold .line {
        width: 30px !important;
        height: 2px !important;
        background: linear-gradient(90deg, rgba(255,215,0,0), #FFD700) !important;
        display: inline-block !important;
        box-shadow: 0 0 6px rgba(255, 215, 0, 0.8) !important;
    }
    .heart-divider-gold .line:last-child {
        background: linear-gradient(90deg, #FFD700, rgba(255,215,0,0)) !important;
    }
    .heart-divider-gold .heart-icon {
        font-size: 1.4rem !important;
        line-height: 1 !important;
        display: inline-block !important;
        filter: drop-shadow(0 0 8px #FFD700) !important;
        animation: emoji-jump 1.2s ease-in-out infinite !important;
    }
    @keyframes emoji-jump {
        0%   { transform: translateY(0) scale(1); }
        20%  { transform: translateY(-10px) scale(1.25); }
        40%  { transform: translateY(0) scale(1); }
        55%  { transform: translateY(-5px) scale(1.12); }
        70%  { transform: translateY(0) scale(1); }
        100% { transform: translateY(0) scale(1); }
    }
`;

const applyGoldEmojiDivider = (emoji) => CLASSIC_GOLD_TEMPLATE
    .replace(
        '<div style="font-size:1.3rem; margin:8px 0; letter-spacing:4px;">✦ 💛 ✦</div>',
        `<div class="heart-divider-gold">
            <span class="line"></span>
            <span class="heart-icon">${emoji}</span>
            <span class="line"></span>
        </div>`
    )
    .replace('</style>', GOLD_EMOJI_DIVIDER_CSS + '\n</style>');

const CLASSIC_GOLD_WEDDING_TEMPLATE = applyGoldEmojiDivider('💞');
const ROYAL_GOLD_TEMPLATE           = applyGoldEmojiDivider('❤️');
const LUXURY_GOLD_TEMPLATE          = applyGoldEmojiDivider('👩‍❤️‍👨');

// ===========================================================
// 🌅 INTRO ANIMATION (Cinematic Intro) — សម្រាប់ម៉ូតធៀបបែប Premium
// ចុច "សូមចុចដើម្បីមើលសំបុត្រ" → ចាក់ Intro (ព្រះអាទិត្យរះ + ឈ្មោះកូនកំលោះ/កូនក្រមុំ) → បើកសំបុត្រ
// រចនាបថ Intro ត្រូវបានជ្រើសតាម id របស់ម៉ូត (មើល INTRO_STYLE ក្នុង applyCeremonyRules):
//   id មានពាក្យ 'royal' → royal | 'luxury' → luxury | ផ្សេងទៀត → sunrise
// ===========================================================
const INTRO_CSS = `    /* ===== INTRO ANIMATION (cinematic sunrise, GPU-friendly: only opacity/transform are animated) ===== */
    #intro-anim { position: fixed; top:0; left:0; width:100%; height:100%; z-index: 20500; display:flex; flex-direction:column; align-items:center; justify-content:flex-start; text-align:center; padding:24px; overflow:hidden; background:#070512; contain: layout paint; opacity:0; visibility:hidden; pointer-events:none; transition: opacity .9s ease, visibility .9s ease; -webkit-tap-highlight-color: transparent; }
    #intro-anim.show { opacity:1; visibility:visible; pointer-events:auto; }
    #intro-anim.hide { opacity:0; visibility:hidden; pointer-events:none; }
    #intro-anim .abs { position:absolute; pointer-events:none; backface-visibility:hidden; }
    .i-sky { top:0; left:0; width:100%; height:100%; z-index:0; background: linear-gradient(to bottom, #3a1650 0%, #6a2660 35%, #b8436a 62%, #ee7a3a 82%, #ffb347 93%, #ffd98a 100%); }
    .i-night { top:0; left:0; width:100%; height:100%; z-index:0; background: linear-gradient(to bottom, #070512 0%, #100a2b 45%, #1d1040 75%, #3a1650 100%); will-change:opacity; }
    .i-stars { top:0; left:0; width:100%; height:65%; z-index:1; will-change:opacity; }
    .i-star { position:absolute; width:2px; height:2px; border-radius:50%; background:#fff; animation: iTwinkle 2s ease-in-out infinite; }
    .i-horizon { left:0; bottom:0; width:100%; height:60%; z-index:1; background: radial-gradient(ellipse at 50% 100%, rgba(255,190,90,.85) 0%, rgba(255,120,40,.45) 35%, rgba(255,90,40,0) 70%); opacity:0; will-change:opacity; }
    .i-glow { left:50%; top:24%; width:120vmax; height:120vmax; margin:-60vmax 0 0 -60vmax; z-index:1; background: radial-gradient(circle, rgba(255,230,140,.8) 0%, rgba(255,170,60,.35) 15%, rgba(255,120,20,0) 40%); opacity:0; transform:scale(.2); will-change:transform,opacity; }
    .i-rays { left:50%; top:24%; width:130vmax; height:130vmax; margin:-65vmax 0 0 -65vmax; z-index:1; opacity:0; background: repeating-conic-gradient(from 0deg, rgba(255,230,150,.30) 0deg 4deg, rgba(255,230,150,0) 4deg 12deg); -webkit-mask-image: radial-gradient(circle, #000 0%, transparent 38%); mask-image: radial-gradient(circle, #000 0%, transparent 38%); will-change:transform,opacity; }
    .i-cloud { z-index:1; height:46px; border-radius:50%; background: radial-gradient(ellipse at center, rgba(255,190,150,.8) 0%, rgba(214,100,120,.5) 45%, rgba(214,100,120,0) 72%); opacity:0; will-change:transform,opacity; }
    .i-cloud.c1 { top:29%; left:-10%; width:60vw; } .i-cloud.c2 { top:39%; right:-15%; width:75vw; height:56px; } .i-cloud.c3 { top:18%; left:35%; width:50vw; height:36px; }
    .i-sun { left:50%; top:24%; width:180px; height:180px; margin:-90px 0 0 -90px; z-index:2; border-radius:50%; border:4px solid #FFD54A; background: radial-gradient(circle at 45% 40%, #ffb347 0%, #ff6a1a 70%, #d63a00 100%); box-shadow: 0 0 40px 15px rgba(255,190,70,.75), 0 0 130px 55px rgba(255,120,0,.5); transform: translateY(66vh) scale(.8); opacity:0; overflow:hidden; will-change:transform,opacity; }
    .i-sun::after { content:''; position:absolute; inset:0; border-radius:50%; z-index:1; background: radial-gradient(circle at 45% 40%, #fffbe0 0%, #ffe066 55%, #ffc21a 100%); opacity:0; }
    .i-sun::before { content:''; position:absolute; top:0; left:0; width:100%; height:100%; border-radius:50%; z-index:3; background: radial-gradient(circle, rgba(255,240,180,0) 55%, rgba(255,200,80,.45) 100%); }
    .i-sun-photo { position:absolute; top:0; left:0; width:100%; height:100%; border-radius:50%; z-index:2; background: url('{{COVER_IMAGE}}') no-repeat center 20%; background-size:cover; opacity:0; box-shadow: inset 0 0 30px 8px rgba(255,150,30,.65); }
    .i-flare { z-index:2; border-radius:50%; opacity:0; will-change:opacity; }
    .i-flare.f1 { left:calc(50% + 90px); top:calc(24% + 110px); width:26px; height:26px; background: radial-gradient(circle, rgba(255,255,255,.6), rgba(255,200,80,0)); }
    .i-flare.f2 { left:calc(50% + 135px); top:calc(24% + 190px); width:46px; height:46px; background: radial-gradient(circle, rgba(255,140,200,.4), rgba(255,140,200,0)); }
    .i-flare.f3 { left:calc(50% - 120px); top:calc(24% - 110px); width:34px; height:34px; background: radial-gradient(circle, rgba(255,240,170,.5), rgba(255,220,100,0)); }
    .i-hills { left:0; bottom:0; width:100%; height:24vh; z-index:4; }
    .i-hills svg { position:absolute; bottom:0; left:0; width:100%; height:100%; display:block; }
    .i-bird { z-index:4; top:22%; left:-40px; width:26px; opacity:0; will-change:transform,opacity; }
    .i-bird.b2 { top:30%; width:20px; } .i-bird.b3 { top:16%; width:16px; }
    .i-spark { position:absolute; bottom:-20px; color:#FFE38A; opacity:0; z-index:4; animation: introFloat linear infinite; pointer-events:none; will-change:transform,opacity; }
    .intro-content { position:relative; z-index:5; margin-top: calc(24vh + 105px); }
    @media (max-height: 660px) { .i-sun { width:150px; height:150px; margin:-75px 0 0 -75px; } .intro-content { margin-top: calc(24vh + 85px); } .intro-nm { font-size:2.2rem; } .intro-title { font-size:1.1rem; } .intro-date { font-size:.95rem; } .intro-line { margin:10px auto 8px; } }
    .intro-title { font-family:'Moul', cursive; color:#FFD700; font-size:1.25rem; line-height:1.8; text-shadow: 0 0 15px rgba(255,215,0,.7), 0 2px 6px rgba(0,0,0,.95); opacity:0; transform: translateY(25px); }
    .intro-nm { font-family:'Great Vibes', cursive; color:#fff; font-size:2.6rem; line-height:1.4; text-shadow: 0 0 18px rgba(255,215,0,.7), 0 2px 6px rgba(0,0,0,.95); opacity:0; transform: translateY(25px); }
    .intro-amp { font-size:1.5rem; color:#FF69B4; margin:2px 0; opacity:0; transform: scale(0); text-shadow:0 0 14px rgba(255,105,180,.9); }
    .intro-line { width:70%; height:2px; margin:16px auto 14px; background: linear-gradient(90deg, transparent, #FFD700, transparent); transform: scaleX(0); }
    .intro-date { font-family:'Moul', cursive; color:#fff3c4; font-size:1.05rem; line-height:1.8; text-shadow: 0 0 12px rgba(0,0,0,.9), 0 2px 5px rgba(0,0,0,.95); opacity:0; transform: translateY(25px); }
    .intro-skip { position:absolute; bottom:14px; left:0; right:0; z-index:6; color:rgba(255,255,255,.6); font-size:.72rem; letter-spacing:2px; }

    /* Lite mode for weak devices / slow connections: fewer layers */
    #intro-anim.lite .i-rays, #intro-anim.lite .i-flare, #intro-anim.lite .i-stars, #intro-anim.lite .i-spark { display:none; }
    #intro-anim.lite svg.i-bird:nth-of-type(n+6) { display:none; }

    /* Timeline part 1: starts on click */
    #intro-anim.show .i-night { animation: iNight 5s ease-in-out .1s forwards; }
    #intro-anim.show .i-stars { animation: iFadeOut 3s ease-in .6s forwards; }
    #intro-anim.show .i-horizon { animation: introFade 3.5s ease-out .4s forwards; }
    #intro-anim.show .i-sun { animation: iRise 3.6s cubic-bezier(.25,.7,.3,1) .3s forwards, iPulse 3s ease-in-out 4s infinite; }
    #intro-anim.show .i-sun::after { animation: introFade 3s ease-in .6s forwards; }
    #intro-anim.show .i-glow { animation: iGlow 3s ease-out 1s forwards; }
    #intro-anim.show .i-rays { animation: introFade 2s ease-out 1.8s forwards, iSpin 45s linear 1.8s infinite; }
    #intro-anim.show .i-cloud.c1 { animation: introFade 2s ease-out 1.3s forwards, iDrift 14s ease-in-out 1.3s infinite alternate; }
    #intro-anim.show .i-cloud.c2 { animation: introFade 2s ease-out 1.8s forwards, iDrift2 16s ease-in-out 1.8s infinite alternate; }
    #intro-anim.show .i-cloud.c3 { animation: introFade 2s ease-out 2.2s forwards, iDrift 12s ease-in-out 2.2s infinite alternate; }
    #intro-anim.show .i-flare { animation: iFlare 3s ease-in-out 3.2s infinite alternate; }
    #intro-anim.show .i-bird { animation: iBird 9s linear 4.5s infinite, iFlap .5s ease-in-out infinite alternate; }
    #intro-anim.show .i-bird.b2 { animation-delay: 5.5s, 0s; } #intro-anim.show .i-bird.b3 { animation-delay: 6.3s, 0s; }
    /* Photo fades in once the image is really loaded */
    #intro-anim.ph-on .i-sun-photo { animation: introFade 1.6s ease-in forwards; }
    /* Timeline part 2: text starts once fonts are ready */
    #intro-anim.txt-on .intro-title { animation: introText 1.1s ease-out 0s forwards; }
    #intro-anim.txt-on .intro-nm.g { animation: introText 1.1s ease-out .8s forwards; }
    #intro-anim.txt-on .intro-amp { animation: introPop .8s cubic-bezier(.34,1.56,.64,1) 1.5s forwards, introBeat 1.2s ease-in-out 2.4s infinite; }
    #intro-anim.txt-on .intro-nm.b { animation: introText 1.1s ease-out 1.8s forwards; }
    #intro-anim.txt-on .intro-line { animation: introLine 1s ease-out 2.6s forwards; }
    #intro-anim.txt-on .intro-date { animation: introText 1.1s ease-out 2.9s forwards; }

    @keyframes iNight { to { opacity:0; } }
    @keyframes iFadeOut { to { opacity:0; } }
    @keyframes iTwinkle { 0%,100% { opacity:.25; } 50% { opacity:1; } }
    @keyframes iRise { 0% { opacity:0; transform: translateY(66vh) scale(.8); } 10% { opacity:1; } 100% { opacity:1; transform: translateY(0) scale(1); } }
    @keyframes iPulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.06); } }
    @keyframes iGlow { to { opacity:1; transform: scale(1); } }
    @keyframes iSpin { to { transform: rotate(360deg); } }
    @keyframes iDrift { from { transform: translateX(-4vw); } to { transform: translateX(8vw); } }
    @keyframes iDrift2 { from { transform: translateX(6vw); } to { transform: translateX(-8vw); } }
    @keyframes iFlare { from { opacity:.15; } to { opacity:.9; } }
    @keyframes iBird { 0% { opacity:0; transform: translate(0,0); } 8% { opacity:.9; } 92% { opacity:.9; } 100% { opacity:0; transform: translate(115vw,-40px); } }
    @keyframes iFlap { from { scale:1 1; } to { scale:1 .55; } }
    @keyframes introFade { to { opacity:1; } }
    @keyframes introText { to { opacity:1; transform: translateY(0); } }
    @keyframes introPop { to { opacity:1; transform: scale(1); } }
    @keyframes introBeat { 0%,100% { transform: scale(1); } 50% { transform: scale(1.3); } }
    @keyframes introLine { to { transform: scaleX(1); } }
    @keyframes introFloat { 0% { transform: translateY(0) scale(.6); opacity:0; } 15% { opacity:.9; } 100% { transform: translateY(-110vh) scale(1.1); opacity:0; } }
    @media (prefers-reduced-motion: reduce) { #intro-anim * { animation-duration: .01s !important; } }
    /* ===== INTRO STYLES: one look per template (sun/moon shape, sky, motion) ===== */
    #intro-anim[data-intro]{--s:1;--w:180px;--h:180px;--br:50%;--shape:none;--an:iRise;--bd:4px solid #FFD54A;--ring:none;--ry:rgba(255,230,150,.3);--tt:#FFD700;--ac:#FF69B4}
    @media (max-height:660px){#intro-anim[data-intro]{--s:.83}}
    #intro-anim:is([data-intro=luxury],[data-intro=blossom],[data-intro=love],[data-intro=house],[data-intro=baby],[data-intro=party]){--br:0;--bd:0 solid transparent;--bs:none}
    #intro-anim[data-intro] .i-sky{background:var(--sky)}
    #intro-anim[data-intro] .i-night{background:var(--nt)}
    #intro-anim[data-intro] .i-horizon{background:radial-gradient(ellipse at 50% 100%,var(--hz),transparent 70%)}
    #intro-anim[data-intro] .i-glow{background:radial-gradient(circle,var(--gl),transparent 40%)}
    #intro-anim[data-intro] .i-rays{background:repeating-conic-gradient(from 0deg,var(--ry) 0 4deg,transparent 4deg 12deg)}
    #intro-anim[data-intro] .i-cloud{filter:hue-rotate(var(--hr,0deg))}
    #intro-anim[data-intro] .i-hills path:first-child{fill:var(--h1)}
    #intro-anim[data-intro] .i-hills path:last-child{fill:var(--h2)}
    #intro-anim[data-intro] .i-sun{width:calc(var(--w)*var(--s));height:calc(var(--h)*var(--s));margin:calc(var(--h)*var(--s)/-2) 0 0 calc(var(--w)*var(--s)/-2);border:var(--bd);background:var(--bg);box-shadow:var(--bs);-webkit-mask:var(--shape) center/100% 100% no-repeat;mask:var(--shape) center/100% 100% no-repeat}
    #intro-anim[data-intro] .i-sun,#intro-anim[data-intro] .i-sun-photo,#intro-anim[data-intro] .i-sun::before,#intro-anim[data-intro] .i-sun::after{border-radius:var(--br)}
    #intro-anim[data-intro] .i-sun::after{background:var(--bg2,var(--bg))}
    #intro-anim[data-intro] .i-sun::before{box-shadow:var(--ring)}
    #intro-anim.show[data-intro] .i-sun{animation:var(--an) 3.6s cubic-bezier(.25,.7,.3,1) .3s forwards,iPulse 3s ease-in-out 4s infinite}
    #intro-anim[data-intro] .intro-content{margin-top:calc(24vh + var(--h)*var(--s)/2 + 15px)}
    #intro-anim[data-intro] .intro-title{color:var(--tt)}
    #intro-anim[data-intro] .intro-amp{color:var(--ac);text-shadow:0 0 14px var(--ac)}
    #intro-anim[data-intro] .intro-line{background:linear-gradient(90deg,transparent,var(--tt),transparent)}
    #intro-anim[data-intro] .i-spark{color:var(--tt)}
    #intro-anim:is([data-intro=luxury],[data-intro=love],[data-intro=party],[data-intro=blossom],[data-intro=baby]) .i-bird,#intro-anim[data-intro=luxury] .i-cloud{display:none}
    #intro-anim.show[data-intro=night] .i-stars{animation:none}
    @keyframes iDrop{0%{opacity:0;transform:translateY(-45vh) scale(.5)}60%{opacity:1;transform:translateY(4vh) scale(1.08)}80%{transform:translateY(-1.5vh) scale(.98)}100%{opacity:1;transform:none}}
    @keyframes iZoom{0%{opacity:0;transform:scale(.3) rotate(-25deg)}100%{opacity:1;transform:none}}
    @keyframes iSet{0%{opacity:0;transform:translateY(-30vh) scale(1.15)}25%{opacity:1}100%{opacity:1;transform:none}}
    #intro-anim[data-intro=sunrise]{--sky:linear-gradient(#3a1650,#6a2660 35%,#b8436a 62%,#ee7a3a 82%,#ffb347 93%,#ffd98a);--nt:linear-gradient(#070512,#100a2b 45%,#1d1040 75%,#3a1650);--hz:rgba(255,190,90,.85);--gl:rgba(255,215,120,.75);--h1:#3a1236;--h2:#160819;--bg:radial-gradient(circle at 45% 40%,#ffb347,#ff6a1a 70%,#d63a00);--bg2:radial-gradient(circle at 45% 40%,#fffbe0,#ffe066 55%,#ffc21a);--bs:0 0 40px 15px rgba(255,190,70,.75),0 0 130px 55px rgba(255,120,0,.5)}
    #intro-anim[data-intro=royal]{--sky:linear-gradient(#2a0a0a,#6e1414 42%,#c4632a 78%,#ffd27a);--nt:linear-gradient(#0d0202,#2a0808 60%,#4a1010);--hz:rgba(255,170,60,.85);--gl:rgba(255,200,90,.75);--h1:#3b0d0d;--h2:#140406;--w:160px;--h:210px;--br:50% 50% 6px 6px/40% 40% 6px 6px;--bd:5px double #FFD54A;--bg:radial-gradient(circle at 50% 35%,#ffcf6a,#e08a1e 70%,#9a4a00);--bs:0 0 40px 14px rgba(255,190,70,.75),0 0 120px 50px rgba(255,120,0,.45);--tt:#FFD76A}
    #intro-anim[data-intro=luxury]{--shape:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M50 2 88 30 98 50 88 70 50 98 12 70 2 50 12 30Z'/%3E%3C/svg%3E");--w:190px;--h:190px;--sky:linear-gradient(#050505,#15110a 50%,#3a2a0b 85%,#8a6a1c);--nt:#050505;--hz:rgba(245,200,90,.6);--gl:rgba(245,214,122,.55);--ry:rgba(245,214,122,.22);--h1:#1a1408;--h2:#080604;--bg:linear-gradient(135deg,#fff3c4,#d9a93a 55%,#8a6a1c);--ring:inset 0 0 0 5px #f5d67a,inset 0 0 30px 6px rgba(245,214,122,.5);--tt:#f5d67a;--ac:#f5d67a;--an:iZoom}
    #intro-anim[data-intro=blossom]{--shape:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='20' r='19'/%3E%3Ccircle cx='71.2' cy='28.8' r='19'/%3E%3Ccircle cx='80' cy='50' r='19'/%3E%3Ccircle cx='71.2' cy='71.2' r='19'/%3E%3Ccircle cx='50' cy='80' r='19'/%3E%3Ccircle cx='28.8' cy='71.2' r='19'/%3E%3Ccircle cx='20' cy='50' r='19'/%3E%3Ccircle cx='28.8' cy='28.8' r='19'/%3E%3Ccircle cx='50' cy='50' r='32'/%3E%3C/svg%3E");--w:200px;--h:200px;--sky:linear-gradient(#ffe3ee,#ffb5d0 50%,#ff8fb8 82%,#ffd6c0);--nt:linear-gradient(#3b1030,#7a2a5c 55%,#c25b8a);--hz:rgba(255,255,255,.7);--gl:rgba(255,255,255,.7);--ry:rgba(255,255,255,.35);--h1:#d05a8c;--h2:#8a2a5c;--bg:linear-gradient(#ffd1e0,#ff8fb8);--ring:inset 0 0 0 4px #fff,inset 0 0 30px 8px rgba(255,120,170,.6);--tt:#fff;--ac:#ff2e88;--an:iZoom}
    #intro-anim[data-intro=night]{--sky:linear-gradient(#050a1f,#0d1b4a 50%,#233a7a 85%,#5b7bc4);--nt:linear-gradient(#02040d,#070f2e 60%,#0d1b4a);--hz:rgba(120,150,255,.5);--gl:rgba(170,200,255,.55);--ry:rgba(190,210,255,.2);--hr:200deg;--h1:#0a1436;--h2:#040914;--bg:radial-gradient(circle at 40% 35%,#fff,#dbe6ff 55%,#8fa6e0);--bs:0 0 40px 12px rgba(170,200,255,.65),0 0 120px 50px rgba(90,120,255,.35);--bd:4px solid #dfe8ff;--tt:#cfe0ff;--ac:#ff7eb6}
    #intro-anim[data-intro=sunset]{--sky:linear-gradient(#2b1b3d,#7b2f5a 32%,#e8613c 62%,#ffb347 86%,#ffe08a);--nt:linear-gradient(#1b0f2e,#3a1650);--hz:rgba(255,200,90,.9);--gl:rgba(255,190,80,.75);--h1:#183524;--h2:#08180f;--br:0 68% 0 68%;--bg:radial-gradient(circle at 45% 40%,#ffd36b,#ff8a1a 70%,#d64a00);--bs:0 0 40px 15px rgba(255,190,70,.7),0 0 120px 50px rgba(255,120,0,.45);--tt:#ffe08a;--ac:#9be36b;--an:iSet}
    #intro-anim[data-intro=gift]{--sky:linear-gradient(#1c0710,#5a1230 45%,#a8324a 80%,#ffcf8a);--nt:linear-gradient(#0a0206,#2a0a18 60%,#3a1020);--hz:rgba(255,190,90,.8);--gl:rgba(255,210,120,.7);--h1:#3a0c1e;--h2:#12040a;--br:26%;--bg:linear-gradient(135deg,#ffe29a,#e0a030 60%,#9a5a10);--bs:0 0 40px 14px rgba(255,200,90,.7),0 0 110px 45px rgba(255,130,40,.4);--tt:#ffd166;--ac:#ffd166;--an:iZoom}
    #intro-anim[data-intro=love]{--shape:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M50 92C8 62 2 34 18 18 32 5 46 14 50 27 54 14 68 5 82 18 98 34 92 62 50 92Z'/%3E%3C/svg%3E");--w:200px;--h:185px;--sky:linear-gradient(#2a0a24,#7a1f52 45%,#e0567f 80%,#ffc2a8);--nt:linear-gradient(#12030f,#3a0b2a 60%,#5a1240);--hz:rgba(255,140,150,.8);--gl:rgba(255,150,170,.7);--h1:#4a1038;--h2:#1a0512;--bg:linear-gradient(135deg,#ff9ab8,#e0245e);--ring:inset 0 0 0 5px #ffd0b5,inset 0 0 30px 8px rgba(255,90,140,.5);--tt:#ffd0b5;--ac:#ff5c8a;--an:iDrop}
    #intro-anim[data-intro=house]{--shape:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M50 3 98 45H86V96H14V45H2Z'/%3E%3C/svg%3E");--w:190px;--h:190px;--sky:linear-gradient(#0f2a3a,#1f5f6b 45%,#e39a4a 80%,#ffe1a1);--nt:linear-gradient(#050f16,#0f2a3a);--hz:rgba(255,200,110,.85);--gl:rgba(255,220,140,.7);--hr:150deg;--h1:#0f3a3a;--h2:#061a1c;--bg:linear-gradient(#ffe28a,#e39a4a);--ring:inset 0 0 0 5px #ffe28a,inset 0 0 26px 6px rgba(255,200,90,.5);--tt:#ffe28a;--ac:#ffc86b}
    #intro-anim[data-intro=baby]{--shape:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='28' cy='62' r='22'/%3E%3Ccircle cx='50' cy='44' r='28'/%3E%3Ccircle cx='74' cy='60' r='23'/%3E%3Crect x='28' y='60' width='46' height='26' rx='13'/%3E%3C/svg%3E");--w:220px;--h:180px;--sky:linear-gradient(#8fb8ff,#c9b6ff 45%,#ffc6e0 80%,#fff0c2);--nt:linear-gradient(#2a3b6b,#5a5fa0);--hz:rgba(255,255,255,.8);--gl:rgba(255,255,255,.7);--ry:rgba(255,255,255,.35);--h1:#9a8fe0;--h2:#6a5fc0;--bg:linear-gradient(#fff,#cfe4ff);--ring:inset 0 0 0 5px #fff,inset 0 0 26px 6px rgba(150,190,255,.6);--tt:#fff;--ac:#ff8ab8;--an:iDrop}
    #intro-anim[data-intro=party]{--shape:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M50 3 62 36 97 37 69 58 79 92 50 72 21 92 31 58 3 37 38 36Z'/%3E%3C/svg%3E");--w:210px;--h:210px;--sky:linear-gradient(#2b0a4a,#8a1f8f 42%,#ff5fa2 75%,#ffc44a);--nt:linear-gradient(#120224,#3a0a5a);--hz:rgba(255,190,80,.85);--gl:rgba(255,200,90,.7);--h1:#5a1466;--h2:#220832;--bg:linear-gradient(135deg,#fff3a0,#ffb300);--ring:inset 0 0 0 5px #fff,inset 0 0 26px 6px rgba(255,80,160,.5);--tt:#ffe066;--ac:#5cf0ff;--an:iDrop}


`;

const INTRO_HTML = `  <div id="intro-anim" data-intro="{{INTRO_STYLE}}" onclick="finishIntro()">
    <div class="abs i-sky"></div>
    <div class="abs i-night"></div>
    <div class="abs i-stars" id="i-stars"></div>
    <div class="abs i-horizon"></div>
    <div class="abs i-glow"></div>
    <div class="abs i-rays"></div>
    <div class="abs i-cloud c1"></div><div class="abs i-cloud c2"></div><div class="abs i-cloud c3"></div>
    <div class="abs i-sun"><div class="i-sun-photo"></div></div>
    <div class="abs i-flare f1"></div><div class="abs i-flare f2"></div><div class="abs i-flare f3"></div>
    <svg class="abs i-bird b1" viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke="#1a0a1a" stroke-width="1.6" stroke-linecap="round"/></svg><svg class="abs i-bird b2" viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke="#1a0a1a" stroke-width="1.6" stroke-linecap="round"/></svg><svg class="abs i-bird b3" viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke="#1a0a1a" stroke-width="1.6" stroke-linecap="round"/></svg>
    <svg class="abs i-bird" style="top:12%;width:18px;animation-duration:10s,0.45s;animation-delay:4.8s,0s" viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke="#1a0a1a" stroke-width="1.6" stroke-linecap="round"/></svg><svg class="abs i-bird" style="top:20%;width:24px;animation-duration:8s,0.55s;animation-delay:5.2s,0.1s" viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke="#1a0a1a" stroke-width="1.6" stroke-linecap="round"/></svg><svg class="abs i-bird" style="top:27%;width:14px;animation-duration:11s,0.4s;animation-delay:5.6s,0.2s" viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke="#1a0a1a" stroke-width="1.6" stroke-linecap="round"/></svg><svg class="abs i-bird" style="top:33%;width:22px;animation-duration:9s,0.5s;animation-delay:6.0s,0s" viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke="#1a0a1a" stroke-width="1.6" stroke-linecap="round"/></svg><svg class="abs i-bird" style="top:15%;width:16px;animation-duration:9.5s,0.42s;animation-delay:6.4s,0.3s" viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke="#1a0a1a" stroke-width="1.6" stroke-linecap="round"/></svg><svg class="abs i-bird" style="top:24%;width:28px;animation-duration:8.5s,0.6s;animation-delay:6.8s,0s" viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke="#1a0a1a" stroke-width="1.6" stroke-linecap="round"/></svg><svg class="abs i-bird" style="top:30%;width:15px;animation-duration:10.5s,0.4s;animation-delay:7.2s,0.15s" viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke="#1a0a1a" stroke-width="1.6" stroke-linecap="round"/></svg><svg class="abs i-bird" style="top:10%;width:20px;animation-duration:9s,0.5s;animation-delay:7.6s,0.05s" viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke="#1a0a1a" stroke-width="1.6" stroke-linecap="round"/></svg><svg class="abs i-bird" style="top:35%;width:18px;animation-duration:10s,0.45s;animation-delay:8.0s,0.25s" viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke="#1a0a1a" stroke-width="1.6" stroke-linecap="round"/></svg><svg class="abs i-bird" style="top:18%;width:13px;animation-duration:11.5s,0.38s;animation-delay:8.4s,0.1s" viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke="#1a0a1a" stroke-width="1.6" stroke-linecap="round"/></svg>
    <div class="abs i-hills">
      <svg viewBox="0 0 400 100" preserveAspectRatio="none"><path d="M0 60 Q40 25 90 48 T190 40 T290 50 T400 30 L400 100 L0 100 Z" fill="#3a1236"/><path d="M0 78 Q60 52 130 70 T260 66 T400 62 L400 100 L0 100 Z" fill="#160819"/></svg>
    </div>
    <div class="intro-content">
      <div class="intro-title">{{PROGRAM_TITLE}}</div>
      <div class="intro-nm g" style="margin-top:14px;">{{GROOM_EN}}</div>
      <div class="intro-amp">&#10084;</div>
      <div class="intro-nm b">{{BRIDE_EN}}</div>
      <div class="intro-line"></div>
      <div class="intro-date">{{DATE_KH}}</div>
    </div>
    <div class="intro-skip">TAP TO SKIP</div>
  </div>
`;

const INTRO_JS = `    let introStarted = false, introDone = false, introTimer = null;
    // --- Preload everything the intro needs as early as possible (helps slow internet) ---
    const introPreload = (function () {
        const timeout = (ms) => new Promise(r => setTimeout(r, ms));
        let imgP = Promise.resolve();
        try {
            const bg = getComputedStyle(document.getElementById('invite-cover')).backgroundImage || '';
            const m = bg.match(/url\\(["']?(.*?)["']?\\)/);
            if (m && m[1]) {
                imgP = new Promise(res => {
                    const im = new Image();
                    const done = () => { (im.decode ? im.decode() : Promise.resolve()).then(res, res); };
                    im.onload = done; im.onerror = res; im.src = m[1];
                    if (im.complete && im.naturalWidth) done();
                });
            }
        } catch (e) {}
        let fontP = Promise.resolve();
        try {
            if (document.fonts && document.fonts.load) {
                fontP = Promise.all([
                    document.fonts.load("1em 'Great Vibes'", "Abc"),
                    document.fonts.load("1em 'Moul'", "សិរីមង្គលក្ថៃខែឆ្នាំ")
                ]).catch(() => {});
            }
        } catch (e) {}
        return { img: Promise.race([imgP, timeout(7000)]), fonts: Promise.race([fontP, timeout(5000)]) };
    })();

    window.finishIntro = function() {
        if (introDone) return; introDone = true; clearTimeout(introTimer);
        window.__introBusy = false;
        const cover = document.getElementById('invite-cover'); const intro = document.getElementById('intro-anim');
        cover.classList.add('open'); intro.classList.add('hide');
        setTimeout(() => { intro.style.display = 'none'; }, 1200);
    };
    window.openInvitation = function() {
        if (introStarted) return; introStarted = true;
        const audio = document.getElementById('bgMusic'); const intro = document.getElementById('intro-anim');
        if(audio.paused) { audio.play().catch(e => console.log("Audio autoplay failed", e)); }
        // Respect "reduce motion": skip the intro
        if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { window.finishIntro(); return; }
        window.__introBusy = true; // pause the background snow canvas while intro plays
        const conn = navigator.connection || {};
        const lite = (navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4) || (navigator.deviceMemory && navigator.deviceMemory <= 2) || conn.saveData || /2g/.test(conn.effectiveType || '');
        if (lite) intro.classList.add('lite');
        if (!lite) {
            const IS = intro.getAttribute('data-intro') || 'sunrise'; const GL = {blossom:['&#10047;','&#10048;'],night:['&#9733;','&#10022;'],sunset:['&#10086;','&#10022;'],love:['&#9829;'],party:['&#9733;','&#9679;','&#9650;','&#9830;'],baby:['&#9733;','&#9825;'],gift:['&#10022;','&#9830;']}[IS] || ['&#10022;'];
            const stars = document.getElementById('i-stars'); const frag = document.createDocumentFragment();
            for (let i = 0; i < (IS === 'night' ? 70 : 30); i++) { const st = document.createElement('div'); st.className = 'i-star'; st.style.left = (Math.random()*100)+'%'; st.style.top = (Math.random()*100)+'%'; st.style.animationDelay = (Math.random()*2)+'s'; frag.appendChild(st); }
            stars.appendChild(frag);
            const f2 = document.createDocumentFragment();
            for (let i = 0; i < 10; i++) {
                const h = document.createElement('div'); h.className = 'i-spark'; h.innerHTML = GL[(Math.random()*GL.length)|0]; if (IS === 'party') h.style.color = 'hsl(' + ((Math.random()*360)|0) + ',90%,65%)';
                h.style.left = (Math.random() * 100) + '%'; h.style.fontSize = (12 + Math.random() * 14) + 'px';
                h.style.animationDuration = (4 + Math.random() * 3) + 's'; h.style.animationDelay = (Math.random() * 3) + 's';
                f2.appendChild(h);
            }
            intro.appendChild(f2);
        }
        const t0 = performance.now();
        requestAnimationFrame(() => requestAnimationFrame(() => intro.classList.add('show')));
        // Photo appears inside the sun only when the image is really loaded (no half-loaded flash)
        introPreload.img.then(() => {
            const wait = Math.max(0, 2000 - (performance.now() - t0));
            setTimeout(() => { if (!introDone) intro.classList.add('ph-on'); }, wait);
        });
        // Text starts once fonts are ready (and the sun has risen)
        Promise.all([introPreload.fonts, new Promise(r => setTimeout(r, 3200))]).then(() => {
            if (introDone) return;
            intro.classList.add('txt-on');
            introTimer = setTimeout(window.finishIntro, 8800);
        });
        // Safety net: never get stuck
        setTimeout(() => { if (!introDone) window.finishIntro(); }, 20000);
    };`;

// ===========================================================
// DOOR INTRO v3 (ROYAL CRIMSON + DIAMOND LUXE) ~១៥វិនាទី
// គោះទ្វារ ៣ដង → ចាក់សោ → ទ្វារបើក (សំឡេងស្រែក+ផ្ទុះ+chime) → រូប/ឈ្មោះ → សត្វ ២២ + មេអំបៅ ១៨ + ធូលីមាស
// ម៉ាស៊ីនខ្សោយ (lite) បន្ថយចំនួន ១០/៨/១០
// ===========================================================
const DOOR_INTRO_CSS = `/* ===== DOOR INTRO: knock x3, doors burst open with sound, groom and bride appear ===== */
    #intro-anim[data-intro^=door-]{--h:172px;--g:#e6b84a;--gh:#fff0b8;--d1:#8a1424;--d2:#45080f;--sky:radial-gradient(ellipse at 50% 28%,#ffe0a0,#d9782e 40%,#5a1420 80%,#2a060c);--nt:#12030a;--gl:rgba(255,220,140,.9);--ry:rgba(255,230,150,.35);--tt:#FFD76A;--ac:#FF8FA3}
    #intro-anim[data-intro=door-royal]{--d1:#5a3a14;--d2:#2a1806;--g:#f0c45a;--gh:#fff2c0;--sky:linear-gradient(#1a0b2e,#5a1a4a 35%,#d9583a 70%,#ffc46b);--tt:#ffd88a;--ac:#ffb347}
    #intro-anim[data-intro=door-luxury]{--d1:#1c1c1c;--d2:#050505;--g:#d9b24a;--gh:#fff3c4;--sky:radial-gradient(ellipse at 50% 28%,#4a3510,#0a0805 70%);--nt:#000;--gl:rgba(245,214,122,.6);--ry:rgba(245,214,122,.25);--tt:#f5d67a;--ac:#f5d67a}
    #intro-anim[data-intro^=door-] .i-sun{display:none}
    #intro-anim[data-intro^=door-] .i-rays{display:block;opacity:0}
    #intro-anim.show[data-intro^=door-] .i-rays,#intro-anim.show[data-intro^=door-] .i-glow{animation:none;opacity:0}
    #intro-anim.d-go[data-intro^=door-] .i-rays{animation:dRays 2s ease-out forwards,iSpin 45s linear 2s infinite}
    #intro-anim.d-go[data-intro^=door-] .i-glow{animation:iGlow 2s ease-out forwards}
    #intro-anim[data-intro^=door-] .intro-skip{z-index:11}
    #intro-anim[data-intro=door-royal] .i-x1{display:block;left:0;bottom:0;width:100%;height:24vh;z-index:2;background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 380 100' preserveAspectRatio='none'%3E%3Cpath fill='%230a0414' d='M0 100V80H70V72H110V60H140L150 48H160L166 30H172L178 14H184L190 0L196 14H202L208 30H214L220 48H230L240 60H270V72H310V80H380V100Z'/%3E%3C/svg%3E") center bottom/100% 100% no-repeat}
    .i-couple,.i-flash,.i-doors,.i-seam{display:none}
    #intro-anim[data-intro^=door-] :is(.i-couple,.i-doors,.i-seam,.i-flash){display:block}
    #intro-anim[data-intro^=door-] .i-couple{display:flex;align-items:center;justify-content:center;gap:calc(14px*var(--s));left:50%;top:24%;transform:translate(-50%,-50%);z-index:3}
    .cp{position:relative;width:calc(130px*var(--s));height:calc(172px*var(--s));padding:5px;box-sizing:border-box;border-radius:50% 50% 12px 12px/32% 32% 12px 12px;background:linear-gradient(135deg,var(--gh),var(--g) 55%,#8a6a1c);box-shadow:0 0 30px 6px rgba(255,200,90,.45),0 8px 20px rgba(0,0,0,.5);opacity:0}
    .cph{width:100%;height:100%;border-radius:50% 50% 8px 8px/32% 32% 8px 8px;background:#444 center 18%/cover no-repeat}
    .cph-heart{font-size:1.5rem;color:var(--ac);text-shadow:0 0 12px var(--ac);opacity:0;transform:scale(0)}
    .i-couple.solo .cp.b,.i-couple.solo .cph-heart{display:none}
    #intro-anim.d-go .cp.g{animation:cpInL 1s cubic-bezier(.2,.9,.3,1.2) .5s forwards}
    #intro-anim.d-go .cp.b{animation:cpInR 1s cubic-bezier(.2,.9,.3,1.2) .8s forwards}
    #intro-anim.d-go .cph-heart{animation:introPop .8s cubic-bezier(.34,1.56,.64,1) 1.5s forwards,introBeat 1.2s ease-in-out 2.4s infinite}
    .i-doors{left:0;top:0;width:100%;height:100%;z-index:8;perspective:1100px}
    .i-door{position:absolute;top:0;width:50.3%;height:100%;box-sizing:border-box;backface-visibility:hidden;will-change:transform;background:repeating-linear-gradient(90deg,rgba(255,255,255,.03) 0 3px,rgba(0,0,0,.05) 3px 7px),linear-gradient(90deg,var(--d2),var(--d1) 45%,var(--d2));box-shadow:inset 0 0 60px rgba(0,0,0,.55)}
    .i-door.dl{left:0;transform-origin:0 50%;border-right:3px solid var(--g)}
    .i-door.dr{right:0;transform-origin:100% 50%;border-left:3px solid var(--g)}
    .dp{position:absolute;top:5%;bottom:5%;left:9%;right:9%;border:2px solid var(--g);border-radius:10px 10px 4px 4px;box-shadow:inset 0 0 0 5px rgba(0,0,0,.16),inset 0 0 0 7px rgba(230,184,74,.3)}
    .dp::before{content:'';position:absolute;left:12%;right:12%;top:6%;height:38%;border:2px solid var(--g);border-radius:50% 50% 6px 6px/28% 28% 6px 6px;background:radial-gradient(circle at 50% 55%,rgba(255,215,120,.35),transparent 60%)}
    .dp::after{content:'';position:absolute;left:12%;right:12%;bottom:6%;height:38%;border:2px solid var(--g);border-radius:6px;background:repeating-linear-gradient(45deg,rgba(230,184,74,.16) 0 6px,transparent 6px 12px)}
    .dk{position:absolute;top:52%;width:22px;height:22px;border-radius:50%;background:radial-gradient(circle at 35% 30%,#fff6c8,var(--g) 55%,#7a5510);box-shadow:0 3px 8px rgba(0,0,0,.6);z-index:2}
    .dl .dk{right:8px}.dr .dk{left:8px}
    .i-seam{left:50%;top:0;width:6px;margin-left:-3px;height:100%;z-index:9;background:linear-gradient(transparent,#fff6c8 20%,#fff6c8 80%,transparent);filter:blur(2px);opacity:0}
    .i-flash{left:0;top:0;width:100%;height:100%;z-index:10;opacity:0;background:radial-gradient(circle at 50% 40%,#fff,rgba(255,240,180,.85) 40%,transparent 75%)}
    #intro-anim.show .i-doors{animation:dKnock 1.7s linear forwards}
    #intro-anim.show .i-seam{animation:dSeam 1.7s ease-out forwards}
    #intro-anim.d-go .i-seam{animation:dSeamOut .5s ease-out forwards}
    #intro-anim.d-go .i-flash{animation:dFlash 1.5s ease-out forwards}
    #intro-anim.d-go .i-door.dl{animation:dOpenL 1.2s cubic-bezier(.16,.84,.3,1) forwards}
    #intro-anim.d-go .i-door.dr{animation:dOpenR 1.2s cubic-bezier(.16,.84,.3,1) forwards}
    #intro-anim.d-go .i-doors{animation:none}
    @keyframes dKnock{0%,14%{transform:none}16%{transform:translate(-3px,1px)}19%{transform:translate(3px,-1px)}22%,45%{transform:none}47%{transform:translate(-4px,1px)}50%{transform:translate(3px)}53%,76%{transform:none}78%{transform:translate(-6px,2px)}81%{transform:translate(5px,-2px)}84%{transform:translate(-3px)}88%,100%{transform:none}}
    @keyframes dSeam{0%{opacity:0}16%{opacity:.8}24%{opacity:.1}47%{opacity:.9}55%{opacity:.1}78%{opacity:1}100%{opacity:.7}}
    @keyframes dSeamOut{0%{opacity:.7;transform:scaleX(1)}100%{opacity:0;transform:scaleX(40)}}
    @keyframes dFlash{0%{opacity:0}10%{opacity:1}100%{opacity:0}}
    @keyframes dOpenL{0%{transform:rotateY(0)}10%{transform:rotateY(5deg)}100%{transform:rotateY(-118deg)}}
    @keyframes dOpenR{0%{transform:rotateY(0)}10%{transform:rotateY(-5deg)}100%{transform:rotateY(118deg)}}
    @keyframes dRays{0%{opacity:0;transform:scale(.3)}30%{opacity:1}100%{opacity:1;transform:scale(1)}}
    @keyframes cpInL{0%{opacity:0;transform:translateX(-50px) scale(.7)}100%{opacity:1;transform:none}}
    @keyframes cpInR{0%{opacity:0;transform:translateX(50px) scale(.7)}100%{opacity:1;transform:none}}

    /* ===== DOOR INTRO v2: longer, smoother, richer (overrides the rules above) ===== */
    #intro-anim.show .i-doors{animation:dKnock 2.8s linear forwards}
    #intro-anim.show .i-seam{animation:dSeam 2.8s ease-out forwards}
    #intro-anim.d-go .i-seam{animation:dSeamOut .7s ease-out forwards}
    #intro-anim.d-go .i-flash{animation:dFlash 1.8s ease-out forwards}
    #intro-anim.d-go .i-door.dl{animation:dOpenL 2.6s cubic-bezier(.35,0,.15,1) forwards}
    #intro-anim.d-go .i-door.dr{animation:dOpenR 2.6s cubic-bezier(.35,0,.15,1) forwards}
    #intro-anim.d-go .i-doors{animation:none}
    @keyframes dKnock{0%,9%{transform:none}10.7%{transform:translate(-3px,1px)}12.5%{transform:translate(3px,-1px)}14.5%,37%{transform:none}39%{transform:translate(-4px,1px)}41%{transform:translate(3px)}43%,69%{transform:none}71%{transform:translate(-7px,2px)}73%{transform:translate(6px,-2px)}75.5%{transform:translate(-4px)}78%{transform:translate(2px)}81%,100%{transform:none}}
    @keyframes dSeam{0%{opacity:0}10%{opacity:.8}15%{opacity:.15}39%{opacity:.9}45%{opacity:.15}71%{opacity:1}80%{opacity:.45}89%{opacity:1}100%{opacity:.85}}
    @keyframes dOpenL{0%{transform:rotateY(0)}7%{transform:rotateY(3deg)}100%{transform:rotateY(-116deg)}}
    @keyframes dOpenR{0%{transform:rotateY(0)}7%{transform:rotateY(-3deg)}100%{transform:rotateY(116deg)}}
    #intro-anim.d-latch .dk{animation:dkGlow .7s ease-out forwards}
    @keyframes dkGlow{0%{transform:scale(1)}35%{transform:scale(1.35);box-shadow:0 0 18px 8px rgba(255,230,150,.95),0 3px 8px rgba(0,0,0,.6)}100%{transform:scale(1.1);box-shadow:0 0 12px 4px rgba(255,220,130,.7),0 3px 8px rgba(0,0,0,.6)}}
    /* couple: slow entrance, camera push-in, gentle float, light sheen */
    #intro-anim.d-go .i-couple{animation:dPush 4.2s cubic-bezier(.2,.7,.2,1) forwards}
    #intro-anim.d-go .cp.g{animation:cpInL 1.5s cubic-bezier(.2,.9,.3,1.15) 1s forwards,cpFloat 5.5s ease-in-out 2.6s infinite alternate}
    #intro-anim.d-go .cp.b{animation:cpInR 1.5s cubic-bezier(.2,.9,.3,1.15) 1.4s forwards,cpFloat 5.5s ease-in-out 3s infinite alternate-reverse}
    #intro-anim.d-go .cph-heart{animation:introPop .9s cubic-bezier(.34,1.56,.64,1) 2.4s forwards,introBeat 1.2s ease-in-out 3.4s infinite}
    .cp::after{content:'';position:absolute;inset:5px;border-radius:50% 50% 8px 8px/32% 32% 8px 8px;background:linear-gradient(105deg,transparent 40%,rgba(255,255,255,.5) 50%,transparent 60%);background-size:260% 100%;background-position:160% 0;pointer-events:none;opacity:0}
    #intro-anim.d-go .cp::after{animation:cpSheen 4.2s ease-in-out 3.2s infinite}
    @keyframes dPush{from{scale:1.22}to{scale:1}}
    @keyframes cpFloat{from{translate:0 -3px}to{translate:0 7px}}
    @keyframes cpSheen{0%{opacity:0;background-position:160% 0}5%{opacity:1}35%{opacity:1;background-position:-60% 0}36%,100%{opacity:0;background-position:-60% 0}}
    /* text: slower, blur-in */
    #intro-anim.txt-on[data-intro^=door-] .intro-title{animation:dTxt 1.5s cubic-bezier(.2,.8,.2,1) 0s forwards}
    #intro-anim.txt-on[data-intro^=door-] .intro-nm.g{animation:dTxt 1.5s cubic-bezier(.2,.8,.2,1) .9s forwards}
    #intro-anim.txt-on[data-intro^=door-] .intro-amp{animation:introPop .9s cubic-bezier(.34,1.56,.64,1) 1.8s forwards,introBeat 1.2s ease-in-out 2.8s infinite}
    #intro-anim.txt-on[data-intro^=door-] .intro-nm.b{animation:dTxt 1.5s cubic-bezier(.2,.8,.2,1) 2.1s forwards}
    #intro-anim.txt-on[data-intro^=door-] .intro-line{animation:introLine 1.4s ease-out 3s forwards}
    #intro-anim.txt-on[data-intro^=door-] .intro-date{animation:dTxt 1.5s cubic-bezier(.2,.8,.2,1) 3.3s forwards}
    @keyframes dTxt{from{opacity:0;transform:translateY(22px) scale(.96);filter:blur(8px)}to{opacity:1;transform:none;filter:blur(0)}}
    /* exit: golden light bloom, then the invitation appears */
    #intro-anim.d-end .i-flash{animation:dFlashEnd .9s ease-in forwards}
    @keyframes dFlashEnd{0%{opacity:0}100%{opacity:1}}
    /* flyers: birds, butterflies, gold dust */
    .i-fly{left:0;top:0;width:100%;height:100%;z-index:6;overflow:hidden;display:none}
    #intro-anim[data-intro^=door-] .i-fly{display:block}
    #intro-anim[data-intro^=door-]{--bird:#fff0c8}
    #intro-anim[data-intro=door-luxury]{--bird:#f5d67a}
    #intro-anim .fl{position:absolute;left:0;opacity:0;pointer-events:none;will-change:transform,opacity}
    #intro-anim .fl-b{animation:dbFly var(--t,9s) linear var(--dl,0s) infinite}
    #intro-anim .fl-b svg{display:block;width:100%;height:auto;animation:dbFlapB var(--f,.5s) ease-in-out infinite alternate,dbBob var(--bb,1.6s) ease-in-out infinite alternate}
    #intro-anim .fl-b svg path{stroke:var(--bird)}
    #intro-anim .fl-m{animation:dbFly var(--t,12s) linear var(--dl,0s) infinite}
    #intro-anim .fl-m .sw{animation:dbSway var(--sw,2.4s) ease-in-out infinite alternate}
    #intro-anim .fl-m svg{display:block;width:100%;height:auto;transform-origin:50% 55%;animation:dbFlapM var(--f,.28s) ease-in-out infinite alternate}
    #intro-anim .fl-d{left:var(--x);top:auto;bottom:-12px;width:var(--s);height:var(--s);border-radius:50%;background:radial-gradient(circle,#fff6c8 0%,rgba(255,200,90,.6) 40%,rgba(255,200,90,0) 70%);animation:dbDust var(--t,9s) ease-in var(--dl,0s) infinite}
    @keyframes dbFly{0%{opacity:0;transform:translate(var(--sx),var(--sy))}8%{opacity:.95}92%{opacity:.95}100%{opacity:0;transform:translate(var(--ex),var(--ey))}}
    @keyframes dbSway{from{transform:translateY(-16px) rotate(-7deg)}to{transform:translateY(16px) rotate(7deg)}}
    @keyframes dbFlapB{from{scale:1 1}to{scale:1 .5}}
    @keyframes dbBob{from{translate:0 -3px}to{translate:0 4px}}
    @keyframes dbFlapM{from{transform:scaleX(1)}to{transform:scaleX(.22)}}
    @keyframes dbDust{0%{opacity:0;transform:translate(0,0)}15%{opacity:1}100%{opacity:0;transform:translate(var(--dx,0px),-112vh)}}

    /* ===== DOOR INTRO v3: longer, cinematic (overrides v2) ===== */
    #intro-anim.show .i-doors{animation:dKnock 3.6s linear forwards}
    #intro-anim.show .i-seam{animation:dSeam 3.6s ease-out forwards}
    #intro-anim.d-go .i-doors{animation:none}
    #intro-anim.d-go .i-seam{animation:dSeamOut .9s ease-out forwards}
    #intro-anim.d-go .i-flash{animation:dFlash 2.6s ease-out forwards}
    #intro-anim.d-go .i-door.dl{animation:dOpenL 4.4s cubic-bezier(.45,0,.12,1) forwards}
    #intro-anim.d-go .i-door.dr{animation:dOpenR 4.4s cubic-bezier(.45,0,.12,1) forwards}
    @keyframes dKnock{0%,13%{transform:none}14.5%{transform:translate(-3px,1px)}16.5%{transform:translate(3px,-1px)}18.5%,43%{transform:none}44.5%{transform:translate(-4px,1px)}46.5%{transform:translate(3px)}48.5%,76%{transform:none}77.5%{transform:translate(-8px,2px)}79.5%{transform:translate(7px,-2px)}82%{transform:translate(-4px)}84.5%{transform:translate(2px)}87%,100%{transform:none}}
    @keyframes dSeam{0%{opacity:0}14%{opacity:.8}19%{opacity:.15}44%{opacity:.9}50%{opacity:.15}77%{opacity:1}84%{opacity:.45}92%{opacity:1}100%{opacity:.9}}
    @keyframes dFlash{0%{opacity:0}8%{opacity:1}100%{opacity:0}}
    @keyframes dOpenL{0%{transform:rotateY(0)}5%{transform:rotateY(3deg)}85%{transform:rotateY(-113deg)}100%{transform:rotateY(-110deg)}}
    @keyframes dOpenR{0%{transform:rotateY(0)}5%{transform:rotateY(-3deg)}85%{transform:rotateY(113deg)}100%{transform:rotateY(110deg)}}
    /* sheen sweeping across the doors while waiting */
    .i-door::after{content:'';position:absolute;inset:0;pointer-events:none;background:linear-gradient(105deg,transparent 38%,rgba(255,235,170,.22) 50%,transparent 62%) 160% 0/300% 100% no-repeat;animation:dSheen 3.6s ease-in-out .3s 1 both}
    @keyframes dSheen{0%{background-position:160% 0}100%{background-position:-60% 0}}
    /* light pouring through the gap */
    .i-beam{display:none;left:50%;top:0;width:100%;height:100%;z-index:4;opacity:0;mix-blend-mode:screen;transform:translateX(-50%) scaleX(.05);background:radial-gradient(ellipse 60% 70% at 50% 45%,rgba(255,236,170,.55),rgba(255,200,90,.18) 45%,transparent 72%)}
    #intro-anim[data-intro^=door-] .i-beam{display:block}
    #intro-anim.d-go .i-beam{animation:dBeam 4.6s ease-out forwards}
    @keyframes dBeam{0%{opacity:0;transform:translateX(-50%) scaleX(.05)}25%{opacity:1}100%{opacity:.55;transform:translateX(-50%) scaleX(1)}}
    /* slow camera dolly on the backdrop */
    #intro-anim.d-go[data-intro^=door-] .i-sky{animation:dSky 9s cubic-bezier(.2,.7,.2,1) forwards}
    @keyframes dSky{from{scale:1.2}to{scale:1}}
    /* couple enters later, as the doors widen */
    #intro-anim.d-go .i-couple{animation:dPush 7s cubic-bezier(.2,.7,.2,1) forwards}
    #intro-anim.d-go .cp.g{animation:cpInL 1.8s cubic-bezier(.2,.9,.3,1.15) 1.6s forwards,cpFloat 5.5s ease-in-out 3.6s infinite alternate}
    #intro-anim.d-go .cp.b{animation:cpInR 1.8s cubic-bezier(.2,.9,.3,1.15) 2.1s forwards,cpFloat 5.5s ease-in-out 4.1s infinite alternate-reverse}
    #intro-anim.d-go .cph-heart{animation:introPop .9s cubic-bezier(.34,1.56,.64,1) 3.2s forwards,introBeat 1.2s ease-in-out 4.2s infinite}
    #intro-anim.d-go .cp::after{animation:cpSheen 4.2s ease-in-out 4.4s infinite}
    /* gold spark burst when doors open */
    #intro-anim .fl-s{left:50%;top:42%;width:var(--s);height:var(--s);margin:-3px;border-radius:50%;background:radial-gradient(circle,#fff,#ffd76a 45%,rgba(255,200,90,0) 72%);animation:dbSpark var(--t,2s) cubic-bezier(.1,.7,.3,1) var(--dl,0s) forwards}
    @keyframes dbSpark{0%{opacity:0;transform:translate(0,0) scale(.4)}12%{opacity:1}100%{opacity:0;transform:translate(var(--bx),var(--by)) scale(.6)}}

    /* ===== v3.1: opening copied from CLASSIC GOLD (panels slide apart, 2.4s, same easing) ===== */
    .i-door.dl{border-right:6px solid var(--g);box-shadow:inset 0 0 60px rgba(0,0,0,.55),0 0 30px #000}
    .i-door.dr{border-left:6px solid var(--g);box-shadow:inset 0 0 60px rgba(0,0,0,.55),0 0 30px #000}
    #intro-anim.d-go .i-door.dl{animation:dSlideL 2.4s cubic-bezier(.7,0,.2,1) forwards}
    #intro-anim.d-go .i-door.dr{animation:dSlideR 2.4s cubic-bezier(.7,0,.2,1) forwards}
    @keyframes dSlideL{to{transform:translateX(-101%)}}
    @keyframes dSlideR{to{transform:translateX(101%)}}
    @keyframes dFlash{0%{opacity:0}10%{opacity:.4}100%{opacity:0}}
    #intro-anim.d-go .i-flash{animation:dFlash 2s ease-out forwards}
    #intro-anim.d-go .i-beam{animation:dBeam 3.2s ease-out forwards}
    #intro-anim.d-go .cp.g{animation:cpInL 1.6s cubic-bezier(.2,.9,.3,1.15) 1s forwards,cpFloat 5.5s ease-in-out 2.6s infinite alternate}
    #intro-anim.d-go .cp.b{animation:cpInR 1.6s cubic-bezier(.2,.9,.3,1.15) 1.5s forwards,cpFloat 5.5s ease-in-out 3.1s infinite alternate-reverse}
    #intro-anim.d-go .cph-heart{animation:introPop .9s cubic-bezier(.34,1.56,.64,1) 2.5s forwards,introBeat 1.2s ease-in-out 3.5s infinite}
    #intro-anim.d-go .cp::after{animation:cpSheen 4.2s ease-in-out 3.6s infinite}

    /* ===== v3.2: no door shaking; quick wait, then zoom-in through the doors ===== */
    #intro-anim.show .i-doors{animation:none}
    #intro-anim.show .i-seam{animation:dSeam2 1.9s ease-out forwards}
    .i-door::after{animation:dSheen 1.6s ease-in-out .1s 1 both}
    #intro-anim.d-go .i-doors{animation:dZoom 2.4s cubic-bezier(.7,0,.2,1) forwards}
    @keyframes dZoom{0%{scale:1}100%{scale:1.9}}
    @keyframes dSeam2{0%{opacity:0}13%{opacity:.8}20%{opacity:.2}40%{opacity:.9}47%{opacity:.2}68%{opacity:1}100%{opacity:.9}}
    /* v3.3: remove the centre light line + glow so they never cover the text */
    #intro-anim .i-seam,#intro-anim .i-beam{display:none !important}
`;

const DOOR_INTRO_HTML = `  <div id="intro-anim" data-intro="{{INTRO_STYLE}}" onclick="finishIntro()">
    <div class="abs i-sky"></div><div class="abs i-x1"></div><div class="abs i-x2"></div>
    <div class="abs i-couple"><div class="cp g"><div class="cph" style="background-image:url('{{GROOM_IMAGE}}')"></div></div><div class="cph-heart">&#10084;</div><div class="cp b"><div class="cph" style="background-image:url('{{BRIDE_IMAGE}}')"></div></div></div>
    <div class="abs i-beam"></div><div class="abs i-flash"></div>
    <div class="abs i-doors"><div class="i-door dl"><div class="dp"></div><i class="dk"></i></div><div class="i-door dr"><div class="dp"></div><i class="dk"></i></div></div><div class="abs i-seam"></div>
    <div class="abs i-night"></div>
    <div class="abs i-stars" id="i-stars"></div>
    <div class="abs i-glow"></div>
    <div class="abs i-rays"></div>
    <div class="abs i-fly" id="i-fly"></div>
    <div class="intro-content">
      <div class="intro-title">{{PROGRAM_TITLE}}</div>
      <div class="intro-nm g" style="margin-top:14px;">{{GROOM_EN}}</div>
      <div class="intro-amp">&#10084;</div>
      <div class="intro-nm b">{{BRIDE_EN}}</div>
      <div class="intro-line"></div>
      <div class="intro-date">{{DATE_KH}}</div>
    </div>
    <div class="intro-skip">TAP TO SKIP</div>
  </div>
`;

const DOOR_INTRO_JS = `    let introStarted = false, introDone = false, introTimer = null;
    // --- Preload everything the intro needs as early as possible (helps slow internet) ---
    const introPreload = (function () {
        const timeout = (ms) => new Promise(r => setTimeout(r, ms));
        let imgP = Promise.resolve();
        try {
            const bg = getComputedStyle(document.getElementById('invite-cover')).backgroundImage || '';
            const m = bg.match(/url\\\\(["']?(.*?)["']?\\\\)/);
            if (m && m[1]) {
                imgP = new Promise(res => {
                    const im = new Image();
                    const done = () => { (im.decode ? im.decode() : Promise.resolve()).then(res, res); };
                    im.onload = done; im.onerror = res; im.src = m[1];
                    if (im.complete && im.naturalWidth) done();
                });
            }
        } catch (e) {}
        let fontP = Promise.resolve();
        try {
            if (document.fonts && document.fonts.load) {
                fontP = Promise.all([
                    document.fonts.load("1em 'Great Vibes'", "Abc"),
                    document.fonts.load("1em 'Moul'", "\\u179F\\u17B7\\u179A\\u17B8\\u1798\\u1784\\u17D2\\u1782\\u179B\\u1780\\u17D2\\u1790\\u17C3\\u1781\\u17C2\\u1786\\u17D2\\u1793\\u17B6\\u17C6")
                ]).catch(() => {});
            }
        } catch (e) {}
        return { img: Promise.race([imgP, timeout(7000)]), fonts: Promise.race([fontP, timeout(5000)]) };
    })();

    window.finishIntro = function() {
        if (introDone) return; introDone = true; clearTimeout(introTimer);
        window.__introBusy = false; try { const sc = document.getElementById('snow-canvas'); if (sc) sc.style.visibility = ''; const au = document.getElementById('bgMusic'); if (au) au.volume = 1; } catch (e) {}
        const cover = document.getElementById('invite-cover'); const intro = document.getElementById('intro-anim');
        cover.classList.add('open'); intro.classList.add('hide'); if (window.__introAC) { const a = window.__introAC; window.__introAC = null; setTimeout(function () { try { a.close(); } catch (e) {} }, 1500); }
        setTimeout(() => { intro.style.display = 'none'; }, 1200);
    };
    // ---- Flying birds, butterflies and gold dust (appear after the doors open) ----
    function spawnFlyers(lite) {
        const box = document.getElementById('i-fly'); if (!box || box.dataset.f) return; box.dataset.f = '1';
        const R = (a, b) => a + Math.random() * (b - a);
        const BIRD = '<svg viewBox="0 0 24 10"><path d="M1 8 Q6 -1 12 6 Q18 -1 23 8" fill="none" stroke-width="1.7" stroke-linecap="round"/></svg>';
        const PAL = [['#ffd76a','#ff9f43'], ['#ff8fa3','#ffd1dc'], ['#fff0b8','#e6b84a'], ['#ffb347','#c2417f'], ['#f5d67a','#fff3c4'], ['#ffe08a','#ff7a59'], ['#ffc2d4','#ff6f91']];
        const BFLY = (c) => '<svg viewBox="0 0 40 32"><path style="fill:' + c[0] + '" d="M20 16C14 1 1 2 2 12C3 21 12 22 20 17Z"/><path style="fill:' + c[0] + '" d="M20 16C26 1 39 2 38 12C37 21 28 22 20 17Z"/><path style="fill:' + c[1] + '" d="M20 17C13 18 6 22 9 28C13 31 19 25 20 18Z"/><path style="fill:' + c[1] + '" d="M20 17C27 18 34 22 31 28C27 31 21 25 20 18Z"/><circle cx="9" cy="10" r="2.2" fill="rgba(255,255,255,.7)"/><circle cx="31" cy="10" r="2.2" fill="rgba(255,255,255,.7)"/><path d="M20 10Q17 4 14 3M20 10Q23 4 26 3" stroke="#3a2a10" stroke-width=".9" fill="none"/><rect x="19.2" y="9" width="1.6" height="16" rx=".8" fill="#3a2a10"/></svg>';
        const nb = lite ? 10 : 22, nm = 0, nd = lite ? 10 : 28;
        const frag = document.createDocumentFragment();
        for (let i = 0; i < nb; i++) {
            const dir = Math.random() < 0.5 ? 1 : -1; const d = document.createElement('div'); d.className = 'fl fl-b';
            d.style.top = R(4, 44) + '%'; d.style.width = R(13, 32) + 'px';
            d.style.setProperty('--sx', dir > 0 ? '-14vw' : '114vw'); d.style.setProperty('--ex', dir > 0 ? '114vw' : '-14vw');
            d.style.setProperty('--sy', '0px'); d.style.setProperty('--ey', R(-80, 50) + 'px');
            d.style.setProperty('--t', R(7, 13) + 's'); d.style.setProperty('--dl', R(0, 7) + 's');
            d.style.setProperty('--f', R(0.36, 0.62) + 's'); d.style.setProperty('--bb', R(1.2, 2.2) + 's');
            d.innerHTML = BIRD; if (dir < 0) d.firstChild.style.transform = 'scaleX(-1)';
            frag.appendChild(d);
        }
        for (let i = 0; i < nm; i++) {
            const dir = Math.random() < 0.5 ? 1 : -1; const d = document.createElement('div'); d.className = 'fl fl-m';
            d.style.top = R(16, 80) + '%'; d.style.width = R(18, 38) + 'px';
            d.style.setProperty('--sx', dir > 0 ? '-14vw' : '114vw'); d.style.setProperty('--ex', dir > 0 ? '114vw' : '-14vw');
            d.style.setProperty('--sy', '0px'); d.style.setProperty('--ey', R(-170, -20) + 'px');
            d.style.setProperty('--t', R(10, 17) + 's'); d.style.setProperty('--dl', R(0, 6) + 's');
            d.style.setProperty('--sw', R(1.8, 3.2) + 's'); d.style.setProperty('--f', R(0.2, 0.34) + 's');
            d.innerHTML = '<div class="sw">' + BFLY(PAL[(Math.random() * PAL.length) | 0]) + '</div>';
            frag.appendChild(d);
        }
        for (let i = 0; i < nd; i++) {
            const d = document.createElement('div'); d.className = 'fl fl-d';
            d.style.setProperty('--x', R(0, 100) + '%'); d.style.setProperty('--s', R(5, 14) + 'px');
            d.style.setProperty('--dx', R(-40, 40) + 'px'); d.style.setProperty('--t', R(6, 11) + 's'); d.style.setProperty('--dl', R(0, 6) + 's');
            frag.appendChild(d);
        }
        box.appendChild(frag);
    }

    // ---- Gold spark burst when the doors open ----
    function spawnBurst(lite) {
        const box = document.getElementById('i-fly'); if (!box) return;
        const n = lite ? 14 : 34; const frag = document.createDocumentFragment();
        for (let i = 0; i < n; i++) {
            const a = Math.random() * Math.PI * 2, r = 80 + Math.random() * 260;
            const d = document.createElement('div'); d.className = 'fl fl-s';
            d.style.setProperty('--s', (4 + Math.random() * 9) + 'px');
            d.style.setProperty('--bx', (Math.cos(a) * r * 1.3) + 'px'); d.style.setProperty('--by', (Math.sin(a) * r * 0.9) + 'px');
            d.style.setProperty('--t', (1.6 + Math.random() * 1.8) + 's'); d.style.setProperty('--dl', (Math.random() * 0.5) + 's');
            frag.appendChild(d);
        }
        box.appendChild(frag);
    }

    // ---- Synthesised sound: knocks, bolt, latch, long wooden creak, boom, door-stop thud, pad, chimes, birds (with hall reverb) ----
    function makeDoorSfx() {
        try {
            const AC = window.AudioContext || window.webkitAudioContext; if (!AC) return null;
            const ac = new AC(); if (ac.resume) ac.resume(); window.__introAC = ac;
            const master = ac.createGain(); master.gain.value = 0.9;
            const comp = ac.createDynamicsCompressor(); master.connect(comp); comp.connect(ac.destination);
            // hall reverb (generated impulse response)
            const irLen = Math.floor(ac.sampleRate * 2.6); const ir = ac.createBuffer(2, irLen, ac.sampleRate);
            for (let c = 0; c < 2; c++) { const dd = ir.getChannelData(c); for (let i = 0; i < irLen; i++) dd[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / irLen, 2.6); }
            const verb = ac.createConvolver(); verb.buffer = ir; const wet = ac.createGain(); wet.gain.value = 0.32;
            master.connect(verb); verb.connect(wet); wet.connect(comp);
            const noise = (dur) => { const n = Math.floor(ac.sampleRate * dur); const b = ac.createBuffer(1, n, ac.sampleRate); const d = b.getChannelData(0); for (let i = 0; i < n; i++) d[i] = Math.random() * 2 - 1; const s = ac.createBufferSource(); s.buffer = b; return s; };
            const gainEnv = (t, peak, dec) => { const g = ac.createGain(); g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(peak, t + 0.01); g.gain.exponentialRampToValueAtTime(0.0001, t + dec); g.connect(master); return g; };
            const tone = (type, f0, f1, t, dur, peak) => { const o = ac.createOscillator(); o.type = type; o.frequency.setValueAtTime(f0, t); o.frequency.exponentialRampToValueAtTime(f1, t + dur * 0.8); o.connect(gainEnv(t, peak, dur)); o.start(t); o.stop(t + dur + 0.05); };
            const filt = (type, f, q) => { const b = ac.createBiquadFilter(); b.type = type; b.frequency.value = f; b.Q.value = q || 1; return b; };
            const thump = (t, v) => {
                tone('sine', 170, 55, t, 0.34, v); tone('triangle', 320, 150, t, 0.12, v * 0.45);
                const n = noise(0.09); const f = filt('lowpass', 1200); n.connect(f); f.connect(gainEnv(t, v * 0.6, 0.08)); n.start(t);
                const k = noise(0.05); const kb = filt('bandpass', 900, 2); k.connect(kb); kb.connect(gainEnv(t, v * 0.35, 0.05)); k.start(t);
            };
            const creak = (t, dur) => {
                const o = ac.createOscillator(); o.type = 'sawtooth';
                o.frequency.setValueAtTime(58, t); o.frequency.linearRampToValueAtTime(96, t + dur * 0.45); o.frequency.linearRampToValueAtTime(64, t + dur);
                const lfo = ac.createOscillator(); lfo.frequency.value = 15; const lg = ac.createGain(); lg.gain.value = 9; lfo.connect(lg); lg.connect(o.frequency);
                const bp = filt('bandpass', 420, 7); bp.frequency.setValueAtTime(300, t); bp.frequency.linearRampToValueAtTime(780, t + dur * 0.55); bp.frequency.linearRampToValueAtTime(380, t + dur);
                const g = ac.createGain(); g.gain.setValueAtTime(0.0001, t); g.gain.linearRampToValueAtTime(0.22, t + 0.35); g.gain.linearRampToValueAtTime(0.16, t + dur * 0.7); g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
                o.connect(bp); bp.connect(g); g.connect(master); o.start(t); lfo.start(t); o.stop(t + dur + 0.05); lfo.stop(t + dur + 0.05);
                [0.15, 0.55, 1.0, 1.45, 1.9, 2.4, 2.9, 3.4].forEach((d, i) => { if (d < dur) tone('sine', 900 + i * 80, 1250 + i * 55, t + d, 0.22, 0.032); });
            };
            const pad = (t, dur) => {
                [196, 246.94, 293.66, 392, 493.88].forEach((f, i) => {
                    const o = ac.createOscillator(); o.type = i % 2 ? 'sine' : 'triangle'; o.frequency.value = f;
                    const g = ac.createGain(); g.gain.setValueAtTime(0.0001, t); g.gain.linearRampToValueAtTime(0.045, t + dur * 0.4); g.gain.linearRampToValueAtTime(0.0001, t + dur);
                    o.connect(g); g.connect(master); o.start(t); o.stop(t + dur + 0.05);
                });
            };
            const chirp = (t) => { const f0 = 2400 + Math.random() * 1500, n = 2 + ((Math.random() * 3) | 0); for (let k = 0; k < n; k++) tone('sine', f0 + k * 120, f0 * 1.35 + k * 140, t + k * 0.11, 0.09, 0.035); };
            return {
                knocks: () => { const t = ac.currentTime; thump(t + 0.25, 0.55); thump(t + 0.75, 0.65); thump(t + 1.25, 1.0); },
                latch: () => {
                    const t = ac.currentTime;
                    const sc = noise(0.3); const sb = filt('bandpass', 1800, 4); sc.connect(sb); sb.connect(gainEnv(t, 0.12, 0.28)); sc.start(t);
                    tone('square', 2200, 1400, t + 0.3, 0.05, 0.12); tone('square', 1500, 900, t + 0.4, 0.06, 0.1); tone('sine', 90, 50, t + 0.43, 0.3, 0.55);
                },
                open: () => {
                    const t = ac.currentTime + 0.02;
                    creak(t, 2.8);
                    tone('sine', 110, 28, t, 1.8, 0.95);
                    pad(t + 0.3, 7);
                    const rb = noise(3.4); const rl = filt('lowpass', 220); rb.connect(rl); rl.connect(gainEnv(t, 0.22, 3.2)); rb.start(t);
                    const sp = noise(0.15); const hp = filt('highpass', 1500); sp.connect(hp); hp.connect(gainEnv(t, 0.5, 0.14)); sp.start(t);
                    const wh = noise(2.8); const wb = filt('bandpass', 400, 1.2); wb.frequency.setValueAtTime(400, t); wb.frequency.exponentialRampToValueAtTime(2800, t + 0.9); wb.frequency.exponentialRampToValueAtTime(600, t + 2.6);
                    wh.connect(wb); wb.connect(gainEnv(t, 0.35, 2.7)); wh.start(t);
                    // doors reach the stop
                    tone('sine', 95, 40, t + 2.3, 0.8, 0.8);
                    const st = noise(0.12); const sl = filt('lowpass', 700); st.connect(sl); sl.connect(gainEnv(t + 2.3, 0.45, 0.12)); st.start(t + 2.3);
                },
                chime: () => {
                    const t = ac.currentTime;
                    [784, 988, 1175, 1568, 1976].forEach((f, i) => { tone('sine', f, f * 0.999, t + i * 0.11, 1.2, 0.16); tone('triangle', f * 2, f * 2, t + i * 0.11, 0.7, 0.05); });
                    [1568, 1976, 2349, 2637].forEach((f, i) => { tone('sine', f, f * 0.999, t + 1.2 + i * 0.14, 1.1, 0.07); });
                    [1175, 1568, 1976, 2349, 3136].forEach((f, i) => { tone('sine', f, f * 0.999, t + 2.6 + i * 0.12, 1.4, 0.06); });
                },
                chirps: () => { const t = ac.currentTime; for (let i = 0; i < 14; i++) chirp(t + 0.2 + Math.random() * 11); }
            };
        } catch (e) { return null; }
    }

    window.openInvitation = function() {
        if (introStarted) return; introStarted = true;
        const audio = document.getElementById('bgMusic'); const intro = document.getElementById('intro-anim');
        try { audio.volume = 0.18; } catch (e) {}
        if (audio.paused) { audio.play().catch(e => console.log("Audio autoplay failed", e)); }
        // Respect "reduce motion": skip the intro
        if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { window.finishIntro(); return; }
        window.__introBusy = true; // pause the background snow canvas while intro plays
        try { const sc = document.getElementById('snow-canvas'); if (sc) sc.style.visibility = 'hidden'; } catch (e) {}
        const conn = navigator.connection || {};
        const lite = (navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4) || (navigator.deviceMemory && navigator.deviceMemory <= 2) || conn.saveData || /2g/.test(conn.effectiveType || '');
        if (lite) intro.classList.add('lite');
        if (!lite) {
            const stars = document.getElementById('i-stars'); const frag = document.createDocumentFragment();
            for (let i = 0; i < 40; i++) { const st = document.createElement('div'); st.className = 'i-star'; st.style.left = (Math.random()*100)+'%'; st.style.top = (Math.random()*100)+'%'; st.style.animationDelay = (Math.random()*2)+'s'; frag.appendChild(st); }
            stars.appendChild(frag);
            const f2 = document.createDocumentFragment();
            for (let i = 0; i < 14; i++) {
                const h = document.createElement('div'); h.className = 'i-spark'; h.innerHTML = '&#10022;';
                h.style.left = (Math.random() * 100) + '%'; h.style.fontSize = (12 + Math.random() * 14) + 'px';
                h.style.animationDuration = (4 + Math.random() * 3) + 's'; h.style.animationDelay = (Math.random() * 3) + 's';
                f2.appendChild(h);
            }
            intro.appendChild(f2);
        }
        // couple photos: preload, and use a single frame when groom and bride share the same photo
        const ph = intro.querySelectorAll('.cph'); const cpw = intro.querySelector('.i-couple');
        const urlOf = (el) => { const b = el.style.backgroundImage || ''; return b.length > 7 ? b.slice(5, -2) : ''; };
        const gU = urlOf(ph[0]), bU = urlOf(ph[1]);
        if (gU === bU) cpw.classList.add('solo');
        const load = (u) => new Promise(res => { if (!u) return res(); const im = new Image(); im.onload = im.onerror = () => res(); im.src = u; });
        const wait = (ms) => new Promise(r => setTimeout(r, ms));
        const imgP = Promise.race([Promise.all([load(gU), load(bU)]), wait(4500)]);
        const sfx = makeDoorSfx();
        if (sfx) sfx.knocks();
        // 2.5s: the latch unlocks
        setTimeout(() => { if (introDone) return; intro.classList.add('d-latch'); if (sfx) sfx.latch(); }, 1600);
        // ~2.9s: doors swing open (creak + boom + light burst), then chimes, birds and butterflies
        const doorP = new Promise(resolve => {
            Promise.all([imgP, wait(2050)]).then(() => {
                if (introDone) return resolve();
                intro.classList.add('d-go'); spawnBurst(lite);
                if (sfx) { sfx.open(); setTimeout(sfx.chime, 1500); }
                setTimeout(() => { let v = 0.18; const iv = setInterval(() => { v = Math.min(1, v + 0.04); try { audio.volume = v; } catch (e) {} if (v >= 1 || introDone) clearInterval(iv); }, 150); }, 1200);
                setTimeout(() => { if (introDone) return; spawnFlyers(lite); if (sfx) sfx.chirps(); }, 1700);
                setTimeout(resolve, 2600);
            });
        });
        requestAnimationFrame(() => requestAnimationFrame(() => intro.classList.add('show')));
        // names appear once the doors are open and fonts are ready; hold, then bloom out
        Promise.all([introPreload.fonts, wait(3200), doorP]).then(() => {
            if (introDone) return;
            intro.classList.add('txt-on');
            setTimeout(() => { if (!introDone) intro.classList.add('d-end'); }, 9800);
            introTimer = setTimeout(window.finishIntro, 10800);
        });
        // Safety net: never get stuck
        setTimeout(() => { if (!introDone) window.finishIntro(); }, 32000);
    };`;

// បន្ថែម Intro ចូលក្នុង Template ណាមួយ (មិនប៉ះពាល់ Template ដើម)
// pack = { css, html, js } ប្រសិនមិនដាក់ → ប្រើ Intro ព្រះអាទិត្យរះ (Golden Dawn)
const withIntro = (tpl, pack = {}) => {
    const P_CSS = pack.css || INTRO_CSS, P_HTML = pack.html || INTRO_HTML, P_JS = pack.js || INTRO_JS;
    const swap = (src, from, to, label) => {
        if (!src.includes(from)) { console.warn('[withIntro] anchor not found:', label); return src; }
        return src.replace(from, () => to);
    };
    let out = String(tpl);
    out = swap(out, `<audio id="bgMusic" loop><source`, `<audio id="bgMusic" loop preload="auto"><source`, 'audio');
    out = swap(out, `    /* 1. Cover must be the HIGHEST layer */`, P_CSS + `    /* 1. Cover must be the HIGHEST layer */`, 'css');
    out = swap(out, `</div> 
  <div class="container">`, `</div> \n` + P_HTML + `  <div class="container">`, 'html');
    out = swap(out, `    window.openInvitation = function() { const cover = document.getElementById('invite-cover'); const audio = document.getElementById('bgMusic'); cover.classList.add('open'); if(audio.paused) { audio.play().catch(e => console.log("Audio autoplay failed", e)); } };`, P_JS, 'js');
    out = swap(out, `function animateSnow() {
                drawFlakes();`, `function animateSnow() {\n                if (!window.__introBusy) drawFlakes();`, 'snow1');
    out = swap(out, `var animateSnow = function () {
                drawFlakes();`, `var animateSnow = function () {\n                if (!window.__introBusy) drawFlakes();`, 'snow2');
    return out;
};

// ៣ ម៉ូតថ្មី (មាន Intro) — ចម្លងពី Classic / Royal / Luxury ដើម ប៉ុន្តែដាក់ឈ្មោះថ្មី
// ===========================================================
// ✨ PREMIUM FX — ស្រទាប់ Animation បន្ថែមសម្រាប់ Intro ម៉ូត Premium ទាំង ៣
// (ធូលីមាសអណ្តែត, ផ្កាយភ្លឺ, ពន្លឺបោសកាត់, រង្វង់ផ្ទុះ, Halo ក្រោយឈ្មោះ)
// ប្រើតែ transform/opacity → រលូន មិនធ្ងន់ ហើយបិទដោយស្វ័យប្រវត្តិពេលអ្នកប្រើកំណត់ reduce-motion
// ===========================================================
const PREMIUM_FX_CSS = `
    /* ===== PREMIUM FX LAYER: floating dust, twinkling stars, light sweep, shock rings, halo ===== */
    #intro-anim{--fxr:255,211,107}
    #intro-anim[data-intro=sunrise]{--fxr:255,205,120}
    #intro-anim[data-intro=door-royal]{--fxr:255,200,87}
    #intro-anim[data-intro=door-luxury]{--fxr:245,227,161}
    .fx-l{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;z-index:9;overflow:hidden}
    .fx-vig{background:radial-gradient(ellipse at 50% 45%,transparent 52%,rgba(0,0,0,.5) 100%);opacity:0;animation:fxFade 2s ease-out .2s forwards}
    .fx-halo{z-index:4;opacity:0;background:radial-gradient(circle at 50% 62%,rgba(var(--fxr),.34),rgba(var(--fxr),.1) 34%,transparent 62%)}
    #intro-anim.txt-on .fx-halo{animation:fxHalo 3.8s ease-in-out infinite alternate}
    .fx-dust i{position:absolute;left:var(--x);bottom:-16px;width:var(--z);height:var(--z);border-radius:50%;background:radial-gradient(circle,#fff 0,rgb(var(--fxr)) 45%,transparent 72%);opacity:0;animation:fxUp var(--d) linear var(--l) infinite;will-change:transform,opacity}
    .fx-dust i.s{bottom:auto;top:var(--y);border-radius:0;background:linear-gradient(#fff,rgb(var(--fxr)));clip-path:polygon(50% 0,59% 41%,100% 50%,59% 59%,50% 100%,41% 59%,0 50%,41% 41%);animation:fxTw var(--d) ease-in-out var(--l) infinite}
    .fx-sheen::before{content:'';position:absolute;top:-10%;bottom:-10%;left:-40%;width:30%;background:linear-gradient(100deg,transparent,rgba(255,255,255,.3),rgba(var(--fxr),.2),transparent);transform:translateX(-120%) skewX(-18deg);opacity:0}
    #intro-anim.d-go .fx-sheen::before{animation:fxSheen 1.9s ease-out .15s forwards}
    #intro-anim.txt-on .fx-sheen::before{animation:fxSheen2 2.4s ease-in-out .5s forwards}
    .fx-ring{position:absolute;left:50%;top:24%;width:120px;height:120px;margin:-60px 0 0 -60px;border-radius:50%;border:2px solid rgb(var(--fxr));box-shadow:0 0 26px rgba(var(--fxr),.85),inset 0 0 26px rgba(var(--fxr),.5);opacity:0;z-index:9;pointer-events:none}
    #intro-anim.d-go .fx-ring,#intro-anim.ph-on .fx-ring{animation:fxRing 2.3s cubic-bezier(.1,.7,.3,1) forwards}
    #intro-anim.d-go .fx-ring.r2,#intro-anim.ph-on .fx-ring.r2{animation-delay:.4s}
    #intro-anim.hide :is(.fx-dust i,.fx-halo){animation-play-state:paused}
    @keyframes fxFade{to{opacity:1}}
    @keyframes fxUp{0%{transform:translate3d(0,0,0) scale(.5);opacity:0}12%{opacity:.95}85%{opacity:.7}100%{transform:translate3d(var(--dx),-110vh,0) scale(1.1);opacity:0}}
    @keyframes fxTw{0%,100%{opacity:0;transform:scale(.15) rotate(0)}50%{opacity:1;transform:scale(1) rotate(90deg)}}
    @keyframes fxSheen{0%{transform:translateX(-120%) skewX(-18deg);opacity:0}15%{opacity:1}100%{transform:translateX(560%) skewX(-18deg);opacity:0}}
    @keyframes fxSheen2{0%{transform:translateX(-120%) skewX(-18deg);opacity:0}15%{opacity:1}100%{transform:translateX(560%) skewX(-18deg);opacity:0}}
    @keyframes fxRing{0%{transform:scale(.3);opacity:.9}100%{transform:scale(7);opacity:0}}
    @keyframes fxHalo{0%{opacity:.55;transform:scale(.94)}100%{opacity:1;transform:scale(1.06)}}
    @media (prefers-reduced-motion:reduce){.fx-l,.fx-ring{display:none}}
`;
const PREMIUM_FX_HTML = `    <div class="fx-l fx-vig"></div><div class="fx-l fx-halo"></div>
    <div class="fx-l fx-dust" id="fx-dust"></div><div class="fx-l fx-sheen"></div>
    <div class="fx-ring"></div><div class="fx-ring r2"></div>
`;
const PREMIUM_FX_JS = `
    ;(function () {
        try {
            var d = document.getElementById('fx-dust'); if (!d) return;
            var lite = (navigator.hardwareConcurrency || 4) <= 4 || window.innerWidth < 500;
            var n = lite ? 20 : 34, h = '', i, r = Math.random;
            for (i = 0; i < n; i++) {
                var star = i % 3 === 0;
                var z = star ? (9 + r() * 12) : (2 + r() * 5);
                var st = '--x:' + (r() * 100).toFixed(1) + '%;--z:' + z.toFixed(1) + 'px;--d:' + (star ? 2.2 + r() * 2.6 : 6 + r() * 7).toFixed(1) + 's;--l:' + (r() * (star ? 6 : 8)).toFixed(1) + 's;--dx:' + ((r() - 0.5) * 90).toFixed(0) + 'px;';
                if (star) st += '--y:' + (14 + r() * 76).toFixed(1) + '%;';
                h += '<i' + (star ? ' class="s"' : '') + ' style="' + st + '"></i>';
            }
            d.innerHTML = h;
        } catch (e) {}
    })();

    // ---- Bird chirps (synthesised with WebAudio; plays once when the scene opens) ----
    ;(function () {
        try {
            var it = document.getElementById('intro-anim'); if (!it) return;
            var AC = window.AudioContext || window.webkitAudioContext; if (!AC) return;
            var ctx = null, own = false, bus = null, played = false, r = Math.random;
            function ensure() {
                if (!ctx) { ctx = window.__introAC || new AC(); own = !window.__introAC; }
                if (ctx.state === 'suspended' && ctx.resume) { try { ctx.resume(); } catch (e) {} }
                return ctx;
            }
            function makeBus() {
                bus = ctx.createGain(); bus.gain.value = 1;
                var dl = ctx.createDelay(0.5), fb = ctx.createGain(), wet = ctx.createGain();
                dl.delayTime.value = 0.17; fb.gain.value = 0.28; wet.gain.value = 0.35;
                bus.connect(ctx.destination); bus.connect(dl); dl.connect(fb); fb.connect(dl); dl.connect(wet); wet.connect(ctx.destination);
            }
            function tweet(t, base, pan, vol) {
                var n = 2 + Math.floor(r() * 4);
                for (var i = 0; i < n; i++) {
                    var dur = 0.05 + r() * 0.07, f1 = base * (0.9 + r() * 0.35);
                    var f2 = r() > 0.4 ? f1 * (1.25 + r() * 0.35) : f1 * (0.65 + r() * 0.2);
                    var o = ctx.createOscillator(), g = ctx.createGain(), m = ctx.createOscillator(), mg = ctx.createGain();
                    o.type = 'sine'; o.frequency.setValueAtTime(f1, t); o.frequency.exponentialRampToValueAtTime(f2, t + dur);
                    m.frequency.value = 30 + r() * 30; mg.gain.value = f1 * 0.04; m.connect(mg); mg.connect(o.frequency);
                    g.gain.setValueAtTime(0.0001, t); g.gain.exponentialRampToValueAtTime(vol, t + 0.012); g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
                    o.connect(g);
                    if (ctx.createStereoPanner) { var p = ctx.createStereoPanner(); p.pan.value = pan; g.connect(p); p.connect(bus); } else { g.connect(bus); }
                    o.start(t); m.start(t); o.stop(t + dur + 0.03); m.stop(t + dur + 0.03);
                    t += dur + 0.03 + r() * 0.05;
                }
                return t;
            }
            function play() {
                if (played) return; played = true;
                try {
                    ensure(); makeBus();
                    var bases = [3200, 2500, 3900], pans = [-0.6, 0.5, 0.1];
                    for (var b = 0; b < 3; b++) {
                        var t = ctx.currentTime + 0.1 + b * 0.7;
                        for (var k = 0; k < 4; k++) { t = tweet(t, bases[b], pans[b], 0.11) + 0.5 + r() * 0.9; }
                    }
                } catch (e) {}
            }
            var mo = new MutationObserver(function () {
                var c = it.classList;
                if (!played && (c.contains('d-go') || c.contains('ph-on'))) { setTimeout(play, 500); played = false; }
                if (!played && c.contains('txt-on')) play();
                if (c.contains('hide') && bus) {
                    try { bus.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.6); } catch (e) {}
                    if (own) setTimeout(function () { try { ctx.close(); } catch (e) {} }, 1200);
                    mo.disconnect();
                }
            });
            mo.observe(it, { attributes: true, attributeFilter: ['class'] });
            // browsers need a user gesture before audio: unlock on first tap if still suspended
            var unlock = function () { if (ctx && ctx.state === 'suspended') { try { ctx.resume(); } catch (e) {} } };
            document.addEventListener('pointerdown', unlock, { once: true });
        } catch (e) {}
    })();
`;
const withPremiumFx = (introHtml) => introHtml.replace('<div class="intro-content">', () => PREMIUM_FX_HTML + '    <div class="intro-content">');

// ===========================================================
// 🎬 CINEMATIC PRE-WEDDING INTRO (Golden Dawn)
// Cover → ឈ្មោះគូស្វាមីភរិយា → រូបអនុស្សាវរីយ៍ (ពី Gallery) → កាលបរិច្ឆេទ → Welcome → បើកសំបុត្រ
// - ប្រើទិន្នន័យដើម: {{COVER_IMAGE}} {{GALLERY_JSON}} {{GROOM_EN}} {{BRIDE_EN}} {{DATE_KH}} {{PROGRAM_TITLE}} និង <audio id="bgMusic">
// - ប្រើតែ opacity/transform (CSS) → ស្រាល រលូនលើទូរស័ព្ទ; គ្មាន Library ថ្មី
// - កែចំនួនរូប/ល្បឿន៖ CINE_MAX_PHOTOS និង CINE_HOLD ក្នុង CINE_JS
// ===========================================================
const CINE_CSS = `    /* ===== CINEMATIC INTRO (pre-wedding film) ===== */
    #intro-anim { position:fixed; top:0; left:0; width:100%; height:100%; z-index:20500; overflow:hidden; background:#050403; opacity:0; visibility:hidden; pointer-events:none; transition:opacity 1s ease, visibility 1s ease; contain:layout paint; -webkit-tap-highlight-color:transparent; touch-action:manipulation; }
    @supports (height:100dvh) { #intro-anim { height:100dvh; } }
    #intro-anim.show { opacity:1; visibility:visible; pointer-events:auto; }
    #intro-anim.hide { opacity:0; visibility:hidden; pointer-events:none; }
    #intro-anim .cine-stage, #intro-anim .cine-ov { position:absolute; top:0; left:0; width:100%; height:100%; pointer-events:none; }
    #intro-anim .cine-ph { position:absolute; top:0; left:0; width:100%; height:100%; opacity:0; transition:opacity 1.4s ease; overflow:hidden; backface-visibility:hidden; }
    #intro-anim .cine-ph.slow { transition-duration:2.4s; }
    #intro-anim .cine-ph.on { opacity:1; }
    #intro-anim .cine-bd { position:absolute; top:-6%; left:-6%; width:112%; height:112%; background:#0b0907 center / cover no-repeat; filter:blur(22px) brightness(.55) saturate(1.1); }
    #intro-anim.lite .cine-bd { filter:none; }
    #intro-anim .cine-fg { position:absolute; top:0; left:0; width:100%; height:100%; background:center 30% / cover no-repeat; will-change:transform; animation-timing-function:linear; animation-fill-mode:both; animation-duration:10s; }
    #intro-anim .cine-fg.fit { background-size:contain; background-position:center center; }
    #intro-anim .cine-ph.m0 .cine-fg { animation-name:cineIn; }
    #intro-anim .cine-ph.m1 .cine-fg { animation-name:cineOut; }
    #intro-anim .cine-ph.m2 .cine-fg { animation-name:cinePan; }
    #intro-anim .cine-ph.m3 .cine-fg { animation-name:cineRise; }
    #intro-anim .cine-shade { background:radial-gradient(ellipse at 50% 42%, rgba(0,0,0,0) 38%, rgba(0,0,0,.55) 100%), linear-gradient(180deg, rgba(14,6,0,.5) 0%, rgba(14,6,0,.06) 30%, rgba(14,6,0,.1) 56%, rgba(14,6,0,.74) 100%); z-index:2; }
    #intro-anim .cine-deep { background:rgba(10,5,0,.5); opacity:0; transition:opacity 1.4s ease; z-index:3; }
    #intro-anim .cine-deep.on { opacity:1; }
    #intro-anim .cine-light { top:-10%; left:-20%; width:140%; height:80%; background:radial-gradient(ellipse at 40% 40%, rgba(255,196,120,.34), rgba(255,160,80,.1) 45%, transparent 70%); opacity:.4; animation:cineLight 9s ease-in-out infinite alternate; z-index:4; will-change:transform,opacity; }
    #intro-anim.lite .cine-light { display:none; }
    #intro-anim .cine-t { position:absolute; top:0; left:0; width:100%; height:100%; box-sizing:border-box; display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; padding:calc(env(safe-area-inset-top,0px) + 24px) 24px calc(env(safe-area-inset-bottom,0px) + 84px); pointer-events:none; z-index:6; }
    #intro-anim .cine-t.low { justify-content:flex-end; padding-bottom:calc(env(safe-area-inset-bottom,0px) + 104px); }
    #intro-anim .cine-t > * { opacity:0; transform:translate3d(0,12px,0); transition:opacity .8s ease, transform .8s ease; max-width:560px; }
    #intro-anim .cine-t.on > * { opacity:1; transform:translate3d(0,0,0); transition:opacity 1.4s ease, transform 1.8s cubic-bezier(.2,.7,.2,1); }
    #intro-anim .cine-t.on > *:nth-child(2) { transition-delay:.45s; }
    #intro-anim .cine-t.on > *:nth-child(3) { transition-delay:.9s; }
    #intro-anim .cine-t.on > *:nth-child(4) { transition-delay:1.35s; }
    #intro-anim .cine-t.on > *:nth-child(5) { transition-delay:1.8s; }
    #intro-anim .cine-t > .cine-line { width:min(60%,220px); height:1px; margin:16px auto 14px; background:linear-gradient(90deg, transparent, #F5D67A, transparent); transform:scaleX(0); }
    #intro-anim .cine-t.on > .cine-line { transform:scaleX(1); }
    #intro-anim .cine-kicker { font-family:'Moul',cursive; color:#F5D67A; font-size:1.05rem; line-height:1.9; text-shadow:0 2px 10px rgba(0,0,0,.85), 0 0 18px rgba(245,214,122,.45); }
    #intro-anim .cine-nm { font-family:'Great Vibes',cursive; color:#fff; font-size:clamp(2.5rem, 12vw, 3.7rem); line-height:1.25; word-break:break-word; text-shadow:0 2px 14px rgba(0,0,0,.8), 0 0 22px rgba(255,214,140,.35); }
    #intro-anim .cine-amp { font-family:'Great Vibes',cursive; color:#F5D67A; font-size:1.9rem; line-height:1.1; text-shadow:0 0 14px rgba(245,214,122,.7); }
    #intro-anim .cine-fam { font-family:'Suwannaphum',serif; font-style:italic; color:rgba(255,255,255,.9); font-size:.98rem; line-height:1.7; text-shadow:0 2px 8px rgba(0,0,0,.9); }
    #intro-anim .cine-date { font-family:'Moul',cursive; color:#fff3c4; font-size:1.2rem; line-height:1.9; text-shadow:0 2px 10px rgba(0,0,0,.9), 0 0 16px rgba(245,214,122,.4); }
    #intro-anim .cine-quote { font-family:'Suwannaphum',serif; font-style:italic; color:#fff; font-size:1.2rem; line-height:1.7; text-shadow:0 2px 12px rgba(0,0,0,.95); }
    #intro-anim .cine-welcome { font-family:'Moul',cursive; color:#F5D67A; font-size:.98rem; line-height:1.9; text-shadow:0 2px 10px rgba(0,0,0,.9), 0 0 16px rgba(245,214,122,.45); }
    #intro-anim .cine-t.fin .cine-nm { font-size:clamp(2.1rem, 9.5vw, 2.9rem); line-height:1.15; }
    #intro-anim .cine-t.fin .cine-date { font-size:1rem; margin-top:6px; }
    #intro-anim .cine-flash { background:radial-gradient(circle at 50% 45%, #fffdf0 0, #ffeab0 48%, #f4d17a 100%); opacity:0; transition:opacity 1.4s ease; z-index:20; }
    #intro-anim .cine-flash.on { opacity:1; }
    #intro-anim .cine-skip { position:absolute; left:50%; bottom:calc(env(safe-area-inset-bottom,0px) + 18px); transform:translateX(-50%); z-index:30; padding:8px 20px; border:1px solid rgba(255,255,255,.32); border-radius:999px; background:rgba(0,0,0,.28); color:rgba(255,255,255,.82); font:600 .68rem 'Kantumruy Pro',sans-serif; letter-spacing:.24em; cursor:pointer; -webkit-backdrop-filter:blur(4px); backdrop-filter:blur(4px); }
    #intro-anim .cine-skip:active { background:rgba(0,0,0,.5); }
    #intro-anim .cine-sound { display:none; position:absolute; top:calc(env(safe-area-inset-top,0px) + 14px); right:14px; z-index:30; padding:7px 14px; border:1px solid rgba(245,214,122,.5); border-radius:999px; background:rgba(0,0,0,.4); color:#F5D67A; font:600 .72rem 'Kantumruy Pro',sans-serif; cursor:pointer; }
    #intro-anim .cine-sound.on { display:block; }
    @media (min-width:768px) { #intro-anim .cine-kicker { font-size:1.25rem; } #intro-anim .cine-nm { font-size:4.2rem; } #intro-anim .cine-date { font-size:1.45rem; } #intro-anim .cine-quote { font-size:1.45rem; } #intro-anim .cine-welcome { font-size:1.15rem; } }
    @media (max-height:640px) { #intro-anim .cine-nm { font-size:2.2rem; } #intro-anim .cine-t { padding-bottom:calc(env(safe-area-inset-bottom,0px) + 64px); } }
    @keyframes cineIn { from { transform:scale(1) translate3d(0,0,0); } to { transform:scale(1.13) translate3d(0,-1.2%,0); } }
    @keyframes cineOut { from { transform:scale(1.14) translate3d(0,0,0); } to { transform:scale(1.02) translate3d(0,0,0); } }
    @keyframes cinePan { from { transform:scale(1.1) translate3d(2.5%,0,0); } to { transform:scale(1.1) translate3d(-2.5%,0,0); } }
    @keyframes cineRise { from { transform:scale(1.1) translate3d(0,2.2%,0); } to { transform:scale(1.1) translate3d(0,-2.2%,0); } }
    @keyframes cineLight { from { opacity:.25; transform:translate3d(-5%,0,0); } to { opacity:.6; transform:translate3d(5%,-3%,0); } }
`;

const CINE_HTML = `  <div id="intro-anim" data-intro="{{INTRO_STYLE}}">
    <div class="cine-stage" id="cine-stage"></div>
    <div class="cine-ov cine-shade"></div>
    <div class="cine-ov cine-deep" id="cine-deep"></div>
    <div class="cine-ov cine-light"></div>
    <div class="cine-t" id="cine-t-title"><div class="cine-kicker">{{PROGRAM_TITLE}}</div><div class="cine-line"></div></div>
    <div class="cine-t" id="cine-t-names"><div class="cine-nm">{{GROOM_EN}}</div><div class="cine-amp">&amp;</div><div class="cine-nm">{{BRIDE_EN}}</div><div class="cine-line"></div><div class="cine-fam">ជាមួយក្រុមគ្រួសារទាំងសងខាង</div></div>
    <div class="cine-t low" id="cine-t-quote"><div class="cine-quote">បេះដូងពីរ ដំណើរតែមួយ</div></div>
    <div class="cine-t" id="cine-t-date"><div class="cine-date">{{DATE_KH}}</div><div class="cine-line"></div><div class="cine-quote">ដំណើរថ្មីចាប់ផ្ដើមពីទីនេះ</div></div>
    <div class="cine-t fin" id="cine-t-final"><div class="cine-nm">{{GROOM_EN}} &amp; {{BRIDE_EN}}</div><div class="cine-date">{{DATE_KH}}</div><div class="cine-line"></div><div class="cine-welcome">សូមស្វាគមន៍មកកាន់សិរីមង្គលអាពាហ៍ពិពាហ៍របស់យើង</div></div>
    <div class="cine-ov cine-flash" id="cine-flash"></div>
    <button type="button" class="cine-sound" id="cine-sound" onclick="cineSound(event)">♪ ចុចដើម្បីបើកសំឡេង</button>
    <button type="button" class="cine-skip" onclick="cineSkip(event)">SKIP INTRO</button>
  </div>
`;

const CINE_JS = `    (function () {
    var CINE_MAX_PHOTOS = 4, CINE_HOLD = 2.5; // ចំនួនរូបអតិបរមា និងរយៈពេលរូបនីមួយៗ (វិនាទី)
    var CINE_COVER = '{{COVER_IMAGE}}';
    var introDone = false, introStarted = false, cineStop = false, cineLite = false, cineTimers = [];
    var cineEl = function (id) { return document.getElementById(id); };
    var cineLater = function (fn, ms) { cineTimers.push(setTimeout(function () { if (!introDone && !cineStop) fn(); }, ms)); };
    var cineSleep = function (ms) { return new Promise(function (r) { cineLater(r, ms); }); };

    var cineFade = function (au, to, ms) {
        try {
            var from = au.volume, t0 = performance.now();
            var step = function () {
                var k = Math.min(1, (performance.now() - t0) / ms);
                au.volume = Math.max(0, Math.min(1, from + (to - from) * k));
                if (k < 1) requestAnimationFrame(step);
            };
            step();
        } catch (e) {}
    };

    var cineLoad = function (src, ms) {
        return new Promise(function (res) {
            if (!src || src.indexOf('{{') > -1) { res(null); return; }
            var im = new Image(), done = false;
            var fin = function (v) { if (done) return; done = true; res(v); };
            setTimeout(function () { fin(null); }, ms || 7000);
            im.onload = function () { try { if (im.decode) im.decode().catch(function () {}); } catch (e) {} fin(im); };
            im.onerror = function () { fin(null); };
            im.src = src;
        });
    };

    var cineGallery = function () {
        var arr = [];
        try {
            var raw = '{{GALLERY_JSON}}';
            if (raw.indexOf('{{') < 0) arr = JSON.parse(raw.replace(/&quot;/g, '"'));
        } catch (e) {}
        var seen = {}, out = [];
        (Array.isArray(arr) ? arr : []).forEach(function (u) { if (typeof u === 'string' && u && !seen[u]) { seen[u] = 1; out.push(u); } });
        return out;
    };

    var cinePick = function (list, n) {
        if (list.length <= n) return list;
        var out = [];
        for (var i = 0; i < n; i++) out.push(list[Math.round(i * (list.length - 1) / (n - 1))]);
        return out;
    };

    var cineLayer = function (res, src, dur, mo) {
        var el = document.createElement('div'); el.className = 'cine-ph m' + mo;
        var fit = false;
        if (res && res.naturalWidth && res.naturalHeight) {
            var r = res.naturalWidth / res.naturalHeight, v = window.innerWidth / Math.max(1, window.innerHeight);
            fit = Math.max(r / v, v / r) > 1.5; // រូបផ្ដេក/ឈរខុសពីអេក្រង់ខ្លាំង → បង្ហាញទាំងរូប (មិនកាត់មុខ)
        }
        var url = 'url("' + String(src).replace(/["'()]/g, '') + '")';
        if (fit) { var bd = document.createElement('div'); bd.className = 'cine-bd'; if (!cineLite) bd.style.backgroundImage = url; el.appendChild(bd); }
        var fg = document.createElement('div'); fg.className = 'cine-fg' + (fit ? ' fit' : '');
        fg.style.backgroundImage = url; fg.style.animationDuration = dur + 's';
        el.appendChild(fg);
        return el;
    };

    window.finishIntro = function () {
        if (introDone) return; introDone = true;
        cineTimers.forEach(clearTimeout);
        window.__introBusy = false;
        var cover = cineEl('invite-cover'), intro = cineEl('intro-anim');
        if (cover) cover.classList.add('open');
        if (intro) intro.classList.add('hide');
        try { var sc = cineEl('snow-canvas'); if (sc) sc.style.visibility = ''; } catch (e) {}
        var au = cineEl('bgMusic'); if (au) cineFade(au, 1, 2800); // ភ្លេងបន្តទៅក្នុងសំបុត្រ ដោយឡើងសំឡេងវិញយឺតៗ
        setTimeout(function () { if (intro) intro.style.display = 'none'; }, 1300);
    };

    window.cineSkip = function (ev) {
        if (ev && ev.stopPropagation) ev.stopPropagation();
        if (introDone || cineStop) return; cineStop = true;
        var fl = cineEl('cine-flash'); if (fl) { fl.style.transitionDuration = '.6s'; fl.classList.add('on'); }
        var au = cineEl('bgMusic'); if (au) cineFade(au, .35, 500);
        setTimeout(window.finishIntro, 650);
    };

    window.cineSound = function (ev) {
        if (ev && ev.stopPropagation) ev.stopPropagation();
        var au = cineEl('bgMusic'), chip = cineEl('cine-sound');
        try { au.volume = 0; var p = au.play(); if (p && p.then) p.then(function () { if (chip) chip.classList.remove('on'); cineFade(au, 1, 2000); }).catch(function () {}); } catch (e) {}
    };

    var cineRun = async function () {
        var stage = cineEl('cine-stage'), deep = cineEl('cine-deep'), T = {};
        ['title', 'names', 'quote', 'date', 'final'].forEach(function (k) { T[k] = cineEl('cine-t-' + k); });
        var cur = null;
        var swap = function (next) {
            var prev = cur; cur = next;
            stage.appendChild(next); void next.offsetWidth; next.classList.add('on');
            if (prev) cineLater(function () { if (prev.parentNode) prev.parentNode.removeChild(prev); }, 1700);
        };
        var covP = cineLoad(CINE_COVER, 6000);
        var picks = cinePick(cineGallery(), CINE_MAX_PHOTOS);
        var photoPs = picks.map(function (s) { return cineLoad(s, 8000); });
        var fontP = Promise.resolve();
        try { if (document.fonts && document.fonts.load) fontP = Promise.all([document.fonts.load("1em 'Great Vibes'", 'Abc'), document.fonts.load("1em 'Moul'", 'សិរីមង្គល')]).catch(function () {}); } catch (e) {}

        var covRes = await Promise.race([covP, cineSleep(3500).then(function () { return null; })]);
        var cover = cineLayer(covRes, CINE_COVER, 22, 0); cover.classList.add('slow');
        swap(cover);

        // 1) Cover + ចំណងជើង
        await Promise.race([fontP, cineSleep(2500)]);
        await cineSleep(700);
        T.title.classList.add('on'); await cineSleep(3200);
        T.title.classList.remove('on'); await cineSleep(500);

        // 2) ឈ្មោះគូស្វាមីភរិយា
        deep.classList.add('on'); T.names.classList.add('on'); await cineSleep(4800);
        T.names.classList.remove('on'); deep.classList.remove('on'); await cineSleep(600);

        // 3) រូបអនុស្សាវរីយ៍ (តែរូបដែលផ្ទុកបាន)
        var rs = await Promise.all(photoPs), ready = [];
        rs.forEach(function (r, i) { if (r) ready.push({ r: r, s: picks[i] }); });
        var n = ready.length, qi = n > 1 ? 1 : 0, mos = [0, 3, 1, 2];
        for (var i = 0; i < n; i++) {
            var last = i === n - 1;
            swap(cineLayer(ready[i].r, ready[i].s, last ? 16 : CINE_HOLD + 3.2, mos[i % 4]));
            if (i === qi) {
                cineLater(function () { T.quote.classList.add('on'); }, 800);
                cineLater(function () { T.quote.classList.remove('on'); }, CINE_HOLD * 1000 - 200);
            }
            await cineSleep(CINE_HOLD * 1000);
        }
        T.quote.classList.remove('on');

        // 4) កាលបរិច្ឆេទ
        deep.classList.add('on'); T.date.classList.add('on'); await cineSleep(3600);
        T.date.classList.remove('on'); await cineSleep(500);

        // 5) ឈ្មោះចុងក្រោយ + Welcome → ពន្លឺមាស/ស → សំបុត្រ
        T.final.classList.add('on'); await cineSleep(4600);
        var au = cineEl('bgMusic'); if (au) cineFade(au, .4, 1300);
        cineEl('cine-flash').classList.add('on');
        cineLater(window.finishIntro, 1300);
    };

    window.openInvitation = function () {
        if (introStarted) return; introStarted = true;
        var au = cineEl('bgMusic'), intro = cineEl('intro-anim'), chip = cineEl('cine-sound');
        // ចាក់ភ្លេងពេលចុច (user gesture) → ឡើងសំឡេងយឺតៗ; បើ browser រារាំង → បង្ហាញប៊ូតុងបើកសំឡេង
        if (au && au.paused) {
            try { au.volume = 0; } catch (e) {}
            try {
                var p = au.play();
                if (p && p.then) p.then(function () { cineFade(au, 1, 2600); }).catch(function () { try { au.volume = 1; } catch (e) {} if (chip) chip.classList.add('on'); });
                else cineFade(au, 1, 2600);
            } catch (e) { if (chip) chip.classList.add('on'); }
        }
        if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { window.finishIntro(); return; }
        window.__introBusy = true; // pause the background snow canvas while intro plays
        var conn = navigator.connection || {};
        cineLite = !!((navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4) || (navigator.deviceMemory && navigator.deviceMemory <= 2) || conn.saveData || /2g/.test(conn.effectiveType || ''));
        if (cineLite) intro.classList.add('lite');
        requestAnimationFrame(function () { requestAnimationFrame(function () { intro.classList.add('show'); }); });
        cineRun().catch(function () { window.finishIntro(); });
        // Safety net: never get stuck
        setTimeout(function () { if (!introDone) window.finishIntro(); }, 70000);
    };
    })();`;

const CINEMATIC_PACK = { css: CINE_CSS, html: CINE_HTML, js: CINE_JS };

const GOLDEN_DAWN_PACK = { css: INTRO_CSS + PREMIUM_FX_CSS, html: withPremiumFx(INTRO_HTML), js: INTRO_JS + PREMIUM_FX_JS };
// Golden Dawn now uses the cinematic pre-wedding intro (old sunrise pack kept above as GOLDEN_DAWN_PACK for easy rollback)
const GOLDEN_DAWN_TEMPLATE    = withIntro(CLASSIC_GOLD_WEDDING_TEMPLATE, CINEMATIC_PACK);
// ===========================================================
// 💎 DIAMOND LUXE — "Eternal Heart" (Romantic, គ្មានទ្វារ)
// បេះដូងមាសគូរខ្លួនឯង → រូបគូស្វាមីភរិយាផ្ការីកក្នុងបេះដូង → ផ្កាកុលាបជ្រុះ + bokeh ផ្កាយភ្លឺ
// ===========================================================
const ROSE_INTRO_CSS = `
    /* ===== DIAMOND LUXE: "Eternal Heart" romantic intro (no doors) ===== */
    #intro-anim[data-intro=rose]{--hw:min(74vw,38vh,320px);--fxr:255,186,200;--tt:#ffe0ea;--ac:#ff7aa8}
    #intro-anim[data-intro=rose] .rm-bg{left:0;top:0;width:100%;height:100%;z-index:0;background:radial-gradient(ellipse at 50% 26%,#6e1d4c 0,#3a0f36 42%,#150519 100%)}
    #intro-anim[data-intro=rose] .rm-glow{left:50%;top:calc(6vh + var(--hw)*.42);width:calc(var(--hw)*2.4);height:calc(var(--hw)*2.4);z-index:1;border-radius:50%;background:radial-gradient(circle,rgba(255,120,170,.4),rgba(255,170,120,.12) 40%,transparent 66%);transform:translate(-50%,-50%);opacity:0;will-change:transform,opacity}
    #intro-anim.show[data-intro=rose] .rm-glow{animation:rmBreath 4s ease-in-out .3s infinite alternate}
    #intro-anim[data-intro=rose] :is(.rm-bokeh,.rm-petals){left:0;top:0;width:100%;height:100%;overflow:hidden}
    #intro-anim[data-intro=rose] .rm-bokeh{z-index:1}
    #intro-anim[data-intro=rose] .rm-petals{z-index:4}
    .rm-bokeh i{position:absolute;bottom:-70px;left:var(--x);width:var(--s);height:var(--s);border-radius:50%;background:radial-gradient(circle,rgba(255,205,220,.55),rgba(255,150,190,.18) 55%,transparent 70%);opacity:0;animation:rmRise var(--d) ease-in-out var(--l) infinite;will-change:transform,opacity}
    .rm-petals i{position:absolute;top:-30px;left:var(--x);width:var(--s);height:calc(var(--s)*1.25);background:linear-gradient(135deg,#ffc9db,#ff6f9f 60%,#c2386f);border-radius:150% 0 150% 0;opacity:0;animation:rmFall var(--d) linear var(--l) infinite;will-change:transform,opacity}
    #intro-anim[data-intro=rose] .rm-heart{left:50%;top:6vh;width:var(--hw);height:calc(var(--hw)*.92);margin-left:calc(var(--hw)/-2);z-index:3;opacity:0;will-change:transform}
    #intro-anim.show[data-intro=rose] .rm-heart{animation:rmHeartIn 1.4s ease-out .2s forwards,rmBeat 2.6s ease-in-out 4.4s infinite}
    .rm-heart svg{z-index:2;position:absolute;left:0;top:0;width:100%;height:100%;overflow:visible;filter:drop-shadow(0 0 6px rgba(255,160,190,.7))}
    .rm-heart path{fill:none;stroke-linecap:round;stroke-linejoin:round}
    .rm-line{stroke:url(#rmg);stroke-width:1.7;stroke-dasharray:1;stroke-dashoffset:1}
    .rm-in{stroke:#ffd9e6;stroke-width:.5;stroke-dasharray:1;stroke-dashoffset:1;opacity:.55}
    .rm-fill{fill:url(#rmf)!important;opacity:0}
    #intro-anim.show .rm-line{animation:rmDraw 3.6s cubic-bezier(.5,0,.2,1) .5s forwards}
    #intro-anim.show .rm-in{animation:rmDraw 3.6s cubic-bezier(.5,0,.2,1) 1s forwards}
    #intro-anim.show .rm-fill{animation:rmFadeIn 2.2s ease 2.4s forwards}
    .rm-pair{position:absolute;left:0;top:0;width:100%;height:100%;clip-path:url(#rmClip);z-index:1;opacity:0;transform:scale(.92);filter:blur(8px);will-change:transform,opacity}
    .rm-ph{position:absolute;top:0;height:100%;background-size:cover;background-position:center 25%;background-repeat:no-repeat}
    .rm-ph.g{left:0;width:100%}
    .rm-ph.b{left:46%;width:54%;-webkit-mask-image:linear-gradient(90deg,transparent,#000 28%);mask-image:linear-gradient(90deg,transparent,#000 28%)}
    .rm-pair.solo .rm-ph.b{display:none}
    #intro-anim.ph-on .rm-pair{animation:rmBloom 1.8s cubic-bezier(.2,.9,.25,1) forwards,rmZoom 14s ease-out 1.8s forwards}
    #intro-anim.ph-on .rm-fill,#intro-anim.ph-on .rm-in{animation:rmFillOut 1.2s ease forwards}
    .rm-gem{position:absolute;left:50%;top:72%;width:calc(var(--hw)*.07);height:calc(var(--hw)*.07);background:linear-gradient(135deg,#fff,#ffd9e6 40%,#f3c26b);box-shadow:0 0 12px #fff,0 0 28px rgba(255,170,200,.9);transform:translate(-50%,-50%) rotate(45deg) scale(0);opacity:0}
    #intro-anim.ph-on .rm-gem{animation:rmGem 2.4s ease-in-out 1.2s infinite}
    .rm-glint{position:absolute;width:20px;height:20px;background:#fff;clip-path:polygon(50% 0,58% 42%,100% 50%,58% 58%,50% 100%,42% 58%,0 50%,42% 42%);opacity:0;filter:drop-shadow(0 0 6px #fff)}
    .rm-glint.a{left:18%;top:10%}.rm-glint.b{left:74%;top:16%;width:15px;height:15px}
    #intro-anim.ph-on .rm-glint.a{animation:rmGlint 3.4s ease-in-out 1.6s infinite}
    #intro-anim.ph-on .rm-glint.b{animation:rmGlint 3.4s ease-in-out 2.9s infinite}
    #intro-anim[data-intro=rose] .intro-content{margin-top:calc(6vh + var(--hw)*.92 + 8px)}
    #intro-anim[data-intro=rose] .intro-title{color:#ffe0ea;font-size:1.05rem;text-shadow:0 0 14px rgba(255,150,190,.8),0 2px 6px rgba(0,0,0,.9)}
    #intro-anim[data-intro=rose] .intro-nm{color:#fff;text-shadow:0 0 20px rgba(255,140,185,.85),0 0 4px rgba(255,220,160,.6),0 2px 6px rgba(0,0,0,.9)}
    #intro-anim[data-intro=rose] .intro-amp{color:#ff7aa8}
    #intro-anim[data-intro=rose] .intro-line{background:linear-gradient(90deg,transparent,#f3c26b,#ffb3cc,#f3c26b,transparent)}
    #intro-anim[data-intro=rose] .intro-date{color:#ffe9c9}
    @keyframes rmBreath{from{transform:translate(-50%,-50%) scale(.9);opacity:.65}to{transform:translate(-50%,-50%) scale(1.12);opacity:1}}
    @keyframes rmRise{0%{transform:translate3d(0,0,0);opacity:0}20%{opacity:.85}100%{transform:translate3d(var(--dx),-115vh,0);opacity:0}}
    @keyframes rmFall{0%{transform:translate3d(0,-30px,0) rotate(0);opacity:0}10%{opacity:.9}50%{transform:translate3d(var(--dx),55vh,0) rotate(200deg)}100%{transform:translate3d(calc(var(--dx)*-.6),112vh,0) rotate(420deg);opacity:.5}}
    @keyframes rmHeartIn{from{opacity:0;transform:scale(.88)}to{opacity:1;transform:scale(1)}}
    @keyframes rmBeat{0%,70%,100%{transform:scale(1)}14%{transform:scale(1.045)}28%{transform:scale(1)}42%{transform:scale(1.03)}}
    @keyframes rmDraw{to{stroke-dashoffset:0}}
    @keyframes rmFadeIn{to{opacity:1}}
    @keyframes rmBloom{to{opacity:1;transform:scale(1);filter:blur(0)}}
    @keyframes rmZoom{from{transform:scale(1)}to{transform:scale(1.07)}}
    @keyframes rmFillOut{to{opacity:0}}
    @keyframes rmGem{0%,100%{transform:translate(-50%,-50%) rotate(45deg) scale(.5);opacity:.5}50%{transform:translate(-50%,-50%) rotate(225deg) scale(1.2);opacity:1}}
    @keyframes rmGlint{0%,68%,100%{opacity:0;transform:scale(0) rotate(0)}84%{opacity:1;transform:scale(1.2) rotate(90deg)}}
`;
const ROSE_INTRO_HTML = `  <div id="intro-anim" data-intro="{{INTRO_STYLE}}" onclick="finishIntro()">
    <div class="abs rm-bg"></div>
    <div class="abs rm-glow"></div>
    <div class="abs rm-bokeh" id="rm-bokeh"></div>
    <div class="abs rm-heart">
      <div class="rm-pair"><div class="rm-ph g" style="background-image:url('{{GROOM_IMAGE}}')"></div><div class="rm-ph b" style="background-image:url('{{BRIDE_IMAGE}}')"></div></div>
      <svg viewBox="0 0 100 92" aria-hidden="true">
        <defs>
          <clipPath id="rmClip" clipPathUnits="objectBoundingBox"><path transform="scale(.01 .0108696)" d="M50 86 C14 58 2 38 2 25 C2 11 13 3 26 3 C36 3 45 9 50 18 C55 9 64 3 74 3 C87 3 98 11 98 25 C98 38 86 58 50 86 Z"/></clipPath>
          <linearGradient id="rmg" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#fff1c2"/><stop offset=".5" stop-color="#f3c26b"/><stop offset="1" stop-color="#ff9fbd"/></linearGradient>
          <radialGradient id="rmf" cx="50%" cy="35%" r="70%"><stop offset="0" stop-color="#ff7aa8" stop-opacity=".38"/><stop offset="1" stop-color="#7a1a52" stop-opacity=".12"/></radialGradient>
        </defs>
        <path class="rm-fill" d="M50 86 C14 58 2 38 2 25 C2 11 13 3 26 3 C36 3 45 9 50 18 C55 9 64 3 74 3 C87 3 98 11 98 25 C98 38 86 58 50 86 Z"/>
        <path class="rm-in" pathLength="1" transform="translate(5 4.4) scale(.9)" d="M50 86 C14 58 2 38 2 25 C2 11 13 3 26 3 C36 3 45 9 50 18 C55 9 64 3 74 3 C87 3 98 11 98 25 C98 38 86 58 50 86 Z"/>
        <path class="rm-line" pathLength="1" d="M50 86 C14 58 2 38 2 25 C2 11 13 3 26 3 C36 3 45 9 50 18 C55 9 64 3 74 3 C87 3 98 11 98 25 C98 38 86 58 50 86 Z"/>
      </svg>
      <i class="rm-gem"></i><i class="rm-glint a"></i><i class="rm-glint b"></i>
    </div>
    <div class="abs rm-petals" id="rm-petals"></div>
    <div class="intro-content">
      <div class="intro-title">{{PROGRAM_TITLE}}</div>
      <div class="intro-nm g" style="margin-top:12px;">{{GROOM_EN}}</div>
      <div class="intro-amp">&#10084;</div>
      <div class="intro-nm b">{{BRIDE_EN}}</div>
      <div class="intro-line"></div>
      <div class="intro-date">{{DATE_KH}}</div>
    </div>
    <div class="intro-skip">TAP TO SKIP</div>
  </div>
`;
const ROSE_INTRO_JS = `    let introStarted = false, introDone = false, introTimer = null;
    // --- Preload fonts early (helps slow internet) ---
    const introPreload = (function () {
        const timeout = (ms) => new Promise(r => setTimeout(r, ms));
        let fontP = Promise.resolve();
        try {
            if (document.fonts && document.fonts.load) {
                fontP = Promise.all([
                    document.fonts.load("1em 'Great Vibes'", "Abc"),
                    document.fonts.load("1em 'Moul'", "សិរីមង្គលក្ថៃខែឆ្នាំ")
                ]).catch(() => {});
            }
        } catch (e) {}
        return { fonts: Promise.race([fontP, timeout(5000)]) };
    })();

    window.finishIntro = function() {
        if (introDone) return; introDone = true; clearTimeout(introTimer);
        window.__introBusy = false; try { const sc = document.getElementById('snow-canvas'); if (sc) sc.style.visibility = ''; const au = document.getElementById('bgMusic'); if (au) au.volume = 1; } catch (e) {}
        const cover = document.getElementById('invite-cover'); const intro = document.getElementById('intro-anim');
        cover.classList.add('open'); intro.classList.add('hide');
        setTimeout(() => { intro.style.display = 'none'; }, 1200);
    };
    window.openInvitation = function() {
        if (introStarted) return; introStarted = true;
        const audio = document.getElementById('bgMusic'); const intro = document.getElementById('intro-anim');
        if (audio.paused) { audio.play().catch(e => console.log("Audio autoplay failed", e)); }
        if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { window.finishIntro(); return; }
        window.__introBusy = true;
        try { const sc = document.getElementById('snow-canvas'); if (sc) sc.style.visibility = 'hidden'; } catch (e) {}
        const conn = navigator.connection || {};
        const lite = (navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4) || (navigator.deviceMemory && navigator.deviceMemory <= 2) || conn.saveData || /2g/.test(conn.effectiveType || '');
        if (lite) intro.classList.add('lite');
        // falling rose petals + rising bokeh
        const R = (a, b) => a + Math.random() * (b - a);
        let ph1 = '', bk1 = '';
        for (let i = 0; i < (lite ? 9 : 18); i++) { ph1 += '<i style="--x:' + R(0, 96).toFixed(1) + '%;--s:' + R(9, 17).toFixed(1) + 'px;--d:' + R(7, 12).toFixed(1) + 's;--l:' + R(0, 8).toFixed(1) + 's;--dx:' + R(-70, 70).toFixed(0) + 'px"></i>'; }
        for (let i = 0; i < (lite ? 5 : 10); i++) { bk1 += '<i style="--x:' + R(0, 92).toFixed(1) + '%;--s:' + R(24, 70).toFixed(0) + 'px;--d:' + R(9, 16).toFixed(1) + 's;--l:' + R(0, 8).toFixed(1) + 's;--dx:' + R(-40, 40).toFixed(0) + 'px"></i>'; }
        document.getElementById('rm-petals').innerHTML = ph1; document.getElementById('rm-bokeh').innerHTML = bk1;
        // couple photos: preload; one frame when groom and bride share the same photo
        const ph = intro.querySelectorAll('.rm-ph'); const pair = intro.querySelector('.rm-pair');
        const urlOf = (el) => { const b = el.style.backgroundImage || ''; return b.length > 7 ? b.slice(5, -2) : ''; };
        const gU = urlOf(ph[0]), bU = urlOf(ph[1]);
        if (gU === bU) pair.classList.add('solo');
        const load = (u) => new Promise(res => { if (!u) return res(); const im = new Image(); im.onload = im.onerror = () => res(); im.src = u; });
        const wait = (ms) => new Promise(r => setTimeout(r, ms));
        const imgP = Promise.race([Promise.all([load(gU), load(bU)]), wait(4500)]);
        const t0 = performance.now();
        requestAnimationFrame(() => requestAnimationFrame(() => intro.classList.add('show')));
        // portraits bloom inside the heart once the heart is mostly drawn and images are ready
        imgP.then(() => { setTimeout(() => { if (!introDone) intro.classList.add('ph-on'); }, Math.max(0, 2300 - (performance.now() - t0))); });
        // names appear when fonts are ready; hold longer so guests can enjoy the scene
        Promise.all([introPreload.fonts, wait(3300)]).then(() => {
            if (introDone) return;
            intro.classList.add('txt-on');
            introTimer = setTimeout(window.finishIntro, 8800);
        });
        // Safety net: never get stuck
        setTimeout(() => { if (!introDone) window.finishIntro(); }, 22000);
    };`;
const ROSE_PACK = { css: INTRO_CSS + ROSE_INTRO_CSS + PREMIUM_FX_CSS, html: withPremiumFx(ROSE_INTRO_HTML), js: ROSE_INTRO_JS + PREMIUM_FX_JS };

const DOOR_PACK = { css: INTRO_CSS + DOOR_INTRO_CSS + PREMIUM_FX_CSS, html: withPremiumFx(DOOR_INTRO_HTML), js: DOOR_INTRO_JS + PREMIUM_FX_JS };
const ROYAL_CRIMSON_TEMPLATE  = withIntro(ROYAL_GOLD_TEMPLATE, DOOR_PACK);
const DIAMOND_LUXE_TEMPLATE   = withIntro(LUXURY_GOLD_TEMPLATE, ROSE_PACK);

// ===========================================================
// 🎁💍 PREMIUM សម្រាប់ "កាត់ចំណងដៃ" និង "ភ្ជាប់ពាក្យ"
// យកម៉ូត Premium ទាំង ៣ (Golden Dawn / Royal Crimson / Diamond Luxe) មកប្តូរតែពិធី
// - ចំណងជើង intro / ពាក្យ Celebration ត្រូវបានប្តូរតាម id (gift-… / eng-…) ដោយ getProgramTitle + applyCeremonyRules
// - បន្ថែមម៉ូតថ្មី → ត្រូវដាក់ id ជា "gift-<style>" ឬ "eng-<style>" (style = golden-dawn | royal-crimson | luxury-diamond)
// ===========================================================
const toGiftReceptionTemplate = (tpl) => tpl
    .replace(/សិរីមង្គលអាពាហ៍ពិពាហ៍/g, "សិរីសួស្ដីពិធីកាត់ចំណងដៃ")
    .replace(/Wedding Celebration/g, "Gift Reception")
    .replace(/Wedding Invitation/g, "Gift Reception");
const toEngagementPremiumTemplate = (tpl) => tpl
    .replace(/សិរីមង្គលអាពាហ៍ពិពាហ៍/g, "ពិធីភ្ជាប់ពាក្យ")
    .replace(/Wedding Celebration/g, "Engagement Ceremony")
    .replace(/Wedding Invitation/g, "Engagement Ceremony");

const PREMIUM_STYLES = [
    { key: 'golden-dawn',    name: 'GOLDEN DAWN',  tpl: GOLDEN_DAWN_TEMPLATE,   giftAssets: 'gift-classic', engAssets: 'eng-classic' },
    { key: 'royal-crimson',  name: 'ROYAL CRIMSON', tpl: ROYAL_CRIMSON_TEMPLATE, giftAssets: 'gift-royal',   engAssets: 'eng-royal' },
    { key: 'luxury-diamond', name: 'DIAMOND LUXE', tpl: DIAMOND_LUXE_TEMPLATE,  giftAssets: 'gift-luxury',  engAssets: 'luxury-diamond' }
];
const PREMIUM_GIFT_TEMPLATES = PREMIUM_STYLES.map(p => ({ id: 'gift-' + p.key, category: 'gift-reception', name: p.name, content: toGiftReceptionTemplate(p.tpl) }));
const PREMIUM_ENG_TEMPLATES  = PREMIUM_STYLES.map(p => ({ id: 'eng-' + p.key,  category: 'engagement',     name: p.name, content: toEngagementPremiumTemplate(p.tpl) }));
PREMIUM_STYLES.forEach(p => {
    ['gift', 'eng'].forEach(prefix => {
        const id = prefix + '-' + p.key;
        TEMPLATE_PRICES[id] = 12.99;            // Premium
        TEMPLATE_ORIGINAL_PRICES[id] = 14.99;   // តម្លៃពេញ (ឆូតកាត់)
        THEME_ASSETS[id] = THEME_ASSETS[prefix === 'gift' ? p.giftAssets : p.engAssets];  // រូប Cover/Background តាមពិធី
    });
});

// ===========================================================
// 🚪 DOOR INTRO (LUXURY GOLD) — ទ្វារខ្មៅ-មាស ២ ផ្ទាំង (គូរដោយ CSS ទាំងអស់ គ្មានរូបភាព)
// ចុចប៊ូតុង → ទ្វារបង្វិលបើកបែប 3D ចូលខាងក្នុង (ពីផ្នែកកណ្ដាលទៅខាងក្រៅ) → បង្ហាញសំបុត្រ
// ===========================================================

const APSARA_DOOR_CSS = `    /* ===== APSARA DOOR INTRO (LUXURY GOLD) ===== */
    #invite-cover { display: none !important; }
    #intro-anim { position: fixed; top:0; left:0; width:100%; height:100%; z-index: 20500; overflow:hidden; background:#0c0904; touch-action:none; overscroll-behavior:contain; -webkit-tap-highlight-color:transparent; transition: background-color .5s ease .15s, opacity .7s ease, visibility .7s ease; }
    #intro-anim.hide { opacity:0; visibility:hidden; pointer-events:none; }
    #intro-anim.d-go { background-color: transparent; pointer-events:none; }
    #intro-anim .ap-doors { position:absolute; top:0; bottom:0; left:50%; width:min(100vw, 520px); transform:translateX(-50%); perspective:1300px; perspective-origin:50% 50%; }
    #intro-anim .ap-door { position:absolute; top:0; height:100%; width:50%; backface-visibility:hidden; -webkit-backface-visibility:hidden; will-change:transform; }
    #intro-anim .ap-door.dl { left:0; transform-origin:0 50%; }
    #intro-anim .ap-door.dr { right:0; transform-origin:100% 50%; }
    #intro-anim .ap-door { box-sizing:border-box; background: repeating-linear-gradient(90deg, rgba(255,255,255,.035) 0 3px, rgba(0,0,0,.06) 3px 7px), linear-gradient(90deg, #050505, #20201f 48%, #050505); box-shadow: inset 0 0 70px rgba(0,0,0,.65); }
    #intro-anim .ap-door.dl { border:7px solid #d9b24a; border-right-width:3px; }
    #intro-anim .ap-door.dr { border:7px solid #d9b24a; border-left-width:3px; }
    #intro-anim .ap-panel { position:absolute; top:4.5%; bottom:4.5%; left:10%; right:10%; border:2px solid #d9b24a; border-radius:12px 12px 4px 4px; box-shadow: inset 0 0 0 5px rgba(0,0,0,.2), inset 0 0 0 7px rgba(217,178,74,.3); }
    #intro-anim .ap-panel::before { content:''; position:absolute; left:11%; right:11%; top:5%; height:42%; border:2px solid #d9b24a; border-radius:50% 50% 6px 6px/26% 26% 6px 6px; background: radial-gradient(circle at 50% 55%, rgba(255,215,120,.38), transparent 62%); animation: apGlow 2.8s ease-in-out infinite; }
    #intro-anim .ap-panel::after { content:''; position:absolute; left:11%; right:11%; bottom:5%; height:38%; border:2px solid #d9b24a; border-radius:6px; background: repeating-linear-gradient(45deg, rgba(217,178,74,.18) 0 6px, transparent 6px 12px); }
    #intro-anim .ap-bar { position:absolute; top:41%; height:18%; width:9px; border-radius:6px; background: linear-gradient(90deg, #8a6a1c, #fff3c4 45%, #d9b24a 70%, #7a5510); box-shadow: 0 3px 8px rgba(0,0,0,.6); z-index:2; }
    #intro-anim .ap-door.dl .ap-bar { right:5%; }
    #intro-anim .ap-door.dr .ap-bar { left:5%; }
    #intro-anim.d-go .ap-door.dl { animation: apOpenL 3.4s cubic-bezier(.5,0,.2,1) .35s forwards; }
    #intro-anim.d-go .ap-door.dr { animation: apOpenR 3.4s cubic-bezier(.5,0,.2,1) .35s forwards; }
    #intro-anim.d-go .ap-door::after { animation: apShade 3.4s ease-in .35s forwards; }
    #intro-anim.d-go .ap-panel::before { animation:none; opacity:1; }
    @keyframes apGlow { 0%,100% { opacity:.55; } 50% { opacity:1; } }
    @keyframes apOpenL { from { transform: rotateY(0deg); } to { transform: rotateY(100deg); } }
    @keyframes apOpenR { from { transform: rotateY(0deg); } to { transform: rotateY(-100deg); } }
    @keyframes apShade { from { opacity:0; } to { opacity:.75; } }
    #intro-anim .ap-plate { position:absolute; left:50%; top:50%; transform:translate(-50%,-50%); width:min(86%, 330px); padding:18px 14px 20px; text-align:center; border-radius:22px; background:linear-gradient(180deg, rgba(15,10,3,.80), rgba(15,10,3,.70)); border:1.5px solid #d9b24a; box-shadow:0 10px 36px rgba(0,0,0,.65), 0 0 26px rgba(217,178,74,.35); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); z-index:5; }
    #intro-anim.d-go .ap-plate { animation: apPlateOut .6s ease forwards; pointer-events:none; }
    @keyframes apPlateOut { to { opacity:0; transform:translate(-50%,-56%) scale(.94); } }
    #intro-anim .ap-title { font-family:'Moul', cursive; color:#FFD700; font-size:1.05rem; line-height:1.9; text-shadow:0 2px 6px rgba(0,0,0,.95); }
    #intro-anim .ap-nm { font-family:'Great Vibes', cursive; color:#fff; font-size:2.1rem; line-height:1.35; text-shadow:0 0 14px rgba(255,215,0,.6), 0 2px 5px rgba(0,0,0,.95); }
    #intro-anim .ap-heart { font-size:1.2rem; margin:2px 0; filter:drop-shadow(0 0 6px #FFD700); }
    #intro-anim .ap-small { font-family:'Suwannaphum', serif; font-style:italic; font-weight:bold; color:#F9E4B7; font-size:1.05rem; margin-top:12px; text-shadow:0 2px 4px rgba(0,0,0,.9); }
    #intro-anim .ap-guest { font-family:'Moul', cursive; color:#FFD700; font-size:1.1rem; line-height:1.9; text-shadow:0 2px 6px rgba(0,0,0,.95); word-wrap:break-word; }
    @media (prefers-reduced-motion: reduce) { #intro-anim * { animation-duration: .01s !important; } }
`;

const APSARA_DOOR_HTML = `  <div id="intro-anim" data-intro="{{INTRO_STYLE}}">
    <div class="ap-doors">
      <div class="ap-door dl"><div class="ap-panel"></div><i class="ap-bar"></i></div>
      <div class="ap-door dr"><div class="ap-panel"></div><i class="ap-bar"></i></div>
      <div class="ap-plate">
        <div class="ap-title">{{PROGRAM_TITLE}}</div>
        <div class="ap-nm">{{GROOM_EN}}</div>
        <div class="ap-heart">&#10084;</div>
        <div class="ap-nm">{{BRIDE_EN}}</div>
        <div class="ap-small">សូមគោរពអញ្ជើញ</div>
        <div class="ap-guest">{{GUEST_NAME}}</div>
        <div class="btn-frame-container" onclick="openInvitation()">
          <div class="btn-frame-outer"><div class="btn-frame-inner"><div class="btn-frame-content">
            <i class="fa-solid fa-envelope-open" style="color:#FFD700; font-size:0.9rem;"></i>
            <span class="btn-frame-text">សូមចុចដើម្បីមើលសំបុត្រ</span>
          </div></div></div>
        </div>
      </div>
    </div>
  </div>
`;

const APSARA_DOOR_JS = `    let introStarted = false, introDone = false;
    // ចាក់សោ Scroll រហូតដល់ទ្វារបើក (ទំព័រស្ថិតនៅខាងក្រោយទ្វារ)
    function apLock(on) { try { document.documentElement.style.overflow = on ? 'hidden' : ''; document.body.style.overflow = on ? 'hidden' : ''; } catch (e) {} }
    apLock(true);
    window.finishIntro = function() {
        if (introDone) return; introDone = true;
        apLock(false);
        window.__introBusy = false;
        const cover = document.getElementById('invite-cover'); const intro = document.getElementById('intro-anim');
        if (cover) cover.classList.add('open');
        if (intro) { intro.classList.add('hide'); setTimeout(() => { intro.style.display = 'none'; }, 900); }
    };
    window.openInvitation = function() {
        if (introStarted) return; introStarted = true;
        const audio = document.getElementById('bgMusic'); const intro = document.getElementById('intro-anim');
        if (audio && audio.paused) { audio.play().catch(e => console.log("Audio autoplay failed", e)); }
        if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { window.finishIntro(); return; }
        apLock(false);
        intro.classList.add('d-go');
        setTimeout(window.finishIntro, 3900);
    };`;

const APSARA_DOOR_PACK = { css: APSARA_DOOR_CSS, html: APSARA_DOOR_HTML, js: APSARA_DOOR_JS };
// Luxury Gold ($9.99): មិនមាន Intro (បើកសំបុត្រផ្ទាល់ដូច Classic/Royal Gold)
const LUXURY_GOLD_DOOR_TEMPLATE = LUXURY_GOLD_TEMPLATE;

// ម៉ូតធៀបអាពាហ៍ពិពាហ៍ដែលប្រើចំណងជើងអង់គ្លេស
const isEnglishWeddingTitle = (id) => {
    const t = String(id || '');
    return t.includes('blossom') || t.includes('night') || t.includes('leaves') || t.includes('sunset');
};

// ===========================================================
// ENGINE រួម (SINGLE SOURCE OF TRUTH)
// ធានាថា Preview និង Link ដែលផ្ញើទៅភ្ញៀវ ចេញពិធីតែមួយដូចគ្នា ១០០%
// ប្រើដោយ generateHTML (Link ពិត) និង generateSamplePreview (Preview)
// ===========================================================
const ROYAL_NAME_STYLE = "display:block; font-family: 'Moul', cursive !important; font-size: 1.8rem !important; font-weight: normal !important; -webkit-text-stroke: 0px !important; text-shadow: 1px 1px 3px rgba(0,0,0,0.8) !important;";

// 1. chomnong cheung
const getProgramTitle = (id) => {
    const t = String(id || 'classic-gold');
    if (isEnglishWeddingTitle(t)) return "Wedding Invitation";
    if (t.includes('birthday')) return "កម្មវិធីខួបកំណើត";
    if (t.includes('baby'))     return "សិរីសួស្ដីពិធីបង្កក់កូន";
    if (t.includes('gift'))     return "សិរីសួស្ដីពិធីកាត់ចំណងដៃ";
    if (t.includes('house'))    return "សិរីសួស្ដីពិធីឡើងគេហដ្ឋាន";
    if (t.includes('eng'))      return "កម្មវិធីភ្ជាប់ពាក្យ";
    return "សិរីមង្គលអាពាហ៍ពិពាហ៍";
};

// ១.៥ បទភ្លេងតាមប្រភេទពិធី (រួមគ្នាទាំង Preview និង Link)
// បង្រួមបទភ្លេង (Cloudinary 64kbps mp3) → ស្រាល ចាក់លឿន មិនទាក់
// បើចង់បិទ៖ ដូរបន្ទាត់ចុងក្រោយ (return u.replace...) ទៅជា return u;
const optimizeMusicUrl = (url) => {
    const u = String(url || '');
    if (!u.includes('res.cloudinary.com') || !u.includes('/video/upload/')) return u;
    if (/\/upload\/[^/]*br_\d+k/.test(u)) return u;
    return u.replace('/video/upload/', '/video/upload/br_64k,f_mp3/');
};

const getCeremonyMusic = (id) => {
    const t = String(id || 'classic-gold');
    if (t.includes('birthday') || t.includes('baby')) return APP_CONFIG.BIRTHDAY_MUSIC;
    if (t.includes('house')) return APP_CONFIG.HOUSEWARMING_MUSIC;
    if (t.includes('eng')) return APP_CONFIG.ENGAGEMENT_MUSIC;
    return APP_CONFIG.WEDDING_MUSIC;
};

// 2. ceremony rules
const applyCeremonyRules = (html, id) => {
    const t = String(id || 'classic-gold');
    const isRoyal = t.includes('royal');
    const isSingleOwner = t.includes('birthday') || t.includes('baby');

    // រចនាបថ Intro តាមប្រភេទម៉ូត (មានតែ Template ដែលមាន {{INTRO_STYLE}} ប៉ុណ្ណោះដែលរងផលប៉ះពាល់)
    html = html.replace('{{INTRO_STYLE}}',
        t.endsWith('golden-dawn') ? 'sunrise' : t.endsWith('royal-crimson') ? 'door-royal' : t.endsWith('luxury-diamond') ? 'rose' :
        t.includes('birthday') ? 'party' : t.includes('baby') ? 'baby' : t.includes('house') ? 'house' :
        t.includes('eng') ? 'love' : t.includes('gift') ? 'gift' :
        t === 'luxury-gold' ? 'door-luxury' : t === 'royal-crimson' ? 'door-royal' : t === 'luxury-diamond' ? 'rose' : t.includes('royal') ? 'royal' :
        t.includes('luxury') ? 'luxury' : t.includes('blossom') ? 'blossom' : t.includes('night') ? 'night' :
        t.includes('leaves') ? 'sunset' : 'sunrise');

    if (isRoyal) {
        html = html.replace('<div style="display:block">{{GROOM_EN}}</div>', '<div style="' + ROYAL_NAME_STYLE + '">{{GROOM}}</div>');
        html = html.replace('<div style="display:block">{{BRIDE_EN}}</div>', '<div style="' + ROYAL_NAME_STYLE + '">{{BRIDE}}</div>');
    }

    if (isSingleOwner) {
        const coverInner = isRoyal
            ? '<div style="' + ROYAL_NAME_STYLE + '">{{GROOM}}</div>'
            : '<div style="display:block">{{GROOM_EN}}</div>';

        html = html.replace(/Wedding Celebration/g, t.includes('baby') ? 'Baby Blessing Ceremony' : 'Birthday Celebration');
        html = html.replace(/ឪពុកម្ដាយខាងកូនកំលោះ/g, 'មាតាបិតា');
        html = html.replace(/<div class="cover-names anim-highlight"[\s\S]*?<\/div>\s*<\/div>/,
            '<div class="cover-names anim-highlight" style="padding-top: 30px !important;">' + coverInner + '</div>');
        html = html.replace(/<div class="couple-names animate-box anim-top-bottom">[\s\S]*?<\/div>\s*<\/div>/,
            '<div class="couple-names animate-box anim-top-bottom"><div class="c-name" style="font-size:3rem">{{GROOM}}</div></div>');
        html = html.replace(/<div class="parent-block animate-box anim-right-left">[\s\S]*?<\/div><\/div>/, '');
    }
    else if (t.includes('house')) {
        html = html.replace(/Wedding Celebration/g, 'Housewarming Ceremony');
        html = html.replace('<div class="parents-row">', '<div class="parents-row" style="display:none">');
    }
    else if (t.includes('eng')) {
        html = html.replace(/Wedding Celebration/g, 'Engagement Ceremony');
    }
    else if (t.includes('gift')) {
        html = html.replace(/Wedding Celebration/g, 'Gift Reception');
    }

    return html;
};

const escapeJsString = (str) => {
    return String(str)
        .replace(/\\/g, '\\\\')
        .replace(/'/g, "\\'")
        .replace(/\n/g, '\\n')
        .replace(/\r/g, '');
};

// ការពារ XSS៖ Escape តម្លៃដែលអ្នកប្រើវាយបញ្ចូល (ឈ្មោះ, ទីកន្លែង, ។ល។)
// មុននឹងបញ្ចូលទៅជា HTML នៅក្នុងសំបុត្រអញ្ជើញ ដើម្បីកុំឱ្យ <script> ឬ Tag គ្រោះថ្នាក់ផ្សេងៗ
// ដែលភ្ញៀវ ឬ ម្ចាស់ព្រឹត្តិការណ៍វាយបញ្ចូល អាចដំណើរការបានក្នុងកម្មវិធីរុករករបស់អ្នកផ្សេង
const escapeHtml = (str) => {
    return String(str == null ? '' : str)
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
};

    const generateHTML = (formData, template = CLASSIC_GOLD_TEMPLATE, eventId = '', templateId = 'classic-gold') => {
    const currentTemplateId = String(templateId || 'classic-gold');
    const assets = (THEME_ASSETS && THEME_ASSETS[currentTemplateId]) 
                   ? THEME_ASSETS[currentTemplateId] 
                   : (THEME_ASSETS['classic-gold'] || { bgImage: APP_CONFIG.DEFAULT_IMAGE, defaultCover: APP_CONFIG.DEFAULT_IMAGE });
    
    let html = template;

    // ១. កំណត់កាលបរិច្ឆេទខ្មែរ
    let dateKh = "";
    if (formData.date) {
        const d = new Date(formData.date);
        const khmerMonths = ["មករា", "កុម្ភៈ", "មីនា", "មេសា", "ឧសភា", "មិថុនា", "កក្កដា", "សីហា", "កញ្ញា", "តុលា", "វិច្ឆិកា", "ធ្នូ"];
        dateKh = `${d.getDate()} - ${khmerMonths[d.getMonth()]} - ${d.getFullYear()}`;
    }

    // ការពារ XSS៖ ប្ដូរ <br> ដែលអ្នកប្រើវាយចូល (សម្រាប់កថាខណ្ឌថ្មី) ទៅជាចុះបណ្ដាត់ជាមុន
    // រួចទាំង Escape HTML លើសម្រៀបផ្សេងទៀត ដើម្បីកុំឱ្យ Tag គ្រោះថ្នាក់ដំណើរការបាន
    const scheduleNormalized = (formData.schedule || "")
        .replace(/&lt;br\s*\/?&gt;/gi, '\n')
        .replace(/<br\s*\/?>/gi, '\n');
    const safeSchedule = escapeJsString(escapeHtml(scheduleNormalized));
    const galleryImagesSafe = Array.isArray(formData.galleryImages) ? formData.galleryImages.map(u => cldOpt(u, 1000)) : [];
    const safeGallery = JSON.stringify(galleryImagesSafe).replace(/\\/g, '\\\\').replace(/'/g, "\\'");

    // 👇 === បន្ថែមកូដថ្មីនៅទីនេះ ដើម្បីដោះស្រាយបញ្ហាលាក់ "លោក/អ្នកស្រី" លុះត្រាតែវាយ '-' === 👇
    const cleanName = (name) => (name || '').trim();

    // ខាងកូនកំលោះ
    if (cleanName(formData.groomFather) === '-') {
        html = html.replace(/<div class="p-name">លោក {{GROOM_FATHER}}<\/div>/g, '');
    }
    if (cleanName(formData.groomMother) === '-') {
        html = html.replace(/<div class="p-name">អ្នកស្រី {{GROOM_MOTHER}}<\/div>/g, '');
    }

    // ខាងកូនក្រមុំ
    if (cleanName(formData.brideFather) === '-') {
        html = html.replace(/<div class="p-name">លោក {{BRIDE_FATHER}}<\/div>/g, '');
    }
    if (cleanName(formData.brideMother) === '-') {
        html = html.replace(/<div class="p-name">អ្នកស្រី {{BRIDE_MOTHER}}<\/div>/g, '');
    }

    // បើវាយ '-' ទាំងឪពុក និងម្តាយ គឺលុបចំណងជើង "ឪពុកម្ដាយខាង..." ចេញតែម្តង
    if (cleanName(formData.groomFather) === '-' && cleanName(formData.groomMother) === '-') {
        html = html.replace(/<div class="p-label">ឪពុកម្ដាយខាងកូនកំលោះ<\/div>/g, '');
    }
    if (cleanName(formData.brideFather) === '-' && cleanName(formData.brideMother) === '-') {
        html = html.replace(/<div class="p-label">ឪពុកម្ដាយខាងកូនក្រមុំ<\/div>/g, '');
    }
    // 👆 === បញ្ចប់ការបន្ថែមថ្មី === 👆

    // --- កែសម្រួលតាមប្រភេទពិធី (ENGINE រួម - ដូច Preview មិនខុស) ---
    // Background ដែល User Upload → ដាក់ទង់ ដើម្បីឱ្យ Overlay ងងឹតជាងបន្តិច អក្សរច្បាស់
    if (formData.bgImage) html = html.replace('<div class="overlay"></div>', '<div class="overlay" data-custom="1"></div>');
    html = applyCeremonyRules(html, currentTemplateId);

    // ៣. កំណត់ចំណងជើងកម្មវិធី
const programTitle = getProgramTitle(currentTemplateId);
			   
					   // បង្កើត Link សម្រាប់ប្រតិទិន
    const dateStr = formData.date ? formData.date.replace(/-/g, '') : "20260118";
    const timeStr = formData.time ? formData.time.replace(/:/g, '') : "1700";
    const calStart = `${dateStr}T${timeStr}00`;
    const calEnd = `${dateStr}T${(parseInt(timeStr.substring(0, 2)) + 4).toString().padStart(2, '0')}${timeStr.substring(2)}00`;
    const calTitle = encodeURIComponent(`${formData.groomName || 'Event'}`);
    const calLink = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${calTitle}&dates=${calStart}/${calEnd}&sf=true&output=xml`;

    // បង្កើត Variable សម្រាប់ឆែកថាជាកម្មវិធីដែលមានម្ចាស់តែម្នាក់ (ខួបកំណើត ឬ បង្កក់កូន)
    const isSingle = currentTemplateId.includes('birthday') || currentTemplateId.includes('baby');

    const replacements = {
        '{{PROGRAM_TITLE}}': programTitle,
        '{{GROOM}}': escapeHtml(formData.groomName || ''),
        // បើជា Birthday/Baby ឱ្យតម្លៃ Bride ទៅជា "ទទេ" ដើម្បីកុំឱ្យចេញឈ្មោះ Default មកជាន់
        '{{BRIDE}}': isSingle ? '' : escapeHtml(formData.brideName || ''),
        '{{GROOM_EN}}': escapeHtml(formData.groomEn || ''),
        // បើជា Birthday/Baby ឱ្យតម្លៃ Bride_EN ទៅជា "ទទេ"
        '{{BRIDE_EN}}': isSingle ? '' : escapeHtml(formData.brideEn || ''),
        '{{GUEST_NAME}}': escapeHtml(formData.guestName || 'ភ្ញៀវកិត្តិយស'),
        
        // បើឡើងគេហដ្ឋាន គឺមិនឱ្យមានតម្លៃឈ្មោះឪពុកម្ដាយឡើយ
        '{{GROOM_FATHER}}': currentTemplateId.includes('house') ? '' : escapeHtml(formData.groomFather || ''),
        '{{GROOM_MOTHER}}': currentTemplateId.includes('house') ? '' : escapeHtml(formData.groomMother || ''),
        '{{BRIDE_FATHER}}': currentTemplateId.includes('house') ? '' : escapeHtml(formData.brideFather || ''),
        '{{BRIDE_MOTHER}}': currentTemplateId.includes('house') ? '' : escapeHtml(formData.brideMother || ''),
        
        '{{DATE_KH}}': dateKh,
        '{{DATE_ISO}}': formData.date || '',
        '{{TIME_ISO}}': formData.time || '',
        '{{VENUE}}': escapeHtml(formData.venueName || ''),
        '{{VENUE_ENCODED}}': encodeURIComponent(formData.venueName || ''),
        '{{MAP_LINK}}': escapeHtml(formData.mapLink || ''),
        '{{QR_CODE_URL}}': escapeHtml(formData.qrCodeUrl || ''),
        '{{SCHEDULE}}': safeSchedule,
        '{{MUSIC_URL}}': escapeHtml(optimizeMusicUrl(formData.musicUrl || getCeremonyMusic(currentTemplateId))),
        '{{TELEGRAM_TEXT}}': escapeHtml(formData.telegramText || ''),
        '{{TELEGRAM_LINK}}': escapeHtml(formData.telegramLink || ''),
        '{{GALLERY_JSON}}': safeGallery,
        // ចំណាំ៖ COVER_IMAGE ត្រូវប្រើនៅក្នុង CSS (url(...)) ក្នុង <style> ដូច្នេះមិនអាច escape ជា HTML entity បានទេ
        // (CSS មិនស្គាល់ &amp; ។ល។) ត្រូវសម្អាតតែសញ្ញា Quote វិញ ដើម្បីកុំឲ្យបំបែក url('...')
        '{{COVER_IMAGE}}': String(cldOpt(formData.coverImage || assets.defaultCover, 1080)).replace(/['"()\\]/g, ''),
        '{{GROOM_IMAGE}}': String(cldOpt(formData.groomImage || formData.coverImage || assets.defaultCover, 1080)).replace(/['"()\\]/g, ''),
        '{{BRIDE_IMAGE}}': String(cldOpt(formData.brideImage || formData.coverImage || assets.defaultCover, 1080)).replace(/['"()\\]/g, ''),
        '{{BG_IMAGE}}': escapeHtml(cldOpt(formData.bgImage || assets.bgImage, 1080)),
        '{{PARTICLE_TYPE}}': escapeHtml(assets.particle || ''),
        '{{EVENT_ID}}': eventId || '',
        '{{LOCATION_DESCRIPTION}}': escapeHtml(formData.locationDescription || ''),
        '{{CALENDAR_LINK}}': calLink
    };

    // បន្ថែម Logic ពិសេស៖ បើជាកម្មវិធីម្នាក់ឯង ត្រូវលុបសញ្ញា " & " ចេញពី Template ទាំងអស់
    // (Fix: ដកចេញពី Object.keys().forEach() ព្រោះ replace ទាំងពីរនេះមិនពឹងផ្អែកលើ key/value ទេ
    //  ការដាក់ក្នុង loop ធ្វើឱ្យវារត់ដោយមិនចំាបាច់ម្ដងហើយម្ដងទៀតគ្មានប្រយោជន៍)
    if (isSingle) {
        html = html.replace(/{{GROOM}}\s*&\s*{{BRIDE}}/g, '{{GROOM}}');
        html = html.replace(/{{GROOM_EN}}\s*&\s*{{BRIDE_EN}}/g, '{{GROOM_EN}}');
    }

    Object.entries(replacements).forEach(([key, value]) => {
        html = html.split(key).join(value || "");
    });

    return optimizeInviteHtml(html);
};
const generateSamplePreview = (htmlContent, templateId = 'classic-gold', thumb = false) => {
    const assets = (THEME_ASSETS && THEME_ASSETS[templateId]) 
                   ? THEME_ASSETS[templateId] 
                   : (THEME_ASSETS['classic-gold'] || { bgImage: APP_CONFIG.DEFAULT_IMAGE, defaultCover: APP_CONFIG.DEFAULT_IMAGE });

const pTitle = getProgramTitle(templateId);

    let previewHtml = htmlContent;

    // --- ប្រើ ENGINE រួមដូច Link ពិត ---
    previewHtml = applyCeremonyRules(previewHtml, templateId);

    const isSingleOwner = templateId.includes('birthday') || templateId.includes('baby');

    if (isSingleOwner) {
        previewHtml = previewHtml
            .replace(/{{BRIDE_EN}}/g, '') 
            .replace(/<div style="font-size:2rem; margin:5px 0;">&<\/div>/g, '')
            .replace(/{{BRIDE}}/g, '')
            .replace(/<div class="c-amp anim-highlight">&<\/div>/g, '');
    }

    return optimizeInviteHtml(stubFirebaseImports(previewHtml
        .replace(/{{PROGRAM_TITLE}}/g, pTitle)
        .replace(/{{GROOM}}/g, isSingleOwner ? 'ឡៃ សុជាតា' : 'ចាន់ សីហា')
        .replace(/{{BRIDE}}/g, isSingleOwner ? '' : 'ហ៊ាង សុជាតា')
        .replace(/{{GROOM_EN}}/g, isSingleOwner ? 'Lai Socheata' : (templateId.includes('blossom') || templateId.includes('night') || templateId.includes('leaves') ? 'Chan Sophea' : 'Chan Seyha'))
        .replace(/{{BRIDE_EN}}/g, isSingleOwner ? '' : (templateId.includes('blossom') || templateId.includes('night') || templateId.includes('leaves') ? 'Heng Soknita' : 'Heang Socheata'))
        .replace(/{{GROOM_FATHER}}/g, 'លោក ស៊ីណា ចាន់')
        .replace(/{{GROOM_MOTHER}}/g, 'អ្នកស្រី មឿន ម៉ាឡៃ')
        .replace(/{{BRIDE_FATHER}}/g, isSingleOwner ? '' : 'លោក ហ៊ិន ហ៊ាង')
        .replace(/{{BRIDE_MOTHER}}/g, isSingleOwner ? '' : 'អ្នកស្រី ភា សោភ័ណ្ឌ')
        .replace(/{{GUEST_NAME}}/g, (templateId.includes('blossom') || templateId.includes('night') || templateId.includes('leaves') ? 'ភ្ញៀវកិត្តិយស' : 'ភ្ញៀវកិត្តិយស'))
        .replace(/{{DATE_KH}}/g, (templateId.includes('blossom') || templateId.includes('night') || templateId.includes('leaves') ? '២៨ ធ្នូ ២០២៦' : '១៨ - ឧសភា - ២០២៦'))
        .replace(/{{VENUE}}/g, 'មជ្ឈមណ្ឌលពិព័រណ៍ និងសន្និបាត')
        .replace(/{{LOCATION_DESCRIPTION}}/g, 'ពិធីនេះរៀបចំឡើងនៅសាលមង្គល...')
        
        // នេះគឺជាកូដកម្មវិធីថ្មីដែលបងបានផ្ញើមក
        .replace(/{{SCHEDULE}}/g, '- វេលាម៉ោង ០៦:៣០ ព្រឹក\\nជួបជុំភ្ញៀវកិត្តិយសដើម្បីរៀបចំហែចំនូន\\n- វេលាម៉ោង ០៧:០០ ព្រឹក\\nពិធីហែចំនូនចូលរោងជ័យ\\n- វេលាម៉ោង ០៨:០០ ព្រឹក\\nពិធីចុះសំបុត្រអាពាហ៍ពិពាហ៍\\n- វេលាម៉ោង ០៨:៣០ ព្រឹក\\nពិធីកាត់សក់បង្កក់សិរី\\n- វេលាម៉ោង ១០:០០ ព្រឹក\\nពិធីសំពះផ្ទឹម សែនចងដៃ ព្រះថោងនាងនាគ\\n- វេលាម៉ោង ១២:០០ ថ្ងៃត្រង់\\nអញ្ជើញភ្ញៀវកិត្តិយសពិសារអាហារថ្ងៃត្រង់\\n- វេលាម៉ោង ០៥:០០ ល្ងាច\\nទទួលបដិសណ្ឋារកិច្ចភ្ញៀវកិត្តិយស')
        
        .replace(/{{DATE_ISO}}/g, '2028-12-18')
        .replace(/{{TIME_ISO}}/g, '17:00')
        .replace(/{{QR_CODE_URL}}/g, cldOpt(APP_CONFIG.DEFAULT_IMAGE, 400))
        .replace(/{{COVER_IMAGE}}/g, cldOpt(assets.defaultCover, thumb ? 480 : 1080))
        .replace(/{{GROOM_IMAGE}}/g, cldOpt(assets.defaultCover, thumb ? 480 : 1080))
        .replace(/{{BRIDE_IMAGE}}/g, cldOpt(assets.defaultCover, thumb ? 480 : 1080))
        .replace(/{{BG_IMAGE}}/g, cldOpt(assets.bgImage, thumb ? 480 : 1080))
        .replace(/{{PARTICLE_TYPE}}/g, assets.particle || '')
        .replace(/{{MUSIC_URL}}/g, optimizeMusicUrl(getCeremonyMusic(templateId)))
        .replace(/{{VENUE_ENCODED}}/g, encodeURIComponent('មជ្ឆមណ្ដលពិព័រណ៍ និងសន្និបាត'))
        .replace(/{{MAP_LINK}}/g, 'https://maps.app.goo.gl/gmxb8VaN78xgkFiu5')
        .replace(/{{TELEGRAM_TEXT}}/g, '012 345 678')
        .replace(/{{TELEGRAM_LINK}}/g, '#')
        .replace(/{{EVENT_ID}}/g, '')
        .replace(/{{GALLERY_JSON}}/g, JSON.stringify(TEMPLATE_DEFAULT_IMAGES.map(u => cldOpt(u, thumb ? 240 : 800))))
        .replace(/{{CALENDAR_LINK}}/g, '#')));
};

// ===== Preview ស្រាល៖ Mount iframe តែពេលកាតនោះនៅជិត/ក្នុងអេក្រង់ (សំខាន់សម្រាប់ Safari/iPhone) =====
const LazyPreviewFrame = ({ content, templateId, title }) => {
    const hostRef = useRef(null);
    const [visible, setVisible] = useState(false);
    useEffect(() => {
        const el = hostRef.current;
        if (!el) return;
        if (typeof IntersectionObserver === 'undefined') { setVisible(true); return; }
        const io = new IntersectionObserver(
            (entries) => setVisible(entries[entries.length - 1].isIntersecting),
            { rootMargin: '200px 0px' }
        );
        io.observe(el);
        return () => io.disconnect();
    }, []);
    const doc = useMemo(() => (visible ? generateSamplePreview(content, templateId, true) : ''), [visible, content, templateId]);
    return (
        <div ref={hostRef} className="w-full h-full bg-white">
            {visible
                ? <iframe srcDoc={doc} className="w-full h-full border-none bg-white scrollbar-hide" tabIndex={-1} title={title} />
                : <div className="w-full h-full flex items-center justify-center bg-white text-gray-300 text-sm animate-pulse">...</div>}
        </div>
    );
};
const AuthModal = ({ onClose, initialTab = 'login', onLoginSuccess }) => {
    // ប្រើ React Hooks ពី global React object
    const { useState, useEffect, useRef } = React;
    useNavLayer(true, onClose);   // ប៊ូតុង Back លើទូរស័ព្ទ = បិទ Login popup

    const [activeTab, setActiveTab] = useState(initialTab);
    const [step, setStep] = useState('input');
    const [loading, setLoading] = useState(false);
    const [contact, setContact] = useState('');
    const [password, setPassword] = useState('');
    const [showPassword, setShowPassword] = useState(false);
    const [name, setName] = useState('');
    const [otp, setOtp] = useState('');
    const [error, setError] = useState('');
    const [confirmationResult, setConfirmationResult] = useState(null);

    const recaptchaVerifierRef = useRef(null);

    useEffect(() => {
        setStep('input');
        setOtp('');
        setError('');
    }, [activeTab]);

    // 🔥 Pre-warm reCAPTCHA ពេល modal បើក ដើម្បីកុំឲ្យអ្នកប្រើមានអារម្មណ៍ថាប៊ូតុងគាំងពេលចុច
    // (មុននេះ recaptcha widget ត្រូវបាន render តែពេលចុចប៊ូតុងប៉ុណ្ណោះ ធ្វើឲ្យមានការយឺតលើកដំបូង)
    useEffect(() => {
        setupRecaptcha();
        return () => {
            if (recaptchaVerifierRef.current) {
                try { recaptchaVerifierRef.current.clear(); } catch (e) {}
                recaptchaVerifierRef.current = null;
            }
            try { const h = document.getElementById('recaptcha-container'); if (h) h.innerHTML = ''; } catch (e) {}
        };
    }, []);

    const formatPhoneNumber = (phone) => {
        let cleaned = phone.replace(/\D/g, '');
        if (cleaned.startsWith('0')) {
            cleaned = '855' + cleaned.substring(1);
        } else if (!cleaned.startsWith('855')) {
            cleaned = '855' + cleaned;
        }
        return '+' + cleaned;
    };

    // 🔧 បង្កើត reCAPTCHA ថ្មីស្រឡះរាល់ដង (element ថ្មី) → មិនដែលជួប "reCAPTCHA has already been rendered in this element"
    const disposeRecaptcha = () => {
        if (recaptchaVerifierRef.current) {
            try { recaptchaVerifierRef.current.clear(); } catch (e) {}
            recaptchaVerifierRef.current = null;
        }
        const host = document.getElementById('recaptcha-container');
        if (host) host.innerHTML = '';
    };

    const buildRecaptcha = () => {
        disposeRecaptcha();
        const host = document.getElementById('recaptcha-container');
        if (!host || !window.FirebaseAuthConfig) return null;
        const { RecaptchaVerifier } = window.FirebaseAuthConfig;
        const el = document.createElement('div');
        host.appendChild(el);
        const verifier = new RecaptchaVerifier(window.firebaseAuth, el, {
            size: 'invisible',
            callback: () => {},
            // ហួសកំណត់ → លុបចោល ដើម្បីឱ្យចុចលើកក្រោយបង្កើតថ្មី (មិនបង្ហាញ Error រំខានទេ)
            'expired-callback': () => { recaptchaVerifierRef.current = null; }
        });
        recaptchaVerifierRef.current = verifier;
        // 🔥 render() ជាមុន ដើម្បីត្រៀមរួចជាស្រេច (មិនរង់ចាំពេលចុចប៊ូតុង)
        try { const r = verifier.render(); if (r && r.catch) r.catch(() => {}); } catch (e) {}
        return verifier;
    };

    const setupRecaptcha = () => recaptchaVerifierRef.current || buildRecaptcha();

    // សារ Error ជាភាសាខ្មែរ ងាយយល់ (មិនបង្ហាញកូដ Firebase ឆៅៗ)
    const friendlyAuthError = (err) => {
        const code = String((err && err.code) || '');
        const all = code + ' ' + String((err && err.message) || '');
        if (/invalid-phone-number/i.test(all)) return 'លេខទូរស័ព្ទមិនត្រឹមត្រូវ សូមពិនិត្យម្ដងទៀត (ឧ. 0971234567)';
        if (/too-many-requests/i.test(all)) return 'ព្យាយាមច្រើនដងពេក សូមរង់ចាំបន្តិច រួចសាកល្បងម្ដងទៀត';
        if (/network-request-failed/i.test(all)) return 'បណ្ដាញអ៊ីនធឺណិតមានបញ្ហា សូមពិនិត្យការតភ្ជាប់ រួចសាកល្បងម្ដងទៀត';
        if (/quota-exceeded/i.test(all)) return 'ប្រព័ន្ធផ្ញើ SMS ពេញកំណត់ថ្ងៃនេះ សូមសាកល្បងពេលក្រោយ ឬចូលតាម Google';
        if (/operation-not-allowed|billing-not-enabled/i.test(all)) return 'ការចូលតាមលេខទូរស័ព្ទមិនទាន់បើកដំណើរការ សូមចូលតាម Google ឬទាក់ទង Admin';
        if (/captcha|already been rendered|error-code:-39/i.test(all)) return 'ការផ្ទៀងផ្ទាត់សុវត្ថិភាពមិនជោគជ័យ សូមចុចម្ដងទៀត ឬចូលតាម Google';
        return 'មិនអាចផ្ញើកូដ OTP បានទេ សូមព្យាយាមម្ដងទៀត ឬចូលតាម Google' + (code ? ' (' + code.replace('auth/', '') + ')' : '');
    };

    const handleSendOtp = async () => {
        if (loading) return; // ការពារចុចជាន់គ្នា
        if (!contact || !password) {
            setError("សូមបញ្ចូលលេខទូរស័ព្ទ និងពាក្យសម្ងាត់");
            return;
        }

        setError('');
        setLoading(true);

        try {
            const formattedPhone = formatPhoneNumber(contact);
            let lastErr = null;

            // សាកល្បងរហូត ២ដង៖ បើ reCAPTCHA មានបញ្ហា បង្កើតថ្មីស្រឡះ ហើយសាកល្បងម្ដងទៀតដោយស្វ័យប្រវត្តិ
            for (let attempt = 0; attempt < 2; attempt++) {
                try {
                    const appVerifier = attempt === 0 ? setupRecaptcha() : buildRecaptcha();
                    if (!appVerifier) throw new Error('recaptcha-not-ready');

                    const confirmation = await window.FirebaseAuthConfig.signInWithPhoneNumber(window.firebaseAuth, formattedPhone, appVerifier);
                    setConfirmationResult(confirmation);
                    setStep('verify');
                    lastErr = null;
                    break;
                } catch (err) {
                    lastErr = err;
                    console.error("Firebase Phone Auth Error (attempt " + (attempt + 1) + "):", err);
                    disposeRecaptcha(); // reCAPTCHA ប្រើហើយ/ខូច → លុបចោលជានិច្ច
                    const retryable = /captcha|already been rendered|error-code:-39|recaptcha-not-ready/i.test(String((err && err.code) || '') + ' ' + String((err && err.message) || ''));
                    if (!retryable) break;
                }
            }

            if (lastErr) {
                setError(friendlyAuthError(lastErr));
                setTimeout(() => { try { buildRecaptcha(); } catch (e) {} }, 0); // ត្រៀមថ្មីសម្រាប់ការចុចលើកក្រោយ
            }
        } finally {
            setLoading(false);
        }
    };

    const handleVerify = async () => {
        if (otp.length !== 6 || !confirmationResult) return;
        setLoading(true);
        setError('');

        try {
            try { window.__pendingSignupName = (name || '').trim(); } catch(e) {}
            const result = await confirmationResult.confirm(otp);
            const user = result.user;

            if (onLoginSuccess) {
                onLoginSuccess({
                    ...user,
                    displayName: name || user.displayName || contact
                });
            }
            if (onClose) onClose();
        } catch (err) {
            console.error("OTP Verification Error:", err);
            setError("កូដ OTP មិនត្រឹមត្រូវ ឬហួសកំណត់");
        } finally {
            setLoading(false);
        }
    };

    const handleGoogleLogin = async () => {
        setLoading(true);
        try {
            const provider = new window.FirebaseAuthConfig.GoogleAuthProvider();
            const typedName = (name || '').trim();
            try { window.__pendingSignupName = typedName; } catch(e) {}
            const result = await window.FirebaseAuthConfig.signInWithPopup(window.firebaseAuth, provider);
            if (onLoginSuccess) onLoginSuccess(result.user);
            if (onClose) onClose();
        } catch (err) {
            console.error("Google Auth Error:", err);
            setError("មានបញ្ហាក្នុងការចូល Google");
        } finally {
            setLoading(false);
        }
    };

    return (
        <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-md p-4 animate-fade-in font-suwannaphum">
            <div id="recaptcha-container"></div>

            <div className="bg-white dark:bg-[#18181b] border border-pink-100 dark:border-pink-950/40 w-full max-w-[400px] rounded-3xl p-6 relative shadow-2xl shadow-pink-500/10 transition-all">
                <button 
                    onClick={onClose} 
                    className="absolute top-4 right-4 text-gray-400 hover:text-pink-600 bg-pink-50 dark:bg-zinc-800 p-2 rounded-full transition-all"
                >
                    ✕
                </button>

                <div className="flex flex-col items-center justify-center mb-5 mt-2">
                    <h2 className="text-xl font-bold text-gray-800 dark:text-white font-moul">
                        {step === 'verify' ? 'ផ្ទៀងផ្ទាត់ OTP' : (activeTab === 'login' ? 'ចូលប្រើប្រាស់' : 'បង្កើតគណនី')}
                    </h2>
                </div>

                <div className="space-y-4">
                    {step === 'verify' ? (
                        <div className="animate-fade-in text-center">
                            <p className="text-xs text-gray-500 dark:text-gray-400 mb-6">
                                យើងបានផ្ញើកូដ ៦ ខ្ទង់ទៅកាន់លេខ <span className="font-bold text-pink-500">{formatPhoneNumber(contact)}</span>
                            </p>
                            
                            <div className="flex justify-center gap-2 mb-6">
                                {[0, 1, 2, 3, 4, 5].map((index) => (
                                    <input
                                        key={index}
                                        id={`otp-input-${index}`}
                                        type="text"
                                        inputMode="numeric"
                                        maxLength="1"
                                        className="w-10 h-12 text-center text-xl font-bold border-2 border-pink-200 dark:border-zinc-700 rounded-xl bg-pink-50/40 dark:bg-zinc-800 text-gray-800 dark:text-white outline-none focus:border-pink-500 focus:bg-white dark:focus:bg-zinc-900 focus:ring-2 focus:ring-pink-500/20 transition-all shadow-sm"
                                        value={otp[index] || ''}
                                        onChange={(e) => {
                                            const value = e.target.value;
                                            if (/^[0-9]$/.test(value) || value === '') {
                                                let newOtp = otp.split('');
                                                newOtp[index] = value;
                                                const finalOtp = newOtp.join('');
                                                setOtp(finalOtp);
                                                
                                                if (value !== '' && index < 5) {
                                                    const nextInput = document.getElementById(`otp-input-${index + 1}`);
                                                    if (nextInput) nextInput.focus();
                                                }
                                            }
                                        }}
                                        onKeyDown={(e) => {
                                            if (e.key === 'Backspace' && !otp[index] && index > 0) {
                                                const prevInput = document.getElementById(`otp-input-${index - 1}`);
                                                if (prevInput) prevInput.focus();
                                            }
                                        }}
                                    />
                                ))}
                            </div>

                            {error && (
                                <div className="text-red-500 text-xs mb-4 font-medium bg-red-50 dark:bg-red-950/30 border border-red-200 p-2.5 rounded-xl">
                                    {error}
                                </div>
                            )}
                            
                            <button 
                                onClick={handleVerify} 
                                disabled={otp.length !== 6 || loading}
                                className={`w-full py-3 rounded-xl font-bold flex items-center justify-center gap-2 transition-all ${
                                    otp.length === 6 && !loading
                                    ? 'bg-pink-500 hover:bg-pink-600 text-white shadow-lg shadow-pink-500/30' 
                                    : 'bg-gray-200 dark:bg-zinc-800 text-gray-400 cursor-not-allowed'
                                }`}
                            >
                                <span>ផ្ទៀងផ្ទាត់ OTP</span>
                            </button>
                            
                            <div className="mt-5 pt-3 border-t border-gray-100 dark:border-zinc-800">
                                <p className="text-xs text-gray-400">
                                    មិនបានទទួលកូដ? <button onClick={handleSendOtp} disabled={loading} className="text-pink-500 font-bold hover:underline ml-1">ផ្ញើឡើងវិញ</button>
                                </p>
                            </div>
                        </div>
                    ) : (
                        <>
                            <div className="flex bg-pink-50 dark:bg-zinc-800 p-1 rounded-2xl mb-4 border border-pink-100 dark:border-zinc-700">
                                <button
                                    type="button"
                                    onClick={() => setActiveTab('login')}
                                    className={`flex-1 py-2 text-xs font-bold rounded-xl transition-all ${
                                        activeTab === 'login' 
                                        ? 'bg-white dark:bg-zinc-900 text-pink-500 shadow-sm' 
                                        : 'text-gray-500 dark:text-gray-400'
                                    }`}
                                >
                                    ចូលប្រើប្រាស់
                                </button>
                                <button
                                    type="button"
                                    onClick={() => setActiveTab('register')}
                                    className={`flex-1 py-2 text-xs font-bold rounded-xl transition-all ${
                                        activeTab === 'register' 
                                        ? 'bg-white dark:bg-zinc-900 text-pink-500 shadow-sm' 
                                        : 'text-gray-500 dark:text-gray-400'
                                    }`}
                                >
                                    បង្កើតគណនី
                                </button>
                            </div>

                            {activeTab === 'register' && (
                                <div>
                                    <label className="block text-gray-600 dark:text-gray-300 mb-1 text-xs font-bold">ឈ្មោះពេញ</label>
                                    <input 
                                        type="text" 
                                        value={name} 
                                        onChange={(e) => setName(e.target.value)} 
                                        placeholder="ឈ្មោះរបស់អ្នក" 
                                        className="w-full bg-gray-50 dark:bg-zinc-800 border border-pink-100 dark:border-zinc-700 rounded-xl px-4 py-2.5 text-sm text-gray-800 dark:text-white outline-none focus:border-pink-500 focus:bg-white dark:focus:bg-zinc-900 focus:ring-2 focus:ring-pink-500/20 transition-all placeholder:text-gray-400" 
                                    />
                                </div>
                            )}

                            <div>
                                <label className="block text-gray-600 dark:text-gray-300 mb-1 text-xs font-bold">លេខទូរស័ព្ទ</label>
                                <input 
                                    type="text" 
                                    value={contact} 
                                    onChange={(e) => setContact(e.target.value)} 
                                    placeholder="0718240548 ឬ +855718240548" 
                                    className="w-full bg-gray-50 dark:bg-zinc-800 border border-pink-100 dark:border-zinc-700 rounded-xl px-4 py-2.5 text-sm text-gray-800 dark:text-white outline-none focus:border-pink-500 focus:bg-white dark:focus:bg-zinc-900 focus:ring-2 focus:ring-pink-500/20 transition-all placeholder:text-gray-400" 
                                />
                            </div>

                            <div>
                                <label className="block text-gray-600 dark:text-gray-300 mb-1 text-xs font-bold">ពាក្យសម្ងាត់</label>
                                <div className="relative">
                                    <input 
                                        type={showPassword ? "text" : "password"} 
                                        value={password} 
                                        onChange={(e) => setPassword(e.target.value)} 
                                        placeholder="••••••••" 
                                        className="w-full bg-gray-50 dark:bg-zinc-800 border border-pink-100 dark:border-zinc-700 rounded-xl px-4 py-2.5 text-sm text-gray-800 dark:text-white outline-none focus:border-pink-500 focus:bg-white dark:focus:bg-zinc-900 focus:ring-2 focus:ring-[#E91E63]/20 transition-all placeholder:text-gray-400 pr-10" 
                                    />
                                    <button 
                                        type="button" 
                                        onClick={() => setShowPassword(!showPassword)} 
                                        className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-pink-500 transition-colors"
                                    >
                                        {showPassword ? "🙈" : "👁️"}
                                    </button>
                                </div>
                            </div>

                            {error && (
                                <p className="text-red-500 text-xs text-center font-medium bg-red-50 dark:bg-red-950/30 border border-red-200 p-2.5 rounded-xl">
                                    {error}
                                </p>
                            )}

                            <button 
                                onClick={handleSendOtp} 
                                disabled={loading} 
                                className="w-full bg-pink-500 hover:bg-pink-600 active:scale-[0.99] text-white font-bold py-3 rounded-xl mt-2 flex items-center justify-center gap-2 shadow-lg shadow-pink-500/25 transition-all disabled:opacity-70 disabled:cursor-not-allowed"
                            >
                                {loading ? (
                                    <>
                                        <svg className="animate-spin h-4 w-4 text-white" viewBox="0 0 24 24" fill="none">
                                            <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
                                            <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
                                        </svg>
                                        <span>កំពុងផ្ញើកូដ...</span>
                                    </>
                                ) : (
                                    <span>{activeTab === 'login' ? 'ចូលប្រើប្រាស់' : 'ចុះឈ្មោះ'}</span>
                                )}
                            </button>

                            <div className="relative flex items-center justify-center my-3">
                                <div className="border-t border-gray-100 dark:border-zinc-800 w-full absolute"></div>
                                <span className="bg-white dark:bg-[#18181b] px-3 text-gray-400 text-xs relative z-10 font-bold">ឬ</span>
                            </div>

                            <button 
                                onClick={handleGoogleLogin} 
                                className="w-full border border-gray-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 py-2.5 rounded-xl flex items-center justify-center gap-2 hover:bg-pink-50/50 dark:hover:bg-zinc-700 transition-all text-xs font-bold text-gray-700 dark:text-gray-200 shadow-sm"
                            >
                                <svg className="w-4 h-4" viewBox="0 0 24 24">
                                    <path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
                                    <path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
                                    <path fill="#FBBC05" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.06H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.94l2.85-2.22.81-.63z" />
                                    <path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.06l3.66 2.84c.87-2.6 3.3-4.52 6.16-4.52z" />
                                </svg>
                                <span>បន្តជាមួយ Google</span>
                            </button>

                            <div className="text-center mt-3">
                                <button 
                                    onClick={() => setActiveTab(activeTab === 'login' ? 'register' : 'login')} 
                                    className="text-xs text-pink-500 hover:underline font-bold"
                                >
                                    {activeTab === 'login' ? 'មិនទាន់មានគណនី? ចុះឈ្មោះ' : 'មានគណនីរួចហើយ? ចូលប្រើ'}
                                </button>
                            </div>
                        </>
                    )}
                </div>
            </div>
        </div>
    );
};
    
    const ShareModal = ({ onClose, eventId, defaultGuestName, formData, currentUser, templateId, setCurrentEventId }) => {
        const [guestName, setGuestName] = useState(defaultGuestName);
        const [generatedShortLink, setGeneratedShortLink] = useState('');
        const [isGenerating, setIsGenerating] = useState(false);
        const [copied, setCopied] = useState(false);
        const [isDownloading, setIsDownloading] = useState(false);
        const debouncedName = useDebounce(guestName, 800);

        useEffect(() => {
            const generateLink = async () => {
                if (!debouncedName || !eventId || !db) return;
                setIsGenerating(true);
                try {
                    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
                    let shortId = '';
                    for (let i = 0; i < 6; i++) {
                        shortId += chars.charAt(Math.floor(Math.random() * chars.length));
                    }
                    
                    setGeneratedShortLink(`${window.location.origin}${window.location.pathname}?i=${shortId}`);
                } catch (e) {
                    console.error("Short link failed", e);
                } finally {
                    setIsGenerating(false);
                }
            };
            generateLink();
        }, [debouncedName, eventId]);

        // ១. បង្កើត Link សម្រាប់ Invite ឱ្យច្បាស់លាស់ (Direct Link)
const directInviteLink = `${window.location.origin}${window.location.pathname}?view=invite&id=${eventId}&guest=${encodeURIComponent(guestName)}`;

// ២. កំណត់ Link បង្ហាញ (ប្រើ Short Link បើមាន បើមិនទាន់មានប្រើ Direct Link)
const displayLink = generatedShortLink || directInviteLink;

// ៣. បង្កើត QR Code (បងអាចជ្រើសរើសយក displayLink ឬ directInviteLink)
// ផ្ដល់យោបល់៖ ប្រើ directInviteLink ក្នុង QR គឺមានសុវត្ថិភាពបំផុតសម្រាប់ភ្ញៀវ Scan
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(directInviteLink)}`;

        const handleCopy = async (channel = 'copy') => {
            if (!guestName.trim()) { 
                alert("សូមបញ្ចូលឈ្មោះភ្ញៀវសិន!"); 
                return; 
            }
            
            setIsGenerating(true);
            try {
                let finalEventId = eventId;
                const shortId = Math.random().toString(36).substring(2, 8);
                const finalLink = `${window.location.origin}${window.location.pathname}?i=${shortId}`;

                // 🔥 ដំណោះស្រាយ៖ ធ្វើការ Copy លីងចូលទូរស័ព្ទមុនគេបង្អស់ ដើម្បីកុំឱ្យ iPhone (Safari) Block
                try {
                    if (navigator.clipboard && window.isSecureContext) {
                        await navigator.clipboard.writeText(finalLink);
                    } else {
                        // Fallback សម្រាប់ទូរស័ព្ទស៊េរីចាស់ ឬ Browser ដែលមិនស្គាល់ Clipboard API
                        const textArea = document.createElement("textarea");
                        textArea.value = finalLink;
                        textArea.style.position = "fixed"; // ការពារកុំឱ្យលោតអេក្រង់
                        document.body.appendChild(textArea);
                        textArea.select();
                        document.execCommand('copy');
                        document.body.removeChild(textArea);
                    }
                } catch (err) {
                    console.warn("Clipboard error, but proceeding...", err);
                }

                // បន្ទាប់ពី Copy ជាប់ហើយ ទើបយើងធ្វើការ Save ទិន្នន័យចូល Database តាមក្រោយ
                if (!finalEventId || finalEventId.startsWith('{{')) {
                    finalEventId = `evt_${Date.now()}`;
                    if (db && currentUser) {
                        await setDoc(doc(db, "events", finalEventId), {
                            id: finalEventId,
                            userId: currentUser.id,
                            templateId: templateId,
                            formData: formData,
                            createdAt: serverTimestamp(),
                            updatedAt: serverTimestamp()
                        });
                        if (setCurrentEventId) setCurrentEventId(finalEventId);
                    }
                }

                if (db) {
                    // 🔥 កត់ត្រា Channel ដែលបានប្រើផ្ញើចេញ (telegram / messenger / copy)
                    // ដើម្បីអោយ Admin អាចដឹងថា User ម្នាក់ៗបានផ្ញើតាម Telegram ប៉ុន្មាន Messenger ប៉ុន្មាន
                    await setDoc(doc(db, "short_invites", shortId), {
                        eventId: finalEventId,
                        guestName: guestName,
                        channel: channel,
                        createdAt: serverTimestamp()
                    });
                }

                setGeneratedShortLink(finalLink);

                if (channel === 'telegram') {
                    // ១. បើក Telegram Share ជាមួយ Link + Text ដែលបានរៀបចំរួច
                    const shareText = `សូមអញ្ជើញ ${guestName} ចូលរួមកម្មវិធីមង្គលការ! 💍`;
                    window.open(`https://t.me/share/url?url=${encodeURIComponent(finalLink)}&text=${encodeURIComponent(shareText)}`, '_blank');
                    setCopied(true);
                    setTimeout(() => setCopied(false), 2000);
                } else if (channel === 'messenger') {
                    // ២. ព្យាយាមបើក App Messenger ជាមុនសិន (Deep Link), បើគ្មាន App សិចនោះនឹងបើក Messenger.com ជំនួស
                    // Link ត្រូវបាន Copy ចូល Clipboard រួចស្រេចហើយ (ជំហានខាងលើ) ដូច្នេះអាច Paste ចូល Chat បានភ្លាមៗ
                    const deepLink = `fb-messenger://share/?link=${encodeURIComponent(finalLink)}`;
                    const fallbackTimer = setTimeout(() => {
                        window.open('https://www.messenger.com/', '_blank');
                    }, 1200);
                    window.location.href = deepLink;
                    setCopied(true);
                    setTimeout(() => setCopied(false), 2000);
                } else {
                    setCopied(true);
                    setTimeout(() => setCopied(false), 2000);
                }
            } catch (e) {
                console.error("Error:", e);
                alert("ការផ្ញើមានបញ្ហា សូមសាកល្បងម្ដងទៀត!");
            } finally {
                setIsGenerating(false);
            }
        };

        const handleDownloadQr = async () => {
            setIsDownloading(true);
            try {
                const response = await fetch(qrUrl);
                const blob = await response.blob();
                const img = new Image();
                const url = URL.createObjectURL(blob);

                img.onload = () => {
                    const canvas = document.createElement('canvas');
                    const ctx = canvas.getContext('2d');
                    const padding = 50; 
                    canvas.width = img.width + (padding * 2);
                    canvas.height = img.height + (padding * 2);
                    ctx.fillStyle = '#FFFFFF';
                    ctx.fillRect(0, 0, canvas.width, canvas.height);
                    ctx.drawImage(img, padding, padding);
                    const finalUrl = canvas.toDataURL("image/png");
                    const a = document.createElement('a');
                    a.href = finalUrl;
                    a.download = `QR-${guestName || 'invitation'}.png`;
                    document.body.appendChild(a);
                    a.click();
                    document.body.removeChild(a);
                    window.URL.revokeObjectURL(url);
                };
                img.src = url;
            } catch (error) {
                console.error("Download failed", error);
                alert("Download failed!");
            } finally {
                setIsDownloading(false);
            }
        };

        return (
            <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/95 backdrop-blur-sm p-4 animate-fade-in font-suwannaphum">
                <div className="bg-white dark:bg-[#1E1E1E] w-full max-w-sm rounded-xl shadow-2xl p-6 relative flex flex-col gap-4">
                    <button onClick={onClose} className="absolute top-4 right-4 text-gray-400 hover:text-gray-600 dark:hover:text-white transition"><X size={20} /></button>
                    <h2 className="text-center text-xl font-bold text-[#D4AF37] font-moul">ចែករំលែកធៀប</h2>
                    <div>
                        <label className="block text-gray-600 dark:text-gray-400 mb-1 text-sm font-bold">ឈ្មោះភ្ញៀវ (Guest Name)</label>
                        <input type="text" value={guestName} onChange={(e) => setGuestName(e.target.value)} className="w-full bg-gray-50 dark:bg-[#2A2A2A] border border-gray-300 dark:border-gray-700 rounded-lg p-2 focus:border-[#D4AF37] outline-none text-slate-800 dark:text-white font-bold text-center"/>
                    </div>
                    <div className="relative">
                        <label className="text-gray-600 dark:text-gray-400 text-xs font-bold mb-1 block">តំណភ្ជាប់ (Link)</label>
                        <input readOnly value={displayLink} className="w-full bg-gray-100 dark:bg-[#2A2A2A] border border-gray-300 dark:border-gray-700 rounded-lg p-2 text-xs text-gray-600 dark:text-gray-300 font-mono focus:outline-none"/>
                        {isGenerating && <span className="absolute right-2 top-8 text-xs text-[#D4AF37]"><RefreshCw size={10} className="animate-spin"/></span>}
                    </div>
                    <div className="grid grid-cols-2 gap-3">
                        <button onClick={() => handleCopy('copy')} className="bg-[#D4AF37] hover:bg-[#B5942E] text-white font-bold py-2 rounded-lg flex items-center justify-center gap-2 transition text-sm shadow-md">{copied ? <Check size={16}/> : <Copy size={16}/>} {copied ? 'បានចម្លង' : 'Copy'}</button>
                        <button className="bg-black hover:bg-gray-900 text-white font-bold py-2 rounded-lg flex items-center justify-center gap-2 transition text-sm shadow-md"><QrCode size={16}/> View QR</button>
                    </div>

                    <div className="flex justify-center my-2">
                        <div className="bg-white p-2 rounded-lg border-2 border-gray-200">
                            <img src={qrUrl} className="w-32 h-32 object-contain" alt="QR Code"/>
                        </div>
                    </div>
                    <button onClick={handleDownloadQr} disabled={isDownloading} className="w-full bg-gradient-to-r from-blue-600 to-blue-500 hover:from-blue-700 hover:to-blue-600 text-white font-bold py-3 rounded-lg flex items-center justify-center gap-2 transition text-sm shadow-lg transform hover:scale-[1.02]">
                        {isDownloading ? <RefreshCw size={18} className="animate-spin"/> : <Download size={18}/>} Download QR Code
                    </button>
                </div>
            </div>
        );
    };
	
	
// --- PERFECTLY CENTERED & BALANCED DESIGN: UPGRADE MODAL ---
    const UpgradeModal = ({ onClose, onUpgrade, currentUser, targetTemplateId, title }) => { 
        const [verifyCode, setVerifyCode] = useState(''); 
        const [loading, setLoading] = useState(false); 
        
        const handleVerifyPayment = async () => { 
            if (!currentUser) return; 
            if (!db) { alert("Database unavailable."); return; } 
            
            const inputCode = verifyCode.trim(); 
            if (!inputCode) { alert("សូមបញ្ចូលលេខកូដ"); return; } 
            
            setLoading(true); 
            try { 
                const codeRef = doc(db, "verification_codes", inputCode); 
                const codeSnap = await getDoc(codeRef); 
                if (codeSnap.exists() && codeSnap.data().status === 'active') { 
                    await updateDoc(codeRef, { status: 'used', usedBy: currentUser.id, usedAt: serverTimestamp() }); 
                    const userRef = doc(db, "users", currentUser.id); 
                    
                    // ប្រើ setDoc + merge:true ជំនួស updateDoc ដើម្បីកុំឲ្យ Error
                    // "No document to update" ក្នុងករណី Document របស់ User មិនទាន់មាននៅឡើយ
                    // 🔥 កត់ត្រាថ្ងៃ Pro ដំបូង (proDate) តែម្ដងគត់ — ថ្ងៃ Login ដំបូង (createdAt) នៅដដែល
                    let prevUserData = {};
                    try { const prevSnap = await getDoc(userRef); if (prevSnap.exists()) prevUserData = prevSnap.data(); } catch(e) {}
                    const proPayload = { purchasedTemplates: arrayUnion(targetTemplateId), type: 'PRO' };
                    if (!prevUserData.proDate || prevUserData.type !== 'PRO') proPayload.proDate = new Date().toISOString();
                    await setDoc(userRef, proPayload, { merge: true }); 
                    
                    alert("ជោគជ័យ! Template ត្រូវបានបើកដំណើរការ។"); 
                    if (onUpgrade) onUpgrade(targetTemplateId); 
                    onClose(); 
                } else { 
                    alert("លេខកូដមិនត្រឹមត្រូវ ឬហួសសុពលភាព។"); 
                } 
            } catch (e) { 
                console.error(e); 
                alert("Verification failed: " + e.message); 
            } finally { 
                setLoading(false); 
            } 
        }; 

        return (
            <div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/80 backdrop-blur-md p-4 animate-fade-in font-suwannaphum">
                <div className="bg-white dark:bg-[#1A1A1A] w-full max-w-4xl rounded-2xl shadow-2xl overflow-hidden flex flex-col max-h-[90vh] border border-amber-500/20">
                    
                    {/* Header Bar - Centered Title */}
                    <div className="bg-gradient-to-r from-amber-50 to-orange-50 dark:from-gray-800 dark:to-gray-900 px-6 py-4 border-b border-amber-200/60 dark:border-gray-800 flex items-center justify-between relative">
                        <div className="w-full flex items-center justify-center gap-2 pr-6">
                            <Crown className="text-amber-500 fill-amber-500 flex-shrink-0" size={20}/>
                            <h3 className="font-bold text-lg text-slate-800 dark:text-amber-400 font-moul text-center leading-normal">
                                {title || "Purchase Template"}
                            </h3>
                        </div>
                        <button onClick={onClose} className="absolute right-4 top-4 p-1 rounded-full text-gray-400 hover:text-gray-600 dark:hover:text-white transition">
                            <X size={20}/>
                        </button>
                    </div>

                    {/* Main Content Layout */}
                    <div className="flex flex-col md:flex-row p-6 gap-6 overflow-y-auto">
                        
                        {/* Left Card: QR Payment Code (Centered) */}
                        <div className="bg-gradient-to-b from-amber-50/40 via-white to-amber-50/20 dark:from-gray-900 dark:to-gray-950 p-6 rounded-2xl border border-amber-100 dark:border-gray-800 shadow-sm flex-1 flex flex-col items-center justify-center text-center">
                            <h3 className="font-bold text-sm md:text-base text-slate-700 dark:text-gray-200 mb-2 leading-relaxed text-center"> 
                                <i>សូមធ្វើការ Scan QR Code រួច Screenshot ទៅកាន់ Admin ដើម្បីធ្វើការ Verify Code</i> 
                            </h3>
                            
                            <p className="text-gray-500 dark:text-gray-400 text-sm mb-4 font-bold text-center">
                                <PriceTag id={targetTemplateId} compact />
                            </p>

                            <div className="border-2 border-amber-400/40 rounded-2xl p-3 mb-3 bg-white shadow-md hover:shadow-lg transition">
                                <img 
                                    src={cldOpt(hasTemplateIntro(targetTemplateId) ? QR_PREMIUM_12_99 : QR_DEFAULT_9_99, 600)} loading="lazy" decoding="async" 
                                    alt="ABA QR" 
                                    className="w-48 h-48 object-contain"
                                />
                            </div>
                            <div className="text-xs font-bold text-amber-600 dark:text-amber-400 font-sans tracking-wide text-center">Scan via ABA Mobile</div>
                        </div>

                        {/* Right Card: Admin Contact & Activation (Centered) */}
                        <div className="bg-white dark:bg-gray-900 p-6 rounded-2xl border border-gray-100 dark:border-gray-800 shadow-sm flex-1 flex flex-col items-center justify-center text-center gap-4">
                            
                            <h3 className="font-bold text-base text-slate-800 dark:text-white font-moul text-center w-full">
                                ទំនាក់ទំនង Admin
                            </h3>
                            
                            {/* Telegram Button */}
                            <a 
                                href={`https://t.me/HinHeang?text=ខ្ញុំចង់ទិញ Template: ${targetTemplateId}`} 
                                target="_blank" 
                                rel="noreferrer"
                                className="w-full bg-gradient-to-r from-sky-500 via-blue-600 to-indigo-600 hover:from-sky-600 hover:to-indigo-700 text-white font-bold py-3.5 rounded-xl flex items-center justify-center gap-2 transition shadow-md shadow-sky-500/20 active:scale-[0.98]"
                            >
                                <Send size={18}/> Telegram Admin
                            </a>

                            <div className="border-t border-gray-100 dark:border-gray-800 w-full my-1"></div>
                            
                            {/* Code Input & Unlock Button Area (Centered) */}
                            <div className="flex flex-col items-center justify-center gap-3 w-full">
                                
                                <label className="block text-sm font-bold text-slate-700 dark:text-gray-300 text-center w-full">
                                    Verification Code
                                </label>
                                
                                <div className="w-full">
                                    <input 
                                        type="text" 
                                        value={verifyCode} 
                                        onChange={(e) => setVerifyCode(e.target.value)} 
                                        className="w-full border-2 border-gray-200 dark:border-gray-700 rounded-xl p-3 focus:border-amber-500 dark:focus:border-amber-400 outline-none font-mono text-center tracking-widest uppercase bg-gray-50 dark:bg-gray-800 text-slate-900 dark:text-white font-bold text-base shadow-inner placeholder:text-center" 
                                        placeholder="ENTER CODE"
                                    />
                                </div>

                                <p className="text-xs text-gray-400 mt-0.5 text-center w-full">
                                    ទទួលបានលេខកូដពី Admin បន្ទាប់ពីផ្ញើ Slip រួច។
                                </p>

                                {/* Unlock Button Centered */}
                                <button 
                                    onClick={handleVerifyPayment} 
                                    disabled={loading} 
                                    className="w-full bg-gradient-to-r from-emerald-500 to-green-600 hover:from-emerald-600 hover:to-green-700 text-white font-bold py-3.5 rounded-xl flex items-center justify-center gap-2 transition shadow-lg shadow-emerald-500/20 active:scale-[0.98] mt-1"
                                >
                                    {loading ? <RefreshCw className="animate-spin" size={18}/> : <Crown size={18}/>}
                                    <span>Unlock Now</span>
                                </button>
                            </div>

                        </div>

                    </div>
                </div>
            </div>
        ); 
    };
	
	
    // --- Lightbox សម្រាប់ Gallery (ចុចមើលធំ + ប្តូររូប + ការពារមិនអោយ Save) ---
    const GalleryLightbox = ({ images, index, onClose, onChange }) => {
        const touchStartX = useRef(null);
        const [loadedIdx, setLoadedIdx] = useState(-1);
        const total = images.length;
        const go = (dir) => onChange((index + dir + total) % total);

        useNavLayer(true, onClose);   // ប៊ូតុង Back លើទូរស័ព្ទ = បិទរូបធំ (មិនចេញពីទំព័រ)

        useEffect(() => {
            const onKey = (e) => {
                if (e.key === 'Escape') onClose();
                else if (e.key === 'ArrowLeft') onChange((index - 1 + total) % total);
                else if (e.key === 'ArrowRight') onChange((index + 1) % total);
            };
            window.addEventListener('keydown', onKey);
            return () => window.removeEventListener('keydown', onKey);
        }, [index, total]);

        useEffect(() => {
            const prevOverflow = document.body.style.overflow;
            document.body.style.overflow = 'hidden';
            return () => { document.body.style.overflow = prevOverflow; };
        }, []);

        // Preload រូបបន្ទាប់/មុន ទុកជាមុន → ប្តូររូបភ្លាមៗ មិនរង់ចាំ
        useEffect(() => {
            [1, -1, 2].forEach((d) => {
                const im = new Image();
                im.decoding = 'async';
                im.src = cldOpt(images[(index + d + total) % total], 900);
            });
        }, [index]);

        const block = (e) => { e.preventDefault(); e.stopPropagation(); return false; };
        const noSave = { WebkitTouchCallout: 'none', WebkitUserSelect: 'none', userSelect: 'none' };

        return (
            <div
                className="fixed inset-0 z-[10000] bg-black/95 flex items-center justify-center select-none"
                style={noSave}
                onClick={onClose}
                onContextMenu={block}
                onTouchStart={(e) => { touchStartX.current = e.touches[0].clientX; }}
                onTouchEnd={(e) => {
                    if (touchStartX.current === null) return;
                    const dx = e.changedTouches[0].clientX - touchStartX.current;
                    touchStartX.current = null;
                    if (Math.abs(dx) > 50) go(dx < 0 ? 1 : -1);
                }}
            >
                <button onClick={onClose} className="absolute top-4 right-4 w-11 h-11 rounded-full bg-white/15 hover:bg-white/30 text-white flex items-center justify-center z-20 transition" aria-label="Close"><X size={24} /></button>
                <div className="absolute top-5 left-1/2 -translate-x-1/2 text-white/80 text-sm font-bold bg-black/40 px-3 py-1 rounded-full z-20">{index + 1} / {total}</div>

                <button onClick={(e) => { e.stopPropagation(); go(-1); }} className="absolute left-2 md:left-6 w-11 h-11 md:w-12 md:h-12 rounded-full bg-white/15 hover:bg-white/30 text-white flex items-center justify-center z-20 transition" aria-label="Previous"><ChevronLeft size={28} /></button>
                <button onClick={(e) => { e.stopPropagation(); go(1); }} className="absolute right-2 md:right-6 w-11 h-11 md:w-12 md:h-12 rounded-full bg-white/15 hover:bg-white/30 text-white flex items-center justify-center z-20 transition" aria-label="Next"><ChevronRight size={28} /></button>

                <div className="relative" onClick={(e) => e.stopPropagation()} onContextMenu={block}>
                    {/* រូបតូច (មាននៅក្នុង cache ស្រាប់ពី Grid) បង្ហាញភ្លាមៗ ខណៈរង់ចាំរូបធំ */}
                    <img
                        src={cldOpt(images[index], 640)}
                        alt=""
                        draggable={false}
                        style={{ ...noSave, pointerEvents: 'none' }}
                        className="max-h-[82vh] max-w-[88vw] w-auto h-auto object-contain rounded-xl"
                    />
                    <img
                        key={index}
                        ref={(el) => { if (el && el.complete && el.naturalWidth) setLoadedIdx(index); }}
                        src={cldOpt(images[index], 900)}
                        alt={`Gallery ${index + 1}`}
                        draggable={false}
                        decoding="async"
                        onLoad={() => setLoadedIdx(index)}
                        onContextMenu={block}
                        onDragStart={block}
                        style={{ ...noSave, pointerEvents: 'none', opacity: loadedIdx === index ? 1 : 0, transition: 'opacity 150ms' }}
                        className="absolute inset-0 w-full h-full object-contain rounded-xl"
                    />
                    {/* ស្រទាប់ថ្លាគ្របលើរូប — ទប់ស្កាត់ Right click / Long-press / Drag */}
                    <div className="absolute inset-0" style={noSave} onContextMenu={block} onDragStart={block}></div>
                </div>
            </div>
        );
    };

    // --- ផ្នែកទី ៣៖ LandingPage ពេញលេញ (មានប៊ូតុងខួបកំណើត) ---
    const LandingPage = ({ setViewMode, currentUser, setCurrentUser, templates, onSelectTemplate, onTriggerUpgrade }) => {
        const viewCounts = useMemo(() => buildViewCounts(templates), [templates]);
        const [showAuthModal, setShowAuthModal] = useState(false);
        const [galleryIndex, setGalleryIndex] = useState(null);
        const [authTab, setAuthTab] = useState('login');
        const [isDark, setIsDark] = useState(false);
        const [previewTemplate, setPreviewTemplate] = useState(null);
        useNavLayer(!!previewTemplate, () => setPreviewTemplate(null));   // Back = បិទ Preview
        const [activeCategory, setActiveCategory] = useState('wedding');
        const [isNavRowVisible, setIsNavRowVisible] = useState(true);
        const scrollContainerRef = useRef(null);
        const lastScrollYRef = useRef(0);
        const navLockRef = useRef(0);      // ពេលចុច Menu ៖ សោរកុំឱ្យក្បាលទំព័របាត់
        const navLockTimerRef = useRef(null);

        // ធ្វើឱ្យក្បាលទំព័របង្ហាញ និងចាក់សោររយៈពេលខ្លី (ពេល Smooth Scroll កំពុងដំណើរការ)
        const lockHeaderVisible = () => {
            navLockRef.current = Date.now() + 1500;
            setIsNavRowVisible(true);
            if (navLockTimerRef.current) clearTimeout(navLockTimerRef.current);
            navLockTimerRef.current = setTimeout(() => {
                const el = scrollContainerRef.current;
                if (el) lastScrollYRef.current = el.scrollTop;
                navLockRef.current = 0;
            }, 1500);
        };

        useEffect(() => () => { if (navLockTimerRef.current) clearTimeout(navLockTimerRef.current); }, []);

        useEffect(() => {
            const el = scrollContainerRef.current;
            if (!el) return;
            const handleScroll = () => {
                const currentY = el.scrollTop;
                const lastY = lastScrollYRef.current;
                // កំពុងចាក់សោរ (ទើបចុច Menu) ៖ រក្សាក្បាលទំព័រនៅដដែល
                if (navLockRef.current && Date.now() < navLockRef.current) {
                    lastScrollYRef.current = currentY;
                    setIsNavRowVisible(true);
                    return;
                }
                if (currentY <= 30) {
                    // នៅជិតខាងលើគេ ៖ បង្ហាញក្បាលទំព័រជានិច្ច
                    setIsNavRowVisible(true);
                } else if (currentY > lastY + 6) {
                    // Scroll ចុះក្រោម ៖ លាក់ក្បាលទំព័រទាំងស្រុង (Phone only)
                    setIsNavRowVisible(false);
                } else if (currentY < lastY - 6) {
                    // Scroll ឡើងលើ ៖ បង្ហាញក្បាលទំព័រវិញ
                    setIsNavRowVisible(true);
                }
                lastScrollYRef.current = currentY;
            };
            el.addEventListener('scroll', handleScroll, { passive: true });
            return () => el.removeEventListener('scroll', handleScroll);
        }, []);

        const featuresList = [ 
            { title: "លិខិតអញ្ជើញបែប Digital", desc: "បង្ហាញព័ត៌មានសង្ខេបអំពីពិធីមង្គលការ, ពាក្យស្លោក និង Link ចូលមើលគំរូ។ ជាជម្រើសសម្រាប់ចែករំលែកតាមការផ្ញើ Messenger / Chat", icon: Smartphone }, 
            { title: "រចនាប័ទ្មឈ្មោះភ្ញៀវកិត្តិយស", desc: "ផ្ទាំងសម្រាប់រៀបចំដាក់ឈ្មោះភ្ញៀវកិត្តិយស អាចពេញឈ្មោះ, អ្នកចូលរួម, ចំណីអាហារជាដើម។", icon: Users }, 
            { title: "ប្រតិទិន - Event Calendar", desc: "បង្ហាញពេលវេលា និងកាលបរិច្ឆេទពេញលេញ នៃពិធីមង្គលការ។ រួមទាំងរយៈពេលកម្ម្វិធីផ្សេងៗចាប់ពីព្រឹកដល់យប់។", icon: Calendar }, 
            { title: "រាប់ថយក្រោយ - Event Countdown", desc: "បង្ហាញម៉ោង/ថ្ងៃរាប់ថយក្រោយរហូតដល់ថ្ងៃពិធីមង្គលការ។ ធ្វើឱ្យមានអារម្មណ៍រំភើប និងទន្ទឹងរង់ចាំ។", icon: Clock }, 
            { title: "កម្មវិធី - Wedding", desc: "បង្ហាញសេចក្តីលម្អិតក្នុងពិធីមង្គលការ។ អាចមានពេលវេលា តាមដំណាក់កាលពិធីផ្សេងៗ។", icon: Heart }, 
            { title: "អរគុណ & អធ្យាស្រ័យ\nThank You & Sorry", desc: "ផ្ទាំងសម្រាប់បង្ហាញការអរគុណដល់ភ្ញៀវ និងសុំទោសបើមានកំហុសឆ្គងណាមួយ។", icon: MessageSquare }, 
            { title: "ទីតាំងពិធី - Google Maps", desc: "បង្ហាញទីតាំងពិធីតាម Google Maps។ អនុញ្ញាតឱ្យភ្ញៀវចុច Navigate ទៅទីតាំងបានភ្លាមៗ។", icon: MapPin }, 
            { title: "រូបថត អនុស្សាវរីយ៍ - Pre-Wedding", desc: "បង្ហាញរូប pre-wedding ឬ photo shoot របស់គូស្នេហ៍។ មាន layout gallery ស្អាតៗ។", icon: ImageIcon }, 
            { title: "Scan QR Code", desc: "ផ្ទាំងសម្រាប់បង្ហាញ QR Code ជាពិសេសសម្រាប់ Payment (ABA Payway) ឬ Check-in QR ឬ Link Sharing។", icon: QrCode }, 
            { title: "សារជូនពរ - Best Wishes", desc: "ផ្ទាំងឱ្យភ្ញៀវសរសេរ Message ជូនពរគូស្នេហ៍។ អាចបង្ហាញជា List និង Save ក្នុង Database។", icon: Mail }, 
            { title: "ទំនាក់ទំនងម្ចាស់ពិធីមង្គល", desc: "បង្ហាញ link អនុញ្ញាតឱ្យទៅកាន់ម្ចាស់ពិធីភ្លាមៗ។", icon: Send } 
        ];

        useEffect(() => {
            setIsDark(document.documentElement.classList.contains('dark'));
        }, []);

        const toggleTheme = () => {
            const html = document.documentElement;
            if (html.classList.contains('dark')) {
                html.classList.remove('dark');
                try { localStorage.setItem('theme', 'light'); } catch(e){}
                setIsDark(false);
            } else {
                html.classList.add('dark');
                try { localStorage.setItem('theme', 'dark'); } catch(e){}
                setIsDark(true);
            }
        };

        const openAuth = (tab) => {
            setAuthTab(tab);
            setShowAuthModal(true);
        };

        const handleLoginSuccess = (user) => {
             if (user.contact === ADMIN_EMAIL || user.email === ADMIN_EMAIL) {
                user = { ...user, type: 'PRO', purchasedTemplates: ['classic-gold', 'royal-gold', 'luxury-gold', 'default'] };
                try { localStorage.setItem('active_session', JSON.stringify(user)); } catch(e){}
            }
            setCurrentUser(user);
        };

        const handleLogout = async () => {
            if(window.confirm('តើអ្នកចង់ចាកចេញមែនទេ?')) {
                const auth = getAuth(app); 
                try { await signOut(auth); } catch(e) {}
                setCurrentUser(null);
                try { localStorage.removeItem('active_session'); } catch(e){}
            }
        };
        
        const handleNavClick = (item) => {
            const map = {
                'ទំព័រដើម': 'section-hero',
                'គំរូរៀបការ': 'section-templates',
                'មុខងារ': 'section-features',
                'រូបភាព': 'section-gallery',
                'សំនួរញឹកញាប់': 'section-faq',
                'មតិអ្នកប្រើប្រាស់': 'section-reviews',
                'ទាក់ទងយើង': 'section-contact-footer'
            };
            // ពេលចុចប៊ូតុង Menu ៖ ចាក់សោរឱ្យក្បាលទំព័រនៅដដែល (មិនបាត់)
            lockHeaderVisible();
            const id = map[item];
            if (id) {
                const el = document.getElementById(id);
                if (el) el.scrollIntoView({ behavior: 'smooth' });
            }
        };
        
        return (
            <div className="h-screen flex flex-col bg-[#FFF0F5] dark:bg-black text-slate-900 dark:text-white font-suwannaphum overflow-hidden transition-colors duration-500" style={{height: '100dvh'}}>
            <header className={`mobile-autohide-header ${isNavRowVisible ? '' : 'mobile-header-hidden'} flex flex-col md:flex-row items-center justify-between gap-3 md:gap-0 px-4 py-3 md:px-12 md:py-6 bg-white/50 dark:bg-black/50 backdrop-blur-sm sticky top-0 z-50 shadow-sm border-b border-gray-100 dark:border-gray-800`}>
                {/* ជួរទី ១ លើទូរស័ព្ទ ៖ Logo ខាងឆ្វេង + Account ខាងស្ដាំ (លើ Computer ដូចដើម) */}
                <div className="flex flex-wrap w-full items-center justify-between gap-y-3 md:w-auto md:contents">
                <div className="flex items-center gap-3 order-1 md:order-1">
                <div className="w-10 h-10 md:w-12 md:h-12 rounded-full border-2 border-amber-500 dark:border-white/20 overflow-hidden shadow-md cursor-pointer shrink-0" onClick={() => handleNavClick('ទំព័រដើម')}>
                    <img src={cldOpt("https://res.cloudinary.com/duwva5xdx/image/upload/v1767102024/logo_swlob4.webp", 160)} className="w-full h-full object-cover" alt="Logo" decoding="async" />
                </div>
                </div>
                
                <div className="w-full order-3 md:w-auto md:order-2 md:overflow-visible mt-0">
                <nav className="grid grid-cols-4 gap-1.5 w-full md:w-auto md:flex md:flex-wrap md:justify-center md:gap-4 items-stretch md:items-center">
    {/* ទំព័រដើម */}
    <button onClick={() => handleNavClick('ទំព័រដើម')} className="w-full md:w-auto px-1.5 py-2.5 md:px-4 md:py-1.5 rounded-full text-[12px] md:text-base font-bold transition hover:scale-105 bg-white dark:bg-[#FDF5E6] border border-gray-200 dark:border-transparent text-slate-700 dark:text-black hover:bg-[#FFD700] dark:hover:bg-[#FFD700] flex items-center justify-center gap-1 md:gap-1.5 text-center leading-tight md:whitespace-nowrap min-w-0 break-words">
        <span className="shrink-0">🏠</span>
        <span>ទំព័រដើម</span>
    </button>

    {/* Dropdown គំរូធៀប */}
    <div className="relative group w-full md:w-auto">
        <button className="w-full md:w-auto px-1.5 py-2.5 md:px-4 md:py-1.5 rounded-full text-[12px] md:text-base font-bold transition hover:scale-105 bg-[#FFD700] text-black flex items-center justify-center gap-1 md:gap-1.5 cursor-pointer text-center leading-tight md:whitespace-nowrap min-w-0 break-words">
            <span>💌</span>
            <span>គំរូធៀប</span> 
            <i className="fa-solid fa-chevron-down text-[10px] md:text-xs ml-0.5"></i>
        </button>
        <div className="absolute top-full left-1/2 -translate-x-1/2 md:left-0 md:translate-x-0 mt-2 w-56 max-w-[80vw] bg-white dark:bg-[#1E1E1E] rounded-xl shadow-xl border border-gray-100 dark:border-gray-700 overflow-hidden hidden group-hover:block animate-up z-50">
            <button onClick={() => { setActiveCategory('wedding'); handleNavClick('គំរូរៀបការ'); document.getElementById('templates-section')?.scrollIntoView({ behavior: 'smooth' }); }} className="w-full text-left px-4 py-3 text-sm hover:bg-amber-50 dark:hover:bg-gray-800 border-b border-gray-100 dark:border-gray-800 transition flex items-center gap-2 text-gray-700 dark:text-gray-200">
                <span>👩‍❤️‍👨</span> ធៀបមង្គលការ
            </button>
            <button onClick={() => { setActiveCategory('gift-reception'); handleNavClick('គំរូរៀបការ'); document.getElementById('templates-section')?.scrollIntoView({ behavior: 'smooth' }); }} className="w-full text-left px-4 py-3 text-sm hover:bg-amber-50 dark:hover:bg-gray-800 border-b border-gray-100 dark:border-gray-800 transition flex items-center gap-2 text-gray-700 dark:text-gray-200">
                <span>🎁</span> ធៀបកាត់ចំណងដៃ
            </button>
            <button onClick={() => { setActiveCategory('engagement'); handleNavClick('គំរូរៀបការ'); document.getElementById('templates-section')?.scrollIntoView({ behavior: 'smooth' }); }} className="w-full text-left px-4 py-3 text-sm hover:bg-amber-50 dark:hover:bg-gray-800 border-b border-gray-100 dark:border-gray-800 transition flex items-center gap-2 text-gray-700 dark:text-gray-200">
                <span>💍</span> ធៀបភ្ជាប់ពាក្យ
            </button>
            <button onClick={() => { setActiveCategory('housewarming'); handleNavClick('គំរូរៀបការ'); document.getElementById('templates-section')?.scrollIntoView({ behavior: 'smooth' }); }} className="w-full text-left px-4 py-3 text-sm hover:bg-amber-50 dark:hover:bg-gray-800 border-b border-gray-100 dark:border-gray-800 transition flex items-center gap-2 text-gray-700 dark:text-gray-200">
                <span>🏠</span> ធៀបឡើងគេហដ្ឋាន
            </button>
            <button onClick={() => { setActiveCategory('birthday'); handleNavClick('គំរូរៀបការ'); document.getElementById('templates-section')?.scrollIntoView({ behavior: 'smooth' }); }} className="w-full text-left px-4 py-3 text-sm hover:bg-amber-50 dark:hover:bg-gray-800 border-b border-gray-100 dark:border-gray-800 transition flex items-center gap-2 text-gray-700 dark:text-gray-200">
                <span>🎂</span> ធៀបខួបកំណើត
            </button>
            <button onClick={() => { setActiveCategory('baby-ceremony'); handleNavClick('គំរូរៀបការ'); document.getElementById('templates-section')?.scrollIntoView({ behavior: 'smooth' }); }} className="w-full text-left px-4 py-3 text-sm hover:bg-amber-50 dark:hover:bg-gray-800 transition flex items-center gap-2 text-gray-700 dark:text-gray-200">
                <span>👶</span> ធៀបបង្កក់កូន
            </button>
        </div>
    </div>

    {/* Menu ផ្សេងៗទៀត */}
    {[
        { name: 'មុខងារ', emoji: '✨' },
        { name: 'រូបភាព', emoji: '🖼️' },
        { name: 'សំនួរញឹកញាប់', emoji: '❓' },
        { name: 'មតិអ្នកប្រើប្រាស់', emoji: '💬' },
        { name: 'ទាក់ទងយើង', emoji: '📞' }
    ].map((item, index) => (
        <button key={index} onClick={() => handleNavClick(item.name)} className="w-full md:w-auto px-1.5 py-2.5 md:px-4 md:py-1.5 rounded-full text-[12px] md:text-base font-bold transition hover:scale-105 bg-white dark:bg-[#FDF5E6] border border-gray-200 dark:border-transparent text-slate-700 dark:text-black hover:bg-[#FFD700] dark:hover:bg-[#FFD700] flex items-center justify-center gap-1 md:gap-1.5 text-center leading-tight md:whitespace-nowrap min-w-0 break-words">
            <span className="shrink-0">{item.emoji}</span>
            <span>{item.name}</span>
        </button>
    ))}
</nav>
                </div>

<div className="flex items-center gap-2 md:gap-4 order-2 md:order-3 shrink-0">
    <button onClick={toggleTheme} className="text-slate-600 dark:text-gray-400 hover:text-amber-600 dark:hover:text-white transition-colors p-2 rounded-full hover:bg-gray-100 dark:hover:bg-white/10">
        {isDark ? <i className="fa-solid fa-sun text-lg"></i> : <i className="fa-solid fa-moon text-lg"></i>}
    </button>
    {currentUser ? (
        <div className="flex items-center gap-3">
            <div className="flex items-center gap-2 px-3 py-1 bg-white dark:bg-gray-800 rounded-full border border-gray-200 dark:border-gray-700 shadow-sm">
                <img src={currentUser.avatar} alt="User" className="w-8 h-8 rounded-full border border-amber-500 object-cover" />
                <span className="font-bold text-sm hidden sm:block">{currentUser.name}</span>
                {currentUser.type === 'PRO' && (
                    <div className="bg-blue-500 text-white rounded-full w-4 h-4 flex items-center justify-center text-[10px] ml-1" title="Verified Pro">✓</div>
                )}
            </div>
            <button onClick={handleLogout} className="bg-red-50 text-red-500 p-2 rounded-full hover:bg-red-100 transition" title="Logout">
                <i className="fa-solid fa-right-from-bracket"></i>
            </button>
        </div>
    ) : (
        <>
            <button onClick={() => openAuth('login')} className="text-slate-800 dark:text-white hover:text-[#D4AF37] text-sm md:text-base font-bold whitespace-nowrap">ចូលប្រើ</button>
            <button onClick={() => openAuth('register')} className="bg-[#E91E63] hover:bg-[#C2185B] text-white px-3 md:px-5 py-1.5 rounded-full text-sm md:text-base font-bold transition flex items-center gap-2 shadow-lg whitespace-nowrap">
                <i className="fa-solid fa-user-plus text-sm"></i> សមាជិកថ្មី
            </button>
        </>
    )}
</div>
                </div>
            </header>

            <div ref={scrollContainerRef} className="flex-1 overflow-y-auto overflow-x-hidden scroll-smooth">
                <section id="section-hero" className="container mx-auto px-6 py-8 md:px-12 flex flex-col md:flex-row items-center relative z-10 min-h-[calc(100vh-100px)]">
                    <div className="w-full md:w-1/2 space-y-6 md:pr-10 text-center md:text-left pt-10 md:pt-0">
                        <h1 className="text-2xl md:text-3xl font-bold font-moul text-slate-900 dark:text-white mb-2 leading-relaxed">Phumtheapka - វេទិកាអញ្ជើញ</h1>
                        <div className="space-y-2 text-slate-600 dark:text-gray-300 text-lg md:text-xl leading-relaxed">
                            <p>រាល់ការចងចាំដ៏ផ្អែមល្ហែម</p><p>ចាប់ផ្ដើមពី ការអញ្ជើញដ៏ស្រស់ស្អាត និងមានអត្ថន័យ។</p><p>ជាមួយ Phumtheapka លិខិតអញ្ជើញឌីជីថលរបស់អ្នក</p><p>មិនត្រឹមតែជាការអញ្ជើញភ្ញៀវប៉ុណ្ណោះទេ</p><p>ប៉ុន្តែជាផ្នែកមួយនៃ ភាពស្រស់ស្អាត និងអនុស្សាវរីយ៍</p><p>ក្នុងថ្ងៃដ៏ពិសេសបំផុតរបស់អ្នក។</p>
                        </div>

                        <div className="mt-8 p-6 md:p-8 rounded-xl relative overflow-hidden group border border-gray-200 dark:border-gray-800 bg-white dark:bg-gradient-to-r dark:from-[#1a1a1a] dark:to-[#000] card-shadow-dark">
                            <div className="absolute inset-0 opacity-5 dark:opacity-20 bg-[url('https://www.transparenttextures.com/patterns/dark-matter.png')]"></div>
                            <div className="relative z-10 flex flex-col items-center md:items-start text-center md:text-left">
                                <h2 className="text-3xl md:text-5xl font-cinzel text-[#D4AF37] font-bold mb-2 tracking-wide drop-shadow-md">WEDDING DIGITAL</h2>
                                <p className="text-slate-500 dark:text-gray-400 font-script text-xl md:text-2xl mb-8">The Art of Khmer Elegant Invitations</p>
                                <div className="flex flex-wrap gap-4 justify-center md:justify-start">
                                    <button onClick={() => handleNavClick('គំរូរៀបការ')} className="px-6 py-3 bg-[#B8860B] hover:bg-[#DAA520] text-white dark:text-black font-bold rounded-full flex items-center gap-2 transition transform hover:scale-105 shadow-lg shadow-amber-900/20"><Heart size={20} className="text-white"/> <span className="font-moul text-sm">បង្កើតធៀបការ</span></button>
                                    <button onClick={() => setViewMode('EVENTS_DASHBOARD')} className="px-6 py-3 border border-[#D4AF37] text-[#D4AF37] hover:bg-[#D4AF37] hover:text-white dark:hover:text-black font-bold rounded-full flex items-center gap-2 transition transform hover:scale-105"><Calendar size={20} /> <span className="font-moul text-sm">ព្រឹត្តិការណ៍របស់អ្នក</span></button>
                                    {(currentUser?.contact === ADMIN_EMAIL || currentUser?.email === ADMIN_EMAIL) && (
                                        <button onClick={() => setViewMode('ADMIN')} className="px-6 py-3 border border-[#D4AF37] text-[#D4AF37] hover:bg-[#D4AF37] hover:text-white dark:hover:text-black font-bold rounded-full flex items-center gap-2 transition transform hover:scale-105">
                                            <ShieldCheck size={20} /> <span className="font-cinzel text-sm">ADMIN PANEL</span>
                                        </button>
                                    )}
                                </div>
                            </div>
                        </div>
                    </div>
                    <div className="w-full md:w-1/2 mt-12 md:mt-0 relative flex justify-center md:justify-end">
                        <div className="relative w-[300px] h-[400px] md:w-[400px] md:h-[550px] transform rotate-6 hover:rotate-0 transition duration-700 ease-in-out">
                            <div className="absolute inset-0 border-[10px] border-gray-200 dark:border-white/10 rounded-lg transform -rotate-6 shadow-2xl"></div>
                            <div className="w-full h-full bg-white p-3 shadow-2xl rounded transform rotate-6 overflow-hidden">
                                <img src={cldOpt("https://res.cloudinary.com/duwva5xdx/image/upload/v1766303861/111_aogr2s.webp", 800)} className="w-full h-full object-cover filter brightness-110 contrast-110" alt="Couple" decoding="async" fetchPriority="high" />
                            </div>
                        </div>
                    </div>
                </section>

                <section id="section-templates" className="min-h-screen flex flex-col justify-center py-20 relative border-b border-gray-100 dark:border-gray-800">
    <div className="max-w-6xl mx-auto px-4">
        {/* --- ផ្នែក Header និង Navigation Bar --- */}
        <div className="text-center mb-14">
            <h2 className="font-moul text-3xl md:text-4xl text-[#D4AF37] mb-3 tracking-wide drop-shadow-sm leading-relaxed">
                {activeCategory === 'wedding' && 'ជ្រើសរើសម៉ូតធៀបមង្គលការ'}
                {activeCategory === 'gift-reception' && 'ជ្រើសរើសម៉ូតធៀបកាត់ចំណងដៃ'}
                {activeCategory === 'engagement' && 'ជ្រើសរើសម៉ូតធៀបភ្ជាប់ពាក្យ'}
                {activeCategory === 'housewarming' && 'ជ្រើសរើសម៉ូតធៀបឡើងគេហដ្ឋាន'}
                {activeCategory === 'birthday' && 'ជ្រើសរើសម៉ូតធៀបខួបកំណើត'}
                {activeCategory === 'baby-ceremony' && 'ជ្រើសរើសម៉ូតធៀបបង្កក់កូន'}
            </h2>
            <p className="text-gray-400 dark:text-gray-400 font-suwannaphum text-base italic">
                Choose a design to start customizing
            </p>
            
            {/* Filter Navigation Bar (ប្តូរមកប្រើ Emoji កូនកំលោះកូនក្រមុំ 👩‍❤️‍👨) */}
            <div className="inline-flex flex-wrap justify-center items-center gap-2 md:gap-3 mt-8 p-2.5 bg-white/80 dark:bg-[#1E1E1E]/80 backdrop-blur-md rounded-2xl md:rounded-full shadow-lg border border-gray-100 dark:border-gray-800">
                <button 
                    onClick={() => setActiveCategory('wedding')} 
                    className={`px-5 py-2.5 rounded-xl md:rounded-full text-xs md:text-sm font-bold transition-all duration-300 flex items-center gap-2 ${
                        activeCategory === 'wedding' 
                        ? 'bg-gradient-to-r from-[#D4AF37] to-[#AA771C] text-white shadow-md scale-105' 
                        : 'text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
                    }`}>
                    <span className="text-base">👩‍❤️‍👨</span>
                    <span>ពិធីមង្គលការ</span>
                </button>

                <button 
                    onClick={() => setActiveCategory('gift-reception')} 
                    className={`px-5 py-2.5 rounded-xl md:rounded-full text-xs md:text-sm font-bold transition-all duration-300 flex items-center gap-2 ${
                        activeCategory === 'gift-reception' 
                        ? 'bg-gradient-to-r from-orange-500 to-amber-600 text-white shadow-md scale-105' 
                        : 'text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
                    }`}>
                    <span className="text-base">🎁</span>
                    <span>កាត់ចំណងដៃ</span>
                </button>

                <button 
                    onClick={() => setActiveCategory('engagement')} 
                    className={`px-5 py-2.5 rounded-xl md:rounded-full text-xs md:text-sm font-bold transition-all duration-300 flex items-center gap-2 ${
                        activeCategory === 'engagement' 
                        ? 'bg-gradient-to-r from-blue-500 to-indigo-600 text-white shadow-md scale-105' 
                        : 'text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
                    }`}>
                    <span className="text-base">💍</span>
                    <span>ពិធីភ្ជាប់ពាក្យ</span>
                </button>

                <button 
                    onClick={() => setActiveCategory('housewarming')} 
                    className={`px-5 py-2.5 rounded-xl md:rounded-full text-xs md:text-sm font-bold transition-all duration-300 flex items-center gap-2 ${
                        activeCategory === 'housewarming' 
                        ? 'bg-gradient-to-r from-emerald-500 to-green-600 text-white shadow-md scale-105' 
                        : 'text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
                    }`}>
                    <span className="text-base">🏠</span>
                    <span>ពិធីឡើងគេហដ្ឋាន</span>
                </button>

                <button 
                    onClick={() => setActiveCategory('birthday')} 
                    className={`px-5 py-2.5 rounded-xl md:rounded-full text-xs md:text-sm font-bold transition-all duration-300 flex items-center gap-2 ${
                        activeCategory === 'birthday' 
                        ? 'bg-gradient-to-r from-purple-500 to-pink-600 text-white shadow-md scale-105' 
                        : 'text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
                    }`}>
                    <span className="text-base">🎂</span>
                    <span>ពិធីខួបកំណើត</span>
                </button>

                <button 
                    onClick={() => setActiveCategory('baby-ceremony')} 
                    className={`px-5 py-2.5 rounded-xl md:rounded-full text-xs md:text-sm font-bold transition-all duration-300 flex items-center gap-2 ${
                        activeCategory === 'baby-ceremony' 
                        ? 'bg-gradient-to-r from-cyan-500 to-teal-600 text-white shadow-md scale-105' 
                        : 'text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
                    }`}>
                    <span className="text-base">👶</span>
                    <span>ពិធីបង្កក់កូន</span>
                </button>
            </div>
        </div>

        {/* --- ផ្នែក Card Items Grid --- */}
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 justify-items-center">
            {templates
                .filter(t => t.category === activeCategory)
                .map((template) => {
                    const isUnlocked = currentUser?.purchasedTemplates?.includes(template.id);
                    return (
                        <div key={template.id} className={`group bg-white dark:bg-[#1E1E1E] rounded-2xl card-shadow-dark transition-all duration-300 overflow-hidden flex flex-col h-[580px] w-full max-w-sm ${hasTemplateIntro(template.id) ? 'premium-card' : ''}`}>
                            <div className="relative flex-1 phone-preview-white overflow-hidden cursor-pointer" onClick={() => setPreviewTemplate(template)}>
                                <div className="absolute inset-0 pointer-events-none transform scale-[0.5] origin-top-left w-[200%] h-[200%]">
                                    <LazyPreviewFrame content={template.content} templateId={template.id} title={`Preview ${template.name}`} />
                                </div>
                                <div className="absolute top-3 left-3 z-10 pointer-events-none">{hasTemplateIntro(template.id) && (<span className="premium-badge"><Crown size={13} strokeWidth={2.5} /> PREMIUM<span className="premium-badge-sub">INTRO</span></span>)}</div>
                                <div className="absolute inset-0 bg-black/0 group-hover:bg-black/5 transition-colors flex items-center justify-center opacity-0 group-hover:opacity-100">
                                    <div className="bg-white/90 backdrop-blur px-4 py-2 rounded-full text-sm font-bold shadow-lg transform translate-y-4 group-hover:translate-y-0 transition duration-300 text-gray-800">
                                        Click to Preview
                                    </div>
                                </div>
                            </div>
                            <div className={`p-5 bg-white dark:bg-[#1E1E1E] border-t border-gray-100 dark:border-gray-700 ${hasTemplateIntro(template.id) ? 'premium-info' : ''}`}>
                                <div className="flex items-start justify-between gap-2 mb-3">
                                    <div className="font-suwannaphum"><PriceTag id={template.id} /></div>
                                    <ViewBadge label={viewCounts[String(template.id)]} className="mt-2" />
                                    
                                </div>
                                <div className="w-full h-px bg-gray-200 dark:bg-gray-700 mb-4"></div>
                                <h3 className={`font-bold text-lg text-gray-800 dark:text-white text-center font-cinzel ${hasTemplateIntro(template.id) ? 'mb-1' : 'mb-4'}`}>{template.name}</h3>{hasTemplateIntro(template.id) && (<div className="premium-perk">មាន Intro បែបភាពយន្ត និង Animation ពិសេស</div>)}
                                <div className="flex gap-3 mb-3">
                                    <button onClick={() => setPreviewTemplate(template)} className="flex-1 py-2 rounded-lg border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 font-medium text-xs hover:bg-gray-50 dark:hover:bg-gray-800 transition flex items-center justify-center gap-1">
                                        <Eye size={14} /> Preview
                                    </button>
                                    <button onClick={() => onSelectTemplate(template)} className="flex-1 py-2 rounded-lg border border-[#D4AF37] text-[#D4AF37] font-medium text-xs hover:bg-amber-50 dark:hover:bg-amber-900/20 transition flex items-center justify-center gap-1">
                                        <Edit size={14} /> Edit
                                    </button>
                                </div>
                                {isUnlocked ? (
                                    <div className="w-full py-2.5 rounded-lg bg-green-50 dark:bg-green-900/20 text-green-600 dark:text-green-400 font-bold text-sm text-center border border-green-200 dark:border-green-800 flex items-center justify-center gap-2">
                                        <CheckCircle2 size={16} /> Premium Unlocked
                                    </div>
                                ) : (
                                    <button onClick={() => onTriggerUpgrade(template.id, `Upgrade ${template.name}`)} className={`w-full py-3 mt-1 rounded-lg font-bold text-sm shadow-md flex items-center justify-center gap-2 transition transform active:scale-95 ${hasTemplateIntro(template.id) ? 'premium-cta' : 'bg-[#B8860B] hover:bg-[#DAA520] text-white'}`}>
                                        {hasTemplateIntro(template.id) ? <Crown size={18} /> : <ShoppingCart size={18} />} Upgrade to {hasTemplateIntro(template.id) ? 'PREMIUM' : 'PRO'} ({formatTemplatePrice(template.id)})
                                    </button>
                                )}
                            </div>
                        </div>
                    );
                })}
        </div>
    </div>
</section>

                <section id="section-features" className="min-h-screen flex flex-col justify-center py-20 relative border-b border-gray-100 dark:border-gray-800 bg-[#FFF0F5] dark:bg-[#0a0a0a]">
                    <div className="max-w-7xl mx-auto px-4 w-full">
                        <span className="block text-center text-[#E91E63] text-xs md:text-sm font-bold tracking-[0.3em] mb-3 uppercase font-suwannaphum">Everything You Need</span>
                        <h2 className="text-3xl md:text-4xl font-bold text-[#D4AF37] text-center mb-12 font-moul tracking-wide drop-shadow-lg">មុខងារពិសេសៗ</h2>
                        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 px-4">
                            {featuresList.map((item, idx) => (
                                <div key={idx} className="group bg-white dark:bg-gray-800 rounded-2xl p-6 shadow-md hover:shadow-2xl hover:shadow-pink-200/60 dark:hover:shadow-none border border-pink-100/70 dark:border-gray-700 hover:-translate-y-1.5 transition-all duration-300 flex flex-col h-full justify-start text-left relative overflow-hidden">
                                    <div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-[#F48FB1] via-[#D4AF37] to-[#F48FB1] opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
                                    <div className="w-12 h-12 rounded-xl bg-gradient-to-br from-pink-50 to-rose-100 dark:bg-gray-700 dark:from-transparent dark:to-transparent flex items-center justify-center mb-4 group-hover:scale-110 transition-transform duration-300 shrink-0">
                                        <item.icon className="w-6 h-6 text-[#E91E63] dark:text-[#FFD700]" strokeWidth={1.75} />
                                    </div>
                                    <h3 className="text-[#E53935] font-bold text-lg mb-3 font-suwannaphum leading-relaxed whitespace-pre-line">{item.title}</h3>
                                    <p className="text-gray-600 dark:text-gray-300 text-sm leading-relaxed font-suwannaphum">{item.desc}</p>
                                </div>
                            ))}
                        </div>
                    </div>
                </section>

                <section id="section-gallery" className="min-h-screen flex flex-col justify-center py-20 relative border-b border-gray-100 dark:border-gray-800">
                     <div className="container mx-auto px-6 text-center">
                        <h2 className="text-3xl md:text-4xl font-bold text-[#E53935] text-center mb-12 font-moul leading-relaxed">រូបភាពអនុស្សាវរីយ៍</h2>
                        <div className="grid grid-cols-2 md:grid-cols-3 gap-6 max-w-5xl mx-auto">
                            {HOME_GALLERY_IMAGES.map((src, index) => (
                                <div
                                    key={index}
                                    onClick={() => setGalleryIndex(index)}
                                    onContextMenu={(e) => e.preventDefault()}
                                    className="relative group overflow-hidden rounded-xl card-shadow-dark cursor-pointer select-none"
                                    style={{ WebkitTouchCallout: 'none', WebkitUserSelect: 'none', userSelect: 'none' }}
                                >
                                    <img src={cldOpt(src, 640)} loading="lazy" decoding="async" draggable={false} onContextMenu={(e) => e.preventDefault()} style={{ aspectRatio: 'auto 4 / 7', WebkitTouchCallout: 'none', pointerEvents: 'none' }} className="w-full h-auto object-cover transform group-hover:scale-105 transition duration-700" alt={`Gallery ${index}`}/>
                                    {/* ស្រទាប់ថ្លាគ្របលើរូប — ទប់ស្កាត់ Save/Long-press តែនៅតែចុចបើកបាន */}
                                    <div className="absolute inset-0" onContextMenu={(e) => e.preventDefault()} onDragStart={(e) => e.preventDefault()}></div>
                                </div>
                            ))}
                        </div>
                     </div>
                </section>

                <section id="section-faq" className="min-h-screen flex flex-col justify-center py-20 relative border-b border-gray-100 dark:border-gray-800 bg-[#FFF0F5] dark:bg-[#0a0a0a]">
                    <div className="container mx-auto px-6">
                        <h2 className="text-3xl md:text-4xl font-bold text-[#E53935] text-center mb-12 font-moul drop-shadow-sm leading-relaxed">សំនួរដែលគេសួរញឹកញាប់</h2>
                        <div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-5xl mx-auto">
                            {[
                                { q: "តើខ្ញុំត្រូវចាប់ផ្តើមយ៉ាងដូចម្តេច?", a: "អ្នកគ្រាន់តែចុះឈ្មោះជាមួយយើង បន្ទាប់មកជ្រើសរើសគំរូការដែលអ្នកចូលចិត្ត ហើយចាប់ផ្តើមបំពេញព័ត៌មានកម្មវិធីរបស់អ្នក។" },
                                { q: "តើមានការគាំទ្រ ២៤/៧ ដែរទេ?", a: "បាទ/ចាស យើងមានក្រុមការងារចាំគាំទ្រ ២៤ ម៉ោង ដើម្បីជួយដោះស្រាយបញ្ហារបស់អ្នក។" },
                                { q: "តើមានការទូទាត់តាមរយៈអ្វីខ្លះ?", a: "យើងទទួលការទូទាត់តាមរយៈគ្រប់ធនាគារដែលគាំទ្រ KHQR ។" },
                                { q: "តើមានគំរូរៀបការច្រើនទេ?", a: "យើងមានគំរូរៀបការច្រើនប្រភេទ និងកែសម្រួលបានតាមតម្រូវការរបស់អ្នក។" }
                            ].map((item, idx) => (
                                <div key={idx} className="bg-white dark:bg-[#1E1E1E] p-8 rounded-2xl shadow-lg hover:shadow-xl transition border border-gray-100 dark:border-gray-800 transform hover:-translate-y-1 duration-300">
                                    <h3 className="font-bold text-lg text-slate-800 dark:text-[#FFD700] mb-3 font-suwannaphum flex gap-3 items-start">
                                        <span className="bg-red-50 dark:bg-red-900/30 text-red-600 w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 text-sm border border-red-100 dark:border-red-800 font-bold">Q</span>
                                        {item.q}
                                    </h3>
                                    <p className="text-gray-600 dark:text-gray-300 pl-11 font-suwannaphum text-sm leading-relaxed">{item.a}</p>
                                </div>
                            ))}
                        </div>
                    </div>
                </section>

                <section id="section-reviews" className="min-h-screen flex flex-col justify-center py-20 relative border-b border-gray-100 dark:border-gray-800 bg-[#FFF0F5] dark:bg-[#121212]">
                    <div className="container mx-auto px-6">
                        <div className="text-center mb-12">
                            <h2 className="text-3xl md:text-4xl font-bold text-[#E53935] mb-4 font-moul leading-relaxed">មតិអ្នកប្រើប្រាស់</h2>
                            <div className="flex items-center justify-center gap-4">
                                <div className="h-px w-16 bg-gradient-to-r from-transparent to-gray-300 dark:to-gray-700"></div>
                                <span className="text-gray-500 dark:text-gray-400 font-suwannaphum text-lg">បញ្ជីមតិអ្នកប្រើប្រាស់</span>
                                <div className="h-px w-16 bg-gradient-to-l from-transparent to-gray-300 dark:to-gray-700"></div>
                            </div>
                        </div>
                        <div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-6xl mx-auto">
                            {[
                                { msg: "ខ្ញុំពេញចិត្ត website Theara ដែលបង្កើតឡើងមានភាពងាយស្រួលនិង រហ័ស... 👍", name: "Mao Sokha", role: "ភ្ញៀវកិត្តិយស", img: "https://ui-avatars.com/api/?name=Mao+Sokha&background=FFD700&color=fff" },
                                { msg: "សេវាកម្មល្អ", name: "Chann Pech Rangsey", role: "ភ្ញៀវកិត្តិយស", img: "https://ui-avatars.com/api/?name=Chann+Pech&background=E91E63&color=fff" },
                                { msg: "ពេញចិត្តការ design និងការ support 100%", name: "Hea Bol", role: "ភ្ញៀវកិត្តិយស", img: "https://ui-avatars.com/api/?name=Hea+Bol&background=0088cc&color=fff" }
                            ].map((review, idx) => (
                                <div key={idx} className="bg-white dark:bg-[#1E1E1E] p-8 rounded-2xl card-shadow-dark flex flex-col items-center text-center hover:-translate-y-2 transition duration-300 group">
                                    <div className="relative mb-6">
                                        <div className="absolute inset-0 bg-[#D4AF37] rounded-full blur opacity-20 group-hover:opacity-40 transition"></div>
                                        <img src={review.img} loading="lazy" decoding="async" className="relative w-20 h-20 rounded-full border-4 border-white dark:border-gray-700 shadow-sm" alt={review.name}/>
                                    </div>
                                    <div className="flex text-amber-400 gap-1 mb-6 text-xs">
                                        {[1,2,3,4,5].map(i => <Star key={i} size={14} fill="currentColor" />)}
                                    </div>
                                    <p className="text-gray-600 dark:text-gray-300 italic mb-6 font-suwannaphum flex-grow text-lg leading-relaxed">"{review.msg}"</p>
                                    <div className="mt-auto">
                                        <h4 className="font-bold text-slate-800 dark:text-white font-suwannaphum text-lg">{review.name}</h4>
                                        <span className="text-xs text-gray-400 uppercase tracking-widest font-bold mt-1 block">{review.role}</span>
                                    </div>
                                </div>
                            ))}
                        </div>
                    </div>
                </section>
                
                <footer id="section-contact-footer" className="bg-[#FDEAF4] text-white py-12 border-t border-pink-100">
                    <div className="container mx-auto px-6 text-center">
                        <h3 className="font-moul text-2xl text-[#EC4899] mb-4">Phumtheapka</h3>
                        <p className="text-pink-400 mb-6 font-suwannaphum">Wedding Digital Platform - The Art of Khmer Elegant Invitations</p>
                        <div className="flex justify-center gap-6 mb-8">
                             <a href={APP_CONFIG.SOCIAL.TELEGRAM_CHANNEL} target="_blank" className="w-10 h-10 rounded-full bg-[#0088cc] flex items-center justify-center hover:scale-110 transition"><i className="fa-brands fa-telegram text-xl"></i></a>
                             <a href={APP_CONFIG.SOCIAL.TIKTOK} target="_blank" className="w-10 h-10 rounded-full bg-black border border-gray-700 flex items-center justify-center hover:scale-110 transition"><i className="fa-brands fa-tiktok text-xl"></i></a>
                             <a href={APP_CONFIG.SOCIAL.WEBSITE} target="_blank" className="w-10 h-10 rounded-full bg-blue-600 flex items-center justify-center hover:scale-110 transition"><i className="fa-brands fa-facebook text-xl"></i></a>
                        </div>
                        <div className="w-full max-w-xs mx-auto h-px bg-pink-200/70 mb-6"></div>
                        <div className="flex items-center justify-center gap-3 mb-3">
                            <img src={cldOpt("https://res.cloudinary.com/duwva5xdx/image/upload/v1790164584/flage_gukxmr.webp", 160)} loading="lazy" decoding="async" alt="Cambodia" className="h-10 w-auto" />
                            <span className="text-[#EC4899] text-xl font-bold font-suwannaphum">បង្កើតឡើងដោយកូនខ្មែរ</span>
                        </div>
                        <div className="text-pink-300 text-xs font-suwannaphum">
                            © {new Date().getFullYear()} Phumtheapka. All rights reserved.
                        </div>
                    </div>
                </footer>
            </div>
            
            {showAuthModal && <AuthModal onClose={() => setShowAuthModal(false)} initialTab={authTab} onLoginSuccess={handleLoginSuccess} />}
            {galleryIndex !== null && <GalleryLightbox images={HOME_GALLERY_IMAGES} index={galleryIndex} onClose={() => setGalleryIndex(null)} onChange={setGalleryIndex} />}
            {/* --- កូដថ្មី៖ Preview Modal សម្រាប់ LandingPage (គ្មាន Header, ប៊ូតុង Back & Edit) --- */}
      {previewTemplate && (
        <div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/90 p-0 md:p-4 backdrop-blur-md">
            <div className="bg-white w-full h-full md:max-w-[400px] md:h-[90vh] md:rounded-3xl rounded-none overflow-hidden relative flex flex-col shadow-2xl md:border-4 border-gray-800">
                
                {/* 1. ផ្នែក Preview (ពេញលេញ គ្មាន Header) */}
                <iframe title="Full Preview" srcDoc={generateSamplePreview(previewTemplate.content, previewTemplate.id)} className="w-full h-full bg-white border-0" />
                
                {/* 2. ផ្នែកប៊ូតុងខាងក្រោម (Back ឆ្វេង & Edit ស្តាំ) */}
                <div className="p-4 bg-white border-t border-gray-200 relative z-50 flex justify-between items-center gap-4">
                    
                    {/* ប៊ូតុង Back (ខាងឆ្វេង - ពណ៌ប្រផេះ) */}
                    <button onClick={() => setPreviewTemplate(null)} className="px-6 py-2 bg-gray-200 text-gray-800 rounded-lg hover:bg-gray-300 transition font-bold text-sm flex items-center shadow-sm">
                        <ArrowLeft size={16} className="mr-2"/> Back
                    </button>
                    
                    {/* ប៊ូតុង Edit (ខាងស្តាំ - ពណ៌មាស) */}
                    <button onClick={() => { onSelectTemplate(previewTemplate); setPreviewTemplate(null); }} className="px-6 py-2 bg-[#D4AF37] text-white rounded-lg hover:bg-[#B5942E] transition font-bold text-sm shadow-md flex items-center">
                        Edit <Check size={16} className="ml-2"/>
                    </button>
                    
                </div>
            </div>
        </div>
      )}
            </div>
        );
    };
const GuestListModal = ({ eventId, onClose }) => {
        const [rsvpList, setRsvpList] = useState([]);
        const [sentList, setSentList] = useState([]);
        const [loading, setLoading] = useState(true);

        useEffect(() => {
            if (!db || !eventId) return;

            const fetchData = async () => {
                setLoading(true);
                try {
                    // ១. ទាញទិន្នន័យបញ្ជីឈ្មោះដែលបានផ្ញើ (Sent List)
                    const sentQuery = query(collection(db, "short_invites"), where("eventId", "==", eventId));
                    const sentSnap = await getDocs(sentQuery);
                    const loadedSent = [];
                    sentSnap.forEach(doc => loadedSent.push({ id: doc.id, ...doc.data() }));
                    setSentList(loadedSent);

                    // ២. ទាញទិន្នន័យបញ្ជីអ្នកឆ្លើយតប (RSVP List)
                    const rsvpQuery = query(collection(db, "events", eventId, "rsvp"));
                    const rsvpSnap = await getDocs(rsvpQuery);
                    const loadedRsvp = [];
                    rsvpSnap.forEach(doc => loadedRsvp.push({ id: doc.id, ...doc.data() }));
                    setRsvpList(loadedRsvp);
                } catch (error) {
                    console.error("Error fetching guest data:", error);
                } finally {
                    setLoading(false);
                }
            };

            fetchData();
        }, [db, eventId]);

        const handleDeleteSent = async (id) => {
            if (window.confirm("តើបងពិតជាចង់លុបឈ្មោះនេះចេញពីបញ្ជីផ្ញើមែនទេ?")) {
                try {
                    await deleteDoc(doc(db, "short_invites", id));
                    setSentList(prev => prev.filter(item => item.id !== id)); 
                } catch (e) { alert("Error: " + e.message); }
            }
        };

        const handleDeleteRsvp = async (id) => {
            if (window.confirm("តើបងពិតជាចង់លុបការឆ្លើយតបរបស់ភ្ញៀវម្នាក់នេះមែនទេ?")) {
                try {
                    await deleteDoc(doc(db, "events", eventId, "rsvp", id));
                    setRsvpList(prev => prev.filter(item => item.id !== id)); 
                } catch (e) { alert("Error: " + e.message); }
            }
        };

        return (
            <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 animate-fade-in font-suwannaphum">
                <div className="bg-white dark:bg-[#1E1E1E] w-full max-w-2xl rounded-2xl shadow-2xl flex flex-col max-h-[85vh] overflow-hidden border border-gray-200 dark:border-gray-800">
                    <div className="p-5 border-b border-gray-100 dark:border-gray-800 flex justify-between items-center bg-gray-50 dark:bg-gray-900/50">
                        <h3 className="font-bold text-lg text-gray-800 dark:text-white flex items-center gap-2">
                            <i className="fa-solid fa-users text-[#D4AF37]"></i> របាយការណ៍ភ្ញៀវ (Guest Report)
                        </h3>
                        <button onClick={onClose} className="p-2 hover:bg-gray-200 dark:hover:bg-gray-800 rounded-full transition text-gray-500">
                           <i className="fa-solid fa-xmark"></i>
                        </button>
                    </div>

                    <div className="grid grid-cols-2 gap-4 p-4 bg-amber-50/50 dark:bg-amber-900/10">
                        <div className="bg-white dark:bg-gray-800 p-3 rounded-xl border border-amber-100 dark:border-gray-700 text-center">
                            <div className="text-xs text-gray-500 uppercase font-bold">ធៀបបានផ្ញើ</div>
                            <div className="text-2xl font-bold text-blue-600">{sentList.length}</div>
                        </div>
                        <div className="bg-white dark:bg-gray-800 p-3 rounded-xl border border-amber-100 dark:border-gray-700 text-center">
                            <div className="text-xs text-gray-500 uppercase font-bold">បានឆ្លើយតប</div>
                            <div className="text-2xl font-bold text-green-600">{rsvpList.length}</div>
                        </div>
                    </div>

                    <div className="flex-1 overflow-y-auto p-4">
                        {loading ? (
                            <div className="text-center py-10"><i className="fa-solid fa-spinner fa-spin text-[#D4AF37] text-3xl"></i></div>
                        ) : (
                            <div className="space-y-6">
                                <div>
                                    <h4 className="font-bold text-sm mb-3 text-gray-600 flex items-center gap-2">
                                        <i className="fa-solid fa-paper-plane"></i> បញ្ជីឈ្មោះផ្ញើចេញ ({sentList.length})
                                    </h4>
                                    <div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
                                        {sentList.length === 0 ? (
                                            <p className="text-xs text-gray-400 italic py-2">មិនទាន់មានទិន្នន័យផ្ញើចេញ</p>
                                        ) : (
                                            sentList.map((item, index) => (
                                                <div key={item.id} className="p-3 bg-gray-50 dark:bg-gray-800 rounded-lg text-sm border border-gray-100 dark:border-gray-700 flex justify-between items-center group">
                                                    <span className="font-bold text-slate-700 dark:text-gray-200">
                                                        {index + 1}. {item.guestName}
                                                    </span>
                                                    <button onClick={() => handleDeleteSent(item.id)} className="text-red-400 opacity-0 group-hover:opacity-100 hover:text-red-600 transition">
                                                        <i className="fa-solid fa-trash-can"></i>
                                                    </button>
                                                </div>
                                            ))
                                        )}
                                    </div>
                                </div>

                                <div className="border-t pt-4 dark:border-gray-800">
                                    <h4 className="font-bold text-sm mb-3 text-green-600 flex items-center gap-2">
                                        <i className="fa-solid fa-circle-check"></i> បញ្ជីអ្នកឆ្លើយតប ({rsvpList.length})
                                    </h4>
                                    <table className="w-full text-sm text-left">
                                        <thead className="text-xs text-gray-500 uppercase bg-gray-50 dark:bg-gray-800">
                                            <tr>
                                                <th className="px-4 py-3 text-center w-12">ល.រ</th>
                                                <th className="px-4 py-3">ឈ្មោះ</th>
                                                <th className="px-4 py-3 text-center">ស្ថានភាព</th>
                                                <th className="px-4 py-3 text-right">សកម្មភាព</th>
                                            </tr>
                                        </thead>
                                        <tbody className="divide-y divide-gray-100 dark:divide-gray-800">
                                            {rsvpList.map((guest, index) => (
                                                <tr key={guest.id} className="group hover:bg-gray-50 dark:hover:bg-gray-800/50">
                                                    <td className="px-4 py-3 text-center text-gray-400 font-mono">{index + 1}</td>
                                                    <td className="px-4 py-3 font-bold">
                                                        {guest.name} <span className="text-[10px] text-gray-400 font-normal">({guest.guests}នាក់)</span>
                                                    </td>
                                                    <td className="px-4 py-3 text-center">
                                                        <span className={`px-2 py-1 rounded-full text-[10px] font-bold ${guest.attendance === 'going' ? 'bg-green-100 text-green-600' : 'bg-red-100 text-red-600'}`}>
                                                            {guest.attendance === 'going' ? 'ចូលរួម' : 'មិនបាន'}
                                                        </span>
                                                    </td>
                                                    <td className="px-4 py-3 text-right">
                                                        <button onClick={() => handleDeleteRsvp(guest.id)} className="text-red-400 opacity-0 group-hover:opacity-100 hover:text-red-600 transition">
                                                            <i className="fa-solid fa-trash-can"></i>
                                                        </button>
                                                    </td>
                                                </tr>
                                            ))}
                                        </tbody>
                                    </table>
                                </div>
                            </div>
                        )}
                    </div>
                </div>
            </div>
        );
    };

    const EventsDashboard = ({ onEditEvent, onBack, onCreateNew, currentUser }) => { 
        const [events, setEvents] = useState([]); 
        const [loading, setLoading] = useState(true); 
        const [showGuestList, setShowGuestList] = useState(null);

        useEffect(() => { 
            const fetchEvents = async () => { 
                if (currentUser?.id) { 
                    setLoading(true); 
                    try { 
                        if (db) { 
                            const q = query(collection(db, "events"), where("userId", "==", currentUser.id)); 
                            const querySnapshot = await getDocs(q); 
                            const eventsData = []; 
                            querySnapshot.forEach((doc) => { eventsData.push({ ...doc.data(), id: doc.id }); }); 
                            setEvents(eventsData); 
                        } else { 
                            setEvents([]); 
                        } 
                    } catch (error) { 
                        console.error("Error fetching events:", error); 
                    } finally { 
                        setLoading(false); 
                    } 
                } else { 
                    setEvents([]); 
                    setLoading(false); 
                } 
            }; 
            fetchEvents(); 
        }, [currentUser]); 
        
        const handleDelete = async (id) => { 
            if(window.confirm("Are you sure you want to delete this event?")) { 
                if (!db) { alert("Database unavailable."); return; } 
                try { 
                    await deleteDoc(doc(db, "events", id)); 
                    setEvents(prev => prev.filter(e => e.id !== id)); 
                } catch (error) { 
                    alert("Failed to delete event: " + error.message); 
                } 
            } 
        };

        return (
            <div className="min-h-screen bg-[#FDFBF7] dark:bg-[#121212] font-sans text-slate-700 dark:text-gray-200 pb-20 relative transition-colors duration-300">
                <header className="bg-white/90 dark:bg-[#1E1E1E]/90 backdrop-blur-sm shadow-sm border-b border-amber-100 dark:border-gray-800 sticky top-0 z-30">
                    <div className="max-w-6xl mx-auto px-4 py-4 flex items-center justify-between">
                        <div className="flex items-center gap-4">
                            <button onClick={onBack} className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition text-gray-600 dark:text-gray-300"><ArrowLeft size={20} /></button>
                            <h1 className="font-bold text-gray-800 dark:text-white font-cinzel tracking-wide text-lg flex items-center gap-2"><Calendar className="text-[#D4AF37]" size={20}/> Your Events</h1>
                        </div>
                        <button onClick={onCreateNew} className="px-4 py-2 bg-[#D4AF37] text-white rounded-full font-bold text-sm shadow-md hover:bg-[#AA771C] transition"><Plus size={16} className="inline mr-1"/> New Event</button>
                    </div>
                </header>
                <div className="max-w-6xl mx-auto px-4 py-8">
                    {!currentUser ? (
                        <div className="text-center py-20"><p className="text-lg text-gray-500">Please log in to view your events.</p></div>
                    ) : loading ? (
                        <div className="text-center py-20"><RefreshCw className="animate-spin mx-auto text-[#D4AF37]" size={32}/></div>
                    ) : events.length === 0 ? (
                        <div className="flex flex-col items-center justify-center py-20 text-center">
                            <div className="w-24 h-24 bg-gray-100 dark:bg-gray-800 rounded-full flex items-center justify-center mb-4"><Calendar size={48} className="text-gray-300 dark:text-gray-600" /></div>
                            <h2 className="text-2xl font-bold text-gray-400 dark:text-gray-500 font-suwannaphum mb-2">មិនទាន់មានព្រឹត្តិការណ៍</h2>
                            <p className="text-gray-400 dark:text-gray-600 mb-6">You haven't created any wedding invitations yet.</p>
                            <button onClick={onCreateNew} className="px-6 py-3 bg-[#B8860B] text-white rounded-full font-bold shadow-lg hover:scale-105 transition">បង្កើតថ្មីឥឡូវនេះ</button>
                        </div>
                    ) : (
                        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
                            {events.map(event => (
                                <div key={event.id} className="bg-white dark:bg-[#1E1E1E] rounded-xl shadow-md hover:shadow-xl transition border border-gray-100 dark:border-gray-700 overflow-hidden flex flex-col">
                                    <div className="h-32 bg-gradient-to-r from-amber-100 to-orange-100 dark:from-gray-800 dark:to-gray-900 flex items-center justify-center relative">
                                        {event.formData?.coverImage ? (<img src={cldOpt(event.formData.coverImage, 400)} loading="lazy" decoding="async" alt="Cover" className="w-full h-full object-cover opacity-80"/>) : (<Heart size={32} className="text-amber-300" />)}
                                        <div className="absolute bottom-2 right-2 bg-black/60 text-white text-xs px-2 py-1 rounded">{event.templateName || 'Classic'}</div>
                                    </div>
                                    <div className="p-5 flex-1 flex flex-col">
                                        <h3 className="font-bold text-lg text-gray-800 dark:text-white font-suwannaphum mb-1">{event.formData?.groomName || 'Groom'} & {event.formData?.brideName || 'Bride'}</h3>
                                        <p className="text-sm text-gray-500 dark:text-gray-400 mb-4 flex items-center gap-1"><Calendar size={12}/> {event.formData?.date || 'No Date'}</p>
                                        <div className="flex gap-2 mt-auto">
                                            <button onClick={() => onEditEvent(event)} className="flex-1 py-2 bg-amber-50 dark:bg-amber-900/20 text-amber-600 dark:text-amber-400 rounded-lg font-bold text-sm hover:bg-amber-100 dark:hover:bg-amber-900/40 transition border border-amber-200 dark:border-amber-800"><Edit size={14} className="inline mr-1"/> Edit</button>
                                            <button onClick={() => setShowGuestList(event.id)} className="flex-1 py-2 bg-blue-50 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400 rounded-lg font-bold text-sm hover:bg-blue-100 dark:hover:bg-blue-900/40 transition border border-blue-200 dark:border-blue-800">
                                                <Users size={14} className="inline mr-1"/> Guest List
                                            </button>
                                            <button onClick={() => handleDelete(event.id)} className="px-3 py-2 bg-red-50 dark:bg-red-900/20 text-red-500 rounded-lg hover:bg-red-100 dark:hover:bg-red-900/40 transition border border-red-100 dark:border-red-900"><Trash2 size={16} /></button>
                                        </div>
                                    </div>
                                </div>
                            ))}
                        </div>
                    )}
                </div>
                {showGuestList && <GuestListModal eventId={showGuestList} onClose={() => setShowGuestList(null)} />}
            </div>
        ); 
    };

    const TemplateGallery = ({ templates, onSelect, onBack, currentUser, onTriggerUpgrade }) => {
    const viewCounts = useMemo(() => buildViewCounts(templates), [templates]);
    const [previewTemplate, setPreviewTemplate] = useState(null);
        useNavLayer(!!previewTemplate, () => setPreviewTemplate(null));   // Back = បិទ Preview
    
    // ✅ បន្ថែមជួរនេះ ដើម្បីគ្រប់គ្រងការដូរប្រភេទធៀប (Wedding, Engagement, etc.)
    const [activeCategory, setActiveCategory] = useState('wedding'); 

    return (
        <div className="min-h-screen bg-[#FDFBF7] dark:bg-[#121212] font-sans text-slate-700 dark:text-gray-200 pb-20 relative transition-colors duration-300">
            <header className="bg-white/90 dark:bg-[#1E1E1E]/90 backdrop-blur-sm shadow-sm border-b border-amber-100 dark:border-gray-800 sticky top-0 z-30">
                <div className="max-w-6xl mx-auto px-4 py-4 flex items-center gap-4">
                    <button onClick={onBack} className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition text-gray-600 dark:text-gray-300">
                        <ArrowLeft size={20} />
                    </button>
                    <h1 className="font-bold text-gray-800 dark:text-white font-cinzel tracking-wide text-lg">Select a Style</h1>
                </div>
            </header>

            <div className="max-w-6xl mx-auto px-4 py-8">
                <div className="text-center mb-10">
                    <h2 className="font-dangrek text-2xl md:text-3xl text-[#D4AF37] mb-2">
                        {activeCategory === 'wedding' && 'ជ្រើសរើសម៉ូតធៀបមង្គលការ'}
                        {activeCategory === 'gift-reception' && 'ជ្រើសរើសម៉ូតធៀបកាត់ចំណងដៃ'}
                        {activeCategory === 'engagement' && 'ជ្រើសរើសម៉ូតធៀបភ្ជាប់ពាក្យ'}
                        {activeCategory === 'housewarming' && 'ជ្រើសរើសម៉ូតធៀបឡើងគេហដ្ឋាន'}
                        {activeCategory === 'birthday' && 'ជ្រើសរើសម៉ូតធៀបខួបកំណើត'}
                        {activeCategory === 'baby-ceremony' && 'ជ្រើសរើសម៉ូតធៀបបង្កក់កូន'}
                    </h2>
                    
                    {/* ប៊ូតុងសម្រាប់ដូរ Category */}
                    <div className="flex justify-center gap-2 mt-4 flex-wrap">
                        <button onClick={() => setActiveCategory('wedding')} className={`px-3 py-1 rounded-full text-xs font-bold border transition ${activeCategory === 'wedding' ? 'bg-[#D4AF37] text-white' : 'bg-white text-gray-500'}`}>មង្គលការ</button>
                        <button onClick={() => setActiveCategory('gift-reception')} className={`px-3 py-1 rounded-full text-xs font-bold border transition ${activeCategory === 'gift-reception' ? 'bg-orange-500 text-white' : 'bg-white text-gray-500'}`}>កាត់ចំណងដៃ</button>
                        <button onClick={() => setActiveCategory('engagement')} className={`px-3 py-1 rounded-full text-xs font-bold border transition ${activeCategory === 'engagement' ? 'bg-blue-500 text-white' : 'bg-white text-gray-500'}`}>ភ្ជាប់ពាក្យ</button>
                        <button onClick={() => setActiveCategory('housewarming')} className={`px-3 py-1 rounded-full text-xs font-bold border transition ${activeCategory === 'housewarming' ? 'bg-green-500 text-white' : 'bg-white text-gray-500'}`}>ឡើងផ្ទះ</button>
                        <button onClick={() => setActiveCategory('birthday')} className={`px-3 py-1 rounded-full text-xs font-bold border transition ${activeCategory === 'birthday' ? 'bg-purple-500 text-white' : 'bg-white text-gray-500'}`}>ខួបកំណើត</button>
                    </div>
                </div>

                <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 justify-items-center">
                    {/* បន្ថែម .filter() ដើម្បីបង្ហាញតែធៀបតាមប្រភេទដែលបានរើស */}
                    {templates
                        .filter(t => t.category === activeCategory)
                        .map((template) => {
                            const isUnlocked = currentUser?.purchasedTemplates?.includes(template.id);
                            return (
                                <div key={template.id} className={`group bg-white dark:bg-[#1E1E1E] rounded-2xl card-shadow-dark transition-all duration-300 overflow-hidden flex flex-col h-[580px] w-full max-w-sm ${hasTemplateIntro(template.id) ? 'premium-card' : ''}`}>
                                    <div className="relative flex-1 phone-preview-white overflow-hidden cursor-pointer" onClick={() => setPreviewTemplate(template)}>
                                        <div className="absolute inset-0 pointer-events-none transform scale-[0.5] origin-top-left w-[200%] h-[200%]">
                                            <LazyPreviewFrame content={template.content} templateId={template.id} title={`Preview ${template.name}`} />
                                        </div>
                                        <div className="absolute top-3 left-3 z-10 pointer-events-none">{hasTemplateIntro(template.id) && (<span className="premium-badge"><Crown size={13} strokeWidth={2.5} /> PREMIUM<span className="premium-badge-sub">INTRO</span></span>)}</div>
                                        <div className="absolute top-3 right-3 z-10 pointer-events-none bg-white/90 dark:bg-black/60 backdrop-blur px-2 py-1 rounded-full shadow"><ViewBadge label={viewCounts[String(template.id)]} /></div>
                                    </div>
                                    <div className={`p-5 bg-white dark:bg-[#1E1E1E] border-t border-gray-100 dark:border-gray-700 ${hasTemplateIntro(template.id) ? 'premium-info' : ''}`}>
                                        <h3 className={`font-bold text-lg text-gray-800 dark:text-white text-center font-cinzel ${hasTemplateIntro(template.id) ? 'mb-1' : 'mb-4'}`}>{template.name}</h3>{hasTemplateIntro(template.id) && (<div className="premium-perk">មាន Intro បែបភាពយន្ត និង Animation ពិសេស</div>)}
                                        <div className="flex gap-3 mb-3">
                                            <button onClick={() => setPreviewTemplate(template)} className="flex-1 py-2 rounded-lg border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 font-medium text-xs hover:bg-gray-50 transition flex items-center justify-center gap-1"><Eye size={14} /> Preview</button>
                                            <button onClick={() => onSelect(template)} className="flex-1 py-2 rounded-lg border border-[#D4AF37] text-[#D4AF37] font-medium text-xs hover:bg-amber-50 transition flex items-center justify-center gap-1"><Edit size={14} /> Edit</button>
                                        </div>
                                        {isUnlocked ? (
                                            <div className="w-full py-2.5 rounded-lg bg-green-50 text-green-600 font-bold text-sm text-center border border-green-200 flex items-center justify-center gap-2">
                                                <CheckCircle2 size={16} /> Premium Unlocked
                                            </div>
                                        ) : (
                                            <button onClick={() => onTriggerUpgrade(template.id, `Upgrade ${template.name}`)} className={`w-full py-3 mt-1 rounded-lg font-bold text-sm shadow-md flex items-center justify-center gap-2 transition transform active:scale-95 ${hasTemplateIntro(template.id) ? 'premium-cta' : 'bg-[#B8860B] hover:bg-[#DAA520] text-white'}`}>
                                                {hasTemplateIntro(template.id) ? <Crown size={18} /> : <ShoppingCart size={18} />} Upgrade to {hasTemplateIntro(template.id) ? 'PREMIUM' : 'PRO'} ({formatTemplatePrice(template.id)})
                                            </button>
                                        )}
                                    </div>
                                </div>
                            );
                        })}
                </div>
            </div>
            
            {/* Preview Modal កូដដដែល... */}
            {previewTemplate && (
                <div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/90 p-0 md:p-4 backdrop-blur-md">
                    <div className="bg-white w-full h-full md:max-w-[400px] md:h-[90vh] md:rounded-3xl overflow-hidden relative flex flex-col shadow-2xl">
                        <iframe title="Full Preview" srcDoc={generateSamplePreview(previewTemplate.content, previewTemplate.id)} className="w-full h-full bg-white border-0" />
                        <div className="p-4 bg-white border-t flex justify-between items-center gap-3">
                            <button onClick={() => setPreviewTemplate(null)} className="px-6 py-3 bg-gray-100 text-gray-800 rounded-xl font-bold border border-gray-300">
                                <ArrowLeft size={16}/> Back
                            </button>
                            <button onClick={() => { onSelect(previewTemplate); setPreviewTemplate(null); }} className="flex-1 py-3 bg-[#D4AF37] text-white rounded-xl font-bold shadow-lg hover:bg-[#B5942E] transition flex items-center justify-center gap-2">
                                Edit <Check size={16}/>
                            </button>
                        </div>
                    </div>
                </div>
            )}
        </div>
    );
};


// ---------------------------------------------------------
// ១. ដាក់កូដ useDebounce នៅទីនេះ (ខាងលើ UserPanel)
// ---------------------------------------------------------
const useDebounce = (value, delay) => {
    const [debouncedValue, setDebouncedValue] = useState(value);
    useEffect(() => {
        const handler = setTimeout(() => {
            setDebouncedValue(value);
        }, delay);
        return () => {
            clearTimeout(handler);
        };
    }, [value, delay]);
    return debouncedValue;
};

// ---------------------------------------------------------
// ២. ដាក់កូដ ImageUpload នៅទីនេះ (ខាងលើ UserPanel ដែរ)
// ---------------------------------------------------------
const ImageUpload = ({ label, onUpload, multiple = false, helperText }) => {
    return (
        <div className="w-full mb-4">
            <label className="flex items-center justify-center text-center min-h-[40px] text-sm font-bold text-gray-700 dark:text-gray-300 mb-2 font-cinzel leading-tight">
                {label}
            </label>
            <div className="flex items-center justify-center w-full">
                <label className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed rounded-lg cursor-pointer bg-pink-50/50 dark:bg-gray-800 border-pink-200 dark:border-gray-600 hover:bg-pink-100/70 dark:hover:bg-gray-700 transition-colors">
                    <div className="flex flex-col items-center justify-center pt-5 pb-6">
                        <i className="fa-solid fa-cloud-arrow-up text-2xl text-pink-400 mb-2"></i>
                        <p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
                            <span className="font-semibold">Click to upload</span>
                        </p>
                        <p className="text-xs text-gray-500 dark:text-gray-400">
                            {helperText || "PNG, JPG or GIF"}
                        </p>
                    </div>
                    <input 
                        type="file" 
                        className="hidden" 
                        multiple={multiple} 
                        accept="image/*" 
                        onChange={(e) => onUpload(e.target.files)} 
                    />
                </label>
            </div>
        </div>
    );
};

// --- ផ្នែកទី ៤៖ UserPanel ពេញលេញ (កែចំណងជើង & លាក់ប្រអប់ពេល Birthday) ---
    const UserPanel = ({ template, templateId, templateName, existingEvent, onBack, onSaveSuccess, currentUser, onTriggerUpgrade }) => {
        // --- កំណត់ប្រភេទកម្មវិធី (Wedding, Engagement, Housewarming, Birthday) ---
        const isHousewarming = templateId ? templateId.includes('house') : false;
        const isEngagement = templateId ? templateId.includes('eng') : false;
        const isBirthday = templateId ? templateId.includes('birthday') : false;
		const isBabyCeremony = templateId ? templateId.includes('baby') : false;
		const isGift = templateId ? templateId.includes('gift') : false; // បន្ថែមជួរនេះ	
        
        const [showPreviewModal, setShowPreviewModal] = useState(false);
        const [showShareModal, setShowShareModal] = useState(false);
        const [isSaved, setIsSaved] = useState(false);
        const [isSaving, setIsSaving] = useState(false);
        const [currentEventId, setCurrentEventId] = useState(existingEvent ? existingEvent.id : null);
        
        const DEFAULT_DATA = {
            groomName: isBirthday ? 'ឡៃ សុជាតា' : 'ចាន់ សីហា', 
            groomEn: isBirthday ? 'Lai Socheata' : 'Chan Seyha', 
            
            groomFather: isBirthday ? 'សាយ សំណាង' : 'ស៊ីណា ចាន់', 
            groomMother: isBirthday ? 'ឡៃ ម៉ាលីដា' : 'មឿន ម៉ាឡៃ',
            
            brideName: 'ហ៊ាង សុជាតា', brideEn: 'Heang Socheata', brideFather: 'ហ៊ិន ហ៊ាង', brideMother: 'ភា សោភ័ណ្ឌ',
            guestName: 'ភ្ញៀវកិត្តិយស',
            date: '2026-01-18', time: '17:00', venueName: 'គេហដ្ឋាន', 
            mapLink: 'https://maps.app.goo.gl/gmxb8VaN78xgkFiu5?g_st=atm', 
            qrCodeUrl: APP_CONFIG.DEFAULT_IMAGE,
            
            // 🔥 កូដចាប់យកបទភ្លេង (កុំកែត្រង់នេះ អោយវាចាប់ពី APP_CONFIG វិញ)
            musicUrl: isBirthday ? APP_CONFIG.BIRTHDAY_MUSIC 
                    : isHousewarming ? APP_CONFIG.HOUSEWARMING_MUSIC 
                    : isEngagement ? APP_CONFIG.ENGAGEMENT_MUSIC 
                    : APP_CONFIG.WEDDING_MUSIC,

            coverImage: null,
            bgImage: null,
            schedule: isBirthday ? `- វេលាម៉ោង ០៥:០០ ល្ងាច\nជួបជុំភ្ញៀវកិត្តិយស\n- វេលាម៉ោង ០៦:០០ ល្ងាច\nពិសារអាហារសាមគ្គី\n- វេលាម៉ោង ០៧:០០ យប់\nកម្មវិធីកាត់នំ និងរាំលេងកម្សាន្ត` : `- វេលាម៉ោង ០៦:៣០ ព្រឹក\nជួបជុំភ្ញៀវកិត្តិយសដើម្បីរៀបចំហែចំនូន\n- វេលាម៉ោង ០៧:០០ ព្រឹក\nពិធីហែចំនូនចូលរោងជ័យ\n- វេលាម៉ោង ០៨:០០ ព្រឹក\nពិធីចុះសំបុត្រអាពាហ៍ពិពាហ៍\n- វេលាម៉ោង ០៨:៣០ ព្រឹក\nពិធីកាត់សក់បង្កក់សិរី\n- វេលាម៉ោង ១០:០០ ព្រឹក\nពិធីសំពះផ្ទឹម សែនចងដៃ ព្រះថោងនាងនាគ\n- វេលាម៉ោង ១២:០០ ថ្ងៃត្រង់\nអញ្ជើញភ្ញៀវកិត្តិយសពិសារអាហារថ្ងៃត្រង់\n- វេលាម៉ោង ០៥:០០ ល្ងាច\nទទួលបដិសណ្ឋារកិច្ចភ្ញៀវកិត្តិយស`,
            telegramText: '012 345 678',
            telegramLink: 'https://t.me/heangsopheak007',
            messengerText: '',
            messengerLink: '',
            galleryImages: TEMPLATE_DEFAULT_IMAGES,
            locationDescription: 'ពិធីនេះធ្វើឡើងនៅ...'
        };

        const [formData, setFormData] = useState(() => {
            const assets = (THEME_ASSETS && THEME_ASSETS[templateId]) 
                           ? THEME_ASSETS[templateId] 
                           : (THEME_ASSETS['classic-gold'] || { defaultCover: APP_CONFIG.DEFAULT_IMAGE });
            const base = { ...DEFAULT_DATA, coverImage: assets.defaultCover };
            if (existingEvent && existingEvent.formData) { return { ...base, ...existingEvent.formData }; }
            return base;
        });
		
        const debouncedFormData = useDebounce(formData, 500);
		// បន្ថែមកូដ Auto-Save នៅត្រង់នេះ (ក្រោម debouncedFormData)
    useEffect(() => {
        if (currentUser && currentEventId) {
            const autoSave = async () => {
                try {
                    const eventRef = doc(db, "events", currentEventId);

                    // ឆែកមើលថាតើមានវាល​ណាមួយនៅតែជា base64 ដែរឬទេ មុននឹងចំណាយពេល upload
                    // (បើគ្មាន base64 សល់ទេ ធ្វើ save ធម្មតាលឿនៗ ដូចមុន ដើម្បីកុំឲ្យ
                    // setFormData ខាងក្រោមបង្កើត loop គ្មានចេះចប់រាល់ 500ms)
                    const hasBase64 = [formData.coverImage, formData.bgImage, formData.qrCodeUrl]
                        .some(v => typeof v === 'string' && v.startsWith('data:image'))
                        || (formData.galleryImages || []).some(v => typeof v === 'string' && v.startsWith('data:image'));

                    if (!hasBase64) {
                        await updateDoc(eventRef, {
                            formData: formData,
                            updatedAt: serverTimestamp()
                        });
                        setIsSaved(true);
                        return;
                    }

                    // 🔧 ជួសជុល៖ មុននេះ Auto-Save សរសេរ formData ចូល Firestore ដោយផ្ទាល់
                    // ដោយមិនបាន upload base64 ទៅ Storage ជាមុនទេ — ធ្វើឲ្យ coverImage/bgImage/
                    // qrCodeUrl/galleryImages ស្ទើរតែជានិច្ចនៅតែជា base64 ក្នុង Firestore
                    // (ជាន់ត្រឡប់លើ URL ស្អាតដែល handleSave ធ្លាប់ដាក់ចូល) ធ្វើឲ្យ Cover Photo
                    // preview លើ Messenger/Facebook មិនចាប់ (base64 ធ្វើឲ្យ dynamicInviteMeta
                    // ត្រូវ serve រូបភាពខ្លួនឯង ដែលយឺតជាង URL ផ្ទាល់ច្រើន)។
                    // ដូច្នេះឥឡូវនេះ ត្រូវ upload base64 ណាមួយឲ្យទៅជា URL ជានិច្ច មុននឹង save។
                    const [uploadedGallery, uploadedCover, uploadedBg, uploadedQr] = await Promise.all([
                        Promise.all((formData.galleryImages || []).map(img => uploadImageToStorage(img, 'gallery'))),
                        uploadImageToStorage(formData.coverImage, 'covers'),
                        uploadImageToStorage(formData.bgImage, 'backgrounds'),
                        uploadImageToStorage(formData.qrCodeUrl, 'qrcodes'),
                    ]);

                    const cleanFormData = {
                        ...formData,
                        galleryImages: uploadedGallery,
                        coverImage: uploadedCover,
                        bgImage: uploadedBg,
                        qrCodeUrl: uploadedQr,
                    };

                    await updateDoc(eventRef, {
                        formData: cleanFormData,
                        updatedAt: serverTimestamp()
                    });

                    // ធ្វើឲ្យ state ក្នុង React ដូចគ្នានឹងអ្វីដែល save ចូល Firestore
                    // ដើម្បីកុំឲ្យ Auto-Save លើកក្រោយជាន់ត្រឡប់ជា base64 វិញ
                    setFormData(prev => ({ ...prev, ...cleanFormData }));
                    setIsSaved(true);
                } catch (e) {
                    console.error("Auto-save error:", e);
                }
            };
            autoSave();
        }
}, [debouncedFormData]);

    useEffect(() => { 
        if (existingEvent && existingEvent.formData) { 
            const assets = (THEME_ASSETS && THEME_ASSETS[templateId]) ? THEME_ASSETS[templateId] : (THEME_ASSETS['classic-gold'] || { defaultCover: APP_CONFIG.DEFAULT_IMAGE }); 
            const base = { ...DEFAULT_DATA, coverImage: assets.defaultCover }; 
            setFormData(prev => ({ ...base, ...existingEvent.formData })); 
        } 
    }, [existingEvent]);

    const handleInputChange = (e) => { 
        const { name, value } = e.target; 
        setFormData(prev => ({ ...prev, [name]: value })); 
        setIsSaved(false); 
    };

    // --- មុខងារ Save ថ្មីដែលប្រើ Firebase Storage (កែសម្រួលត្រឹមត្រូវ) ---
    const handleSave = async () => { 
        if (!currentUser || isSaving) return; 
        if (!db) { alert("Database unavailable."); return; } 
        
        setIsSaving(true); 
        try { 
            // ១. ប្ដូររូបភាពពី Base64 ទៅជា URL (រក្សាក្នុង Firebase Storage)
            const uploadedGallery = await Promise.all(
                formData.galleryImages.map(img => uploadImageToStorage(img, 'gallery'))
            );
            const uploadedCover = await uploadImageToStorage(formData.coverImage, 'covers');
            const uploadedBg = await uploadImageToStorage(formData.bgImage, 'backgrounds');
            const uploadedQr = await uploadImageToStorage(formData.qrCodeUrl, 'qrcodes');

            // ២. រៀបចំទិន្នន័យដែលមានតែ URL
            const finalFormData = {
                ...formData,
                galleryImages: uploadedGallery,
                coverImage: uploadedCover,
                bgImage: uploadedBg,
                qrCodeUrl: uploadedQr
            };

            const eventIdToUse = currentEventId || (existingEvent ? existingEvent.id : `evt_${Date.now()}`); 
            
            const newEventData = { 
                id: eventIdToUse, 
                userId: currentUser.id, 
                templateId: templateId, 
                templateName: templateName, 
                formData: finalFormData, 
                updatedAt: serverTimestamp(), 
                createdAt: (existingEvent || currentEventId) ? (existingEvent?.createdAt || new Date().toISOString()) : serverTimestamp() 
            }; 

            // ៣. រក្សាទុកក្នុង Firestore
            await setDoc(doc(db, "events", eventIdToUse), newEventData, { merge: true }); 
            
            // 🔧 ជួសជុល៖ ធ្វើឲ្យ state formData ក្នុង React ដូចគ្នានឹងអ្វីដែល save ចូល Firestore
            // (មុននេះមិនបានធ្វើទេ ធ្វើឲ្យ formData.coverImage នៅតែសល់ជា base64 ក្នុង memory
            // ហើយ Auto-Save លើកក្រោយសរសេរជាន់ត្រឡប់ base64 នោះមកវិញ លុប URL ស្អាតចោល)
            setFormData(finalFormData);
            
            if (!currentEventId) { setCurrentEventId(eventIdToUse); } 
            setIsSaved(true); 
            setTimeout(() => setIsSaved(false), 3000); 
            if (onSaveSuccess) onSaveSuccess(); 
            
            alert("រក្សាទុកបានជោគជ័យ!");
        } catch (error) { 
            console.error("Failed to save event", error); 
            alert("ការរក្សាទុកបរាជ័យ៖ " + error.message); 
        } finally { 
            setIsSaving(false); 
        } 
    };

    const handleQrUpload = async (files) => { if (files && files[0]) { try { const compressed = await compressImage(files[0]); setFormData(prev => ({ ...prev, qrCodeUrl: compressed })); setIsSaved(false); } catch(e) { alert("Image processing failed"); } } };
    const handleCoverUpload = async (files) => { if (files && files[0]) { try { const compressed = await compressImage(files[0]); setFormData(prev => ({ ...prev, coverImage: compressed })); setIsSaved(false); } catch(e) { alert("Image processing failed"); } } };
    const handleBgUpload = async (files) => { if (files && files[0]) { try { const compressed = await compressImage(files[0]); setFormData(prev => ({ ...prev, bgImage: compressed })); setIsSaved(false); } catch(e) { alert("Image processing failed"); } } };
    
    const handleGalleryUpload = async (files) => { 
        const currentCount = formData.galleryImages.length; 
        const remainingSlots = 7 - currentCount; 
        if (remainingSlots <= 0) { alert("You can only upload up to 7 images."); return; } 
        const filesToProcess = Array.from(files).slice(0, remainingSlots); 
        try { 
            const images = await Promise.all(filesToProcess.map(file => compressImage(file))); 
            setFormData(prev => ({ ...prev, galleryImages: [...prev.galleryImages, ...images] })); 
            setIsSaved(false); 
        } catch(e) { alert("Image processing failed"); } 
    };

    const handleReplaceImage = async (index, files) => { 
        if (!files || !files[0]) return; 
        try { 
            const newImage = await compressImage(files[0]); 
            setFormData(prev => { 
                const newGallery = [...prev.galleryImages]; 
                newGallery[index] = newImage; 
                return { ...prev, galleryImages: newGallery }; 
            }); 
            setIsSaved(false); 
        } catch(e) { alert("Image processing failed"); } 
    };

    const handleDeleteGalleryImage = (index) => { 
        setFormData(prev => ({ ...prev, galleryImages: prev.galleryImages.filter((_, i) => i !== index) })); 
        setIsSaved(false); 
    };

    const handleShare = () => { 
        if (!isSaved && !currentEventId) { alert("Please save the event first to share it."); return; } 
        if (!currentUser || !currentUser.purchasedTemplates || !currentUser.purchasedTemplates.includes(templateId)) { 
            onTriggerUpgrade(templateId, "Unlock Sharing for " + formatTemplatePrice(templateId)); 
            return; 
        } 
        setShowShareModal(true); 
    };

        return (
        <div className="min-h-screen bg-[#FDF2F8] dark:bg-[#121212] font-sans text-slate-700 dark:text-gray-200 pb-20 relative overflow-x-hidden transition-colors duration-300">
            <header className="bg-white/90 dark:bg-[#1E1E1E]/90 backdrop-blur-sm shadow-sm border-b border-pink-100 dark:border-gray-800 sticky top-0 z-30">
                <div className="max-w-6xl mx-auto px-4 py-3 flex justify-between items-center">
                    <div className="flex items-center gap-3">
                        <button onClick={onBack} className="p-1.5 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition text-gray-500 dark:text-gray-400 sm:hidden"><ArrowLeft size={20} /></button>
                        <button onClick={onBack} className="hidden sm:flex items-center gap-2 text-gray-500 dark:text-gray-400 hover:text-[#EC4899] transition text-sm font-medium mr-2"><ArrowLeft size={16} /> Back</button>
                        <div className="flex items-center gap-2">
                            <div className="w-9 h-9 bg-gradient-to-br from-[#EC4899] to-[#DB2777] rounded-full flex items-center justify-center text-white font-serif font-bold shadow-md border-2 border-[#FBCFE8]">W</div>
                            <h1 className="font-bold text-gray-800 dark:text-white font-cinzel tracking-wide text-lg hidden sm:block">Wedding Digital</h1>
                        </div>
                    </div>
                    <div className="flex gap-2 items-center">
                        <button onClick={handleSave} disabled={isSaving} className={`flex items-center gap-2 px-4 py-2 rounded-full text-sm font-bold transition shadow-md ${isSaved ? 'bg-green-500 text-white' : 'bg-[#EC4899] text-white hover:bg-[#DB2777]'}`}>
                            {isSaving ? <RefreshCw className="animate-spin" size={16}/> : isSaved ? <Check size={16} /> : <Save size={16} />}
                            <span className="hidden sm:inline">{isSaving ? 'Saving...' : isSaved ? 'Saved!' : 'Save'}</span>
                        </button>
                        <button onClick={handleShare} className="flex items-center gap-2 px-3 sm:px-4 py-2 text-gray-600 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition text-sm font-bold"><Share2 size={16} /><span className="hidden sm:inline">Share</span></button>
                        <button onClick={() => setShowPreviewModal(true)} className="flex items-center gap-2 px-4 py-2 text-[#EC4899] bg-white dark:bg-[#1E1E1E] border border-[#EC4899] rounded-full hover:bg-[#FDF2F8] dark:hover:bg-gray-900 transition text-sm font-bold font-dangrek"><Eye size={16} /> មើលគំរូ</button>
                    </div>
                </div>
            </header>

            <div className="max-w-3xl mx-auto px-4 py-8 relative z-10">
                <div className="text-center mb-12">

				<h2 className="font-dangrek text-2xl md:text-4xl text-[#EC4899] mb-4 drop-shadow-sm leading-relaxed">
				{isBirthday ? 'ពិធីខួបកំណើត' : 
				isBabyCeremony ? 'សិរីសួស្ដីពិធីបង្កក់កូន' : 
				isGift ? 'សិរីសួស្ដីពិធីកាត់ចំណងដៃ' : // <--- បន្ថែមបន្ទាត់នេះ
				isHousewarming ? 'ពិធីឡើងគេហដ្ឋាន' : 
				isEngagement ? 'ពិធីភ្ជាប់ពាក្យ' : 'សិរីមង្គលអាពាហ៍ពិពាហ៍'}
				</h2>
                    <p className="text-gray-500 dark:text-gray-400 font-suwannaphum">
                        {isBirthday ? 'Happy Birthday' : (isHousewarming ? 'Housewarming Ceremony' : (isEngagement ? 'Engagement Ceremony' : 'Fill in your information to generate your invite'))}
                    </p>
                </div>

                <div className="bg-white dark:bg-[#1E1E1E] rounded-xl shadow-xl border border-[#FBCFE8] dark:border-gray-800 p-6 sm:p-10 relative overflow-hidden">
                    <div className="flex flex-col gap-12 items-center">

                        {/* --- ១. ផ្នែកម្ចាស់កម្មវិធី (Groom / Host / Baby) --- */}
                        <div className="w-full max-w-lg space-y-6 text-center">

                            {/* ១.១ ក្រុមទី ១ — ឪពុកម្ដាយកូនកំលោះ */}
                            <div className="flex items-center gap-2 text-[#DB2777] mb-2 justify-center">
                                <div className="h-px bg-[#DB2777] w-8"></div>
                                <h3 className="font-dangrek text-lg">
                                    {(isBirthday || isBabyCeremony) ? 'ឈ្មោះមាតាបិតា' : (isHousewarming ? 'លោក' : 'ឪពុកម្ដាយកូនកំលោះ')}
                                </h3>
                                <div className="h-px bg-[#DB2777] w-8"></div>
                            </div>

                            {!isHousewarming && (
                                <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-2">
                                    <input name="groomFather" value={formData.groomFather} onChange={handleInputChange} placeholder={(isBirthday || isBabyCeremony) ? "ឈ្មោះលោកឪពុក" : "ឈ្មោះឪពុក (Father Name)"} className="w-full px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 focus:border-[#EC4899] outline-none text-center"/>
                                    <input name="groomMother" value={formData.groomMother} onChange={handleInputChange} placeholder={(isBirthday || isBabyCeremony) ? "ឈ្មោះអ្នកម្ដាយ" : "ឈ្មោះម្តាយ (Mother Name)"} className="w-full px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 focus:border-[#EC4899] outline-none text-center"/>
                                </div>
                            )}

                            {/* --- បន្ទាត់ខណ្ឌចែក --- */}
                            <div className="border-t border-dashed border-gray-200 dark:border-gray-700 pt-6 mt-2"></div>

                            {/* ១.២ ក្រុមទី ២ — កូនកំលោះ (Groom) */}
                            <div className="flex items-center gap-2 text-[#DB2777] mb-2 justify-center">
                                <div className="h-px bg-[#DB2777] w-8"></div>
                                <h3 className="font-dangrek text-lg">
                                    {isBabyCeremony ? 'បុត្រា/បុត្រី (Baby Name)' : isBirthday ? 'ម្ចាស់ខួប (Birthday Owner)' : 'កូនកំលោះ (Groom)'}
                                </h3>
                                <div className="h-px bg-[#DB2777] w-8"></div>
                            </div>

                            <input name="groomName" value={formData.groomName} onChange={handleInputChange} placeholder={isBabyCeremony ? "ឈ្មោះបុត្រា/បុត្រី" : isBirthday ? "ឈ្មោះម្ចាស់ខួប" : (isHousewarming ? "ឈ្មោះពេញ" : "ឈ្មោះកូនកំលោះ")} className="w-full px-4 py-3 rounded-lg border border-[#FBCFE8] dark:border-gray-700 bg-[#FDF2F8] dark:bg-gray-900 focus:border-[#EC4899] outline-none transition font-suwannaphum text-lg text-center placeholder:text-center mb-2"/>
                            <input name="groomEn" value={formData.groomEn} onChange={handleInputChange} placeholder="English Name" className="w-full px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 focus:border-[#EC4899] outline-none text-center font-suwannaphum text-pink-600"/>
                        </div>

                        {/* --- ២. ផ្នែកកូនក្រមុំ (លាក់ទាំងស្រុង ប្រសិនបើជា ខួបកំណើត ឬ បង្កក់កូន) --- */}
                        {!(isBirthday || isBabyCeremony) && (
                            <div className="w-full max-w-lg space-y-6 text-center">

                                {/* ២.១ ក្រុមទី ១ — ឪពុកម្ដាយកូនក្រមុំ */}
                                <div className="flex items-center gap-2 text-[#e91e63] mb-2 justify-center">
                                    <div className="h-px bg-[#e91e63] w-8"></div>
                                    <h3 className="font-dangrek text-lg">{isHousewarming ? 'លោកស្រី' : 'ឪពុកម្ដាយកូនក្រមុំ'}</h3>
                                    <div className="h-px bg-[#e91e63] w-8"></div>
                                </div>

                                {!isHousewarming && (
                                    <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-2">
                                        <input name="brideFather" value={formData.brideFather} onChange={handleInputChange} placeholder="ឈ្មោះឪពុក" className="w-full px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 focus:border-[#e91e63] outline-none text-center"/>
                                        <input name="brideMother" value={formData.brideMother} onChange={handleInputChange} placeholder="ឈ្មោះម្តាយ" className="w-full px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 focus:border-[#e91e63] outline-none text-center"/>
                                    </div>
                                )}

                                {/* --- បន្ទាត់ខណ្ឌចែក --- */}
                                <div className="border-t border-dashed border-gray-200 dark:border-gray-700 pt-6 mt-2"></div>

                                {/* ២.២ ក្រុមទី ២ — កូនក្រមុំ (Bride) */}
                                <div className="flex items-center gap-2 text-[#e91e63] mb-2 justify-center">
                                    <div className="h-px bg-[#e91e63] w-8"></div>
                                    <h3 className="font-dangrek text-lg">កូនក្រមុំ (Bride)</h3>
                                    <div className="h-px bg-[#e91e63] w-8"></div>
                                </div>

                                <input name="brideName" value={formData.brideName} onChange={handleInputChange} placeholder={isHousewarming ? "ឈ្មោះពេញ" : "ឈ្មោះកូនក្រមុំ"} className="w-full px-4 py-3 rounded-lg border border-[#FBCFE8] dark:border-gray-700 bg-[#FDF2F8] dark:bg-gray-900 focus:border-[#e91e63] outline-none transition font-suwannaphum text-lg text-center placeholder:text-center mb-2"/>
                                <input name="brideEn" value={formData.brideEn} onChange={handleInputChange} placeholder="English Name" className="w-full px-4 py-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 focus:border-[#e91e63] outline-none text-center font-suwannaphum text-pink-600"/>
                            </div>
                        )}

                    {/* ... (The rest is same as before: Date, Location, Gallery, etc.) ... */}
                    <div className="flex items-center justify-center my-4 text-[#EC4899] text-xl animate-pulse">✦</div>
                    <div className="bg-red-50 dark:bg-red-900/20 p-6 rounded-xl border border-red-100 dark:border-red-900 w-full max-w-lg">
                        <div className="flex items-center justify-center gap-2 mb-4 text-red-800 dark:text-red-400 font-cinzel font-bold uppercase tracking-wider text-sm"><User size={16} className="text-red-600"/> Guest Name (Cover)</div>
                        <input name="guestName" value={formData.guestName} onChange={handleInputChange} placeholder="ភ្ញៀវកិត្តិយស (Guest Name)" className="w-full px-4 py-3 rounded-lg border border-red-200 dark:border-red-800 outline-none focus:border-red-500 text-center bg-white dark:bg-gray-900 text-gray-700 dark:text-gray-200 font-suwannaphum text-lg"/>
                        <p className="text-xs text-red-400 mt-2 text-center">This name appears on the red scroll banner on the opening screen.</p>
                    </div>
                    <div className="bg-slate-50 dark:bg-gray-800 p-6 rounded-xl border border-gray-100 dark:border-gray-700 w-full">
                        <div className="flex items-center justify-center gap-2 mb-4 text-slate-800 dark:text-white font-cinzel font-bold uppercase tracking-wider text-sm"><Calendar size={16} className="text-[#EC4899]"/> Date & Time</div>
                        <div className="flex flex-col sm:flex-row gap-4"><div className="relative flex-1"><input type="date" name="date" value={formData.date} onChange={handleInputChange} className="w-full px-4 py-3 rounded-lg border border-pink-200 dark:border-gray-600 outline-none focus:border-[#EC4899] text-center bg-white dark:bg-gray-900 text-gray-700 dark:text-gray-200"/></div><div className="relative flex-1"><input type="time" name="time" value={formData.time} onChange={handleInputChange} className="w-full px-4 py-3 rounded-lg border border-pink-200 dark:border-gray-600 outline-none focus:border-[#EC4899] text-center bg-white dark:bg-gray-900 text-gray-700 dark:text-gray-200"/></div></div>
                    </div>
                    <div className="flex flex-col gap-6 items-center text-center w-full mt-4">
                        <div className="bg-pink-50/40 dark:bg-gray-800 p-6 rounded-lg border border-pink-100 dark:border-gray-700 w-full"><div className="flex items-center gap-2 mb-3 text-gray-700 dark:text-gray-300 font-cinzel font-bold justify-center"><MapPin size={18}/> Location</div><textarea name="locationDescription" rows={3} value={formData.locationDescription || ''} onChange={handleInputChange} className="w-full mb-3 px-3 py-2 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 outline-none focus:border-[#EC4899] text-center text-sm" placeholder="ពិធីនេះធ្វើឡើងនៅ..."/><input name="mapLink" value={formData.mapLink} onChange={handleInputChange} placeholder="Google Maps Link" className="w-full px-3 py-2 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 outline-none focus:border-[#EC4899] text-sm text-blue-600 text-center"/></div>
                        <div className="bg-pink-50/40 dark:bg-gray-800 p-6 rounded-lg border border-pink-100 dark:border-gray-700 w-full"><div className="flex items-center justify-between mb-4 px-2"><div className="flex items-center gap-2 text-gray-700 dark:text-gray-300 font-cinzel font-bold"><LucideImageIcon size={18}/> Gallery ({formData.galleryImages.length}/7)</div>{formData.galleryImages.length < 7 && (<span className="text-xs text-green-600 font-medium bg-green-50 dark:bg-green-900/30 px-2 py-1 rounded-full border border-green-100 dark:border-green-800">Add {7 - formData.galleryImages.length} more</span>)}</div>{formData.galleryImages.length < 7 && (<div className="mb-6"><ImageUpload label={formData.galleryImages.length === 0 ? "Upload Photos" : "Add More Photos"} onUpload={handleGalleryUpload} multiple={true} helperText={`PNG, JPG, GIF up to 5 MB. (Max 7 images)`}/></div>)}{formData.galleryImages.length > 0 && (<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-7 gap-3">{formData.galleryImages.map((img, idx) => (<div key={idx} className="relative group aspect-square rounded-xl overflow-hidden border-2 border-gray-100 dark:border-gray-600 hover:border-[#EC4899] transition-all shadow-sm bg-white dark:bg-gray-900"><img src={img} className="w-full h-full object-cover" alt={`Gallery ${idx + 1}`} /><div className="absolute top-1 left-1 bg-black/50 backdrop-blur-sm text-white text-[10px] w-5 h-5 flex items-center justify-center rounded-full font-bold z-10">{idx + 1}</div><div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center gap-2 backdrop-blur-[1px]"><label className="w-8 h-8 flex items-center justify-center bg-white text-pink-600 rounded-full cursor-pointer hover:bg-pink-50 hover:scale-110 transition-all shadow-lg" title="Replace Image"><Edit size={14} /><input type="file" accept="image/*" className="hidden" onChange={(e) => handleReplaceImage(idx, e.target.files)} /></label><button onClick={() => handleDeleteGalleryImage(idx)} className="w-8 h-8 flex items-center justify-center bg-white text-red-500 rounded-full hover:bg-red-50 hover:scale-110 transition-all shadow-lg" title="Delete Image"><Trash2 size={14} /></button></div></div>))}</div>)}</div>
                        <div className="bg-pink-50/40 dark:bg-gray-800 p-6 rounded-lg border border-pink-100 dark:border-gray-700 w-full"><div className="flex items-center gap-2 mb-3 text-gray-700 dark:text-gray-300 font-cinzel font-bold justify-center"><PenTool size={18}/> Program Schedule</div><textarea name="schedule" rows={15} value={formData.schedule} onChange={handleInputChange} className="w-full px-3 py-2 rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 outline-none focus:border-[#EC4899] text-sm text-left" placeholder="Time - Description" /></div>
                        <div className="grid grid-cols-1 sm:grid-cols-3 gap-6 w-full items-stretch"><div className="bg-pink-50/40 dark:bg-gray-800 p-6 rounded-lg border border-pink-100 dark:border-gray-700 flex flex-col h-full"><div className="flex items-center gap-2 mb-3 text-gray-700 dark:text-gray-300 font-cinzel font-bold justify-center min-h-[52px] text-center leading-tight"><LucideImageIcon size={18} className="shrink-0"/> QR Code URL</div><ImageUpload label="Upload QR Code" onUpload={handleQrUpload} helperText="PNG, JPG, GIF up to 5 MB"/>{formData.qrCodeUrl ? (<div className="mt-4 relative group w-full h-48 bg-gray-100 dark:bg-gray-900 rounded-lg overflow-hidden border border-gray-200 dark:border-gray-600"><img src={formData.qrCodeUrl} className="w-full h-full object-contain p-2" alt="QR Preview" /><button onClick={() => setFormData(p => ({...p, qrCodeUrl: ''}))} className="absolute top-2 right-2 bg-red-500 text-white rounded-full p-2 shadow-md opacity-0 group-hover:opacity-100 transition-opacity hover:scale-110"><Trash2 size={16}/></button></div>) : (<div className="mt-4 relative w-full h-48 rounded-lg overflow-hidden border-2 border-dashed border-pink-200 dark:border-gray-600"><img src={APP_CONFIG.DEFAULT_IMAGE} className="w-full h-full object-cover opacity-40" alt="Sample" /><div className="absolute inset-0 flex flex-col items-center justify-center gap-1 bg-white/30 dark:bg-black/30"><span className="text-[10px] font-bold text-pink-500 bg-white/90 dark:bg-gray-900/90 px-2 py-1 rounded-full shadow-sm">គំរូ (Sample)</span></div></div>)}</div><div className="bg-pink-50/40 dark:bg-gray-800 p-6 rounded-lg border border-pink-100 dark:border-gray-700 flex flex-col h-full"><div className="flex items-center gap-2 mb-3 text-gray-700 dark:text-gray-300 font-cinzel font-bold justify-center min-h-[52px] text-center leading-tight"><LucideImageIcon size={18} className="shrink-0"/> Wedding Cover</div><ImageUpload label="Upload Cover" onUpload={handleCoverUpload} helperText="Portrait (9:16) Recommended"/>{formData.coverImage ? (<div className="mt-4 relative group w-full h-48 bg-gray-100 dark:bg-gray-900 rounded-lg overflow-hidden border border-gray-200 dark:border-gray-600"><img src={formData.coverImage} className="w-full h-full object-cover" alt="Cover Preview" /><button onClick={() => setFormData(p => ({...p, coverImage: APP_CONFIG.DEFAULT_IMAGE}))} className="absolute top-2 right-2 bg-red-500 text-white rounded-full p-2 shadow-md opacity-0 group-hover:opacity-100 transition-opacity hover:scale-110"><Trash2 size={16}/></button></div>) : (<div className="mt-4 relative w-full h-48 rounded-lg overflow-hidden border-2 border-dashed border-pink-200 dark:border-gray-600"><img src={APP_CONFIG.DEFAULT_IMAGE} className="w-full h-full object-cover opacity-40" alt="Sample" /><div className="absolute inset-0 flex flex-col items-center justify-center gap-1 bg-white/30 dark:bg-black/30"><span className="text-[10px] font-bold text-pink-500 bg-white/90 dark:bg-gray-900/90 px-2 py-1 rounded-full shadow-sm">គំរូ (Sample)</span></div></div>)}</div><div className="bg-pink-50/40 dark:bg-gray-800 p-6 rounded-lg border border-pink-100 dark:border-gray-700 flex flex-col h-full"><div className="flex items-center gap-2 mb-3 text-gray-700 dark:text-gray-300 font-cinzel font-bold justify-center min-h-[52px] text-center leading-tight"><LucideImageIcon size={18} className="shrink-0"/> Background Image</div><ImageUpload label="Upload Background" onUpload={handleBgUpload} helperText="Portrait Recommended"/>{formData.bgImage ? (<div className="mt-4 relative group w-full h-48 bg-gray-100 dark:bg-gray-900 rounded-lg overflow-hidden border border-gray-200 dark:border-gray-600"><img src={formData.bgImage} className="w-full h-full object-cover" alt="BG Preview" /><button onClick={() => setFormData(p => ({...p, bgImage: ''}))} className="absolute top-2 right-2 bg-red-500 text-white rounded-full p-2 shadow-md opacity-0 group-hover:opacity-100 transition-opacity hover:scale-110"><Trash2 size={16}/></button></div>) : (<div className="mt-4 relative w-full h-48 rounded-lg overflow-hidden border-2 border-dashed border-pink-200 dark:border-gray-600"><img src={formData.coverImage || (THEME_ASSETS[templateId] && THEME_ASSETS[templateId].defaultCover) || APP_CONFIG.DEFAULT_IMAGE} className="w-full h-full object-cover" alt="Background Preview" /></div>)}</div></div>
                        
						{/* --- ផ្នែកជ្រើសរើសបទភ្លេង (Music Selection) --- */}
                    <div className="bg-pink-50/40 dark:bg-gray-800 p-8 rounded-lg border border-pink-100 dark:border-gray-700 w-full mt-4">
                        <div className="flex items-center gap-2 mb-4 text-gray-700 dark:text-gray-300 font-cinzel font-bold justify-center">
                            <Music size={18} className="text-[#EC4899]"/> ជ្រើសរើសបទភ្លេង (Select Music)
                        </div>
                        
                        <div className="space-y-4 max-w-lg mx-auto">
                            <select 
                                name="musicUrl" 
                                value={formData.musicUrl} 
                                onChange={handleInputChange}
                                className="w-full px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 outline-none focus:border-[#EC4899] text-sm font-suwannaphum"
                            >
                                <option value="https://res.cloudinary.com/dlnze2pct/video/upload/v1765279333/Audio-5_ubmrua.mp3">បទភ្លេងទី 1 - Romentic</option>
                                <option value="https://res.cloudinary.com/duwva5xdx/video/upload/v1773565900/15_ntpccf.mp3">បទភ្លេងទី 2 - គូពីព្រេងព្រឹទ្ធ</option>
                                <option value="https://res.cloudinary.com/dlnze2pct/video/upload/v1763377411/Audio-3_jmsr0n.mp3">បទភ្លេងទី 3 - Beautiful in white</option>
                                <option value="https://res.cloudinary.com/duwva5xdx/video/upload/v1791112417/4_djtytz.mp3">បទភ្លេងទី 4 - ភ្ជាប់និស្ស័យ</option>
                                <option value="https://res.cloudinary.com/duwva5xdx/video/upload/v1773564188/13_ncnblu.mp3">បទភ្លេងទី 5 - ជូនពរអាពាហ៍ពិពាហ៍</option>
                                <option value="https://res.cloudinary.com/duwva5xdx/video/upload/v1791112465/6_ezwbjg.mp3">បទភ្លេងទី 6 - ថ្ងៃអូនរង់ចាំ</option>
                                <option value="https://res.cloudinary.com/duwva5xdx/video/upload/v1766308257/Audio-6_ewhxe2.mp3">បទភ្លេងទី 7 - បទខួបកំណើត</option>
                                <option value="https://res.cloudinary.com/duwva5xdx/video/upload/v1775348610/KEO_KIMHUOT_sg8ql3.mp3">បទភ្លេងទី 8 - បទភ្លេងពិរោះ</option>
                                <option value="https://res.cloudinary.com/duwva5xdx/video/upload/v1776597931/8_akaehn.mp3">បទភ្លេងទី 9 - ថ្ងៃដែលរង់ចាំ</option>
                                <option value="https://res.cloudinary.com/duwva5xdx/video/upload/v1791112615/10_tvfifu.mp3">បទភ្លេងទី 10 - ស្នេហ៍ធម្មតា</option>
                                <option value="https://res.cloudinary.com/duwva5xdx/video/upload/v1791112680/11_hfo0vx.mp3">បទភ្លេងទី 11 - ផ្កាស្នេហ៍និរន្តរ៍</option>
                                <option value="https://res.cloudinary.com/duwva5xdx/video/upload/v1791112331/Until_I_Found_You_ooc4vd.mp3">បទភ្លេងទី 12 - Until I Found You</option>
                                <option value="https://res.cloudinary.com/duwva5xdx/video/upload/v1791113307/Perfect_gpnhur.mp3">បទភ្លេងទី 13 - Perfect</option>
                                <option value="https://res.cloudinary.com/duwva5xdx/video/upload/v1791113694/A_Thousand_Years_usone5.mp3">បទភ្លេងទី 14 - A Thousand Years</option>
                                <option value="https://res.cloudinary.com/duwva5xdx/video/upload/v1791114248/Love_Me_Like_You_Do_os1xvs.mp3">បទភ្លេងទី 15 - Love Me Like You Do</option>
                            </select>

                            {/* Music Preview Player */}
                            <div className="bg-white dark:bg-gray-900 p-3 rounded-xl border border-dashed border-pink-200 flex flex-col items-center gap-2">
                                <p className="text-[10px] font-bold text-pink-600 uppercase tracking-wider">ស្ដាប់សាកល្បង (Preview Player)</p>
                                <audio 
                                    key={formData.musicUrl} 
                                    className="w-full h-8 accent-[#EC4899]"
                                    controls
                                    preload="none"
                                >
                                    <source src={optimizeMusicUrl(formData.musicUrl)} type="audio/mpeg" />
                                </audio>
                                <p className="text-[9px] text-gray-400 mt-1 italic">* ចុចប៊ូតុង Play ដើម្បីស្ដាប់សាកល្បង</p>
                            </div>
                        </div>
                    </div>

                    {/* --- ផ្នែក Telegram --- */}
                    <div className="bg-pink-50/40 dark:bg-gray-800 p-8 rounded-lg border border-pink-100 dark:border-gray-700 w-full mt-6 shadow-sm">
                        <div className="flex items-center gap-2 mb-6 text-gray-700 dark:text-gray-300 font-cinzel font-bold justify-center text-lg">
                            <Send size={24} className="text-[#0088cc]"/> Contact Telegram
                        </div>
                        
                        <div className="flex flex-col gap-4 max-w-lg mx-auto">
                            <div className="space-y-1">
                                <label className="text-[11px] font-bold text-gray-400 ml-2 uppercase">លេខទូរស័ព្ទបង្ហាញ (Display Text)</label>
                                <input 
                                    name="telegramText" 
                                    value={formData.telegramText} 
                                    onChange={handleInputChange} 
                                    placeholder="Display Text (e.g. 012 345 678)" 
                                    className="w-full px-6 py-4 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 outline-none focus:border-[#EC4899] text-lg text-center shadow-sm placeholder-gray-400 font-suwannaphum"
                                />
                            </div>

                            <div className="space-y-1">
                                <label className="text-[11px] font-bold text-gray-400 ml-2 uppercase">Link ទៅកាន់ Telegram (URL)</label>
                                <input 
                                    name="telegramLink" 
                                    value={formData.telegramLink} 
                                    onChange={handleInputChange} 
                                    placeholder="Telegram Link (https://t.me/username)" 
                                    className="w-full px-6 py-4 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900 outline-none focus:border-[#EC4899] text-lg text-blue-600 text-center shadow-sm placeholder-gray-400 font-suwannaphum"
                                />
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
            {showPreviewModal && (<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/85 p-0 md:p-4 backdrop-blur-sm"><div className="bg-white w-full h-full md:max-w-[400px] md:h-[85vh] md:rounded-3xl rounded-none overflow-hidden relative flex flex-col shadow-2xl md:border-2 border-[#EC4899]"><div className="p-3 border-b flex justify-between items-center bg-[#FDF2F8]"><span className="text-xs font-bold text-[#EC4899] uppercase tracking-wide font-cinzel">Mobile Preview</span><button onClick={() => setShowPreviewModal(false)} className="w-8 h-8 flex items-center justify-center bg-gray-200 rounded-full text-gray-800">×</button></div><iframe title="Preview" srcDoc={generateHTML({
    ...debouncedFormData,
    musicUrl: debouncedFormData.musicUrl || getCeremonyMusic(templateId)
}, template, currentEventId, templateId)} className="w-full h-full bg-white border-0" /></div></div>)}
            {showShareModal && (<ShareModal onClose={() => setShowShareModal(false)} eventId={currentEventId || (existingEvent ? existingEvent.id : '')} defaultGuestName={formData.guestName}/>)}
        </div>
        );
    };

    const AdminCodeGeneratorModal = ({ onClose }) => { const [phone, setPhone] = useState(''); const [code, setCode] = useState(''); const [copied, setCopied] = useState(false); const generate = async () => { 
    // --- UPDATED GENERATION LOGIC START ---
    // Added lowercase 'a-z' to the character set
    const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789#@$%"; 
    let generatedCode = "";
    for (let i = 0; i < 8; i++) { // Generate 8 characters
        generatedCode += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    // Set this 'generatedCode' to your state variable (e.g., setVerificationCode(generatedCode))
    // --- UPDATED GENERATION LOGIC END ---
    
    const newCode = generatedCode; 

    try { if (db) await setDoc(doc(db, "verification_codes", newCode), { code: newCode, status: 'active', customerPhone: phone, createdAt: serverTimestamp(), createdBy: 'admin' }); setCode(newCode); setCopied(false); } catch (e) { alert("Error saving code to database: " + e.message); } }; const copyToClipboard = () => { navigator.clipboard.writeText(code); setCopied(true); setTimeout(() => setCopied(false), 2000); }; return (<div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/80 backdrop-blur-sm p-4 animate-fade-in font-sans"><div className="bg-white dark:bg-[#1E1E1E] w-full max-w-md rounded-xl shadow-2xl overflow-hidden border border-gray-200 dark:border-gray-700"><div className="flex justify-between items-center p-4 border-b border-gray-100 dark:border-gray-800"><h3 className="font-bold text-lg text-gray-800 dark:text-white flex items-center gap-2"><ShieldCheck className="text-[#EC4899]" /> Verification Code</h3><button onClick={onClose} className="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"><X size={20} /></button></div><div className="p-6 space-y-4"><div><label className="block text-sm font-bold text-gray-600 dark:text-gray-400 mb-1">Customer Phone (Reference)</label><input type="text" value={phone} onChange={(e) => setPhone(e.target.value)} placeholder="e.g. 012 345 678" className="w-full border border-gray-300 dark:border-gray-600 rounded-lg p-3 focus:border-[#EC4899] outline-none bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-white"/></div><button onClick={generate} className="w-full bg-[#EC4899] hover:bg-[#B5942E] text-white font-bold py-3 rounded-lg shadow-md transition">Generate Code</button>{code && (<div className="mt-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-4 text-center"><p className="text-sm text-gray-500 dark:text-gray-400 mb-1">Generated Code</p><div className="text-3xl font-mono font-bold text-gray-800 dark:text-white tracking-widest mb-3">{code}</div><button onClick={copyToClipboard} className="text-sm flex items-center justify-center gap-2 mx-auto text-green-600 dark:text-green-400 font-bold hover:underline">{copied ? <Check size={16}/> : <Copy size={16}/>}{copied ? 'Copied!' : 'Copy Code'}</button></div>)}</div></div></div>); };

    const AdminDashboard = ({ templates, setTemplates, onExit, currentUser }) => {
        if (currentUser?.contact !== ADMIN_EMAIL && currentUser?.email !== ADMIN_EMAIL) {
            return (
                <div className="min-h-screen flex items-center justify-center bg-gray-100 dark:bg-[#121212]">
                    <div className="text-center p-8 bg-white dark:bg-[#1E1E1E] rounded-xl shadow-xl">
                         <div className="text-red-500 text-6xl mb-4"><Lock size={64} className="mx-auto"/></div>
                         <h2 className="text-2xl font-bold text-gray-800 dark:text-white mb-2">Access Denied</h2>
                         <p className="text-gray-500 dark:text-gray-400 mb-6">You do not have permission to view this page.</p>
                         <button onClick={onExit} className="px-6 py-2 bg-gray-800 text-white rounded-lg hover:bg-black transition">Go Back</button>
                    </div>
                </div>
            );
        }

        const [activeTab, setActiveTab] = useState('dashboard');
        const [selectedTemplateId, setSelectedTemplateId] = useState(templates[0]?.id || 'default');
        const [editorContent, setEditorContent] = useState('');
        const [templateName, setTemplateName] = useState('');
        const [showCodeModal, setShowCodeModal] = useState(false);
        const chartData = [{ name: 'Mon', revenue: 400 }, { name: 'Tue', revenue: 300 }, { name: 'Wed', revenue: 200 }, { name: 'Thu', revenue: 278 }, { name: 'Fri', revenue: 189 }, { name: 'Sat', revenue: 239 }, { name: 'Sun', revenue: 349 }];
        
        const [users, setUsers] = useState([]);
        const [stats, setStats] = useState({ totalUsers: 0, revenue: 0 }); // បន្ថែមជួរនេះ
        const [loadingUsers, setLoadingUsers] = useState(false);
        const [filterType, setFilterType] = useState('ALL');

        // 🔥 បន្ថែម State សម្រាប់ស្រង់ទិន្នន័យ ចំនួនភ្ញៀវកិត្តិយស + Telegram/Messenger តាម User ម្នាក់ៗ
        const [userEventStats, setUserEventStats] = useState({});
        const [loadingEventStats, setLoadingEventStats] = useState(false);
    // 🔥 Sent: មិនទាញទិន្នន័យទេ រហូតដល់ Admin ចុចប៊ូតុង "Sent" (កុំឲ្យទំព័រ Users យឺត)
    const [showSent, setShowSent] = useState(false);
    const [statsLoaded, setStatsLoaded] = useState(false);
        const [templatesModalUser, setTemplatesModalUser] = useState(null);

        // --- កូដ Function ជំនួយទាំង ៣ ដែលបានបន្ថែម ---

        // ១. Function សម្រាប់បំប្លែងរាល់ទម្រង់ថ្ងៃខែឱ្យទៅជា Date Object
        const parseDate = (dateVal) => {
            if (!dateVal) return null;
            if (dateVal.toDate) return dateVal.toDate(); // បើជា Firebase Timestamp
            if (dateVal.seconds) return new Date(dateVal.seconds * 1000); // បើជា Object ដែលមាន seconds
            const parsed = new Date(dateVal); // បើជា String
            return isNaN(parsed.getTime()) ? null : parsed;
        };

        // ២. Function សម្រាប់បង្ហាញថ្ងៃខែជាទម្រង់ DD/MM/YYYY
        const formatDate = (dateObj) => {
            if (!dateObj) return "N/A"; // បើអត់មានទិន្នន័យ
            const day = String(dateObj.getDate()).padStart(2, '0');
            const month = String(dateObj.getMonth() + 1).padStart(2, '0');
            const year = dateObj.getFullYear();
            return `${day}/${month}/${year}`;
        };

        // ៣. Function សម្រាប់គណនាចំនួនថ្ងៃ (Duration) ច្បាស់លាស់
        const calculateDuration = (startDateObj) => {
            if (!startDateObj) return "N/A";
            const today = new Date();
            today.setHours(0, 0, 0, 0); // គិតត្រឹមម៉ោង 00:00 ថ្ងៃនេះ
            
            const start = new Date(startDateObj);
            start.setHours(0, 0, 0, 0); // គិតត្រឹមម៉ោង 00:00 ថ្ងៃបង្កើត

            const diffTime = today.getTime() - start.getTime();
            const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
            
            // បើថ្ងៃហ្នឹងគឺរាប់ជា 1 ថ្ងៃ, បើអតីតកាលយកចំនួនថ្ងៃ + 1
            return `${diffDays <= 0 ? 1 : diffDays + 1} Day${diffDays >= 1 ? 's' : ''}`; 
        };

        // ----------------------------------------

        useEffect(() => {
            const tmpl = templates.find(t => t.id === selectedTemplateId);
            if (tmpl) { setEditorContent(tmpl.content); setTemplateName(tmpl.name); }
        }, [selectedTemplateId, templates]);

        const fetchUsers = async () => {
    setLoadingUsers(true);
    try {
        if(db) {
            const q = query(collection(db, "users"));
            const snap = await getDocs(q);
            const loadedUsers = [];
            snap.forEach(doc => loadedUsers.push(doc.data()));

            // 🔥 តម្រៀប User ដែល Login/ចុះឈ្មោះថ្មីបំផុត ឲ្យឡើងនៅខាងលើគេ ស្រួលសម្រាប់តាមដាន
            loadedUsers.sort((a, b) => {
                const dateA = parseDate(a.createdAt);
                const dateB = parseDate(b.createdAt);
                const timeA = dateA ? dateA.getTime() : 0;
                const timeB = dateB ? dateB.getTime() : 0;
                return timeB - timeA; // ថ្មីទៅចាស់
            });

            setUsers(loadedUsers);

            // --- គណនាទិន្នន័យសម្រាប់ Dashboard ---
            const total = loadedUsers.length; 
            const proUsers = loadedUsers.filter(u => u.type === 'PRO').length; 
            const totalRevenue = proUsers * 9.99; 

            setStats({ totalUsers: total, revenue: totalRevenue });
            // ---------------------------------
        }
    } catch (e) { console.error("Error fetching users", e); }
    finally { setLoadingUsers(false); }
};

        // 🔥 Function ថ្មី៖ ស្រង់ព័ត៌មាន Event/RSVP/Invite របស់ User ម្នាក់ៗ
        // - ចំនួនភ្ញៀវកិត្តិយសសរុបដែលបានផ្ញើ Invite ចេញ (short_invites -> ចំនួន Link ដែលបានបង្កើត/ចម្លងផ្ញើ)
        // - ចំនួនអ្នកឆ្លើយតប RSVP ត្រឡប់មកវិញ (rsvp subcollection)
        // - តើបានដាក់ Link Telegram / Messenger ដែរឬទេ
        const fetchUserEventStats = async () => {
            if (!db) return;
            setLoadingEventStats(true);
            try {
                const eventsSnap = await getDocs(collection(db, "events"));
                const eventsList = eventsSnap.docs.map(d => ({ id: d.id, ...d.data() }));

                // ១. Map eventId -> userId សម្រាប់ងាយប្រើពេលក្រោយ
                const eventOwnerMap = {};
                eventsList.forEach(ev => { eventOwnerMap[ev.id] = ev.userId; });

                // ២. ស្រង់ RSVP (ចំនួនអ្នកឆ្លើយតបត្រឡប់មកវិញ) សម្រាប់ Event ទាំងអស់
                // 🔥 FIX: ពីមុនកូដនេះ loop query ដាច់ដោយឡែកម្តងមួយៗសម្រាប់ event ម្នាក់ៗ (N+1 queries)
                // ដែលធ្វើឲ្យយឺតខ្លាំង នៅពេលមាន Event ច្រើន។ ឥឡូវប្តូរមកប្រើ collectionGroup
                // ដើម្បីទាញយក "rsvp" subcollection ទាំងអស់ក្នុង Query តែមួយ (លឿនជាងច្រើន)
                const rsvpMap = {};
                // 🔥 លែងទាញ RSVP (collectionGroup) ទៀតហើយ ព្រោះតារាង Users បង្ហាញតែចំនួន Sent → លឿនជាងមុនច្រើន

                // ៣. ស្រង់ short_invites (= ចំនួន Guest ដែលបានផ្ញើ Link ចេញផ្ទាល់ គិតជា "ចំនួនផ្ញើចេញសរុប")
                let invitesList = [];
                try {
                    const invitesSnap = await getDocs(collection(db, "short_invites"));
                    invitesList = invitesSnap.docs.map(d => d.data());
                } catch (e) {
                    console.error("Error fetching short_invites", e);
                }

                // ប្រមូលផ្តុំតាម userId
                const grouped = {};
                const ensureBucket = (uid) => {
                    if (!grouped[uid]) {
                        grouped[uid] = {
                            sentCount: 0,
                            sentTelegram: 0,   // 🔥 ចំនួនផ្ញើចេញតាម Telegram
                            sentMessenger: 0,  // 🔥 ចំនួនផ្ញើចេញតាម Messenger
                            sentOther: 0,      // ចំនួនផ្ញើចេញតាមរបៀបផ្សេង (Copy link ជាដើម)
                            totalResponses: 0,
                            hasTelegram: false,
                            hasMessenger: false,
                        };
                    }
                    return grouped[uid];
                };

                eventsList.forEach(ev => {
                    if (!ev.userId) return;
                    const bucket = ensureBucket(ev.userId);
                    bucket.totalResponses += (rsvpMap[ev.id]?.responseCount || 0);
                    if (ev.formData?.telegramLink) bucket.hasTelegram = true;
                    if (ev.formData?.messengerLink) bucket.hasMessenger = true;
                });

                invitesList.forEach(inv => {
                    const ownerId = eventOwnerMap[inv.eventId];
                    if (!ownerId) return;
                    const bucket = ensureBucket(ownerId);
                    bucket.sentCount += 1;

                    // 🔥 បំបែកតាម Channel ដែលបានផ្ញើចេញ (telegram / messenger / ផ្សេងទៀត)
                    const ch = String(inv.channel || '').toLowerCase();
                    if (ch === 'telegram') bucket.sentTelegram += 1;
                    else if (ch === 'messenger') bucket.sentMessenger += 1;
                    else bucket.sentOther += 1;
                });

                setUserEventStats(grouped);
            setStatsLoaded(true);
            } catch (e) {
                console.error("Error fetching event/RSVP stats", e);
            } finally {
                setLoadingEventStats(false);
            }
        };

        useEffect(() => {
            if(activeTab === 'users') {
                fetchUsers();
        }
    }, [activeTab]);

        const toggleUserStatus = async (user) => {
    if(!db || !user.id) return;
    const newType = user.type === 'PRO' ? 'BASIC' : 'PRO';
    try {
        const userRef = doc(db, "users", user.id);
        const updateData = { 
            type: newType,
            // ប្រសិនបើ Upgrade វានឹងយកថ្ងៃបច្ចុប្បន្ន (ថ្ងៃចុច) ទៅរក្សាទុក
            proDate: newType === 'PRO' ? new Date().toISOString() : null 
        };
        await setDoc(userRef, updateData, { merge: true });
        setUsers(prev => prev.map(u => u.id === user.id ? { ...u, ...updateData } : u));
    } catch(e) {
        alert("Error: " + e.message);
    }
};

        const handleDeleteUser = async (userId) => {
            if (!window.confirm("Are you sure you want to PERMANENTLY delete this user? This cannot be undone.")) return;
            if (!db || !userId) return;
            
            try {
                await deleteDoc(doc(db, "users", userId));
                setUsers(prev => prev.filter(u => u.id !== userId));
            } catch(e) {
                alert("Failed to delete user: " + e.message);
            }
        };

        const handleSaveTemplate = async () => {
            const updatedTemplates = templates.map(t => t.id === selectedTemplateId ? { ...t, content: editorContent, name: templateName } : t);
            setTemplates(updatedTemplates); 
            
            if (db) {
                try {
                    await setDoc(doc(db, "master_templates", selectedTemplateId), {
                        id: selectedTemplateId,
                        name: templateName,
                        content: editorContent,
                        updatedAt: serverTimestamp()
                    });
                    alert('Template saved to Database!');
                } catch (e) {
                    console.error("Template save error", e);
                    alert("Error saving to database: " + e.message);
                }
            } else {
                alert('Template saved locally (No DB connection)!');
            }
        };

        const handleFileUpload = (event) => {
            const file = event.target.files?.[0]; if (!file) return;
            const reader = new FileReader(); 
            reader.onload = (e) => {
            if (typeof e.target?.result === 'string') {
                const newId = `custom-${Date.now()}`;
                setTemplates([...templates, { id: newId, name: file.name, content: e.target.result }]);
                setSelectedTemplateId(newId);
            }
            }; 
            reader.readAsText(file);
        };
		// --- ចាប់ផ្តើម កូដ Backup ---
    const downloadBackup = async () => {
        if (!db) return;
        const confirm = window.confirm("តើបងចង់ Download ទិន្នន័យទាំងអស់ទុកមែនទេ?");
        if (!confirm) return;

        try {
            const backupData = {};
            
            // ១. ទាញយក Users
            const usersSnap = await getDocs(collection(db, "users"));
            backupData.users = usersSnap.docs.map(doc => ({ id: doc.id, ...doc.data() }));

            // ២. ទាញយក Events
            const eventsSnap = await getDocs(collection(db, "events"));
            backupData.events = eventsSnap.docs.map(doc => ({ id: doc.id, ...doc.data() }));

            // ៣. ទាញយក Verification Codes
            const codesSnap = await getDocs(collection(db, "verification_codes"));
            backupData.codes = codesSnap.docs.map(doc => ({ id: doc.id, ...doc.data() }));

            // បង្កើតជា File
            const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(backupData, null, 2));
            const downloadAnchorNode = document.createElement('a');
            downloadAnchorNode.setAttribute("href", dataStr);
            downloadAnchorNode.setAttribute("download", "backup_phumtheapka_" + new Date().toISOString().slice(0,10) + ".json");
            document.body.appendChild(downloadAnchorNode);
            downloadAnchorNode.click();
            downloadAnchorNode.remove();
            
            alert("Backup បានជោគជ័យ! File ត្រូវបាន Download ចូលកុំព្យូទ័រ។");

        } catch (error) {
            console.error("Backup Failed:", error);
            alert("Backup បរាជ័យ៖ " + error.message);
        }
    };
    // --- បញ្ចប់ កូដ Backup ---

        // --- UPDATED: Helper function for Device Icon ---
        const getDeviceIcon = (userAgent) => {
            if (!userAgent) return <span className="text-gray-400">-</span>;
            const isMobile = /iPhone|Android|Mobile/i.test(userAgent);
            return isMobile ? <Smartphone size={18} className="text-blue-500" /> : <Laptop size={18} className="text-gray-500" />;
        };

        return (
            <div className="flex min-h-screen bg-gray-50 dark:bg-[#121212] font-sans text-slate-800 dark:text-gray-200 transition-colors duration-300">
            <button onClick={onExit} className="fixed bottom-6 left-6 z-50 bg-white dark:bg-gray-800 text-slate-800 dark:text-white px-5 py-2 rounded-full shadow-xl border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition font-bold text-sm flex items-center gap-2 md:hidden"><ArrowLeft size={16}/> Exit Admin</button>
            <aside className="w-64 bg-white dark:bg-[#1E1E1E] border-r border-gray-200 dark:border-gray-800 flex flex-col fixed h-full z-10 hidden md:flex">
                <div className="p-6 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between"><h2 className="text-xl font-bold text-[#D4AF37] tracking-wide font-cinzel">WEDDING<span className="text-slate-800 dark:text-white">DIGITAL</span></h2></div>
                <nav className="flex-1 p-4 space-y-2">
                <button onClick={() => setActiveTab('dashboard')} className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm transition ${activeTab === 'dashboard' ? 'bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-400 font-medium' : 'text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800'}`}><LayoutDashboard size={18} /> Dashboard</button>
                <button onClick={() => setActiveTab('users')} className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm transition ${activeTab === 'users' ? 'bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-400 font-medium' : 'text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800'}`}><Users size={18} /> Users</button>
                <button onClick={() => setActiveTab('editor')} className={`w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm transition ${activeTab === 'editor' ? 'bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-400 font-medium' : 'text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800'}`}><Code size={18} /> HTML Editor</button>
                <button onClick={() => setShowCodeModal(true)} className="w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm transition text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800"><ShieldCheck size={18} /> Verification Codes</button>
				<button onClick={downloadBackup} className="w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm transition text-blue-600 hover:bg-blue-50 dark:hover:bg-blue-900/20 font-bold border border-blue-200 dark:border-blue-800 mt-2">
    <Download size={18} /> Download Backup
</button>
                </nav>
                <div className="p-4 border-t border-gray-100 dark:border-gray-800"><button onClick={onExit} className="w-full flex items-center gap-3 px-4 py-3 rounded-lg text-sm text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 transition font-medium"><ArrowLeft size={18} /> Exit to Home</button></div>
            </aside>
            <main className="flex-1 md:ml-64 p-4 md:p-8 mt-12 md:mt-0">
                {/* --- ផ្នែក Dashboard (រចនាឱ្យដូចរូបភាពដែលបងផ្ញើមក) --- */}
{activeTab === 'dashboard' && (
    <div className="space-y-8 animate-fade-in">
        <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
            {/* Total Users Card */}
            <div className="bg-white dark:bg-[#1E1E1E] p-8 rounded-[24px] shadow-sm border border-gray-100 dark:border-gray-800 flex items-center gap-6">
                <div className="w-16 h-16 bg-orange-50 dark:bg-orange-900/20 rounded-full flex items-center justify-center text-orange-500">
                    <Users size={32} />
                </div>
                <div>
                    <p className="text-gray-500 dark:text-gray-400 font-medium mb-1">Total Users</p>
                    <h3 className="text-4xl font-black text-slate-800 dark:text-white">{stats.totalUsers}</h3>
                </div>
            </div>

            {/* Revenue Card */}
            <div className="bg-white dark:bg-[#1E1E1E] p-8 rounded-[24px] shadow-sm border border-gray-100 dark:border-gray-800 flex items-center gap-6">
                <div className="w-16 h-16 bg-emerald-50 dark:bg-emerald-900/20 rounded-full flex items-center justify-center text-emerald-500">
                    <DollarSign size={32} />
                </div>
                <div>
                    <p className="text-gray-500 dark:text-gray-400 font-medium mb-1">Revenue</p>
                    <h3 className="text-4xl font-black text-slate-800 dark:text-white">${stats.revenue.toFixed(2)}</h3>
                </div>
            </div>
        </div>
    </div>
)}

{/* --- ផ្នែក Users (Sticky Stats Cards រចនាដូច Dashboard ដែរ) --- */}
{activeTab === 'users' && (
    <div className="space-y-6 animate-fade-in relative">
        
        {/* Sticky Stats Bar */}
        <div className="sticky top-0 z-30 bg-[#FDFBF7] dark:bg-[#121212] pt-2 pb-6 -mx-4 px-4 border-b border-gray-100 dark:border-gray-800">
            <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
                {/* Total Users */}
                <div className="bg-white dark:bg-[#1E1E1E] p-5 rounded-[20px] shadow-sm border border-gray-50 dark:border-gray-800 flex items-center gap-4">
                    <div className="w-12 h-12 bg-blue-50 dark:bg-blue-900/20 rounded-full flex items-center justify-center text-blue-500">
                        <Users size={24} />
                    </div>
                    <div>
                        <p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest">Total Users</p>
                        <h4 className="text-xl font-black text-slate-800 dark:text-white">{users.length}</h4>
                    </div>
                </div>

                {/* Pro Users */}
                <div className="bg-white dark:bg-[#1E1E1E] p-5 rounded-[20px] shadow-sm border border-gray-50 dark:border-gray-800 flex items-center gap-4">
                    <div className="w-12 h-12 bg-amber-50 dark:bg-amber-900/20 rounded-full flex items-center justify-center text-amber-500">
                        <Crown size={24} />
                    </div>
                    <div>
                        <p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest">Pro Users</p>
                        <h4 className="text-xl font-black text-slate-800 dark:text-white">
                            {users.filter(u => String(u.type).toUpperCase() === 'PRO').length}
                        </h4>
                    </div>
                </div>

                {/* Basic Users */}
                <div className="bg-white dark:bg-[#1E1E1E] p-5 rounded-[20px] shadow-sm border border-gray-50 dark:border-gray-800 flex items-center gap-4">
                    <div className="w-12 h-12 bg-slate-50 dark:bg-slate-800 rounded-full flex items-center justify-center text-slate-400">
                        <User size={24} />
                    </div>
                    <div>
                        <p className="text-[10px] font-bold text-gray-400 uppercase tracking-widest">Basic Users</p>
                        <h4 className="text-xl font-black text-slate-800 dark:text-white">
                            {users.filter(u => !u.type || String(u.type).toUpperCase() === 'BASIC').length}
                        </h4>
                    </div>
                </div>

            </div>
        </div>

        <div className="bg-white dark:bg-[#1E1E1E] rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
            <div className="p-6 border-b border-gray-100 dark:border-gray-700 flex justify-between items-center">
                <h3 className="font-bold text-lg text-gray-800 dark:text-white">User Management <span className="text-[10px] font-normal text-gray-300 ml-2">build 1004-otp</span></h3>
                <button onClick={() => { fetchUsers(); if (showSent) fetchUserEventStats(); }} className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full transition">
                    <RefreshCw size={18}/>
                </button>
            </div>
            
            <div className="flex gap-2 mb-4 px-6 pt-4">
                <button onClick={() => { setFilterType('ALL'); setShowSent(false); }} className={`px-4 py-2 rounded-full text-sm font-bold transition ${filterType === 'ALL' ? 'bg-gray-800 text-white' : 'bg-gray-200 text-gray-600 hover:bg-gray-300'}`}>All Users</button>
                <button onClick={() => { setFilterType('BASIC'); setShowSent(false); }} className={`px-4 py-2 rounded-full text-sm font-bold transition ${filterType === 'BASIC' ? 'bg-gray-800 text-white' : 'bg-gray-200 text-gray-600 hover:bg-gray-300'}`}>Basic</button>
                <button onClick={() => { setFilterType('PRO'); setShowSent(false); }} className={`px-4 py-2 rounded-full text-sm font-bold transition ${filterType === 'PRO' ? 'bg-blue-600 text-white' : 'bg-blue-100 text-blue-600 hover:bg-blue-200'}`}>Pro</button>
                <button
                    onClick={() => { const next = !showSent; setShowSent(next); if (next && !statsLoaded && !loadingEventStats) fetchUserEventStats(); }}
                    title="ចុចដើម្បីមើលចំនួនផ្ញើចេញ (ទាញទិន្នន័យតែពេលចុច)"
                    className={`px-4 py-2 rounded-full text-sm font-bold transition flex items-center gap-1.5 ${showSent ? 'bg-emerald-600 text-white' : 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200'}`}
                >
                    {loadingEventStats ? <RefreshCw size={14} className="animate-spin"/> : <Send size={14}/>}
                    Sent
                    {showSent && statsLoaded && !loadingEventStats && (
                        <span className="bg-white/25 px-1.5 rounded-full text-xs">{Object.values(userEventStats).reduce((a, b) => a + (b?.sentCount || 0), 0)}</span>
                    )}
                </button>
            </div>

            <div className="overflow-x-auto">
                <table className="w-full text-sm text-left">
                    <thead className="text-xs text-gray-500 uppercase bg-gray-50 dark:bg-gray-800 dark:text-gray-400">
                        <tr>
                            <th className="px-6 py-3">User</th>
                            <th className="px-6 py-3">Contact</th>
							<th className="px-6 py-3 text-center">Usage Basic</th>
                            <th className="px-6 py-3 text-center text-blue-500">Usage Pro</th>
                            <th className="px-6 py-3 text-center">Activity Date</th>
                            <th className="px-6 py-3">ធៀបបានទិញ</th>
                            {showSent && <th className="px-6 py-3 text-center">Sent</th>}
                            <th className="px-6 py-3">Device</th>
                            <th className="px-6 py-3">Status</th>
                            <th className="px-6 py-3">Action</th>
                        </tr>
                    </thead>
                    <tbody>
                        {loadingUsers ? (
                            <tr><td colSpan={showSent ? 10 : 9} className="text-center py-8"><RefreshCw className="animate-spin inline text-[#D4AF37]" size={24}/></td></tr>
                        ) : users.filter(u => {
                            const userType = String(u.type || 'BASIC').toUpperCase();
                            return filterType === 'ALL' || userType === filterType;
                        }).map((user) => (
                            <tr key={user.id} className="bg-white dark:bg-[#1E1E1E] border-b dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800/50 align-top">
                                <td className="px-6 py-4 font-medium text-gray-900 dark:text-white flex items-center gap-3">
                                    <img className="w-8 h-8 rounded-full" src={user.avatar} alt="Avatar"/>
                                    {user.name}
                                </td>
                                <td className="px-6 py-4 text-gray-500 dark:text-gray-400">{user.contact || user.email || user.phone || user.phoneNumber || '—'}</td>
                                

                    {/* --- Column USAGE BASIC: ចំនួនថ្ងៃចាប់ពី Login ដំបូង (createdAt) --- */}
<td className="px-6 py-4 text-center">
    <div className="flex flex-col items-center">
        <span className="text-xs font-bold text-slate-700 dark:text-gray-200 bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded-md whitespace-nowrap">
            {calculateDuration(parseDate(user.createdAt))}
        </span>
    </div>
</td>

{/* --- Column USAGE PRO: ចំនួនថ្ងៃចាប់ពីទិញ PRO (proDate) — មានតែ User PRO --- */}
<td className="px-6 py-4 text-center">
    {user.type === 'PRO' ? (
        <div className="flex flex-col items-center">
            <span className="text-xs font-bold text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-900/30 px-2 py-1 rounded-md whitespace-nowrap">
                {calculateDuration(parseDate(user.proDate) || parseDate(user.createdAt))}
            </span>
        </div>
    ) : (
        <span className="text-xs text-gray-300">—</span>
    )}
</td>

{/* --- Column ថ្ងៃខែសកម្មភាព (Activity Date) --- */}
<td className="px-6 py-4 text-center">
    {user.type === 'PRO' ? (
        <div className="flex flex-col items-center">
            <span className="text-[10px] text-blue-500 font-bold uppercase leading-none mb-1">Pro Since</span>
            <span className="text-xs font-mono text-gray-600 dark:text-gray-300">
                {(() => {
                    const dateToUse = parseDate(user.proDate) || parseDate(user.createdAt);
                    return formatDate(dateToUse);
                })()}
            </span>
        </div>
    ) : (
        <div className="flex flex-col items-center">
            <span className="text-[10px] text-gray-400 font-bold uppercase leading-none mb-1">Login Date</span>
            <span className="text-xs font-mono text-gray-500">
                {formatDate(parseDate(user.lastLogin) || parseDate(user.createdAt))}
            </span>
        </div>
    )}
</td>

                    {/* --- Column ធៀបដែល User បានទិញ (ប៊ូតុងសង្ខេប + Popup មើលទាំងអស់) --- */}
                    <td className="px-6 py-4">
                        {(() => {
                            const purchased = user.purchasedTemplates || [];
                            if (purchased.length === 0) {
                                return <span className="text-xs text-gray-400 italic">មិនទាន់ទិញ</span>;
                            }
                            return (
                                <button
                                    onClick={() => setTemplatesModalUser(user)}
                                    className="flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-bold bg-amber-50 text-amber-700 border border-amber-200 hover:bg-amber-100 dark:bg-amber-900/20 dark:text-amber-400 dark:border-amber-800 transition whitespace-nowrap"
                                >
                                    <Crown size={12}/> {purchased.length} ធៀប
                                </button>
                            );
                        })()}
                    </td>

                    {/* --- Column ចំនួនផ្ញើចេញសរុប (Sent) --- */}
                    {showSent && <td className="px-6 py-4 text-center">
                        {loadingEventStats ? (
                            <RefreshCw className="animate-spin inline text-gray-300" size={14}/>
                        ) : (() => {
                            const evStat = userEventStats[user.id];
                            const sent = evStat?.sentCount || 0;
                            return (
                                <span className="text-sm font-black text-emerald-600 dark:text-emerald-400">{sent}</span>
                            );
                        })()}
                    </td>}

                    <td className="px-6 py-4 text-gray-500 dark:text-gray-400">{getDeviceIcon(user.device)}</td>
                    <td className="px-6 py-4">
                        <span className={`px-2 py-1 rounded-full text-xs font-bold ${user.type === 'PRO' ? 'bg-blue-100 text-blue-600' : 'bg-gray-100 text-gray-600'}`}>
                            {user.type || 'BASIC'}
                        </span>
                    </td>
                    <td className="px-6 py-4 flex items-center gap-2">
                        <button onClick={() => toggleUserStatus(user)} className={`px-3 py-1 rounded border text-xs font-bold transition ${user.type === 'PRO' ? 'border-red-200 text-red-500 hover:bg-red-50' : 'border-green-200 text-green-500 hover:bg-green-50'}`}>{user.type === 'PRO' ? 'Downgrade' : 'Make PRO'}</button>
                        <button onClick={() => handleDeleteUser(user.id)} className="p-1.5 text-red-500 hover:bg-red-100 rounded transition"><Trash2 size={16} /></button>
                    </td>
                </tr>
            ))}
        </tbody>
    </table>
</div>
</div>
</div>
)}
                {activeTab === 'editor' && (
                    <div className="h-full flex flex-col gap-4">
                    <div className="bg-white dark:bg-[#1E1E1E] p-4 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 flex flex-col gap-3">
                        <div className="flex justify-between items-center"><h3 className="font-semibold text-gray-800 dark:text-white font-cinzel">Template Library</h3><span className="text-xs text-gray-400 bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">{templates.length} Available</span></div>
                        <div className="flex gap-3 overflow-x-auto pb-2 pt-1 scrollbar-hide">
                            {templates.map((t) => (<button key={t.id} onClick={() => setSelectedTemplateId(t.id)} className={`flex items-center gap-2 px-3 py-2 rounded-lg border text-sm whitespace-nowrap transition-all ${selectedTemplateId === t.id ? 'bg-amber-50 dark:bg-amber-900/20 border-amber-500 text-amber-700 dark:text-amber-400' : 'bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700 text-gray-700 dark:text-gray-300'}`}><FileCode size={14} /><span className="font-medium">{t.name}</span></button>))}
                            <label className="flex items-center gap-2 px-3 py-2 rounded-lg border border-dashed border-gray-300 dark:border-gray-600 text-gray-500 hover:border-amber-400 hover:bg-amber-50 dark:hover:bg-amber-900/10 cursor-pointer"><Plus size={14} /><span className="text-sm font-medium">Add New</span><input type="file" accept=".html,.htm" onChange={handleFileUpload} className="hidden" /></label>
                        </div>
                    </div>
                    <div className="flex flex-col lg:flex-row flex-1 gap-4 min-h-[600px]">
                        <div className="lg:w-1/2 bg-gray-900 rounded-xl overflow-hidden flex flex-col border border-gray-700 shadow-inner h-[500px] lg:h-auto">
                        <div className="bg-gray-800 px-4 py-2 text-xs text-gray-400 font-mono border-b border-gray-700 flex justify-between items-center"><span>Source Code</span><input type="text" value={templateName} onChange={(e) => setTemplateName(e.target.value)} className="bg-gray-700 text-amber-400 border border-gray-600 rounded px-2 py-1 text-xs w-40 text-right"/></div>
                        <textarea className="flex-1 bg-gray-900 text-green-400 font-mono text-sm p-4 focus:outline-none resize-none" value={editorContent} onChange={(e) => setEditorContent(e.target.value)} spellCheck={false} />
                        </div>
                        <div className="lg:w-1/2 bg-white rounded-xl border border-gray-200 overflow-hidden shadow-sm flex flex-col h-[500px] lg:h-auto">
                            <div className="bg-gray-50 px-4 py-2 text-xs text-gray-500 border-b border-gray-200">Live Preview</div>
                            <iframe srcDoc={generateSamplePreview(editorContent)} className="flex-1 w-full h-full bg-white border-0" title="Preview" />
                        </div>
                    </div>
                    <div className="bg-white dark:bg-[#1E1E1E] p-4 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 flex flex-col sm:flex-row justify-between items-center gap-4">
                        <div className="text-sm text-gray-500 dark:text-gray-400">Uploading a file creates a new style.</div>
                        <div className="flex items-center gap-3 w-full sm:w-auto"><label className="flex-1 sm:flex-none flex items-center justify-center gap-2 px-4 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg shadow-sm cursor-pointer"><Upload size={16} /><span>Upload HTML</span><input type="file" accept=".html,.htm" onChange={handleFileUpload} className="hidden" /></label><button onClick={handleSaveTemplate} className="flex-1 sm:flex-none flex items-center justify-center gap-2 px-6 py-2 bg-amber-500 hover:bg-amber-600 text-white rounded-lg shadow-sm"><Save size={16} /> Save to Database</button></div>
                    </div>
                    </div>
                )}
                {showCodeModal && <AdminCodeGeneratorModal onClose={() => setShowCodeModal(false)} />}
                {templatesModalUser && (
                    <div className="fixed inset-0 z-[80] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 animate-fade-in" onClick={() => setTemplatesModalUser(null)}>
                        <div className="bg-white dark:bg-[#1E1E1E] w-full max-w-lg rounded-2xl shadow-2xl overflow-hidden flex flex-col max-h-[80vh]" onClick={(e) => e.stopPropagation()}>
                            <div className="p-5 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between">
                                <div>
                                    <h3 className="font-bold text-lg text-gray-800 dark:text-white flex items-center gap-2"><Crown size={18} className="text-amber-500"/> ធៀបដែលបានទិញ</h3>
                                    <p className="text-xs text-gray-400 mt-0.5">{templatesModalUser.name} • {(templatesModalUser.purchasedTemplates || []).length} ធៀប</p>
                                </div>
                                <button onClick={() => setTemplatesModalUser(null)} className="p-1.5 rounded-full text-gray-400 hover:text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-800 transition"><X size={18}/></button>
                            </div>
                            <div className="p-5 overflow-y-auto flex flex-wrap gap-2">
                                {(templatesModalUser.purchasedTemplates || []).map((tid) => {
                                    const tmpl = templates.find(t => t.id === tid);
                                    return (
                                        <span key={tid} className="px-3 py-1.5 rounded-full text-xs font-bold bg-amber-50 text-amber-700 border border-amber-200 dark:bg-amber-900/20 dark:text-amber-400 dark:border-amber-800">
                                            {tmpl ? tmpl.name : tid}
                                        </span>
                                    );
                                })}
                            </div>
                        </div>
                    </div>
                )}
            </main>
            </div>
        );
    };

    // --- ផ្នែកទី ៥៖ App ពេញលេញ (ផ្ទុក Template ខួបកំណើត) ---
    const App = () => {
      const [viewMode, setViewMode] = useState('LANDING');
      const [currentUser, setCurrentUser] = useState(() => {
          try {
              const cached = localStorage.getItem('active_session');
              return cached ? JSON.parse(cached) : null;
          } catch (e) { return null; }
      });
      const [isLoading, setIsLoading] = useState(true);
      
      const [templates, setTemplates] = useState([
        // 1. Wedding (៦ ម៉ូតផ្សេងៗគ្នា - ៣ ម៉ូតដើម + ៣ ម៉ូតរចនាបថ Cover ថ្មី)
        // ម៉ូតធម្មតា ($9.99)
        { id: 'classic-gold', category: 'wedding', name: 'CLASSIC GOLD', content: CLASSIC_GOLD_WEDDING_TEMPLATE },
        { id: 'royal-gold', category: 'wedding', name: 'ROYAL GOLD', content: ROYAL_GOLD_TEMPLATE },
        { id: 'luxury-gold', category: 'wedding', name: 'LUXURY GOLD', content: LUXURY_GOLD_DOOR_TEMPLATE },
        { id: 'sweet-blossom', category: 'wedding', name: 'SWEET BLOSSOM', content: SWEET_BLOSSOM_TEMPLATE },
        { id: 'night-sky-romance', category: 'wedding', name: 'NIGHT SKY ROMANCE', content: NIGHT_SKY_ROMANCE_TEMPLATE },
        { id: 'green-leaves-minimal', category: 'wedding', name: 'GOLDEN SUNSET', content: GREEN_LEAVES_MINIMAL_TEMPLATE },
        // ⭐ Premium + Intro ($12.99) — ម៉ូត ៣ នេះនៅខាងក្រោមគេ
        { id: 'golden-dawn', category: 'wedding', name: 'GOLDEN DAWN', content: GOLDEN_DAWN_TEMPLATE },
        { id: 'royal-crimson', category: 'wedding', name: 'ROYAL CRIMSON', content: ROYAL_CRIMSON_TEMPLATE },
        { id: 'luxury-diamond', category: 'wedding', name: 'DIAMOND LUXE', content: DIAMOND_LUXE_TEMPLATE },

        // 1.5 Gift Reception (សិរីសួស្ដីពិធីកាត់ចំណងដៃ)
        { id: 'gift-classic', category: 'gift-reception', name: 'GIFT CLASSIC', content: CLASSIC_GOLD_TEMPLATE },
        { id: 'gift-royal', category: 'gift-reception', name: 'GIFT ROYAL', content: CLASSIC_GOLD_TEMPLATE },
        { id: 'gift-luxury', category: 'gift-reception', name: 'GIFT LUXURY', content: CLASSIC_GOLD_TEMPLATE },
                ...PREMIUM_GIFT_TEMPLATES,

        // 2. Engagement
        { id: 'eng-classic', category: 'engagement', name: 'ENGAGEMENT CLASSIC', content: ENGAGEMENT_TEMPLATE }, 
        { id: 'eng-royal', category: 'engagement', name: 'ENGAGEMENT ROYAL', content: ENGAGEMENT_TEMPLATE },
                ...PREMIUM_ENG_TEMPLATES,

        // 3. Housewarming
        { id: 'house-classic', category: 'housewarming', name: 'HOUSEWARMING CLASSIC', content: HOUSEWARMING_TEMPLATE },
        { id: 'house-royal', category: 'housewarming', name: 'HOUSEWARMING ROYAL', content: HOUSEWARMING_TEMPLATE },

        // 4. Birthday Templates
        { id: 'birthday-classic', category: 'birthday', name: 'BIRTHDAY CLASSIC', content: BIRTHDAY_TEMPLATE },
        { id: 'birthday-royal', category: 'birthday', name: 'BIRTHDAY ROYAL', content: BIRTHDAY_TEMPLATE },
		{ id: 'baby-classic', category: 'baby-ceremony', name: 'BABY CLASSIC', content: BABY_CEREMONY_TEMPLATE },
		{ id: 'baby-royal', category: 'baby-ceremony', name: 'BABY ROYAL', content: BABY_CEREMONY_TEMPLATE },
      ]);

      const [showUpgradeModal, setShowUpgradeModal] = useState(false);
      const [isTemplateSwitching, setIsTemplateSwitching] = useState(false);
      const [selectedTemplate, setSelectedTemplate] = useState(null);
      const [editingEvent, setEditingEvent] = useState(null);
      const [inviteData, setInviteData] = useState(null);
      const [upgradeTargetId, setUpgradeTargetId] = useState(null);
      const [upgradeTitle, setUpgradeTitle] = useState("");
      const audioRef = useRef(null);
      const viewStackRef = useRef([]);

      // ប៊ូតុង Back លើទូរស័ព្ទ៖ ថយពីទំព័រមួយទៅទំព័រមួយ (ជំនួសឱ្យចេញពី Website)
      useEffect(() => {
          const stack = viewStackRef.current;
          if (viewMode === 'LANDING' || viewMode === 'INVITE_RENDER') {
              stack.splice(0).forEach(x => Nav.close(x.id));
              return;
          }
          const idx = stack.findIndex(x => x.mode === viewMode);
          if (idx >= 0) { stack.splice(idx + 1).forEach(x => Nav.close(x.id)); return; }
          const prev = stack.length ? stack[stack.length - 1].mode : 'LANDING';
          const entry = { mode: viewMode, id: null };
          entry.id = Nav.open(() => {
              const i = stack.indexOf(entry);
              if (i >= 0) stack.splice(i);
              if (prev === 'TEMPLATE_GALLERY') { setEditingEvent(null); setSelectedTemplate(null); }
              setViewMode(prev);
          });
          stack.push(entry);
      }, [viewMode]);

      // Dynamic Title
      useEffect(() => {
          if (viewMode === 'INVITE_RENDER' && inviteData && inviteData.formData) { 
              document.title = `${inviteData.formData.groomName} - ${inviteData.formData.brideName}`; 
          } else { 
              document.title = "Phumtheapka - វេទិកាអញ្ជើញ"; 
          }
      }, [viewMode, inviteData]);

      // Fetch Templates from Firestore
      useEffect(() => {
        const fetchTemplates = async () => {
            // បងបានបន្ថែម category: 'gift-reception' ចូលទៅក្នុងនេះហើយ
            setTemplates([
                // ម៉ូតធម្មតា ($9.99)
                { id: 'classic-gold', category: 'wedding', name: 'CLASSIC GOLD', content: CLASSIC_GOLD_WEDDING_TEMPLATE },
                { id: 'royal-gold', category: 'wedding', name: 'ROYAL GOLD', content: ROYAL_GOLD_TEMPLATE },
                { id: 'luxury-gold', category: 'wedding', name: 'LUXURY GOLD', content: LUXURY_GOLD_DOOR_TEMPLATE },
                { id: 'sweet-blossom', category: 'wedding', name: 'SWEET BLOSSOM', content: SWEET_BLOSSOM_TEMPLATE },
                { id: 'night-sky-romance', category: 'wedding', name: 'NIGHT SKY ROMANCE', content: NIGHT_SKY_ROMANCE_TEMPLATE },
                { id: 'green-leaves-minimal', category: 'wedding', name: 'GOLDEN SUNSET', content: GREEN_LEAVES_MINIMAL_TEMPLATE },
                // ⭐ Premium + Intro ($12.99) — ម៉ូត ៣ នេះនៅខាងក្រោមគេ
                { id: 'golden-dawn', category: 'wedding', name: 'GOLDEN DAWN', content: GOLDEN_DAWN_TEMPLATE },
                { id: 'royal-crimson', category: 'wedding', name: 'ROYAL CRIMSON', content: ROYAL_CRIMSON_TEMPLATE },
                { id: 'luxury-diamond', category: 'wedding', name: 'DIAMOND LUXE', content: DIAMOND_LUXE_TEMPLATE },
                
                // បន្ថែម ៣ ជួរនេះចូល
                { id: 'gift-classic', category: 'gift-reception', name: 'GIFT CLASSIC', content: CLASSIC_GOLD_TEMPLATE },
                { id: 'gift-royal', category: 'gift-reception', name: 'GIFT ROYAL', content: CLASSIC_GOLD_TEMPLATE },
                { id: 'gift-luxury', category: 'gift-reception', name: 'GIFT LUXURY', content: CLASSIC_GOLD_TEMPLATE },
                ...PREMIUM_GIFT_TEMPLATES,

                { id: 'eng-classic', category: 'engagement', name: 'ENGAGEMENT CLASSIC', content: ENGAGEMENT_TEMPLATE }, 
                { id: 'eng-royal', category: 'engagement', name: 'ENGAGEMENT ROYAL', content: ENGAGEMENT_TEMPLATE },
                ...PREMIUM_ENG_TEMPLATES,
                { id: 'house-classic', category: 'housewarming', name: 'HOUSEWARMING CLASSIC', content: HOUSEWARMING_TEMPLATE },
                { id: 'house-royal', category: 'housewarming', name: 'HOUSEWARMING ROYAL', content: HOUSEWARMING_TEMPLATE },
                { id: 'birthday-classic', category: 'birthday', name: 'BIRTHDAY CLASSIC', content: BIRTHDAY_TEMPLATE },
                { id: 'birthday-royal', category: 'birthday', name: 'BIRTHDAY ROYAL', content: BIRTHDAY_TEMPLATE },
				{ id: 'baby-classic', category: 'baby-ceremony', name: 'BABY CLASSIC', content: BABY_CEREMONY_TEMPLATE },
				{ id: 'baby-royal', category: 'baby-ceremony', name: 'BABY ROYAL', content: BABY_CEREMONY_TEMPLATE },
				]);
        };
        fetchTemplates();
      }, []);

// Auth State Change & URL Params Handling (FIXED FOR MESSENGER)
      useEffect(() => { 
          const params = new URLSearchParams(window.location.search);
          const shortCode = params.get('i'); 
          const viewParam = params.get('view'); 
          const idParam = params.get('id'); 
          const guestParam = params.get('guest'); 

          const isWaitingForInvite = !!(shortCode || (viewParam === 'invite' && idParam));

          // --- ផ្នែកត្រួតពិនិត្យ Invite Link ---
const runInviteCheck = async () => {
    // ប្រសិនបើមិនមែនជា Link ធៀបទេ ឱ្យវាបង្ហាញ Website ធម្មតាភ្លាម
    if (!isWaitingForInvite || !db) {
        setIsLoading(false);
        return;
    }

    try {
        let targetId = idParam;
        let targetGuest = guestParam;

        // ១. ករណីប្រើ Short Link (i=xxxxxx)
        if (shortCode) {
            const snap = await getDoc(doc(db, "short_invites", shortCode));
            if (snap.exists()) {
                const info = snap.data();
                targetId = info.eventId;
                targetGuest = info.guestName;
            } else {
                // បើរកលេខកូដធៀបមិនឃើញ ឱ្យទៅ Landing Page
                setViewMode('LANDING');
                setIsLoading(false);
                return;
            }
        }

        // ២. បន្ទាប់ពីបាន ID ហើយ ទើបទៅទាញយកទិន្នន័យធៀបមកបង្ហាញ
        if (targetId) {
            const docRef = doc(db, "events", targetId);
            const docSnap = await getDoc(docRef);
            
            if (docSnap.exists()) {
                const data = docSnap.data();
                // ការពារកុំឱ្យ crash បើធៀបចំណាស់គ្មាន field formData
                if (!data.formData) data.formData = {};

                // ប្ដូរឈ្មោះភ្ញៀវតាម Link ដែលបានផ្ញើទៅ
                if (targetGuest) { 
                    try {
                        data.formData.guestName = decodeURIComponent(targetGuest);
                    } catch (decodeErr) {
                        console.warn("Invalid guest param", decodeErr);
                        data.formData.guestName = targetGuest;
                    }
                }
                setInviteData({ ...data, id: docSnap.id });
                setViewMode('INVITE_RENDER'); // បង្ហាញធៀប
            } else {
                setViewMode('LANDING'); // បើគ្មានធៀបក្នុង Database ឱ្យទៅ Website វិញ
            }
        } else {
            // shortCode resolve បានប៉ុន្តែគ្មាន eventId ក្នុង document
            setViewMode('LANDING');
        }
    } catch (e) {
        console.error("Error loading invite", e);
        setViewMode('LANDING');
    } finally {
        // បិទអេក្រង់ Loading តែពេលដែលទិន្នន័យទាញបានសម្រេច ឬ បរាជ័យច្បាស់លាស់
        setIsLoading(false);
    }
};

          runInviteCheck();

          // 🚀 បង្ហាញ Website ភ្លាមៗ (ប្រើ currentUser ដែល Cache ស្រាប់ពី localStorage)
          // មិនចាំបាច់រង់ចាំ Firebase Auth + Firestore ដើម្បីលុប Loading Screen ទៀតទេ
          if (!isWaitingForInvite) {
              setIsLoading(false);
          }

          // ២. ផ្នែក Auth State (សម្រាប់ម្ចាស់ធៀប Login) — ផ្ទៀងផ្ទាត់នៅផ្ទៃខាងក្រោយ
          const auth = getAuth(app); 
          const unsubscribe = onAuthStateChanged(auth, async (user) => { 
              if (user) { 
                  // 🔥 អ្នកចូលតាមលេខទូរស័ព្ទ មិនមាន email/displayName ពី Firebase → ប្រើលេខទូរស័ព្ទជា contact និងឈ្មោះដែលបានវាយពេលចុះឈ្មោះ
                  const pendingName = (typeof window !== 'undefined' && window.__pendingSignupName) || '';
                  const liveName = pendingName || user.displayName || "User";
                  try { window.__pendingSignupName = ''; } catch(e) {}
                  const liveContact = user.email || user.phoneNumber || '';
                  let userData = { id: user.uid, name: liveName, contact: liveContact, email: user.email || '', avatar: user.photoURL || ("https://ui-avatars.com/api/?name=" + encodeURIComponent(liveName)), type: 'BASIC', purchasedTemplates: [], device: navigator.userAgent }; 
                  if (user.email === ADMIN_EMAIL) { userData.type = 'PRO'; userData.purchasedTemplates = ['classic-gold', 'royal-gold', 'luxury-gold', 'default']; } 
                  if (db) { 
                      try { 
                          const userDocRef = doc(db, "users", user.uid);
                          const docSnap = await getDoc(userDocRef); 
                          if (docSnap.exists()) { 
                              userData = { ...userData, ...docSnap.data() }; 
                              // 🔥 User ចាស់ៗដែលបានចុះឈ្មោះមុននឹងកែនេះ អាចគ្មាន createdAt រួចមកហើយ
                              // ដូច្នេះបើរកឃើញថាខ្វះ ត្រូវបំពេញបន្ថែមភ្លាមៗ ដើម្បីអោយ Admin លែងឃើញ N/A ទៀត
                              if (!docSnap.data().createdAt) {
                                  userData.createdAt = serverTimestamp();
                                  try { await updateDoc(userDocRef, { createdAt: serverTimestamp() }); } catch(e) {}
                              }
                              // 🔥 User ចាស់ៗដែល Login មុននឹងកែនេះ អាចគ្មាន device រួចមកហើយ
                              // ដូច្នេះរាល់ពេល Login ថ្មី ត្រូវធ្វើបច្ចុប្បន្នភាព device ជានិច្ច ដើម្បីអោយ Admin ឃើញត្រឹមត្រូវ
                              userData.device = navigator.userAgent;
                              // 🔥 រាល់ដងដែល User Login ចូលម្តងទៀត (ក្រោយពី Logout) ត្រូវ update lastLogin
                              // ជាថ្ងៃថ្មីបំផុតជានិច្ច ដើម្បីអោយ Admin ឃើញ Login Date/Duration ត្រឹមត្រូវទាន់សម័យ
                              const nowLogin = serverTimestamp();
                              userData.lastLogin = new Date().toISOString();
                              // 🔥 បំពេញ contact/name ដែលខ្វះ (User ចាស់ដែលចូលតាមលេខទូរស័ព្ទ ត្រូវបានរក្សាទុកទទេ)
                               const storedDoc = docSnap.data();
                               const backfill = {};
                               if (!storedDoc.contact && liveContact) { backfill.contact = liveContact; userData.contact = liveContact; }
                               // 🔒 គណនីមានរួចហើយ (លេខ/Gmail ដដែល) → រក្សាឈ្មោះចាស់ជានិច្ច មិនសរសេរពីលើដោយឈ្មោះថ្មីទេ
                               // បំពេញឈ្មោះថ្មីតែពេលឈ្មោះចាស់ទទេ ឬជា "User" ប៉ុណ្ណោះ
                               if ((!storedDoc.name || storedDoc.name === 'User') && liveName !== 'User') { backfill.name = liveName; userData.name = liveName; }
                               try { await updateDoc(userDocRef, { device: navigator.userAgent, lastLogin: nowLogin, ...backfill }); } catch(e) {}
                          } else { 
                              // ⚠️ គ្មាន Document ដែលមាន ID ត្រូវនឹង uid បច្ចុប្បន្ននេះទេ
                              // មូលហេតុមួយ៖ User ធ្លាប់ Login ពីមុនដោយវិធីមួយ (ឧ. Google) ហើយពេលនេះ
                              // Login ដោយវិធីផ្សេង (ឧ. លេខទូរស័ព្ទ) ដែល Firebase ផ្ដល់ uid ថ្មីខុសគ្នា
                              // → បើមិនពិនិត្យអោយច្បាស់ វានឹងចាត់ទុកថាជា User ថ្មី ហើយ Reset createdAt ទៅជាថ្ងៃនេះ (Duration = 1 Day)
                              // ដូច្នេះត្រូវរកមើលមុនថាតើមាន Account ចាស់ ប្រើ Email ឬលេខទូរស័ព្ទដូចគ្នាដែរឬទេ
                              let existingData = null;
                              try {
                                  if (user.email) {
                                      const byEmail = query(collection(db, "users"), where("email", "==", user.email));
                                      const emailSnap = await getDocs(byEmail);
                                      if (!emailSnap.empty) existingData = emailSnap.docs[0].data();
                                  }
                                  if (!existingData && user.phoneNumber) {
                                      const byPhone = query(collection(db, "users"), where("contact", "==", user.phoneNumber));
                                      const phoneSnap = await getDocs(byPhone);
                                      if (!phoneSnap.empty) existingData = phoneSnap.docs[0].data();
                                  }
                              } catch (e) { console.log("Lookup old account error", e); }

                              if (existingData) {
                                  // 🔥 រកឃើញ Account ចាស់ (ធ្លាប់ Login ពីមុនម្ដងហើយ) → យក createdAt/type/purchasedTemplates ចាស់មកប្រើបន្ត
                                  // កុំអោយ Duration កែច្នៃទៅជា 1 Day វិញ ទោះបី Login តាមវិធីថ្មី ឬក្រោយ Logout ប៉ុន្មានដងក៏ដោយ
                                  userData = { ...userData, ...existingData, id: user.uid, device: navigator.userAgent };
                                   if ((!userData.name || userData.name === 'User') && liveName !== 'User') userData.name = liveName;
                                   if (!userData.contact && liveContact) userData.contact = liveContact;
                                   if (!userData.createdAt) userData.createdAt = serverTimestamp();
                                  userData.lastLogin = serverTimestamp();
                                  await setDoc(userDocRef, userData, { merge: true });
                              } else {
                                  // សំខាន់៖ ប្រសិនបើពិតជាមិនធ្លាប់មាន Document សម្រាប់ User នេះសោះ (ចូលដំបូងពិតប្រាកដ)
                                  // ត្រូវបង្កើតវាភ្លាមៗនៅក្នុង Firestore កុំឲ្យ updateDoc បរាជ័យពេលក្រោយ
                                  // 🔥 ត្រូវកត់ត្រា createdAt និង device ផងដែរ បើគ្មានចំណុចនេះ Admin នឹងឃើញ N/A/ទទេ រហូត
                                  userData.createdAt = serverTimestamp();
                                  userData.lastLogin = serverTimestamp();
                                  await setDoc(userDocRef, userData, { merge: true }); 
                              }
                          }
                      } catch (e) { console.log("DB sync error", e); } 
                  } 
                  setCurrentUser(userData); 
                  try { localStorage.setItem('active_session', JSON.stringify(userData)); } catch(e) {} 
              } else { 
                  setCurrentUser(null); 
                  try { localStorage.removeItem('active_session'); } catch(e) {} 
              } 
          }); 

          return () => unsubscribe(); 
      }, [db]);

      if (viewMode === 'INVITE_RENDER') { 
          if (!inviteData) return <div className="min-h-screen flex items-center justify-center bg-black"><RefreshCw className="animate-spin text-[#D4AF37]" size={48}/></div>; 
          const resolvedTemplateId = inviteData.templateId || 'classic-gold';
          const matchedTemplate = templates.find(t => t.id === resolvedTemplateId);
          const html = generateHTML(inviteData.formData, matchedTemplate ? matchedTemplate.content : CLASSIC_GOLD_TEMPLATE, inviteData.id, resolvedTemplateId); 
          return ( <div style={{width:'100vw', height:'100vh', margin:0, padding:0, overflow:'hidden'}}> <iframe srcDoc={html} style={{width:'100%', height:'100%', border:0}} title="Invitation"></iframe> </div> ); 
      }

      const handleSetViewMode = (mode) => { setViewMode(mode); if (mode === 'TEMPLATE_GALLERY') { setEditingEvent(null); setSelectedTemplate(null); } };
      const handleSelectTemplate = async (template) => {
          // សម្គាល់ថាកំពុងផ្លាស់ប្ដូរភ្លាមៗ ដើម្បីអោយ User ឃើញ loading ជំនួសឲ្យអារម្មណ៍ថាប៊ូតុងគាំង
          setIsTemplateSwitching(true);
          setSelectedTemplate(template);
          // ត្រូវរក event ចាស់ (userId + templateId) ជាមុនសិន បើមាន ត្រូវយកវាមកបើកកែ
          // ជំនួសឲ្យការបង្កើត event ថ្មីរាល់ពេលចុច Edit លើ template ដែលធ្លាប់ប្រើរួចហើយ
          try {
              if (currentUser?.id && db) {
                  try {
                      const q = query(
                          collection(db, "events"),
                          where("userId", "==", currentUser.id),
                          where("templateId", "==", template.id)
                      );
                      const querySnapshot = await getDocs(q);
                      if (!querySnapshot.empty) {
                          const existing = { ...querySnapshot.docs[0].data(), id: querySnapshot.docs[0].id };
                          setEditingEvent(existing);
                          setViewMode('USER_PANEL');
                          return;
                      }
                  } catch (error) {
                      console.error("Error checking existing event:", error);
                  }
              }
              setEditingEvent(null);
              setViewMode('USER_PANEL');
          } finally {
              setIsTemplateSwitching(false);
          }
      };
      const handleEditEvent = (event) => { const tmpl = templates.find(t => t.id === event.templateId) || templates[0]; setSelectedTemplate(tmpl); setEditingEvent(event); setViewMode('USER_PANEL'); };
      const handleSaveSuccess = () => {};
      const handleTriggerUpgrade = (templateId, title = "") => { setUpgradeTargetId(templateId); setUpgradeTitle(title); setShowUpgradeModal(true); }
      const handleUpgrade = async (templateId) => { if (!currentUser) return; const currentPurchases = currentUser.purchasedTemplates || []; const updatedPurchases = [...currentPurchases, templateId]; const updatedUser = { ...currentUser, purchasedTemplates: updatedPurchases, type: 'PRO', proDate: currentUser.proDate || new Date().toISOString() }; setCurrentUser(updatedUser); try { localStorage.setItem('active_session', JSON.stringify(updatedUser)); } catch(e){} };

      if (isLoading) { 
    return ( 
        <div className="min-h-screen flex items-center justify-center bg-[#FDFBF7] dark:bg-black transition-colors duration-500"> 
            <div className="flex flex-col items-center animate-fade-in"> 
                
                {/* ផ្នែក Logo និង រង្វង់វិល */}
                <div className="relative flex items-center justify-center mb-8">
                    {/* ១. រង្វង់វិលខាងក្រៅ (បងដំឡើងទំហំដល់ w-28 h-28) */}
                    <div className="w-28 h-28 border-[3px] border-[#D4AF37]/10 border-t-[#D4AF37] rounded-full animate-spin"></div>
                    
                    {/* ២. Logo ប្អូន (ដំឡើងទំហំឱ្យធំពេញកណ្តាលរង្វង់) */}
                    <div className="absolute w-[100px] h-[100px] rounded-full overflow-hidden shadow-inner bg-white flex items-center justify-center border border-[#D4AF37]/20">
                        <img 
                            src={cldOpt("https://res.cloudinary.com/duwva5xdx/image/upload/v1767102024/logo_swlob4.webp", 300)} decoding="async" 
                            className="w-[85%] h-[85%] object-contain animate-pulse" 
                            alt="Phumtheapka Logo" 
                        />
                    </div>
                </div>
                
                {/* អត្ថបទបង្ហាញ */}
                <div className="font-suwannaphum font-bold text-[#D4AF37] text-2xl tracking-wider animate-bounce">
                    សូមរង់ចាំបន្តិច...
                </div>

                {/* ឆ្នូតពន្លឺតុបតែងខាងក្រោម */}
                <div className="mt-3 w-28 h-0.5 bg-gradient-to-r from-transparent via-[#D4AF37] to-transparent opacity-30"></div>
            </div> 
        </div> 
    ); 
}

      return (
        <React.Fragment>
          <audio ref={audioRef} src={optimizeMusicUrl(APP_CONFIG.MUSIC_URL)} loop />
          {viewMode === 'LANDING' && ( <LandingPage setViewMode={handleSetViewMode} currentUser={currentUser} setCurrentUser={setCurrentUser} templates={templates} onSelectTemplate={handleSelectTemplate} onTriggerUpgrade={handleTriggerUpgrade} /> )}
          {viewMode === 'TEMPLATE_GALLERY' && ( <TemplateGallery templates={templates} onSelect={handleSelectTemplate} onBack={() => setViewMode('LANDING')} currentUser={currentUser} onTriggerUpgrade={handleTriggerUpgrade} /> )}
          {viewMode === 'USER_PANEL' && selectedTemplate && ( <UserPanel key={`${selectedTemplate.id}-${editingEvent ? editingEvent.id : 'new'}`} template={selectedTemplate.content} templateId={selectedTemplate.id} templateName={selectedTemplate.name} existingEvent={editingEvent} onBack={() => setViewMode(editingEvent ? 'EVENTS_DASHBOARD' : 'LANDING')} onSaveSuccess={handleSaveSuccess} currentUser={currentUser} onTriggerUpgrade={(title) => handleTriggerUpgrade(selectedTemplate.id, title)} /> )}
          {viewMode === 'EVENTS_DASHBOARD' && ( <EventsDashboard onEditEvent={handleEditEvent} onBack={() => setViewMode('LANDING')} onCreateNew={() => { document.getElementById('section-templates').scrollIntoView({ behavior: 'smooth' }); setViewMode('LANDING'); }} currentUser={currentUser} /> )}
          {viewMode === 'ADMIN' && ( <AdminDashboard templates={templates} setTemplates={setTemplates} onExit={() => setViewMode('LANDING')} currentUser={currentUser} /> )}
          {showUpgradeModal && <UpgradeModal onClose={() => setShowUpgradeModal(false)} onUpgrade={handleUpgrade} currentUser={currentUser} targetTemplateId={upgradeTargetId} title={upgradeTitle} />}
          {isTemplateSwitching && (
            <div className="fixed inset-0 z-[9999] bg-black/40 backdrop-blur-sm flex items-center justify-center">
              <div className="bg-white dark:bg-gray-900 rounded-2xl px-8 py-6 shadow-xl flex flex-col items-center gap-3">
                <RefreshCw className="animate-spin text-[#D4AF37]" size={36} />
                <span className="text-sm font-medium text-gray-600 dark:text-gray-300">កំពុងបើក Editor...</span>
              </div>
            </div>
          )}
        </React.Fragment>
      );
    };

    const rootElement = document.getElementById('root');
    if (!rootElement) throw new Error("Could not find root element to mount to");
    const root = createRoot(rootElement);
    root.render(<React.StrictMode><App /></React.StrictMode>);
