diff --git a/public/patterns/light-pattern.svg b/public/patterns/light-pattern.svg new file mode 100644 index 0000000..e69de29 diff --git a/src/components/ThemeBackground.astro b/src/components/ThemeBackground.astro new file mode 100644 index 0000000..1b2d3f4 --- /dev/null +++ b/src/components/ThemeBackground.astro @@ -0,0 +1,96 @@ +--- +/** + * ThemeBackground component + * + * Provides subtle theme-specific background patterns that change with the theme. + * These patterns add visual interest without distracting from the content. + * + * @component + */ +--- + + + + diff --git a/src/components/ThemeScheduler.astro b/src/components/ThemeScheduler.astro new file mode 100644 index 0000000..f6265d3 --- /dev/null +++ b/src/components/ThemeScheduler.astro @@ -0,0 +1,88 @@ +--- +/** + * ThemeScheduler component + * + * Provides automatic theme switching based on time of day. + * - Day time (7 AM to 7 PM): Light theme + * - Night time (7 PM to 7 AM): Dark theme + * + * This component only runs its logic if the user has selected the "auto" theme mode. + * + * @component + */ +--- + + diff --git a/src/components/ThemeToggle.astro b/src/components/ThemeToggle.astro index f252431..bda3c9d 100644 --- a/src/components/ThemeToggle.astro +++ b/src/components/ThemeToggle.astro @@ -1,46 +1,284 @@ --- +/** + * Enhanced ThemeToggle component + * + * Provides a theme toggle button with a dropdown menu for additional theme options: + * - Light Mode + * - Dark Mode + * - System Preference + * - Time-Based (automatic switching based on time of day) + * + * @component + */ +--- ---- - - - - diff --git a/src/components/ThemeTransitionEffect.astro b/src/components/ThemeTransitionEffect.astro new file mode 100644 index 0000000..f28c742 --- /dev/null +++ b/src/components/ThemeTransitionEffect.astro @@ -0,0 +1,94 @@ +--- +/** + * ThemeTransitionEffect component + * + * Provides a visual transition effect when switching between light and dark themes. + * Creates a radial gradient that expands from the theme toggle button. + * + * @component + */ +--- + + + + + + diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 2982f93..a56c2f1 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -3,6 +3,9 @@ import Footer from "../components/Footer.astro"; import Header from "../components/Header.astro"; import SearchScript from "../components/SearchScript.astro"; import LoadingOverlay from "../components/common/LoadingOverlay.astro"; +import ThemeTransitionEffect from "../components/ThemeTransitionEffect.astro"; +import ThemeBackground from "../components/ThemeBackground.astro"; +import ThemeScheduler from "../components/ThemeScheduler.astro"; import "../styles/global.css"; --- @@ -116,6 +119,12 @@ import "../styles/global.css"; } + + + + + + @@ -123,6 +132,10 @@ import "../styles/global.css"; + + + +
diff --git a/src/scripts/ThemeTransition.js b/src/scripts/ThemeTransition.js new file mode 100644 index 0000000..5c9dd0d --- /dev/null +++ b/src/scripts/ThemeTransition.js @@ -0,0 +1,116 @@ +/** + * Theme Transition Script + * + * Handles element-specific animations during theme changes. + * Applies staggered animations to different elements when the theme changes. + */ + +document.addEventListener('DOMContentLoaded', () => { + // Listen for theme change events + window.addEventListener('theme-changed', (e) => { + const customEvent = e instanceof CustomEvent ? e : null; + const theme = customEvent?.detail?.theme || (document.documentElement.classList.contains('dark') ? 'dark' : 'light'); + + // Skip animations if reduced motion is preferred + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { + return; + } + + // Animate headings with staggered delay + document.querySelectorAll('h1, h2, h3').forEach((heading, index) => { + // Remove any existing animation classes + heading.classList.remove('theme-animate-slide'); + + // Force a reflow to restart the animation + void heading.offsetWidth; + + // Add animation class with delay based on index + setTimeout(() => { + heading.classList.add('theme-animate-slide'); + }, 50 + (index * 30)); + }); + + // Animate cards and sections with staggered delay + document.querySelectorAll('.card, article, section:not(section section)').forEach((element, index) => { + // Remove any existing animation classes + element.classList.remove('theme-animate-scale'); + + // Force a reflow to restart the animation + void element.offsetWidth; + + // Add animation class with delay based on index + setTimeout(() => { + element.classList.add('theme-animate-scale'); + }, 100 + (index * 40)); + }); + + // Animate images and icons + document.querySelectorAll('img, svg').forEach((element, index) => { + // Remove any existing animation classes + element.classList.remove('theme-animate-fade'); + + // Force a reflow to restart the animation + void element.offsetWidth; + + // Add animation class with delay based on index + setTimeout(() => { + element.classList.add('theme-animate-fade'); + }, 150 + (index * 20)); + }); + + // Add theme-specific classes to enhance certain elements + if (theme === 'dark') { + // Dark theme enhancements + document.querySelectorAll('code, pre').forEach(element => { + element.classList.add('dark-theme-code'); + }); + + // Add subtle glow to important buttons in dark mode + document.querySelectorAll('.button-primary, .cta-button').forEach(element => { + element.classList.add('dark-theme-glow'); + }); + } else { + // Light theme enhancements + document.querySelectorAll('code, pre').forEach(element => { + element.classList.remove('dark-theme-code'); + }); + + // Remove glow from buttons in light mode + document.querySelectorAll('.button-primary, .cta-button').forEach(element => { + element.classList.remove('dark-theme-glow'); + }); + } + }); +}); + +// Add CSS classes for theme-specific enhancements +const addThemeStyles = () => { + // Create a style element + const style = document.createElement('style'); + + // Add CSS for dark theme code blocks + style.textContent = ` + .dark-theme-code { + box-shadow: 0 0 8px rgba(254, 128, 25, 0.3); + } + + .dark-theme-glow { + box-shadow: 0 0 12px rgba(254, 128, 25, 0.4); + } + + @media (prefers-reduced-motion: reduce) { + .theme-animate-fade, + .theme-animate-slide, + .theme-animate-scale { + animation: none !important; + transition: none !important; + } + } + `; + + // Append the style element to the head + document.head.appendChild(style); +}; + +// Add the styles when the DOM is ready +document.addEventListener('DOMContentLoaded', addThemeStyles); diff --git a/src/styles/global.css b/src/styles/global.css index 5623dcb..1cd6e94 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -8,9 +8,21 @@ html:not(.theme-loaded) body { display: none; } -/* Ensure smooth transitions between themes */ +/* Enhanced transitions between themes */ html.theme-loaded body { - transition: background-color 0.3s ease, color 0.3s ease; + transition: background-color 0.5s cubic-bezier(0.4, 0, 0.2, 1), + color 0.5s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Add transition to all themed elements */ +.theme-transition-element { + transition: background-color 0.5s cubic-bezier(0.4, 0, 0.2, 1), + color 0.5s cubic-bezier(0.4, 0, 0.2, 1), + border-color 0.5s cubic-bezier(0.4, 0, 0.2, 1), + fill 0.5s cubic-bezier(0.4, 0, 0.2, 1), + stroke 0.5s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1), + box-shadow 0.5s cubic-bezier(0.4, 0, 0.2, 1); } /* Font loading states */ @@ -181,16 +193,58 @@ html.fonts-loaded body { width: 100%; } - .zag-transition { - @media (prefers-reduced-motion: no-preference) { - transition: - background-color var(--zag-transition-duration) var(--zag-transition-timing-function), - color var(--zag-transition-duration) var(--zag-transition-timing-function), - fill var(--zag-transition-duration) var(--zag-transition-timing-function), - border-color var(--zag-transition-duration) var(--zag-transition-timing-function), - transform var(--zag-transition-duration) var(--zag-transition-timing-function); - } +.zag-transition { + @media (prefers-reduced-motion: no-preference) { + transition: + background-color var(--zag-transition-duration) var(--zag-transition-timing-function), + color var(--zag-transition-duration) var(--zag-transition-timing-function), + fill var(--zag-transition-duration) var(--zag-transition-timing-function), + border-color var(--zag-transition-duration) var(--zag-transition-timing-function), + transform var(--zag-transition-duration) var(--zag-transition-timing-function), + opacity var(--zag-transition-duration) var(--zag-transition-timing-function), + box-shadow var(--zag-transition-duration) var(--zag-transition-timing-function); } +} + +/* Theme transition animations for specific elements */ +@keyframes theme-fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes theme-slide-up { + from { + opacity: 0.5; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes theme-scale-in { + from { + opacity: 0.8; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} + +.theme-animate-fade { + animation: theme-fade-in 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.theme-animate-slide { + animation: theme-slide-up 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} + +.theme-animate-scale { + animation: theme-scale-in 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards; +} /* Base backgrounds and text */ .zag-bg {