diff --git a/src/lib/components/AudioVisualization.svelte b/src/lib/components/AudioVisualization.svelte index beee6ba..b368218 100644 --- a/src/lib/components/AudioVisualization.svelte +++ b/src/lib/components/AudioVisualization.svelte @@ -11,7 +11,11 @@ }>(); let particles: Particle[] = []; - const numParticles = 40; // Reduced from 60 for better performance with two visualizations + // Detect mobile for performance optimization + const isMobile = typeof window !== 'undefined' && + (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || + window.innerWidth < 768); + const numParticles = isMobile ? 15 : 40; // Significantly reduced for mobile let audioData: Float32Array | null = null; // Cache frequently used calculations @@ -19,6 +23,10 @@ let midRange = 0; let trebleStart = 0; + // Track if user is scrolling to reduce load + let isScrolling = false; + let scrollTimeout: number; + class Particle { x: number; y: number; @@ -42,13 +50,14 @@ this.alpha = p.random(100, 255); } - update(p: p5, audioLevel: number, bass: number, mid: number) { + update(p: p5, audioLevel: number, bass: number, mid: number, isMobile: boolean) { // Base movement - gentle constant speed this.x += this.vx; this.y += this.vy; - // Subtle audio reactive displacement (only every 3rd frame for performance) - if (p.frameCount % 3 === 0) { + // Subtle audio reactive displacement (less frequent on mobile) + const updateFrequency = isMobile ? 5 : 3; + if (p.frameCount % updateFrequency === 0) { const displacement = audioLevel * 20; const angle = p.noise(this.x * 0.01, this.y * 0.01, p.frameCount * 0.01) * p.TWO_PI; this.x += p.cos(angle) * displacement * 0.05; @@ -86,7 +95,8 @@ const sketch = (p: p5) => { p.setup = () => { p.createCanvas(width, height); - p.frameRate(30); // Reduce from 60fps to 30fps for better performance + // Lower framerate on mobile for better performance + p.frameRate(isMobile ? 20 : 30); p.background(0); // Initialize particles @@ -94,11 +104,22 @@ for (let i = 0; i < numParticles; i++) { particles.push(new Particle(p, i)); } + + // Handle scroll events to pause visualization + if (typeof window !== 'undefined') { + window.addEventListener('scroll', () => { + isScrolling = true; + clearTimeout(scrollTimeout); + scrollTimeout = window.setTimeout(() => { + isScrolling = false; + }, 150); + }, { passive: true }); + } }; p.draw = () => { - // Pause visualization when document is hidden (tab not focused) - if (document.hidden) { + // Pause visualization when document is hidden, scrolling, or on mobile during heavy load + if (document.hidden || (isMobile && isScrolling)) { return; } p.background(0, 30); // Fade effect @@ -153,12 +174,12 @@ // Update and display particles for (let i = 0; i < particles.length; i++) { - particles[i].update(p, audioLevel, bass, mid); + particles[i].update(p, audioLevel, bass, mid, isMobile); particles[i].display(p, audioLevel); } - // Draw connections less frequently (every other frame) for better performance - if (p.frameCount % 2 === 0) { + // Draw connections - skip on mobile for better performance + if (!isMobile && p.frameCount % 2 === 0) { for (let i = 0; i < particles.length; i++) { // Only check next 5 particles instead of all, reduces O(n²) significantly for (let j = i + 1; j < Math.min(i + 6, particles.length); j++) { diff --git a/src/lib/generators/air-quality/AirQualityGen.svelte b/src/lib/generators/air-quality/AirQualityGen.svelte index 6912260..f2688a6 100644 --- a/src/lib/generators/air-quality/AirQualityGen.svelte +++ b/src/lib/generators/air-quality/AirQualityGen.svelte @@ -19,6 +19,12 @@ let isPlaying = $state(false); let isInitialized = $state(false); + // Detect mobile for visualization sizing + const isMobile = typeof window !== 'undefined' && + (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || + window.innerWidth < 768); + const vizSize = isMobile ? 280 : 400; + // Audio components let noiseSynth: Tone.NoiseSynth | null = null; let reverb: Tone.Reverb | null = null; @@ -192,6 +198,26 @@ // Lifecycle onMount(() => { initializeAudio(); + + // Handle page visibility changes (screen sleep/wake, tab switching) + const handleVisibilityChange = async () => { + if (!document.hidden && isPlaying) { + // Page is visible again - resume audio context if suspended + if (Tone.getContext().state === 'suspended') { + await Tone.getContext().resume(); + } + // Restart loop if it stopped + if (loop && loop.state !== 'started') { + loop.start(0); + } + } + }; + + document.addEventListener('visibilitychange', handleVisibilityChange); + + return () => { + document.removeEventListener('visibilitychange', handleVisibilityChange); + }; }); onDestroy(() => { @@ -243,6 +269,6 @@
- +
diff --git a/src/lib/generators/weather/WeatherGen.svelte b/src/lib/generators/weather/WeatherGen.svelte index d9460e3..d93efbb 100644 --- a/src/lib/generators/weather/WeatherGen.svelte +++ b/src/lib/generators/weather/WeatherGen.svelte @@ -31,6 +31,12 @@ let isPlaying = $state(false); let isInitialized = $state(false); + // Detect mobile for visualization sizing + const isMobile = typeof window !== 'undefined' && + (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || + window.innerWidth < 768); + const vizSize = isMobile ? 280 : 400; + // Audio components let synth: Tone.PolySynth | null = null; let arpSynth: Tone.Synth | null = null; @@ -397,6 +403,26 @@ // Lifecycle onMount(() => { initializeAudio(); + + // Handle page visibility changes (screen sleep/wake, tab switching) + const handleVisibilityChange = async () => { + if (!document.hidden && isPlaying) { + // Page is visible again - resume audio context if suspended + if (Tone.getContext().state === 'suspended') { + await Tone.getContext().resume(); + } + // Restart transport if it stopped + if (Tone.getTransport().state !== 'started') { + Tone.getTransport().start(); + } + } + }; + + document.addEventListener('visibilitychange', handleVisibilityChange); + + return () => { + document.removeEventListener('visibilitychange', handleVisibilityChange); + }; }); onDestroy(() => { @@ -471,6 +497,6 @@
- +