feat(themes): add Clean theme — Google Maps-inspired style

Add a plain, utilitarian theme focused on readability and wayfinding:
- White/light gray land (#f5f5f5), soft pastel green parks (#c3ecb2)
- Gentle blue water (#aadaff) with classic road hierarchy
- White minor roads, yellow primary (#fbc02d), orange motorway (#f9a825)
- Pure white UI panels with Google-standard gray text
- All 73 protomaps flavor keys + pois + landcover objects
- Full UI CSS custom properties using Google color palette
- Overlay config for hillshade, contours, public lands, trails

Update theme switcher to cycle through all available themes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Matt 2026-05-01 16:27:08 +00:00
commit 7ec87f0945
3 changed files with 382 additions and 8 deletions

View file

@ -1,5 +1,6 @@
import { useRef, useCallback, useEffect, useState } from 'react'
import { Sun, Moon, LogIn, LogOut } from 'lucide-react'
import { Sun, Moon, Sparkles, LogIn, LogOut } from 'lucide-react'
import { themeList } from '../themes/registry'
import { useStore, usePanelState } from '../store'
import { hasFeature } from '../config'
import SearchBar from './SearchBar'
@ -54,10 +55,30 @@ export default function Panel({ onManeuverClick }) {
return () => window.removeEventListener('resize', check)
}, [])
// Theme toggle
// Theme toggle - cycles through all available themes
const toggleTheme = () => {
const next = theme === 'dark' ? 'light' : 'dark'
setThemeOverride(next)
const themes = themeList()
const currentIdx = themes.findIndex(t => t.id === theme)
const nextIdx = (currentIdx + 1) % themes.length
setThemeOverride(themes[nextIdx].id)
}
// Get theme icon based on current theme
const getThemeIcon = () => {
switch (theme) {
case 'dark': return <Moon size={16} />
case 'light': return <Sun size={16} />
case 'clean': return <Sparkles size={16} />
default: return <Sun size={16} />
}
}
// Get next theme name for tooltip
const getNextThemeName = () => {
const themes = themeList()
const currentIdx = themes.findIndex(t => t.id === theme)
const nextIdx = (currentIdx + 1) % themes.length
return themes[nextIdx].name
}
// Auth handlers
@ -257,10 +278,10 @@ export default function Panel({ onManeuverClick }) {
onClick={toggleTheme}
className="p-1.5 rounded"
style={{ color: 'var(--text-secondary)' }}
aria-label={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
title={`Switch to ${theme === 'dark' ? 'light' : 'dark'} mode`}
aria-label={`Switch to ${getNextThemeName()} theme`}
title={`Switch to ${getNextThemeName()} theme`}
>
{theme === 'dark' ? <Sun size={16} /> : <Moon size={16} />}
{getThemeIcon()}
</button>
</div>
</div>