Drawaria Advanced Particles Generator With Menu

Advanced particle generator with draggable menu for Drawaria, using multiple techniques

// ==UserScript==
// @name         Drawaria Advanced Particles Generator With Menu
// @namespace    http://tampermonkey.net/
// @version      2025-02-23
// @description  Advanced particle generator with draggable menu for Drawaria, using multiple techniques
// @author       YouTubeDrawaria
// @match        https://drawaria.online/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// @grant        none
// @license      MIT
// @require      https://unpkg.com/[email protected]/dist/draggabilly.pkgd.min.js
// @require      https://cdn.jsdelivr.net/particles.js/2.0.0/particles.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js
// ==/UserScript==

(function() {
    'use strict';

    const styles = `
        .particle-menu {
            position: fixed;
            top: 20px;
            left: 20px;
            background: rgba(40, 44, 52, 0.9);
            border-radius: 10px;
            padding: 15px;
            z-index: 10000;
            min-width: 250px; /* Increased width */
            box-shadow: 0 0 15px rgba(0,0,0,0.3);
            cursor: move;
            user-select: none; /* Prevent text selection */
        }
        .particle-menu h3 {
            color: #fff;
            margin: 0 0 15px 0; /* Increased bottom margin */
            text-align: center;
            cursor: move;
            font-size: 1.2em; /* Larger font */
        }
        .particle-button {
            display: block;
            width: 100%;
            margin: 8px 0; /* Increased vertical margin */
            padding: 10px; /* Increased padding */
            border: none;
            border-radius: 5px;
            background: linear-gradient(45deg, #4CAF50, #45a049);
            color: white;
            cursor: pointer;
            transition: 0.3s;
            font-size: 1em; /* Standardized font size */
        }
        .particle-button:hover {
            transform: scale(1.05);
            background: linear-gradient(45deg, #45a049, #4CAF50);
        }
        #particles-js, #canvas-particles, #threejs-particles {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            z-index: 1;
            pointer-events: none;
        }
        #threejs-particles {
            z-index: 2; /* Ensure Three.js is above particles.js */
        }

    `;

    const styleSheet = document.createElement("style");
    styleSheet.innerText = styles;
    document.head.appendChild(styleSheet);

    // --- Particle Containers ---
    const particleContainer = document.createElement('div');
    particleContainer.id = 'particles-js';
    document.body.appendChild(particleContainer);

    const canvasContainer = document.createElement('canvas');
    canvasContainer.id = 'canvas-particles';
    document.body.appendChild(canvasContainer);

    const threejsContainer = document.createElement('div');
    threejsContainer.id = 'threejs-particles';
    document.body.appendChild(threejsContainer);

    // --- Menu ---
    const menu = document.createElement('div');
    menu.className = 'particle-menu';
    menu.innerHTML = `
        <h3>Particle Generator</h3>
        <button class="particle-button" data-type="constellation">✨ Constellation</button>
        <button class="particle-button" data-type="dna">🧬 DNA Helix</button>
        <button class="particle-button" data-type="fireflies">🌟 Fireflies</button>
        <button class="particle-button" data-type="rain">🌧️ Rain</button>
        <button class="particle-button" data-type="atoms">⚛️ Atoms</button>
        <button class="particle-button" data-type="bubbles">🫧 Magic Bubbles</button>
        <button class="particle-button" data-type="matrix">👾 Matrix Rain</button>
        <button class="particle-button" data-type="clear">🧹 Clear</button>
    `;
    document.body.appendChild(menu);

    new Draggabilly(menu, { handle: 'h3' });

    // --- Particle.js Configs (Improved) ---
    const particleConfigs = {
      constellation: {
          particles: {
            number: { value: 150, density: { enable: true, value_area: 800 } },
            color: { value: "#ffffff" },
            shape: { type: "star", polygon: { nb_sides: 5 } },
            opacity: { value: 0.7, random: true, anim: { enable: false, speed: 1, opacity_min: 0.1, sync: false } },
            size: { value: 3, random: true, anim: { enable: true, speed: 4, size_min: 0.1, sync: false } },
            line_linked: { enable: true, distance: 150, color: "#ffffff", opacity: 0.4, width: 1 },
            move: { enable: true, speed: 1, direction: "none", random: false, straight: false, out_mode: "out", bounce: false }
          },
          interactivity: { detect_on: "canvas", events: { onhover: { enable: false }, onclick: { enable: false }, resize: true } },
          retina_detect: true
      },
        fireflies: {
            particles: {
                number: { value: 70, density: { enable: true, value_area: 800 } },
                color: { value: "#ffeb3b" },
                shape: { type: "circle" },
                opacity: { value: 0.9, random: true, anim: { enable: true, speed: 1, opacity_min: 0.1, sync: false } },
                size: { value: 3, random: true, anim: { enable: true, speed: 2, size_min: 0.1, sync: false } },
                line_linked: { enable: false },
                move: { enable: true, speed: 1, direction: "none", random: true, straight: false, out_mode: "out", bounce: false }
            },
            interactivity: { detect_on: "canvas", events: { onhover: { enable: false }, onclick: { enable: false }, resize: true } },
            retina_detect: true
        },

        //Basic config for the other systems, just for not get an error
        dna:{},
        rain:{},
        atoms:{},
        bubbles:{},
        matrix:{},
    };




    // --- Canvas-Based Rain (Improved) ---
    let rainParticles = [];
    function initRain() {
        const canvas = document.getElementById('canvas-particles');
        const ctx = canvas.getContext('2d');
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;

        rainParticles = []; // Clear previous particles
        for (let i = 0; i < 200; i++) {
            rainParticles.push({
                x: Math.random() * canvas.width,
                y: Math.random() * canvas.height,
                length: Math.random() * 10 + 5,
                speed: Math.random() * 5 + 2,
            });
        }

        function drawRain() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.strokeStyle = 'rgba(135, 206, 235, 0.8)'; // Light blue
            ctx.lineWidth = 1;

            for (const particle of rainParticles) {
                ctx.beginPath();
                ctx.moveTo(particle.x, particle.y);
                ctx.lineTo(particle.x, particle.y + particle.length);
                ctx.stroke();

                particle.y += particle.speed;
                if (particle.y > canvas.height) {
                    particle.y = Math.random() * -20; // Reset above the screen
                    particle.x = Math.random() * canvas.width; // New random x
                }
            }
            requestAnimationFrame(drawRain);
        }

      drawRain()
    }


    // --- Three.js DNA Helix ---
    let dnaScene, dnaCamera, dnaRenderer, dnaParticles, dnaGroup;

    function initDNA() {
      if (!dnaScene) { // Only initialize once
          dnaScene = new THREE.Scene();
          dnaCamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
          dnaRenderer = new THREE.WebGLRenderer({ alpha: true }); // Use alpha for transparency
          dnaRenderer.setSize(window.innerWidth, window.innerHeight);
          document.getElementById('threejs-particles').appendChild(dnaRenderer.domElement);
          dnaCamera.position.z = 100;

          // Create a group to hold the particles (for rotation)
          dnaGroup = new THREE.Group();
          dnaScene.add(dnaGroup);

           const particleCount = 200; // More particles for a denser helix
        const geometry = new THREE.BufferGeometry();
        const positions = new Float32Array(particleCount * 3);

        for (let i = 0; i < particleCount; i++) {
            const i3 = i * 3;
            const helixRadius = 20;
            const angle = (i / particleCount) * Math.PI * 8; // 4 full turns

            // Distribute along the helix
            positions[i3 + 0] = Math.sin(angle) * helixRadius; // X
            positions[i3 + 1] = (i / particleCount) * 80 - 40; // Y (spread along the height)
            positions[i3 + 2] = Math.cos(angle) * helixRadius; // Z
        }

        geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
        const material = new THREE.PointsMaterial({
            size: 2.5, // Slightly larger
            color: 0xFF4444, // More vibrant red
            transparent: true,
            opacity: 0.7,
        });
        dnaParticles = new THREE.Points(geometry, material);
        dnaGroup.add(dnaParticles);


          // --- Create connecting lines ---
          const lineGeometry = new THREE.BufferGeometry();
          const linePositions = new Float32Array(particleCount * 2 * 3); // * 2 for start/end points
          const lineMaterial = new THREE.LineBasicMaterial({ color: 0xFF0000, opacity: 0.3, transparent: true });


        // Connect particles along the helix
          for (let i = 0; i < particleCount - 1; i++) { // -1 to avoid connecting the last to the first
            const i6 = i * 6; // * 6 because we have two 3D points per line segment
              linePositions[i6 + 0] = positions[i * 3 + 0];
              linePositions[i6 + 1] = positions[i * 3 + 1];
              linePositions[i6 + 2] = positions[i * 3 + 2];

              linePositions[i6 + 3] = positions[(i + 1) * 3 + 0];
              linePositions[i6 + 4] = positions[(i + 1) * 3 + 1];
              linePositions[i6 + 5] = positions[(i + 1) * 3 + 2];
          }


          lineGeometry.setAttribute('position', new THREE.BufferAttribute(linePositions, 3));
          const dnaLines = new THREE.LineSegments(lineGeometry, lineMaterial); //Use LineSegments
          dnaGroup.add(dnaLines);


          // --- Animation Loop ---
          function animateDNA() {
            if (dnaScene) {
                requestAnimationFrame(animateDNA);
                // Rotate the entire group
                dnaGroup.rotation.y += 0.005;  // Rotate around the Y-axis
                dnaGroup.rotation.x += 0.002; // Slight rotation on X for a more dynamic look

                dnaRenderer.render(dnaScene, dnaCamera);
            }
          }
            animateDNA();
        }
    }


   // --- Three.js Atoms ---
let atomsScene, atomsCamera, atomsRenderer, atomParticles, nucleus;

function initAtoms() {
    if (!atomsScene) {
        atomsScene = new THREE.Scene();
        atomsCamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        atomsRenderer = new THREE.WebGLRenderer({ alpha: true });
        atomsRenderer.setSize(window.innerWidth, window.innerHeight);
        document.getElementById('threejs-particles').appendChild(atomsRenderer.domElement);
        atomsCamera.position.z = 100;

        // Nucleus
        const nucleusGeometry = new THREE.SphereGeometry(10, 32, 32);
        const nucleusMaterial = new THREE.MeshBasicMaterial({ color: 0x3498db });
        nucleus = new THREE.Mesh(nucleusGeometry, nucleusMaterial);
        atomsScene.add(nucleus);

        // Electrons (Particles)
        const electronCount = 8; // Fewer electrons
        const electronGeometry = new THREE.BufferGeometry();
        const electronPositions = new Float32Array(electronCount * 3);
        const electronOrbits = []; // Store orbit data (radius, speed)

        for (let i = 0; i < electronCount; i++) {
            const i3 = i * 3;
            // Initial positions (will be updated in animation)
            electronPositions[i3 + 0] = 0;
            electronPositions[i3 + 1] = 0;
            electronPositions[i3 + 2] = 0;

            // Randomize orbit radius and speed
            electronOrbits.push({
                radius: 20 + Math.random() * 30,  // Varying radii
                speed: 0.01 + Math.random() * 0.02, // Varying speeds
                angle: Math.random() * Math.PI * 2, // Start at random angle
            });
        }

        electronGeometry.setAttribute('position', new THREE.BufferAttribute(electronPositions, 3));
        const electronMaterial = new THREE.PointsMaterial({ size: 4, color: 0xffffff });
        atomParticles = new THREE.Points(electronGeometry, electronMaterial);
        atomsScene.add(atomParticles);


        // --- Animation ---
          function animateAtoms() {
              if (atomsScene) {
                  requestAnimationFrame(animateAtoms);

                  // Update electron positions
                  const positions = atomParticles.geometry.attributes.position.array;
                  for (let i = 0; i < electronCount; i++) {
                    const i3 = i * 3;
                    const orbit = electronOrbits[i];

                    // Circular motion
                    orbit.angle += orbit.speed;
                    positions[i3 + 0] = Math.cos(orbit.angle) * orbit.radius; // x
                    positions[i3 + 1] = Math.sin(orbit.angle) * orbit.radius; // y
                    positions[i3 + 2] = 0; // Keep in the same plane (for simplicity)
                   }

                  atomParticles.geometry.attributes.position.needsUpdate = true;

                  // Rotate the nucleus slightly for a more dynamic look.
                  nucleus.rotation.y += 0.005;
                  nucleus.rotation.x += 0.003;

                  atomsRenderer.render(atomsScene, atomsCamera);
              }
            }
        animateAtoms();
    }
}


// --- Three.js Bubbles ---
let bubblesScene, bubblesCamera, bubblesRenderer, bubbleParticles;

function initBubbles() {
    if (!bubblesScene) {
        bubblesScene = new THREE.Scene();
        bubblesCamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        bubblesRenderer = new THREE.WebGLRenderer({ alpha: true });
        bubblesRenderer.setSize(window.innerWidth, window.innerHeight);
        document.getElementById('threejs-particles').appendChild(bubblesRenderer.domElement);
        bubblesCamera.position.z = 200; // Further away

        const bubbleCount = 40; // More bubbles
        const bubbleGeometry = new THREE.BufferGeometry();
        const bubblePositions = new Float32Array(bubbleCount * 3);
        const bubbleSizes = new Float32Array(bubbleCount); // Store sizes
        const bubbleSpeeds = new Float32Array(bubbleCount); // Store speeds
        const bubbleColors = []; // Array to store colors

      const colorPalette = [
          new THREE.Color(0x87ceeb), // Sky Blue
          new THREE.Color(0x00bfff), // Deep Sky Blue
          new THREE.Color(0x1e90ff)  // Dodger Blue
      ];


        for (let i = 0; i < bubbleCount; i++) {
            const i3 = i * 3;
            // Random positions within a range
            bubblePositions[i3 + 0] = (Math.random() - 0.5) * 200; // x
            bubblePositions[i3 + 1] = (Math.random() - 0.5) * 200; // y
            bubblePositions[i3 + 2] = (Math.random() - 0.5) * 200; // z

            // Random sizes
            bubbleSizes[i] = 5 + Math.random() * 15;
            //Random Speeds
            bubbleSpeeds[i] = 0.5 + Math.random() * 1.5; // Slower speeds

          // Randomly select a color from the palette
          bubbleColors.push(colorPalette[Math.floor(Math.random() * colorPalette.length)]);
        }

        bubbleGeometry.setAttribute('position', new THREE.BufferAttribute(bubblePositions, 3));
        bubbleGeometry.setAttribute('size', new THREE.BufferAttribute(bubbleSizes, 1));

        // Shader Material for custom appearance (including varied colors)
        const bubbleMaterial = new THREE.ShaderMaterial({
            uniforms: {
                color: { value: new THREE.Color(0xffffff) }, // Default color (will be overridden)
              colors: { value: bubbleColors}
            },
            vertexShader: `
                attribute float size;
                varying vec3 vColor; // Pass color to fragment shader
                uniform vec3 colors[${bubbleCount}];
                void main() {
                    vColor = colors[gl_VertexID];
                    vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
                    gl_PointSize = size * (300.0 / -mvPosition.z); // Scale with distance
                    gl_Position = projectionMatrix * mvPosition;

                }
            `,
            fragmentShader: `
                uniform vec3 color;
                varying vec3 vColor;
                void main() {

                  float distance = length(gl_PointCoord - vec2(0.5, 0.5));
                    if (distance > 0.5) discard; // Make it a circle

                    gl_FragColor = vec4(vColor, 0.5 + (0.5 * (1.0-distance*2.0))); // Use varied color, add gradient
                }
            `,
            transparent: true,
            //blending: THREE.AdditiveBlending // Optional: For a brighter, additive effect
        });


        bubbleParticles = new THREE.Points(bubbleGeometry, bubbleMaterial);
        bubblesScene.add(bubbleParticles);

        function animateBubbles() {
            if(bubblesScene) {
                requestAnimationFrame(animateBubbles);

                const positions = bubbleParticles.geometry.attributes.position.array;
                for (let i = 0; i < bubbleCount; i++) {
                    const i3 = i * 3;
                    positions[i3 + 1] += bubbleSpeeds[i]; // Move upwards

                    // Reset if out of bounds
                    if (positions[i3 + 1] > window.innerHeight / 2) {
                        positions[i3 + 0] = (Math.random() - 0.5) * 200;
                        positions[i3 + 1] = -window.innerHeight / 2;
                        positions[i3 + 2] = (Math.random() - 0.5) * 200;
                    }
                }
                bubbleParticles.geometry.attributes.position.needsUpdate = true;

                bubblesRenderer.render(bubblesScene, bubblesCamera);
            }
        }
        animateBubbles();
    }
}


// --- Canvas Matrix Rain (Working Version) ---
function initMatrix() {
    const canvas = document.getElementById('canvas-particles'); // Use the existing canvas
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    const ctx = canvas.getContext('2d');

    const chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; // More characters
    const fontSize = 16;
    const columns = canvas.width / fontSize;
    const drops = [];

    for (let x = 0; x < columns; x++) {
        drops[x] = 1;
    }

    function drawMatrix() {
        ctx.fillStyle = 'rgba(0, 0, 0, 0.04)'; // More transparent background
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        ctx.fillStyle = '#0F0'; // Green color
        ctx.font = fontSize + 'px monospace';

        for (let i = 0; i < drops.length; i++) {
            const text = chars[Math.floor(Math.random() * chars.length)];
            ctx.fillText(text, i * fontSize, drops[i] * fontSize);

            if (drops[i] * fontSize > canvas.height && Math.random() > 0.975) {
                drops[i] = 0;
            }
            drops[i]++;
        }
    }

      function animateMatrix() {
        if (canvas.style.display !== 'none') { // Check if canvas is visible
           drawMatrix();
           requestAnimationFrame(animateMatrix);
        }
    }
    animateMatrix()
}



    // --- Clear Function ---
function clearParticles() {
    // particles.js clear
    particlesJS('particles-js', { particles: { number: { value: 0 } } });

    // Canvas clear
    const canvas = document.getElementById('canvas-particles');
    if (canvas) {
        const ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, canvas.width, canvas.height);
    }
    rainParticles = []; // Reset rain particles array

     // Three.js clear (DNA)
    if (dnaScene) {
      while (dnaScene.children.length > 0) {
        dnaScene.remove(dnaScene.children[0]);
      }
      dnaRenderer.dispose(); // Dispose of resources
      document.getElementById('threejs-particles').innerHTML = ''; // Clear container
    }
    dnaScene = null; // Set to null so it can be reinitialized

     // Three.js clear (Atoms)
    if (atomsScene) {
        while(atomsScene.children.length > 0){
            atomsScene.remove(atomsScene.children[0]);
        }
        atomsRenderer.dispose();
        document.getElementById('threejs-particles').innerHTML = '';
    }
    atomsScene = null;

    // Three.js clear (Bubbles)
    if (bubblesScene) {
        while(bubblesScene.children.length > 0){
            bubblesScene.remove(bubblesScene.children[0]);
        }
        bubblesRenderer.dispose();
        document.getElementById('threejs-particles').innerHTML = '';
    }
    bubblesScene = null;

}


    // --- Event Listener (with improved logic) ---
menu.addEventListener('click', (e) => {
    if (e.target.classList.contains('particle-button')) {
        const type = e.target.dataset.type;
        clearParticles(); // Clear previous effects before starting a new one

        //Hide all canvas
        document.getElementById('particles-js').style.display = 'none';
        document.getElementById('canvas-particles').style.display = 'none';
        document.getElementById('threejs-particles').style.display = 'none';

        switch (type) {
            case 'constellation':
                document.getElementById('particles-js').style.display = 'block';
                particlesJS('particles-js', particleConfigs.constellation);
                break;
            case 'dna':
                document.getElementById('threejs-particles').style.display = 'block';
                initDNA();
                break;
            case 'fireflies':
                document.getElementById('particles-js').style.display = 'block';
                particlesJS('particles-js', particleConfigs.fireflies);
                break;
            case 'rain':
                document.getElementById('canvas-particles').style.display = 'block';
                initRain();
                break;
            case 'atoms':
                document.getElementById('threejs-particles').style.display = 'block';
                initAtoms();
                break;
            case 'bubbles':
                document.getElementById('threejs-particles').style.display = 'block';
                initBubbles();
                break;
            case 'matrix':
                document.getElementById('canvas-particles').style.display = 'block'; // Show canvas
                initMatrix();
                break;
            case 'clear': // Clear is already handled by clearParticles()
                break;
        }
    }
});


    // --- Resize Handling ---
function handleResize() {
    const canvas = document.getElementById('canvas-particles');
    if (canvas) {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        //For Rain
        if(rainParticles.length > 0) initRain(); // Re-initialize rain on resize
    }


    // Three.js resize handling (DNA)
    if (dnaRenderer) {
        dnaCamera.aspect = window.innerWidth / window.innerHeight;
        dnaCamera.updateProjectionMatrix();
        dnaRenderer.setSize(window.innerWidth, window.innerHeight);
    }
    // Three.js resize handling (Atoms)
     if (atomsRenderer) {
        atomsCamera.aspect = window.innerWidth / window.innerHeight;
        atomsCamera.updateProjectionMatrix();
        atomsRenderer.setSize(window.innerWidth, window.innerHeight);
    }
    // Three.js resize handling (Bubbles)
    if (bubblesRenderer) {
        bubblesCamera.aspect = window.innerWidth / window.innerHeight;
        bubblesCamera.updateProjectionMatrix();
        bubblesRenderer.setSize(window.innerWidth, window.innerHeight);
    }

}

window.addEventListener('resize', handleResize);


})();