Add several options for theme selection
Some checks failed
Build and Deploy / build (push) Failing after 29s

This commit is contained in:
Justin Deal 2025-05-03 14:37:23 -07:00
parent fe566f9e1a
commit ca9cd27acd
8 changed files with 748 additions and 49 deletions

View File

View File

@ -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
*/
---
<div class="theme-background" aria-hidden="true">
<div class="light-pattern"></div>
<div class="dark-pattern"></div>
</div>
<style>
.theme-background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: -1;
overflow: hidden;
}
/* Light theme pattern using CSS gradients */
.light-pattern {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.03;
transition: opacity 0.5s ease;
background-color: rgba(235, 219, 178, 0.01);
background-image:
/* Grid pattern */
linear-gradient(to right, rgba(60, 56, 54, 0.1) 1px, transparent 1px),
linear-gradient(to bottom, rgba(60, 56, 54, 0.1) 1px, transparent 1px),
/* Diagonal lines */
linear-gradient(45deg, rgba(214, 93, 14, 0.1) 25%, transparent 25%),
/* Dots pattern */
radial-gradient(rgba(184, 187, 38, 0.2) 2px, transparent 2px);
background-size:
20px 20px, /* Grid X */
20px 20px, /* Grid Y */
100px 100px, /* Diagonal lines */
40px 40px; /* Dots */
background-position:
0 0, /* Grid X */
0 0, /* Grid Y */
0 0, /* Diagonal lines */
20px 20px; /* Dots */
}
/* Dark theme pattern using CSS gradients */
.dark-pattern {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 0.5s ease;
background-color: rgba(40, 40, 40, 0.01);
background-image:
/* Grid pattern */
linear-gradient(to right, rgba(235, 219, 178, 0.1) 1px, transparent 1px),
linear-gradient(to bottom, rgba(235, 219, 178, 0.1) 1px, transparent 1px),
/* Diagonal lines */
linear-gradient(45deg, rgba(254, 128, 25, 0.1) 25%, transparent 25%),
/* Dots pattern */
radial-gradient(rgba(184, 187, 38, 0.2) 2px, transparent 2px);
background-size:
20px 20px, /* Grid X */
20px 20px, /* Grid Y */
100px 100px, /* Diagonal lines */
40px 40px; /* Dots */
background-position:
0 0, /* Grid X */
0 0, /* Grid Y */
0 0, /* Diagonal lines */
20px 20px; /* Dots */
}
:global(.dark) .light-pattern {
opacity: 0;
}
:global(.dark) .dark-pattern {
opacity: 0.05;
}
</style>

View File

@ -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
*/
---
<script is:inline>
// Time-based theme switching
(function() {
// Only run if user has selected auto mode
const themeMode = localStorage.getItem('theme-mode');
if (themeMode === 'auto') {
const checkTime = () => {
const hour = new Date().getHours();
const isDayTime = hour >= 7 && hour < 19; // 7 AM to 7 PM
const theme = isDayTime ? 'light' : 'dark';
const currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
if (theme !== currentTheme) {
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
localStorage.setItem('theme', theme);
// Dispatch theme changed event
window.dispatchEvent(new CustomEvent('theme-changed', {
detail: { theme, automatic: true, mode: 'auto' }
}));
}
};
// Check immediately
checkTime();
// Check every hour
setInterval(checkTime, 60 * 60 * 1000);
// Also check at specific times (7 AM and 7 PM)
const scheduleCheck = () => {
const now = new Date();
const hours = now.getHours();
const minutes = now.getMinutes();
// Calculate time until next check (either 7 AM or 7 PM)
let nextCheckHour;
if (hours < 7) {
nextCheckHour = 7; // Next check at 7 AM
} else if (hours < 19) {
nextCheckHour = 19; // Next check at 7 PM
} else {
nextCheckHour = 7; // Next check at 7 AM tomorrow
}
// Calculate milliseconds until next check
let msUntilNextCheck;
if (hours >= 19) {
// After 7 PM, next check is 7 AM tomorrow
msUntilNextCheck = ((24 - hours + 7) * 60 - minutes) * 60 * 1000;
} else {
// Before 7 PM, next check is either 7 AM or 7 PM today
msUntilNextCheck = ((nextCheckHour - hours) * 60 - minutes) * 60 * 1000;
}
// Schedule the next check
setTimeout(() => {
checkTime();
scheduleCheck(); // Schedule the next check after this one
}, msUntilNextCheck);
};
// Start the scheduling
scheduleCheck();
}
})();
</script>

View File

@ -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
*/
---
---
<button class="border-none bg-none focus:outline-2 focus:outline-offset-2 focus:outline-zag-dark dark:focus:outline-zag-light" id="themeToggle" aria-label="Theme Toggle">
<svg
width="30px"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
name="Theme toggle"
<div class="theme-toggle-container" x-data="{ open: false }">
<button
class="theme-toggle-button zag-interactive"
id="themeToggle"
aria-label="Theme Toggle"
@click="open = !open"
>
<path
class="zag-transition fill-neutral-900 dark:fill-transparent"
fill-rule="evenodd"
d="M12 17.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zm0 1.5a7 7 0 1 0 0-14 7 7 0 0 0 0 14zm12-7a.8.8 0 0 1-.8.8h-2.4a.8.8 0 0 1 0-1.6h2.4a.8.8 0 0 1 .8.8zM4 12a.8.8 0 0 1-.8.8H.8a.8.8 0 0 1 0-1.6h2.5a.8.8 0 0 1 .8.8zm16.5-8.5a.8.8 0 0 1 0 1l-1.8 1.8a.8.8 0 0 1-1-1l1.7-1.8a.8.8 0 0 1 1 0zM6.3 17.7a.8.8 0 0 1 0 1l-1.7 1.8a.8.8 0 1 1-1-1l1.7-1.8a.8.8 0 0 1 1 0zM12 0a.8.8 0 0 1 .8.8v2.5a.8.8 0 0 1-1.6 0V.8A.8.8 0 0 1 12 0zm0 20a.8.8 0 0 1 .8.8v2.4a.8.8 0 0 1-1.6 0v-2.4a.8.8 0 0 1 .8-.8zM3.5 3.5a.8.8 0 0 1 1 0l1.8 1.8a.8.8 0 1 1-1 1L3.5 4.6a.8.8 0 0 1 0-1zm14.2 14.2a.8.8 0 0 1 1 0l1.8 1.7a.8.8 0 0 1-1 1l-1.8-1.7a.8.8 0 0 1 0-1z"
></path>
<path
class="zag-transition fill-transparent dark:fill-neutral-100"
fill-rule="evenodd"
d="M16.5 6A10.5 10.5 0 0 1 4.7 16.4 8.5 8.5 0 1 0 16.4 4.7l.1 1.3zm-1.7-2a9 9 0 0 1 .2 2 9 9 0 0 1-11 8.8 9.4 9.4 0 0 1-.8-.3c-.4 0-.8.3-.7.7a10 10 0 0 0 .3.8 10 10 0 0 0 9.2 6 10 10 0 0 0 4-19.2 9.7 9.7 0 0 0-.9-.3c-.3-.1-.7.3-.6.7a9 9 0 0 1 .3.8z"
></path>
</svg>
</button>
<!-- Sun icon for light mode -->
<svg
class="theme-icon sun-icon"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
class="zag-transition fill-neutral-900 dark:fill-transparent"
fill-rule="evenodd"
d="M12 17.5a5.5 5.5 0 1 0 0-11 5.5 5.5 0 0 0 0 11zm0 1.5a7 7 0 1 0 0-14 7 7 0 0 0 0 14zm12-7a.8.8 0 0 1-.8.8h-2.4a.8.8 0 0 1 0-1.6h2.4a.8.8 0 0 1 .8.8zM4 12a.8.8 0 0 1-.8.8H.8a.8.8 0 0 1 0-1.6h2.5a.8.8 0 0 1 .8.8zm16.5-8.5a.8.8 0 0 1 0 1l-1.8 1.8a.8.8 0 0 1-1-1l1.7-1.8a.8.8 0 0 1 1 0zM6.3 17.7a.8.8 0 0 1 0 1l-1.7 1.8a.8.8 0 1 1-1-1l1.7-1.8a.8.8 0 0 1 1 0zM12 0a.8.8 0 0 1 .8.8v2.5a.8.8 0 0 1-1.6 0V.8A.8.8 0 0 1 12 0zm0 20a.8.8 0 0 1 .8.8v2.4a.8.8 0 0 1-1.6 0v-2.4a.8.8 0 0 1 .8-.8zM3.5 3.5a.8.8 0 0 1 1 0l1.8 1.8a.8.8 0 1 1-1 1L3.5 4.6a.8.8 0 0 1 0-1zm14.2 14.2a.8.8 0 0 1 1 0l1.8 1.7a.8.8 0 0 1-1 1l-1.8-1.7a.8.8 0 0 1 0-1z"
></path>
</svg>
<script is:inline>
// Theme toggle functionality - works with the flash prevention script in Layout
const handleToggleClick = () => {
const element = document.documentElement;
element.classList.toggle("dark");
<!-- Moon icon for dark mode -->
<svg
class="theme-icon moon-icon"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
class="zag-transition fill-transparent dark:fill-neutral-100"
fill-rule="evenodd"
d="M16.5 6A10.5 10.5 0 0 1 4.7 16.4 8.5 8.5 0 1 0 16.4 4.7l.1 1.3zm-1.7-2a9 9 0 0 1 .2 2 9 9 0 0 1-11 8.8 9.4 9.4 0 0 1-.8-.3c-.4 0-.8.3-.7.7a10 10 0 0 0 .3.8 10 10 0 0 0 9.2 6 10 10 0 0 0 4-19.2 9.7 9.7 0 0 0-.9-.3c-.3-.1-.7.3-.6.7a9 9 0 0 1 .3.8z"
></path>
</svg>
</button>
const isDark = element.classList.contains("dark");
localStorage.setItem("theme", isDark ? "dark" : "light");
<!-- Dropdown menu -->
<div
x-show="open"
@click.away="open = false"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 transform scale-95"
x-transition:enter-end="opacity-100 transform scale-100"
x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100 transform scale-100"
x-transition:leave-end="opacity-0 transform scale-95"
class="theme-dropdown"
x-cloak
>
<div class="theme-dropdown-content">
<button class="theme-option" id="lightTheme">
<svg class="theme-option-icon" viewBox="0 0 24 24" width="16" height="16">
<!-- Sun icon -->
<circle cx="12" cy="12" r="5" fill="currentColor"></circle>
<path d="M12 3V1M12 23v-2M3 12H1m22 0h-2M5.6 5.6L4.2 4.2m14.6 14.6l-1.4-1.4M5.6 18.4l-1.4 1.4M18.4 5.6l1.4-1.4" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
</svg>
<span>Light Mode</span>
</button>
// Dispatch a custom event that other components can listen for
window.dispatchEvent(new CustomEvent('theme-changed', {
detail: { theme: isDark ? 'dark' : 'light' }
}));
};
<button class="theme-option" id="darkTheme">
<svg class="theme-option-icon" viewBox="0 0 24 24" width="16" height="16">
<!-- Moon icon -->
<path d="M12 3a9 9 0 1 0 9 9c0-.46-.04-.92-.1-1.36a5.389 5.389 0 0 1-4.4 2.26 5.403 5.403 0 0 1-3.14-9.8c-.44-.06-.9-.1-1.36-.1z" fill="currentColor"></path>
</svg>
<span>Dark Mode</span>
</button>
// Add event listener when the DOM is ready
<button class="theme-option" id="systemTheme">
<svg class="theme-option-icon" viewBox="0 0 24 24" width="16" height="16">
<!-- System icon -->
<rect x="2" y="3" width="20" height="14" rx="2" stroke="currentColor" stroke-width="2" fill="none"></rect>
<path d="M8 21h8M12 17v4" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
</svg>
<span>System Preference</span>
</button>
<button class="theme-option" id="autoTheme">
<svg class="theme-option-icon" viewBox="0 0 24 24" width="16" height="16">
<!-- Clock icon -->
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none"></circle>
<path d="M12 6v6l4 2" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
</svg>
<span>Time-Based</span>
</button>
</div>
</div>
</div>
<style>
.theme-toggle-container {
position: relative;
}
.theme-toggle-button {
background: none;
border: 1px solid transparent;
cursor: pointer;
padding: 6px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.theme-toggle-button:hover {
background-color: rgba(128, 128, 128, 0.1);
border-color: rgba(128, 128, 128, 0.2);
}
.theme-icon {
width: 24px;
height: 24px;
}
.theme-dropdown {
position: absolute;
top: 100%;
right: 0;
margin-top: 8px;
background-color: var(--color-zag-light);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
width: 200px;
z-index: 50;
overflow: hidden;
}
:global(.dark) .theme-dropdown {
background-color: var(--color-zag-dark);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.theme-dropdown-content {
padding: 8px;
}
.theme-option {
display: flex;
align-items: center;
width: 100%;
padding: 10px 12px;
border: none;
background: none;
text-align: left;
cursor: pointer;
border-radius: 6px;
transition: background-color 0.2s ease;
color: var(--color-zag-dark);
}
:global(.dark) .theme-option {
color: var(--color-zag-light);
}
.theme-option:hover {
background-color: rgba(128, 128, 128, 0.1);
}
.theme-option-icon {
margin-right: 12px;
color: currentColor;
}
[x-cloak] {
display: none !important;
}
</style>
<script>
// Enhanced theme toggle functionality
document.addEventListener('DOMContentLoaded', () => {
document
.getElementById("themeToggle")
?.addEventListener("click", handleToggleClick);
const lightThemeBtn = document.getElementById('lightTheme');
const darkThemeBtn = document.getElementById('darkTheme');
const systemThemeBtn = document.getElementById('systemTheme');
const autoThemeBtn = document.getElementById('autoTheme');
// Apply light theme
lightThemeBtn?.addEventListener('click', () => {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
localStorage.setItem('theme-preference-explicit', 'true');
localStorage.setItem('theme-mode', 'manual');
window.dispatchEvent(new CustomEvent('theme-changed', {
detail: { theme: 'light', mode: 'manual' }
}));
});
// Apply dark theme
darkThemeBtn?.addEventListener('click', () => {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
localStorage.setItem('theme-preference-explicit', 'true');
localStorage.setItem('theme-mode', 'manual');
window.dispatchEvent(new CustomEvent('theme-changed', {
detail: { theme: 'dark', mode: 'manual' }
}));
});
// Use system preference
systemThemeBtn?.addEventListener('click', () => {
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (isDark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
localStorage.setItem('theme', isDark ? 'dark' : 'light');
localStorage.setItem('theme-preference-explicit', 'false');
localStorage.setItem('theme-mode', 'system');
window.dispatchEvent(new CustomEvent('theme-changed', {
detail: { theme: isDark ? 'dark' : 'light', mode: 'system' }
}));
});
// Use time-based theme
autoThemeBtn?.addEventListener('click', () => {
const hour = new Date().getHours();
const isDayTime = hour >= 7 && hour < 19; // 7 AM to 7 PM
if (!isDayTime) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
localStorage.setItem('theme', isDayTime ? 'light' : 'dark');
localStorage.setItem('theme-preference-explicit', 'false');
localStorage.setItem('theme-mode', 'auto');
window.dispatchEvent(new CustomEvent('theme-changed', {
detail: { theme: isDayTime ? 'light' : 'dark', mode: 'auto' }
}));
});
// Listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (localStorage.getItem('theme-mode') === 'system') {
const isDark = e.matches;
if (isDark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
localStorage.setItem('theme', isDark ? 'dark' : 'light');
window.dispatchEvent(new CustomEvent('theme-changed', {
detail: { theme: isDark ? 'dark' : 'light', mode: 'system' }
}));
}
});
// Simple toggle functionality for the main button
const themeToggle = document.getElementById('themeToggle');
themeToggle?.addEventListener('click', () => {
// This is now handled by Alpine.js's x-data and @click
});
});
</script>

View File

@ -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
*/
---
<div id="themeTransitionOverlay" class="theme-transition-overlay" aria-hidden="true"></div>
<style>
.theme-transition-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 9999;
opacity: 0;
transition: opacity 0.5s ease;
}
.theme-transition-overlay.light-to-dark {
background: radial-gradient(circle at var(--x) var(--y), rgba(40, 40, 40, 0.8) 0%, rgba(40, 40, 40, 0) 50%);
}
.theme-transition-overlay.dark-to-light {
background: radial-gradient(circle at var(--x) var(--y), rgba(235, 219, 178, 0.8) 0%, rgba(235, 219, 178, 0) 50%);
}
.theme-transition-overlay.active {
opacity: 1;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', () => {
const overlay = document.getElementById('themeTransitionOverlay');
if (!overlay) return;
// Track current theme
let currentTheme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
// Listen for theme changes
window.addEventListener('theme-changed', (e: Event) => {
const customEvent = e as CustomEvent<{theme: string}>;
const newTheme = customEvent.detail.theme;
if (newTheme === currentTheme) return;
// Get toggle button position for centered effect
const toggleBtn = document.getElementById('themeToggle');
let x = '50%';
let y = '50%';
if (toggleBtn) {
const rect = toggleBtn.getBoundingClientRect();
x = `${rect.left + rect.width / 2}px`;
y = `${rect.top + rect.height / 2}px`;
}
// Set the radial gradient position
overlay.style.setProperty('--x', x);
overlay.style.setProperty('--y', y);
// Add the appropriate class
if (newTheme === 'dark') {
overlay.classList.add('light-to-dark');
overlay.classList.remove('dark-to-light');
} else {
overlay.classList.add('dark-to-light');
overlay.classList.remove('light-to-dark');
}
// Trigger the animation
overlay.classList.add('active');
// Remove the animation after it completes
setTimeout(() => {
overlay.classList.remove('active');
overlay.classList.remove('light-to-dark');
overlay.classList.remove('dark-to-light');
}, 500);
// Update current theme
currentTheme = newTheme;
});
});
</script>

View File

@ -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";
}
</script>
<!-- Time-based theme scheduler -->
<ThemeScheduler />
<!-- Theme transition script -->
<script src="/src/scripts/ThemeTransition.js"></script>
<SearchScript />
<slot name="head" />
</head>
@ -123,6 +132,10 @@ import "../styles/global.css";
<!-- Loading overlay for long loading times -->
<LoadingOverlay />
<!-- Theme-specific background patterns and transition effect -->
<ThemeBackground />
<ThemeTransitionEffect />
<Header />
<main>
<slot />

View File

@ -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);

View File

@ -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 {