draft demo-02

This commit is contained in:
Benjamin Hackl 2026-01-15 18:24:51 +01:00
commit ae8c09013c
3 changed files with 291 additions and 0 deletions

8
demo-02/README.md Normal file
View file

@ -0,0 +1,8 @@
# Demo 2: Fixing an Issue, Adding a new Feature
- `demo-01.index.html` has the output of a former run of `../demo1`
- There is an issue where the background flickers as the last few particles
fade out. Smooth out the rapid color change.
- Add a new feature: clicking should cause a burst of particles as well as
a shift in the overall hue.
- The output of a previous plan is in `old-plan.md`.

227
demo-02/demo-01.index.html Normal file
View file

@ -0,0 +1,227 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Particle Trail Effect</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
min-height: 100vh;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
canvas {
display: block;
position: fixed;
top: 0;
left: 0;
z-index: 1;
}
.content {
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
color: white;
text-align: center;
pointer-events: none;
}
h1 {
font-size: 4rem;
font-weight: 300;
letter-spacing: 0.3em;
margin-bottom: 1rem;
text-shadow: 0 0 30px rgba(100, 200, 255, 0.5);
}
p {
font-size: 1.2rem;
opacity: 0.7;
letter-spacing: 0.1em;
}
</style>
</head>
<body>
<canvas id="particleCanvas"></canvas>
<div class="content">
<h1>Particle Trail</h1>
<p>Move your cursor to create the magic</p>
</div>
<script>
const canvas = document.getElementById('particleCanvas');
const ctx = canvas.getContext('2d');
// Set canvas to full window size
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
// Mouse position tracking
const mouse = {
x: undefined,
y: undefined,
prevX: undefined,
prevY: undefined
};
window.addEventListener('mousemove', (e) => {
mouse.prevX = mouse.x;
mouse.prevY = mouse.y;
mouse.x = e.clientX;
mouse.y = e.clientY;
// Spawn particles on mouse move
if (mouse.prevX !== undefined) {
const dx = mouse.x - mouse.prevX;
const dy = mouse.y - mouse.prevY;
const distance = Math.sqrt(dx * dx + dy * dy);
// Spawn more particles for faster movement
const particleCount = Math.min(Math.floor(distance / 2), 10);
for (let i = 0; i < particleCount; i++) {
const t = i / particleCount;
const x = mouse.prevX + dx * t;
const y = mouse.prevY + dy * t;
particles.push(new Particle(x, y));
}
}
});
// Touch support
window.addEventListener('touchmove', (e) => {
e.preventDefault();
const touch = e.touches[0];
mouse.prevX = mouse.x;
mouse.prevY = mouse.y;
mouse.x = touch.clientX;
mouse.y = touch.clientY;
if (mouse.prevX !== undefined) {
const dx = mouse.x - mouse.prevX;
const dy = mouse.y - mouse.prevY;
const distance = Math.sqrt(dx * dx + dy * dy);
const particleCount = Math.min(Math.floor(distance / 2), 10);
for (let i = 0; i < particleCount; i++) {
const t = i / particleCount;
const x = mouse.prevX + dx * t;
const y = mouse.prevY + dy * t;
particles.push(new Particle(x, y));
}
}
}, { passive: false });
// Particle class
class Particle {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = Math.random() * 4 + 2;
this.baseSize = this.size;
// Random velocity for inertia effect
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 2 + 0.5;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
// Color with variation
const hue = Math.random() * 60 + 180; // Blue-cyan range
this.color = `hsla(${hue}, 100%, 70%,`;
this.life = 1; // Full life
this.decay = Math.random() * 0.02 + 0.01; // Random decay rate
}
update() {
// Apply inertia - particles continue moving
this.x += this.vx;
this.y += this.vy;
// Slow down over time (friction)
this.vx *= 0.98;
this.vy *= 0.98;
// Shrink size
this.size = this.baseSize * this.life;
// Fade out
this.life -= this.decay;
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color + this.life + ')';
ctx.fill();
// Add glow effect
ctx.shadowBlur = 15;
ctx.shadowColor = this.color + '0.5)';
}
}
// Particles array
let particles = [];
// Animation loop
function animate() {
// Clear with slight fade effect for trails
ctx.fillStyle = 'rgba(26, 26, 46, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Reset shadow for better performance
ctx.shadowBlur = 0;
// Update and draw particles
for (let i = particles.length - 1; i >= 0; i--) {
const particle = particles[i];
particle.update();
particle.draw();
// Remove dead particles
if (particle.life <= 0) {
particles.splice(i, 1);
}
}
// Limit particles for performance
if (particles.length > 500) {
particles.splice(0, particles.length - 500);
}
requestAnimationFrame(animate);
}
// Start animation
animate();
// Spawn some initial particles for visual interest
for (let i = 0; i < 50; i++) {
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
const particle = new Particle(x, y);
particle.life = Math.random() * 0.5 + 0.5;
particles.push(particle);
}
</script>
</body>
</html>

56
demo-02/old-plan.md Normal file
View file

@ -0,0 +1,56 @@
# Particle Burst Feature Plan
## Overview
Add a click interaction that creates a radial burst of particles and shifts the color palette.
## Implementation Details
### 1. Global State Addition
Add a `hueOffset` variable to track the current color shift:
```javascript
let hueOffset = 0; // Shifts the particle color palette
```
### 2. Click Event Handler
Add to the script (after the mousemove listener):
```javascript
canvas.addEventListener('click', (e) => {
const clickX = e.clientX;
const clickY = e.clientY;
// Shift hue for variety
hueOffset += 15 + Math.random() * 15; // Shift by 15-30 degrees
// Create burst of particles
const burstCount = 40;
for (let i = 0; i < burstCount; i++) {
particles.push(new BurstParticle(clickX, clickY, hueOffset));
}
});
```
### 3. BurstParticle Class (or extend Particle)
Create particles with radial velocity:
- Position: click coordinates
- Angle: random 0 to 2π
- Speed: random range (e.g., 2-8) for varying distances
- Size: slightly larger than normal particles (6-12px)
- Decay: slightly faster for quick burst effect
### 4. Particle Constructor Update
Modify the `Particle` constructor to accept an optional hue offset:
```javascript
constructor(x, y, hueShift = 0) {
// ... existing code ...
const hue = Math.random() * 60 + 180 + hueShift; // Blue-cyan + shift
this.color = `hsla(${hue}, 100%, 70%,`;
}
```
## File Changes
- **index.html**: Add click listener, hueOffset variable, and optional hueShift parameter to Particle
## Visual Effect
- Each click creates an explosion of blue-cyan particles at the cursor
- Subsequent clicks shift the color palette gradually (e.g., cyan → green → yellow)
- Trail effect continues to work with the new particles