Drawaria.online Canvas Effects

An ADVANCED effects menu for Drawaria.online Canvas, with many options and libraries!

// ==UserScript==
// @name         Drawaria.online Canvas Effects
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  An ADVANCED effects menu for Drawaria.online Canvas, with many options and libraries!
// @author       YouTubeDrawaria
// @match        https://drawaria.online/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// @grant        none
// @license      MIT
// @require      https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/build/matter.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/FontLoader.js
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // ---  LIBRARY SETUP  ---

    // Three.js: Scene, camera, renderer, and a container for 3D objects.
    let scene, camera, renderer, threeContainer;
    let threeObjects = []; // Array to keep track of Three.js objects

    // GSAP: Global timeline to control animations (optional, but useful).
    // const tl = gsap.timeline({ paused: true }); // A global timeline is not used in this example

    // Matter.js: Physics engine.
    let engine, world;
    let canvas; // Reference to the Drawaria canvas.


    // ---  UTILITY FUNCTIONS (MODIFIED) ---

    function initThreeJS() {
        if (scene) return; // Avoid re-initializing if it already exists.

        if (!threeContainer) { // Check if threeContainer exists before trying to append
            threeContainer = document.createElement('div');
            threeContainer.style.cssText = `
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                pointer-events: none; /*  VERY IMPORTANT: Allows clicks to "pass through" to the Drawaria canvas. */
                z-index: 500; /*  Above the Drawaria canvas, but below the menu. */
            `;
            document.body.appendChild(threeContainer);
        }


        if (!scene) {
            scene = new THREE.Scene();
        }
        if (!camera) {
            camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
            camera.position.z = 5; // Adjust as needed.
        }
        if (!renderer) {
            renderer = new THREE.WebGLRenderer({ alpha: true }); //  alpha: true is CRUCIAL for transparency.
            renderer.setSize(window.innerWidth, window.innerHeight);
            threeContainer.appendChild(renderer.domElement);
        }


        // Basic ambient light (so objects are not completely black).
        if (!scene.getObjectByName('ambientLight')) { // Check if ambient light already exists
            const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
            ambientLight.name = 'ambientLight'; // Name it for easy checking
            scene.add(ambientLight);
        }

         // Directional light.
         if (!scene.getObjectByName('directionalLight')) { // Check if directional light already exists
            const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
            directionalLight.position.set(1, 1, 1); // Direction of light.
            directionalLight.name = 'directionalLight'; // Name it
            scene.add(directionalLight);
        }


        // Resize Three.js when the window is resized.
        window.addEventListener('resize', onWindowResize, false);

        animateThreeJS(); // Start the rendering loop.
    }

    function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    }

    function animateThreeJS() {
        requestAnimationFrame(animateThreeJS);
        // Basic rotation of *all* Three.js objects (to demonstrate it's working).
        threeObjects.forEach(obj => {
            obj.rotation.x += 0.01;
            obj.rotation.y += 0.01;
        });

        renderer.render(scene, camera);
    }

    function initMatterJS() {
        if (engine) return;

        engine = Matter.Engine.create();
        world = engine.world;

         // Gravity settings (you can modify)
         engine.world.gravity.y = 1; // Gravity downwards

        Matter.Engine.run(engine);
    }

    function getCanvas(){
        if(!canvas) {
            canvas = document.querySelector('canvas');
        }
        return canvas;
    }



    // ---  EFFECTS (IMPLEMENTATIONS) ---
     function createText3D(text, options = {}) {
        initThreeJS(); // Make sure Three.js is initialized.
        const loader = new THREE.FontLoader();

        loader.load('https://threejs.org/examples/fonts/helvetiker_regular.typeface.json', (font) => {
            const textGeometry = new THREE.TextGeometry(text, {
                font: font,
                size: options.size || 0.5,
                height: options.height || 0.1,
                curveSegments: options.curveSegments || 12,
                bevelEnabled: options.bevelEnabled || false,
                bevelThickness: options.bevelThickness || 0.03,
                bevelSize: options.bevelSize || 0.02,
                bevelOffset: options.bevelOffset || 0,
                bevelSegments: options.bevelSegments || 5
            });

            const textMaterial = new THREE.MeshPhongMaterial({ color: options.color || 0xff0000 });
            const textMesh = new THREE.Mesh(textGeometry, textMaterial);

            // Position, rotation, etc., using options.
            textMesh.position.x = options.x || 0;
            textMesh.position.y = options.y || 0;
            textMesh.position.z = options.z || 0;

            if (options.rotationX) textMesh.rotation.x = options.rotationX;
            if (options.rotationY) textMesh.rotation.y = options.rotationY;
            if (options.rotationZ) textMesh.rotation.z = options.rotationZ;


            scene.add(textMesh);
            threeObjects.push(textMesh); // Add to the array for global animation.

            // Animation with GSAP (optional, but recommended).
            if(options.animate){
                 gsap.to(textMesh.position, {
                    duration: options.duration || 2,
                    y: options.toY || 2,
                    repeat: options.repeat || -1,
                    yoyo: options.yoyo !== undefined ? options.yoyo : true, // Control yoyo with options.
                    ease: options.ease || "power2.inOut"
                 });
            }
        });
    }


    let movingTexts = []; // Array to store references to moving texts
    let movingGraphicsInterval; // For moving graphics animation interval
    let graphics3DObjects = []; // Array for 3D graphics objects

    function createMovingText(text, options = {}) {
        const textElement = document.createElement('div');
        textElement.textContent = text;
        textElement.style.cssText = `
            position: absolute;
            top: ${options.y || '50%'};
            left: ${options.x || '50%'};
            transform: translate(-50%, -50%);
            color: ${options.color || 'white'};
            font-size: ${options.fontSize || '24px'};
            pointer-events: none; /*  Important so that it doesn't interfere with drawing. */
            z-index: 500;
        `;
        document.body.appendChild(textElement);

        // Store a reference to the GSAP timeline to be able to control it later.
        const tl = gsap.to(textElement, {
            duration: options.duration || 3,
            x: options.toX || '+=200',  // Move 200px to the right (relative).
            y: options.toY || '+=0', // You can move in Y as well.
            rotation: options.rotation || 360,
            repeat: options.repeat || -1, // Repeat infinitely.
            yoyo: options.yoyo !== undefined ? options.yoyo : true, // Control yoyo with options.
            ease: options.ease || "power1.inOut",
            onComplete: () => {
                if (options.removeOnComplete) {
                    textElement.remove(); // Remove the element on completion (optional).
                }
                 // Remove the reference from the array when the animation completes.
                movingTexts = movingTexts.filter(item => item.element !== textElement);
            }
        });

        // Save the reference to the element and the timeline.
        movingTexts.push({ element: textElement, timeline: tl });
    }


    function createParticleExplosion(options = {}) {
      initThreeJS(); // Make sure Three.js is ready.
      initMatterJS();

      const numParticles = options.numParticles || 50;
      const particleSize = options.size || 0.05;
      const color = options.color || 0xffffff;
      const duration = options.duration || 2;
      const spread = options.spread || 1; // Controls how much particles spread out.

        const particles = [];

       for (let i = 0; i < numParticles; i++) {
        // Geometry and material for each particle (Three.js).
          const geometry = new THREE.SphereGeometry(particleSize, 8, 8);
          const material = new THREE.MeshBasicMaterial({ color: color });
          const particle = new THREE.Mesh(geometry, material);

          // Initial position (center of the explosion).
           particle.position.set(
                (options.x || 0),
                (options.y || 0),
                (options.z || 0)
           );

          scene.add(particle);
          threeObjects.push(particle);  // For global rotation in animateThreeJS.
          particles.push(particle);

        // Matter.js
         const body = Matter.Bodies.circle(
            (options.x || 0) *100 , // Adjust the scale to the Matter.js world
            -(options.y || 0)* 100, // Adjust the scale to the Matter.js world
             particleSize * 100,
             {restitution: 0.6, friction: 0.1}
         );

          // Apply an initial force (explosion).
          const force = (options.force || 0.5) * spread;
          const angle = Math.random() * Math.PI * 2;
          Matter.Body.applyForce(body, body.position, {
              x: Math.cos(angle) * force,
              y: Math.sin(angle) * force
          });

           Matter.World.add(world, body);


           // Animation with GSAP to fade out and scale down.
            gsap.to(particle.scale, {
                duration: duration,
                x: 0,
                y: 0,
                z: 0,
                ease: "power1.out",
               onUpdate: () => {
                // Synchronize the Three.js position with Matter.js.
                particle.position.x = body.position.x / 100;
                particle.position.y = -body.position.y / 100;
                particle.position.z = (options.z || 0);
                },
                onComplete: () => {
                    // Clean up the particle from Three.js and Matter.js.
                    scene.remove(particle);
                     threeObjects.splice(threeObjects.indexOf(particle), 1);
                    Matter.World.remove(world, body);

                }
            });
      }
    }


    function applyFilterEffect(effectId, isActive) {
        const canvas = getCanvas(); //Get the canvas safely
        if (!canvas) return;

        if (isActive) {
            canvas.classList.add(effectId); // Add the class
        } else {
            canvas.classList.remove(effectId); // Remove the class
        }
    }

    // --- Placeholder Effects (Implement basic alerts or console logs for unimplemented effects) ---
    function applyPlaceholderEffect(effectId, isActive) {
        if (isActive) {
            console.log(`Effect ${effectId} ACTIVATED (Placeholder)`); // Or alert(`Efecto ${effectId} ACTIVATED (Placeholder)`);
            const canvas = getCanvas();
            if(canvas) canvas.classList.add('placeholder-effect-indicator'); // Add visual indicator
        } else {
            console.log(`Effect ${effectId} DEACTIVATED (Placeholder)`); // Or alert(`Efecto ${effectId} DESACTIVADO (Placeholder)`);
            const canvas = getCanvas();
            if(canvas) canvas.classList.remove('placeholder-effect-indicator'); // Remove visual indicator
        }
    }

    function createMovingGraphics(isActive) {
        if (isActive) {
            initThreeJS();
            const geometry = new THREE.BoxGeometry(1, 1, 1);
            const material = new THREE.MeshBasicMaterial({ color: 0x00ffff });
            const cube = new THREE.Mesh(geometry, material);
            scene.add(cube);
            threeObjects.push(cube);
            graphics3DObjects.push(cube); // Add to separate array for graphics

            // Basic animation using setInterval (can be replaced with GSAP for smoother animation)
            movingGraphicsInterval = setInterval(() => {
                cube.position.x = Math.sin(Date.now() * 0.001) * 2; // Move horizontally
                cube.position.y = Math.cos(Date.now() * 0.001) * 1.5; // Move vertically
            }, 16); // Roughly 60 FPS
        } else {
            clearInterval(movingGraphicsInterval);
            graphics3DObjects.forEach(obj => {
                scene.remove(obj);
                threeObjects.splice(threeObjects.indexOf(obj), 1);
            });
            graphics3DObjects = [];
        }
    }

    function createGraphics3D(isActive) {
        if (isActive) {
            initThreeJS();
            const geometry = new THREE.SphereGeometry(1, 32, 32);
            const material = new THREE.MeshStandardMaterial({ color: 0xff8800, metalness: 0.5, roughness: 0.1 }); // More interesting material
            const sphere = new THREE.Mesh(geometry, material);
            scene.add(sphere);
            threeObjects.push(sphere);
            graphics3DObjects.push(sphere); // Add to graphics objects array

            sphere.position.set(2, 0, 0); // Position it to the side
        } else {
            graphics3DObjects.forEach(obj => {
                scene.remove(obj);
                threeObjects.splice(threeObjects.indexOf(obj), 1);
            });
            graphics3DObjects = [];
        }
    }

    function createCharacterAnimation(isActive) {
        applyPlaceholderEffect("character-animation", isActive); // Placeholder for now. Complex to implement without assets/models
    }

    function createObjectAnimation(isActive) {
        applyPlaceholderEffect("object-animation", isActive); // Placeholder, could be animations on existing 3D objects
    }

    function createTextAnimation(isActive) {
        applyPlaceholderEffect("text-animation", isActive); // Placeholder, could be more complex text animations than moving text
    }

    function createGraphicsAnimation(isActive) {
        applyPlaceholderEffect("graphics-animation", isActive); // Placeholder, could be animations of 3D graphics objects
    }

    function applyARGeneral(isActive) {
        applyPlaceholderEffect("ar-general", isActive); // AR General Placeholder - very complex, needs device access and tracking
    }

    function applyARText(isActive) {
        applyPlaceholderEffect("ar-text", isActive); // AR Text Placeholder - requires AR setup
    }

    function applyARGraphics(isActive) {
        applyPlaceholderEffect("ar-graphics", isActive); // AR Graphics Placeholder - requires AR setup
    }


     // --- Menu Structure (HTML) ---
    const menuHTML = `
    <div id="effects-menu" style="position: fixed; top: 10px; left: 10px; z-index: 1000; background-color: #222; color: #fff; border-radius: 5px; padding: 10px; box-shadow: 0 2px 5px rgba(0,0,0,0.5); display: none;">
        <div id="menu-header" style="cursor: move; padding-bottom: 5px; border-bottom: 1px solid #444; margin-bottom: 5px; user-select: none;">
            <span style="font-weight: bold;">Enhanced Effects</span>
            <button id="close-menu" style="float: right; background: none; border: none; color: #fff; font-size: 1.2em; cursor: pointer;">×</button>
        </div>
        <div id="menu-content" style="overflow-y: auto; max-height: 80vh; padding-right: 5px;">  <!-- Container with scroll -->
            <div class="category">
                <details>
                    <summary style="cursor: pointer; font-weight: bold; margin-bottom: 5px; outline: none;">Filters and Adjustments</summary>
                    ${createEffectGroup("Filters", [
                        { name: "Blur", id: "blur" },
                        { name: "Focus", id: "focus" },
                        { name: "Color Change", id: "color-change" },
                        { name: "Brightness", id: "brightness" },
                        { name: "Contrast", id: "contrast" },
                        { name: "Saturation", id: "saturation" },
                        { name: "Temperature", id: "temperature" },
                        { name: "Fog Adjust", id: "fog-adjust" }
                    ])}
                </details>
            </div>

              <div class="category">
                <details>
                    <summary style="cursor: pointer; font-weight: bold; margin-bottom: 5px; outline: none;">Weather Effects</summary>
                    ${createEffectGroup("Weather", [
                         { name: "Snow", id: "snow-change" },
                         { name: "Rain", id: "rain-change" },
                         { name: "Wind", id: "wind-change"},
                         { name: "Fog", id: "fog-change" }
                    ])}
                </details>
            </div>

            <div class="category">
                <details>
                    <summary style="cursor: pointer; font-weight: bold; margin-bottom: 5px; outline: none;">Elemental Effects</summary>
                    ${createEffectGroup("Elemental", [
                        {name: "Water", id: "water-change"},
                        {name: "Fire", id: "fire-change"},
                        {name: "Explosion", id:"explosion-change"},
                        { name: "Impact", id: "impact-change" },
                        { name: "Shock", id: "shock-change" }
                    ])}

                </details>
            </div>

             <div class="category">
                <details>
                    <summary style="cursor: pointer; font-weight: bold; margin-bottom: 5px; outline: none;">Explosion Effects (Combined)</summary>
                   ${createEffectGroup("Combined Explosions", [
                        { name: "Water", id: "explosion-water" },
                        { name: "Fire", id: "explosion-fire" },
                        { name: "Fog", id: "explosion-fog" },
                        { name: "Snow", id: "explosion-snow" },
                        { name: "Rain", id: "explosion-rain" },
                        { name: "Wind", id: "explosion-wind" }
                   ])}
                </details>
            </div>

            <div class="category">
                <details>
                    <summary style="cursor: pointer; font-weight: bold; margin-bottom: 5px; outline: none;">Light and Shadow Effects</summary>
                      ${createEffectGroup("Light and Shadow", [
                        {name: "Light Effects", id:"light-effects"},
                        {name: "Shadow Effects", id: "shadow-effects"}
                    ])}

                </details>
            </div>
              <div class="category">
                <details>
                  <summary style="cursor: pointer; font-weight: bold; margin-bottom: 5px; outline: none;">Text and Graphics</summary>

                    ${createEffectGroup("Text and Graphics", [
                        {name: "Moving Text", id: "moving-text"},
                        {name: "Moving Graphics", id: "moving-graphics"},
                        {name: "3D Text", id: "text-3d"},
                        {name: "3D Graphics", id: "graphics-3d"}
                    ])}
                </details>
            </div>

            <div class="category">
                <details>
                  <summary style="cursor: pointer; font-weight: bold; margin-bottom: 5px; outline: none;">Animations (Advanced)</summary>
                    ${createEffectGroup("Animations", [
                        {name: "Character Animation", id: "character-animation"},
                        {name: "Object Animation", id: "object-animation"},
                        {name: "Text Animation", id: "text-animation"},
                        {name: "Graphics Animation", id: "graphics-animation"}
                    ])}
                </details>
            </div>

            <div class="category">
               <details>
                    <summary style="cursor: pointer; font-weight: bold; margin-bottom: 5px; outline: none;">Augmented Reality (Experimental)</summary>
                    ${createEffectGroup("Augmented Reality", [
                        {name: "AR General", id: "ar-general"},
                        {name: "AR Text", id:"ar-text"},
                        {name: "AR Graphics", id: "ar-graphics"}
                    ])}
              </details>
            </div>

        </div>
    </div>`;



    // --- Utility Functions ---

    // Creates a group of checkboxes for an effects category.
    function createEffectGroup(categoryName, effects) {
        let groupHTML = `<div style="padding-left: 10px;">`;
        effects.forEach(effect => {
            groupHTML += `
                <label style="display: block; margin-bottom: 2px;">
                    <input type="checkbox" id="${effect.id}-checkbox" class="effect-toggle" data-effect="${effect.id}">
                    ${effect.name}
                </label>`;
        });
        groupHTML += `</div>`;
        return groupHTML;
    }

    // --- INITIALIZATION (MODIFIED) ---

    document.body.insertAdjacentHTML('beforeend', menuHTML);
    const menu = document.getElementById('effects-menu');
    const closeButton = document.getElementById('close-menu');

    const toggleButton = document.createElement('button');
    toggleButton.id = "menu-toggle";
    toggleButton.innerHTML = '☰';
    toggleButton.style.cssText = `
        position: fixed;
        top: 10px;
        left: 10px;
        z-index: 1001;
        background-color: #444;
        color: #fff;
        border: none;
        border-radius: 3px;
        padding: 5px 8px;
        cursor: pointer;
    `;
    document.body.appendChild(toggleButton);

    toggleButton.addEventListener('click', () => {
       menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
    });

    closeButton.addEventListener('click', () => {
        menu.style.display = 'none';
    });

     // ---  EVENT HANDLING (MODIFIED) ---

    menu.addEventListener('change', (event) => {
        if (event.target.classList.contains('effect-toggle')) {
            const effectId = event.target.dataset.effect;
            const isActive = event.target.checked;

             // CSS Filters (simple).
            if ([
                "blur", "focus", "color-change", "brightness", "contrast",
                "saturation", "temperature", "fog-adjust", "snow-change",
                "rain-change", "wind-change", "fog-change", "water-change",
                "fire-change",  "impact-change", "shock-change",
                "explosion-water", "explosion-fire", "explosion-fog",
                "explosion-snow", "explosion-rain", "explosion-wind",
                "light-effects", "shadow-effects"
            ].includes(effectId)) {
                applyFilterEffect(effectId, isActive);
            }
            else if (effectId === "moving-text") {
                if (isActive) {
                   // Example of use. You can customize this from the menu.
                    createMovingText("Drawaria!", {
                        x: '50%',
                        y: '20%',
                        toX: '+=300',
                        duration: 5,
                        repeat: -1,
                        yoyo: true,
                        color: 'orange',
                         fontSize: '36px'
                    });
                } else {
                     // Stop all moving text animations.
                    movingTexts.forEach(item => {
                        item.timeline.kill(); // Stop the animation.
                        item.element.remove(); // Remove the element from the DOM.
                    });
                    movingTexts = []; // Empty the array.
                }
            }
            else if (effectId === "text-3d") {
                if (isActive) {
                    createText3D("3D Effect", {
                        x: -1,
                        y: 1,
                        z: 0,
                        size: 0.8,
                        color: 0x00ff00,
                        animate: true,
                        duration: 3,
                        toY: 2,
                        yoyo: false,
                        rotationX: 0.5,
                        rotationY: 0.8
                    });
                } else {
                    // Clear 3D objects.
                    threeObjects.forEach(obj => {
                       scene.remove(obj);
                    });
                    threeObjects = []; // Empty the array.
                }
            }

            else if(effectId === "explosion-change"){
                if(isActive){
                    createParticleExplosion({
                       x: 0,  // Coordinates relative to the canvas (0, 0 is the center).
                       y: 0,
                       numParticles: 100,
                       size: 0.1,
                       color: 0xffa500, // Orange.
                       duration: 1.5,
                       spread: 1.5,
                       force: 0.8
                    });
                } //No else, since the explosion self-destructs
            }
            // Combined explosions - reuse explosion effect with different colors if needed, or keep the same effect for simplicity
            else if (["explosion-water", "explosion-fire", "explosion-fog", "explosion-snow", "explosion-rain", "explosion-wind"].includes(effectId)) {
                if (isActive) {
                    let color;
                    switch (effectId) {
                        case "explosion-water": color = 0x00aaff; break; // Blue
                        case "explosion-fire": color = 0xff4500; break; // Red-Orange
                        case "explosion-fog": color = 0xdddddd; break; // Light Grey
                        case "explosion-snow": color = 0xffffff; break; // White
                        case "explosion-rain": color = 0x87cefa; break; // Light Blue
                        case "explosion-wind": color = 0xadff2f; break; // Green-Yellow
                        default: color = 0xffa500; // Default Orange
                    }
                    createParticleExplosion({
                        x: 0,
                        y: 0,
                        numParticles: 100,
                        size: 0.1,
                        color: color,
                        duration: 1.5,
                        spread: 1.5,
                        force: 0.8
                    });
                }
            }
            // Placeholder effects implementations:
            else if (effectId === "focus") { applyPlaceholderEffect("focus", isActive); } // Focus - can be complex, placeholder for now
            else if (effectId === "wind-change") { applyPlaceholderEffect("wind-change", isActive); } // Wind Change - CSS is limited, placeholder
            else if (effectId === "water-change") { applyPlaceholderEffect("water-change", isActive); } // Water Change - complex, placeholder
            else if (effectId === "fire-change") { applyPlaceholderEffect("fire-change", isActive); } // Fire Change - complex, placeholder
            else if (effectId === "impact-change") { applyPlaceholderEffect("impact-change", isActive); } // Impact - placeholder
            else if (effectId === "shock-change") { applyPlaceholderEffect("shock-change", isActive); } // Shock - placeholder
            else if (effectId === "light-effects") { applyPlaceholderEffect("light-effects", isActive); } // Light Effects - CSS limited, placeholder
            else if (effectId === "shadow-effects") { applyPlaceholderEffect("shadow-effects", isActive); } // Shadow Effects - CSS limited, placeholder
            else if (effectId === "moving-graphics") { createMovingGraphics(isActive); }
            else if (effectId === "graphics-3d") { createGraphics3D(isActive); }
            else if (effectId === "character-animation") { createCharacterAnimation(isActive); }
            else if (effectId === "object-animation") { createObjectAnimation(isActive); }
            else if (effectId === "text-animation") { createTextAnimation(isActive); }
            else if (effectId === "graphics-animation") { createGraphicsAnimation(isActive); }
            else if (effectId === "ar-general") { applyARGeneral(isActive); }
            else if (effectId === "ar-text") { applyARText(isActive); }
            else if (effectId === "ar-graphics") { applyARGraphics(isActive); }


        }
    });



    let isDragging = false;
    let dragOffsetX = 0;
    let dragOffsetY = 0;

    const menuHeader = document.getElementById('menu-header');

    menuHeader.addEventListener('mousedown', (event) => {
        isDragging = true;
        dragOffsetX = event.clientX - menu.offsetLeft;
        dragOffsetY = event.clientY - menu.offsetTop;
    });

    document.addEventListener('mousemove', (event) => {
        if (isDragging) {
            menu.style.left = (event.clientX - dragOffsetX) + 'px';
            menu.style.top = (event.clientY - dragOffsetY) + 'px';
        }
    });

    document.addEventListener('mouseup', () => {
        isDragging = false;
    });

    // --- CSS Styles (within the script) ---
     const style = document.createElement('style');
    style.textContent = `
        /* General styles for checkboxes (optional, but improves appearance) */
        .effect-toggle {
            margin-right: 5px;
        }
        /* Classes for effects. This is where the "magic" happens! (Or at least, the simulation) */
        .blur { filter: blur(5px); }
        .focus {  filter: contrast(120%) brightness(90%); /* Basic sharpen-like effect using contrast and brightness */ } /* You could use a sharpen filter here, but it's more complex */
        .color-change { filter: hue-rotate(90deg); } /* Example: Rotates colors */
        .brightness { filter: brightness(150%); }
        .contrast { filter: contrast(150%); }
        .saturation { filter: saturate(200%); }
        .temperature { filter: sepia(60%); }  /* Sepia gives a "warm" effect */
        .fog-adjust { filter: grayscale(50%) brightness(120%) opacity: 0.8; } /* Simulates basic fog */
        /*Weather*/
        /*Snow example*/
        .snow-change {
            animation: snowfall 10s linear infinite;
            background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAAXNSR0IArs4c6QAAAHVJREFUKFNjZCARMILX/7+DLIAANSMRxgZGRkeMWB4wMDD8p8FjFQMDP8YmBga/GOjrwMC/4Y8KCBg+/MFRQvP/D4LRC4jEBgMDAx/GRgYGT4xMDD8Z2Bg+A8jIYgMDAx/GRgY/jEwMPxnYGD4w5ABbBYWlFvCNAAAAABJRU5ErkJggg==");
            background-repeat: repeat; /* Important to cover the canvas */
        }
          @keyframes snowfall {
            0% {
              background-position: 0 0;
            }
            100% {
               background-position: 100px 400px; /* Adjust for speed/direction */
             }
          }
        /*Rain*/
        .rain-change{
            animation: rain 0.2s linear infinite;
            background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAICAYAAADeM14FAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAABNJREFUKFNjZCARLID/h3h8YPhPBQMDPxpDphAANgcEDpL1NqIAAAAASUVORK5CYII=");
            background-repeat: repeat;
        }
         @keyframes rain {
              from {
                background-position: 0 0;
              }
              to {
                background-position: 20px 100vh; /* Adjust speed */
              }
         }
        /*Wind*/
         .wind-change {
            /* There is no direct CSS filter for wind. The best simulation would be with animations and transformations */
            /* You could use something like this with JavaScript and GSAP: */
            /* gsap.to(canvas, {duration: 1, x: "+=10", yoyo: true, repeat: -1}); */
            /* But that requires GSAP (or another animation library). */
              filter: blur(1px); /* A slight blur can give a *little* sense of movement */

          }
         /*Fog*/
          .fog-change {
            filter: blur(3px) opacity(0.7); /* A stronger blur and reduced opacity */
           }
        /*Water*/
         .water-change {
            /* Water is *extremely* difficult to simulate with just CSS. The best would be to use a WebGL shader. */
             filter:  contrast(110%) brightness(110%); /* Very basic adjustments */
          }

        /*Fire*/
        .fire-change {
          /* Similar to water, realistic fire requires shaders or complex animations. */
          filter: contrast(150%) brightness(120%) hue-rotate(-20deg); /* A basic attempt to simulate warm colors. */
        }
        /*Explosion*/
        .explosion-change {
          /* Without an animation or library, this is VERY limited. */
          filter: brightness(200%) contrast(150%); /* A very basic "flash" */
        }
        /*Impact and Shock: Similar to explosion - need real animation.*/
          .impact-change, .shock-change {
            transform: scale(1.1);  /* A slight increase in size (temporary, ideally animated) */
          }
       /*Combined explosions*/
        .explosion-water, .explosion-fire, .explosion-fog,
        .explosion-snow, .explosion-rain, .explosion-wind {
          filter: brightness(200%) contrast(150%);  /* Common "flash" base */
        }
         /*Light and shadow effects*/
          .light-effects {
            /* VERY basic simulation with an overlaid gradient */
            /* This should be done with JavaScript for real control. */
            background-image: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.3), rgba(0,0,0,0));
          }
          .shadow-effects {
            /* VERY basic simulation with an overlaid gradient */
            background-image: radial-gradient(circle at 70% 70%, rgba(0,0,0,0.4), rgba(0,0,0,0));
            /* Again, JavaScript is needed for something realistic. */
          }
           /* Placeholder effect indicator style */
           .placeholder-effect-indicator {
               border: 3px dashed orange; /* Visual cue for placeholder effects */
           }
         .moving-graphics, .graphics-3d, .character-animation, .object-animation, .text-animation, .graphics-animation, .ar-general, .ar-text, .ar-graphics {
              /* Placeholder: These WILL NOT WORK without external libraries. */
              /* border: 1px dashed red; Removed, using placeholder indicator class instead */
         }
    `;

    document.head.appendChild(style);
})();