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