Fruit merge code revision

<!DOCTYPE html>
<html lang=”en”>
<head>
    <meta charset=”UTF-8″>
    <meta name=”viewport” content=”width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no”>
    <title>Fruit Merge Game</title>
    <script src=”https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js”></script>
    <style>
        body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            background-color: #fff8e1;
            font-family: ‘Segoe UI’, Tahoma, Geneva, Verdana, sans-serif;
            touch-action: none;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            align-items: center;
            height: 100dvh;
            user-select: none;
            -webkit-user-select: none;
        }

        #evolution-guide {
            width: 100%;
            max-width: 450px;
            height: 5dvh;
            display: flex;
            justify-content: space-evenly;
            align-items: center;
            padding: 0 5px;
            box-sizing: border-box;
            font-size: min(4vw, 2.5dvh);
            color: #f57c00;
        }

        #evolution-guide .arrow { opacity: 0.5; font-size: 0.8em; font-weight: bold; }

        #game-container {
            position: relative;
            width: 100vw;
            max-width: 450px;
            height: 95dvh;
            max-height: 900px;
            background-color: #ffecb3;
            border-left: 12px solid #ffb300;
            border-right: 12px solid #ffb300;
            border-bottom: 12px solid #ffb300;
            border-radius: 0 0 20px 20px;
            box-sizing: border-box;
            box-shadow: 0 15px 35px rgba(255, 160, 0, 0.2);
            overflow: hidden;
        }

        canvas { display: block; width: 100%; height: 100%; }

        #ui {
            position: absolute; top: 20px; left: 20px; color: #333; z-index: 10;
            pointer-events: none;
        }

        .ui-row {
            display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
        }

        .ui-box {
            background: rgba(255, 255, 255, 0.9); padding: 8px 15px; border-radius: 20px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1); border: 2px solid #ffca28;
            display: inline-block;
        }

        #score { font-size: 24px; font-weight: 800; }
        #high-score { font-size: 16px; font-weight: 700; color: #e65100; margin-bottom: 8px; }

        #btn-toggle-tools {
            pointer-events: auto;
            background: rgba(255, 255, 255, 0.9);
            border: 2px solid #ffca28;
            border-radius: 50%;
            width: 42px;
            height: 42px;
            font-size: 22px;
            cursor: pointer;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            display: flex;
            justify-content: center;
            align-items: center;
            transition: transform 0.2s, background-color 0.2s;
        }
       
        #btn-toggle-tools:active { transform: scale(0.9); }
        #btn-toggle-tools:hover { background: #fff3e0; }

        #powerups {
            pointer-events: auto; display: flex; flex-wrap: wrap; gap: 8px;
            transition: opacity 0.3s ease, transform 0.3s ease, max-height 0.3s ease;
            transform-origin: top left;
            max-height: 100px;
            opacity: 1;
        }
       
        #powerups.hidden {
            opacity: 0;
            transform: scaleY(0);
            max-height: 0;
            pointer-events: none;
        }
       
        .powerup-btn {
            color: white; border-radius: 15px; padding: 8px 12px; font-size: 14px;
            font-weight: bold; cursor: pointer; box-shadow: 0 4px 6px rgba(0,0,0,0.2);
            transition: all 0.2s;
        }
        .powerup-btn:active { transform: scale(0.95); }
        .powerup-btn:disabled { background: #9e9e9e !important; border-color: #757575 !important; color: #e0e0e0 !important; cursor: not-allowed; transform: none; box-shadow: none; }

        /* Button Colors */
        .slice-btn { background: #e91e63; border: 2px solid #c2185b; }
        .slice-btn.active { background: #ff4081; box-shadow: 0 0 15px #ff4081; transform: scale(1.05); }
       
        .quake-btn { background: #ff9800; border: 2px solid #e65100; }
       
        .rainbow-btn { background: #9c27b0; border: 2px solid #7b1fa2; }
        .rainbow-btn.active { background: #e040fb; box-shadow: 0 0 15px #e040fb; transform: scale(1.05); }

        .auto-btn { background: #2196f3; border: 2px solid #1976d2; }
        .auto-btn.active { background: #64b5f6; box-shadow: 0 0 15px #64b5f6; transform: scale(1.05); }

        #next-piece {
            position: absolute; top: 20px; right: 20px; background: rgba(255, 255, 255, 0.9);
            padding: 10px 15px; border-radius: 20px; text-align: center; font-size: 14px; font-weight: bold;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1); pointer-events: none; z-index: 10; border: 2px solid #ffca28;
        }
        #next-emoji { font-size: 32px; margin-top: 2px; }

        #game-over {
            position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
            background: rgba(255, 255, 255, 0.95); padding: 30px 40px; border-radius: 20px;
            text-align: center; display: none; box-shadow: 0 10px 30px rgba(0,0,0,0.3); z-index: 20;
            width: 70%; border: 3px solid #ffb300;
        }
        #game-over h1 { margin-top: 0; color: #e65100; font-size: 32px; }
        #final-score { font-size: 24px; font-weight: bold; }
        #game-over button {
            padding: 12px 24px; font-size: 20px; background: #4caf50; color: white; border: none;
            border-radius: 12px; cursor: pointer; margin-top: 15px; font-weight: bold;
            box-shadow: 0 4px 6px rgba(76, 175, 80, 0.4); transition: transform 0.1s; width: 100%;
        }
        #game-over button:active { transform: scale(0.95); }
    </style>
</head>
<body>

    <div id=”evolution-guide”></div>

    <div id=”game-container”>
        <div id=”ui”>
            <div class=”ui-row”>
                <div id=”score” class=”ui-box”>Score: 0</div>
                <button id=”btn-toggle-tools” onclick=”toggleTools()” title=”Toggle Tools”>βš™οΈ</button>
            </div>
            <div id=”high-score” class=”ui-box”>Best: 0</div>
           
            <!– Added the “hidden” class here so it starts closed –>
            <div id=”powerups” class=”hidden”>
                <button id=”btn-slice” class=”powerup-btn slice-btn” onclick=”toggleSlice()”>πŸ”ͺ Slice (2)</button>
                <button id=”btn-quake” class=”powerup-btn quake-btn” onclick=”triggerQuake()”>πŸŒ‹ Quake (3)</button>
                <button id=”btn-rainbow” class=”powerup-btn rainbow-btn” onclick=”toggleRainbow()”>🌈 Rainbow (2)</button>
                <button id=”btn-auto” class=”powerup-btn auto-btn” onclick=”toggleAutoPlay()”>πŸ€– Auto</button>
            </div>
        </div>
       
        <div id=”next-piece”>
            Next
            <div id=”next-emoji”>πŸ’</div>
        </div>

        <div id=”game-over”>
            <h1>Basket Full! πŸ‰</h1>
            <p id=”final-score”>Score: 0</p>
            <button onclick=”resetGame()”>Play Again</button>
        </div>

        <canvas id=”gameCanvas”></canvas>
    </div>

    <script type=”module”>
        import { initializeApp } from “https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js”;
        import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from “https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js”;
        import { getFirestore, doc, getDoc, setDoc } from “https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js”;

        let manualFirebaseConfig = null;
        const firebaseConfig = typeof __firebase_config !== ‘undefined’ ? JSON.parse(__firebase_config) : manualFirebaseConfig;
        const appId = typeof __app_id !== ‘undefined’ ? __app_id : ‘standalone-app’;
        let db, auth, currentUser;
        let bestScore = 0;

        if (firebaseConfig) {
            const app = initializeApp(firebaseConfig);
            auth = getAuth(app); db = getFirestore(app);
            const initAuth = async () => {
                if (typeof __initial_auth_token !== ‘undefined’ && __initial_auth_token) await signInWithCustomToken(auth, __initial_auth_token);
                else await signInAnonymously(auth);
            };
            initAuth();
            onAuthStateChanged(auth, async (user) => {
                if (user) { currentUser = user; await loadBestScore(); }
            });
        }

        async function loadBestScore() {
            if (!currentUser || !db) return;
            try {
                const scoreRef = doc(db, ‘artifacts’, appId, ‘users’, currentUser.uid, ‘gameData’, ‘highScore’);
                const snap = await getDoc(scoreRef);
                if (snap.exists()) { bestScore = snap.data().score || 0; document.getElementById(‘high-score’).innerText = ‘Best: ‘ + bestScore; }
            } catch (e) { console.error(“Failed to load best score”, e); }
        }

        async function saveBestScore(newScore) {
            if (!currentUser || !db) return;
            try {
                const scoreRef = doc(db, ‘artifacts’, appId, ‘users’, currentUser.uid, ‘gameData’, ‘highScore’);
                await setDoc(scoreRef, { score: newScore });
            } catch (e) { console.error(“Failed to save best score”, e); }
        }

        // ==========================================
        // GAME LOGIC & CONFIG
        // ==========================================
        const { Engine, Runner, Bodies, Composite, Events, Query } = window.Matter;

        const engine = Engine.create();
        const world = engine.world;
        const container = document.getElementById(‘game-container’);
        const canvas = document.getElementById(‘gameCanvas’);
        const ctx = canvas.getContext(‘2d’);
        const dpr = window.devicePixelRatio || 1;
        let width, height, ground, leftWall, rightWall;

        const particles = [];
        const floatingTexts = [];
        let shakeFrames = 0;
        let shakeIntensity = 0;

        const orbTypes = [
            { level: 0, scale: 1.0, emoji: ‘πŸ’’, score: 2, color: ‘#e53935’ },
            { level: 1, scale: 1.3, emoji: ‘πŸ“’, score: 4, color: ‘#d81b60’ },
            { level: 2, scale: 1.7, emoji: ‘πŸ‡’, score: 8, color: ‘#8e24aa’ },
            { level: 3, scale: 2.2, emoji: ‘πŸ‹’, score: 16, color: ‘#fdd835’ },
            { level: 4, scale: 2.8, emoji: ‘🍊’, score: 32, color: ‘#fb8c00’ },
            { level: 5, scale: 3.5, emoji: ‘🍎’, score: 64, color: ‘#e53935’ },
            { level: 6, scale: 4.3, emoji: ‘πŸ‘’, score: 128, color: ‘#f48fb1’ },
            { level: 7, scale: 5.2, emoji: ‘πŸ₯₯’, score: 256, color: ‘#8d6e63’ },
            { level: 8, scale: 6.2, emoji: ‘🍈’, score: 512, color: ‘#c0ca33’ },
            { level: 9, scale: 7.3, emoji: ‘🍍’, score: 1024, color: ‘#fbc02d’ },
            { level: 10, scale: 8.5, emoji: ‘πŸ‰’, score: 2048, color: ‘#43a047’ }
        ];

        const guideDiv = document.getElementById(‘evolution-guide’);
        orbTypes.forEach((type, index) => {
            const span = document.createElement(‘span’); span.innerText = type.emoji; guideDiv.appendChild(span);
            if (index < orbTypes.length – 1) {
                const arrow = document.createElement(‘span’); arrow.className = ‘arrow’; arrow.innerText = ‘β€Ί’; guideDiv.appendChild(arrow);
            }
        });

        let score = 0, isGameOver = false, canDrop = true;
        let nextDropLevel = Math.floor(Math.random() * 3);
        const DROP_COOLDOWN = 600, DEATH_LINE_Y = 120;
        let baseR = 20, isAiming = false, aimX = 0;
       
        // Powerup Tracking
        let sliceUses = 2, isSliceActive = false;
        let quakeUses = 3;
        let rainbowUses = 2, isHoldingRainbow = false;
        let isAutoPlay = false, autoPlayTimer = null;
       
        let lineBreachStartTime = null;
        const GRACE_PERIOD_MS = 3000;

        document.getElementById(‘next-emoji’).innerText = orbTypes[nextDropLevel].emoji;

        // — UI VISIBILITY TOGGLE —
        window.toggleTools = function() {
            const powerups = document.getElementById(‘powerups’);
            powerups.classList.toggle(‘hidden’);
        };

        // — FX FUNCTIONS —
        function spawnParticles(x, y, color, count) {
            for(let i=0; i<count; i++) {
                particles.push({
                    x: x, y: y,
                    vx: (Math.random() – 0.5) * 12, vy: (Math.random() – 0.5) * 12 – 2,
                    life: 1.0, color: color, size: Math.random() * 6 + 3
                });
            }
        }

        function spawnFloatingText(x, y, text, color, size) {
            floatingTexts.push({ x: x, y: y, vy: -1.5, life: 1.0, text: text, color: color, size: size });
        }

        function triggerShake(level) {
            if (level < 3) return;
            shakeFrames = 10 + (level * 2); shakeIntensity = level * 1.5;
        }

        // — POWERUP BUTTONS —
        window.toggleSlice = function() {
            if (sliceUses <= 0 || isGameOver || isAutoPlay) return;
            if (isHoldingRainbow) toggleRainbow(); // Mutually exclusive
           
            isSliceActive = !isSliceActive;
            const btn = document.getElementById(‘btn-slice’);
            if (isSliceActive) { btn.classList.add(‘active’); btn.innerText = `CANCEL SLICE`; }
            else { btn.classList.remove(‘active’); btn.innerText = `πŸ”ͺ Slice (${sliceUses})`; }
        };

        window.toggleRainbow = function() {
            if (rainbowUses <= 0 || isGameOver || isAutoPlay) return;
            if (isSliceActive) toggleSlice(); // Mutually exclusive
           
            isHoldingRainbow = !isHoldingRainbow;
            const btn = document.getElementById(‘btn-rainbow’);
            if (isHoldingRainbow) {
                btn.classList.add(‘active’); btn.innerText = `CANCEL RAINBOW`;
                document.getElementById(‘next-emoji’).innerText = ‘🌟’;
            } else {
                btn.classList.remove(‘active’); btn.innerText = `🌈 Rainbow (${rainbowUses})`;
                document.getElementById(‘next-emoji’).innerText = orbTypes[nextDropLevel].emoji;
            }
        }

        window.triggerQuake = function() {
            if (quakeUses <= 0 || isGameOver || isAutoPlay) return;

            quakeUses–;
            const btn = document.getElementById(‘btn-quake’);
            btn.innerText = `πŸŒ‹ Quake (${quakeUses})`;
            if (quakeUses <= 0) btn.disabled = true;

            const orbs = Composite.allBodies(world).filter(b => b.label === ‘orb’ || b.label === ‘rainbow’);
            orbs.forEach(orb => {
                const vx = orb.velocity.x + (Math.random() – 0.5) * 6;
                const vy = – (Math.random() * 8 + 6);
                window.Matter.Body.setVelocity(orb, { x: vx, y: vy });
            });

            triggerShake(12);
            spawnFloatingText(width / 2, height / 2, “EARTHQUAKE!”, “#ff9800”, 42);
        };

        window.toggleAutoPlay = function() {
            if (isGameOver) return;
            isAutoPlay = !isAutoPlay;
            const btn = document.getElementById(‘btn-auto’);
           
            if (isAutoPlay) {
                btn.classList.add(‘active’);
                btn.innerText = `πŸ›‘ Stop Auto`;
                if (isSliceActive) toggleSlice();
                if (isHoldingRainbow) toggleRainbow(); // Cancel rainbow to keep AI simple
                performAutoPlayStep();
            } else {
                btn.classList.remove(‘active’);
                btn.innerText = `πŸ€– Auto`;
                clearTimeout(autoPlayTimer);
                isAiming = false;
            }
        };

        // — AUTO PLAY AI LOGIC —
        function performAutoPlayStep() {
            if (!isAutoPlay || isGameOver) return;
           
            const orbs = Composite.allBodies(world).filter(b => b.label === ‘orb’);
            let highestTopEdge = height;
           
            for (let orb of orbs) {
                if (Date.now() – orb.createdAt > 1000) {
                    const topEdge = orb.position.y – getRadius(orb.orbLevel);
                    if (topEdge < highestTopEdge) highestTopEdge = topEdge;
                }
            }

            if (highestTopEdge < DEATH_LINE_Y + 100) {
                toggleAutoPlay();
                spawnFloatingText(width / 2, height / 2, “AI PAUSED: DANGER!”, “#e91e63”, 28);
                return;
            }

            if (canDrop && !isAiming) {
                let targetX = width / 2;
                const matchingOrbs = orbs.filter(b => b.orbLevel === nextDropLevel);
               
                if (matchingOrbs.length > 0) {
                    matchingOrbs.sort((a, b) => a.position.y – b.position.y);
                    targetX = matchingOrbs[0].position.x;
                    targetX += (Math.random() – 0.5) * 30;
                } else {
                    const radius = getRadius(nextDropLevel);
                    targetX = radius + Math.random() * (width – radius * 2);
                }
               
                const radius = getRadius(nextDropLevel);
                targetX = getSafeX(targetX, radius);

                isAiming = true;
                aimX = targetX;
               
                autoPlayTimer = setTimeout(() => {
                    if (!isAutoPlay || isGameOver) { isAiming = false; return; }
                    isAiming = false;
                    dropOrb(aimX);
                    autoPlayTimer = setTimeout(performAutoPlayStep, 800 + Math.random() * 400);
                }, 400);
               
            } else {
                autoPlayTimer = setTimeout(performAutoPlayStep, 200);
            }
        }

        function getRadius(levelId) { return baseR * orbTypes[levelId].scale; }
        function getSafeX(x, radius) { return Math.max(radius, Math.min(width – radius, x)); }

        function createOrb(x, y, levelId) {
            const radius = getRadius(levelId);
            const orb = Bodies.circle(x, y, radius, { restitution: 0.2, friction: 0.1, density: 0.001 * (levelId + 1), label: ‘orb’ });
            orb.orbLevel = levelId; orb.createdAt = Date.now(); return orb;
        }

        function dropOrb(x) {
            if (!canDrop || isGameOver) return;
           
            let droppedRainbow = false;
            if (isHoldingRainbow) {
                droppedRainbow = true;
                isHoldingRainbow = false;
                rainbowUses–;
                const btn = document.getElementById(‘btn-rainbow’);
                btn.classList.remove(‘active’); btn.innerText = `🌈 Rainbow (${rainbowUses})`;
                if (rainbowUses <= 0) btn.disabled = true;
            }

            const radius = droppedRainbow ? getRadius(2) : getRadius(nextDropLevel);
            const safeX = getSafeX(x, radius);

            let orb;
            if (droppedRainbow) {
                orb = Bodies.circle(safeX, 40, radius, { restitution: 0.4, friction: 0.1, density: 0.003, label: ‘rainbow’ });
                orb.createdAt = Date.now();
            } else {
                orb = createOrb(safeX, 40, nextDropLevel);
                nextDropLevel = Math.floor(Math.random() * 3);
            }
           
            Composite.add(world, orb);
            canDrop = false;
           
            document.getElementById(‘next-emoji’).innerText = orbTypes[nextDropLevel].emoji;
            setTimeout(() => { canDrop = true; }, DROP_COOLDOWN);
        }

        function updateScore(points) {
            score += points;
            document.getElementById(‘score’).innerText = ‘Score: ‘ + score;
            if (score > bestScore) { bestScore = score; document.getElementById(‘high-score’).innerText = ‘Best: ‘ + bestScore; }
        }

        function triggerGameOver() {
            if (isGameOver) return;
            isGameOver = true; isAiming = false; isSliceActive = false; isHoldingRainbow = false; lineBreachStartTime = null;
           
            if (isAutoPlay) toggleAutoPlay();

            saveBestScore(bestScore);
            document.getElementById(‘final-score’).innerText = ‘Score: ‘ + score;
            document.getElementById(‘game-over’).style.display = ‘block’;
        }

        window.resetGame = function() {
            Composite.remove(world, Composite.allBodies(world).filter(b => b.label === ‘orb’ || b.label === ‘rainbow’));
            score = 0; document.getElementById(‘score’).innerText = ‘Score: 0’;
            isGameOver = false; canDrop = true; isAiming = false; lineBreachStartTime = null;
           
            // Reset Powerups
            sliceUses = 2; isSliceActive = false;
            const btnSlice = document.getElementById(‘btn-slice’);
            btnSlice.innerText = `πŸ”ͺ Slice (${sliceUses})`; btnSlice.classList.remove(‘active’); btnSlice.disabled = false;
           
            quakeUses = 3;
            const btnQuake = document.getElementById(‘btn-quake’);
            btnQuake.innerText = `πŸŒ‹ Quake (${quakeUses})`; btnQuake.disabled = false;

            rainbowUses = 2; isHoldingRainbow = false;
            const btnRainbow = document.getElementById(‘btn-rainbow’);
            btnRainbow.innerText = `🌈 Rainbow (${rainbowUses})`; btnRainbow.classList.remove(‘active’); btnRainbow.disabled = false;
           
            nextDropLevel = Math.floor(Math.random() * 3);
            document.getElementById(‘next-emoji’).innerText = orbTypes[nextDropLevel].emoji;
            document.getElementById(‘game-over’).style.display = ‘none’;
        }

        // ==========================================
        // INPUT HANDLING
        // ==========================================
        const getEventX = (e) => { const rect = canvas.getBoundingClientRect(); return (e.touches ? e.touches[0].clientX : e.clientX) – rect.left; };
        const getEventY = (e) => { const rect = canvas.getBoundingClientRect(); return (e.touches ? e.touches[0].clientY : e.clientY) – rect.top; };

        const handleStart = (e) => {
            if (isGameOver || !canDrop || isAutoPlay) return;
            if (e.target.tagName.toLowerCase() === ‘button’) return;
           
            e.preventDefault();

            if (isSliceActive) {
                const touchX = getEventX(e), touchY = getEventY(e);
                const clickedBodies = Query.point(Composite.allBodies(world), { x: touchX, y: touchY });
                const orbToDestroy = clickedBodies.find(b => b.label === ‘orb’ || b.label === ‘rainbow’);
               
                if (orbToDestroy) {
                    const color = orbToDestroy.label === ‘rainbow’ ? ‘#ffd700’ : orbTypes[orbToDestroy.orbLevel].color;
                    spawnParticles(orbToDestroy.position.x, orbToDestroy.position.y, color, 30);
                    Composite.remove(world, orbToDestroy);
                    sliceUses–;
                }
               
                isSliceActive = false;
                const btn = document.getElementById(‘btn-slice’);
                btn.classList.remove(‘active’); btn.innerText = `πŸ”ͺ Slice (${sliceUses})`;
                if (sliceUses <= 0) btn.disabled = true;
                return;
            }
            isAiming = true; aimX = getEventX(e);
        };

        const handleMove = (e) => { if (!isAiming || isGameOver || isSliceActive || isAutoPlay) return; e.preventDefault(); aimX = getEventX(e); };
        const handleEnd = (e) => { if (!isAiming || isGameOver || isSliceActive || isAutoPlay) return; e.preventDefault(); isAiming = false; dropOrb(aimX); };

        canvas.addEventListener(‘touchstart’, handleStart, { passive: false }); canvas.addEventListener(‘touchmove’, handleMove, { passive: false });
        canvas.addEventListener(‘touchend’, handleEnd, { passive: false }); canvas.addEventListener(‘mousedown’, handleStart);
        canvas.addEventListener(‘mousemove’, handleMove); canvas.addEventListener(‘mouseup’, handleEnd);

        // ==========================================
        // COLLISION & LOOP LOGIC
        // ==========================================
        Events.on(engine, ‘collisionStart’, (event) => {
            const pairs = event.pairs;
            for (let i = 0; i < pairs.length; i++) {
                const bodyA = pairs[i].bodyA;
                const bodyB = pairs[i].bodyB;

                if (bodyA.label === ‘orb’ && bodyB.label === ‘orb’) {
                    if (bodyA.orbLevel === bodyB.orbLevel && !bodyA.isMerging && !bodyB.isMerging) {
                        bodyA.isMerging = true; bodyB.isMerging = true;
                        executeMerge(bodyA, bodyB, bodyA.orbLevel);
                    }
                }
                else {
                    const isRainbowA = bodyA.label === ‘rainbow’ && bodyB.label === ‘orb’;
                    const isRainbowB = bodyB.label === ‘rainbow’ && bodyA.label === ‘orb’;
                   
                    if ((isRainbowA || isRainbowB) && !bodyA.isMerging && !bodyB.isMerging) {
                        bodyA.isMerging = true; bodyB.isMerging = true;
                        const normalBody = isRainbowA ? bodyB : bodyA;
                        executeMerge(bodyA, bodyB, normalBody.orbLevel, true);
                    }
                    else if (bodyA.label === ‘rainbow’ && bodyB.label === ‘rainbow’ && !bodyA.isMerging && !bodyB.isMerging) {
                        bodyA.isMerging = true; bodyB.isMerging = true;
                        setTimeout(() => {
                            Composite.remove(world, [bodyA, bodyB]);
                            spawnFloatingText(bodyA.position.x, bodyA.position.y, “POOF!”, “#ffd700”, 30);
                            spawnParticles(bodyA.position.x, bodyA.position.y, “#ffd700”, 20);
                        }, 0);
                    }
                }
            }
        });

        function executeMerge(bodyA, bodyB, level, isRainbowMerge = false) {
            const newLevel = level + 1;
            const midX = (bodyA.position.x + bodyB.position.x) / 2;
            const midY = (bodyA.position.y + bodyB.position.y) / 2;

            setTimeout(() => {
                Composite.remove(world, [bodyA, bodyB]);
               
                const points = orbTypes[level].score;
                updateScore(points);
                triggerShake(level);
               
                const textOutput = isRainbowMerge ? `🌟 +${points}` : `+${points}`;
                spawnFloatingText(midX, midY, textOutput, orbTypes[level].color, Math.min(20 + level * 2, 50));
               
                if (newLevel < orbTypes.length) {
                    spawnParticles(midX, midY, orbTypes[level].color, 10 + (level * 3));
                    if(isRainbowMerge) spawnParticles(midX, midY, “#ffd700”, 15);

                    const newOrb = createOrb(midX, midY, newLevel);
                    Composite.add(world, newOrb);
                } else {
                    spawnFloatingText(midX, midY – 40, “MAX MERGE!”, “#ffd700”, 40);
                    spawnParticles(midX, midY, “#ffd700”, 60);
                    triggerShake(15);
                }
            }, 0);
        }

        Events.on(engine, ‘beforeUpdate’, () => {
            if (isGameOver) return;
            const orbs = Composite.allBodies(world).filter(b => b.label === ‘orb’ || b.label === ‘rainbow’);
            const now = Date.now();
            let isBreachingLine = false;

            for (let orb of orbs) {
                if (now – orb.createdAt > 2000) {
                    const radius = orb.label === ‘rainbow’ ? getRadius(2) : getRadius(orb.orbLevel);
                    if (orb.position.y – radius < DEATH_LINE_Y && Math.abs(orb.velocity.y) < 1.0 && Math.abs(orb.velocity.x) < 1.0) {
                        isBreachingLine = true; break;
                    }
                }
            }

            if (isBreachingLine) {
                if (lineBreachStartTime === null) lineBreachStartTime = now;
                else if (now – lineBreachStartTime > GRACE_PERIOD_MS) triggerGameOver();
            } else {
                lineBreachStartTime = null;
            }
        });

        function resizeCanvas() {
            width = container.clientWidth; height = container.clientHeight; baseR = width * 0.05;
            canvas.width = width * dpr; canvas.height = height * dpr; ctx.scale(dpr, dpr);
            if (!ground) {
                ground = Bodies.rectangle(width / 2, height + 25, 2000, 50, { isStatic: true });
                leftWall = Bodies.rectangle(-25, height / 2, 50, 4000, { isStatic: true });
                rightWall = Bodies.rectangle(width + 25, height / 2, 50, 4000, { isStatic: true });
                Composite.add(world, [ground, leftWall, rightWall]);
            } else {
                Matter.Body.setPosition(ground, { x: width / 2, y: height + 25 });
                Matter.Body.setPosition(leftWall, { x: -25, y: height / 2 });
                Matter.Body.setPosition(rightWall, { x: width + 25, y: height / 2 });
            }
        }
        window.addEventListener(‘resize’, resizeCanvas); resizeCanvas();

        function renderLoop() {
            ctx.clearRect(0, 0, width, height);
            ctx.save();
           
            if (shakeFrames > 0 && !isGameOver) {
                const sx = (Math.random() – 0.5) * shakeIntensity;
                const sy = (Math.random() – 0.5) * shakeIntensity;
                ctx.translate(sx, sy);
                shakeFrames–;
            }

            if (lineBreachStartTime && !isGameOver) {
                const timeLeft = Math.max(0, GRACE_PERIOD_MS – (Date.now() – lineBreachStartTime));
                const secondsLeft = (timeLeft / 1000).toFixed(1);
                const pulse = Math.abs(Math.sin(Date.now() / 150));
                ctx.fillStyle = `rgba(255, 0, 0, ${0.1 + (pulse * 0.15)})`; ctx.fillRect(0, 0, width, height);
                ctx.fillStyle = “rgba(211, 47, 47, 0.9)”; ctx.font = “bold 28px Arial”; ctx.textAlign = “center”;
                ctx.fillText(`⚠️ ${secondsLeft}s`, width / 2, DEATH_LINE_Y – 20);
               
                ctx.beginPath(); ctx.setLineDash([10, 10]); ctx.moveTo(0, DEATH_LINE_Y); ctx.lineTo(width, DEATH_LINE_Y);
                ctx.strokeStyle = `rgba(255, 0, 0, ${0.5 + pulse})`; ctx.lineWidth = 6; ctx.stroke(); ctx.setLineDash([]);
            } else {
                ctx.beginPath(); ctx.setLineDash([10, 10]); ctx.moveTo(0, DEATH_LINE_Y); ctx.lineTo(width, DEATH_LINE_Y);
                ctx.strokeStyle = ‘rgba(244, 67, 54, 0.6)’; ctx.lineWidth = 4; ctx.stroke(); ctx.setLineDash([]);
            }

            if (isSliceActive) {
                ctx.fillStyle = “rgba(233, 30, 99, 0.1)”; ctx.fillRect(0, 0, width, height);
                ctx.fillStyle = “rgba(194, 24, 91, 0.8)”; ctx.font = “bold 24px Arial”; ctx.textAlign = “center”;
                ctx.fillText(“πŸ”ͺ TAP A FRUIT TO SLICE IT”, width / 2, height / 2);
            }

            if (isAiming && canDrop && !isGameOver && !isSliceActive) {
                const isR = isHoldingRainbow;
                const radius = isR ? getRadius(2) : getRadius(nextDropLevel);
                const safeX = getSafeX(aimX, radius);
                const emojiToDraw = isR ? ‘🌟’ : orbTypes[nextDropLevel].emoji;
               
                ctx.beginPath(); ctx.setLineDash([8, 8]); ctx.moveTo(safeX, 40); ctx.lineTo(safeX, height);
                ctx.strokeStyle = isAutoPlay ? ‘rgba(33, 150, 243, 0.6)’ : ‘rgba(255, 179, 0, 0.5)’;
                ctx.lineWidth = 2; ctx.stroke(); ctx.setLineDash([]);
               
                ctx.save(); ctx.globalAlpha = 0.5; const fontSize = radius * 1.8; ctx.font = `${fontSize}px Arial`;
                ctx.textAlign = ‘center’; ctx.textBaseline = ‘middle’; ctx.fillText(emojiToDraw, safeX, 40 + (radius * 0.1)); ctx.restore();
            }

            const bodies = Composite.allBodies(world).filter(b => b.label === ‘orb’ || b.label === ‘rainbow’);
            for (let body of bodies) {
                const isR = body.label === ‘rainbow’;
                const type = isR ? { emoji: ‘🌟’ } : orbTypes[body.orbLevel];
                const radius = isR ? getRadius(2) : getRadius(body.orbLevel);
               
                ctx.save(); ctx.translate(body.position.x, body.position.y); ctx.rotate(body.angle);
               
                if (isR) {
                    ctx.beginPath(); ctx.arc(0, 0, radius, 0, Math.PI * 2);
                    ctx.fillStyle = “rgba(255, 255, 255, 0.5)”; ctx.fill();
                }

                const fontSize = radius * 1.8; ctx.font = `${fontSize}px Arial`; ctx.textAlign = ‘center’;
                ctx.textBaseline = ‘middle’; ctx.fillText(type.emoji, 0, radius * 0.1); ctx.restore();
            }

            for(let i = particles.length – 1; i >= 0; i–) {
                let p = particles[i];
                p.x += p.vx; p.y += p.vy; p.vy += 0.3;
                p.life -= 0.02;
                ctx.globalAlpha = Math.max(0, p.life);
                ctx.fillStyle = p.color;
                ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill();
                if(p.life <= 0) particles.splice(i, 1);
            }
            ctx.globalAlpha = 1.0;

            for(let i = floatingTexts.length – 1; i >= 0; i–) {
                let t = floatingTexts[i];
                t.y += t.vy; t.life -= 0.015;
                ctx.globalAlpha = Math.max(0, t.life);
                ctx.fillStyle = t.color;
                ctx.font = `900 ${t.size}px Arial`;
                ctx.textAlign = “center”;
                ctx.lineWidth = 4;
                ctx.strokeStyle = “white”;
                ctx.strokeText(t.text, t.x, t.y);
                ctx.fillText(t.text, t.x, t.y);
                if(t.life <= 0) floatingTexts.splice(i, 1);
            }
            ctx.globalAlpha = 1.0;

            ctx.restore();
            requestAnimationFrame(renderLoop);
        }

        Runner.run(Runner.create(), engine);
        renderLoop();
    </script>
</body>
</html>