From 60af0ade65d3db486d8a65ac2940cd44ccb4039d Mon Sep 17 00:00:00 2001 From: "D.Bhavya" Date: Sat, 27 Jun 2026 18:24:54 +0530 Subject: [PATCH 1/2] feat: Add smooth animated opacity transition for Focus Mode --- renderer.js | 4 ++ settings.html | 42 ++++++++++++++++++ settings.js | 110 +++++++++++++++++++++++++++++++++++++++++++++++ settingsStore.js | 8 +++- 4 files changed, 162 insertions(+), 2 deletions(-) diff --git a/renderer.js b/renderer.js index b289032..4fe073c 100644 --- a/renderer.js +++ b/renderer.js @@ -867,6 +867,10 @@ if (window.visualizerSettings) { // Focus Mode: smoothly fade canvas opacity based on user activity if (typeof window.visualizerSettings.onFocusModeOpacity === "function") { window.visualizerSettings.onFocusModeOpacity(({ opacity }) => { + const duration = (visualizerState.focusMode && typeof visualizerState.focusMode.transitionDuration === "number") + ? visualizerState.focusMode.transitionDuration + : 1.5; + canvas.style.transition = `opacity ${duration}s ease`; canvas.style.opacity = typeof opacity === "number" ? String(opacity) : "1"; }); } diff --git a/settings.html b/settings.html index f846e36..a2195a8 100644 --- a/settings.html +++ b/settings.html @@ -584,6 +584,48 @@

Framerate Target

+
+

Focus Mode

+

Dims the visualizer during user activity to keep the desktop clean, and restores full brightness when idle.

+ +
+
+ Enable Focus Mode +

Automatically dim visualizer when you are active.

+
+ +
+ + +
+

System Integration

Configure how Paraline behaves with your operating system.

diff --git a/settings.js b/settings.js index d6d03f2..c847405 100644 --- a/settings.js +++ b/settings.js @@ -246,6 +246,64 @@ document.addEventListener('DOMContentLoaded', () => { }); } + // ---------------------------------------- + // FOCUS MODE BINDINGS + // ---------------------------------------- + const focusModeCheckbox = document.getElementById('focus-mode-checkbox'); + const focusModeSettingsContainer = document.getElementById('focus-mode-settings-container'); + const focusModeDimOpacity = document.getElementById('focus-mode-dim-opacity'); + const focusModeIdleTimeout = document.getElementById('focus-mode-idle-timeout'); + const focusModeTransitionDuration = document.getElementById('focus-mode-transition-duration'); + + function toggleFocusModeControls(isEnabled) { + if (focusModeSettingsContainer) { + focusModeSettingsContainer.style.display = isEnabled ? 'block' : 'none'; + } + } + + function updateFocusModeSetting(patch) { + if (window.visualizerSettings) { + const currentFocusMode = cachedSettings.focusMode || {}; + const nextFocusMode = { ...currentFocusMode, ...patch }; + cachedSettings.focusMode = nextFocusMode; // Optimistic local cache update + window.visualizerSettings.update({ + focusMode: nextFocusMode + }); + } + } + + if (focusModeCheckbox) { + focusModeCheckbox.addEventListener('change', (e) => { + const isChecked = e.target.checked; + toggleFocusModeControls(isChecked); + updateFocusModeSetting({ enabled: isChecked }); + }); + } + + if (focusModeDimOpacity) { + focusModeDimOpacity.addEventListener('input', (e) => { + const val = parseFloat(e.target.value) / 100; + document.getElementById('val-focus-mode-dim-opacity').textContent = `${e.target.value}%`; + updateFocusModeSetting({ dimOpacity: val }); + }); + } + + if (focusModeIdleTimeout) { + focusModeIdleTimeout.addEventListener('input', (e) => { + const val = parseInt(e.target.value, 10) || 5; + document.getElementById('val-focus-mode-idle-timeout').textContent = `${val}s`; + updateFocusModeSetting({ idleTimeout: val }); + }); + } + + if (focusModeTransitionDuration) { + focusModeTransitionDuration.addEventListener('input', (e) => { + const val = parseFloat(e.target.value) || 1.5; + document.getElementById('val-focus-mode-transition-duration').textContent = `${val.toFixed(1)}s`; + updateFocusModeSetting({ transitionDuration: val }); + }); + } + const themeSelector = document.getElementById('theme-selector'); themeSelector.addEventListener('change', (e) => { renderThemeSettings(e.target.value); @@ -671,6 +729,33 @@ refreshThemeProfiles(); nightThemeSelect.value = automation.nightTheme || "reactiveBorder"; } } + + // Load focus mode settings + if (settings.focusMode) { + const fm = settings.focusMode; + if (focusModeCheckbox) { + focusModeCheckbox.checked = !!fm.enabled; + toggleFocusModeControls(fm.enabled); + } + if (focusModeDimOpacity) { + const pct = Math.round((fm.dimOpacity !== undefined ? fm.dimOpacity : 0.1) * 100); + focusModeDimOpacity.value = pct; + const valEl = document.getElementById('val-focus-mode-dim-opacity'); + if (valEl) valEl.textContent = `${pct}%`; + } + if (focusModeIdleTimeout) { + const secs = fm.idleTimeout !== undefined ? fm.idleTimeout : 5; + focusModeIdleTimeout.value = secs; + const valEl = document.getElementById('val-focus-mode-idle-timeout'); + if (valEl) valEl.textContent = `${secs}s`; + } + if (focusModeTransitionDuration) { + const duration = fm.transitionDuration !== undefined ? fm.transitionDuration : 1.5; + focusModeTransitionDuration.value = duration; + const valEl = document.getElementById('val-focus-mode-transition-duration'); + if (valEl) valEl.textContent = `${duration.toFixed(1)}s`; + } + } // set custom variables into UI if they exist globally or on the active theme if (settings.customColors && settings.customColors.length === 3) { @@ -713,6 +798,31 @@ refreshThemeProfiles(); } } + // Sync focus mode properties if updated from outside + if (nextSettings.focusMode) { + const fm = nextSettings.focusMode; + if (focusModeCheckbox && fm.enabled !== undefined) { + focusModeCheckbox.checked = !!fm.enabled; + toggleFocusModeControls(fm.enabled); + } + if (focusModeDimOpacity && fm.dimOpacity !== undefined) { + const pct = Math.round(fm.dimOpacity * 100); + focusModeDimOpacity.value = pct; + const valEl = document.getElementById('val-focus-mode-dim-opacity'); + if (valEl) valEl.textContent = `${pct}%`; + } + if (focusModeIdleTimeout && fm.idleTimeout !== undefined) { + focusModeIdleTimeout.value = fm.idleTimeout; + const valEl = document.getElementById('val-focus-mode-idle-timeout'); + if (valEl) valEl.textContent = `${fm.idleTimeout}s`; + } + if (focusModeTransitionDuration && fm.transitionDuration !== undefined) { + focusModeTransitionDuration.value = fm.transitionDuration; + const valEl = document.getElementById('val-focus-mode-transition-duration'); + if (valEl) valEl.textContent = `${fm.transitionDuration.toFixed(1)}s`; + } + } + if (nextSettings.paused !== undefined) { updatePauseButtonState(nextSettings.paused); } diff --git a/settingsStore.js b/settingsStore.js index 3edcb52..b29ad23 100644 --- a/settingsStore.js +++ b/settingsStore.js @@ -91,7 +91,8 @@ const DEFAULT_SETTINGS = Object.freeze({ focusMode: Object.freeze({ enabled: false, dimOpacity: 0.1, - idleTimeout: 5 + idleTimeout: 5, + transitionDuration: 1.5 }), auroraDrift: Object.freeze({ // Standard settings @@ -509,7 +510,10 @@ function sanitizeFocusMode(input = {}) { const idleTimeout = typeof input.idleTimeout === "number" && input.idleTimeout >= 1 && input.idleTimeout <= 60 ? input.idleTimeout : DEFAULT_SETTINGS.focusMode.idleTimeout; - return { enabled, dimOpacity, idleTimeout }; + const transitionDuration = typeof input.transitionDuration === "number" && input.transitionDuration >= 0.1 && input.transitionDuration <= 10 + ? input.transitionDuration + : DEFAULT_SETTINGS.focusMode.transitionDuration; + return { enabled, dimOpacity, idleTimeout, transitionDuration }; } function sanitizeSettings(input = {}) { From fd5ea7964e10a47e2f25f2d6b89b3d56d2b8b91d Mon Sep 17 00:00:00 2001 From: "D.Bhavya" Date: Sun, 28 Jun 2026 15:14:15 +0530 Subject: [PATCH 2/2] fix(focusMode): resolve CodeRabbit PR review suggestions, remove duplicates and clamp out-of-range inputs --- settings.html | 66 +++++---------- settings.js | 162 +++++++------------------------------ settingsStore.js | 12 +-- test/settingsStore.test.js | 33 ++++++++ 4 files changed, 90 insertions(+), 183 deletions(-) diff --git a/settings.html b/settings.html index 3097e46..392430d 100644 --- a/settings.html +++ b/settings.html @@ -545,16 +545,6 @@

Framerate Target

-

Focus Mode

-

Dims the visualizer during user activity to keep the desktop clean, and restores full brightness when idle.

- -
-
- Enable Focus Mode -

Automatically dim visualizer when you are active.

-
-
-