diff --git a/src/frontend/src/assets/aspiry/aspiry-night-vision.png b/src/frontend/src/assets/aspiry/aspiry-night-vision.png new file mode 100644 index 000000000..939279b24 Binary files /dev/null and b/src/frontend/src/assets/aspiry/aspiry-night-vision.png differ diff --git a/src/frontend/src/assets/aspiry/aspiry-sunglasses.png b/src/frontend/src/assets/aspiry/aspiry-sunglasses.png new file mode 100644 index 000000000..f986346f6 Binary files /dev/null and b/src/frontend/src/assets/aspiry/aspiry-sunglasses.png differ diff --git a/src/frontend/src/components/FooterPreferences.astro b/src/frontend/src/components/FooterPreferences.astro index 13863dba8..1df78b029 100644 --- a/src/frontend/src/components/FooterPreferences.astro +++ b/src/frontend/src/components/FooterPreferences.astro @@ -170,28 +170,40 @@ const themeLabels = { } } - function applyTheme(theme: ThemeOption) { - // Use Starlight's theme system - setStoredPreference('starlight-theme', theme); - - // Apply theme immediately - const root = document.documentElement; - if (theme === 'auto') { - const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - root.dataset.theme = prefersDark ? 'dark' : 'light'; - } else { - root.dataset.theme = theme; + function getThemeOrigin(element: Element, event?: MouseEvent) { + if (event?.detail) { + return { x: event.clientX, y: event.clientY }; } - updateThemeToggle(theme); + const bounds = element.getBoundingClientRect(); + return { + x: bounds.left + bounds.width / 2, + y: bounds.top + bounds.height / 2, + }; + } - // Sync with Starlight's header theme select if present - const headerThemeSelect = document.querySelector( - 'starlight-theme-select select' - ); - if (headerThemeSelect) { - headerThemeSelect.value = theme; + function applyTheme(theme: ThemeOption, origin: { x: number; y: number }) { + const transitionApi = ( + window as Window & { + AspireThemeTransition?: { + setTheme: (theme: ThemeOption, origin?: { x: number; y: number }) => void; + }; + } + ).AspireThemeTransition; + + if (transitionApi) { + transitionApi.setTheme(theme, origin); + return; } + + setStoredPreference('starlight-theme', theme); + document.documentElement.dataset.theme = + theme === 'auto' + ? window.matchMedia('(prefers-color-scheme: dark)').matches + ? 'dark' + : 'light' + : theme; + updateThemeToggle(theme); } // Get current theme from Starlight's storage @@ -199,11 +211,11 @@ const themeLabels = { updateThemeToggle(isThemeOption(storedTheme) ? storedTheme : 'auto'); for (const button of themeButtons) { - button.addEventListener('click', () => { + button.addEventListener('click', (event) => { const theme = button.dataset.themeOption; if (isThemeOption(theme)) { - applyTheme(theme); + applyTheme(theme, getThemeOrigin(button, event)); } }); } @@ -229,9 +241,8 @@ const themeLabels = { : (currentIndex + 1) % themeOptions.length; const nextTheme = themeOptions[nextIndex]; - applyTheme(nextTheme); - const nextButton = themeButtons.find((button) => button.dataset.themeOption === nextTheme); + applyTheme(nextTheme, getThemeOrigin(nextButton ?? themeToggle)); nextButton?.focus(); }); } diff --git a/src/frontend/src/components/ThemeTransition.astro b/src/frontend/src/components/ThemeTransition.astro new file mode 100644 index 000000000..7ac7a101d --- /dev/null +++ b/src/frontend/src/components/ThemeTransition.astro @@ -0,0 +1,577 @@ +--- +interface Props { + sunglassesSrc?: string; + nightVisionSrc?: string; +} + +const { sunglassesSrc = '', nightVisionSrc = '' } = Astro.props; +--- + + + + + + diff --git a/src/frontend/src/components/starlight/Footer.astro b/src/frontend/src/components/starlight/Footer.astro index 49432c8b3..f65b9fd96 100644 --- a/src/frontend/src/components/starlight/Footer.astro +++ b/src/frontend/src/components/starlight/Footer.astro @@ -8,6 +8,9 @@ import FooterPreferences from '@components/FooterPreferences.astro'; import FooterResources from '@components/FooterResources.astro'; import FooterSocials from '@components/FooterSocials.astro'; import ScrollToTop from '@components/ScrollToTop.astro'; +import ThemeTransition from '@components/ThemeTransition.astro'; +import aspiryNightVision from '@assets/aspiry/aspiry-night-vision.png'; +import aspirySunglasses from '@assets/aspiry/aspiry-sunglasses.png'; import aspireLogo from '@assets/aspire-logo-256.svg'; import { isHomepage } from '@utils/helpers'; @@ -20,6 +23,7 @@ const commit = (import.meta.env.PUBLIC_GIT_COMMIT_ID || import.meta.env.GIT_COMM ); --- +