TECH DETAILS

Firefly Garden - Day 27

Advanced Level

Table of Contents

01 / Firefly Particle System

Each firefly is a self-contained particle with its own position, velocity, visual properties, and behavior state. The particle system manages creation, updates, and rendering of all fireflies.

class Firefly { constructor(x, y) { // Position and velocity this.x = x || Math.random() * width; this.y = y || Math.random() * height * 0.8; this.vx = (Math.random() - 0.5) * 2; this.vy = (Math.random() - 0.5) * 2; this.baseSpeed = Math.random() * 0.5 + 0.3; // Glow properties - each firefly pulses independently this.phase = Math.random() * Math.PI * 2; this.pulseSpeed = Math.random() * 0.05 + 0.02; this.brightness = 0; // Color variation (yellow to green spectrum) this.hue = Math.random() * 60 + 60; // 60-120 this.saturation = Math.random() * 30 + 70; // Wandering behavior this.wanderAngle = Math.random() * Math.PI * 2; this.curiosity = Math.random() * 0.5 + 0.5; // Trail history this.trail = []; } }

Property Randomization

Each firefly has randomized properties to create natural variation. The phase offset ensures fireflies don't all blink together (unless in sync mode), while curiosity determines how strongly each firefly is attracted to the cursor.

02 / Bioluminescent Glow Effects

Real fireflies produce a soft, organic glow through bioluminescence. We simulate this using multiple layered radial gradients with varying opacity and size.

draw() { const intensity = settings.glowIntensity; const alpha = this.brightness * intensity; // Outer glow - size pulses with brightness const glowSize = this.size * 8 * (0.5 + this.brightness * 0.5); const gradient = ctx.createRadialGradient( this.x, this.y, 0, this.x, this.y, glowSize ); // Multi-stop gradient for soft falloff gradient.addColorStop(0, `hsla(${hue}, ${sat}%, 80%, ${alpha * 0.6})`); gradient.addColorStop(0.3, `hsla(${hue}, ${sat}%, 60%, ${alpha * 0.3})`); gradient.addColorStop(0.6, `hsla(${hue}, ${sat}%, 50%, ${alpha * 0.1})`); gradient.addColorStop(1, `hsla(${hue}, ${sat}%, 40%, 0)`); ctx.fillStyle = gradient; ctx.fill(); // Core bright spot ctx.arc(this.x, this.y, this.size * 0.8, 0, Math.PI * 2); ctx.fillStyle = `hsla(${hue}, 100%, 95%, ${alpha})`; ctx.fill(); }

Pulsing Brightness

Firefly brightness follows a sine wave, creating the characteristic slow pulse. The brightness value (0-1) controls both the glow opacity and size, making the entire effect breathe naturally.

// Individual pulse timing this.targetBrightness = (Math.sin( time * this.pulseSpeed + this.phase ) + 1) / 2; // Smooth transition (easing) this.brightness += (this.targetBrightness - this.brightness) * 0.1;

03 / Behavior Modes & Flocking

Fireflies support three behavior modes that change how they move and interact. Each mode creates a distinct visual atmosphere.

// WANDER MODE - Natural, random flight if (settings.mode === 'wander') { // Perlin-like wandering using angle accumulation this.wanderAngle += (Math.random() - 0.5) * this.wanderSpeed; this.vx += Math.cos(this.wanderAngle) * 0.02; this.vy += Math.sin(this.wanderAngle) * 0.02; } // SYNC MODE - Synchronized flashing if (settings.mode === 'sync') { // All fireflies share a global phase this.targetBrightness = (Math.sin( syncPhase + this.phase * 0.2 // Slight variation ) + 1) / 2; } // SWARM MODE - Flock toward cursor if (settings.mode === 'swarm' && mouse.active) { const dx = mouse.x - this.x; const dy = mouse.y - this.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist > 50) { // Attract toward cursor this.vx += (dx / dist) * 0.1 * this.curiosity; this.vy += (dy / dist) * 0.1 * this.curiosity; } else { // Orbit around cursor when close const angle = Math.atan2(dy, dx) + Math.PI / 2; this.vx += Math.cos(angle) * 0.05; this.vy += Math.sin(angle) * 0.05; } }
Real Firefly Behavior

In nature, some firefly species actually synchronize their flashing. The "Sync" mode simulates this phenomenon, where a global phase variable creates waves of light across the swarm.

04 / Trail Rendering

Light trails add motion blur and enhance the magical atmosphere. Each firefly maintains a history of recent positions that fade out over time.

// Store position history if (settings.trailLength > 0) { this.trail.push({ x: this.x, y: this.y, brightness: this.brightness }); // Limit trail length based on setting const maxLen = Math.floor(this.maxTrail * settings.trailLength); while (this.trail.length > maxLen) { this.trail.shift(); } } // Render trail with fading opacity for (let i = 0; i < this.trail.length - 1; i++) { const t = i / this.trail.length; // 0 = oldest, 1 = newest const point = this.trail[i]; const trailAlpha = t * alpha * 0.3; ctx.arc(point.x, point.y, this.size * 0.5 * t, 0, Math.PI * 2); ctx.fillStyle = `hsla(${hue}, ${sat}%, 70%, ${trailAlpha})`; ctx.fill(); }

Trail Optimization

Trail length is dynamically adjustable via the UI. Setting trail to 0 completely disables trail storage and rendering, improving performance for slower devices.

05 / Cursor Interaction

Fireflies respond to cursor movement with attraction, avoidance, or orbiting behavior depending on distance and mode. This creates an engaging, interactive experience.

// Gentle attraction when nearby (Wander mode) if (mouse.active) { const dx = mouse.x - this.x; const dy = mouse.y - this.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 200 && dist > 30) { // Attract gently this.vx += (dx / dist) * 0.02 * this.curiosity; this.vy += (dy / dist) * 0.02 * this.curiosity; } else if (dist <= 30) { // Scatter when too close this.vx -= (dx / dist) * 0.05; this.vy -= (dy / dist) * 0.05; } } // Custom cursor glow if (mouse.active) { const cursorGlow = ctx.createRadialGradient( mouse.x, mouse.y, 0, mouse.x, mouse.y, 30 ); cursorGlow.addColorStop(0, 'rgba(255, 255, 200, 0.3)'); cursorGlow.addColorStop(1, 'rgba(255, 255, 100, 0)'); ctx.fillStyle = cursorGlow; ctx.fill(); }
Touch Support

The simulation supports both mouse and touch input. Touch events update the same mouse coordinates, allowing mobile users to interact by dragging their finger across the garden.

Click to Spawn

Clicking anywhere spawns 3 new fireflies at that location, up to a maximum of 200 total. This lets users add fireflies to specific areas.

06 / Night Environment

The garden environment creates an immersive nocturnal setting with a gradient sky, twinkling stars, moon with atmospheric glow, and animated grass in the foreground.

// Night sky gradient const gradient = ctx.createLinearGradient(0, 0, 0, height); gradient.addColorStop(0, '#0a0f0a'); // Deep night at top gradient.addColorStop(0.6, '#0f1a12'); // Slightly lighter gradient.addColorStop(1, '#111f14'); // Ground level // Deterministic star positions (same every frame) for (let i = 0; i < 100; i++) { const x = (Math.sin(i * 567.8) * 0.5 + 0.5) * width; const y = (Math.cos(i * 234.5) * 0.5 + 0.5) * height * 0.5; const twinkle = Math.sin(time * 0.02 + i) * 0.5 + 0.5; // Draw star with twinkling alpha } // Moon with atmospheric glow const moonGradient = ctx.createRadialGradient( moonX - 5, moonY - 5, 0, // Offset for 3D effect moonX, moonY, 35 ); moonGradient.addColorStop(0, 'rgba(255, 255, 240, 0.9)'); moonGradient.addColorStop(1, 'rgba(255, 255, 200, 0)');

Animated Grass

Grass blades in the foreground add depth and movement. Each blade sways independently using sine waves with randomized phase and speed.

// Initialize grass with random properties grassBlades.push({ x: (i / bladeCount) * width + (Math.random() - 0.5) * 15, height: Math.random() * 60 + 40, swayOffset: Math.random() * Math.PI * 2, swaySpeed: Math.random() * 0.02 + 0.01, hue: Math.random() * 30 + 100 // Green variations }); // Draw with quadratic curve for natural bend const sway = Math.sin(time * blade.swaySpeed + blade.swayOffset) * 10; ctx.quadraticCurveTo( blade.x + sway, // Control point with sway height - blade.height * 0.6, blade.x + sway * 1.5, // End point with more sway height - blade.height );
Layer Order

Rendering order matters: background -> stars -> moon -> fireflies -> grass. This creates proper depth with fireflies appearing to fly among the grass blades.