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 {