Drawaria.online Canvas Effects

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);
})();