Google Particle Effect

Adds cool interactive particle effects to Google

Size

18.8 KB

Version

1.2.1

Created

Oct 30, 2025

Updated

15 days ago

1// ==UserScript==
2// @name		Google Particle Effect
3// @description		Adds cool interactive particle effects to Google
4// @version		1.2.1
5// @match		https://*.google.com/*
6// @icon		https://www.gstatic.com/images/branding/searchlogo/ico/favicon.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    // Particle system configuration
12    const particles = [];
13    const maxParticles = 100;
14    let canvas, ctx;
15    let mouseX = 0, mouseY = 0;
16    let hue = 0;
17
18    // Particle class
19    class Particle {
20        constructor(x, y) {
21            this.x = x;
22            this.y = y;
23            this.size = Math.random() * 5 + 1;
24            this.speedX = Math.random() * 3 - 1.5;
25            this.speedY = Math.random() * 3 - 1.5;
26            this.color = `hsl(${hue}, 100%, 50%)`;
27            this.life = 100;
28        }
29
30        update() {
31            this.x += this.speedX;
32            this.y += this.speedY;
33            this.life -= 1;
34            if (this.size > 0.2) this.size -= 0.05;
35        }
36
37        draw() {
38            ctx.fillStyle = this.color;
39            ctx.globalAlpha = this.life / 100;
40            ctx.beginPath();
41            ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
42            ctx.fill();
43            ctx.globalAlpha = 1;
44        }
45    }
46
47    // Ripple class for wave effect
48    class Ripple {
49        constructor(x, y) {
50            this.x = x;
51            this.y = y;
52            this.radius = 0;
53            this.maxRadius = 150;
54            this.speed = 3;
55            this.opacity = 1;
56            this.color = `hsl(${hue}, 100%, 50%)`;
57        }
58
59        update() {
60            this.radius += this.speed;
61            this.opacity = 1 - (this.radius / this.maxRadius);
62        }
63
64        draw() {
65            ctx.strokeStyle = this.color;
66            ctx.lineWidth = 3;
67            ctx.globalAlpha = this.opacity;
68            ctx.beginPath();
69            ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
70            ctx.stroke();
71            ctx.globalAlpha = 1;
72        }
73
74        isDead() {
75            return this.radius >= this.maxRadius;
76        }
77    }
78
79    // Glow trail class
80    class GlowTrail {
81        constructor(x, y) {
82            this.x = x;
83            this.y = y;
84            this.size = 20;
85            this.life = 30;
86            this.color = `hsl(${hue}, 100%, 50%)`;
87        }
88
89        update() {
90            this.life -= 1;
91            this.size *= 0.95;
92        }
93
94        draw() {
95            const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size);
96            gradient.addColorStop(0, this.color);
97            gradient.addColorStop(1, 'transparent');
98            
99            ctx.globalAlpha = this.life / 30;
100            ctx.fillStyle = gradient;
101            ctx.beginPath();
102            ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
103            ctx.fill();
104            ctx.globalAlpha = 1;
105        }
106
107        isDead() {
108            return this.life <= 0;
109        }
110    }
111
112    // Firework class - exploding particles
113    class Firework {
114        constructor(x, y) {
115            this.x = x;
116            this.y = y;
117            this.particles = [];
118            this.exploded = false;
119            this.targetY = y - Math.random() * 200 - 100;
120            this.speed = 5;
121            this.color = `hsl(${hue}, 100%, 50%)`;
122            
123            // Create explosion particles
124            for (let i = 0; i < 30; i++) {
125                const angle = (Math.PI * 2 * i) / 30;
126                const velocity = Math.random() * 3 + 2;
127                this.particles.push({
128                    x: x,
129                    y: y,
130                    vx: Math.cos(angle) * velocity,
131                    vy: Math.sin(angle) * velocity,
132                    life: 100,
133                    size: Math.random() * 3 + 1
134                });
135            }
136        }
137
138        update() {
139            if (!this.exploded) {
140                this.y -= this.speed;
141                if (this.y <= this.targetY) {
142                    this.exploded = true;
143                }
144            } else {
145                this.particles.forEach(p => {
146                    p.x += p.vx;
147                    p.y += p.vy;
148                    p.vy += 0.1; // gravity
149                    p.life -= 2;
150                });
151            }
152        }
153
154        draw() {
155            if (!this.exploded) {
156                ctx.fillStyle = this.color;
157                ctx.beginPath();
158                ctx.arc(this.x, this.y, 3, 0, Math.PI * 2);
159                ctx.fill();
160            } else {
161                this.particles.forEach(p => {
162                    if (p.life > 0) {
163                        ctx.fillStyle = this.color;
164                        ctx.globalAlpha = p.life / 100;
165                        ctx.beginPath();
166                        ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
167                        ctx.fill();
168                        ctx.globalAlpha = 1;
169                    }
170                });
171            }
172        }
173
174        isDead() {
175            return this.exploded && this.particles.every(p => p.life <= 0);
176        }
177    }
178
179    // Confetti class - colorful falling pieces
180    class Confetti {
181        constructor(x, y) {
182            this.x = x;
183            this.y = y;
184            this.width = Math.random() * 10 + 5;
185            this.height = Math.random() * 6 + 3;
186            this.speedX = Math.random() * 4 - 2;
187            this.speedY = Math.random() * 2 + 1;
188            this.rotation = Math.random() * Math.PI * 2;
189            this.rotationSpeed = Math.random() * 0.2 - 0.1;
190            this.color = `hsl(${Math.random() * 360}, 100%, 50%)`;
191            this.life = 200;
192        }
193
194        update() {
195            this.x += this.speedX;
196            this.y += this.speedY;
197            this.speedY += 0.05; // gravity
198            this.rotation += this.rotationSpeed;
199            this.life -= 1;
200        }
201
202        draw() {
203            ctx.save();
204            ctx.translate(this.x, this.y);
205            ctx.rotate(this.rotation);
206            ctx.fillStyle = this.color;
207            ctx.globalAlpha = this.life / 200;
208            ctx.fillRect(-this.width / 2, -this.height / 2, this.width, this.height);
209            ctx.globalAlpha = 1;
210            ctx.restore();
211        }
212
213        isDead() {
214            return this.life <= 0 || this.y > canvas.height;
215        }
216    }
217
218    // Lightning class - electric bolts
219    class Lightning {
220        constructor(x, y) {
221            this.startX = x;
222            this.startY = y;
223            this.segments = [];
224            this.life = 20;
225            this.color = `hsl(${hue}, 100%, 70%)`;
226            
227            // Generate lightning path
228            let currentX = x;
229            let currentY = y;
230            const targetY = y + Math.random() * 150 + 100;
231            
232            while (currentY < targetY) {
233                const nextX = currentX + (Math.random() - 0.5) * 40;
234                const nextY = currentY + Math.random() * 20 + 10;
235                this.segments.push({ x1: currentX, y1: currentY, x2: nextX, y2: nextY });
236                currentX = nextX;
237                currentY = nextY;
238            }
239        }
240
241        update() {
242            this.life -= 2;
243        }
244
245        draw() {
246            ctx.strokeStyle = this.color;
247            ctx.lineWidth = 3;
248            ctx.globalAlpha = this.life / 20;
249            ctx.shadowBlur = 10;
250            ctx.shadowColor = this.color;
251            
252            this.segments.forEach(seg => {
253                ctx.beginPath();
254                ctx.moveTo(seg.x1, seg.y1);
255                ctx.lineTo(seg.x2, seg.y2);
256                ctx.stroke();
257            });
258            
259            ctx.shadowBlur = 0;
260            ctx.globalAlpha = 1;
261        }
262
263        isDead() {
264            return this.life <= 0;
265        }
266    }
267
268    // Snowflake class - gentle falling snow
269    class Snowflake {
270        constructor(x, y) {
271            this.x = x;
272            this.y = y;
273            this.size = Math.random() * 4 + 2;
274            this.speedY = Math.random() * 1 + 0.5;
275            this.speedX = Math.random() * 0.5 - 0.25;
276            this.life = 300;
277            this.opacity = Math.random() * 0.5 + 0.5;
278        }
279
280        update() {
281            this.x += this.speedX;
282            this.y += this.speedY;
283            this.speedX += (Math.random() - 0.5) * 0.1; // drift
284            this.life -= 1;
285        }
286
287        draw() {
288            ctx.fillStyle = 'white';
289            ctx.globalAlpha = this.opacity * (this.life / 300);
290            ctx.beginPath();
291            ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
292            ctx.fill();
293            ctx.globalAlpha = 1;
294        }
295
296        isDead() {
297            return this.life <= 0 || this.y > canvas.height;
298        }
299    }
300
301    // Effect state management
302    const ripples = [];
303    const glowTrails = [];
304    let currentEffect = 'particles'; // 'particles', 'ripples', 'glow'
305    const fireworks = [];
306    const confetti = [];
307    const lightning = [];
308    const snowflakes = [];
309
310    // Create effect toggle controls
311    function createEffectControls() {
312        const controlPanel = document.createElement('div');
313        controlPanel.id = 'effect-controls';
314        controlPanel.style.cssText = `
315            position: fixed;
316            top: 20px;
317            right: 20px;
318            background: rgba(0, 0, 0, 0.8);
319            padding: 15px;
320            border-radius: 10px;
321            z-index: 10000;
322            font-family: Arial, sans-serif;
323            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
324        `;
325
326        const title = document.createElement('div');
327        title.textContent = '✨ Effects';
328        title.style.cssText = `
329            color: white;
330            font-size: 14px;
331            font-weight: bold;
332            margin-bottom: 10px;
333            text-align: center;
334        `;
335        controlPanel.appendChild(title);
336
337        const effects = [
338            { name: 'particles', label: '🎆 Particles', color: '#ff6b6b' },
339            { name: 'ripples', label: '🌊 Ripples', color: '#4ecdc4' },
340            { name: 'glow', label: '✨ Glow Trail', color: '#ffe66d' },
341            { name: 'fireworks', label: '🎆 Fireworks', color: '#f54242' },
342            { name: 'confetti', label: '🌈 Confetti', color: '#f542f5' },
343            { name: 'lightning', label: '⚡ Lightning', color: '#f5f542' },
344            { name: 'snowflakes', label: '❄ Snowflakes', color: '#42f5f5' }
345        ];
346
347        effects.forEach(effect => {
348            const button = document.createElement('button');
349            button.textContent = effect.label;
350            button.style.cssText = `
351                display: block;
352                width: 100%;
353                padding: 10px;
354                margin: 5px 0;
355                border: 2px solid ${effect.color};
356                background: ${currentEffect === effect.name ? effect.color : 'transparent'};
357                color: ${currentEffect === effect.name ? '#000' : effect.color};
358                border-radius: 5px;
359                cursor: pointer;
360                font-size: 13px;
361                font-weight: bold;
362                transition: all 0.3s ease;
363            `;
364
365            button.addEventListener('mouseenter', () => {
366                if (currentEffect !== effect.name) {
367                    button.style.background = `${effect.color}33`;
368                }
369            });
370
371            button.addEventListener('mouseleave', () => {
372                if (currentEffect !== effect.name) {
373                    button.style.background = 'transparent';
374                }
375            });
376
377            button.addEventListener('click', () => {
378                currentEffect = effect.name;
379                console.log(`Switched to ${effect.name} effect`);
380                
381                // Update all button styles
382                const buttons = controlPanel.querySelectorAll('button');
383                buttons.forEach((btn, idx) => {
384                    const eff = effects[idx];
385                    if (eff.name === effect.name) {
386                        btn.style.background = eff.color;
387                        btn.style.color = '#000';
388                    } else {
389                        btn.style.background = 'transparent';
390                        btn.style.color = eff.color;
391                    }
392                });
393            });
394
395            controlPanel.appendChild(button);
396        });
397
398        document.body.appendChild(controlPanel);
399        console.log('Effect controls created');
400    }
401
402    // Initialize canvas
403    function initCanvas() {
404        canvas = document.createElement('canvas');
405        canvas.id = 'particle-canvas';
406        canvas.style.cssText = `
407            position: fixed;
408            top: 0;
409            left: 0;
410            width: 100%;
411            height: 100%;
412            pointer-events: none;
413            z-index: 9999;
414        `;
415        document.body.appendChild(canvas);
416        
417        ctx = canvas.getContext('2d');
418        resizeCanvas();
419        
420        console.log('Particle effect canvas initialized');
421    }
422
423    // Resize canvas to window size
424    function resizeCanvas() {
425        canvas.width = window.innerWidth;
426        canvas.height = window.innerHeight;
427    }
428
429    // Create particles at mouse position
430    function createParticles(x, y) {
431        for (let i = 0; i < 3; i++) {
432            if (particles.length < maxParticles) {
433                particles.push(new Particle(x, y));
434            }
435        }
436    }
437
438    // Animation loop
439    function animate() {
440        ctx.clearRect(0, 0, canvas.width, canvas.height);
441        
442        // Update and draw based on current effect
443        if (currentEffect === 'particles') {
444            for (let i = particles.length - 1; i >= 0; i--) {
445                particles[i].update();
446                particles[i].draw();
447                if (particles[i].life <= 0) {
448                    particles.splice(i, 1);
449                }
450            }
451        } else if (currentEffect === 'ripples') {
452            for (let i = ripples.length - 1; i >= 0; i--) {
453                ripples[i].update();
454                ripples[i].draw();
455                if (ripples[i].isDead()) {
456                    ripples.splice(i, 1);
457                }
458            }
459        } else if (currentEffect === 'glow') {
460            for (let i = glowTrails.length - 1; i >= 0; i--) {
461                glowTrails[i].update();
462                glowTrails[i].draw();
463                if (glowTrails[i].isDead()) {
464                    glowTrails.splice(i, 1);
465                }
466            }
467        } else if (currentEffect === 'fireworks') {
468            for (let i = fireworks.length - 1; i >= 0; i--) {
469                fireworks[i].update();
470                fireworks[i].draw();
471                if (fireworks[i].isDead()) {
472                    fireworks.splice(i, 1);
473                }
474            }
475        } else if (currentEffect === 'confetti') {
476            for (let i = confetti.length - 1; i >= 0; i--) {
477                confetti[i].update();
478                confetti[i].draw();
479                if (confetti[i].isDead()) {
480                    confetti.splice(i, 1);
481                }
482            }
483        } else if (currentEffect === 'lightning') {
484            for (let i = lightning.length - 1; i >= 0; i--) {
485                lightning[i].update();
486                lightning[i].draw();
487                if (lightning[i].isDead()) {
488                    lightning.splice(i, 1);
489                }
490            }
491        } else if (currentEffect === 'snowflakes') {
492            for (let i = snowflakes.length - 1; i >= 0; i--) {
493                snowflakes[i].update();
494                snowflakes[i].draw();
495                if (snowflakes[i].isDead()) {
496                    snowflakes.splice(i, 1);
497                }
498            }
499        }
500        
501        // Cycle hue for rainbow effect
502        hue += 0.5;
503        if (hue > 360) hue = 0;
504        
505        requestAnimationFrame(animate);
506    }
507
508    // Track mouse movement
509    function handleMouseMove(e) {
510        mouseX = e.clientX;
511        mouseY = e.clientY;
512        
513        if (currentEffect === 'particles') {
514            createParticles(mouseX, mouseY);
515        } else if (currentEffect === 'glow') {
516            glowTrails.push(new GlowTrail(mouseX, mouseY));
517        }
518    }
519
520    // Track touch movement for mobile
521    function handleTouchMove(e) {
522        if (e.touches.length > 0) {
523            mouseX = e.touches[0].clientX;
524            mouseY = e.touches[0].clientY;
525            
526            if (currentEffect === 'particles') {
527                createParticles(mouseX, mouseY);
528            } else if (currentEffect === 'glow') {
529                glowTrails.push(new GlowTrail(mouseX, mouseY));
530            }
531        }
532    }
533
534    // Add click effect - burst of particles or ripple
535    function handleClick(e) {
536        const x = e.clientX;
537        const y = e.clientY;
538        
539        if (currentEffect === 'particles') {
540            for (let i = 0; i < 20; i++) {
541                if (particles.length < maxParticles) {
542                    particles.push(new Particle(x, y));
543                }
544            }
545            console.log('Particle burst created at click position');
546        } else if (currentEffect === 'ripples') {
547            ripples.push(new Ripple(x, y));
548            console.log('Ripple wave created at click position');
549        } else if (currentEffect === 'glow') {
550            for (let i = 0; i < 10; i++) {
551                glowTrails.push(new GlowTrail(x, y));
552            }
553            console.log('Glow burst created at click position');
554        } else if (currentEffect === 'fireworks') {
555            fireworks.push(new Firework(x, y));
556            console.log('Firework launched at click position');
557        } else if (currentEffect === 'confetti') {
558            for (let i = 0; i < 30; i++) {
559                confetti.push(new Confetti(x, y));
560            }
561            console.log('Confetti burst created at click position');
562        } else if (currentEffect === 'lightning') {
563            lightning.push(new Lightning(x, y));
564            console.log('Lightning bolt created at click position');
565        } else if (currentEffect === 'snowflakes') {
566            for (let i = 0; i < 15; i++) {
567                snowflakes.push(new Snowflake(x, y));
568            }
569            console.log('Snowflakes created at click position');
570        }
571    }
572
573    // Initialize the effect
574    function init() {
575        console.log('Initializing Google Particle Effect');
576        
577        // Wait for body to be ready
578        if (document.body) {
579            initCanvas();
580            createEffectControls();
581            animate();
582            
583            // Event listeners
584            document.addEventListener('mousemove', handleMouseMove);
585            document.addEventListener('touchmove', handleTouchMove, { passive: true });
586            document.addEventListener('click', handleClick);
587            window.addEventListener('resize', resizeCanvas);
588            
589            console.log('Particle effect is active! Move your mouse to see the magic ✨');
590        } else {
591            setTimeout(init, 100);
592        }
593    }
594
595    // Start the extension
596    init();
597})();
Google Particle Effect | Robomonkey