Skip to main content

oxicode_vtui/theme/
registry.rs

1use anstyle::RgbColor;
2use catppuccin::PALETTE;
3use hashbrown::HashMap;
4use once_cell::sync::Lazy;
5
6use crate::theme::types::{ThemeDefinition, ThemePalette, ThemeSuite};
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
9enum CatppuccinFlavorKind {
10    Latte,
11    Frappe,
12    Macchiato,
13    Mocha,
14}
15
16impl CatppuccinFlavorKind {
17    const fn id(self) -> &'static str {
18        match self {
19            Self::Latte => "catppuccin-latte",
20            Self::Frappe => "catppuccin-frappe",
21            Self::Macchiato => "catppuccin-macchiato",
22            Self::Mocha => "catppuccin-mocha",
23        }
24    }
25
26    const fn label(self) -> &'static str {
27        match self {
28            Self::Latte => "Catppuccin Latte",
29            Self::Frappe => "Catppuccin Frappé",
30            Self::Macchiato => "Catppuccin Macchiato",
31            Self::Mocha => "Catppuccin Mocha",
32        }
33    }
34
35    fn flavor(self) -> catppuccin::Flavor {
36        match self {
37            Self::Latte => PALETTE.latte,
38            Self::Frappe => PALETTE.frappe,
39            Self::Macchiato => PALETTE.macchiato,
40            Self::Mocha => PALETTE.mocha,
41        }
42    }
43}
44
45static CATPPUCCIN_FLAVORS: &[CatppuccinFlavorKind] = &[
46    CatppuccinFlavorKind::Latte,
47    CatppuccinFlavorKind::Frappe,
48    CatppuccinFlavorKind::Macchiato,
49    CatppuccinFlavorKind::Mocha,
50];
51
52static REGISTRY: Lazy<HashMap<&'static str, ThemeDefinition>> = Lazy::new(|| {
53    let mut map = HashMap::new();
54    map.insert(
55        "ciapre-dark",
56        ThemeDefinition {
57            id: "ciapre-dark",
58            label: "Ciapre Dark",
59            palette: ThemePalette {
60                primary_accent: RgbColor(0xBF, 0xB3, 0x8F),
61                background: RgbColor(0x26, 0x26, 0x26),
62                foreground: RgbColor(0xBF, 0xB3, 0x8F),
63                secondary_accent: RgbColor(0xD9, 0x9A, 0x4E),
64                alert: RgbColor(0xFF, 0x8A, 0x8A),
65                logo_accent: RgbColor(0xD9, 0x9A, 0x4E),
66            },
67        },
68    );
69    map.insert(
70        "ciapre-blue",
71        ThemeDefinition {
72            id: "ciapre-blue",
73            label: "Ciapre Blue",
74            palette: ThemePalette {
75                primary_accent: RgbColor(0xBF, 0xB3, 0x8F),
76                background: RgbColor(0x17, 0x1C, 0x26),
77                foreground: RgbColor(0xBF, 0xB3, 0x8F),
78                secondary_accent: RgbColor(0xBF, 0xB3, 0x8F),
79                alert: RgbColor(0xFF, 0x8A, 0x8A),
80                logo_accent: RgbColor(0xD9, 0x9A, 0x4E),
81            },
82        },
83    );
84    map.insert(
85        "ciapre",
86        ThemeDefinition {
87            id: "ciapre",
88            label: "Ciapre",
89            palette: ThemePalette {
90                primary_accent: RgbColor(0xAE, 0xA4, 0x7F),
91                background: RgbColor(0x18, 0x18, 0x18),
92                foreground: RgbColor(0xAE, 0xA4, 0x7F),
93                secondary_accent: RgbColor(0xCC, 0x8A, 0x3E),
94                alert: RgbColor(0xC1, 0x6A, 0x68),
95                logo_accent: RgbColor(0xCC, 0x8A, 0x3E),
96            },
97        },
98    );
99    map.insert(
100        "solarized-dark",
101        ThemeDefinition {
102            id: "solarized-dark",
103            label: "Solarized Dark",
104            palette: ThemePalette {
105                primary_accent: RgbColor(0x83, 0x94, 0x96),
106                background: RgbColor(0x00, 0x2B, 0x36),
107                foreground: RgbColor(0x83, 0x94, 0x96),
108                secondary_accent: RgbColor(0x3A, 0x95, 0xD6),
109                alert: RgbColor(0xE5, 0x67, 0x65),
110                logo_accent: RgbColor(0x8B, 0x69, 0x00),
111            },
112        },
113    );
114    map.insert(
115        "solarized-light",
116        ThemeDefinition {
117            id: "solarized-light",
118            label: "Solarized Light",
119            palette: ThemePalette {
120                primary_accent: RgbColor(0x58, 0x6E, 0x75),
121                background: RgbColor(0xFD, 0xF6, 0xE3),
122                foreground: RgbColor(0x58, 0x6E, 0x75),
123                secondary_accent: RgbColor(0x20, 0x73, 0xAE),
124                alert: RgbColor(0xD3, 0x30, 0x2D),
125                logo_accent: RgbColor(0x8B, 0x69, 0x00),
126            },
127        },
128    );
129    map.insert(
130        "solarized-dark-hc",
131        ThemeDefinition {
132            id: "solarized-dark-hc",
133            label: "Solarized Dark Higher Contrast",
134            palette: ThemePalette {
135                primary_accent: RgbColor(0x83, 0x94, 0x96),
136                background: RgbColor(0x00, 0x28, 0x31),
137                foreground: RgbColor(0xE9, 0xE3, 0xCC),
138                secondary_accent: RgbColor(0x4A, 0x90, 0xD2),
139                alert: RgbColor(0xE0, 0x65, 0x6A),
140                logo_accent: RgbColor(0xAF, 0x86, 0x21),
141            },
142        },
143    );
144    map.insert(
145        "gruvbox-dark",
146        ThemeDefinition {
147            id: "gruvbox-dark",
148            label: "Gruvbox Dark",
149            palette: ThemePalette {
150                primary_accent: RgbColor(0xA8, 0x99, 0x84),
151                background: RgbColor(0x28, 0x28, 0x28),
152                foreground: RgbColor(0xA8, 0x99, 0x84),
153                secondary_accent: RgbColor(0x63, 0x99, 0x9B),
154                alert: RgbColor(0xDE, 0x71, 0x6C),
155                logo_accent: RgbColor(0xD7, 0x99, 0x21),
156            },
157        },
158    );
159    map.insert(
160        "gruvbox-dark-hard",
161        ThemeDefinition {
162            id: "gruvbox-dark-hard",
163            label: "Gruvbox Dark Hard",
164            palette: ThemePalette {
165                primary_accent: RgbColor(0xA8, 0x99, 0x84),
166                background: RgbColor(0x1D, 0x20, 0x21),
167                foreground: RgbColor(0xA8, 0x99, 0x84),
168                secondary_accent: RgbColor(0x58, 0x91, 0x94),
169                alert: RgbColor(0xDB, 0x64, 0x5F),
170                logo_accent: RgbColor(0xD7, 0x99, 0x21),
171            },
172        },
173    );
174    map.insert(
175        "gruvbox-light",
176        ThemeDefinition {
177            id: "gruvbox-light",
178            label: "Gruvbox Light",
179            palette: ThemePalette {
180                primary_accent: RgbColor(0x78, 0x6C, 0x61),
181                background: RgbColor(0xFB, 0xF4, 0xE8),
182                foreground: RgbColor(0x78, 0x6C, 0x61),
183                secondary_accent: RgbColor(0x3E, 0x78, 0x7A),
184                alert: RgbColor(0x9D, 0x00, 0x06),
185                logo_accent: RgbColor(0x98, 0x63, 0x11),
186            },
187        },
188    );
189    map.insert(
190        "gruvbox-light-hard",
191        ThemeDefinition {
192            id: "gruvbox-light-hard",
193            label: "Gruvbox Light Hard",
194            palette: ThemePalette {
195                primary_accent: RgbColor(0x50, 0x49, 0x45),
196                background: RgbColor(0xF9, 0xF5, 0xD7),
197                foreground: RgbColor(0x50, 0x49, 0x45),
198                secondary_accent: RgbColor(0x3D, 0x76, 0x79),
199                alert: RgbColor(0x9D, 0x00, 0x06),
200                logo_accent: RgbColor(0x98, 0x63, 0x11),
201            },
202        },
203    );
204    map.insert(
205        "gruvbox-material",
206        ThemeDefinition {
207            id: "gruvbox-material",
208            label: "Gruvbox Material",
209            palette: ThemePalette {
210                primary_accent: RgbColor(0xD4, 0xBE, 0x98),
211                background: RgbColor(0x28, 0x2E, 0x33),
212                foreground: RgbColor(0xD4, 0xBE, 0x98),
213                secondary_accent: RgbColor(0x89, 0xB4, 0x82),
214                alert: RgbColor(0xEB, 0x70, 0x6A),
215                logo_accent: RgbColor(0xE7, 0xA7, 0x2F),
216            },
217        },
218    );
219    map.insert(
220        "gruvbox-material-dark",
221        ThemeDefinition {
222            id: "gruvbox-material-dark",
223            label: "Gruvbox Material Dark",
224            palette: ThemePalette {
225                primary_accent: RgbColor(0xD4, 0xBE, 0x98),
226                background: RgbColor(0x28, 0x2E, 0x33),
227                foreground: RgbColor(0xD4, 0xBE, 0x98),
228                secondary_accent: RgbColor(0x89, 0xB4, 0x82),
229                alert: RgbColor(0xEB, 0x70, 0x6A),
230                logo_accent: RgbColor(0xE7, 0xA7, 0x2F),
231            },
232        },
233    );
234    map.insert(
235        "gruvbox-material-light",
236        ThemeDefinition {
237            id: "gruvbox-material-light",
238            label: "Gruvbox Material Light",
239            palette: ThemePalette {
240                primary_accent: RgbColor(0x65, 0x49, 0x31),
241                background: RgbColor(0xFB, 0xF1, 0xC7),
242                foreground: RgbColor(0x65, 0x49, 0x31),
243                secondary_accent: RgbColor(0x49, 0x75, 0x59),
244                alert: RgbColor(0xB7, 0x46, 0x46),
245                logo_accent: RgbColor(0x97, 0x5F, 0x08),
246            },
247        },
248    );
249    map.insert(
250        "ayu",
251        ThemeDefinition {
252            id: "ayu",
253            label: "Ayu Dark",
254            palette: ThemePalette {
255                primary_accent: RgbColor(0xE6, 0xE1, 0xCF),
256                background: RgbColor(0x0F, 0x14, 0x19),
257                foreground: RgbColor(0xE6, 0xE1, 0xCF),
258                secondary_accent: RgbColor(0x39, 0xBA, 0xE6),
259                alert: RgbColor(0xFF, 0x33, 0x33),
260                logo_accent: RgbColor(0xE6, 0xB4, 0x50),
261            },
262        },
263    );
264    map.insert(
265        "ayu-mirage",
266        ThemeDefinition {
267            id: "ayu-mirage",
268            label: "Ayu Mirage",
269            palette: ThemePalette {
270                primary_accent: RgbColor(0xCB, 0xCC, 0xCC),
271                background: RgbColor(0x1F, 0x24, 0x2D),
272                foreground: RgbColor(0xCB, 0xCC, 0xCC),
273                secondary_accent: RgbColor(0x5C, 0xC5, 0xFF),
274                alert: RgbColor(0xFF, 0x66, 0x66),
275                logo_accent: RgbColor(0xFF, 0xCC, 0x66),
276            },
277        },
278    );
279    map.insert(
280        "dracula",
281        ThemeDefinition {
282            id: "dracula",
283            label: "Dracula",
284            palette: ThemePalette {
285                primary_accent: RgbColor(0xF8, 0xF8, 0xF2),
286                background: RgbColor(0x28, 0x2A, 0x36),
287                foreground: RgbColor(0xF8, 0xF8, 0xF2),
288                secondary_accent: RgbColor(0x8B, 0xE9, 0xFD),
289                alert: RgbColor(0xFF, 0x58, 0x58),
290                logo_accent: RgbColor(0xFF, 0xB8, 0x6C),
291            },
292        },
293    );
294    map.insert(
295        "github-dark",
296        ThemeDefinition {
297            id: "github-dark",
298            label: "GitHub Dark",
299            palette: ThemePalette {
300                primary_accent: RgbColor(0xC9, 0xD1, 0xD9),
301                background: RgbColor(0x0D, 0x11, 0x17),
302                foreground: RgbColor(0xC9, 0xD1, 0xD9),
303                secondary_accent: RgbColor(0x58, 0xA6, 0xFF),
304                alert: RgbColor(0xF8, 0x51, 0x49),
305                logo_accent: RgbColor(0xD2, 0x99, 0x22),
306            },
307        },
308    );
309    map.insert(
310        "github",
311        ThemeDefinition {
312            id: "github",
313            label: "GitHub Light",
314            palette: ThemePalette {
315                primary_accent: RgbColor(0x24, 0x29, 0x2E),
316                background: RgbColor(0xFF, 0xFF, 0xFF),
317                foreground: RgbColor(0x24, 0x29, 0x2E),
318                secondary_accent: RgbColor(0x03, 0x66, 0xD6),
319                alert: RgbColor(0xD5, 0x39, 0x48),
320                logo_accent: RgbColor(0x90, 0x70, 0x00),
321            },
322        },
323    );
324    map.insert(
325        "atom-one-dark",
326        ThemeDefinition {
327            id: "atom-one-dark",
328            label: "Atom One Dark",
329            palette: ThemePalette {
330                primary_accent: RgbColor(0xAB, 0xB2, 0xBF),
331                background: RgbColor(0x28, 0x2C, 0x34),
332                foreground: RgbColor(0xAB, 0xB2, 0xBF),
333                secondary_accent: RgbColor(0x61, 0xAF, 0xEF),
334                alert: RgbColor(0xE2, 0x73, 0x7C),
335                logo_accent: RgbColor(0xE5, 0xC0, 0x7B),
336            },
337        },
338    );
339    map.insert(
340        "atom-one-light",
341        ThemeDefinition {
342            id: "atom-one-light",
343            label: "Atom One Light",
344            palette: ThemePalette {
345                primary_accent: RgbColor(0x38, 0x3A, 0x42),
346                background: RgbColor(0xFA, 0xFA, 0xFA),
347                foreground: RgbColor(0x38, 0x3A, 0x42),
348                secondary_accent: RgbColor(0x3E, 0x70, 0xC3),
349                alert: RgbColor(0xC2, 0x49, 0x3E),
350                logo_accent: RgbColor(0x98, 0x62, 0x00),
351            },
352        },
353    );
354    map.insert(
355        "tomorrow",
356        ThemeDefinition {
357            id: "tomorrow",
358            label: "Tomorrow",
359            palette: ThemePalette {
360                primary_accent: RgbColor(0x4D, 0x4D, 0x4C),
361                background: RgbColor(0xFF, 0xFF, 0xFF),
362                foreground: RgbColor(0x4D, 0x4D, 0x4C),
363                secondary_accent: RgbColor(0x41, 0x7A, 0xA2),
364                alert: RgbColor(0xC8, 0x28, 0x29),
365                logo_accent: RgbColor(0x99, 0x6C, 0x03),
366            },
367        },
368    );
369    map.insert(
370        "tomorrow-night",
371        ThemeDefinition {
372            id: "tomorrow-night",
373            label: "Tomorrow Night",
374            palette: ThemePalette {
375                primary_accent: RgbColor(0xC5, 0xC8, 0xC6),
376                background: RgbColor(0x1D, 0x1F, 0x21),
377                foreground: RgbColor(0xC5, 0xC8, 0xC6),
378                secondary_accent: RgbColor(0x81, 0xA2, 0xBE),
379                alert: RgbColor(0xCE, 0x6B, 0x6B),
380                logo_accent: RgbColor(0xDE, 0x93, 0x5F),
381            },
382        },
383    );
384    map.insert(
385        "tomorrow-night-blue",
386        ThemeDefinition {
387            id: "tomorrow-night-blue",
388            label: "Tomorrow Night Blue",
389            palette: ThemePalette {
390                primary_accent: RgbColor(0xFF, 0xFF, 0xFF),
391                background: RgbColor(0x00, 0x2E, 0x4E),
392                foreground: RgbColor(0xFF, 0xFF, 0xFF),
393                secondary_accent: RgbColor(0x7A, 0xBD, 0xFF),
394                alert: RgbColor(0xFF, 0x9D, 0x9D),
395                logo_accent: RgbColor(0xFF, 0xC8, 0x80),
396            },
397        },
398    );
399    map.insert(
400        "tomorrow-night-bright",
401        ThemeDefinition {
402            id: "tomorrow-night-bright",
403            label: "Tomorrow Night Bright",
404            palette: ThemePalette {
405                primary_accent: RgbColor(0xDE, 0xDE, 0xDE),
406                background: RgbColor(0x00, 0x00, 0x00),
407                foreground: RgbColor(0xDE, 0xDE, 0xDE),
408                secondary_accent: RgbColor(0x7A, 0xBD, 0xFF),
409                alert: RgbColor(0xFF, 0x87, 0x87),
410                logo_accent: RgbColor(0xFF, 0xC8, 0x80),
411            },
412        },
413    );
414    map.insert(
415        "tomorrow-night-eighties",
416        ThemeDefinition {
417            id: "tomorrow-night-eighties",
418            label: "Tomorrow Night Eighties",
419            palette: ThemePalette {
420                primary_accent: RgbColor(0xCC, 0xCC, 0xCC),
421                background: RgbColor(0x2D, 0x2D, 0x2D),
422                foreground: RgbColor(0xCC, 0xCC, 0xCC),
423                secondary_accent: RgbColor(0x68, 0x9A, 0xCD),
424                alert: RgbColor(0xF2, 0x77, 0x7A),
425                logo_accent: RgbColor(0xF9, 0x91, 0x57),
426            },
427        },
428    );
429    map.insert(
430        "tomorrow-night-burns",
431        ThemeDefinition {
432            id: "tomorrow-night-burns",
433            label: "Tomorrow Night Burns",
434            palette: ThemePalette {
435                primary_accent: RgbColor(0xD0, 0xD0, 0xD0),
436                background: RgbColor(0x15, 0x12, 0x0E),
437                foreground: RgbColor(0xD0, 0xD0, 0xD0),
438                secondary_accent: RgbColor(0x6C, 0x99, 0xB4),
439                alert: RgbColor(0xC0, 0x63, 0x63),
440                logo_accent: RgbColor(0xC4, 0x8D, 0x53),
441            },
442        },
443    );
444    map.insert(
445        "material-ocean",
446        ThemeDefinition {
447            id: "material-ocean",
448            label: "Material Ocean",
449            palette: ThemePalette {
450                primary_accent: RgbColor(0xEE, 0xEE, 0xEE),
451                background: RgbColor(0x0F, 0x11, 0x1A),
452                foreground: RgbColor(0xEE, 0xEE, 0xEE),
453                secondary_accent: RgbColor(0x82, 0xAA, 0xFF),
454                alert: RgbColor(0xF0, 0x72, 0x79),
455                logo_accent: RgbColor(0xFF, 0xCB, 0x6B),
456            },
457        },
458    );
459    map.insert(
460        "material-dark",
461        ThemeDefinition {
462            id: "material-dark",
463            label: "Material Dark",
464            palette: ThemePalette {
465                primary_accent: RgbColor(0xEE, 0xEE, 0xEE),
466                background: RgbColor(0x26, 0x32, 0x38),
467                foreground: RgbColor(0xEE, 0xEE, 0xEE),
468                secondary_accent: RgbColor(0x82, 0xAA, 0xFF),
469                alert: RgbColor(0xF0, 0x72, 0x79),
470                logo_accent: RgbColor(0xFF, 0xCB, 0x6B),
471            },
472        },
473    );
474    map.insert(
475        "material",
476        ThemeDefinition {
477            id: "material",
478            label: "Material",
479            palette: ThemePalette {
480                primary_accent: RgbColor(0xEE, 0xEE, 0xEE),
481                background: RgbColor(0x26, 0x32, 0x38),
482                foreground: RgbColor(0xEE, 0xEE, 0xEE),
483                secondary_accent: RgbColor(0x82, 0xAA, 0xFF),
484                alert: RgbColor(0xF0, 0x72, 0x79),
485                logo_accent: RgbColor(0xFF, 0xCB, 0x6B),
486            },
487        },
488    );
489    map.insert(
490        "monokai-classic",
491        ThemeDefinition {
492            id: "monokai-classic",
493            label: "Monokai Classic",
494            palette: ThemePalette {
495                primary_accent: RgbColor(0xF8, 0xF8, 0xF2),
496                background: RgbColor(0x27, 0x28, 0x22),
497                foreground: RgbColor(0xF8, 0xF8, 0xF2),
498                secondary_accent: RgbColor(0x66, 0xD9, 0xEF),
499                alert: RgbColor(0xFA, 0x4F, 0x8D),
500                logo_accent: RgbColor(0xFD, 0x97, 0x1F),
501            },
502        },
503    );
504    map.insert(
505        "night-owl",
506        ThemeDefinition {
507            id: "night-owl",
508            label: "Night Owl",
509            palette: ThemePalette {
510                primary_accent: RgbColor(0xD6, 0xDE, 0xEB),
511                background: RgbColor(0x01, 0x17, 0x27),
512                foreground: RgbColor(0xD6, 0xDE, 0xEB),
513                secondary_accent: RgbColor(0x82, 0xAA, 0xFF),
514                alert: RgbColor(0xEF, 0x53, 0x50),
515                logo_accent: RgbColor(0xFA, 0xC8, 0x63),
516            },
517        },
518    );
519    map.insert(
520        "zenburn",
521        ThemeDefinition {
522            id: "zenburn",
523            label: "Zenburn",
524            palette: ThemePalette {
525                primary_accent: RgbColor(0xDC, 0xDC, 0xCC),
526                background: RgbColor(0x3F, 0x3F, 0x3F),
527                foreground: RgbColor(0xDC, 0xDC, 0xCC),
528                secondary_accent: RgbColor(0x9B, 0xB3, 0x8E),
529                alert: RgbColor(0xD2, 0x9F, 0x9F),
530                logo_accent: RgbColor(0xDF, 0xAF, 0x8F),
531            },
532        },
533    );
534    map.insert(
535        "jetbrains-darcula",
536        ThemeDefinition {
537            id: "jetbrains-darcula",
538            label: "JetBrains Darcula",
539            palette: ThemePalette {
540                primary_accent: RgbColor(0xA9, 0xB7, 0xC6),
541                background: RgbColor(0x2B, 0x2B, 0x2B),
542                foreground: RgbColor(0xA9, 0xB7, 0xC6),
543                secondary_accent: RgbColor(0x77, 0x97, 0xBC),
544                alert: RgbColor(0xCE, 0x7D, 0x7B),
545                logo_accent: RgbColor(0xBB, 0xA2, 0x6C),
546            },
547        },
548    );
549    map.insert(
550        "spacegray",
551        ThemeDefinition {
552            id: "spacegray",
553            label: "Spacegray",
554            palette: ThemePalette {
555                primary_accent: RgbColor(0xBD, 0xC3, 0xCE),
556                background: RgbColor(0x20, 0x22, 0x2B),
557                foreground: RgbColor(0xBD, 0xC3, 0xCE),
558                secondary_accent: RgbColor(0x7F, 0xA0, 0xC0),
559                alert: RgbColor(0xB8, 0x7A, 0x7A),
560                logo_accent: RgbColor(0xC0, 0x99, 0x70),
561            },
562        },
563    );
564    map.insert(
565        "spacegray-bright",
566        ThemeDefinition {
567            id: "spacegray-bright",
568            label: "Spacegray Bright",
569            palette: ThemePalette {
570                primary_accent: RgbColor(0xF2, 0xF2, 0xF2),
571                background: RgbColor(0x1A, 0x1A, 0x1A),
572                foreground: RgbColor(0xF2, 0xF2, 0xF2),
573                secondary_accent: RgbColor(0x88, 0xB0, 0xD0),
574                alert: RgbColor(0xD0, 0x70, 0x70),
575                logo_accent: RgbColor(0xD0, 0xA0, 0x60),
576            },
577        },
578    );
579    map.insert(
580        "spacegray-eighties",
581        ThemeDefinition {
582            id: "spacegray-eighties",
583            label: "Spacegray Eighties",
584            palette: ThemePalette {
585                primary_accent: RgbColor(0xEC, 0xEC, 0xEC),
586                background: RgbColor(0x22, 0x22, 0x2B),
587                foreground: RgbColor(0xEC, 0xEC, 0xEC),
588                secondary_accent: RgbColor(0x7A, 0x9F, 0xC2),
589                alert: RgbColor(0xCA, 0x72, 0x72),
590                logo_accent: RgbColor(0xC2, 0x95, 0x62),
591            },
592        },
593    );
594    map.insert(
595        "spacegray-eighties-dull",
596        ThemeDefinition {
597            id: "spacegray-eighties-dull",
598            label: "Spacegray Eighties Dull",
599            palette: ThemePalette {
600                primary_accent: RgbColor(0xC2, 0xC8, 0xD0),
601                background: RgbColor(0x2D, 0x30, 0x3A),
602                foreground: RgbColor(0xC2, 0xC8, 0xD0),
603                secondary_accent: RgbColor(0x7F, 0x9C, 0xB9),
604                alert: RgbColor(0xC2, 0x8C, 0x8C),
605                logo_accent: RgbColor(0xB5, 0x93, 0x6A),
606            },
607        },
608    );
609    map.insert(
610        "apple-classic",
611        ThemeDefinition {
612            id: "apple-classic",
613            label: "Apple Classic",
614            palette: ThemePalette {
615                primary_accent: RgbColor(0x00, 0x00, 0x00),
616                background: RgbColor(0xF6, 0xF4, 0xEC),
617                foreground: RgbColor(0x00, 0x00, 0x00),
618                secondary_accent: RgbColor(0x00, 0x66, 0x99),
619                alert: RgbColor(0xB0, 0x00, 0x00),
620                logo_accent: RgbColor(0x85, 0x6B, 0x00),
621            },
622        },
623    );
624    map.insert(
625        "apple-system-colors",
626        ThemeDefinition {
627            id: "apple-system-colors",
628            label: "Apple System Colors",
629            palette: ThemePalette {
630                primary_accent: RgbColor(0xF5, 0xF5, 0xF7),
631                background: RgbColor(0x1C, 0x1C, 0x1E),
632                foreground: RgbColor(0xF5, 0xF5, 0xF7),
633                secondary_accent: RgbColor(0x08, 0x6E, 0xD4),
634                alert: RgbColor(0xFF, 0x45, 0x3A),
635                logo_accent: RgbColor(0xFF, 0x9F, 0x0A),
636            },
637        },
638    );
639    map.insert(
640        "apple-system-colors-light",
641        ThemeDefinition {
642            id: "apple-system-colors-light",
643            label: "Apple System Colors Light",
644            palette: ThemePalette {
645                primary_accent: RgbColor(0x1C, 0x1C, 0x1E),
646                background: RgbColor(0xF5, 0xF5, 0xF7),
647                foreground: RgbColor(0x1C, 0x1C, 0x1E),
648                secondary_accent: RgbColor(0x08, 0x6E, 0xD4),
649                alert: RgbColor(0xD1, 0x30, 0x27),
650                logo_accent: RgbColor(0xA1, 0x5E, 0x00),
651            },
652        },
653    );
654    map.insert(
655        "vitesse-black",
656        ThemeDefinition {
657            id: "vitesse-black",
658            label: "Vitesse Black",
659            palette: ThemePalette {
660                primary_accent: RgbColor(0xDB, 0xD7, 0xCA),
661                background: RgbColor(0x12, 0x12, 0x12),
662                foreground: RgbColor(0xDB, 0xD7, 0xCA),
663                secondary_accent: RgbColor(0x4D, 0x93, 0x8F),
664                alert: RgbColor(0xB3, 0x6A, 0x6A),
665                logo_accent: RgbColor(0xFF, 0xA6, 0x57),
666            },
667        },
668    );
669    map.insert(
670        "vitesse-dark",
671        ThemeDefinition {
672            id: "vitesse-dark",
673            label: "Vitesse Dark",
674            palette: ThemePalette {
675                primary_accent: RgbColor(0xDB, 0xD7, 0xCA),
676                background: RgbColor(0x18, 0x18, 0x18),
677                foreground: RgbColor(0xDB, 0xD7, 0xCA),
678                secondary_accent: RgbColor(0x4D, 0x93, 0x8F),
679                alert: RgbColor(0xB6, 0x6F, 0x6F),
680                logo_accent: RgbColor(0xFF, 0xA6, 0x57),
681            },
682        },
683    );
684    map.insert(
685        "vitesse-dark-soft",
686        ThemeDefinition {
687            id: "vitesse-dark-soft",
688            label: "Vitesse Dark Soft",
689            palette: ThemePalette {
690                primary_accent: RgbColor(0xDB, 0xD7, 0xCA),
691                background: RgbColor(0x20, 0x1F, 0x1F),
692                foreground: RgbColor(0xDB, 0xD7, 0xCA),
693                secondary_accent: RgbColor(0x4D, 0x93, 0x8F),
694                alert: RgbColor(0xBA, 0x77, 0x77),
695                logo_accent: RgbColor(0xFF, 0xA6, 0x57),
696            },
697        },
698    );
699    map.insert(
700        "vitesse-light",
701        ThemeDefinition {
702            id: "vitesse-light",
703            label: "Vitesse Light",
704            palette: ThemePalette {
705                primary_accent: RgbColor(0x39, 0x3A, 0x34),
706                background: RgbColor(0xF6, 0xF1, 0xE5),
707                foreground: RgbColor(0x39, 0x3A, 0x34),
708                secondary_accent: RgbColor(0x1C, 0x6B, 0x48),
709                alert: RgbColor(0xA4, 0x55, 0x55),
710                logo_accent: RgbColor(0x97, 0x60, 0x00),
711            },
712        },
713    );
714    map.insert(
715        "vitesse-light-soft",
716        ThemeDefinition {
717            id: "vitesse-light-soft",
718            label: "Vitesse Light Soft",
719            palette: ThemePalette {
720                primary_accent: RgbColor(0x39, 0x3A, 0x34),
721                background: RgbColor(0xFA, 0xF5, 0xEB),
722                foreground: RgbColor(0x39, 0x3A, 0x34),
723                secondary_accent: RgbColor(0x1C, 0x6B, 0x48),
724                alert: RgbColor(0xA8, 0x57, 0x57),
725                logo_accent: RgbColor(0x9B, 0x63, 0x00),
726            },
727        },
728    );
729    map.insert(
730        "homebrew",
731        ThemeDefinition {
732            id: "homebrew",
733            label: "Homebrew",
734            palette: ThemePalette {
735                primary_accent: RgbColor(0xCC, 0xCC, 0xCC),
736                background: RgbColor(0x00, 0x00, 0x00),
737                foreground: RgbColor(0xCC, 0xCC, 0xCC),
738                secondary_accent: RgbColor(0x00, 0x99, 0xCC),
739                alert: RgbColor(0xD1, 0x45, 0x45),
740                logo_accent: RgbColor(0xCC, 0x99, 0x00),
741            },
742        },
743    );
744    map.insert(
745        "man-page",
746        ThemeDefinition {
747            id: "man-page",
748            label: "Man Page",
749            palette: ThemePalette {
750                primary_accent: RgbColor(0x00, 0x00, 0x00),
751                background: RgbColor(0xF2, 0xF2, 0xE6),
752                foreground: RgbColor(0x00, 0x00, 0x00),
753                secondary_accent: RgbColor(0x00, 0x66, 0x66),
754                alert: RgbColor(0x99, 0x00, 0x00),
755                logo_accent: RgbColor(0x7A, 0x5D, 0x00),
756            },
757        },
758    );
759    map.insert(
760        "framer",
761        ThemeDefinition {
762            id: "framer",
763            label: "Framer",
764            palette: ThemePalette {
765                primary_accent: RgbColor(0xF5, 0xF5, 0xF5),
766                background: RgbColor(0x10, 0x10, 0x10),
767                foreground: RgbColor(0xF5, 0xF5, 0xF5),
768                secondary_accent: RgbColor(0x81, 0x62, 0xFF),
769                alert: RgbColor(0xFF, 0x5C, 0x5C),
770                logo_accent: RgbColor(0xFF, 0xAF, 0x3F),
771            },
772        },
773    );
774    map.insert(
775        "espresso",
776        ThemeDefinition {
777            id: "espresso",
778            label: "Espresso",
779            palette: ThemePalette {
780                primary_accent: RgbColor(0xD0, 0xC8, 0xB8),
781                background: RgbColor(0x2A, 0x21, 0x1C),
782                foreground: RgbColor(0xD0, 0xC8, 0xB8),
783                secondary_accent: RgbColor(0x6F, 0xB3, 0xC2),
784                alert: RgbColor(0xD8, 0x6A, 0x6A),
785                logo_accent: RgbColor(0xC8, 0x92, 0x2D),
786            },
787        },
788    );
789    map.insert(
790        "adventure-time",
791        ThemeDefinition {
792            id: "adventure-time",
793            label: "Adventure Time",
794            palette: ThemePalette {
795                primary_accent: RgbColor(0xF8, 0xF8, 0xF8),
796                background: RgbColor(0x1F, 0x1D, 0x45),
797                foreground: RgbColor(0xF8, 0xF8, 0xF8),
798                secondary_accent: RgbColor(0x8A, 0x9B, 0xFF),
799                alert: RgbColor(0xDB, 0x67, 0x67),
800                logo_accent: RgbColor(0xF5, 0xB8, 0x4A),
801            },
802        },
803    );
804    map.insert(
805        "afterglow",
806        ThemeDefinition {
807            id: "afterglow",
808            label: "Afterglow",
809            palette: ThemePalette {
810                primary_accent: RgbColor(0xD6, 0xD6, 0xD6),
811                background: RgbColor(0x22, 0x22, 0x22),
812                foreground: RgbColor(0xD6, 0xD6, 0xD6),
813                secondary_accent: RgbColor(0x7F, 0xB0, 0xD0),
814                alert: RgbColor(0xE5, 0x72, 0x72),
815                logo_accent: RgbColor(0xD0, 0xA0, 0x60),
816            },
817        },
818    );
819    map.insert(
820        "mono",
821        ThemeDefinition {
822            id: "mono",
823            label: "Mono",
824            palette: ThemePalette {
825                primary_accent: RgbColor(0xFF, 0xFF, 0xFF),
826                background: RgbColor(0x00, 0x00, 0x00),
827                foreground: RgbColor(0xDB, 0xD7, 0xCA),
828                secondary_accent: RgbColor(0xBB, 0xBB, 0xBB),
829                alert: RgbColor(0xFF, 0xFF, 0xFF),
830                logo_accent: RgbColor(0xFF, 0xFF, 0xFF),
831            },
832        },
833    );
834    map.insert(
835        "ansi-classic",
836        ThemeDefinition {
837            id: "ansi-classic",
838            label: "ANSI Classic",
839            palette: ThemePalette {
840                primary_accent: RgbColor(0xC0, 0xC0, 0xC0),
841                background: RgbColor(0x00, 0x00, 0x00),
842                foreground: RgbColor(0xC0, 0xC0, 0xC0),
843                secondary_accent: RgbColor(0x00, 0xAA, 0xAA),
844                alert: RgbColor(0xC5, 0x52, 0x52),
845                logo_accent: RgbColor(0xAA, 0xAA, 0x00),
846            },
847        },
848    );
849
850    map.insert(
851        "nord",
852        ThemeDefinition {
853            id: "nord",
854            label: "Nord",
855            palette: ThemePalette {
856                primary_accent: RgbColor(0x88, 0xC0, 0xD0),
857                background: RgbColor(0x2E, 0x34, 0x40),
858                foreground: RgbColor(0xEC, 0xEF, 0xF4),
859                secondary_accent: RgbColor(0x81, 0xA1, 0xC1),
860                alert: RgbColor(0xCF, 0x88, 0x8F),
861                logo_accent: RgbColor(0xA3, 0xBE, 0x8C),
862            },
863        },
864    );
865    map.insert(
866        "nord-light",
867        ThemeDefinition {
868            id: "nord-light",
869            label: "Nord Light",
870            palette: ThemePalette {
871                primary_accent: RgbColor(0x52, 0x6E, 0x91),
872                background: RgbColor(0xEC, 0xEF, 0xF4),
873                foreground: RgbColor(0x2E, 0x34, 0x40),
874                secondary_accent: RgbColor(0x4C, 0x56, 0x6A),
875                alert: RgbColor(0x9B, 0x56, 0x60),
876                logo_accent: RgbColor(0x5D, 0x6E, 0x55),
877            },
878        },
879    );
880    map.insert(
881        "oxi",
882        ThemeDefinition {
883            id: "oxi",
884            label: "Oxi",
885            palette: ThemePalette {
886                // Restrained terminal palette: a soft slate canvas, cool ink,
887                // neutral chrome, and one teal interaction accent.  Hue is
888                // reserved for meaning (teal = active/interactive, red =
889                // failure); branding deliberately does not add a second color.
890                // The derivation pipeline computes the remaining styles with
891                // contrast guarantees. See docs/oxi-design-system-tui.md.
892                primary_accent: RgbColor(0x8B, 0x94, 0x9E), // #8b949e slate chrome
893                background: RgbColor(0x0D, 0x11, 0x17),     // #0d1117 soft slate canvas
894                foreground: RgbColor(0xE6, 0xED, 0xF3),     // #e6edf3 cool ink
895                secondary_accent: RgbColor(0x58, 0xA6, 0xA6), // #58a6a6 teal interaction
896                alert: RgbColor(0xE0, 0x6C, 0x75),          // #e06c75 restrained error
897                logo_accent: RgbColor(0x58, 0xA6, 0xA6),    // #58a6a6 no separate brand hue
898            },
899        },
900    );
901    map.insert(
902        "oxide-dark",
903        ThemeDefinition {
904            id: "oxide-dark",
905            label: "Oxide Dark",
906            palette: ThemePalette {
907                // GrokNight-inspired: neutral gray base + restrained TokyoNight
908                // accents. Structural elements read as calm gray; color is
909                // reserved for semantic meaning (blue info, red error, magenta
910                // branding). No warm/brown tones.
911                primary_accent: RgbColor(0xC8, 0xC8, 0xC8), // #c8c8c8 calm gray chrome
912                background: RgbColor(0x14, 0x14, 0x14),     // #141414 neutral dark gray
913                foreground: RgbColor(0xE1, 0xE1, 0xE1),     // #e1e1e1 neutral white
914                secondary_accent: RgbColor(0x7A, 0xA2, 0xF7), // #7aa2f7 blue (info/structure)
915                alert: RgbColor(0xF7, 0x76, 0x8E),          // #f7768e red (errors)
916                logo_accent: RgbColor(0xBB, 0x9A, 0xF7),    // #bb9af7 magenta (branding)
917            },
918        },
919    );
920    register_catppuccin_themes(&mut map);
921    map
922});
923
924fn register_catppuccin_themes(map: &mut HashMap<&'static str, ThemeDefinition>) {
925    for &flavor_kind in CATPPUCCIN_FLAVORS {
926        let flavor = flavor_kind.flavor();
927        map.insert(
928            flavor_kind.id(),
929            ThemeDefinition {
930                id: flavor_kind.id(),
931                label: flavor_kind.label(),
932                palette: catppuccin_palette(flavor_kind, flavor),
933            },
934        );
935    }
936}
937
938fn catppuccin_palette(kind: CatppuccinFlavorKind, flavor: catppuccin::Flavor) -> ThemePalette {
939    let colors = flavor.colors;
940    if matches!(kind, CatppuccinFlavorKind::Latte) {
941        let darken = |c: catppuccin::Color| -> RgbColor {
942            RgbColor(
943                c.rgb.r.saturating_sub(32),
944                c.rgb.g.saturating_sub(32),
945                c.rgb.b.saturating_sub(32),
946            )
947        };
948        // Latte (light): use darker accents for WCAG AA contrast on light background
949        ThemePalette {
950            primary_accent: catppuccin_rgb(colors.mauve),
951            background: catppuccin_rgb(colors.base),
952            foreground: catppuccin_rgb(colors.text),
953            secondary_accent: darken(colors.blue),
954            alert: catppuccin_rgb(colors.red),
955            logo_accent: darken(colors.maroon),
956        }
957    } else {
958        ThemePalette {
959            primary_accent: catppuccin_rgb(colors.lavender),
960            background: catppuccin_rgb(colors.base),
961            foreground: catppuccin_rgb(colors.text),
962            secondary_accent: catppuccin_rgb(colors.sapphire),
963            alert: catppuccin_rgb(colors.red),
964            logo_accent: catppuccin_rgb(colors.peach),
965        }
966    }
967}
968
969fn catppuccin_rgb(color: catppuccin::Color) -> RgbColor {
970    RgbColor(color.rgb.r, color.rgb.g, color.rgb.b)
971}
972
973pub(crate) fn theme_definition(theme_id: &str) -> Option<&'static ThemeDefinition> {
974    REGISTRY.get(theme_id)
975}
976
977#[cfg(test)]
978pub(crate) fn all_theme_definitions() -> &'static HashMap<&'static str, ThemeDefinition> {
979    &REGISTRY
980}
981
982/// Return the list of built-in theme identifiers in sorted order.
983pub fn available_themes() -> Vec<&'static str> {
984    let mut keys: Vec<&'static str> = REGISTRY.keys().copied().collect();
985    keys.sort_unstable();
986    keys
987}
988
989/// Return the display label for a built-in theme.
990pub fn theme_label(theme_id: &str) -> Option<&'static str> {
991    theme_definition(theme_id).map(|definition| definition.label)
992}
993
994fn suite_id_for_theme(theme_id: &str) -> Option<&'static str> {
995    if theme_id.starts_with("nord") {
996        Some("nord")
997    } else if theme_id.starts_with("catppuccin-") {
998        Some("catppuccin")
999    } else if theme_id.starts_with("vitesse-") {
1000        Some("vitesse")
1001    } else if theme_id.starts_with("ciapre-") {
1002        Some("ciapre")
1003    } else if theme_id.starts_with("oxide-") {
1004        Some("oxide")
1005    } else if theme_id == "mono" {
1006        Some("mono")
1007    } else if theme_id == "oxi" {
1008        Some("oxi")
1009    } else {
1010        None
1011    }
1012}
1013
1014fn suite_label(suite_id: &str) -> Option<&'static str> {
1015    match suite_id {
1016        "nord" => Some("Nord"),
1017        "catppuccin" => Some("Catppuccin"),
1018        "vitesse" => Some("Vitesse"),
1019        "oxide" => Some("Oxide"),
1020        "oxi" => Some("Oxi"),
1021        "ciapre" => Some("Ciapre"),
1022        "mono" => Some("Mono"),
1023        _ => None,
1024    }
1025}
1026
1027/// Return the logical theme suite identifier for a built-in theme.
1028pub fn theme_suite_id(theme_id: &str) -> Option<&'static str> {
1029    suite_id_for_theme(theme_id)
1030}
1031
1032/// Return the logical theme suite label for a built-in theme.
1033pub fn theme_suite_label(theme_id: &str) -> Option<&'static str> {
1034    suite_id_for_theme(theme_id).and_then(suite_label)
1035}
1036
1037/// Return the built-in theme suites and their member theme identifiers.
1038pub fn available_theme_suites() -> Vec<ThemeSuite> {
1039    const ORDER: &[&str] = &[
1040        "oxi",
1041        "oxide",
1042        "ciapre",
1043        "vitesse",
1044        "catppuccin",
1045        "mono",
1046        "nord",
1047    ];
1048
1049    ORDER
1050        .iter()
1051        .filter_map(|suite_id| {
1052            let mut theme_ids: Vec<&'static str> = available_themes()
1053                .into_iter()
1054                .filter(|theme_id| suite_id_for_theme(theme_id) == Some(*suite_id))
1055                .collect();
1056            if theme_ids.is_empty() {
1057                return None;
1058            }
1059            theme_ids.sort_unstable();
1060            Some(ThemeSuite {
1061                id: suite_id,
1062                label: suite_label(suite_id)?,
1063                theme_ids,
1064            })
1065        })
1066        .collect()
1067}