diff --git a/webui/index.html b/webui/index.html index fb2c5573..662df1d7 100644 --- a/webui/index.html +++ b/webui/index.html @@ -4195,6 +4195,13 @@ Audio-reactive visualizer on the sidebar edge when music is playing +
+ + Animated particle effects behind each page. Disable to reduce GPU usage. +
diff --git a/webui/static/particles.js b/webui/static/particles.js index e54dc2b8..a390db9c 100644 --- a/webui/static/particles.js +++ b/webui/static/particles.js @@ -21,9 +21,15 @@ // ── Helpers ── + let _cachedAccent = '29, 185, 84'; + let _accentCheckFrame = 0; function getAccentRGB() { - const s = getComputedStyle(document.documentElement).getPropertyValue('--accent-rgb').trim(); - return s || '29, 185, 84'; + // Only re-read CSS variable every 60 frames (~1s) to avoid getComputedStyle overhead + if (_accentCheckFrame++ % 60 === 0) { + const s = getComputedStyle(document.documentElement).getPropertyValue('--accent-rgb').trim(); + if (s) _cachedAccent = s; + } + return _cachedAccent; } // Shift an "r, g, b" accent string by a hue offset (degrees), cached per base color @@ -2286,8 +2292,9 @@ stop }; - // Auto-start for initial page + // Auto-start for initial page (respect particles toggle) requestAnimationFrame(() => { + if (window._particlesEnabled === false) return; const activePage = document.querySelector('.page.active'); if (activePage) { const pageId = activePage.id.replace('-page', ''); diff --git a/webui/static/script.js b/webui/static/script.js index 36a8846b..71e3d755 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -706,6 +706,23 @@ function applyAccentColor(hex) { if (swatch) swatch.style.background = hex; } +function applyParticlesSetting(enabled) { + const canvas = document.getElementById('page-particles-canvas'); + if (canvas) canvas.style.display = enabled ? '' : 'none'; + if (window.pageParticles) { + if (enabled) { + const activePage = document.querySelector('.page.active'); + if (activePage) { + window.pageParticles.setPage(activePage.id.replace('-page', '')); + } + } else { + window.pageParticles.stop(); + } + } + window._particlesEnabled = enabled; + localStorage.setItem('soulsync-particles', String(enabled)); +} + function initAccentColorListeners() { const presetSelect = document.getElementById('accent-preset'); const customGroup = document.getElementById('custom-color-group'); @@ -728,12 +745,27 @@ function initAccentColorListeners() { applyAccentColor(customPicker.value); }); } + + // Particles toggle — apply immediately on change + const particlesCheckbox = document.getElementById('particles-enabled'); + if (particlesCheckbox) { + particlesCheckbox.addEventListener('change', () => { + applyParticlesSetting(particlesCheckbox.checked); + }); + } } // Bootstrap accent from localStorage instantly (prevents default-color flash) (function() { const saved = localStorage.getItem('soulsync-accent'); if (saved) applyAccentColor(saved); + // Bootstrap particles setting from localStorage + const particlesSaved = localStorage.getItem('soulsync-particles'); + if (particlesSaved === 'false') { + window._particlesEnabled = false; + const canvas = document.getElementById('page-particles-canvas'); + if (canvas) canvas.style.display = 'none'; + } })(); // ── Profile System ───────────────────────────────────────────── @@ -1819,7 +1851,7 @@ function navigateToPage(pageId) { loadPageData(pageId); // Update page background particles - if (window.pageParticles) window.pageParticles.setPage(pageId); + if (window.pageParticles && window._particlesEnabled !== false) window.pageParticles.setPage(pageId); } // REPLACE your old loadPageData function with this one: @@ -4641,6 +4673,12 @@ async function loadSettingsData() { if (vizSelect) vizSelect.value = vizType; sidebarVisualizerType = vizType; + // Background particles toggle + const particlesEnabled = settings.ui_appearance?.particles_enabled !== false; // default true + const particlesCheckbox = document.getElementById('particles-enabled'); + if (particlesCheckbox) particlesCheckbox.checked = particlesEnabled; + applyParticlesSetting(particlesEnabled); + // Populate Logging information (read-only) document.getElementById('log-level-display').textContent = settings.logging?.level || 'INFO'; document.getElementById('log-path-display').textContent = settings.logging?.path || 'logs/app.log'; @@ -5383,7 +5421,8 @@ async function saveSettings(quiet = false) { ui_appearance: { accent_preset: document.getElementById('accent-preset')?.value || '#1db954', accent_color: document.getElementById('accent-custom-color')?.value || '#1db954', - sidebar_visualizer: document.getElementById('sidebar-visualizer-type')?.value || 'bars' + sidebar_visualizer: document.getElementById('sidebar-visualizer-type')?.value || 'bars', + particles_enabled: document.getElementById('particles-enabled')?.checked !== false }, youtube: { cookies_browser: document.getElementById('youtube-cookies-browser').value,