managarten/apps-archived/wisekeep/apps/landing/src/components/ThemeSwitcher.astro
Till-JS 61d181fbc2 chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace:
- bauntown (community website)
- maerchenzauber (AI story generation)
- memoro (voice memo app)
- news (news aggregation)
- nutriphi (nutrition tracking)
- reader (reading app)
- uload (URL shortener)
- wisekeep (AI wisdom extraction)

Update CLAUDE.md documentation:
- Add presi to active projects
- Document archived projects section
- Update workspace configuration

Archived apps can be re-activated by moving back to apps/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 07:03:59 +01:00

274 lines
8 KiB
Text

---
---
<div class="fixed top-4 right-4 z-50 flex items-center gap-2">
<!-- Theme Selector -->
<div class="relative">
<button
id="theme-menu-button"
class="glass px-4 py-2 rounded-lg flex items-center gap-2 hover:bg-theme-surface-hover text-theme-text"
aria-label="Select theme"
>
<span id="theme-icon">🌊</span>
<span id="theme-name" class="hidden sm:inline">Ocean</span>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"
></path>
</svg>
</button>
<div
id="theme-menu"
class="absolute right-0 mt-2 w-48 glass rounded-lg shadow-theme-lg hidden opacity-0 transform scale-95 transition-all duration-200"
>
<div class="p-2">
<button
data-theme="ocean"
class="theme-option w-full px-4 py-2 rounded-md hover:bg-theme-surface-hover text-left flex items-center gap-3 text-theme-text"
>
<span>🌊</span> Ocean
</button>
<button
data-theme="forest"
class="theme-option w-full px-4 py-2 rounded-md hover:bg-theme-surface-hover text-left flex items-center gap-3 text-theme-text"
>
<span>🌲</span> Forest
</button>
<button
data-theme="sunset"
class="theme-option w-full px-4 py-2 rounded-md hover:bg-theme-surface-hover text-left flex items-center gap-3 text-theme-text"
>
<span>🌅</span> Sunset
</button>
<button
data-theme="monochrome"
class="theme-option w-full px-4 py-2 rounded-md hover:bg-theme-surface-hover text-left flex items-center gap-3 text-theme-text"
>
<span>⚫</span> Monochrome
</button>
</div>
</div>
</div>
<!-- Dark Mode Toggle -->
<button
id="dark-toggle"
class="glass p-2 rounded-lg hover:bg-theme-surface-hover text-theme-text"
aria-label="Toggle dark mode"
>
<svg id="sun-icon" class="w-5 h-5 hidden" fill="currentColor" viewBox="0 0 20 20">
<path
fill-rule="evenodd"
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
clip-rule="evenodd"></path>
</svg>
<svg id="moon-icon" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
</svg>
</button>
</div>
<script>
const themes = {
ocean: { icon: '🌊', name: 'Ocean' },
forest: { icon: '🌲', name: 'Forest' },
sunset: { icon: '🌅', name: 'Sunset' },
monochrome: { icon: '⚫', name: 'Monochrome' },
};
class ThemeManager {
constructor() {
this.currentTheme = 'ocean';
this.isDark = false;
this.init();
}
init() {
// Load saved preferences
this.loadPreferences();
// Apply theme immediately
this.applyTheme();
// Setup event listeners
this.setupEventListeners();
// Listen for system theme changes
this.watchSystemPreference();
}
loadPreferences() {
// Check localStorage first
const savedTheme = localStorage.getItem('theme');
const savedMode = localStorage.getItem('darkMode');
if (savedTheme && themes[savedTheme]) {
this.currentTheme = savedTheme;
}
if (savedMode !== null) {
this.isDark = savedMode === 'true';
} else {
// Check system preference if no saved preference
this.isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
}
}
savePreferences() {
localStorage.setItem('theme', this.currentTheme);
localStorage.setItem('darkMode', this.isDark.toString());
}
applyTheme() {
const html = document.documentElement;
// Set theme
html.setAttribute('data-theme', this.currentTheme);
// Set dark mode
if (this.isDark) {
html.classList.add('dark');
} else {
html.classList.remove('dark');
}
// Update UI
this.updateUI();
}
updateUI() {
// Update theme button
const themeIcon = document.getElementById('theme-icon');
const themeName = document.getElementById('theme-name');
if (themeIcon && themeName) {
themeIcon.textContent = themes[this.currentTheme].icon;
themeName.textContent = themes[this.currentTheme].name;
}
// Update dark mode toggle
const sunIcon = document.getElementById('sun-icon');
const moonIcon = document.getElementById('moon-icon');
if (sunIcon && moonIcon) {
if (this.isDark) {
sunIcon.classList.remove('hidden');
moonIcon.classList.add('hidden');
} else {
sunIcon.classList.add('hidden');
moonIcon.classList.remove('hidden');
}
}
// Update active theme in menu
document.querySelectorAll('.theme-option').forEach((btn) => {
const theme = btn.getAttribute('data-theme');
if (theme === this.currentTheme) {
btn.classList.add('bg-theme-primary/10', 'text-theme-primary');
} else {
btn.classList.remove('bg-theme-primary/10', 'text-theme-primary');
}
});
}
setupEventListeners() {
// Theme menu toggle
const menuButton = document.getElementById('theme-menu-button');
const menu = document.getElementById('theme-menu');
if (menuButton && menu) {
menuButton.addEventListener('click', () => {
const isHidden = menu.classList.contains('hidden');
if (isHidden) {
menu.classList.remove('hidden');
setTimeout(() => {
menu.classList.remove('opacity-0', 'scale-95');
menu.classList.add('opacity-100', 'scale-100');
}, 10);
} else {
menu.classList.remove('opacity-100', 'scale-100');
menu.classList.add('opacity-0', 'scale-95');
setTimeout(() => {
menu.classList.add('hidden');
}, 200);
}
});
// Close menu when clicking outside
document.addEventListener('click', (e) => {
if (!menuButton.contains(e.target) && !menu.contains(e.target)) {
menu.classList.remove('opacity-100', 'scale-100');
menu.classList.add('opacity-0', 'scale-95');
setTimeout(() => {
menu.classList.add('hidden');
}, 200);
}
});
}
// Theme selection
document.querySelectorAll('.theme-option').forEach((btn) => {
btn.addEventListener('click', () => {
const theme = btn.getAttribute('data-theme');
if (theme && themes[theme]) {
this.currentTheme = theme;
this.applyTheme();
this.savePreferences();
// Close menu
const menu = document.getElementById('theme-menu');
if (menu) {
menu.classList.remove('opacity-100', 'scale-100');
menu.classList.add('opacity-0', 'scale-95');
setTimeout(() => {
menu.classList.add('hidden');
}, 200);
}
}
});
});
// Dark mode toggle
const darkToggle = document.getElementById('dark-toggle');
if (darkToggle) {
darkToggle.addEventListener('click', () => {
this.isDark = !this.isDark;
this.applyTheme();
this.savePreferences();
});
}
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
// Alt/Option + T: Open theme menu
if (e.altKey && e.key === 't') {
e.preventDefault();
menuButton?.click();
}
// Alt/Option + D: Toggle dark mode
if (e.altKey && e.key === 'd') {
e.preventDefault();
darkToggle?.click();
}
});
}
watchSystemPreference() {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', (e) => {
// Only apply if user hasn't set a preference
if (localStorage.getItem('darkMode') === null) {
this.isDark = e.matches;
this.applyTheme();
}
});
}
}
// Initialize theme manager when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => new ThemeManager());
} else {
new ThemeManager();
}
</script>