Spaces:
Sleeping
Sleeping
✨ Add interactive particle effects and final polish
Browse files- Mouse-attracted particles create mesmerizing patterns
- Click to create custom particle bursts
- Glowing cursor with radial gradient effect
- Sparkle effect follows mouse movement
- Particles respond to mouse proximity (200px range)
- Enhanced visual feedback for user interaction
- Smooth particle physics with attraction forces
- Professional polish for hackathon submission
app.py
CHANGED
|
@@ -95,11 +95,23 @@ class Particle {
|
|
| 95 |
}
|
| 96 |
|
| 97 |
update() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
this.x += this.vx;
|
| 99 |
this.y += this.vy;
|
| 100 |
this.vy += 0.2;
|
| 101 |
this.life--;
|
| 102 |
this.vx *= 0.98;
|
|
|
|
| 103 |
}
|
| 104 |
|
| 105 |
draw() {
|
|
@@ -148,12 +160,30 @@ function animate() {
|
|
| 148 |
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
| 149 |
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
| 150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
particles = particles.filter(p => {
|
| 152 |
p.update();
|
| 153 |
p.draw();
|
| 154 |
return p.life > 0;
|
| 155 |
});
|
| 156 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
requestAnimationFrame(animate);
|
| 158 |
}
|
| 159 |
animate();
|
|
@@ -174,6 +204,33 @@ setTimeout(() => {
|
|
| 174 |
setTimeout(() => createBurst(3), 1500);
|
| 175 |
setTimeout(() => createBurst(4), 2500);
|
| 176 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
// Auto-start evolution after intro
|
| 178 |
setTimeout(() => {
|
| 179 |
const startBtn = document.querySelector('.gr-button-primary');
|
|
|
|
| 95 |
}
|
| 96 |
|
| 97 |
update() {
|
| 98 |
+
// Mouse attraction
|
| 99 |
+
const dx = mouseX - this.x;
|
| 100 |
+
const dy = mouseY - this.y;
|
| 101 |
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
| 102 |
+
|
| 103 |
+
if (dist < 200 && dist > 0) {
|
| 104 |
+
const force = 0.5 * (1 - dist / 200);
|
| 105 |
+
this.vx += (dx / dist) * force;
|
| 106 |
+
this.vy += (dy / dist) * force;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
this.x += this.vx;
|
| 110 |
this.y += this.vy;
|
| 111 |
this.vy += 0.2;
|
| 112 |
this.life--;
|
| 113 |
this.vx *= 0.98;
|
| 114 |
+
this.vy *= 0.98;
|
| 115 |
}
|
| 116 |
|
| 117 |
draw() {
|
|
|
|
| 160 |
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
|
| 161 |
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
| 162 |
|
| 163 |
+
// Mouse glow effect
|
| 164 |
+
const gradient = ctx.createRadialGradient(mouseX, mouseY, 0, mouseX, mouseY, 100);
|
| 165 |
+
gradient.addColorStop(0, 'rgba(123, 63, 242, 0.3)');
|
| 166 |
+
gradient.addColorStop(0.5, 'rgba(0, 170, 255, 0.1)');
|
| 167 |
+
gradient.addColorStop(1, 'rgba(0, 255, 136, 0)');
|
| 168 |
+
ctx.fillStyle = gradient;
|
| 169 |
+
ctx.fillRect(mouseX - 100, mouseY - 100, 200, 200);
|
| 170 |
+
|
| 171 |
particles = particles.filter(p => {
|
| 172 |
p.update();
|
| 173 |
p.draw();
|
| 174 |
return p.life > 0;
|
| 175 |
});
|
| 176 |
|
| 177 |
+
// Draw cursor sparkle
|
| 178 |
+
ctx.save();
|
| 179 |
+
ctx.fillStyle = '#FFFFFF';
|
| 180 |
+
ctx.shadowBlur = 20;
|
| 181 |
+
ctx.shadowColor = '#00FF88';
|
| 182 |
+
ctx.beginPath();
|
| 183 |
+
ctx.arc(mouseX, mouseY, 3, 0, Math.PI * 2);
|
| 184 |
+
ctx.fill();
|
| 185 |
+
ctx.restore();
|
| 186 |
+
|
| 187 |
requestAnimationFrame(animate);
|
| 188 |
}
|
| 189 |
animate();
|
|
|
|
| 204 |
setTimeout(() => createBurst(3), 1500);
|
| 205 |
setTimeout(() => createBurst(4), 2500);
|
| 206 |
|
| 207 |
+
// Mouse interaction
|
| 208 |
+
let mouseX = canvas.width / 2;
|
| 209 |
+
let mouseY = canvas.height / 2;
|
| 210 |
+
|
| 211 |
+
canvas.addEventListener('mousemove', (e) => {
|
| 212 |
+
const rect = canvas.getBoundingClientRect();
|
| 213 |
+
mouseX = (e.clientX - rect.left) * (canvas.width / rect.width);
|
| 214 |
+
mouseY = (e.clientY - rect.top) * (canvas.height / rect.height);
|
| 215 |
+
});
|
| 216 |
+
|
| 217 |
+
canvas.addEventListener('click', (e) => {
|
| 218 |
+
const rect = canvas.getBoundingClientRect();
|
| 219 |
+
const x = (e.clientX - rect.left) * (canvas.width / rect.width);
|
| 220 |
+
const y = (e.clientY - rect.top) * (canvas.height / rect.height);
|
| 221 |
+
|
| 222 |
+
// Create burst at click location
|
| 223 |
+
for (let i = 0; i < 50; i++) {
|
| 224 |
+
const angle = (Math.PI * 2 * i) / 50;
|
| 225 |
+
const speed = Math.random() * 10 + 5;
|
| 226 |
+
particles.push(new Particle(
|
| 227 |
+
x + Math.cos(angle) * 10,
|
| 228 |
+
y + Math.sin(angle) * 10,
|
| 229 |
+
['#00FF88', '#7B3FF2', '#00AAFF', '#FFD700'][Math.floor(Math.random() * 4)]
|
| 230 |
+
));
|
| 231 |
+
}
|
| 232 |
+
});
|
| 233 |
+
|
| 234 |
// Auto-start evolution after intro
|
| 235 |
setTimeout(() => {
|
| 236 |
const startBtn = document.querySelector('.gr-button-primary');
|