Files
cv-site/prompts/004-system-aware-theme-switcher.md
T
juanatsap 1f7757c848 good
2025-11-15 15:59:54 +00:00

702 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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>