c87fa3d45f6667d109af4753129736aefd324aff
The UI was hardcoded to one Catppuccin Mocha palette (color literals in view() + theme() pinned to Dark). It now sources colours from a chosen theme's palette, selectable in Settings. - theme.rs (NEW): Palette (13 semantic colour roles) + AppTheme enum (Catppuccin Mocha/Macchiato/Frappe/Latte, Dracula, Nord, Tokyo Night, Gruvbox Dark, Solarized Light, Gruvbox Light). Pure palette()/label()/ ALL/base_theme()/is_dark() + WCAG relative_luminance()/contrast_ratio(). - config: theme: AppTheme field (serde-default Mocha) + backward-compat. - app: theme() returns config.theme.base_theme() (iced widget chrome); view() + with_layout_picker() source colours from the palette; new ThemeSwatch canvas widget; a Theme section of clickable swatches in Settings; SelectTheme applies live + persists. Canvas widgets already take colours as data, so they re-theme for free. Tests written alongside (+7 theme, +2 config; 135 -> 144 lib): every palette clears WCAG AA text-on-base contrast (4.5:1) with subtext/accent >= 3:1, is_dark matches luminance direction, variants distinct/labeled, serde round-trips, default = Mocha. Verified live: the Settings swatch grid renders all 10 palettes and the whole UI re-themes (screenshot-checked Latte light + Dracula dark). clippy --all-targets clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>