<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Christmas Gifts & Lights</title>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>

    <style>
        body { margin: 0; overflow: hidden; background-color: #000000; font-family: 'Segoe UI', sans-serif; }
        #canvas-container { width: 100%; height: 100vh; display: block; }
        
        #ui-layer {
            position: absolute; bottom: 30px; width: 100%;
            text-align: center; pointer-events: none; z-index: 100;
        }
        
        .badge {
            display: inline-block; background: rgba(0,0,0,0.7);
            border: 2px solid #FFD700; color: #FFD700;
            padding: 10px 25px; border-radius: 50px;
            font-size: 18px; font-weight: bold; margin-bottom: 10px;
            text-transform: uppercase; letter-spacing: 1px;
            box-shadow: 0 0 20px rgba(255, 215, 0, 0.5);
            text-shadow: 0 0 10px #FFD700;
        }

        .guide-btn {
            pointer-events: auto; cursor: pointer;
            display: inline-flex; align-items: center; justify-content: center;
            background: rgba(255, 215, 0, 0.15);
            color: #FFD700; border: 1.5px solid #FFD700;
            padding: 10px 25px; border-radius: 50px;
            font-size: 14px; font-weight: 600;
            transition: all 0.3s ease;
            margin-bottom: 20px;
            text-transform: uppercase; letter-spacing: 0.5px;
            backdrop-filter: blur(5px);
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
        }
        .guide-btn:hover {
            background: #FFD700; color: #000;
            box-shadow: 0 0 20px rgba(255, 215, 0, 0.6);
            transform: translateY(-2px);
        }

        button#btnStart {
            pointer-events: auto; cursor: pointer;
            background: linear-gradient(to bottom, #D32F2F, #8B0000); 
            color: #FFF; border: 2px solid #FFD700;
            padding: 15px 50px; border-radius: 30px; 
            font-weight: 800; font-size: 16px;
            box-shadow: 0 0 30px rgba(255, 0, 0, 0.6);
            animation: pulse 1.5s infinite;
        }
        @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } }

        #camera-preview { display: none; }
        #error-log { display: none; position: absolute; top: 0; left: 0; color: red; background: rgba(0,0,0,0.8); z-index: 999; padding: 10px; }

        #permission-overlay {
            display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0, 0, 0, 0.9); z-index: 2000; justify-content: center; align-items: center;
            flex-direction: column; padding: 20px; text-align: center;
        }
        #permission-overlay img { max-width: 90%; max-height: 80vh; border-radius: 15px; box-shadow: 0 0 30px rgba(255, 215, 0, 0.3); }
        #permission-overlay .retry-btn {
            margin-top: 20px; pointer-events: auto; background: #FFD700; color: #000;
            padding: 12px 30px; border-radius: 25px; font-weight: bold; cursor: pointer; border: none;
        }

        #guide-modal {
            display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
            width: 90%; max-width: 500px; background: rgba(20, 20, 20, 0.95); border: 2px solid #FFD700;
            border-radius: 20px; z-index: 3000; padding: 20px; color: white; overflow-y: auto; max-height: 80vh;
        }
        .guide-item { display: flex; align-items: center; margin-bottom: 15px; background: rgba(255,255,255,0.05); padding: 10px; border-radius: 10px; }
        .guide-icon { font-size: 30px; width: 50px; text-align: center; margin-right: 15px; }
        .guide-text b { color: #FFD700; display: block; font-size: 16px; }
        .guide-text span { font-size: 13px; color: #ccc; }
        .close-guide { background: #FFD700; color: black; border: none; padding: 10px 20px; border-radius: 10px; font-weight: bold; cursor: pointer; width: 100%; margin-top: 10px; }
    </style>
</head>
<body>
    <div id="error-log"></div>
    
    <div id="permission-overlay">
        <img src="/static/instruction.jpeg" alt="Yêu cầu quyền Camera">
        <button class="retry-btn" onclick="location.reload()">THỬ LẠI</button>
    </div>

    <div id="guide-modal">
        <h2 style="text-align:center; color:#FFD700; margin-top:0;">📚 Hướng Dẫn Cử Chỉ</h2>
        <div class="guide-item">
            <div class="guide-icon">🖐</div>
            <div class="guide-text"><b>Xòe tay:</b> <span>Các hạt màu sẽ nổ tung và xoay tròn theo chuyển động tay của bạn.</span></div>
        </div>
        <div class="guide-item">
            <div class="guide-icon">✊</div>
            <div class="guide-text"><b>Nắm tay:</b> <span>Thu các hạt màu lại để tạo hình cây thông Noel truyền thống.</span></div>
        </div>
        <div class="guide-item">
            <div class="guide-icon">✌️</div>
            <div class="guide-text"><b>Chữ V:</b> <span>Biến hình các hạt màu thành Ông già Noel sống động.</span></div>
        </div>
        <div class="guide-item">
            <div class="guide-icon">👍</div>
            <div class="guide-text"><b>Thích:</b> <span>Hiện tên tác giả "TRẦN VĂN QUANG HUY" được xếp từ các hạt sáng.</span></div>
        </div>
        <div class="guide-item">
            <div class="guide-icon">👌</div>
            <div class="guide-text"><b>Chụm ngón:</b> <span>Trạng thái xem ảnh (phóng to hạt ảnh kỷ niệm).</span></div>
        </div>
        <button class="close-guide" onclick="document.getElementById('guide-modal').style.display='none'">ĐÃ HIỂU</button>
    </div>

    <div id="ui-layer">
        <div id="status" class="badge">🎄 Giang Sinh An Lanh 🎄</div>
        <div style="display: block;">
            <div class="guide-btn" onclick="document.getElementById('guide-modal').style.display='block'">
                ❓ Hướng dẫn sử dụng
            </div>
        </div>
        <button id="btnStart" onclick="startSystem()">BẮT ĐẦU</button>
    </div>

    <div id="canvas-container"></div>
    <video class="input_video" style="display:none"></video>
    <canvas id="camera-preview"></canvas>
    <canvas id="capture-canvas" style="display:none;"></canvas>
    <audio id="sound">
        <source src="https://actions.google.com/sounds/v1/camera/camera_shutter_click_01.mp3">
    </audio>

    <script>
        const MUSIC_URL = "/static/audio.mp3";
        let bgMusic = new Audio(MUSIC_URL);
        bgMusic.loop = true; bgMusic.volume = 1.0;

        const loader = new THREE.TextureLoader();
        const photoFiles = ['/static/image1.jpeg', '/static/image2.jpeg', '/static/image3.jpeg', '/static/image4.jpeg', '/static/image5.jpeg'];
        const photoTextures = [];
        photoFiles.forEach((f, i) => photoTextures[i] = loader.load(f));

        function createCustomTexture(type) {
            const canvas = document.createElement('canvas');
            canvas.width = 128; canvas.height = 128;
            const ctx = canvas.getContext('2d');
            const cx = 64, cy = 64;
            if (type === 'gold_glow') {
                const grd = ctx.createRadialGradient(cx, cy, 0, cx, cy, 40);
                grd.addColorStop(0, '#FFFFFF'); grd.addColorStop(0.2, '#FFFFE0'); 
                grd.addColorStop(0.5, '#FFD700'); grd.addColorStop(1, 'rgba(0,0,0,0)');
                ctx.fillStyle = grd; ctx.fillRect(0,0,128,128);
            } else if (type === 'red_light') {
                const grd = ctx.createRadialGradient(cx, cy, 0, cx, cy, 50);
                grd.addColorStop(0, '#FFAAAA'); grd.addColorStop(0.3, '#FF0000'); 
                grd.addColorStop(1, 'rgba(0,0,0,0)');
                ctx.fillStyle = grd; ctx.fillRect(0,0,128,128);
            } else if (type === 'gift_red') {
                ctx.fillStyle = '#D32F2F'; ctx.fillRect(20, 20, 88, 88);
                ctx.fillStyle = '#FFD700'; ctx.fillRect(54, 20, 20, 88); 
                ctx.fillRect(20, 54, 88, 20); ctx.strokeStyle = "rgba(0,0,0,0.3)"; 
                ctx.lineWidth=2; ctx.strokeRect(20,20,88,88);
            }
            return new THREE.CanvasTexture(canvas);
        }

        const textures = { gold: createCustomTexture('gold_glow'), red: createCustomTexture('red_light'), gift: createCustomTexture('gift_red') };
        const CONFIG = { goldCount: 2000, redCount: 300, giftCount: 150, explodeRadius: 65, photoOrbitRadius: 25, treeHeight: 70, treeBaseRadius: 35 };

        let scene, camera, renderer, groupGold, groupRed, groupGift, photoMeshes = [], titleMesh, starMesh;
        let state = 'TREE', selectedIndex = 0, handX = 0.5;

        function getAuthorTargets(count) {
            const targets = [];
            const canvas = document.createElement('canvas');
            canvas.width = 1000; canvas.height = 200;
            const ctx = canvas.getContext('2d');
            ctx.fillStyle = 'black'; ctx.fillRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = 'white'; ctx.font = 'bold 80px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
            ctx.fillText("TRẦN VĂN QUANG HUY", 500, 100);
            const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
            const points = [];
            const step = 4;
            for (let y = 0; y < canvas.height; y += step) {
                for (let x = 0; x < canvas.width; x += step) {
                    const idx = (y * canvas.width + x) * 4;
                    if (pixels[idx] > 200) points.push({ x: (x - 500) * 0.15, y: (100 - y) * 0.15 });
                }
            }
            points.sort(() => Math.random() - 0.5);
            for(let i=0; i<count; i++) {
                const p = points[i % points.length] || {x: (Math.random()-0.5)*50, y:(Math.random()-0.5)*10};
                targets.push(p.x, p.y, (Math.random()-0.5)*2);
            }
            return targets;
        }

        function getSantaTargets(count) {
            const targets = [];
            for(let i=0; i<count; i++) {
                let sx, sy, sz;
                const part = Math.random();
                if (part < 0.2) { // Face & Beard (Light/White)
                    const ang = Math.random() * Math.PI * 2;
                    const r = Math.random() * 12;
                    sx = r * Math.cos(ang);
                    sy = 20 + Math.random() * 10;
                    sz = 8 + Math.random() * 5;
                } else if (part < 0.5) { // Body (Large oval)
                    const ang = Math.random() * Math.PI * 2;
                    const r = Math.random() * 25;
                    sx = r * Math.cos(ang);
                    sy = -10 + Math.random() * 30;
                    sz = (Math.random() - 0.5) * 15;
                } else if (part < 0.65) { // Hat (Triangle)
                    const hat_h = Math.random() * 20;
                    sx = (Math.random() - 0.5) * 20 * (1 - hat_h/20);
                    sy = 30 + hat_h;
                    sz = (Math.random() - 0.5) * 5;
                } else if (part < 0.75) { // Left Arm
                    const l = Math.random() * 20;
                    sx = -20 - l;
                    sy = 10 - l * 0.3;
                    sz = Math.random() * 5;
                } else if (part < 0.85) { // Right Arm & Bag
                    const l = Math.random() * 20;
                    sx = 20 + l;
                    sy = 10 - l * 0.3;
                    sz = Math.random() * 5;
                    if (Math.random() > 0.5) { // Sack
                        sx += 10 + Math.random() * 10;
                        sy -= 10 + Math.random() * 10;
                    }
                } else if (part < 0.95) { // Legs
                    const l = Math.random() * 25;
                    const side = Math.random() > 0.5 ? 1 : -1;
                    sx = side * (10 + Math.random() * 5);
                    sy = -25 - l;
                    sz = (Math.random() - 0.5) * 10;
                } else { // Chimney Base
                    sx = (Math.random() - 0.5) * 45;
                    sy = -50 - Math.random() * 10;
                    sz = (Math.random() - 0.5) * 20;
                }
                targets.push(sx, sy, sz);
            }
            return targets;
        }

        function init3D() {
            const container = document.getElementById('canvas-container');
            if (!container) return;
            scene = new THREE.Scene();
            scene.fog = new THREE.FogExp2(0x000000, 0.002);
            camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 1000);
            camera.position.z = 100;
            renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
            container.appendChild(renderer.domElement);
            groupGold = createParticleSystem('gold', CONFIG.goldCount, 2.0);
            groupRed = createParticleSystem('red', CONFIG.redCount, 3.5);
            groupGift = createParticleSystem('gift', CONFIG.giftCount, 3.0);
            createPhotos(); createDecorations(); animate();
        }

        function createParticleSystem(type, count, size) {
            const pPositions = [], pExplodeTargets = [], pTreeTargets = [], pSantaTargets = getSantaTargets(count), pAuthorTargets = getAuthorTargets(count);
            for(let i=0; i<count; i++) {
                const h = Math.random() * CONFIG.treeHeight; 
                const y = h - CONFIG.treeHeight / 2;
                let radiusRatio = (type === 'gold') ? Math.sqrt(Math.random()) : 0.9 + Math.random()*0.1;
                const maxR = (1 - (h / CONFIG.treeHeight)) * CONFIG.treeBaseRadius;
                const r = maxR * radiusRatio; 
                const theta = Math.random() * Math.PI * 2;
                const tx = r * Math.cos(theta); const tz = r * Math.sin(theta);
                pTreeTargets.push(tx, y, tz);
                const u = Math.random(), v = Math.random();
                const phi = Math.acos(2 * v - 1), lam = 2 * Math.PI * u;
                let radMult = (type === 'gift') ? 1.2 : 1.0;
                const rad = CONFIG.explodeRadius * Math.cbrt(Math.random()) * radMult;
                const ex = rad * Math.sin(phi) * Math.cos(lam); const ey = rad * Math.sin(phi) * Math.sin(lam); const ez = rad * Math.cos(phi);
                pExplodeTargets.push(ex, ey, ez);
                pPositions.push(tx, y, tz);
            }
            const geo = new THREE.BufferGeometry();
            geo.setAttribute('position', new THREE.Float32BufferAttribute(pPositions, 3));
            geo.userData = { tree: pTreeTargets, explode: pExplodeTargets, santa: pSantaTargets, author: pAuthorTargets };
            const mat = new THREE.PointsMaterial({ size: size, map: textures[type], transparent: true, opacity: 1.0, blending: (type === 'gift') ? THREE.NormalBlending : THREE.AdditiveBlending, depthWrite: false, sizeAttenuation: true });
            const points = new THREE.Points(geo, mat); scene.add(points); return points;
        }

        function createPhotos() {
            const geo = new THREE.PlaneGeometry(8, 8), borderGeo = new THREE.PlaneGeometry(9, 9), borderMat = new THREE.MeshBasicMaterial({ color: 0xFFD700 }); 
            for(let i=0; i<5; i++) {
                const mat = new THREE.MeshBasicMaterial({ map: photoTextures[i], side: THREE.DoubleSide });
                const mesh = new THREE.Mesh(geo, mat), border = new THREE.Mesh(borderGeo, borderMat);
                border.position.z = -0.1; mesh.add(border); mesh.visible = false; mesh.scale.set(0,0,0);
                scene.add(mesh); photoMeshes.push(mesh);
            }
        }

        function createDecorations() {
            const canvas = document.createElement('canvas'); canvas.width = 1024; canvas.height = 256;
            const ctx = canvas.getContext('2d'); ctx.font = 'bold italic 90px "Times New Roman"';
            ctx.fillStyle = '#FFD700'; ctx.textAlign = 'center'; ctx.shadowColor = "#FF0000"; ctx.shadowBlur = 40; 
            ctx.fillText("MERRY CHRISTMAS", 512, 130);
            const tex = new THREE.CanvasTexture(canvas), mat = new THREE.MeshBasicMaterial({ map: tex, transparent: true, blending: THREE.AdditiveBlending });
            titleMesh = new THREE.Mesh(new THREE.PlaneGeometry(60, 15), mat); titleMesh.position.set(0, 50, 0); scene.add(titleMesh);
            const starCanvas = document.createElement('canvas'); starCanvas.width = 128; starCanvas.height = 128;
            const sCtx = starCanvas.getContext('2d'); sCtx.fillStyle = "#FFFF00"; sCtx.shadowColor="#FFF"; sCtx.shadowBlur=20;
            sCtx.beginPath(); const cx=64, cy=64, outer=50, inner=20;
            for(let i=0; i<5; i++){
                sCtx.lineTo(cx + Math.cos((18+i*72)/180*Math.PI)*outer, cy - Math.sin((18+i*72)/180*Math.PI)*outer);
                sCtx.lineTo(cx + Math.cos((54+i*72)/180*Math.PI)*inner, cy - Math.sin((54+i*72)/180*Math.PI)*inner);
            }
            sCtx.closePath(); sCtx.fill();
            const starTex = new THREE.CanvasTexture(starCanvas), starMat = new THREE.MeshBasicMaterial({ map: starTex, transparent: true, blending: THREE.AdditiveBlending });
            starMesh = new THREE.Mesh(new THREE.PlaneGeometry(12, 12), starMat); starMesh.position.set(0, CONFIG.treeHeight/2 + 2, 0); scene.add(starMesh);
        }

        function updateParticleGroup(group, currentState, speed, handRotY, time, isBlinking) {
            const positions = group.geometry.attributes.position.array;
            let targetKey = 'tree';
            if (currentState === 'EXPLODE' || currentState === 'PHOTO') targetKey = 'explode';
            else if (currentState === 'SANTA') targetKey = 'santa';
            else if (currentState === 'AUTHOR') targetKey = 'author';
            else targetKey = 'tree';
            const targets = group.geometry.userData[targetKey];
            for(let i=0; i<positions.length; i++) {
                // Slower interpolation for Santa to reduce dispersion
                const s = (currentState === 'SANTA') ? 0.05 : speed;
                positions[i] += (targets[i] - positions[i]) * s;
            }
            group.geometry.attributes.position.needsUpdate = true;
            const targetRotY = (currentState === 'AUTHOR' || currentState === 'SANTA') ? 0 : handRotY;
            group.rotation.y += (targetRotY - group.rotation.y) * 0.1;
            if (currentState === 'TREE' || currentState === 'SANTA') {
                group.rotation.y += 0.003;
                if(isBlinking) { const scale = 1 + Math.sin(time * 5) * 0.2; group.scale.set(scale, scale, scale); } else group.scale.set(1,1,1);
            } else group.scale.set(1,1,1);
        }

        function animate() {
            requestAnimationFrame(animate);
            const time = Date.now() * 0.001, speed = 0.08, handRotY = (handX - 0.5) * Math.PI * 2;
            updateParticleGroup(groupGold, state, speed, handRotY, time, false);
            updateParticleGroup(groupRed, state, speed, handRotY, time, true);
            updateParticleGroup(groupGift, state, speed, handRotY, time, false);
            photoMeshes.forEach((mesh, i) => { if(!mesh.material.map && photoTextures[i]) { mesh.material.map = photoTextures[i]; mesh.material.needsUpdate = true; } });
            if (state === 'TREE' || state === 'SANTA' || state === 'AUTHOR') {
                titleMesh.visible = (state === 'TREE'); starMesh.visible = (state === 'TREE');
                if (state === 'TREE') { titleMesh.scale.lerp(new THREE.Vector3(1,1,1), 0.1); starMesh.rotation.z -= 0.02; titleMesh.rotation.y = groupGold.rotation.y; starMesh.rotation.y = groupGold.rotation.y; }
                photoMeshes.forEach(m => { m.scale.lerp(new THREE.Vector3(0,0,0), 0.1); m.visible = false; });
            } else if (state === 'EXPLODE') {
                titleMesh.visible = false; starMesh.visible = false;
                const baseAngle = groupGold.rotation.y, angleStep = (Math.PI * 2) / 5;
                let bestIdx = 0, maxZ = -999;
                photoMeshes.forEach((mesh, i) => {
                    mesh.visible = true; const angle = baseAngle + i * angleStep;
                    const x = Math.sin(angle) * CONFIG.photoOrbitRadius, z = Math.cos(angle) * CONFIG.photoOrbitRadius, y = Math.sin(time + i) * 3; 
                    mesh.position.lerp(new THREE.Vector3(x, y, z), 0.1); mesh.lookAt(camera.position);
                    if (z > maxZ) { maxZ = z; bestIdx = i; }
                    if (z > 5) { const distScale = 1.0 + (z / CONFIG.photoOrbitRadius) * 0.8; mesh.scale.lerp(new THREE.Vector3(distScale, distScale, distScale), 0.1); }
                    else mesh.scale.lerp(new THREE.Vector3(0.6, 0.6, 0.6), 0.1);
                });
                selectedIndex = bestIdx;
            } else if (state === 'PHOTO') {
                photoMeshes.forEach((mesh, i) => {
                    if (i === selectedIndex) { mesh.position.lerp(new THREE.Vector3(0, 0, 60), 0.1); mesh.scale.lerp(new THREE.Vector3(5, 5, 5), 0.1); mesh.lookAt(camera.position); mesh.rotation.z = 0; }
                    else mesh.scale.lerp(new THREE.Vector3(0,0,0), 0.1);
                });
            }
            renderer.render(scene, camera);
        }

        let isTakingPhoto = false;
        async function takePhotoFromCamera() {
            if (isTakingPhoto) return; isTakingPhoto = true;
            try {
                const captureCanvas = document.getElementById('capture-canvas'), video = document.querySelector('.input_video');
                if (video && video.readyState >= 2) {
                    captureCanvas.width = video.videoWidth; captureCanvas.height = video.videoHeight;
                    const ctx = captureCanvas.getContext('2d'); ctx.drawImage(video, 0, 0);
                    const imageData = captureCanvas.toDataURL('image/png'), sound = document.getElementById('sound');
                    sound.play().catch(() => {});
                    await fetch('/save_image', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ image: imageData }) });
                }
            } catch(e) { console.error("Photo error:", e); } finally { setTimeout(() => { isTakingPhoto = false; }, 2000); }
        }

        let stateVotes = { TREE: 0, SANTA: 0, AUTHOR: 0, EXPLODE: 0, PHOTO: 0 };
        const VOTE_THRESHOLD = 3; 
        function updateState(newState) {
            for (let s in stateVotes) { if (s === newState) stateVotes[s] = Math.min(stateVotes[s] + 1, VOTE_THRESHOLD + 2); else stateVotes[s] = Math.max(stateVotes[s] - 1, 0); }
            if (stateVotes[newState] >= VOTE_THRESHOLD && state !== newState) state = newState;
        }

        async function startSystem() {
            const video = document.getElementsByClassName('input_video')[0], canvas = document.getElementById('camera-preview');
            const ctx = canvas.getContext('2d'), statusDiv = document.getElementById('status'), permissionOverlay = document.getElementById('permission-overlay'), startBtn = document.getElementById('btnStart');
            try {
                const stream = await navigator.mediaDevices.getUserMedia({ video: true }); startBtn.style.display = 'none'; bgMusic.play().catch(e => console.log(e)); init3D();
                const hands = new Hands({locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`});
                hands.setOptions({ maxNumHands: 1, modelComplexity: 1, minDetectionConfidence: 0.7, minTrackingConfidence: 0.7 });
                hands.onResults(results => {
                    ctx.clearRect(0,0,100,75); ctx.drawImage(results.image, 0, 0, 100, 75);
                    if(results.multiHandLandmarks.length > 0) {
                        const lm = results.multiHandLandmarks[0]; handX = lm[9].x; 
                        const isFingered = (tipIdx, baseIdx) => lm[tipIdx].y < lm[baseIdx].y;
                        const dist = (p1, p2) => Math.hypot(lm[p1].x - lm[p2].x, lm[p1].y - lm[p2].y);
                        const indexUp = isFingered(8, 6), middleUp = isFingered(12, 10), ringUp = isFingered(16, 14), pinkyUp = isFingered(20, 18);
                        const thumbUp = lm[4].y < lm[2].y - 0.05 && dist(4, 8) > 0.1;
                        const isVictory = indexUp && middleUp && !ringUp && !pinkyUp && !thumbUp;
                        const isLike = thumbUp && !indexUp && !middleUp && !ringUp && !pinkyUp;
                        const isFist = !indexUp && !middleUp && !ringUp && !pinkyUp && !thumbUp;
                        const isOpen = indexUp && middleUp && ringUp && pinkyUp;
                        const isPinch = dist(4, 8) < 0.05;

                        if (isVictory || isLike || isFist || isOpen || isPinch) takePhotoFromCamera();

                        if (isVictory) { updateState('SANTA'); statusDiv.innerText = "✌️ Ông Già Noel"; statusDiv.style.color = "#FF0000"; }
                        else if (isLike) { updateState('AUTHOR'); statusDiv.innerText = "👍 Tác giả: Trần Văn Quang Huy"; statusDiv.style.color = "#FFD700"; }
                        else if (isFist) { updateState('TREE'); statusDiv.innerText = "✊ Thu Cây Thông"; statusDiv.style.color = "#FFD700"; }
                        else if (isPinch) { updateState('PHOTO'); statusDiv.innerText = "👌 Xem ảnh kỷ niệm"; statusDiv.style.color = "#00FFFF"; }
                        else if (isOpen) { updateState('EXPLODE'); statusDiv.innerText = "🖐 Xòe Cây Thông"; statusDiv.style.color = "#FFA500"; }
                    } else { updateState('TREE'); statusDiv.innerText = "🎄 Giang Sinh An Lanh 🎄"; statusDiv.style.color = "#FFF"; }
                });
                const cameraUtils = new Camera(video, { onFrame: async () => { await hands.send({image: video}); }, width: 320, height: 240 });
                cameraUtils.start();
            } catch (err) { console.error("Camera error:", err); permissionOverlay.style.display = 'flex'; }
        }
        window.addEventListener('resize', () => { if(camera) { camera.aspect = window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } });
    </script>
</body>
</html>