// --- Setup --- const canvas = document.getElementById('beatCanvas'); // Ensure this ID matches your HTML const ctx = canvas.getContext('2d'); // --- Configuration --- const FADE_DURATION = 2000; // 1.5 seconds for fade in/out of background images const DISPLAY_DURATION = 10000; // 4 seconds of full display time for background images const TRANSITION_CYCLE = FADE_DURATION + DISPLAY_DURATION; // Total background image cycle length const NUM_SPARKLES = 100; // How many sparkles to have on screen const SPARKLE_SIZE_MIN = 5; // Min radius for a sparkle const SPARKLE_SIZE_MAX = 20; // Max radius for a sparkle const SPARKLE_SPEED_MIN = 0.1; // Min speed for sparkle movement const SPARKLE_SPEED_MAX = 0.5; // Max speed for sparkle movement const SPARKLE_FADE_SPEED = 0.002; // How quickly sparkles fade in/out per frame (adjust for desired effect) // --- Global Pulse Configuration --- const BEAT_INTERVAL = 500; // 120 BPM = 500ms per beat let lastBeatTime = 0; let beatPulse = 0; // 0 to 1, indicates how far into the current beat we are const PULSE_DECAY = 0.03; // How quickly the pulse effect fades after the beat hits const PULSE_STRENGTH = 0.3; // Maximum scale increase (e.g., 30% larger) let images = []; // Array to hold loaded Image objects for background let currentImageIndex = 0; let nextImageIndex = 1; let cycleStartTime = 0; // Tracks the start time of the current background image display/fade cycle let sparkles = []; // Array to hold all sparkle objects // --- Sparkle Class (Pulsating) --- class Sparkle { constructor() { this.reset(); } reset() { this.x = Math.random() * canvas.width; this.y = Math.random() * canvas.height; // Store the base radius, which will be scaled in draw() this.baseRadius = SPARKLE_SIZE_MIN + Math.random() * (SPARKLE_SIZE_MAX - SPARKLE_SIZE_MIN); this.speedX = (Math.random() - 0.5) * (SPARKLE_SPEED_MAX - SPARKLE_SPEED_MIN) + SPARKLE_SPEED_MIN; this.speedY = (Math.random() - 0.5) * (SPARKLE_SPEED_MAX - SPARKLE_SPEED_MIN) + SPARKLE_SPEED_MIN; this.alpha = Math.random(); this.alphaDirection = Math.random() > 0.5 ? 1 : -1; this.hue = Math.random() * 360; } update() { // Movement this.x += this.speedX; this.y += this.speedY; // Bounce off edges if (this.x < 0 || this.x > canvas.width) this.speedX *= -1; if (this.y < 0 || this.y > canvas.height) this.speedY *= -1; // Fade in/out this.alpha += this.alphaDirection * SPARKLE_FADE_SPEED; if (this.alpha > 1) { this.alpha = 1; this.alphaDirection = -1; } else if (this.alpha < 0) { this.alpha = 0; this.alphaDirection = 1; } } draw() { // Calculate the current pulsating radius based on global beatPulse const currentRadius = this.baseRadius * (1 + beatPulse * PULSE_STRENGTH); ctx.globalAlpha = this.alpha; ctx.beginPath(); ctx.fillStyle = `hsl(${this.hue}, 100%, 75%)`; ctx.arc(this.x, this.y, currentRadius, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1; } } function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; // When canvas resizes, re-initialize sparkles to match new dimensions sparkles = []; // Clear existing sparkles for (let i = 0; i < NUM_SPARKLES; i++) { sparkles.push(new Sparkle()); } } window.addEventListener('resize', resizeCanvas); resizeCanvas(); // Initial call to set canvas size and create initial sparkles // Load background images from the hidden div const imageElements = Array.from(document.querySelectorAll('#imageAssets img')); let imagesLoadedCount = 0; imageElements.forEach(imgEl => { const img = new Image(); img.src = imgEl.src; img.onload = () => { images.push(img); imagesLoadedCount++; if (imagesLoadedCount === imageElements.length) { if (images.length < 2) { console.error("Need at least two images for a fade transition."); return; } nextImageIndex = (currentImageIndex + 1) % images.length; requestAnimationFrame(animate); // Start animation once backgrounds are ready } }; img.onerror = () => { console.error("Failed to load image:", imgEl.src); }; }); if (imageElements.length === 0) { console.error("No images found in the #imageAssets div. Please check your HTML and paths."); } // Function to draw an image to cover the entire canvas without stretching. function drawImageCover(image, alpha = 1) { if (!image) return; ctx.globalAlpha = alpha; const imgAspectRatio = image.width / image.height; const canvasAspectRatio = canvas.width / canvas.height; let sx, sy, sWidth, sHeight; let dx, dy, dWidth, dHeight; if (imgAspectRatio > canvasAspectRatio) { sHeight = image.height; sWidth = image.height * canvasAspectRatio; sx = (image.width - sWidth) / 2; sy = 0; } else { sWidth = image.width; sHeight = image.width / canvasAspectRatio; sx = 0; sy = (image.height - sHeight) / 2; } dx = 0; dy = 0; dWidth = canvas.width; dHeight = canvas.height; ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); ctx.globalAlpha = 1; // Reset alpha } // --- Main Animation Loop (Updated) --- function animate(currentTime) { requestAnimationFrame(animate); // 1. Clear the canvas for a fresh frame ctx.clearRect(0, 0, canvas.width, canvas.height); // --- Background Image Fading Logic --- if (images.length >= 2) { if (cycleStartTime === 0) { cycleStartTime = currentTime; } const elapsedTimeInCycle = currentTime - cycleStartTime; if (elapsedTimeInCycle < DISPLAY_DURATION) { drawImageCover(images[currentImageIndex], 1); } else if (elapsedTimeInCycle < TRANSITION_CYCLE) { const fadeTime = elapsedTimeInCycle - DISPLAY_DURATION; const fadeProgress = fadeTime / FADE_DURATION; const alphaCurrent = 1 - fadeProgress; const alphaNext = fadeProgress; drawImageCover(images[nextImageIndex], alphaNext); drawImageCover(images[currentImageIndex], alphaCurrent); } else { currentImageIndex = nextImageIndex; nextImageIndex = (currentImageIndex + 1) % images.length; cycleStartTime = currentTime; drawImageCover(images[currentImageIndex], 1); } } else if (images.length === 1) { // If only one image, just display it drawImageCover(images[0], 1); } // 3. Beat Pulse Logic (Controls sparkle pulsation) if (currentTime - lastBeatTime >= BEAT_INTERVAL) { lastBeatTime = currentTime; beatPulse = 1; // Beat hits! } beatPulse = Math.max(0, beatPulse - PULSE_DECAY); // Decay the pulse // --- Sparkle Animation Logic (on top of background) --- sparkles.forEach(sparkle => { sparkle.update(); sparkle.draw(); }); } window.onload = () => { // Animation starts after background images are loaded };