diff --git a/renderer.js b/renderer.js index 17dda34..8806a34 100644 --- a/renderer.js +++ b/renderer.js @@ -591,8 +591,17 @@ function updateAudioLevel(now) { } const helperDriven = latestSource === "helper"; - const breathing = helperDriven ? 0.003 : 0.028 * (Math.sin(now * 0.00023) + 1); - const response = helperDriven ? 0.2 : 0.018; + + let currentBreathingAmp = 0.028; + let currentPhysicsSmooth = 0.018; + + if (visualizerState.selectedTheme === "ambientWave" && visualizerState.ambientWave) { + currentBreathingAmp = parseFloat(visualizerState.ambientWave.breathingAmplitude) || 0.028; + currentPhysicsSmooth = parseFloat(visualizerState.ambientWave.physicsSmoothing) || 0.018; + } + + const breathing = helperDriven ? 0.003 : currentBreathingAmp * (Math.sin(now * 0.00023) + 1); + const response = helperDriven ? 0.2 : currentPhysicsSmooth; smoothedLevel += ((incomingLevel + breathing) - smoothedLevel) * response; } @@ -757,6 +766,8 @@ function renderFrame(now) { performanceMode: visualizerState.performanceMode }); } else { + const ambientSettings = getAmbientWaveSettings(); + context.globalCompositeOperation = ambientSettings.blendMode || "source-over"; drawAmbientWave({ context, width, @@ -765,9 +776,10 @@ function renderFrame(now) { smoothedLevel, latestSource, edgeGradient, - settings: getAmbientWaveSettings(), + settings: ambientSettings, performanceMode: visualizerState.performanceMode }); + context.globalCompositeOperation = "source-over"; } context.globalAlpha = 1; diff --git a/settings.html b/settings.html index 6f4070e..1079cfa 100644 --- a/settings.html +++ b/settings.html @@ -145,6 +145,8 @@

Advanced Settings

Fine-tune the appearance with granular sliders and custom gradients.

+
+
diff --git a/settings.js b/settings.js index 5d58390..0dbc7c5 100644 --- a/settings.js +++ b/settings.js @@ -23,7 +23,10 @@ document.addEventListener('DOMContentLoaded', () => { tone: { label: "Tone", options: ["blue", "purple", "warm", "custom"] }, sensitivity: { label: "Sensitivity", options: ["low", "medium", "high", "custom"] }, edgeMode: { label: "Edge Mode", options: ["top", "bottom", "both"] }, - glowStrength: { label: "Glow Strength", options: ["soft", "medium", "strong", "custom"] } + glowStrength: { label: "Glow Strength", options: ["soft", "medium", "strong", "custom"] }, + blendMode: { label: "Blend Mode", options: ["source-over", "screen", "overlay", "lighter"], advanced: true }, + physicsSmoothing: { label: "Physics Smoothness", options: ["0.01", "0.018", "0.03", "0.05"], advanced: true }, + breathingAmplitude: { label: "Breathing Amp.", options: ["0.01", "0.028", "0.05", "0.08"], advanced: true } }, reactiveBorder: { colorStyle: { label: "Color Style", options: ["rainbow", "neonBlue", "neonPurple", "warmGlow", "custom"] }, @@ -101,10 +104,16 @@ document.addEventListener('DOMContentLoaded', () => { function renderThemeSettings(themeId) { const container = document.getElementById('dynamic-theme-settings'); + const advancedContainer = document.getElementById('dynamic-advanced-theme-settings'); container.innerHTML = ''; + if (advancedContainer) advancedContainer.innerHTML = '

Theme Advanced Tweaks

Tweak the physics and compositing values directly.

'; const schema = THEMES_SCHEMA[themeId]; - if (!schema) return; + if (!schema) { + if (advancedContainer) advancedContainer.style.display = 'none'; + return; + } + let hasAdvanced = false; const currentThemeObj = cachedSettings[themeId] || {}; for (const [key, prop] of Object.entries(schema)) { @@ -137,7 +146,16 @@ document.addEventListener('DOMContentLoaded', () => { select.addEventListener('change', dispatchThemeUpdate); div.appendChild(select); - container.appendChild(div); + if (prop.advanced && advancedContainer) { + hasAdvanced = true; + advancedContainer.appendChild(div); + } else { + container.appendChild(div); + } + } + + if (advancedContainer) { + advancedContainer.style.display = hasAdvanced ? 'block' : 'none'; } updateAdvancedSliders(themeId); @@ -796,7 +814,7 @@ refreshThemeProfiles(); function dispatchThemeUpdate() { if (!window.visualizerSettings) return; const selectedTheme = themeSelector.value; - const dropdowns = document.querySelectorAll('#dynamic-theme-settings .theme-trigger'); + const dropdowns = document.querySelectorAll('#dynamic-theme-settings .theme-trigger, #dynamic-advanced-theme-settings .theme-trigger'); const themePatch = {}; dropdowns.forEach(dd => { diff --git a/settingsStore.js b/settingsStore.js index 3b3ca24..74b822e 100644 --- a/settingsStore.js +++ b/settingsStore.js @@ -42,7 +42,11 @@ const DEFAULT_SETTINGS = Object.freeze({ tone: "blue", sensitivity: "medium", edgeMode: "bottom", - glowStrength: "medium" + glowStrength: "medium", + tuning: "default", + blendMode: "source-over", + physicsSmoothing: 0.018, + breathingAmplitude: 0.028 }), reactiveBorder: Object.freeze({ colorStyle: "rainbow", @@ -331,7 +335,11 @@ function sanitizeAmbientWave(input = {}) { edgeMode: pick(input.edgeMode, VALID_EDGE_MODES, DEFAULT_SETTINGS.ambientWave.edgeMode), glowStrength: pick(input.glowStrength, VALID_GLOW_STRENGTHS, DEFAULT_SETTINGS.ambientWave.glowStrength), customColors: sanitizeCustomColors(input.customColors, DEFAULT_SETTINGS.customColors), - customSensitivity: sanitizeSensitivity(input.customSensitivity) + customSensitivity: sanitizeSensitivity(input.customSensitivity), + tuning: typeof input.tuning === 'string' ? input.tuning : DEFAULT_SETTINGS.ambientWave.tuning, + blendMode: typeof input.blendMode === 'string' ? input.blendMode : DEFAULT_SETTINGS.ambientWave.blendMode, + physicsSmoothing: typeof input.physicsSmoothing === 'number' ? input.physicsSmoothing : DEFAULT_SETTINGS.ambientWave.physicsSmoothing, + breathingAmplitude: typeof input.breathingAmplitude === 'number' ? input.breathingAmplitude : DEFAULT_SETTINGS.ambientWave.breathingAmplitude }; }