702 lines
24 KiB
Markdown
702 lines
24 KiB
Markdown
# Implement System-Aware Theme Switcher with Animated Expanding Button
|
||
|
||
<objective>
|
||
Implement a comprehensive light/dark/auto theme system that respects the user's system preferences and allows manual override. The feature will include an animated expanding button in the top-right corner that reveals three theme options (Light, Dark, Auto) when interacted with.
|
||
|
||
**Key Goals:**
|
||
1. Support three theme modes: Light (force light), Dark (force dark), Auto (follow system)
|
||
2. Detect and respect system theme preference via `prefers-color-scheme` media query
|
||
3. Create an elegant animated button that expands to reveal three options
|
||
4. Persist user preference in localStorage across sessions
|
||
5. Apply theme instantly without page reload using CSS classes
|
||
|
||
**Why This Matters:**
|
||
- Modern UX standard: Users expect dark mode support (65% of users prefer it at night)
|
||
- Accessibility: Dark mode reduces eye strain in low-light environments
|
||
- System integration: Respecting OS preferences shows attention to detail
|
||
- User control: Some users want to override system settings (e.g., dark mode during day)
|
||
</objective>
|
||
|
||
<context>
|
||
**Current State:**
|
||
- The CV application currently has a "theme toggle" that switches between default and "clean" views
|
||
- Theme is applied via `.theme-clean` class on `.cv-container`
|
||
- Existing toggle uses localStorage for persistence: `localStorage['cv-theme']`
|
||
- No current dark/light mode support - only layout theme variations
|
||
|
||
**Desired Implementation:**
|
||
1. **Three Theme Options:**
|
||
- **Light**: Force light color scheme regardless of system preference
|
||
- **Dark**: Force dark color scheme regardless of system preference
|
||
- **Auto**: Follow system preference (uses `prefers-color-scheme` media query)
|
||
|
||
2. **Animated Button Behavior:**
|
||
- Default state: Single circular button showing current theme icon
|
||
- On hover (desktop): Button expands horizontally left-to-right revealing 3 options
|
||
- On click/tap (mobile): Button expands to show 3 options
|
||
- Each option shows an icon (sun, moon, auto) and optional label
|
||
- Smooth animation: width expansion + fade-in of additional buttons
|
||
- Click any option: Collapse button + apply theme + save preference
|
||
|
||
3. **Positioning:**
|
||
- Fixed position at top-right of viewport
|
||
- Always visible (doesn't scroll with page)
|
||
- Positioned above other content (high z-index)
|
||
- Works on mobile and desktop viewports
|
||
|
||
4. **Persistence:**
|
||
- Save user preference to localStorage: `localStorage['theme-mode']`
|
||
- Values: 'light', 'dark', 'auto'
|
||
- On page load: Read localStorage and apply saved preference
|
||
- If no saved preference: Default to 'auto' (follow system)
|
||
|
||
**Reference Files:**
|
||
@templates/partials/navigation/view-controls.html - Existing toggle pattern with localStorage
|
||
@static/css/main.css - Existing theme classes and styles
|
||
@static/hyperscript/functions._hs - Existing hyperscript functions
|
||
|
||
**Tech Stack:**
|
||
- CSS custom properties (CSS variables) for theme colors
|
||
- CSS `prefers-color-scheme` media query for system detection
|
||
- Hyperscript for button animation and theme switching logic
|
||
- localStorage for persistence
|
||
- Iconify icons for theme indicators (already in use)
|
||
</context>
|
||
|
||
<requirements>
|
||
|
||
## 1. Theme System Architecture
|
||
|
||
**CSS Variables Structure:**
|
||
Create a comprehensive set of CSS custom properties for theming:
|
||
|
||
```css
|
||
:root {
|
||
/* Light theme (default) */
|
||
--bg-primary: #ffffff;
|
||
--bg-secondary: #f5f5f5;
|
||
--text-primary: #333333;
|
||
--text-secondary: #666666;
|
||
--border-color: #e0e0e0;
|
||
--shadow: rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
/* Dark theme - applied via [data-theme="dark"] */
|
||
[data-theme="dark"] {
|
||
--bg-primary: #1a1a1a;
|
||
--bg-secondary: #2a2a2a;
|
||
--text-primary: #e0e0e0;
|
||
--text-secondary: #b0b0b0;
|
||
--border-color: #404040;
|
||
--shadow: rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
/* Auto theme - uses media query */
|
||
@media (prefers-color-scheme: dark) {
|
||
[data-theme="auto"] {
|
||
/* Same as dark theme variables */
|
||
}
|
||
}
|
||
```
|
||
|
||
**Why CSS Variables:**
|
||
- Single source of truth for colors
|
||
- Easy to maintain and extend
|
||
- Automatically cascade to all components
|
||
- Better performance than class-based theme switching for large DOMs
|
||
|
||
**Theme Application:**
|
||
- Apply theme via `data-theme` attribute on `<html>` or `<body>`
|
||
- Values: `data-theme="light"`, `data-theme="dark"`, `data-theme="auto"`
|
||
- JavaScript/Hyperscript sets this attribute based on user selection
|
||
|
||
## 2. Animated Button Component
|
||
|
||
**Button States:**
|
||
|
||
1. **Collapsed (default):**
|
||
- Single circular button (~48px diameter)
|
||
- Shows icon representing current theme (sun/moon/auto)
|
||
- Subtle shadow and hover effect
|
||
|
||
2. **Expanded (on hover/click):**
|
||
- Expands horizontally to ~160px width (or 3 × 48px = 144px)
|
||
- Reveals 3 circular buttons side-by-side
|
||
- Each button: icon + optional tooltip label
|
||
- Smooth width transition (300ms ease-out)
|
||
- Icons fade in with staggered delay for polish
|
||
|
||
**Animation Specifications:**
|
||
- Expansion: `width: 48px → 160px` over 300ms with ease-out easing
|
||
- Icon fade: Opacity 0 → 1 over 200ms with 50ms stagger
|
||
- Collapse: Reverse animation when mouse leaves (desktop) or after selection
|
||
- Use `transform` and `opacity` for GPU acceleration (avoid width if possible, use scale + clip)
|
||
|
||
**HTML Structure Example:**
|
||
```html
|
||
<div id="theme-switcher" class="theme-switcher"
|
||
_="on mouseenter add .expanded to me
|
||
on mouseleave remove .expanded from me">
|
||
|
||
<!-- Current theme indicator (always visible) -->
|
||
<button class="theme-btn active" data-theme-mode="auto">
|
||
<iconify-icon icon="mdi:theme-light-dark" width="20"></iconify-icon>
|
||
</button>
|
||
|
||
<!-- Additional options (visible when expanded) -->
|
||
<button class="theme-btn" data-theme-mode="light"
|
||
_="on click call setTheme('light')">
|
||
<iconify-icon icon="mdi:white-balance-sunny" width="20"></iconify-icon>
|
||
<span class="tooltip">Light</span>
|
||
</button>
|
||
|
||
<button class="theme-btn" data-theme-mode="dark"
|
||
_="on click call setTheme('dark')">
|
||
<iconify-icon icon="mdi:moon-waning-crescent" width="20"></iconify-icon>
|
||
<span class="tooltip">Dark</span>
|
||
</button>
|
||
|
||
<button class="theme-btn" data-theme-mode="auto"
|
||
_="on click call setTheme('auto')">
|
||
<iconify-icon icon="mdi:theme-light-dark" width="20"></iconify-icon>
|
||
<span class="tooltip">Auto</span>
|
||
</button>
|
||
</div>
|
||
```
|
||
|
||
**Responsive Behavior:**
|
||
- Desktop (>768px): Expand on hover, collapse on mouse leave
|
||
- Mobile/Tablet (≤768px): Expand on tap, collapse on background tap or selection
|
||
- Use media query + hyperscript to detect and apply appropriate behavior
|
||
|
||
## 3. Theme Switching Logic
|
||
|
||
**Hyperscript Function:**
|
||
Create a global `setTheme(mode)` function in `static/hyperscript/functions._hs`:
|
||
|
||
```hyperscript
|
||
def setTheme(mode)
|
||
-- Save preference to localStorage
|
||
set localStorage['theme-mode'] to mode
|
||
|
||
-- Apply theme to document
|
||
if mode is 'light'
|
||
set document.documentElement's @data-theme to 'light'
|
||
else if mode is 'dark'
|
||
set document.documentElement's @data-theme to 'dark'
|
||
else if mode is 'auto'
|
||
set document.documentElement's @data-theme to 'auto'
|
||
end
|
||
|
||
-- Update button active states
|
||
set buttons to .theme-btn in #theme-switcher
|
||
for btn in buttons
|
||
if btn's @data-theme-mode is mode
|
||
add .active to btn
|
||
else
|
||
remove .active from btn
|
||
end
|
||
end
|
||
|
||
-- Collapse button on mobile
|
||
if window.innerWidth <= 768
|
||
remove .expanded from #theme-switcher
|
||
end
|
||
end
|
||
```
|
||
|
||
**Page Load Theme Detection:**
|
||
Create an initialization script that runs immediately on page load:
|
||
|
||
```hyperscript
|
||
def initTheme()
|
||
-- Get saved preference or default to 'auto'
|
||
set savedTheme to localStorage['theme-mode'] or 'auto'
|
||
call setTheme(savedTheme)
|
||
end
|
||
|
||
-- Run on page load
|
||
on load call initTheme()
|
||
```
|
||
|
||
**System Preference Detection:**
|
||
Listen for system theme changes when in 'auto' mode:
|
||
|
||
```javascript
|
||
// Optional: Listen for system theme changes
|
||
const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||
darkModeQuery.addEventListener('change', (e) => {
|
||
const currentMode = localStorage.getItem('theme-mode');
|
||
if (currentMode === 'auto' || !currentMode) {
|
||
// Theme will automatically update via CSS media query
|
||
// No action needed, but could trigger a visual indicator
|
||
}
|
||
});
|
||
```
|
||
|
||
## 4. Positioning and Layout
|
||
|
||
**Fixed Positioning:**
|
||
```css
|
||
.theme-switcher {
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
z-index: 1000; /* Above all content */
|
||
display: flex;
|
||
gap: 4px;
|
||
background: var(--bg-primary);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 24px;
|
||
padding: 4px;
|
||
box-shadow: 0 2px 8px var(--shadow);
|
||
width: 56px; /* Single button + padding */
|
||
transition: width 300ms ease-out;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.theme-switcher.expanded {
|
||
width: 176px; /* 3 buttons + gaps + padding */
|
||
}
|
||
|
||
.theme-btn {
|
||
min-width: 48px;
|
||
height: 48px;
|
||
border-radius: 50%;
|
||
border: none;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
transition: background 200ms ease;
|
||
opacity: 1;
|
||
}
|
||
|
||
.theme-btn:not(.active) {
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.theme-switcher:not(.expanded) .theme-btn:not(.active) {
|
||
display: none; /* Hide non-active buttons when collapsed */
|
||
}
|
||
|
||
.theme-btn:hover {
|
||
background: var(--bg-secondary);
|
||
}
|
||
|
||
.theme-btn.active {
|
||
background: var(--bg-secondary);
|
||
}
|
||
```
|
||
|
||
**Mobile Considerations:**
|
||
- Increase touch target size to minimum 44×44px (iOS HIG)
|
||
- Ensure button doesn't overlap with hamburger menu or other controls
|
||
- Consider adding backdrop overlay when expanded on mobile
|
||
- Position may need adjustment on smaller screens (e.g., `top: 16px; right: 16px`)
|
||
|
||
## 5. Color Scheme Design
|
||
|
||
**Light Theme Colors:**
|
||
- Background: White to light gray (#ffffff, #f5f5f5)
|
||
- Text: Dark gray to black (#333333, #1a1a1a)
|
||
- Accent: Keep existing brand colors
|
||
- Borders: Light gray (#e0e0e0)
|
||
|
||
**Dark Theme Colors:**
|
||
- Background: Very dark gray to black (#1a1a1a, #0a0a0a)
|
||
- Text: Light gray to white (#e0e0e0, #ffffff)
|
||
- Accent: Slightly brighter versions of brand colors
|
||
- Borders: Medium dark gray (#404040)
|
||
|
||
**Design Principles:**
|
||
- Maintain sufficient contrast (WCAG AA: 4.5:1 for text, AAA: 7:1 preferred)
|
||
- Don't use pure black (#000000) for dark backgrounds (too harsh)
|
||
- Don't use pure white text on dark backgrounds (causes halation)
|
||
- Test with both themes for readability and accessibility
|
||
|
||
## 6. Persistence and State Management
|
||
|
||
**localStorage Schema:**
|
||
```javascript
|
||
// Store theme preference
|
||
localStorage.setItem('theme-mode', 'light'); // or 'dark', 'auto'
|
||
|
||
// Read on page load
|
||
const savedTheme = localStorage.getItem('theme-mode') || 'auto';
|
||
```
|
||
|
||
**State Synchronization:**
|
||
- Button active state must always reflect current theme
|
||
- System theme changes should update UI if in 'auto' mode
|
||
- Manual theme selection should override system preference
|
||
- Theme should apply before page render to avoid flash (FOUC)
|
||
|
||
**Initial Load Optimization:**
|
||
To prevent flash of wrong theme, add inline script in `<head>`:
|
||
|
||
```html
|
||
<script>
|
||
(function() {
|
||
const theme = localStorage.getItem('theme-mode') || 'auto';
|
||
document.documentElement.setAttribute('data-theme', theme);
|
||
})();
|
||
</script>
|
||
```
|
||
|
||
This runs before page render, applying theme instantly.
|
||
|
||
## 7. Integration with Existing Theme System
|
||
|
||
**Current System:**
|
||
- `.theme-clean` class toggles between default and clean layouts
|
||
- This is a **layout** theme, not a **color** theme
|
||
|
||
**New System:**
|
||
- `data-theme` attribute controls **color** theme (light/dark/auto)
|
||
- `.theme-clean` class still controls **layout** theme
|
||
|
||
**Both Can Coexist:**
|
||
```html
|
||
<body class="theme-clean" data-theme="dark">
|
||
<!-- Clean layout + Dark colors -->
|
||
</body>
|
||
```
|
||
|
||
**CSS Organization:**
|
||
- Keep existing `.theme-clean` styles unchanged
|
||
- Add new `[data-theme="dark"]` styles for colors
|
||
- Ensure both systems work independently and together
|
||
|
||
</requirements>
|
||
|
||
<implementation>
|
||
|
||
## Step-by-Step Implementation Plan
|
||
|
||
### Phase 1: Create CSS Theme Variables
|
||
|
||
1. **Add CSS Variables to `static/css/main.css`:**
|
||
- Define `:root` (light theme) color variables
|
||
- Define `[data-theme="dark"]` color variables
|
||
- Define `@media (prefers-color-scheme: dark)` with `[data-theme="auto"]`
|
||
- Update existing components to use CSS variables instead of hardcoded colors
|
||
|
||
2. **Color Mapping Strategy:**
|
||
- Audit existing colors in `main.css`
|
||
- Create a mapping from hardcoded colors to CSS variable names
|
||
- Replace colors incrementally (high-impact areas first: backgrounds, text, borders)
|
||
|
||
### Phase 2: Create Theme Switcher Button Component
|
||
|
||
1. **Create HTML Template:**
|
||
- New file: `templates/partials/theme-switcher.html`
|
||
- Structure: Container with 3 buttons (light, dark, auto)
|
||
- Include iconify icons for each theme
|
||
- Add hyperscript for expand/collapse behavior
|
||
|
||
2. **Create CSS Styles:**
|
||
- Add to `static/css/main.css` or new `static/css/theme-switcher.css`
|
||
- Fixed positioning at top-right
|
||
- Collapsed and expanded states
|
||
- Button hover and active states
|
||
- Smooth transitions and animations
|
||
- Responsive behavior (desktop vs mobile)
|
||
|
||
3. **Include in Main Layout:**
|
||
- Add `{{template "theme-switcher" .}}` to main layout template
|
||
- Position below action bar but above content (z-index management)
|
||
|
||
### Phase 3: Implement Theme Switching Logic
|
||
|
||
1. **Add Hyperscript Functions to `static/hyperscript/functions._hs`:**
|
||
- `setTheme(mode)` - Apply theme and save to localStorage
|
||
- `initTheme()` - Load saved theme on page load
|
||
- Button click handlers for each theme option
|
||
|
||
2. **Add Inline Script for FOUC Prevention:**
|
||
- In `<head>` of main template, add inline script
|
||
- Reads localStorage and sets `data-theme` before render
|
||
- Prevents flash of wrong theme
|
||
|
||
3. **System Theme Detection (Optional Enhancement):**
|
||
- Add media query listener for system theme changes
|
||
- Update UI when system preference changes (if in 'auto' mode)
|
||
|
||
### Phase 4: Update Existing Components with Theme Variables
|
||
|
||
**Priority Order:**
|
||
1. Main backgrounds and text (highest visual impact)
|
||
2. CV paper and content areas
|
||
3. Navigation and controls
|
||
4. Borders and shadows
|
||
5. Accent colors and highlights
|
||
|
||
**Example Refactor:**
|
||
```css
|
||
/* Before */
|
||
.cv-container {
|
||
background: #ffffff;
|
||
color: #333333;
|
||
}
|
||
|
||
/* After */
|
||
.cv-container {
|
||
background: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
}
|
||
```
|
||
|
||
### Phase 5: Testing and Refinement
|
||
|
||
1. **Visual Testing:**
|
||
- Test all three theme modes (light, dark, auto)
|
||
- Verify color contrast meets WCAG AA standards
|
||
- Check all components render correctly in both themes
|
||
|
||
2. **Interaction Testing:**
|
||
- Button expands smoothly on hover (desktop)
|
||
- Button expands/collapses on tap (mobile)
|
||
- Theme applies instantly when selected
|
||
- Active state updates correctly
|
||
|
||
3. **Persistence Testing:**
|
||
- Save theme preference and reload page
|
||
- Verify saved theme is applied before render (no FOUC)
|
||
- Clear localStorage and verify default to 'auto'
|
||
|
||
4. **System Integration Testing:**
|
||
- Change system theme preference (OS settings)
|
||
- Verify 'auto' mode respects system preference
|
||
- Verify 'light' and 'dark' modes override system
|
||
|
||
**What to Prioritize:**
|
||
1. Core theme switching functionality
|
||
2. FOUC prevention (inline script)
|
||
3. Button animation and UX
|
||
4. High-impact component theming (backgrounds, text)
|
||
5. Fine-tuning colors and contrast
|
||
|
||
**What to Avoid:**
|
||
- Don't try to theme every single pixel in first pass - prioritize high-impact areas
|
||
- Don't use JavaScript for theme application if CSS can handle it (performance)
|
||
- Don't forget mobile UX - touch targets, tap behavior, responsive design
|
||
- Don't hardcode colors - always use CSS variables
|
||
- Don't sacrifice accessibility for aesthetics (contrast ratios are critical)
|
||
|
||
**Why These Constraints Matter:**
|
||
- **CSS Variables:** Maintainable, performant, scalable theming system
|
||
- **FOUC Prevention:** Critical for professional UX - theme must apply before render
|
||
- **Mobile-First:** Touch devices are primary interaction method for many users
|
||
- **Accessibility:** WCAG compliance is non-negotiable for professional applications
|
||
- **Progressive Enhancement:** Light theme works even if JavaScript fails
|
||
|
||
</implementation>
|
||
|
||
<output>
|
||
Create/modify the following files:
|
||
|
||
1. **`./templates/partials/theme-switcher.html`** (NEW)
|
||
- Animated theme switcher button component
|
||
- Three buttons for light, dark, auto modes
|
||
- Hyperscript for expand/collapse and theme selection
|
||
- Iconify icons for each theme option
|
||
|
||
2. **`./static/css/theme-variables.css`** (NEW) or add to `./static/css/main.css`
|
||
- CSS custom properties for light theme (`:root`)
|
||
- CSS custom properties for dark theme (`[data-theme="dark"]`)
|
||
- Media query for auto mode (`@media (prefers-color-scheme: dark)`)
|
||
- Theme switcher button styles
|
||
|
||
3. **`./static/hyperscript/functions._hs`**
|
||
- Add `setTheme(mode)` function
|
||
- Add `initTheme()` function
|
||
- Add page load initialization
|
||
|
||
4. **`./templates/index.html`** (or main layout template)
|
||
- Include theme switcher component
|
||
- Add inline FOUC prevention script in `<head>`
|
||
|
||
5. **`./static/css/main.css`**
|
||
- Refactor existing hardcoded colors to use CSS variables
|
||
- Update backgrounds, text, borders, shadows
|
||
- Ensure compatibility with both theme systems (layout + color)
|
||
|
||
**Optional (recommended):**
|
||
6. **`./static/js/theme-system-listener.js`** (NEW)
|
||
- Listen for system theme changes
|
||
- Update UI when system preference changes in 'auto' mode
|
||
- Only needed for dynamic system theme updates
|
||
|
||
</output>
|
||
|
||
<verification>
|
||
Before declaring complete, perform these comprehensive tests:
|
||
|
||
**1. Visual Verification:**
|
||
- [ ] Light mode: All text readable, good contrast, professional appearance
|
||
- [ ] Dark mode: All text readable, good contrast, not too harsh
|
||
- [ ] Auto mode: Follows system preference correctly
|
||
- [ ] Button expands smoothly showing 3 options
|
||
- [ ] Button collapses smoothly after selection
|
||
- [ ] Active button is visually distinct
|
||
|
||
**2. Interaction Testing:**
|
||
- [ ] Desktop: Button expands on hover, collapses on mouse leave
|
||
- [ ] Mobile: Button expands on tap, collapses on selection or backdrop tap
|
||
- [ ] Click light button: Theme switches to light instantly
|
||
- [ ] Click dark button: Theme switches to dark instantly
|
||
- [ ] Click auto button: Theme follows system preference
|
||
- [ ] No delay or lag in theme application
|
||
|
||
**3. Persistence Testing:**
|
||
- [ ] Select light theme, reload page → Still light
|
||
- [ ] Select dark theme, reload page → Still dark
|
||
- [ ] Select auto theme, reload page → Still auto (follows system)
|
||
- [ ] Clear localStorage → Defaults to auto mode
|
||
- [ ] No flash of unstyled content (FOUC) on page load
|
||
|
||
**4. System Integration:**
|
||
- [ ] Set system to light mode, set app to auto → Light theme applied
|
||
- [ ] Change system to dark mode with app in auto → Dark theme applied
|
||
- [ ] Set app to light mode, change system to dark → Stays light (override works)
|
||
- [ ] Media query correctly detects system preference
|
||
|
||
**5. Accessibility Testing:**
|
||
- [ ] Light mode: WCAG AA contrast ratio for all text (4.5:1 minimum)
|
||
- [ ] Dark mode: WCAG AA contrast ratio for all text
|
||
- [ ] Button tooltips/labels are readable
|
||
- [ ] Keyboard navigation works (tab through theme options)
|
||
- [ ] Focus states are visible
|
||
- [ ] Screen reader announces theme changes
|
||
|
||
**6. Component Coverage:**
|
||
- [ ] CV container background themed correctly
|
||
- [ ] Text colors themed correctly (headings, body, secondary)
|
||
- [ ] Navigation elements themed correctly
|
||
- [ ] Borders and dividers visible in both themes
|
||
- [ ] Shadows appropriate for both themes
|
||
- [ ] Icons and images work in both themes
|
||
- [ ] Toggle controls remain functional and readable
|
||
|
||
**7. Layout Compatibility:**
|
||
- [ ] Theme works with default layout (not `.theme-clean`)
|
||
- [ ] Theme works with `.theme-clean` layout
|
||
- [ ] Both theme systems can be used simultaneously
|
||
- [ ] No conflicts between layout theme and color theme
|
||
|
||
**8. Animation Performance:**
|
||
- [ ] Button expansion is smooth (60fps, no jank)
|
||
- [ ] Theme switching is instant (no visible delay)
|
||
- [ ] GPU-accelerated properties used (opacity, transform)
|
||
- [ ] No layout thrashing during animations
|
||
|
||
**9. Mobile Specific:**
|
||
- [ ] Button positioned correctly on mobile (no overlap with other controls)
|
||
- [ ] Touch targets are adequate size (44×44px minimum)
|
||
- [ ] Tap behavior works correctly (expand/collapse)
|
||
- [ ] Responsive design adapts to small screens
|
||
|
||
**10. Edge Cases:**
|
||
- [ ] Very fast clicking doesn't break state
|
||
- [ ] System theme change during 'auto' mode updates correctly
|
||
- [ ] Works in private/incognito mode (localStorage available)
|
||
- [ ] Graceful degradation if JavaScript disabled (defaults to light)
|
||
|
||
**Success Indicators:**
|
||
✅ Three theme modes work correctly (light, dark, auto)
|
||
✅ System preference detected and respected in auto mode
|
||
✅ Theme persists across page reloads (localStorage)
|
||
✅ No FOUC - theme applies before page render
|
||
✅ Animated button expands/collapses smoothly
|
||
✅ Responsive behavior (hover on desktop, tap on mobile)
|
||
✅ All text meets WCAG AA contrast standards
|
||
✅ Theme switching is instant and smooth
|
||
✅ Works alongside existing `.theme-clean` layout system
|
||
✅ Professional, polished UX
|
||
|
||
</verification>
|
||
|
||
<success_criteria>
|
||
1. Three distinct theme modes implemented: Light, Dark, Auto (system)
|
||
2. System preference correctly detected via `prefers-color-scheme` media query
|
||
3. User preference persists in localStorage across sessions
|
||
4. Animated expanding button reveals theme options smoothly (300ms transition)
|
||
5. Responsive behavior: hover on desktop (>768px), tap on mobile (≤768px)
|
||
6. Fixed positioning at top-right, always visible, doesn't interfere with content
|
||
7. No flash of unstyled content (FOUC) - theme applies before render
|
||
8. All text meets WCAG AA contrast ratio (4.5:1 for normal text)
|
||
9. Theme applies instantly on selection (<50ms perceived delay)
|
||
10. Compatible with existing `.theme-clean` layout system
|
||
11. Accessible keyboard navigation and screen reader support
|
||
12. Smooth animations using GPU-accelerated properties
|
||
13. Code follows existing project patterns and conventions
|
||
14. Implementation is maintainable and well-documented
|
||
</success_criteria>
|
||
|
||
<references>
|
||
**CSS Color Schemes:**
|
||
- MDN prefers-color-scheme: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme
|
||
- Material Design dark theme: https://material.io/design/color/dark-theme.html
|
||
- CSS custom properties: https://developer.mozilla.org/en-US/docs/Web/CSS/--*
|
||
|
||
**Best Practices:**
|
||
- Avoid pure black in dark mode (use #1a1a1a or similar)
|
||
- Maintain consistent contrast ratios
|
||
- Use elevation (shadows) to create depth in dark mode
|
||
- Test with real system theme preferences
|
||
- Prevent FOUC with inline script in head
|
||
|
||
**Icon Suggestions (iconify):**
|
||
- Light mode: `mdi:white-balance-sunny` or `mdi:brightness-7`
|
||
- Dark mode: `mdi:moon-waning-crescent` or `mdi:weather-night`
|
||
- Auto mode: `mdi:theme-light-dark` or `mdi:brightness-auto`
|
||
|
||
**Animation Patterns:**
|
||
- Expand: ease-out (starts fast, ends slow)
|
||
- Collapse: ease-in (starts slow, ends fast)
|
||
- Theme switch: instant (no transition on color change)
|
||
- Icons: staggered fade-in for polish
|
||
|
||
**Accessibility:**
|
||
- WCAG AA: 4.5:1 for normal text, 3:1 for large text
|
||
- WCAG AAA: 7:1 for normal text, 4.5:1 for large text
|
||
- Test with actual screen readers (VoiceOver, NVDA)
|
||
- Ensure focus indicators are visible in both themes
|
||
</references>
|
||
|
||
<research>
|
||
**Files to Examine:**
|
||
@static/css/main.css - Current color usage and theme system
|
||
@templates/partials/navigation/view-controls.html - Existing toggle pattern
|
||
@static/hyperscript/functions._hs - Existing hyperscript functions
|
||
@templates/index.html - Main layout structure
|
||
|
||
**Questions to Answer:**
|
||
1. What colors are currently hardcoded and need to become variables?
|
||
2. How is the existing `.theme-clean` system implemented?
|
||
3. Where should the theme switcher button be positioned to not conflict with existing UI?
|
||
4. What iconify icons are already in use (maintain consistency)?
|
||
5. What localStorage keys are already in use (avoid conflicts)?
|
||
</research>
|
||
|
||
<additional_notes>
|
||
**Implementation Philosophy:**
|
||
- Progressive enhancement: Works without JavaScript (defaults to light)
|
||
- Mobile-first: Touch interactions are primary, hover is enhancement
|
||
- Performance-first: CSS handles theme, JavaScript only manages state
|
||
- Accessibility-first: WCAG compliance is non-negotiable
|
||
- Maintainability: CSS variables make future updates trivial
|
||
|
||
**Design Considerations:**
|
||
- Button should feel premium and polished (subtle shadows, smooth animations)
|
||
- Don't over-animate - smooth and subtle is better than flashy
|
||
- Dark mode should be comfortable for extended reading, not just "looks cool"
|
||
- Auto mode should be the intelligent default (respects user's OS preference)
|
||
|
||
**Future Enhancements:**
|
||
- Add transition animation when theme changes (optional fade)
|
||
- Support custom theme colors (user-selectable accent colors)
|
||
- Add sunrise/sunset auto-scheduling (auto-switch based on time)
|
||
- Sync theme preference across devices (server-side storage)
|
||
- Add "high contrast" mode for accessibility
|
||
</additional_notes>
|